Add the build fix for browsers that don't yet support custom elements SPI.
[WebKit.git] / Websites / perf.webkit.org / public / v3 / components / base.js
1
2 class ComponentBase {
3     constructor(name)
4     {
5         this._componentName = name || ComponentBase._componentByClass.get(new.target);
6
7         const currentlyConstructed = ComponentBase._currentlyConstructedByInterface;
8         let element = currentlyConstructed.get(new.target);
9         if (!element) {
10             currentlyConstructed.set(new.target, this);
11             element = document.createElement(this._componentName);
12             currentlyConstructed.delete(new.target);
13         }
14         element.component = () => { return this };
15
16         this._element = element;
17         this._shadow = null;
18     }
19
20     element() { return this._element; }
21     content()
22     {
23         this._ensureShadowTree();
24         return this._shadow;
25     }
26
27     render() { this._ensureShadowTree(); }
28
29     updateRendering()
30     {
31         Instrumentation.startMeasuringTime('ComponentBase', 'updateRendering');
32         this.render();
33         Instrumentation.endMeasuringTime('ComponentBase', 'updateRendering');
34     }
35
36     renderReplace(element, content) { ComponentBase.renderReplace(element, content); }
37
38     static renderReplace(element, content)
39     {
40         element.innerHTML = '';
41         if (content)
42             ComponentBase._addContentToElement(element, content);
43     }
44
45     _ensureShadowTree()
46     {
47         if (this._shadow)
48             return;
49
50         const newTarget = this.__proto__.constructor;
51         const htmlTemplate = newTarget['htmlTemplate'];
52         const cssTemplate = newTarget['cssTemplate'];
53
54         if (!htmlTemplate && !cssTemplate)
55             return;
56
57         const shadow = this._element.attachShadow({mode: 'closed'});
58
59         if (htmlTemplate) {
60             const template = document.createElement('template');
61             template.innerHTML = newTarget.htmlTemplate();
62             shadow.appendChild(document.importNode(template.content, true));
63             this._recursivelyReplaceUnknownElementsByComponents(shadow);
64         }
65
66         if (cssTemplate) {
67             const style = document.createElement('style');
68             style.textContent = newTarget.cssTemplate();
69             shadow.appendChild(style);
70         }
71
72         this._shadow = shadow;
73     }
74
75     _recursivelyReplaceUnknownElementsByComponents(parent)
76     {
77         let nextSibling;
78         for (let child = parent.firstChild; child; child = child.nextSibling) {
79             if (child instanceof HTMLElement && !child.component) {
80                 const elementInterface = ComponentBase._componentByName.get(child.localName);
81                 if (elementInterface) {
82                     const component = new elementInterface();
83                     const newChild = component.element();
84                     parent.replaceChild(newChild, child);
85                     child = newChild;
86                 }
87             }
88             this._recursivelyReplaceUnknownElementsByComponents(child);
89         }
90     }
91
92     static defineElement(name, elementInterface)
93     {
94         ComponentBase._componentByName.set(name, elementInterface);
95         ComponentBase._componentByClass.set(elementInterface, name);
96
97         if (!window.customElements)
98             return;
99
100         class elementClass extends HTMLElement {
101             constructor()
102             {
103                 super();
104
105                 const currentlyConstructed = ComponentBase._currentlyConstructedByInterface;
106                 const component = currentlyConstructed.get(elementInterface);
107                 if (component)
108                     return; // ComponentBase's constructor will take care of the rest.
109
110                 currentlyConstructed.set(elementInterface, this);
111                 new elementInterface();
112                 currentlyConstructed.delete(elementInterface);
113             }
114         }
115
116         const nameDescriptor = Object.getOwnPropertyDescriptor(elementClass, 'name');
117         nameDescriptor.value = `${elementInterface.name}Element`;
118         Object.defineProperty(elementClass, 'name', nameDescriptor);
119
120         customElements.define(name, elementClass);
121     }
122
123     static createElement(name, attributes, content)
124     {
125         var element = document.createElement(name);
126         if (!content && (attributes instanceof Array || attributes instanceof Node
127             || attributes instanceof ComponentBase || typeof(attributes) != 'object')) {
128             content = attributes;
129             attributes = {};
130         }
131
132         if (attributes) {
133             for (var name in attributes) {
134                 if (name.startsWith('on'))
135                     element.addEventListener(name.substring(2), attributes[name]);
136                 else
137                     element.setAttribute(name, attributes[name]);
138             }
139         }
140
141         if (content)
142             ComponentBase._addContentToElement(element, content);
143
144         return element;
145     }
146
147     static _addContentToElement(element, content)
148     {
149         if (content instanceof Array) {
150             for (var nestedChild of content)
151                 this._addContentToElement(element, nestedChild);
152         } else if (content instanceof Node)
153             element.appendChild(content);
154          else if (content instanceof ComponentBase)
155             element.appendChild(content.element());
156         else
157             element.appendChild(document.createTextNode(content));
158     }
159
160     static createLink(content, titleOrCallback, callback, isExternal)
161     {
162         var title = titleOrCallback;
163         if (callback === undefined) {
164             title = content;
165             callback = titleOrCallback;
166         }
167
168         var attributes = {
169             href: '#',
170             title: title,
171         };
172
173         if (typeof(callback) === 'string')
174             attributes['href'] = callback;
175         else
176             attributes['onclick'] = ComponentBase.createActionHandler(callback);
177
178         if (isExternal)
179             attributes['target'] = '_blank';
180         return ComponentBase.createElement('a', attributes, content);
181     }
182
183     static createActionHandler(callback)
184     {
185         return function (event) {
186             event.preventDefault();
187             event.stopPropagation();
188             callback.call(this, event);
189         };
190     }
191 }
192
193 ComponentBase._componentByName = new Map;
194 ComponentBase._componentByClass = new Map;
195 ComponentBase._currentlyConstructedByInterface = new Map;
196
197 ComponentBase.css = Symbol();
198 ComponentBase.html = Symbol();
199 ComponentBase.map = {};