<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[210783] trunk/Websites/perf.webkit.org</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/210783">210783</a></dd>
<dt>Author</dt> <dd>rniwa@webkit.org</dd>
<dt>Date</dt> <dd>2017-01-15 16:12:22 -0800 (Sun, 15 Jan 2017)</dd>
</dl>

<h3>Log Message</h3>
<pre>Adopt custom elements API in perf dashboard
https://bugs.webkit.org/show_bug.cgi?id=167045

Reviewed by Darin Adler.

Adopt custom elements API in ComponentBase, and create the shadow tree lazily in content() and render()
instead of eagerly creating it inside the constructor.

For now, create a separate element class for each component in ComponentBase.defineElement instead of
making ComponentBase inherit from HTMLElement to preserve the semantics we have as well as to test
the boundaries of what custom elements API allows for framework authors.

In order to ensure one-to-one correspondence between elements and their components, we use a static map,
ComponentBase._currentlyConstructedByInterface, to remember which element or component is being created
and use that in custom element's constructor to update element.component() and this._element.

Also dropped the support for not having attachShadow as we've shipped this feature in Safari 10.

Finally, added tests to be ran inside a browser to test the front end code in browser-tests.

* browser-tests/component-base-tests.js: Added. Basic tests for ComponentBase.
* browser-tests/index.html: Added.

* public/v3/components/base.js:
(ComponentBase): Don't create the shadow tree. Use the currently constructed element as this._element if
there is one (the custom element's constructor is getting called). Otherwise create a new element but
store this component in the map to avoid creating a new component in the custom element's constructor.
(ComponentBase.prototype.content): Lazily create the shadow tree now.
(ComponentBase.prototype.render): Ditto.
(ComponentBase.prototype._ensureShadowTree): Renamed from _constructShadowTree. Dropped the support for
not having shadow DOM API. This is now required. Also use importNode instead of cloneNode in cloning
the template content since the latter would not get upgraded.
(ComponentBase.prototype._recursivelyReplaceUnknownElementsByComponents): Modernized the code. Don't
re-create a component if its element had already been upgraded by its custom element constructor.
(ComponentBase.defineElement): Add this component to the static maps. _componentByName is used by
_recursivelyReplaceUnknownElementsByComponents to instantiate new components in the browsers that don't
support custom elements API and _componentByClass is used by ComponentBase's constructor to lookup the
element name. The latter should go away once all components fully adopt ComponentBase.defineElement.
(ComponentBase.defineElement.elementClass): A class to define a custom element for the component.
We need to reconfigure the property since class's name is not writable but configurable.

* public/v3/components/button-base.js:
(ButtonBase.htmlTemplate): Added. Extracted the common code from CloseButton and WarningIcon.
(ButtonBase.buttonContent): Added. An abstract method overridden by CloseButton and WarningIcon.
(ButtonBase.sizeFactor): Added. Overridden by WarningIcon.
(ButtonBase.cssTemplate): Updated to use :host.
* public/v3/components/close-button.js:
(CloseButton.buttonContent): Renamed from htmlTemplate.
* public/v3/components/spinner-icon.js:
(SpinnerIcon.cssTemplate): Removed webkit prefixed properties, and updated it to animate stroke instead
of opacity to reduce the power usage.
(SpinnerIcon.htmlTemplate): Factored stroke, stroke-width, and stroke-linecap into cssTemplate.
* public/v3/components/warning-icon.js:
(WarningIcon.cssTemplate): Deleted.
(WarningIcon.sizeFactor): Added.
(WarningIcon.buttonContent): Renamed from htmlTemplate.
* public/v3/pages/summary-page.js:
(SummaryPage._constructRatioGraph): Fixed a bug that we were not never calling spinner.updateRendering().
(SummaryPage.prototype._renderCell):</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkWebsitesperfwebkitorgChangeLog">trunk/Websites/perf.webkit.org/ChangeLog</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3componentsbasejs">trunk/Websites/perf.webkit.org/public/v3/components/base.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3componentsbuttonbasejs">trunk/Websites/perf.webkit.org/public/v3/components/button-base.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3componentsclosebuttonjs">trunk/Websites/perf.webkit.org/public/v3/components/close-button.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3componentsspinnericonjs">trunk/Websites/perf.webkit.org/public/v3/components/spinner-icon.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3componentswarningiconjs">trunk/Websites/perf.webkit.org/public/v3/components/warning-icon.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3mainjs">trunk/Websites/perf.webkit.org/public/v3/main.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3pagessummarypagejs">trunk/Websites/perf.webkit.org/public/v3/pages/summary-page.js</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li>trunk/Websites/perf.webkit.org/browser-tests/</li>
<li><a href="#trunkWebsitesperfwebkitorgbrowsertestscomponentbasetestsjs">trunk/Websites/perf.webkit.org/browser-tests/component-base-tests.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgbrowsertestsindexhtml">trunk/Websites/perf.webkit.org/browser-tests/index.html</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkWebsitesperfwebkitorgChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/ChangeLog (210782 => 210783)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/ChangeLog        2017-01-15 22:42:53 UTC (rev 210782)
+++ trunk/Websites/perf.webkit.org/ChangeLog        2017-01-16 00:12:22 UTC (rev 210783)
</span><span class="lines">@@ -1,3 +1,65 @@
</span><ins>+2017-01-12  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
+
+        Adopt custom elements API in perf dashboard
+        https://bugs.webkit.org/show_bug.cgi?id=167045
+
+        Reviewed by Darin Adler.
+
+        Adopt custom elements API in ComponentBase, and create the shadow tree lazily in content() and render()
+        instead of eagerly creating it inside the constructor.
+
+        For now, create a separate element class for each component in ComponentBase.defineElement instead of
+        making ComponentBase inherit from HTMLElement to preserve the semantics we have as well as to test
+        the boundaries of what custom elements API allows for framework authors.
+
+        In order to ensure one-to-one correspondence between elements and their components, we use a static map,
+        ComponentBase._currentlyConstructedByInterface, to remember which element or component is being created
+        and use that in custom element's constructor to update element.component() and this._element.
+
+        Also dropped the support for not having attachShadow as we've shipped this feature in Safari 10.
+
+        Finally, added tests to be ran inside a browser to test the front end code in browser-tests.
+
+        * browser-tests/component-base-tests.js: Added. Basic tests for ComponentBase.
+        * browser-tests/index.html: Added.
+
+        * public/v3/components/base.js:
+        (ComponentBase): Don't create the shadow tree. Use the currently constructed element as this._element if
+        there is one (the custom element's constructor is getting called). Otherwise create a new element but
+        store this component in the map to avoid creating a new component in the custom element's constructor.
+        (ComponentBase.prototype.content): Lazily create the shadow tree now.
+        (ComponentBase.prototype.render): Ditto.
+        (ComponentBase.prototype._ensureShadowTree): Renamed from _constructShadowTree. Dropped the support for
+        not having shadow DOM API. This is now required. Also use importNode instead of cloneNode in cloning
+        the template content since the latter would not get upgraded.
+        (ComponentBase.prototype._recursivelyReplaceUnknownElementsByComponents): Modernized the code. Don't
+        re-create a component if its element had already been upgraded by its custom element constructor.
+        (ComponentBase.defineElement): Add this component to the static maps. _componentByName is used by
+        _recursivelyReplaceUnknownElementsByComponents to instantiate new components in the browsers that don't
+        support custom elements API and _componentByClass is used by ComponentBase's constructor to lookup the
+        element name. The latter should go away once all components fully adopt ComponentBase.defineElement.
+        (ComponentBase.defineElement.elementClass): A class to define a custom element for the component.
+        We need to reconfigure the property since class's name is not writable but configurable.
+
+        * public/v3/components/button-base.js:
+        (ButtonBase.htmlTemplate): Added. Extracted the common code from CloseButton and WarningIcon.
+        (ButtonBase.buttonContent): Added. An abstract method overridden by CloseButton and WarningIcon.
+        (ButtonBase.sizeFactor): Added. Overridden by WarningIcon.
+        (ButtonBase.cssTemplate): Updated to use :host.
+        * public/v3/components/close-button.js:
+        (CloseButton.buttonContent): Renamed from htmlTemplate.
+        * public/v3/components/spinner-icon.js:
+        (SpinnerIcon.cssTemplate): Removed webkit prefixed properties, and updated it to animate stroke instead
+        of opacity to reduce the power usage.
+        (SpinnerIcon.htmlTemplate): Factored stroke, stroke-width, and stroke-linecap into cssTemplate.
+        * public/v3/components/warning-icon.js:
+        (WarningIcon.cssTemplate): Deleted.
+        (WarningIcon.sizeFactor): Added.
+        (WarningIcon.buttonContent): Renamed from htmlTemplate.
+        * public/v3/pages/summary-page.js:
+        (SummaryPage._constructRatioGraph): Fixed a bug that we were not never calling spinner.updateRendering().
+        (SummaryPage.prototype._renderCell):
+
</ins><span class="cx"> 2017-01-13  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
</span><span class="cx"> 
</span><span class="cx">         Instrument calls to render()
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgbrowsertestscomponentbasetestsjs"></a>
<div class="addfile"><h4>Added: trunk/Websites/perf.webkit.org/browser-tests/component-base-tests.js (0 => 210783)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/browser-tests/component-base-tests.js                                (rev 0)
+++ trunk/Websites/perf.webkit.org/browser-tests/component-base-tests.js        2017-01-16 00:12:22 UTC (rev 210783)
</span><span class="lines">@@ -0,0 +1,184 @@
</span><ins>+
+describe('ComponentBase', function() {
+
+    function createTestToCheckExistenceOfShadowTree(callback, options = {htmlTemplate: false, cssTemplate: true})
+    {
+        const context = new BrowsingContext();
+        return context.importScript('../public/v3/components/base.js', 'ComponentBase').then((ComponentBase) =&gt; {
+            class SomeComponent extends ComponentBase { }
+            if (options.htmlTemplate)
+                SomeComponent.htmlTemplate = () =&gt; { return '&lt;div style=&quot;height: 10px;&quot;&gt;&lt;/div&gt;'; };
+            if (options.cssTemplate)
+                SomeComponent.cssTemplate = () =&gt; { return ':host { height: 10px; }'; };
+
+            let instance = new SomeComponent('some-component');
+            instance.element().style.display = 'block';
+            context.document.body.appendChild(instance.element());
+            return callback(instance, () =&gt; { return instance.element().offsetHeight == 10; });
+        });
+    }
+
+    describe('constructor', () =&gt; {
+        it('is a function', () =&gt; {
+            return new BrowsingContext().importScript('../public/v3/components/base.js', 'ComponentBase').then((ComponentBase) =&gt; {
+                expect(ComponentBase).toBeA('function');
+            });
+        });
+
+        it('can be instantiated', () =&gt; {
+            return new BrowsingContext().importScript('../public/v3/components/base.js', 'ComponentBase').then((ComponentBase) =&gt; {
+                let callCount = 0;
+                class SomeComponent extends ComponentBase {
+                    constructor() {
+                        super('some-component');
+                        callCount++;
+                    }
+                }
+                let instance = new SomeComponent;
+                expect(instance).toBeA(ComponentBase);
+                expect(instance).toBeA(SomeComponent);
+                expect(callCount).toBe(1);
+            });
+        });
+
+        it('must not create shadow tree eagerly', () =&gt; {
+            return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) =&gt; {
+                expect(hasShadowTree()).toBe(false);
+            });
+        });
+    });
+
+    describe('element()', () =&gt; {
+        it('must return an element', () =&gt; {
+            const context = new BrowsingContext();
+            return context.importScript('../public/v3/components/base.js', 'ComponentBase').then((ComponentBase) =&gt; {
+                class SomeComponent extends ComponentBase { }
+                let instance = new SomeComponent('some-component');
+                expect(instance.element()).toBeA(context.global.HTMLElement);
+            });
+        });
+
+        it('must return an element whose component() matches the component', () =&gt; {
+            return new BrowsingContext().importScript('../public/v3/components/base.js', 'ComponentBase').then((ComponentBase) =&gt; {
+                class SomeComponent extends ComponentBase { }
+                let instance = new SomeComponent('some-component');
+                expect(instance.element().component()).toBe(instance);
+            });
+        });
+
+        it('must not create shadow tree eagerly', () =&gt; {
+            return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) =&gt; {
+                instance.element();
+                expect(hasShadowTree()).toBe(false);
+            });
+        });
+    });
+
+    describe('content()', () =&gt; {
+        it('must create shadow tree', () =&gt; {
+            return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) =&gt; {
+                instance.content();
+                expect(hasShadowTree()).toBe(true);
+            });
+        });
+
+        it('must return the same shadow tree each time it is called', () =&gt; {
+            return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) =&gt; {
+                expect(instance.content()).toBe(instance.content());
+            });
+        });
+    });
+
+    describe('render()', () =&gt; {
+        it('must create shadow tree', () =&gt; {
+            return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) =&gt; {
+                instance.render();
+                expect(hasShadowTree()).toBe(true);
+            });
+        });
+
+        it('must not create shadow tree when neither htmlTemplate nor cssTemplate are present', () =&gt; {
+            return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) =&gt; {
+                instance.render();
+                expect(hasShadowTree()).toBe(false);
+            }, {htmlTemplate: false, cssTemplate: false});
+        });
+
+        it('must create shadow tree when htmlTemplate is present and cssTemplate is not', () =&gt; {
+            return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) =&gt; {
+                instance.render();
+                expect(hasShadowTree()).toBe(true);
+            }, {htmlTemplate: true, cssTemplate: false});
+        });
+
+        it('must create shadow tree when cssTemplate is present and htmlTemplate is not', () =&gt; {
+            return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) =&gt; {
+                instance.render();
+                expect(hasShadowTree()).toBe(true);
+            }, {htmlTemplate: false, cssTemplate: true});
+        });
+    });
+
+    describe('defineElement()', () =&gt; {
+
+        it('must define a custom element with a class of an appropriate name', () =&gt; {
+            const context = new BrowsingContext();
+            return context.importScript('../public/v3/components/base.js', 'ComponentBase').then((ComponentBase) =&gt; {
+                class SomeComponent extends ComponentBase { }
+                ComponentBase.defineElement('some-component', SomeComponent);
+
+                let elementClass = context.global.customElements.get('some-component');
+                expect(elementClass).toBeA('function');
+                expect(elementClass.name).toBe('SomeComponentElement');
+            });
+        });
+
+        it('must define a custom element that can be instantiated via document.createElement', () =&gt; {
+            const context = new BrowsingContext();
+            return context.importScript('../public/v3/components/base.js', 'ComponentBase').then((ComponentBase) =&gt; {
+                let instances = [];
+                class SomeComponent extends ComponentBase {
+                    constructor() {
+                        super();
+                        instances.push(this);
+                    }
+                }
+                ComponentBase.defineElement('some-component', SomeComponent);
+
+                expect(instances.length).toBe(0);
+                let element = context.document.createElement('some-component');
+                expect(instances.length).toBe(1);
+
+                expect(element).toBeA(context.global.HTMLElement);
+                expect(element.component()).toBe(instances[0]);
+                expect(instances[0].element()).toBe(element);
+                expect(instances.length).toBe(1);
+            });
+        });
+
+        it('must define a custom element that can be instantiated via new', () =&gt; {
+            const context = new BrowsingContext();
+            return context.importScript('../public/v3/components/base.js', 'ComponentBase').then((ComponentBase) =&gt; {
+                let instances = [];
+                class SomeComponent extends ComponentBase {
+                    constructor() {
+                        super();
+                        instances.push(this);
+                    }
+                }
+                ComponentBase.defineElement('some-component', SomeComponent);
+
+                expect(instances.length).toBe(0);
+                let component = new SomeComponent;
+                expect(instances.length).toBe(1);
+
+                expect(component).toBe(instances[0]);
+                expect(component.element()).toBeA(context.global.HTMLElement);
+                expect(component.element().component()).toBe(component);
+                expect(instances.length).toBe(1);
+            });
+        });
+
+    });
+
+});
</ins></span></pre></div>
<a id="trunkWebsitesperfwebkitorgbrowsertestsindexhtml"></a>
<div class="addfile"><h4>Added: trunk/Websites/perf.webkit.org/browser-tests/index.html (0 => 210783)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/browser-tests/index.html                                (rev 0)
+++ trunk/Websites/perf.webkit.org/browser-tests/index.html        2017-01-16 00:12:22 UTC (rev 210783)
</span><span class="lines">@@ -0,0 +1,101 @@
</span><ins>+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;head&gt;
+&lt;link rel=&quot;stylesheet&quot; href=&quot;https://cdn.rawgit.com/mochajs/mocha/2.2.5/mocha.css&quot;&gt;
+&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/mocha/2.2.5/mocha.js&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/expect/1.20.2/expect.min.js&quot;&gt;&lt;/script&gt;
+&lt;script&gt;
+
+mocha.setup('bdd');
+
+&lt;/script&gt;
+&lt;/head&gt;
+&lt;body&gt;
+&lt;div id=&quot;mocha&quot;&gt;&lt;/div&gt;
+&lt;script src=&quot;component-base-tests.js&quot;&gt;&lt;/script&gt;
+&lt;script&gt;
+
+afterEach(() =&gt; {
+    BrowsingContext.cleanup();
+});
+
+class BrowsingContext {
+
+    constructor()
+    {
+        let iframe = document.createElement('iframe');
+        document.body.appendChild(iframe);
+        iframe.style.position = 'absolute';
+        iframe.style.left = '0px';
+        iframe.style.top = '0px';
+        BrowsingContext._iframes.push(iframe);
+
+        this._iframe = iframe;
+        this.symbols = {};
+        this.global = this._iframe.contentWindow;
+        this.document = this._iframe.contentDocument;
+    }
+
+    importScript(path, ...symbolList)
+    {
+        const doc = this._iframe.contentDocument;
+        const global = this._iframe.contentWindow;
+        return new Promise((resolve, reject) =&gt; {
+            let script = doc.createElement('script');
+            script.addEventListener('load', resolve);
+            script.addEventListener('error', reject);
+            script.src = path;
+            doc.body.appendChild(script);
+        }).then(() =&gt; {
+            const script = doc.createElement('script');
+            script.textContent = `window.importedSymbols = [${symbolList.join(', ')}];`;
+            doc.body.appendChild(script);
+
+            const importedSymbols = global.importedSymbols;
+            for (let i = 0; i &lt; symbolList.length; i++)
+                this.symbols[symbolList[i]] = importedSymbols[i];
+
+            return symbolList.length == 1 ? importedSymbols[0] : importedSymbols;
+        });
+    }
+
+    static cleanup()
+    {
+        BrowsingContext._iframes.forEach((iframe) =&gt; { iframe.remove(); });
+        BrowsingContext._iframes = [];
+    }
+
+    static createWithScripts(scriptList)
+    {
+        let iframe = document.createElement('iframe');
+        document.body.appendChild(iframe);
+        const doc = iframe.contentDocument;
+
+        let symbolList = [];
+        return Promise.all(scriptList.map((entry) =&gt; {
+            let [path, ...symbols] = entry;
+            symbolList = symbolList.concat(symbols);
+            return new Promise((resolve, reject) =&gt; {
+                let script = doc.createElement('script');
+                script.addEventListener('load', resolve);
+                script.addEventListener('error', reject);
+                script.src = path;
+                doc.body.appendChild(script);
+            });
+        })).then(() =&gt; {
+            const script = doc.createElement('script');
+            script.textContent = `var symbols = { ${symbolList.join(', ')} };`;
+            doc.body.appendChild(script);
+            return iframe.contentWindow;
+        });
+    }
+}
+BrowsingContext._iframes = [];
+
+mocha.checkLeaks();
+mocha.globals(['expect', 'BrowsingContext']);
+mocha.run();
+
+&lt;/script&gt;
+&lt;/body&gt;
+&lt;/html&gt;
</ins></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3componentsbasejs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/components/base.js (210782 => 210783)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/components/base.js        2017-01-15 22:42:53 UTC (rev 210782)
+++ trunk/Websites/perf.webkit.org/public/v3/components/base.js        2017-01-16 00:12:22 UTC (rev 210783)
</span><span class="lines">@@ -1,17 +1,31 @@
</span><span class="cx"> 
</span><del>-// FIXME: ComponentBase should inherit from HTMLElement when custom elements API is available.
</del><span class="cx"> class ComponentBase {
</span><span class="cx">     constructor(name)
</span><span class="cx">     {
</span><del>-        this._element = document.createElement(name);
-        this._element.component = (function () { return this; }).bind(this);
-        this._shadow = this._constructShadowTree();
</del><ins>+        this._componentName = name || ComponentBase._componentByClass.get(new.target);
+
+        const currentlyConstructed = ComponentBase._currentlyConstructedByInterface;
+        let element = currentlyConstructed.get(new.target);
+        if (!element) {
+            currentlyConstructed.set(new.target, this);
+            element = document.createElement(this._componentName);
+            currentlyConstructed.delete(new.target);
+        }
+        element.component = () =&gt; { return this };
+
+        this._element = element;
+        this._shadow = null;
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     element() { return this._element; }
</span><del>-    content() { return this._shadow; }
-    render() { }
</del><ins>+    content()
+    {
+        this._ensureShadowTree();
+        return this._shadow;
+    }
</ins><span class="cx"> 
</span><ins>+    render() { this._ensureShadowTree(); }
+
</ins><span class="cx">     updateRendering()
</span><span class="cx">     {
</span><span class="cx">         Instrumentation.startMeasuringTime('ComponentBase', 'updateRendering');
</span><span class="lines">@@ -28,52 +42,45 @@
</span><span class="cx">             ComponentBase._addContentToElement(element, content);
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    _constructShadowTree()
</del><ins>+    _ensureShadowTree()
</ins><span class="cx">     {
</span><del>-        var newTarget = this.__proto__.constructor;
</del><ins>+        if (this._shadow)
+            return;
</ins><span class="cx"> 
</span><del>-        var htmlTemplate = newTarget['htmlTemplate'];
-        var cssTemplate = newTarget['cssTemplate'];
</del><ins>+        const newTarget = this.__proto__.constructor;
+        const htmlTemplate = newTarget['htmlTemplate'];
+        const cssTemplate = newTarget['cssTemplate'];
</ins><span class="cx"> 
</span><span class="cx">         if (!htmlTemplate &amp;&amp; !cssTemplate)
</span><del>-            return null;
</del><ins>+            return;
</ins><span class="cx"> 
</span><del>-        var shadow = null;
-        if ('attachShadow' in Element.prototype)
-            shadow = this._element.attachShadow({mode: 'closed'});
-        else if ('createShadowRoot' in Element.prototype) // Legacy Chromium API.
-            shadow = this._element.createShadowRoot();
-        else
-            shadow = this._element;
</del><ins>+        const shadow = this._element.attachShadow({mode: 'closed'});
</ins><span class="cx"> 
</span><span class="cx">         if (htmlTemplate) {
</span><del>-            var template = document.createElement('template');
</del><ins>+            const template = document.createElement('template');
</ins><span class="cx">             template.innerHTML = newTarget.htmlTemplate();
</span><del>-            shadow.appendChild(template.content.cloneNode(true));
</del><ins>+            shadow.appendChild(document.importNode(template.content, true));
</ins><span class="cx">             this._recursivelyReplaceUnknownElementsByComponents(shadow);
</span><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         if (cssTemplate) {
</span><del>-            var style = document.createElement('style');
</del><ins>+            const style = document.createElement('style');
</ins><span class="cx">             style.textContent = newTarget.cssTemplate();
</span><span class="cx">             shadow.appendChild(style);
</span><span class="cx">         }
</span><span class="cx"> 
</span><del>-        return shadow;
</del><ins>+        this._shadow = shadow;
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     _recursivelyReplaceUnknownElementsByComponents(parent)
</span><span class="cx">     {
</span><del>-        if (!ComponentBase._map)
-            return;
-
-        var nextSibling;
-        for (var child = parent.firstChild; child; child = child.nextSibling) {
-            if (child instanceof HTMLUnknownElement || child instanceof HTMLElement) {
-                var elementInterface = ComponentBase._map[child.localName];
</del><ins>+        let nextSibling;
+        for (let child = parent.firstChild; child; child = child.nextSibling) {
+            if (child instanceof HTMLElement &amp;&amp; !child.component) {
+                const elementInterface = ComponentBase._componentByName.get(child.localName);
</ins><span class="cx">                 if (elementInterface) {
</span><del>-                    var component = new elementInterface();
-                    var newChild = component.element();
</del><ins>+                    const component = new elementInterface();
+                    const newChild = component.element();
</ins><span class="cx">                     parent.replaceChild(newChild, child);
</span><span class="cx">                     child = newChild;
</span><span class="cx">                 }
</span><span class="lines">@@ -84,9 +91,30 @@
</span><span class="cx"> 
</span><span class="cx">     static defineElement(name, elementInterface)
</span><span class="cx">     {
</span><del>-        if (!ComponentBase._map)
-            ComponentBase._map = {};
-        ComponentBase._map[name] = elementInterface;
</del><ins>+        ComponentBase._componentByName.set(name, elementInterface);
+        ComponentBase._componentByClass.set(elementInterface, name);
+
+        class elementClass extends HTMLElement {
+            constructor()
+            {
+                super();
+
+                const currentlyConstructed = ComponentBase._currentlyConstructedByInterface;
+                const component = currentlyConstructed.get(elementInterface);
+                if (component)
+                    return; // ComponentBase's constructor will take care of the rest.
+
+                currentlyConstructed.set(elementInterface, this);
+                new elementInterface();
+                currentlyConstructed.delete(elementInterface);
+            }
+        }
+
+        const nameDescriptor = Object.getOwnPropertyDescriptor(elementClass, 'name');
+        nameDescriptor.value = `${elementInterface.name}Element`;
+        Object.defineProperty(elementClass, 'name', nameDescriptor);
+
+        customElements.define(name, elementClass);
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     static createElement(name, attributes, content)
</span><span class="lines">@@ -159,6 +187,10 @@
</span><span class="cx">     }
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+ComponentBase._componentByName = new Map;
+ComponentBase._componentByClass = new Map;
+ComponentBase._currentlyConstructedByInterface = new Map;
+
</ins><span class="cx"> ComponentBase.css = Symbol();
</span><span class="cx"> ComponentBase.html = Symbol();
</span><span class="cx"> ComponentBase.map = {};
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3componentsbuttonbasejs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/components/button-base.js (210782 => 210783)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/components/button-base.js        2017-01-15 22:42:53 UTC (rev 210782)
+++ trunk/Websites/perf.webkit.org/public/v3/components/button-base.js        2017-01-16 00:12:22 UTC (rev 210783)
</span><span class="lines">@@ -10,21 +10,37 @@
</span><span class="cx">         this.content().querySelector('a').addEventListener('click', ComponentBase.createActionHandler(callback));
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    static htmlTemplate()
+    {
+        return `&lt;a class=&quot;button&quot; href=&quot;#&quot;&gt;&lt;svg viewBox=&quot;0 0 100 100&quot;&gt;${this.buttonContent()}&lt;/svg&gt;&lt;/a&gt;`;
+    }
+
+    static buttonContent() { throw 'NotImplemented'; }
+    static sizeFactor() { return 1; }
+
</ins><span class="cx">     static cssTemplate()
</span><span class="cx">     {
</span><ins>+        const sizeFactor = this.sizeFactor();
</ins><span class="cx">         return `
</span><ins>+            :host {
+                display: inline-block;
+                width: ${sizeFactor}rem;
+                height: ${sizeFactor}rem;
+            }
+
</ins><span class="cx">             .button {
</span><span class="cx">                 vertical-align: bottom;
</span><del>-                display: inline-block;
-                width: 1rem;
-                height: 1rem;
</del><ins>+                display: block;
</ins><span class="cx">                 opacity: 0.3;
</span><span class="cx">             }
</span><span class="cx"> 
</span><ins>+            .button svg {
+                display: block;
+            }
+
</ins><span class="cx">             .button:hover {
</span><span class="cx">                 opacity: 0.6;
</span><span class="cx">             }
</span><span class="cx">         `;
</span><span class="cx">     }
</span><del>-
</del><span class="cx"> }
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3componentsclosebuttonjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/components/close-button.js (210782 => 210783)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/components/close-button.js        2017-01-15 22:42:53 UTC (rev 210782)
+++ trunk/Websites/perf.webkit.org/public/v3/components/close-button.js        2017-01-16 00:12:22 UTC (rev 210783)
</span><span class="lines">@@ -5,16 +5,13 @@
</span><span class="cx">         super('close-button');
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    static htmlTemplate()
</del><ins>+    static buttonContent()
</ins><span class="cx">     {
</span><del>-        return `
-            &lt;a class=&quot;button&quot; href=&quot;#&quot;&gt;&lt;svg viewBox=&quot;0 0 100 100&quot;&gt;
-                &lt;g stroke=&quot;black&quot; stroke-width=&quot;10&quot;&gt;
-                    &lt;circle cx=&quot;50&quot; cy=&quot;50&quot; r=&quot;45&quot; fill=&quot;transparent&quot;/&gt;
-                    &lt;polygon points=&quot;30,30 70,70&quot; /&gt;
-                    &lt;polygon points=&quot;30,70 70,30&quot; /&gt;
-                &lt;/g&gt;
-            &lt;/svg&gt;&lt;/a&gt;`;
</del><ins>+        return `&lt;g stroke=&quot;black&quot; stroke-width=&quot;10&quot;&gt;
+            &lt;circle cx=&quot;50&quot; cy=&quot;50&quot; r=&quot;45&quot; fill=&quot;transparent&quot;/&gt;
+            &lt;polygon points=&quot;30,30 70,70&quot; /&gt;
+            &lt;polygon points=&quot;30,70 70,30&quot; /&gt;
+        &lt;/g&gt;`;
</ins><span class="cx">     }
</span><span class="cx"> }
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3componentsspinnericonjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/components/spinner-icon.js (210782 => 210783)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/components/spinner-icon.js        2017-01-15 22:42:53 UTC (rev 210782)
+++ trunk/Websites/perf.webkit.org/public/v3/components/spinner-icon.js        2017-01-16 00:12:22 UTC (rev 210783)
</span><span class="lines">@@ -8,76 +8,43 @@
</span><span class="cx">     static cssTemplate()
</span><span class="cx">     {
</span><span class="cx">         return `
</span><del>-        .spinner {
</del><ins>+        :host {
+            display: inline-block;
</ins><span class="cx">             width: 2rem;
</span><span class="cx">             height: 2rem;
</span><del>-            -webkit-transform: translateZ(0);
</del><ins>+            will-change: opacity; /* Threre is no will-change: stroke. */
</ins><span class="cx">         }
</span><del>-        .spinner line {
</del><ins>+        line {
</ins><span class="cx">             animation: spinner-animation 1.6s linear infinite;
</span><del>-            -webkit-animation: spinner-animation 1.6s linear infinite;
-            opacity: 0.1;
</del><ins>+            stroke: rgb(230, 230, 230);
+            stroke-width: 10;
+            stroke-linecap: round;
</ins><span class="cx">         }
</span><del>-        .spinner line:nth-child(0) {
-            -webkit-animation-delay: 0.0s;
-            animation-delay: 0.0s;
-        }
-        .spinner line:nth-child(1) {
-            -webkit-animation-delay: 0.2s;
-            animation-delay: 0.2s;
-        }
-        .spinner line:nth-child(2) {
-            -webkit-animation-delay: 0.4s;
-            animation-delay: 0.4s;
-        }
-        .spinner line:nth-child(3) {
-            -webkit-animation-delay: 0.6s;
-            animation-delay: 0.6s;
-        }
-        .spinner line:nth-child(4) {
-            -webkit-animation-delay: 0.8s;
-            animation-delay: 0.8s;
-        }
-        .spinner line:nth-child(5) {
-            -webkit-animation-delay: 1s;
-            animation-delay: 1s;
-        }
-        .spinner line:nth-child(6) {
-            -webkit-animation-delay: 1.2s;
-            animation-delay: 1.2s;
-        }
-        .spinner line:nth-child(7) {
-            -webkit-animation-delay: 1.4s;
-            animation-delay: 1.4s;
-        }
-        .spinner line:nth-child(8) {
-            -webkit-animation-delay: 1.6s;
-            animation-delay: 1.6s;
-        }
</del><ins>+        line:nth-child(0) { animation-delay: 0.0s; }
+        line:nth-child(1) { animation-delay: 0.2s; }
+        line:nth-child(2) { animation-delay: 0.4s; }
+        line:nth-child(3) { animation-delay: 0.6s; }
+        line:nth-child(4) { animation-delay: 0.8s; }
+        line:nth-child(5) { animation-delay: 1.0s; }
+        line:nth-child(6) { animation-delay: 1.2s; }
+        line:nth-child(7) { animation-delay: 1.4s; }
</ins><span class="cx">         @keyframes spinner-animation {
</span><del>-            0% { opacity: 0.9; }
-            50% { opacity: 0.1; }
-            100% { opacity: 0.1; }
-        }
-        @-webkit-keyframes spinner-animation {
-            0% { opacity: 0.9; }
-            50% { opacity: 0.1; }
-            100% { opacity: 0.1; }
-        }
-        `;
</del><ins>+            0% { stroke: rgb(25, 25, 25); }
+            50% { stroke: rgb(230, 230, 230); }
+        }`;
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     static htmlTemplate()
</span><span class="cx">     {
</span><span class="cx">         return `&lt;svg class=&quot;spinner&quot; viewBox=&quot;0 0 100 100&quot;&gt;
</span><del>-            &lt;line x1=&quot;10&quot; y1=&quot;50&quot; x2=&quot;30&quot; y2=&quot;50&quot; stroke=&quot;black&quot; stroke-width=&quot;10&quot; stroke-linecap=&quot;round&quot;/&gt;
-            &lt;line x1=&quot;21.72&quot; y1=&quot;21.72&quot; x2=&quot;35.86&quot; y2=&quot;35.86&quot; stroke=&quot;black&quot; stroke-width=&quot;10&quot; stroke-linecap=&quot;round&quot;/&gt;
-            &lt;line x1=&quot;50&quot; y1=&quot;10&quot; x2=&quot;50&quot; y2=&quot;30&quot; stroke=&quot;black&quot; stroke-width=&quot;10&quot; stroke-linecap=&quot;round&quot;/&gt;
-            &lt;line x1=&quot;78.28&quot; y1=&quot;21.72&quot; x2=&quot;64.14&quot; y2=&quot;35.86&quot; stroke=&quot;black&quot; stroke-width=&quot;10&quot; stroke-linecap=&quot;round&quot;/&gt;
-            &lt;line x1=&quot;70&quot; y1=&quot;50&quot; x2=&quot;90&quot; y2=&quot;50&quot; stroke=&quot;black&quot; stroke-width=&quot;10&quot; stroke-linecap=&quot;round&quot;/&gt;
-            &lt;line x1=&quot;65.86&quot; y1=&quot;65.86&quot; x2=&quot;78.28&quot; y2=&quot;78.28&quot; stroke=&quot;black&quot; stroke-width=&quot;10&quot; stroke-linecap=&quot;round&quot;/&gt;
-            &lt;line x1=&quot;50&quot; y1=&quot;70&quot; x2=&quot;50&quot; y2=&quot;90&quot; stroke=&quot;black&quot; stroke-width=&quot;10&quot; stroke-linecap=&quot;round&quot;/&gt;
-            &lt;line x1=&quot;21.72&quot; y1=&quot;78.28&quot; x2=&quot;35.86&quot; y2=&quot;65.86&quot; stroke=&quot;black&quot; stroke-width=&quot;10&quot; stroke-linecap=&quot;round&quot;/&gt;
</del><ins>+            &lt;line x1=&quot;10&quot; y1=&quot;50&quot; x2=&quot;30&quot; y2=&quot;50&quot;/&gt;
+            &lt;line x1=&quot;21.72&quot; y1=&quot;21.72&quot; x2=&quot;35.86&quot; y2=&quot;35.86&quot;/&gt;
+            &lt;line x1=&quot;50&quot; y1=&quot;10&quot; x2=&quot;50&quot; y2=&quot;30&quot;/&gt;
+            &lt;line x1=&quot;78.28&quot; y1=&quot;21.72&quot; x2=&quot;64.14&quot; y2=&quot;35.86&quot;/&gt;
+            &lt;line x1=&quot;70&quot; y1=&quot;50&quot; x2=&quot;90&quot; y2=&quot;50&quot;/&gt;
+            &lt;line x1=&quot;65.86&quot; y1=&quot;65.86&quot; x2=&quot;78.28&quot; y2=&quot;78.28&quot;/&gt;
+            &lt;line x1=&quot;50&quot; y1=&quot;70&quot; x2=&quot;50&quot; y2=&quot;90&quot;/&gt;
+            &lt;line x1=&quot;21.72&quot; y1=&quot;78.28&quot; x2=&quot;35.86&quot; y2=&quot;65.86&quot;/&gt;
</ins><span class="cx">         &lt;/svg&gt;`;
</span><span class="cx">     }
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3componentswarningiconjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/components/warning-icon.js (210782 => 210783)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/components/warning-icon.js        2017-01-15 22:42:53 UTC (rev 210782)
+++ trunk/Websites/perf.webkit.org/public/v3/components/warning-icon.js        2017-01-16 00:12:22 UTC (rev 210783)
</span><span class="lines">@@ -5,29 +5,14 @@
</span><span class="cx">         super('warning-icon');
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    static cssTemplate()
-    {
-        return super.cssTemplate() + `
-            .button {
-                display: block;
-                width: 0.7rem;
-                height: 0.7rem;
-            }
-            .button svg {
-                display: block;
-            }
-        `;
-    }
</del><ins>+    static sizeFactor() { return 0.7; }
</ins><span class="cx"> 
</span><del>-    static htmlTemplate()
</del><ins>+    static buttonContent()
</ins><span class="cx">     {
</span><del>-        return `&lt;a class=&quot;button&quot; href=&quot;#&quot;&gt;&lt;svg viewBox=&quot;0 0 100 100&quot;&gt;
-            &lt;g stroke=&quot;#9f6000&quot; fill=&quot;#9f6000&quot; stroke-width=&quot;7&quot;&gt;
</del><ins>+        return `&lt;g stroke=&quot;#9f6000&quot; fill=&quot;#9f6000&quot; stroke-width=&quot;7&quot;&gt;
</ins><span class="cx">                 &lt;polygon points=&quot;0,0, 100,0, 0,100&quot; /&gt;
</span><del>-            &lt;/g&gt;
-        &lt;/svg&gt;&lt;/a&gt;`;
</del><ins>+            &lt;/g&gt;`;
</ins><span class="cx">     }
</span><del>-
</del><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> ComponentBase.defineElement('warning-icon', WarningIcon);
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3mainjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/main.js (210782 => 210783)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/main.js        2017-01-15 22:42:53 UTC (rev 210782)
+++ trunk/Websites/perf.webkit.org/public/v3/main.js        2017-01-16 00:12:22 UTC (rev 210783)
</span><span class="lines">@@ -7,6 +7,17 @@
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> function main() {
</span><ins>+    const requriedFeatures = {
+        'Custom Elements API': () =&gt; { return !!window.customElements; },
+        'Shadow DOM API': () =&gt; { return !!Element.prototype.attachShadow; },
+        'Latest DOM': () =&gt; { return !!Element.prototype.getRootNode; },
+    };
+
+    for (let name in requriedFeatures) {
+        if (!requriedFeatures[name]())
+            return alert(`Your browser does not support ${name}. Try using the latest Safari or Chrome.`);
+    }
+
</ins><span class="cx">     (new SpinningPage).open();
</span><span class="cx"> 
</span><span class="cx">     Manifest.fetch().then(function (manifest) {
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3pagessummarypagejs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/pages/summary-page.js (210782 => 210783)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/pages/summary-page.js        2017-01-15 22:42:53 UTC (rev 210782)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/summary-page.js        2017-01-16 00:12:22 UTC (rev 210783)
</span><span class="lines">@@ -112,14 +112,17 @@
</span><span class="cx"> 
</span><span class="cx">         var state = ChartsPage.createStateForConfigurationList(configurationList);
</span><span class="cx">         var anchor = link(ratioGraph, this.router().url('charts', state));
</span><del>-        var cell = element('td', [anchor, new SpinnerIcon]);
</del><ins>+        var spinner = new SpinnerIcon;
+        var cell = element('td', [anchor, spinner]);
</ins><span class="cx"> 
</span><del>-        this._renderQueue.push(this._renderCell.bind(this, cell, anchor, ratioGraph, configurationGroup));
</del><ins>+        this._renderQueue.push(this._renderCell.bind(this, cell, spinner, anchor, ratioGraph, configurationGroup));
</ins><span class="cx">         return cell;
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    _renderCell(cell, anchor, ratioGraph, configurationGroup)
</del><ins>+    _renderCell(cell, spinner, anchor, ratioGraph, configurationGroup)
</ins><span class="cx">     {
</span><ins>+        spinner.updateRendering();
+
</ins><span class="cx">         if (configurationGroup.isFetching())
</span><span class="cx">             cell.classList.add('fetching');
</span><span class="cx">         else
</span></span></pre>
</div>
</div>

</body>
</html>