Improve test freshness page interaction experience.
[WebKit.git] / Websites / perf.webkit.org / public / v3 / components / base.js
index 2ca8b97..1fd79e0 100644 (file)
@@ -1,7 +1,8 @@
 
 
-class ComponentBase {
+class ComponentBase extends CommonComponentBase {
     constructor(name)
     {
     constructor(name)
     {
+        super();
         this._componentName = name || ComponentBase._componentByClass.get(new.target);
 
         const currentlyConstructed = ComponentBase._currentlyConstructedByInterface;
         this._componentName = name || ComponentBase._componentByClass.get(new.target);
 
         const currentlyConstructed = ComponentBase._currentlyConstructedByInterface;
@@ -16,6 +17,7 @@ class ComponentBase {
         this._element = element;
         this._shadow = null;
         this._actionCallbacks = new Map;
         this._element = element;
         this._shadow = null;
         this._actionCallbacks = new Map;
+        this._oldSizeToCheckForResize = null;
 
         if (!ComponentBase.useNativeCustomElements)
             element.addEventListener('DOMNodeInsertedIntoDocument', () => this.enqueueToRender());
 
         if (!ComponentBase.useNativeCustomElements)
             element.addEventListener('DOMNodeInsertedIntoDocument', () => this.enqueueToRender());
@@ -74,18 +76,55 @@ class ComponentBase {
     {
         Instrumentation.startMeasuringTime('ComponentBase', 'renderingTimerDidFire');
 
     {
         Instrumentation.startMeasuringTime('ComponentBase', 'renderingTimerDidFire');
 
+        const componentsToRender = ComponentBase._componentsToRender;
+        this._renderLoop();
+        if (ComponentBase._componentsToRenderOnResize) {
+            const resizedComponents = this._resizedComponents(ComponentBase._componentsToRenderOnResize);
+            if (resizedComponents.length) {
+                ComponentBase._componentsToRender = new Set(resizedComponents);
+                this._renderLoop();
+            }
+        }
+
+        Instrumentation.endMeasuringTime('ComponentBase', 'renderingTimerDidFire');
+    }
+
+    static _renderLoop()
+    {
+        const componentsToRender = ComponentBase._componentsToRender;
         do {
         do {
-            const currentSet = [...ComponentBase._componentsToRender];
-            ComponentBase._componentsToRender.clear();
+            const currentSet = [...componentsToRender];
+            componentsToRender.clear();
+            const resizeSet = ComponentBase._componentsToRenderOnResize;
             for (let component of currentSet) {
                 Instrumentation.startMeasuringTime('ComponentBase', 'renderingTimerDidFire.render');
                 component.render();
             for (let component of currentSet) {
                 Instrumentation.startMeasuringTime('ComponentBase', 'renderingTimerDidFire.render');
                 component.render();
+                if (resizeSet && resizeSet.has(component)) {
+                    const element = component.element();
+                    component._oldSizeToCheckForResize = {width: element.offsetWidth, height: element.offsetHeight};
+                }
                 Instrumentation.endMeasuringTime('ComponentBase', 'renderingTimerDidFire.render');
             }
                 Instrumentation.endMeasuringTime('ComponentBase', 'renderingTimerDidFire.render');
             }
-        } while (ComponentBase._componentsToRender.size);
+        } while (componentsToRender.size);
         ComponentBase._componentsToRender = null;
         ComponentBase._componentsToRender = null;
+    }
 
 
-        Instrumentation.endMeasuringTime('ComponentBase', 'renderingTimerDidFire');
+    static _resizedComponents(componentSet)
+    {
+        if (!componentSet)
+            return [];
+
+        const resizedList = [];
+        for (let component of componentSet) {
+            const element = component.element();
+            const width = element.offsetWidth;
+            const height = element.offsetHeight;
+            const oldSize = component._oldSizeToCheckForResize;
+            if (oldSize && oldSize.width == width && oldSize.height == height)
+                continue;
+            resizedList.push(component);
+        }
+        return resizedList;
     }
 
     static _connectedComponentToRenderOnResize(component)
     }
 
     static _connectedComponentToRenderOnResize(component)
@@ -93,7 +132,8 @@ class ComponentBase {
         if (!ComponentBase._componentsToRenderOnResize) {
             ComponentBase._componentsToRenderOnResize = new Set;
             window.addEventListener('resize', () => {
         if (!ComponentBase._componentsToRenderOnResize) {
             ComponentBase._componentsToRenderOnResize = new Set;
             window.addEventListener('resize', () => {
-                for (let component of ComponentBase._componentsToRenderOnResize)
+                const resized = this._resizedComponents(ComponentBase._componentsToRenderOnResize);
+                for (const component of resized)
                     component.enqueueToRender();
             });
         }
                     component.enqueueToRender();
             });
         }
@@ -105,39 +145,56 @@ class ComponentBase {
         ComponentBase._componentsToRenderOnResize.delete(component);
     }
 
         ComponentBase._componentsToRenderOnResize.delete(component);
     }
 
-    renderReplace(element, content) { ComponentBase.renderReplace(element, content); }
-
-    static renderReplace(element, content)
-    {
-        element.innerHTML = '';
-        if (content)
-            ComponentBase._addContentToElement(element, content);
-    }
-
     _ensureShadowTree()
     {
         if (this._shadow)
             return;
 
     _ensureShadowTree()
     {
         if (this._shadow)
             return;
 
-        const newTarget = this.__proto__.constructor;
-        const htmlTemplate = newTarget['htmlTemplate'];
-        const cssTemplate = newTarget['cssTemplate'];
+        const thisClass = this.__proto__.constructor;
+
+        let content;
+        let stylesheet;
+        if (!thisClass.hasOwnProperty('_parsed') || !thisClass._parsed) {
+            thisClass._parsed = true;
+
+            const contentTemplate = thisClass['contentTemplate'];
+            if (contentTemplate)
+                content = ComponentBase._constructNodeTreeFromTemplate(contentTemplate);
+            else if (thisClass.htmlTemplate) {
+                const templateElement = document.createElement('template');
+                templateElement.innerHTML = thisClass.htmlTemplate();
+                content = [templateElement.content];
+            }
+
+            const styleTemplate = thisClass['styleTemplate'];
+            if (styleTemplate)
+                stylesheet = ComponentBase._constructStylesheetFromTemplate(styleTemplate);
+            else if (thisClass.cssTemplate)
+                stylesheet = thisClass.cssTemplate();
+
+            thisClass._parsedContent = content;
+            thisClass._parsedStylesheet = stylesheet;
+        } else {
+            content = thisClass._parsedContent;
+            stylesheet = thisClass._parsedStylesheet;
+        }
 
 
-        if (!htmlTemplate && !cssTemplate)
+        if (!content && !stylesheet)
             return;
 
         const shadow = this._element.attachShadow({mode: 'closed'});
 
             return;
 
         const shadow = this._element.attachShadow({mode: 'closed'});
 
-        if (htmlTemplate) {
-            const template = document.createElement('template');
-            template.innerHTML = newTarget.htmlTemplate();
-            shadow.appendChild(document.importNode(template.content, true));
-            this._recursivelyReplaceUnknownElementsByComponents(shadow);
+        if (content) {
+            for (const node of content)
+                shadow.appendChild(document.importNode(node, true));
+            this._recursivelyUpgradeUnknownElements(shadow, (node) => {
+                return node instanceof Element ? ComponentBase._componentByName.get(node.localName) : null;
+            });
         }
 
         }
 
-        if (cssTemplate) {
+        if (stylesheet) {
             const style = document.createElement('style');
             const style = document.createElement('style');
-            style.textContent = newTarget.cssTemplate();
+            style.textContent = stylesheet;
             shadow.appendChild(style);
         }
         this._shadow = shadow;
             shadow.appendChild(style);
         }
         this._shadow = shadow;
@@ -146,29 +203,6 @@ class ComponentBase {
 
     didConstructShadowTree() { }
 
 
     didConstructShadowTree() { }
 
-    _recursivelyReplaceUnknownElementsByComponents(parent)
-    {
-        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) {
-                    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;
-                }
-            }
-            this._recursivelyReplaceUnknownElementsByComponents(child);
-        }
-    }
-
     static defineElement(name, elementInterface)
     {
         ComponentBase._componentByName.set(name, elementInterface);
     static defineElement(name, elementInterface)
     {
         ComponentBase._componentByName.set(name, elementInterface);
@@ -215,80 +249,24 @@ class ComponentBase {
         customElements.define(name, elementClass);
     }
 
         customElements.define(name, elementClass);
     }
 
-    static createElement(name, attributes, content)
-    {
-        var element = document.createElement(name);
-        if (!content && (Array.isArray(attributes) || attributes instanceof Node
-            || attributes instanceof ComponentBase || typeof(attributes) != 'object')) {
-            content = attributes;
-            attributes = {};
-        }
-
-        if (attributes) {
-            for (let name in attributes) {
-                if (name.startsWith('on'))
-                    element.addEventListener(name.substring(2), attributes[name]);
-                else if (attributes[name] === true)
-                    element.setAttribute(name, name);
-                else if (attributes[name] !== false)
-                    element.setAttribute(name, attributes[name]);
-            }
-        }
-
-        if (content)
-            ComponentBase._addContentToElement(element, content);
-
-        return element;
-    }
-
-    static _addContentToElement(element, content)
-    {
-        if (Array.isArray(content)) {
-            for (var nestedChild of content)
-                this._addContentToElement(element, nestedChild);
-        } else if (content instanceof Node)
-            element.appendChild(content);
-         else if (content instanceof ComponentBase)
-            element.appendChild(content.element());
-        else
-            element.appendChild(document.createTextNode(content));
-    }
-
-    static createLink(content, titleOrCallback, callback, isExternal)
-    {
-        var title = titleOrCallback;
-        if (callback === undefined) {
-            title = content;
-            callback = titleOrCallback;
-        }
-
-        var attributes = {
-            href: '#',
-            title: title,
-        };
-
-        if (typeof(callback) === 'string')
-            attributes['href'] = callback;
-        else
-            attributes['onclick'] = ComponentBase.createEventHandler(callback);
-
-        if (isExternal)
-            attributes['target'] = '_blank';
-        return ComponentBase.createElement('a', attributes, content);
-    }
-
-    createEventHandler(callback) { return ComponentBase.createEventHandler(callback); }
-    static createEventHandler(callback)
+    createEventHandler(callback, options={}) { return ComponentBase.createEventHandler(callback, options); }
+    static createEventHandler(callback, options={})
     {
         return function (event) {
     {
         return function (event) {
-            event.preventDefault();
-            event.stopPropagation();
+            if (!('preventDefault' in options) || options['preventDefault'])
+                event.preventDefault();
+            if (!('stopPropagation' in options) || options['stopPropagation'])
+                event.stopPropagation();
             callback.call(this, event);
         };
     }
 }
 
             callback.call(this, event);
         };
     }
 }
 
-ComponentBase.useNativeCustomElements = false;//!!window.customElements;
+CommonComponentBase._context = document;
+CommonComponentBase._isNode = (node) => node instanceof Node;
+CommonComponentBase._baseClass = ComponentBase;
+
+ComponentBase.useNativeCustomElements = !!window.customElements;
 ComponentBase._componentByName = new Map;
 ComponentBase._componentByClass = new Map;
 ComponentBase._currentlyConstructedByInterface = new Map;
 ComponentBase._componentByName = new Map;
 ComponentBase._componentByClass = new Map;
 ComponentBase._currentlyConstructedByInterface = new Map;