From 43a97878ce14b72f0981164f87f2e35e14151312 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:22:09 +0200 Subject: Adding upstream version 110.0.1. Signed-off-by: Daniel Baumann --- toolkit/content/customElements.js | 851 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 851 insertions(+) create mode 100644 toolkit/content/customElements.js (limited to 'toolkit/content/customElements.js') diff --git a/toolkit/content/customElements.js b/toolkit/content/customElements.js new file mode 100644 index 0000000000..9562d69410 --- /dev/null +++ b/toolkit/content/customElements.js @@ -0,0 +1,851 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file defines these globals on the window object. +// Define them here so that ESLint can find them: +/* globals MozXULElement, MozHTMLElement, MozElements */ + +"use strict"; + +// This is loaded into chrome windows with the subscript loader. Wrap in +// a block to prevent accidentally leaking globals onto `window`. +(() => { + // Handle customElements.js being loaded as a script in addition to the subscriptLoader + // from MainProcessSingleton, to handle pages that can open both before and after + // MainProcessSingleton starts. See Bug 1501845. + if (window.MozXULElement) { + return; + } + + const MozElements = {}; + window.MozElements = MozElements; + + const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" + ); + const instrumentClasses = Services.env.get("MOZ_INSTRUMENT_CUSTOM_ELEMENTS"); + const instrumentedClasses = instrumentClasses ? new Set() : null; + const instrumentedBaseClasses = instrumentClasses ? new WeakSet() : null; + + // If requested, wrap the normal customElements.define to give us a chance + // to modify the class so we can instrument function calls in local development: + if (instrumentClasses) { + let define = window.customElements.define; + window.customElements.define = function(name, c, opts) { + instrumentCustomElementClass(c); + return define.call(this, name, c, opts); + }; + window.addEventListener( + "load", + () => { + MozElements.printInstrumentation(true); + }, + { once: true, capture: true } + ); + } + + MozElements.printInstrumentation = function(collapsed) { + let summaries = []; + let totalCalls = 0; + let totalTime = 0; + for (let c of instrumentedClasses) { + // Allow passing in something like MOZ_INSTRUMENT_CUSTOM_ELEMENTS=MozXULElement,Button to filter + let includeClass = + instrumentClasses == 1 || + instrumentClasses + .split(",") + .some(n => c.name.toLowerCase().includes(n.toLowerCase())); + let summary = c.__instrumentation_summary; + if (includeClass && summary) { + summaries.push(summary); + totalCalls += summary.totalCalls; + totalTime += summary.totalTime; + } + } + if (summaries.length) { + let groupName = `Instrumentation data for custom elements in ${document.documentURI}`; + console[collapsed ? "groupCollapsed" : "group"](groupName); + console.log( + `Total function calls ${totalCalls} and total time spent inside ${totalTime.toFixed( + 2 + )}` + ); + for (let summary of summaries) { + console.log(`${summary.name} (# instances: ${summary.instances})`); + if (Object.keys(summary.data).length > 1) { + console.table(summary.data); + } + } + console.groupEnd(groupName); + } + }; + + function instrumentCustomElementClass(c) { + // Climb up prototype chain to see if we inherit from a MozElement. + // Keep track of classes to instrument, for example: + // MozMenuCaption->MozMenuBase->BaseText->BaseControl->MozXULElement + let inheritsFromBase = instrumentedBaseClasses.has(c); + let classesToInstrument = [c]; + let proto = Object.getPrototypeOf(c); + while (proto) { + classesToInstrument.push(proto); + if (instrumentedBaseClasses.has(proto)) { + inheritsFromBase = true; + break; + } + proto = Object.getPrototypeOf(proto); + } + + if (inheritsFromBase) { + for (let c of classesToInstrument.reverse()) { + instrumentIndividualClass(c); + } + } + } + + function instrumentIndividualClass(c) { + if (instrumentedClasses.has(c)) { + return; + } + + instrumentedClasses.add(c); + let data = { instances: 0 }; + + function wrapFunction(name, fn) { + return function() { + if (!data[name]) { + data[name] = { time: 0, calls: 0 }; + } + data[name].calls++; + let n = performance.now(); + let r = fn.apply(this, arguments); + data[name].time += performance.now() - n; + return r; + }; + } + function wrapPropertyDescriptor(obj, name) { + if (name == "constructor") { + return; + } + let prop = Object.getOwnPropertyDescriptor(obj, name); + if (prop.get) { + prop.get = wrapFunction(` ${name}`, prop.get); + } + if (prop.set) { + prop.set = wrapFunction(` ${name}`, prop.set); + } + if (prop.writable && prop.value && prop.value.apply) { + prop.value = wrapFunction(name, prop.value); + } + Object.defineProperty(obj, name, prop); + } + + // Handle static properties + for (let name of Object.getOwnPropertyNames(c)) { + wrapPropertyDescriptor(c, name); + } + + // Handle instance properties + for (let name of Object.getOwnPropertyNames(c.prototype)) { + wrapPropertyDescriptor(c.prototype, name); + } + + c.__instrumentation_data = data; + Object.defineProperty(c, "__instrumentation_summary", { + enumerable: false, + configurable: false, + get() { + if (data.instances == 0) { + return null; + } + + let clonedData = JSON.parse(JSON.stringify(data)); + delete clonedData.instances; + let totalCalls = 0; + let totalTime = 0; + for (let d in clonedData) { + let { time, calls } = clonedData[d]; + time = parseFloat(time.toFixed(2)); + totalCalls += calls; + totalTime += time; + clonedData[d]["time (ms)"] = time; + delete clonedData[d].time; + clonedData[d].timePerCall = parseFloat((time / calls).toFixed(4)); + } + + let timePerCall = parseFloat((totalTime / totalCalls).toFixed(4)); + totalTime = parseFloat(totalTime.toFixed(2)); + + // Add a spaced-out final row with summed up totals + clonedData["\ntotals"] = { + "time (ms)": `\n${totalTime}`, + calls: `\n${totalCalls}`, + timePerCall: `\n${timePerCall}`, + }; + return { + instances: data.instances, + data: clonedData, + name: c.name, + totalCalls, + totalTime, + }; + }, + }); + } + + // The listener of DOMContentLoaded must be set on window, rather than + // document, because the window can go away before the event is fired. + // In that case, we don't want to initialize anything, otherwise we + // may be leaking things because they will never be destroyed after. + let gIsDOMContentLoaded = false; + const gElementsPendingConnection = new Set(); + window.addEventListener( + "DOMContentLoaded", + () => { + gIsDOMContentLoaded = true; + for (let element of gElementsPendingConnection) { + try { + if (element.isConnected) { + element.isRunningDelayedConnectedCallback = true; + element.connectedCallback(); + } + } catch (ex) { + console.error(ex); + } + element.isRunningDelayedConnectedCallback = false; + } + gElementsPendingConnection.clear(); + }, + { once: true, capture: true } + ); + + const gXULDOMParser = new DOMParser(); + gXULDOMParser.forceEnableXULXBL(); + + MozElements.MozElementMixin = Base => { + let MozElementBase = class extends Base { + constructor() { + super(); + + if (instrumentClasses) { + let proto = this.constructor; + while (proto && proto != Base) { + proto.__instrumentation_data.instances++; + proto = Object.getPrototypeOf(proto); + } + } + } + /* + * A declarative way to wire up attribute inheritance and automatically generate + * the `observedAttributes` getter. For example, if you returned: + * { + * ".foo": "bar,baz=bat" + * } + * + * Then the base class will automatically return ["bar", "bat"] from `observedAttributes`, + * and set up an `attributeChangedCallback` to pass those attributes down onto an element + * matching the ".foo" selector. + * + * See the `inheritAttribute` function for more details on the attribute string format. + * + * @return {Object} + */ + static get inheritedAttributes() { + return null; + } + + static get flippedInheritedAttributes() { + // Have to be careful here, if a subclass overrides inheritedAttributes + // and its parent class is instantiated first, then reading + // this._flippedInheritedAttributes on the child class will return the + // computed value from the parent. We store it separately on each class + // to ensure everything works correctly when inheritedAttributes is + // overridden. + if (!this.hasOwnProperty("_flippedInheritedAttributes")) { + let { inheritedAttributes } = this; + if (!inheritedAttributes) { + this._flippedInheritedAttributes = null; + } else { + this._flippedInheritedAttributes = {}; + for (let selector in inheritedAttributes) { + let attrRules = inheritedAttributes[selector].split(","); + for (let attrRule of attrRules) { + let attrName = attrRule; + let attrNewName = attrRule; + let split = attrName.split("="); + if (split.length == 2) { + attrName = split[1]; + attrNewName = split[0]; + } + + if (!this._flippedInheritedAttributes[attrName]) { + this._flippedInheritedAttributes[attrName] = []; + } + this._flippedInheritedAttributes[attrName].push([ + selector, + attrNewName, + ]); + } + } + } + } + + return this._flippedInheritedAttributes; + } + /* + * Generate this array based on `inheritedAttributes`, if any. A class is free to override + * this if it needs to do something more complex or wants to opt out of this behavior. + */ + static get observedAttributes() { + return Object.keys(this.flippedInheritedAttributes || {}); + } + + /* + * Provide default lifecycle callback for attribute changes that will inherit attributes + * based on the static `inheritedAttributes` Object. This can be overridden by callers. + */ + attributeChangedCallback(name, oldValue, newValue) { + if (oldValue === newValue || !this.initializedAttributeInheritance) { + return; + } + + let list = this.constructor.flippedInheritedAttributes[name]; + if (list) { + this.inheritAttribute(list, name); + } + } + + /* + * After setting content, calling this will cache the elements from selectors in the + * static `inheritedAttributes` Object. It'll also do an initial call to `this.inheritAttributes()`, + * so in the simple case, this is the only function you need to call. + * + * This should be called any time the children that are inheriting attributes changes. For instance, + * it's common in a connectedCallback to do something like: + * + * this.textContent = ""; + * this.append(MozXULElement.parseXULToFragment(`