Bug 1728955: part 5) Add missing `// static` comment to `nsClipboard::CreateNativeDat...
[gecko.git] / toolkit / content / customElements.js
blob691dd5ad84801a28956d75a11d1fad0b129c66da
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 // This file defines these globals on the window object.
6 // Define them here so that ESLint can find them:
7 /* globals MozXULElement, MozHTMLElement, MozElements */
9 "use strict";
11 // This is loaded into chrome windows with the subscript loader. Wrap in
12 // a block to prevent accidentally leaking globals onto `window`.
13 (() => {
14   // Handle customElements.js being loaded as a script in addition to the subscriptLoader
15   // from MainProcessSingleton, to handle pages that can open both before and after
16   // MainProcessSingleton starts. See Bug 1501845.
17   if (window.MozXULElement) {
18     return;
19   }
21   const MozElements = {};
22   window.MozElements = MozElements;
24   const { Services } = ChromeUtils.import(
25     "resource://gre/modules/Services.jsm"
26   );
27   const { AppConstants } = ChromeUtils.import(
28     "resource://gre/modules/AppConstants.jsm"
29   );
30   const env = Cc["@mozilla.org/process/environment;1"].getService(
31     Ci.nsIEnvironment
32   );
33   const instrumentClasses = env.get("MOZ_INSTRUMENT_CUSTOM_ELEMENTS");
34   const instrumentedClasses = instrumentClasses ? new Set() : null;
35   const instrumentedBaseClasses = instrumentClasses ? new WeakSet() : null;
37   // If requested, wrap the normal customElements.define to give us a chance
38   // to modify the class so we can instrument function calls in local development:
39   if (instrumentClasses) {
40     let define = window.customElements.define;
41     window.customElements.define = function(name, c, opts) {
42       instrumentCustomElementClass(c);
43       return define.call(this, name, c, opts);
44     };
45     window.addEventListener(
46       "load",
47       () => {
48         MozElements.printInstrumentation(true);
49       },
50       { once: true, capture: true }
51     );
52   }
54   MozElements.printInstrumentation = function(collapsed) {
55     let summaries = [];
56     let totalCalls = 0;
57     let totalTime = 0;
58     for (let c of instrumentedClasses) {
59       // Allow passing in something like MOZ_INSTRUMENT_CUSTOM_ELEMENTS=MozXULElement,Button to filter
60       let includeClass =
61         instrumentClasses == 1 ||
62         instrumentClasses
63           .split(",")
64           .some(n => c.name.toLowerCase().includes(n.toLowerCase()));
65       let summary = c.__instrumentation_summary;
66       if (includeClass && summary) {
67         summaries.push(summary);
68         totalCalls += summary.totalCalls;
69         totalTime += summary.totalTime;
70       }
71     }
72     if (summaries.length) {
73       let groupName = `Instrumentation data for custom elements in ${document.documentURI}`;
74       console[collapsed ? "groupCollapsed" : "group"](groupName);
75       console.log(
76         `Total function calls ${totalCalls} and total time spent inside ${totalTime.toFixed(
77           2
78         )}`
79       );
80       for (let summary of summaries) {
81         console.log(`${summary.name} (# instances: ${summary.instances})`);
82         if (Object.keys(summary.data).length > 1) {
83           console.table(summary.data);
84         }
85       }
86       console.groupEnd(groupName);
87     }
88   };
90   function instrumentCustomElementClass(c) {
91     // Climb up prototype chain to see if we inherit from a MozElement.
92     // Keep track of classes to instrument, for example:
93     //   MozMenuCaption->MozMenuBase->BaseText->BaseControl->MozXULElement
94     let inheritsFromBase = instrumentedBaseClasses.has(c);
95     let classesToInstrument = [c];
96     let proto = Object.getPrototypeOf(c);
97     while (proto) {
98       classesToInstrument.push(proto);
99       if (instrumentedBaseClasses.has(proto)) {
100         inheritsFromBase = true;
101         break;
102       }
103       proto = Object.getPrototypeOf(proto);
104     }
106     if (inheritsFromBase) {
107       for (let c of classesToInstrument.reverse()) {
108         instrumentIndividualClass(c);
109       }
110     }
111   }
113   function instrumentIndividualClass(c) {
114     if (instrumentedClasses.has(c)) {
115       return;
116     }
118     instrumentedClasses.add(c);
119     let data = { instances: 0 };
121     function wrapFunction(name, fn) {
122       return function() {
123         if (!data[name]) {
124           data[name] = { time: 0, calls: 0 };
125         }
126         data[name].calls++;
127         let n = performance.now();
128         let r = fn.apply(this, arguments);
129         data[name].time += performance.now() - n;
130         return r;
131       };
132     }
133     function wrapPropertyDescriptor(obj, name) {
134       if (name == "constructor") {
135         return;
136       }
137       let prop = Object.getOwnPropertyDescriptor(obj, name);
138       if (prop.get) {
139         prop.get = wrapFunction(`<get> ${name}`, prop.get);
140       }
141       if (prop.set) {
142         prop.set = wrapFunction(`<set> ${name}`, prop.set);
143       }
144       if (prop.writable && prop.value && prop.value.apply) {
145         prop.value = wrapFunction(name, prop.value);
146       }
147       Object.defineProperty(obj, name, prop);
148     }
150     // Handle static properties
151     for (let name of Object.getOwnPropertyNames(c)) {
152       wrapPropertyDescriptor(c, name);
153     }
155     // Handle instance properties
156     for (let name of Object.getOwnPropertyNames(c.prototype)) {
157       wrapPropertyDescriptor(c.prototype, name);
158     }
160     c.__instrumentation_data = data;
161     Object.defineProperty(c, "__instrumentation_summary", {
162       enumerable: false,
163       configurable: false,
164       get() {
165         if (data.instances == 0) {
166           return null;
167         }
169         let clonedData = JSON.parse(JSON.stringify(data));
170         delete clonedData.instances;
171         let totalCalls = 0;
172         let totalTime = 0;
173         for (let d in clonedData) {
174           let { time, calls } = clonedData[d];
175           time = parseFloat(time.toFixed(2));
176           totalCalls += calls;
177           totalTime += time;
178           clonedData[d]["time (ms)"] = time;
179           delete clonedData[d].time;
180           clonedData[d].timePerCall = parseFloat((time / calls).toFixed(4));
181         }
183         let timePerCall = parseFloat((totalTime / totalCalls).toFixed(4));
184         totalTime = parseFloat(totalTime.toFixed(2));
186         // Add a spaced-out final row with summed up totals
187         clonedData["\ntotals"] = {
188           "time (ms)": `\n${totalTime}`,
189           calls: `\n${totalCalls}`,
190           timePerCall: `\n${timePerCall}`,
191         };
192         return {
193           instances: data.instances,
194           data: clonedData,
195           name: c.name,
196           totalCalls,
197           totalTime,
198         };
199       },
200     });
201   }
203   // The listener of DOMContentLoaded must be set on window, rather than
204   // document, because the window can go away before the event is fired.
205   // In that case, we don't want to initialize anything, otherwise we
206   // may be leaking things because they will never be destroyed after.
207   let gIsDOMContentLoaded = false;
208   const gElementsPendingConnection = new Set();
209   window.addEventListener(
210     "DOMContentLoaded",
211     () => {
212       gIsDOMContentLoaded = true;
213       for (let element of gElementsPendingConnection) {
214         try {
215           if (element.isConnected) {
216             element.isRunningDelayedConnectedCallback = true;
217             element.connectedCallback();
218           }
219         } catch (ex) {
220           console.error(ex);
221         }
222         element.isRunningDelayedConnectedCallback = false;
223       }
224       gElementsPendingConnection.clear();
225     },
226     { once: true, capture: true }
227   );
229   const gXULDOMParser = new DOMParser();
230   gXULDOMParser.forceEnableXULXBL();
232   MozElements.MozElementMixin = Base => {
233     let MozElementBase = class extends Base {
234       constructor() {
235         super();
237         if (instrumentClasses) {
238           let proto = this.constructor;
239           while (proto && proto != Base) {
240             proto.__instrumentation_data.instances++;
241             proto = Object.getPrototypeOf(proto);
242           }
243         }
244       }
245       /*
246        * A declarative way to wire up attribute inheritance and automatically generate
247        * the `observedAttributes` getter.  For example, if you returned:
248        *    {
249        *      ".foo": "bar,baz=bat"
250        *    }
251        *
252        * Then the base class will automatically return ["bar", "bat"] from `observedAttributes`,
253        * and set up an `attributeChangedCallback` to pass those attributes down onto an element
254        * matching the ".foo" selector.
255        *
256        * See the `inheritAttribute` function for more details on the attribute string format.
257        *
258        * @return {Object<string selector, string attributes>}
259        */
260       static get inheritedAttributes() {
261         return null;
262       }
264       static get flippedInheritedAttributes() {
265         // Have to be careful here, if a subclass overrides inheritedAttributes
266         // and its parent class is instantiated first, then reading
267         // this._flippedInheritedAttributes on the child class will return the
268         // computed value from the parent.  We store it separately on each class
269         // to ensure everything works correctly when inheritedAttributes is
270         // overridden.
271         if (!this.hasOwnProperty("_flippedInheritedAttributes")) {
272           let { inheritedAttributes } = this;
273           if (!inheritedAttributes) {
274             this._flippedInheritedAttributes = null;
275           } else {
276             this._flippedInheritedAttributes = {};
277             for (let selector in inheritedAttributes) {
278               let attrRules = inheritedAttributes[selector].split(",");
279               for (let attrRule of attrRules) {
280                 let attrName = attrRule;
281                 let attrNewName = attrRule;
282                 let split = attrName.split("=");
283                 if (split.length == 2) {
284                   attrName = split[1];
285                   attrNewName = split[0];
286                 }
288                 if (!this._flippedInheritedAttributes[attrName]) {
289                   this._flippedInheritedAttributes[attrName] = [];
290                 }
291                 this._flippedInheritedAttributes[attrName].push([
292                   selector,
293                   attrNewName,
294                 ]);
295               }
296             }
297           }
298         }
300         return this._flippedInheritedAttributes;
301       }
302       /*
303        * Generate this array based on `inheritedAttributes`, if any. A class is free to override
304        * this if it needs to do something more complex or wants to opt out of this behavior.
305        */
306       static get observedAttributes() {
307         return Object.keys(this.flippedInheritedAttributes || {});
308       }
310       /*
311        * Provide default lifecycle callback for attribute changes that will inherit attributes
312        * based on the static `inheritedAttributes` Object. This can be overridden by callers.
313        */
314       attributeChangedCallback(name, oldValue, newValue) {
315         if (oldValue === newValue || !this.initializedAttributeInheritance) {
316           return;
317         }
319         let list = this.constructor.flippedInheritedAttributes[name];
320         if (list) {
321           this.inheritAttribute(list, name);
322         }
323       }
325       /*
326        * After setting content, calling this will cache the elements from selectors in the
327        * static `inheritedAttributes` Object. It'll also do an initial call to `this.inheritAttributes()`,
328        * so in the simple case, this is the only function you need to call.
329        *
330        * This should be called any time the children that are inheriting attributes changes. For instance,
331        * it's common in a connectedCallback to do something like:
332        *
333        *   this.textContent = "";
334        *   this.append(MozXULElement.parseXULToFragment(`<label />`))
335        *   this.initializeAttributeInheritance();
336        *
337        */
338       initializeAttributeInheritance() {
339         let { flippedInheritedAttributes } = this.constructor;
340         if (!flippedInheritedAttributes) {
341           return;
342         }
344         // Clear out any existing cached elements:
345         this._inheritedElements = null;
347         this.initializedAttributeInheritance = true;
348         for (let attr in flippedInheritedAttributes) {
349           if (this.hasAttribute(attr)) {
350             this.inheritAttribute(flippedInheritedAttributes[attr], attr);
351           }
352         }
353       }
355       /*
356        * Implements attribute value inheritance by child elements.
357        *
358        * @param {array} list
359        *        An array of (to-element-selector, to-attr) pairs.
360        * @param {string} attr
361        *        An attribute to propagate.
362        */
363       inheritAttribute(list, attr) {
364         if (!this._inheritedElements) {
365           this._inheritedElements = {};
366         }
368         let hasAttr = this.hasAttribute(attr);
369         let attrValue = this.getAttribute(attr);
371         for (let [selector, newAttr] of list) {
372           if (!(selector in this._inheritedElements)) {
373             this._inheritedElements[
374               selector
375             ] = this.getElementForAttrInheritance(selector);
376           }
377           let el = this._inheritedElements[selector];
378           if (el) {
379             if (newAttr == "text") {
380               el.textContent = hasAttr ? attrValue : "";
381             } else if (hasAttr) {
382               el.setAttribute(newAttr, attrValue);
383             } else {
384               el.removeAttribute(newAttr);
385             }
386           }
387         }
388       }
390       /**
391        * Used in setting up attribute inheritance. Takes a selector and returns
392        * an element for that selector from shadow DOM if there is a shadowRoot,
393        * or from the light DOM if not.
394        *
395        * Here's one problem this solves. ElementB extends ElementA which extends
396        * MozXULElement. ElementA has a shadowRoot. ElementB tries to inherit
397        * attributes in light DOM by calling `initializeAttributeInheritance`
398        * but that fails because it defaults to inheriting from the shadow DOM
399        * and not the light DOM. (See bug 1545824.)
400        *
401        * To solve this, ElementB can override `getElementForAttrInheritance` so
402        * it queries the light DOM for some selectors as needed. For example:
403        *
404        *  class ElementA extends MozXULElement {
405        *    static get inheritedAttributes() {
406        *      return { ".one": "attr" };
407        *    }
408        *  }
409        *
410        *  class ElementB extends customElements.get("elementa") {
411        *    static get inheritedAttributes() {
412        *      return Object.assign({}, super.inheritedAttributes(), {
413        *        ".two": "attr",
414        *      });
415        *    }
416        *    getElementForAttrInheritance(selector) {
417        *      if (selector == ".two") {
418        *        return this.querySelector(selector)
419        *      } else {
420        *        return super.getElementForAttrInheritance(selector);
421        *      }
422        *    }
423        *  }
424        *
425        * @param {string} selector
426        *        A selector used to query an element.
427        *
428        * @return {Element} The element found by the selector.
429        */
430       getElementForAttrInheritance(selector) {
431         let parent = this.shadowRoot || this;
432         return parent.querySelector(selector);
433       }
435       /**
436        * Sometimes an element may not want to run connectedCallback logic during
437        * parse. This could be because we don't want to initialize the element before
438        * the element's contents have been fully parsed, or for performance reasons.
439        * If you'd like to opt-in to this, then add this to the beginning of your
440        * `connectedCallback` and `disconnectedCallback`:
441        *
442        *    if (this.delayConnectedCallback()) { return }
443        *
444        * And this at the beginning of your `attributeChangedCallback`
445        *
446        *    if (!this.isConnectedAndReady) { return; }
447        */
448       delayConnectedCallback() {
449         if (gIsDOMContentLoaded) {
450           return false;
451         }
452         gElementsPendingConnection.add(this);
453         return true;
454       }
456       get isConnectedAndReady() {
457         return gIsDOMContentLoaded && this.isConnected;
458       }
460       /**
461        * Passes DOM events to the on_<event type> methods.
462        */
463       handleEvent(event) {
464         let methodName = "on_" + event.type;
465         if (methodName in this) {
466           this[methodName](event);
467         } else {
468           throw new Error("Unrecognized event: " + event.type);
469         }
470       }
472       /**
473        * Used by custom elements for caching fragments. We now would be
474        * caching once per class while also supporting subclasses.
475        *
476        * If available, returns the cached fragment.
477        * Otherwise, creates it.
478        *
479        * Example:
480        *
481        *  class ElementA extends MozXULElement {
482        *    static get markup() {
483        *      return `<hbox class="example"`;
484        *    }
485        *
486        *    static get entities() {
487        *      // Optional field for parseXULToFragment
488        *      return `["chrome://global/locale/notification.dtd"]`;
489        *    }
490        *
491        *    connectedCallback() {
492        *      this.appendChild(this.constructor.fragment);
493        *    }
494        *  }
495        *
496        * @return {importedNode} The imported node that has not been
497        * inserted into document tree.
498        */
499       static get fragment() {
500         if (!this.hasOwnProperty("_fragment")) {
501           let markup = this.markup;
502           if (markup) {
503             this._fragment = MozXULElement.parseXULToFragment(
504               markup,
505               this.entities
506             );
507           } else {
508             throw new Error("Markup is null");
509           }
510         }
511         return document.importNode(this._fragment, true);
512       }
514       /**
515        * Allows eager deterministic construction of XUL elements with XBL attached, by
516        * parsing an element tree and returning a DOM fragment to be inserted in the
517        * document before any of the inner elements is referenced by JavaScript.
518        *
519        * This process is required instead of calling the createElement method directly
520        * because bindings get attached when:
521        *
522        * 1. the node gets a layout frame constructed, or
523        * 2. the node gets its JavaScript reflector created, if it's in the document,
524        *
525        * whichever happens first. The createElement method would return a JavaScript
526        * reflector, but the element wouldn't be in the document, so the node wouldn't
527        * get XBL attached. After that point, even if the node is inserted into a
528        * document, it won't get XBL attached until either the frame is constructed or
529        * the reflector is garbage collected and the element is touched again.
530        *
531        * @param {string} str
532        *        String with the XML representation of XUL elements.
533        * @param {string[]} [entities]
534        *        An array of DTD URLs containing entity definitions.
535        *
536        * @return {DocumentFragment} `DocumentFragment` instance containing
537        *         the corresponding element tree, including element nodes
538        *         but excluding any text node.
539        */
540       static parseXULToFragment(str, entities = []) {
541         let doc = gXULDOMParser.parseFromSafeString(
542           `
543       ${
544         entities.length
545           ? `<!DOCTYPE bindings [
546         ${entities.reduce((preamble, url, index) => {
547           return (
548             preamble +
549             `<!ENTITY % _dtd-${index} SYSTEM "${url}">
550             %_dtd-${index};
551             `
552           );
553         }, "")}
554       ]>`
555           : ""
556       }
557       <box xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
558            xmlns:html="http://www.w3.org/1999/xhtml">
559         ${str}
560       </box>
561     `,
562           "application/xml"
563         );
565         if (doc.documentElement.localName === "parsererror") {
566           throw new Error("not well-formed XML");
567         }
569         // The XUL/XBL parser is set to ignore all-whitespace nodes, whereas (X)HTML
570         // does not do this. Most XUL code assumes that the whitespace has been
571         // stripped out, so we simply remove all text nodes after using the parser.
572         let nodeIterator = doc.createNodeIterator(doc, NodeFilter.SHOW_TEXT);
573         let currentNode = nodeIterator.nextNode();
574         while (currentNode) {
575           // Remove whitespace-only nodes. Regex is taken from:
576           // https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace_in_the_DOM
577           if (!/[^\t\n\r ]/.test(currentNode.textContent)) {
578             currentNode.remove();
579           }
581           currentNode = nodeIterator.nextNode();
582         }
583         // We use a range here so that we don't access the inner DOM elements from
584         // JavaScript before they are imported and inserted into a document.
585         let range = doc.createRange();
586         range.selectNodeContents(doc.querySelector("box"));
587         return range.extractContents();
588       }
590       /**
591        * Insert a localization link to an FTL file. This is used so that
592        * a Custom Element can wait to inject the link until it's connected,
593        * and so that consuming documents don't require the correct <link>
594        * present in the markup.
595        *
596        * @param path
597        *        The path to the FTL file
598        */
599       static insertFTLIfNeeded(path) {
600         let container = document.head || document.querySelector("linkset");
601         if (!container) {
602           if (
603             document.documentElement.namespaceURI ===
604             "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
605           ) {
606             container = document.createXULElement("linkset");
607             document.documentElement.appendChild(container);
608           } else if (document.documentURI == AppConstants.BROWSER_CHROME_URL) {
609             // Special case for browser.xhtml. Here `document.head` is null, so
610             // just insert the link at the end of the window.
611             container = document.documentElement;
612           } else {
613             throw new Error(
614               "Attempt to inject localization link before document.head is available"
615             );
616           }
617         }
619         for (let link of container.querySelectorAll("link")) {
620           if (link.getAttribute("href") == path) {
621             return;
622           }
623         }
625         let link = document.createElementNS(
626           "http://www.w3.org/1999/xhtml",
627           "link"
628         );
629         link.setAttribute("rel", "localization");
630         link.setAttribute("href", path);
632         container.appendChild(link);
633       }
635       /**
636        * Indicate that a class defining a XUL element implements one or more
637        * XPCOM interfaces by adding a getCustomInterface implementation to it,
638        * as well as an implementation of QueryInterface.
639        *
640        * The supplied class should implement the properties and methods of
641        * all of the interfaces that are specified.
642        *
643        * @param cls
644        *        The class that implements the interface.
645        * @param names
646        *        Array of interface names.
647        */
648       static implementCustomInterface(cls, ifaces) {
649         if (cls.prototype.customInterfaces) {
650           ifaces.push(...cls.prototype.customInterfaces);
651         }
652         cls.prototype.customInterfaces = ifaces;
654         cls.prototype.QueryInterface = ChromeUtils.generateQI(ifaces);
655         cls.prototype.getCustomInterfaceCallback = function getCustomInterfaceCallback(
656           ifaceToCheck
657         ) {
658           if (
659             cls.prototype.customInterfaces.some(iface =>
660               iface.equals(ifaceToCheck)
661             )
662           ) {
663             return getInterfaceProxy(this);
664           }
665           return null;
666         };
667       }
668     };
670     // Rename the class so we can distinguish between MozXULElement and MozXULPopupElement, for example.
671     Object.defineProperty(MozElementBase, "name", { value: `Moz${Base.name}` });
672     if (instrumentedBaseClasses) {
673       instrumentedBaseClasses.add(MozElementBase);
674     }
675     return MozElementBase;
676   };
678   const MozXULElement = MozElements.MozElementMixin(XULElement);
679   const MozHTMLElement = MozElements.MozElementMixin(HTMLElement);
681   /**
682    * Given an object, add a proxy that reflects interface implementations
683    * onto the object itself.
684    */
685   function getInterfaceProxy(obj) {
686     /* globals MozQueryInterface */
687     if (!obj._customInterfaceProxy) {
688       obj._customInterfaceProxy = new Proxy(obj, {
689         get(target, prop, receiver) {
690           let propOrMethod = target[prop];
691           if (typeof propOrMethod == "function") {
692             if (propOrMethod instanceof MozQueryInterface) {
693               return Reflect.get(target, prop, receiver);
694             }
695             return function(...args) {
696               return propOrMethod.apply(target, args);
697             };
698           }
699           return propOrMethod;
700         },
701       });
702     }
704     return obj._customInterfaceProxy;
705   }
707   MozElements.BaseControlMixin = Base => {
708     class BaseControl extends Base {
709       get disabled() {
710         return this.getAttribute("disabled") == "true";
711       }
713       set disabled(val) {
714         if (val) {
715           this.setAttribute("disabled", "true");
716         } else {
717           this.removeAttribute("disabled");
718         }
719       }
721       get tabIndex() {
722         return parseInt(this.getAttribute("tabindex")) || 0;
723       }
725       set tabIndex(val) {
726         if (val) {
727           this.setAttribute("tabindex", val);
728         } else {
729           this.removeAttribute("tabindex");
730         }
731       }
732     }
734     MozXULElement.implementCustomInterface(BaseControl, [
735       Ci.nsIDOMXULControlElement,
736     ]);
737     return BaseControl;
738   };
739   MozElements.BaseControl = MozElements.BaseControlMixin(MozXULElement);
741   const BaseTextMixin = Base =>
742     class BaseText extends MozElements.BaseControlMixin(Base) {
743       set label(val) {
744         this.setAttribute("label", val);
745       }
747       get label() {
748         return this.getAttribute("label");
749       }
751       set crop(val) {
752         this.setAttribute("crop", val);
753       }
755       get crop() {
756         return this.getAttribute("crop");
757       }
759       set image(val) {
760         this.setAttribute("image", val);
761       }
763       get image() {
764         return this.getAttribute("image");
765       }
767       set command(val) {
768         this.setAttribute("command", val);
769       }
771       get command() {
772         return this.getAttribute("command");
773       }
775       set accessKey(val) {
776         // Always store on the control
777         this.setAttribute("accesskey", val);
778         // If there is a label, change the accesskey on the labelElement
779         // if it's also set there
780         if (this.labelElement) {
781           this.labelElement.accessKey = val;
782         }
783       }
785       get accessKey() {
786         return this.labelElement
787           ? this.labelElement.accessKey
788           : this.getAttribute("accesskey");
789       }
790     };
791   MozElements.BaseTextMixin = BaseTextMixin;
792   MozElements.BaseText = BaseTextMixin(MozXULElement);
794   // Attach the base class to the window so other scripts can use it:
795   window.MozXULElement = MozXULElement;
796   window.MozHTMLElement = MozHTMLElement;
798   customElements.setElementCreationCallback("browser", () => {
799     Services.scriptloader.loadSubScript(
800       "chrome://global/content/elements/browser-custom-element.js",
801       window
802     );
803   });
805   // Skip loading any extra custom elements in the extension dummy document
806   // and GeckoView windows.
807   const loadExtraCustomElements = !(
808     document.documentURI == "chrome://extensions/content/dummy.xhtml" ||
809     document.documentURI == "chrome://geckoview/content/geckoview.xhtml"
810   );
811   if (loadExtraCustomElements) {
812     for (let script of [
813       "chrome://global/content/elements/arrowscrollbox.js",
814       "chrome://global/content/elements/dialog.js",
815       "chrome://global/content/elements/general.js",
816       "chrome://global/content/elements/button.js",
817       "chrome://global/content/elements/checkbox.js",
818       "chrome://global/content/elements/menu.js",
819       "chrome://global/content/elements/menupopup.js",
820       "chrome://global/content/elements/moz-input-box.js",
821       "chrome://global/content/elements/notificationbox.js",
822       "chrome://global/content/elements/panel.js",
823       "chrome://global/content/elements/popupnotification.js",
824       "chrome://global/content/elements/radio.js",
825       "chrome://global/content/elements/richlistbox.js",
826       "chrome://global/content/elements/autocomplete-popup.js",
827       "chrome://global/content/elements/autocomplete-richlistitem.js",
828       "chrome://global/content/elements/tabbox.js",
829       "chrome://global/content/elements/text.js",
830       "chrome://global/content/elements/toolbarbutton.js",
831       "chrome://global/content/elements/tree.js",
832       "chrome://global/content/elements/wizard.js",
833     ]) {
834       Services.scriptloader.loadSubScript(script, window);
835     }
837     for (let [tag, script] of [
838       ["button-group", "chrome://global/content/elements/named-deck.js"],
839       ["findbar", "chrome://global/content/elements/findbar.js"],
840       ["menulist", "chrome://global/content/elements/menulist.js"],
841       ["message-bar", "chrome://global/content/elements/message-bar.js"],
842       ["named-deck", "chrome://global/content/elements/named-deck.js"],
843       ["named-deck-button", "chrome://global/content/elements/named-deck.js"],
844       ["search-textbox", "chrome://global/content/elements/search-textbox.js"],
845       ["stringbundle", "chrome://global/content/elements/stringbundle.js"],
846       [
847         "printpreview-toolbar",
848         "chrome://global/content/printPreviewToolbar.js",
849       ],
850       [
851         "printpreview-pagination",
852         "chrome://global/content/printPreviewPagination.js",
853       ],
854       [
855         "autocomplete-input",
856         "chrome://global/content/elements/autocomplete-input.js",
857       ],
858       ["editor", "chrome://global/content/elements/editor.js"],
859     ]) {
860       customElements.setElementCreationCallback(tag, () => {
861         Services.scriptloader.loadSubScript(script, window);
862       });
863     }
864   }
865 })();