Diff
Modified: trunk/Websites/perf.webkit.org/ChangeLog (210937 => 210938)
--- trunk/Websites/perf.webkit.org/ChangeLog 2017-01-19 21:35:10 UTC (rev 210937)
+++ trunk/Websites/perf.webkit.org/ChangeLog 2017-01-19 22:58:46 UTC (rev 210938)
@@ -1,3 +1,42 @@
+2017-01-18 Ryosuke Niwa <[email protected]>
+
+ Add a mechanism to dispatch and listen to an action
+ https://bugs.webkit.org/show_bug.cgi?id=167191
+
+ Reviewed by Antti Koivisto.
+
+ Added the notion of an action to components. Like DOM events, it can be dispatched or listen to.
+
+ Also added ComponentBase.prototype.part which finds a sub-component inside a component's shadow tree,
+ and made ComponentBase.prototype.content take an id to find an element that matches it.
+
+ * browser-tests/close-button-tests.js: Added. Tests for CloseButton.
+ * browser-tests/component-base-tests.js: Added tests for ComponentBase's part(~), content(id), dispatchEvent.
+ * browser-tests/index.html:
+ * public/v3/components/base.js:
+ (ComponentBase): Added this._actionCallbacks, which is a map of an action name to a callback to be invoked.
+ (ComponentBase.prototype.content): Return an element of the given id if one is specified.
+ (ComponentBase.prototype.part): Find a component whose element has the matching id.
+ (ComponentBase.prototype.dispatchAction): Added.
+ (ComponentBase.prototype.listenToAction): Added.
+ (ComponentBase.prototype._ensureShadowTree): Call didConstructShadowTree.
+ (ComponentBase.prototype.didConstructShadowTree): Added.
+ (ComponentBase.prototype._recursivelyReplaceUnknownElementsByComponents): Copy attributes when instantiating
+ an element for a component when the browser doesn't support custom elements API.
+ (ComponentBase.createLink):
+ (ComponentBase.prototype.createEventHandler): Added.
+ (ComponentBase.createEventHandler): Renamed from createActionHandler.
+ * public/v3/components/button-base.js:
+ (ButtonBase.prototype.didConstructShadowTree): Added. Dispatch "activate" action when the button is clicked.
+ (ButtonBase.prototype.setCallback): Deleted.
+ (ButtonBase.htmlTemplate): Use id instead of class so that this.content() can find it.
+ (ButtonBase.cssTemplate): Updated style rules.
+ * public/v3/pages/chart-pane.js:
+ (ChartPane):
+ (ChartPane.prototype.didConstructShadowTree): Added. Listen to "activate" action on the close button.
+ (ChartPane.prototype.render): Fixed a bug that we were never calling enqueueToRender on the close button.
+ (ChartPane.htmlTemplate): Add the id on the close button.
+
2017-01-18 Dewei Zhu <[email protected]>
'buildbot-syncer.js' should be able to determine force build argument from a list of possible repositories.
Added: trunk/Websites/perf.webkit.org/browser-tests/close-button-tests.js (0 => 210938)
--- trunk/Websites/perf.webkit.org/browser-tests/close-button-tests.js (rev 0)
+++ trunk/Websites/perf.webkit.org/browser-tests/close-button-tests.js 2017-01-19 22:58:46 UTC (rev 210938)
@@ -0,0 +1,25 @@
+
+describe('CloseButton', () => {
+ const scripts = ['instrumentation.js', 'components/base.js', 'components/button-base.js', 'components/close-button.js'];
+
+ it('must dispatch "activate" action when the anchor is clicked', () => {
+ const context = new BrowsingContext();
+ return context.importScripts(scripts, 'CloseButton').then((CloseButton) => {
+ const closeButton = new CloseButton;
+ context.document.body.appendChild(closeButton.element());
+
+ closeButton.content().querySelector('a').click();
+
+ let activateCount = 0;
+ closeButton.listenToAction('activate', () => {
+ activateCount++;
+ });
+ expect(activateCount).toBe(0);
+ closeButton.content().querySelector('a').click();
+ expect(activateCount).toBe(1);
+ closeButton.content().querySelector('a').click();
+ expect(activateCount).toBe(2);
+ });
+ });
+
+});
Modified: trunk/Websites/perf.webkit.org/browser-tests/component-base-tests.js (210937 => 210938)
--- trunk/Websites/perf.webkit.org/browser-tests/component-base-tests.js 2017-01-19 21:35:10 UTC (rev 210937)
+++ trunk/Websites/perf.webkit.org/browser-tests/component-base-tests.js 2017-01-19 22:58:46 UTC (rev 210938)
@@ -7,7 +7,7 @@
return context.importScript('components/base.js', 'ComponentBase').then((ComponentBase) => {
class SomeComponent extends ComponentBase { }
if (options.htmlTemplate)
- SomeComponent.htmlTemplate = () => { return '<div style="height: 10px;"></div>'; };
+ SomeComponent.htmlTemplate = () => { return '<div id="div" style="height: 10px;"></div>'; };
if (options.cssTemplate)
SomeComponent.cssTemplate = () => { return ':host { height: 10px; }'; };
@@ -87,8 +87,84 @@
expect(instance.content()).toBe(instance.content());
});
});
+
+ it('must return the element matching the id if an id is specified', () => {
+ return new BrowsingContext().importScripts(['instrumentation.js', 'components/base.js'], 'ComponentBase').then((ComponentBase) => {
+ class SomeComponent extends ComponentBase {
+ static htmlTemplate() { return '<div id="part1" title="foo"></div><div id="part1"></div>'; }
+ }
+ ComponentBase.defineElement('some-component', SomeComponent);
+
+ const instance = new SomeComponent;
+ const part1 = instance.content('part1');
+ expect(part1.localName).toBe('div');
+ expect(part1.title).toBe('foo');
+ expect(instance.content('part2')).toBe(null);
+ });
+ });
});
+ describe('part()', () => {
+ it('must create shadow tree', () => {
+ return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) => {
+ instance.part('foo');
+ expect(hasShadowTree()).toBe(true);
+ });
+ });
+
+ it('must return the component matching the id if an id is specified', () => {
+ return new BrowsingContext().importScripts(['instrumentation.js', 'components/base.js'], 'ComponentBase').then((ComponentBase) => {
+ class SomeComponent extends ComponentBase { }
+ ComponentBase.defineElement('some-component', SomeComponent);
+
+ class OtherComponent extends ComponentBase {
+ static htmlTemplate() { return '<some-component id="foo"></some-component>'; }
+ }
+ ComponentBase.defineElement('other-component', OtherComponent);
+
+ const otherComponent = new OtherComponent;
+ const someComponent = otherComponent.part('foo');
+ expect(someComponent).toBeA(SomeComponent);
+ expect(someComponent.element().id).toBe('foo');
+ expect(otherComponent.part('foo')).toBe(someComponent);
+ expect(otherComponent.part('bar')).toBe(null);
+ });
+ });
+ });
+
+ describe('dispatchAction()', () => {
+ it('must invoke a callback specified in listenToAction', () => {
+ return new BrowsingContext().importScripts(['instrumentation.js', 'components/base.js'], 'ComponentBase').then((ComponentBase) => {
+ class SomeComponent extends ComponentBase { }
+ ComponentBase.defineElement('some-component', SomeComponent);
+
+ const instance = new SomeComponent;
+
+ const calls = [];
+ instance.listenToAction('action', (...args) => {
+ calls.push(args);
+ });
+ const object = {'foo': 1};
+ instance.dispatchAction('action', 'bar', object, 5);
+
+ expect(calls.length).toBe(1);
+ expect(calls[0][0]).toBe('bar');
+ expect(calls[0][1]).toBe(object);
+ expect(calls[0][2]).toBe(5);
+ });
+ });
+
+ it('must not do anything when there are no callbacks', () => {
+ return new BrowsingContext().importScripts(['instrumentation.js', 'components/base.js'], 'ComponentBase').then((ComponentBase) => {
+ class SomeComponent extends ComponentBase { }
+ ComponentBase.defineElement('some-component', SomeComponent);
+
+ const object = {'foo': 1};
+ (new SomeComponent).dispatchAction('action', 'bar', object, 5);
+ });
+ });
+ });
+
describe('enqueueToRender()', () => {
it('must not immediately call render()', () => {
const context = new BrowsingContext();
@@ -298,6 +374,36 @@
expect(hasShadowTree()).toBe(true);
}, {htmlTemplate: false, cssTemplate: true});
});
+
+ it('must invoke didConstructShadowTree after creating the shadow tree', () => {
+ const context = new BrowsingContext();
+ return context.importScripts(['instrumentation.js', 'components/base.js'], 'ComponentBase').then((ComponentBase) => {
+ let didConstructShadowTreeCount = 0;
+ let htmlTemplateCount = 0;
+
+ class SomeComponent extends ComponentBase {
+ didConstructShadowTree()
+ {
+ expect(this.content()).toBeA(context.global.ShadowRoot);
+ didConstructShadowTreeCount++;
+ }
+
+ static htmlTemplate()
+ {
+ htmlTemplateCount++;
+ return '';
+ }
+ }
+ ComponentBase.defineElement('some-component', SomeComponent);
+
+ const instance = new SomeComponent;
+ expect(didConstructShadowTreeCount).toBe(0);
+ expect(htmlTemplateCount).toBe(0);
+ instance.render();
+ expect(didConstructShadowTreeCount).toBe(1);
+ expect(htmlTemplateCount).toBe(1);
+ });
+ });
});
describe('defineElement()', () => {
Modified: trunk/Websites/perf.webkit.org/browser-tests/index.html (210937 => 210938)
--- trunk/Websites/perf.webkit.org/browser-tests/index.html 2017-01-19 21:35:10 UTC (rev 210937)
+++ trunk/Websites/perf.webkit.org/browser-tests/index.html 2017-01-19 22:58:46 UTC (rev 210938)
@@ -1,6 +1,7 @@
<!DOCTYPE html>
<html>
<head>
+<title>In-Browser Tests for Performance Dashboard</title>
<link rel="stylesheet" href=""
<script src=""
<script src=""
@@ -13,6 +14,7 @@
<body>
<div id="mocha"></div>
<script src=""
+<script src=""
<script>
afterEach(() => {
@@ -47,6 +49,7 @@
script.addEventListener('load', resolve);
script.addEventListener('error', reject);
script.src = '' + path;
+ script.async = false;
doc.body.appendChild(script);
});
})).then(() => {
Modified: trunk/Websites/perf.webkit.org/public/v3/components/base.js (210937 => 210938)
--- trunk/Websites/perf.webkit.org/public/v3/components/base.js 2017-01-19 21:35:10 UTC (rev 210937)
+++ trunk/Websites/perf.webkit.org/public/v3/components/base.js 2017-01-19 22:58:46 UTC (rev 210938)
@@ -15,6 +15,7 @@
this._element = element;
this._shadow = null;
+ this._actionCallbacks = new Map;
if (!window.customElements && new.target.enqueueToRenderOnResize)
ComponentBase._connectedComponentToRenderOnResize(this);
@@ -21,12 +22,37 @@
}
element() { return this._element; }
- content()
+ content(id = null)
{
this._ensureShadowTree();
+ if (this._shadow && id != null)
+ return this._shadow.getElementById(id);
return this._shadow;
}
+ part(id)
+ {
+ this._ensureShadowTree();
+ if (!this._shadow)
+ return null;
+ const part = this._shadow.getElementById(id);
+ if (!part)
+ return null;
+ return part.component();
+ }
+
+ dispatchAction(actionName, ...args)
+ {
+ const callback = this._actionCallbacks.get(actionName);
+ if (callback)
+ callback.apply(this, args);
+ }
+
+ listenToAction(actionName, callback)
+ {
+ this._actionCallbacks.set(actionName, callback);
+ }
+
render() { this._ensureShadowTree(); }
enqueueToRender()
@@ -112,10 +138,12 @@
style.textContent = newTarget.cssTemplate();
shadow.appendChild(style);
}
-
this._shadow = shadow;
+ this.didConstructShadowTree();
}
+ didConstructShadowTree() { }
+
_recursivelyReplaceUnknownElementsByComponents(parent)
{
let nextSibling;
@@ -125,6 +153,12 @@
if (elementInterface) {
const component = new elementInterface();
const newChild = component.element();
+
+ for (let i = 0; i < child.attributes.length; i++) {
+ const attr = child.attributes[i];
+ newChild.setAttribute(attr.name, attr.value);
+ }
+
parent.replaceChild(newChild, child);
child = newChild;
}
@@ -231,7 +265,7 @@
if (typeof(callback) === 'string')
attributes['href'] = callback;
else
- attributes['onclick'] = ComponentBase.createActionHandler(callback);
+ attributes['onclick'] = ComponentBase.createEventHandler(callback);
if (isExternal)
attributes['target'] = '_blank';
@@ -238,7 +272,8 @@
return ComponentBase.createElement('a', attributes, content);
}
- static createActionHandler(callback)
+ createEventHandler(callback) { return ComponentBase.createEventHandler(callback); }
+ static createEventHandler(callback)
{
return function (event) {
event.preventDefault();
Modified: trunk/Websites/perf.webkit.org/public/v3/components/button-base.js (210937 => 210938)
--- trunk/Websites/perf.webkit.org/public/v3/components/button-base.js 2017-01-19 21:35:10 UTC (rev 210937)
+++ trunk/Websites/perf.webkit.org/public/v3/components/button-base.js 2017-01-19 22:58:46 UTC (rev 210938)
@@ -1,18 +1,15 @@
class ButtonBase extends ComponentBase {
- constructor(name)
+ didConstructShadowTree()
{
- super(name);
+ this.content('button').addEventListener('click', this.createEventHandler(() => {
+ this.dispatchAction('activate');
+ }));
}
- setCallback(callback)
- {
- this.content().querySelector('a').addEventListener('click', ComponentBase.createActionHandler(callback));
- }
-
static htmlTemplate()
{
- return `<a class="button" href="" viewBox="0 0 100 100">${this.buttonContent()}</svg></a>`;
+ return `<a id="button" href="" viewBox="0 0 100 100">${this.buttonContent()}</svg></a>`;
}
static buttonContent() { throw 'NotImplemented'; }
@@ -28,18 +25,18 @@
height: ${sizeFactor}rem;
}
- .button {
+ a {
vertical-align: bottom;
display: block;
opacity: 0.3;
}
- .button svg {
- display: block;
+ a:hover {
+ opacity: 0.6;
}
- .button:hover {
- opacity: 0.6;
+ svg {
+ display: block;
}
`;
}
Modified: trunk/Websites/perf.webkit.org/public/v3/pages/chart-pane.js (210937 => 210938)
--- trunk/Websites/perf.webkit.org/public/v3/pages/chart-pane.js 2017-01-19 21:35:10 UTC (rev 210937)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/chart-pane.js 2017-01-19 22:58:46 UTC (rev 210938)
@@ -77,11 +77,16 @@
this._trendLineVersion = 0;
this._renderedTrendLineOptions = false;
- this.content().querySelector('close-button').component().setCallback(chartsPage.closePane.bind(chartsPage, this));
-
this.configure(platformId, metricId);
}
+ didConstructShadowTree()
+ {
+ this.part('close').listenToAction('activate', () => {
+ this._chartsPage.closePane(this);
+ })
+ }
+
serializeState()
{
var state = [this._platformId, this._metricId];
@@ -252,6 +257,8 @@
var link = ComponentBase.createLink;
var self = this;
+ this.part('close').enqueueToRender();
+
if (this._chartsPage.canBreakdown(platform, metric)) {
actions.push(element('li', link('Breakdown', function () {
self._chartsPage.insertBreakdownPanesAfter(platform, metric, self);
@@ -514,7 +521,7 @@
<h2 class="chart-pane-title">-</h2>
<nav class="chart-pane-actions">
<ul>
- <li class="close"><close-button></close-button></li>
+ <li><close-button id="close"></close-button></li>
</ul>
<ul class="chart-pane-action-buttons buttoned-toolbar"></ul>
<ul class="chart-pane-alternative-platforms popover" style="display:none"></ul>