Diff
Modified: trunk/Websites/perf.webkit.org/ChangeLog (210782 => 210783)
--- 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)
@@ -1,3 +1,65 @@
+2017-01-12 Ryosuke Niwa <rn...@webkit.org>
+
+ 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):
+
2017-01-13 Ryosuke Niwa <rn...@webkit.org>
Instrument calls to render()
Added: trunk/Websites/perf.webkit.org/browser-tests/component-base-tests.js (0 => 210783)
--- 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)
@@ -0,0 +1,184 @@
+
+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) => {
+ class SomeComponent extends ComponentBase { }
+ if (options.htmlTemplate)
+ SomeComponent.htmlTemplate = () => { return '<div style="height: 10px;"></div>'; };
+ if (options.cssTemplate)
+ SomeComponent.cssTemplate = () => { return ':host { height: 10px; }'; };
+
+ let instance = new SomeComponent('some-component');
+ instance.element().style.display = 'block';
+ context.document.body.appendChild(instance.element());
+ return callback(instance, () => { return instance.element().offsetHeight == 10; });
+ });
+ }
+
+ describe('constructor', () => {
+ it('is a function', () => {
+ return new BrowsingContext().importScript('../public/v3/components/base.js', 'ComponentBase').then((ComponentBase) => {
+ expect(ComponentBase).toBeA('function');
+ });
+ });
+
+ it('can be instantiated', () => {
+ return new BrowsingContext().importScript('../public/v3/components/base.js', 'ComponentBase').then((ComponentBase) => {
+ 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', () => {
+ return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) => {
+ expect(hasShadowTree()).toBe(false);
+ });
+ });
+ });
+
+ describe('element()', () => {
+ it('must return an element', () => {
+ const context = new BrowsingContext();
+ return context.importScript('../public/v3/components/base.js', 'ComponentBase').then((ComponentBase) => {
+ 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', () => {
+ return new BrowsingContext().importScript('../public/v3/components/base.js', 'ComponentBase').then((ComponentBase) => {
+ class SomeComponent extends ComponentBase { }
+ let instance = new SomeComponent('some-component');
+ expect(instance.element().component()).toBe(instance);
+ });
+ });
+
+ it('must not create shadow tree eagerly', () => {
+ return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) => {
+ instance.element();
+ expect(hasShadowTree()).toBe(false);
+ });
+ });
+ });
+
+ describe('content()', () => {
+ it('must create shadow tree', () => {
+ return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) => {
+ instance.content();
+ expect(hasShadowTree()).toBe(true);
+ });
+ });
+
+ it('must return the same shadow tree each time it is called', () => {
+ return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) => {
+ expect(instance.content()).toBe(instance.content());
+ });
+ });
+ });
+
+ describe('render()', () => {
+ it('must create shadow tree', () => {
+ return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) => {
+ instance.render();
+ expect(hasShadowTree()).toBe(true);
+ });
+ });
+
+ it('must not create shadow tree when neither htmlTemplate nor cssTemplate are present', () => {
+ return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) => {
+ instance.render();
+ expect(hasShadowTree()).toBe(false);
+ }, {htmlTemplate: false, cssTemplate: false});
+ });
+
+ it('must create shadow tree when htmlTemplate is present and cssTemplate is not', () => {
+ return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) => {
+ instance.render();
+ expect(hasShadowTree()).toBe(true);
+ }, {htmlTemplate: true, cssTemplate: false});
+ });
+
+ it('must create shadow tree when cssTemplate is present and htmlTemplate is not', () => {
+ return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) => {
+ instance.render();
+ expect(hasShadowTree()).toBe(true);
+ }, {htmlTemplate: false, cssTemplate: true});
+ });
+ });
+
+ describe('defineElement()', () => {
+
+ it('must define a custom element with a class of an appropriate name', () => {
+ const context = new BrowsingContext();
+ return context.importScript('../public/v3/components/base.js', 'ComponentBase').then((ComponentBase) => {
+ 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', () => {
+ const context = new BrowsingContext();
+ return context.importScript('../public/v3/components/base.js', 'ComponentBase').then((ComponentBase) => {
+ 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', () => {
+ const context = new BrowsingContext();
+ return context.importScript('../public/v3/components/base.js', 'ComponentBase').then((ComponentBase) => {
+ 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);
+ });
+ });
+
+ });
+
+});
Added: trunk/Websites/perf.webkit.org/browser-tests/index.html (0 => 210783)
--- 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)
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="stylesheet" href=""
+<script src=""
+<script src=""
+<script>
+
+mocha.setup('bdd');
+
+</script>
+</head>
+<body>
+<div id="mocha"></div>
+<script src=""
+<script>
+
+afterEach(() => {
+ 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) => {
+ let script = doc.createElement('script');
+ script.addEventListener('load', resolve);
+ script.addEventListener('error', reject);
+ script.src = ""
+ doc.body.appendChild(script);
+ }).then(() => {
+ const script = doc.createElement('script');
+ script.textContent = `window.importedSymbols = [${symbolList.join(', ')}];`;
+ doc.body.appendChild(script);
+
+ const importedSymbols = global.importedSymbols;
+ for (let i = 0; i < symbolList.length; i++)
+ this.symbols[symbolList[i]] = importedSymbols[i];
+
+ return symbolList.length == 1 ? importedSymbols[0] : importedSymbols;
+ });
+ }
+
+ static cleanup()
+ {
+ BrowsingContext._iframes.forEach((iframe) => { 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) => {
+ let [path, ...symbols] = entry;
+ symbolList = symbolList.concat(symbols);
+ return new Promise((resolve, reject) => {
+ let script = doc.createElement('script');
+ script.addEventListener('load', resolve);
+ script.addEventListener('error', reject);
+ script.src = ""
+ doc.body.appendChild(script);
+ });
+ })).then(() => {
+ 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();
+
+</script>
+</body>
+</html>
Modified: trunk/Websites/perf.webkit.org/public/v3/components/base.js (210782 => 210783)
--- 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)
@@ -1,17 +1,31 @@
-// FIXME: ComponentBase should inherit from HTMLElement when custom elements API is available.
class ComponentBase {
constructor(name)
{
- this._element = document.createElement(name);
- this._element.component = (function () { return this; }).bind(this);
- this._shadow = this._constructShadowTree();
+ 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 = () => { return this };
+
+ this._element = element;
+ this._shadow = null;
}
element() { return this._element; }
- content() { return this._shadow; }
- render() { }
+ content()
+ {
+ this._ensureShadowTree();
+ return this._shadow;
+ }
+ render() { this._ensureShadowTree(); }
+
updateRendering()
{
Instrumentation.startMeasuringTime('ComponentBase', 'updateRendering');
@@ -28,52 +42,45 @@
ComponentBase._addContentToElement(element, content);
}
- _constructShadowTree()
+ _ensureShadowTree()
{
- var newTarget = this.__proto__.constructor;
+ if (this._shadow)
+ return;
- var htmlTemplate = newTarget['htmlTemplate'];
- var cssTemplate = newTarget['cssTemplate'];
+ const newTarget = this.__proto__.constructor;
+ const htmlTemplate = newTarget['htmlTemplate'];
+ const cssTemplate = newTarget['cssTemplate'];
if (!htmlTemplate && !cssTemplate)
- return null;
+ return;
- 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;
+ const shadow = this._element.attachShadow({mode: 'closed'});
if (htmlTemplate) {
- var template = document.createElement('template');
+ const template = document.createElement('template');
template.innerHTML = newTarget.htmlTemplate();
- shadow.appendChild(template.content.cloneNode(true));
+ shadow.appendChild(document.importNode(template.content, true));
this._recursivelyReplaceUnknownElementsByComponents(shadow);
}
if (cssTemplate) {
- var style = document.createElement('style');
+ const style = document.createElement('style');
style.textContent = newTarget.cssTemplate();
shadow.appendChild(style);
}
- return shadow;
+ this._shadow = shadow;
}
_recursivelyReplaceUnknownElementsByComponents(parent)
{
- 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];
+ let nextSibling;
+ for (let child = parent.firstChild; child; child = child.nextSibling) {
+ if (child instanceof HTMLElement && !child.component) {
+ const elementInterface = ComponentBase._componentByName.get(child.localName);
if (elementInterface) {
- var component = new elementInterface();
- var newChild = component.element();
+ const component = new elementInterface();
+ const newChild = component.element();
parent.replaceChild(newChild, child);
child = newChild;
}
@@ -84,9 +91,30 @@
static defineElement(name, elementInterface)
{
- if (!ComponentBase._map)
- ComponentBase._map = {};
- ComponentBase._map[name] = elementInterface;
+ 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);
}
static createElement(name, attributes, content)
@@ -159,6 +187,10 @@
}
}
+ComponentBase._componentByName = new Map;
+ComponentBase._componentByClass = new Map;
+ComponentBase._currentlyConstructedByInterface = new Map;
+
ComponentBase.css = Symbol();
ComponentBase.html = Symbol();
ComponentBase.map = {};
Modified: trunk/Websites/perf.webkit.org/public/v3/components/button-base.js (210782 => 210783)
--- 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)
@@ -10,21 +10,37 @@
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>`;
+ }
+
+ static buttonContent() { throw 'NotImplemented'; }
+ static sizeFactor() { return 1; }
+
static cssTemplate()
{
+ const sizeFactor = this.sizeFactor();
return `
+ :host {
+ display: inline-block;
+ width: ${sizeFactor}rem;
+ height: ${sizeFactor}rem;
+ }
+
.button {
vertical-align: bottom;
- display: inline-block;
- width: 1rem;
- height: 1rem;
+ display: block;
opacity: 0.3;
}
+ .button svg {
+ display: block;
+ }
+
.button:hover {
opacity: 0.6;
}
`;
}
-
}
Modified: trunk/Websites/perf.webkit.org/public/v3/components/close-button.js (210782 => 210783)
--- 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)
@@ -5,16 +5,13 @@
super('close-button');
}
- static htmlTemplate()
+ static buttonContent()
{
- return `
- <a class="button" href="" viewBox="0 0 100 100">
- <g stroke="black" stroke-width="10">
- <circle cx="50" cy="50" r="45" fill="transparent"/>
- <polygon points="30,30 70,70" />
- <polygon points="30,70 70,30" />
- </g>
- </svg></a>`;
+ return `<g stroke="black" stroke-width="10">
+ <circle cx="50" cy="50" r="45" fill="transparent"/>
+ <polygon points="30,30 70,70" />
+ <polygon points="30,70 70,30" />
+ </g>`;
}
}
Modified: trunk/Websites/perf.webkit.org/public/v3/components/spinner-icon.js (210782 => 210783)
--- 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)
@@ -8,76 +8,43 @@
static cssTemplate()
{
return `
- .spinner {
+ :host {
+ display: inline-block;
width: 2rem;
height: 2rem;
- -webkit-transform: translateZ(0);
+ will-change: opacity; /* Threre is no will-change: stroke. */
}
- .spinner line {
+ line {
animation: spinner-animation 1.6s linear infinite;
- -webkit-animation: spinner-animation 1.6s linear infinite;
- opacity: 0.1;
+ stroke: rgb(230, 230, 230);
+ stroke-width: 10;
+ stroke-linecap: round;
}
- .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;
- }
+ 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; }
@keyframes spinner-animation {
- 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; }
- }
- `;
+ 0% { stroke: rgb(25, 25, 25); }
+ 50% { stroke: rgb(230, 230, 230); }
+ }`;
}
static htmlTemplate()
{
return `<svg class="spinner" viewBox="0 0 100 100">
- <line x1="10" y1="50" x2="30" y2="50" stroke="black" stroke-width="10" stroke-linecap="round"/>
- <line x1="21.72" y1="21.72" x2="35.86" y2="35.86" stroke="black" stroke-width="10" stroke-linecap="round"/>
- <line x1="50" y1="10" x2="50" y2="30" stroke="black" stroke-width="10" stroke-linecap="round"/>
- <line x1="78.28" y1="21.72" x2="64.14" y2="35.86" stroke="black" stroke-width="10" stroke-linecap="round"/>
- <line x1="70" y1="50" x2="90" y2="50" stroke="black" stroke-width="10" stroke-linecap="round"/>
- <line x1="65.86" y1="65.86" x2="78.28" y2="78.28" stroke="black" stroke-width="10" stroke-linecap="round"/>
- <line x1="50" y1="70" x2="50" y2="90" stroke="black" stroke-width="10" stroke-linecap="round"/>
- <line x1="21.72" y1="78.28" x2="35.86" y2="65.86" stroke="black" stroke-width="10" stroke-linecap="round"/>
+ <line x1="10" y1="50" x2="30" y2="50"/>
+ <line x1="21.72" y1="21.72" x2="35.86" y2="35.86"/>
+ <line x1="50" y1="10" x2="50" y2="30"/>
+ <line x1="78.28" y1="21.72" x2="64.14" y2="35.86"/>
+ <line x1="70" y1="50" x2="90" y2="50"/>
+ <line x1="65.86" y1="65.86" x2="78.28" y2="78.28"/>
+ <line x1="50" y1="70" x2="50" y2="90"/>
+ <line x1="21.72" y1="78.28" x2="35.86" y2="65.86"/>
</svg>`;
}
Modified: trunk/Websites/perf.webkit.org/public/v3/components/warning-icon.js (210782 => 210783)
--- 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)
@@ -5,29 +5,14 @@
super('warning-icon');
}
- static cssTemplate()
- {
- return super.cssTemplate() + `
- .button {
- display: block;
- width: 0.7rem;
- height: 0.7rem;
- }
- .button svg {
- display: block;
- }
- `;
- }
+ static sizeFactor() { return 0.7; }
- static htmlTemplate()
+ static buttonContent()
{
- return `<a class="button" href="" viewBox="0 0 100 100">
- <g stroke="#9f6000" fill="#9f6000" stroke-width="7">
+ return `<g stroke="#9f6000" fill="#9f6000" stroke-width="7">
<polygon points="0,0, 100,0, 0,100" />
- </g>
- </svg></a>`;
+ </g>`;
}
-
}
ComponentBase.defineElement('warning-icon', WarningIcon);
Modified: trunk/Websites/perf.webkit.org/public/v3/main.js (210782 => 210783)
--- 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)
@@ -7,6 +7,17 @@
}
function main() {
+ const requriedFeatures = {
+ 'Custom Elements API': () => { return !!window.customElements; },
+ 'Shadow DOM API': () => { return !!Element.prototype.attachShadow; },
+ 'Latest DOM': () => { 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.`);
+ }
+
(new SpinningPage).open();
Manifest.fetch().then(function (manifest) {
Modified: trunk/Websites/perf.webkit.org/public/v3/pages/summary-page.js (210782 => 210783)
--- 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)
@@ -112,14 +112,17 @@
var state = ChartsPage.createStateForConfigurationList(configurationList);
var anchor = link(ratioGraph, this.router().url('charts', state));
- var cell = element('td', [anchor, new SpinnerIcon]);
+ var spinner = new SpinnerIcon;
+ var cell = element('td', [anchor, spinner]);
- this._renderQueue.push(this._renderCell.bind(this, cell, anchor, ratioGraph, configurationGroup));
+ this._renderQueue.push(this._renderCell.bind(this, cell, spinner, anchor, ratioGraph, configurationGroup));
return cell;
}
- _renderCell(cell, anchor, ratioGraph, configurationGroup)
+ _renderCell(cell, spinner, anchor, ratioGraph, configurationGroup)
{
+ spinner.updateRendering();
+
if (configurationGroup.isFetching())
cell.classList.add('fetching');
else