Title: [210938] trunk/Websites/perf.webkit.org
Revision
210938
Author
[email protected]
Date
2017-01-19 14:58:46 -0800 (Thu, 19 Jan 2017)

Log Message

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.

Modified Paths

Added Paths

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>
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to