From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- devtools/client/fronts/accessibility.js | 583 ++++++++++++++++ devtools/client/fronts/addon/addons.js | 25 + devtools/client/fronts/addon/moz.build | 10 + .../fronts/addon/webextension-inspected-window.js | 31 + devtools/client/fronts/animation.js | 214 ++++++ devtools/client/fronts/array-buffer.js | 26 + devtools/client/fronts/blackboxing.js | 17 + devtools/client/fronts/breakpoint-list.js | 17 + devtools/client/fronts/changes.js | 33 + devtools/client/fronts/compatibility.js | 18 + devtools/client/fronts/css-properties.js | 203 ++++++ .../client/fronts/descriptors/descriptor-mixin.js | 60 ++ .../client/fronts/descriptors/descriptor-types.js | 17 + devtools/client/fronts/descriptors/moz.build | 14 + devtools/client/fronts/descriptors/process.js | 142 ++++ devtools/client/fronts/descriptors/tab.js | 330 +++++++++ devtools/client/fronts/descriptors/webextension.js | 173 +++++ devtools/client/fronts/descriptors/worker.js | 146 ++++ devtools/client/fronts/device.js | 23 + devtools/client/fronts/frame.js | 27 + devtools/client/fronts/highlighters.js | 53 ++ devtools/client/fronts/inspector.js | 282 ++++++++ devtools/client/fronts/inspector/moz.build | 9 + devtools/client/fronts/inspector/rule-rewriter.js | 745 +++++++++++++++++++++ devtools/client/fronts/layout.js | 184 +++++ devtools/client/fronts/manifest.js | 25 + devtools/client/fronts/memory.js | 116 ++++ devtools/client/fronts/moz.build | 59 ++ devtools/client/fronts/network-content.js | 24 + devtools/client/fronts/network-parent.js | 27 + devtools/client/fronts/node.js | 633 +++++++++++++++++ devtools/client/fronts/object.js | 472 +++++++++++++ devtools/client/fronts/objects-manager.js | 25 + devtools/client/fronts/page-style.js | 123 ++++ devtools/client/fronts/perf.js | 22 + devtools/client/fronts/preference.js | 33 + .../client/fronts/private-properties-iterator.js | 60 ++ devtools/client/fronts/property-iterator.js | 67 ++ devtools/client/fronts/reflow.js | 31 + devtools/client/fronts/responsive.js | 28 + devtools/client/fronts/root.js | 332 +++++++++ devtools/client/fronts/screenshot-content.js | 25 + devtools/client/fronts/screenshot.js | 25 + devtools/client/fronts/source.js | 102 +++ devtools/client/fronts/storage.js | 55 ++ devtools/client/fronts/string.js | 62 ++ devtools/client/fronts/style-rule.js | 310 +++++++++ devtools/client/fronts/style-sheets.js | 37 + devtools/client/fronts/symbol-iterator.js | 56 ++ devtools/client/fronts/target-configuration.js | 33 + devtools/client/fronts/targets/content-process.js | 57 ++ devtools/client/fronts/targets/moz.build | 12 + devtools/client/fronts/targets/target-mixin.js | 630 +++++++++++++++++ devtools/client/fronts/targets/window-global.js | 172 +++++ devtools/client/fronts/targets/worker.js | 47 ++ devtools/client/fronts/thread-configuration.js | 25 + devtools/client/fronts/thread.js | 285 ++++++++ devtools/client/fronts/tracer.js | 22 + devtools/client/fronts/walker.js | 461 +++++++++++++ devtools/client/fronts/watcher.js | 202 ++++++ devtools/client/fronts/webconsole.js | 111 +++ devtools/client/fronts/worker/moz.build | 11 + devtools/client/fronts/worker/push-subscription.js | 30 + .../fronts/worker/service-worker-registration.js | 79 +++ devtools/client/fronts/worker/service-worker.js | 62 ++ 65 files changed, 8370 insertions(+) create mode 100644 devtools/client/fronts/accessibility.js create mode 100644 devtools/client/fronts/addon/addons.js create mode 100644 devtools/client/fronts/addon/moz.build create mode 100644 devtools/client/fronts/addon/webextension-inspected-window.js create mode 100644 devtools/client/fronts/animation.js create mode 100644 devtools/client/fronts/array-buffer.js create mode 100644 devtools/client/fronts/blackboxing.js create mode 100644 devtools/client/fronts/breakpoint-list.js create mode 100644 devtools/client/fronts/changes.js create mode 100644 devtools/client/fronts/compatibility.js create mode 100644 devtools/client/fronts/css-properties.js create mode 100644 devtools/client/fronts/descriptors/descriptor-mixin.js create mode 100644 devtools/client/fronts/descriptors/descriptor-types.js create mode 100644 devtools/client/fronts/descriptors/moz.build create mode 100644 devtools/client/fronts/descriptors/process.js create mode 100644 devtools/client/fronts/descriptors/tab.js create mode 100644 devtools/client/fronts/descriptors/webextension.js create mode 100644 devtools/client/fronts/descriptors/worker.js create mode 100644 devtools/client/fronts/device.js create mode 100644 devtools/client/fronts/frame.js create mode 100644 devtools/client/fronts/highlighters.js create mode 100644 devtools/client/fronts/inspector.js create mode 100644 devtools/client/fronts/inspector/moz.build create mode 100644 devtools/client/fronts/inspector/rule-rewriter.js create mode 100644 devtools/client/fronts/layout.js create mode 100644 devtools/client/fronts/manifest.js create mode 100644 devtools/client/fronts/memory.js create mode 100644 devtools/client/fronts/moz.build create mode 100644 devtools/client/fronts/network-content.js create mode 100644 devtools/client/fronts/network-parent.js create mode 100644 devtools/client/fronts/node.js create mode 100644 devtools/client/fronts/object.js create mode 100644 devtools/client/fronts/objects-manager.js create mode 100644 devtools/client/fronts/page-style.js create mode 100644 devtools/client/fronts/perf.js create mode 100644 devtools/client/fronts/preference.js create mode 100644 devtools/client/fronts/private-properties-iterator.js create mode 100644 devtools/client/fronts/property-iterator.js create mode 100644 devtools/client/fronts/reflow.js create mode 100644 devtools/client/fronts/responsive.js create mode 100644 devtools/client/fronts/root.js create mode 100644 devtools/client/fronts/screenshot-content.js create mode 100644 devtools/client/fronts/screenshot.js create mode 100644 devtools/client/fronts/source.js create mode 100644 devtools/client/fronts/storage.js create mode 100644 devtools/client/fronts/string.js create mode 100644 devtools/client/fronts/style-rule.js create mode 100644 devtools/client/fronts/style-sheets.js create mode 100644 devtools/client/fronts/symbol-iterator.js create mode 100644 devtools/client/fronts/target-configuration.js create mode 100644 devtools/client/fronts/targets/content-process.js create mode 100644 devtools/client/fronts/targets/moz.build create mode 100644 devtools/client/fronts/targets/target-mixin.js create mode 100644 devtools/client/fronts/targets/window-global.js create mode 100644 devtools/client/fronts/targets/worker.js create mode 100644 devtools/client/fronts/thread-configuration.js create mode 100644 devtools/client/fronts/thread.js create mode 100644 devtools/client/fronts/tracer.js create mode 100644 devtools/client/fronts/walker.js create mode 100644 devtools/client/fronts/watcher.js create mode 100644 devtools/client/fronts/webconsole.js create mode 100644 devtools/client/fronts/worker/moz.build create mode 100644 devtools/client/fronts/worker/push-subscription.js create mode 100644 devtools/client/fronts/worker/service-worker-registration.js create mode 100644 devtools/client/fronts/worker/service-worker.js (limited to 'devtools/client/fronts') diff --git a/devtools/client/fronts/accessibility.js b/devtools/client/fronts/accessibility.js new file mode 100644 index 0000000000..3a60856164 --- /dev/null +++ b/devtools/client/fronts/accessibility.js @@ -0,0 +1,583 @@ +/* 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/. */ + +"use strict"; + +const { + FrontClassWithSpec, + registerFront, +} = require("resource://devtools/shared/protocol.js"); +const { + accessibleSpec, + accessibleWalkerSpec, + accessibilitySpec, + parentAccessibilitySpec, + simulatorSpec, +} = require("resource://devtools/shared/specs/accessibility.js"); +const events = require("resource://devtools/shared/event-emitter.js"); + +class AccessibleFront extends FrontClassWithSpec(accessibleSpec) { + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); + + this.before("audited", this.audited.bind(this)); + this.before("name-change", this.nameChange.bind(this)); + this.before("value-change", this.valueChange.bind(this)); + this.before("description-change", this.descriptionChange.bind(this)); + this.before("shortcut-change", this.shortcutChange.bind(this)); + this.before("reorder", this.reorder.bind(this)); + this.before("text-change", this.textChange.bind(this)); + this.before("index-in-parent-change", this.indexInParentChange.bind(this)); + this.before("states-change", this.statesChange.bind(this)); + this.before("actions-change", this.actionsChange.bind(this)); + this.before("attributes-change", this.attributesChange.bind(this)); + } + + marshallPool() { + return this.getParent(); + } + + get useChildTargetToFetchChildren() { + return this._form.useChildTargetToFetchChildren; + } + + get role() { + return this._form.role; + } + + get name() { + return this._form.name; + } + + get value() { + return this._form.value; + } + + get description() { + return this._form.description; + } + + get keyboardShortcut() { + return this._form.keyboardShortcut; + } + + get childCount() { + return this._form.childCount; + } + + get domNodeType() { + return this._form.domNodeType; + } + + get indexInParent() { + return this._form.indexInParent; + } + + get states() { + return this._form.states; + } + + get actions() { + return this._form.actions; + } + + get attributes() { + return this._form.attributes; + } + + get checks() { + return this._form.checks; + } + + form(form) { + this.actorID = form.actor; + this._form = this._form || {}; + Object.assign(this._form, form); + } + + nameChange(name, parent) { + this._form.name = name; + // Name change event affects the tree rendering, we fire this event on + // accessibility walker as the point of interaction for UI. + const accessibilityWalkerFront = this.getParent(); + if (accessibilityWalkerFront) { + events.emit(accessibilityWalkerFront, "name-change", this, parent); + } + } + + valueChange(value) { + this._form.value = value; + } + + descriptionChange(description) { + this._form.description = description; + } + + shortcutChange(keyboardShortcut) { + this._form.keyboardShortcut = keyboardShortcut; + } + + reorder(childCount) { + this._form.childCount = childCount; + // Reorder event affects the tree rendering, we fire this event on + // accessibility walker as the point of interaction for UI. + const accessibilityWalkerFront = this.getParent(); + if (accessibilityWalkerFront) { + events.emit(accessibilityWalkerFront, "reorder", this); + } + } + + textChange() { + // Text event affects the tree rendering, we fire this event on + // accessibility walker as the point of interaction for UI. + const accessibilityWalkerFront = this.getParent(); + if (accessibilityWalkerFront) { + events.emit(accessibilityWalkerFront, "text-change", this); + } + } + + indexInParentChange(indexInParent) { + this._form.indexInParent = indexInParent; + } + + statesChange(states) { + this._form.states = states; + } + + actionsChange(actions) { + this._form.actions = actions; + } + + attributesChange(attributes) { + this._form.attributes = attributes; + } + + audited(checks) { + this._form.checks = this._form.checks || {}; + Object.assign(this._form.checks, checks); + } + + hydrate() { + return super.hydrate().then(properties => { + Object.assign(this._form, properties); + }); + } + + async children() { + if (!this.useChildTargetToFetchChildren) { + return super.children(); + } + + const { walker: domWalkerFront } = await this.targetFront.getFront( + "inspector" + ); + const node = await domWalkerFront.getNodeFromActor(this.actorID, [ + "rawAccessible", + "DOMNode", + ]); + // We are using DOM inspector/walker API here because we want to keep both + // the accessiblity tree and the DOM tree in sync. This is necessary for + // several features that the accessibility panel provides such as inspecting + // a corresponding DOM node or any other functionality that requires DOM + // node ancestries to be resolved all the way up to the top level document. + const { + nodes: [documentNodeFront], + } = await domWalkerFront.children(node); + const accessibilityFront = await documentNodeFront.targetFront.getFront( + "accessibility" + ); + + return accessibilityFront.accessibleWalkerFront.children(); + } + + /** + * Helper function that helps with building a complete snapshot of + * accessibility tree starting at the level of current accessible front. It + * accumulates subtrees from possible out of process frames that are children + * of the current accessible front. + * @param {JSON} snapshot + * Snapshot of the current accessible front or one of its in process + * children when recursing. + * + * @return {JSON} + * Complete snapshot of current accessible front. + */ + async _accumulateSnapshot(snapshot) { + const { childCount, useChildTargetToFetchChildren } = snapshot; + // No children, we are done. + if (childCount === 0) { + return snapshot; + } + + // If current accessible is not a remote frame, continue accumulating inside + // its children. + if (!useChildTargetToFetchChildren) { + const childSnapshots = []; + for (const childSnapshot of snapshot.children) { + childSnapshots.push(this._accumulateSnapshot(childSnapshot)); + } + await Promise.all(childSnapshots); + return snapshot; + } + + // When we have a remote frame, we need to obtain an accessible front for a + // remote frame document and retrieve its snapshot. + const inspectorFront = await this.targetFront.getFront("inspector"); + const frameNodeFront = + await inspectorFront.getNodeActorFromContentDomReference( + snapshot.contentDOMReference + ); + // Remove contentDOMReference and useChildTargetToFetchChildren properties. + delete snapshot.contentDOMReference; + delete snapshot.useChildTargetToFetchChildren; + if (!frameNodeFront) { + return snapshot; + } + + // Remote frame lives in the same process as the current accessible + // front we can retrieve the accessible front directly. + const frameAccessibleFront = await this.parentFront.getAccessibleFor( + frameNodeFront + ); + if (!frameAccessibleFront) { + return snapshot; + } + + const [docAccessibleFront] = await frameAccessibleFront.children(); + const childSnapshot = await docAccessibleFront.snapshot(); + snapshot.children.push(childSnapshot); + + return snapshot; + } + + /** + * Retrieves a complete JSON snapshot for an accessible subtree of a given + * accessible front (inclduing OOP frames). + */ + async snapshot() { + const snapshot = await super.snapshot(); + await this._accumulateSnapshot(snapshot); + return snapshot; + } +} + +class AccessibleWalkerFront extends FrontClassWithSpec(accessibleWalkerSpec) { + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); + + this.documentReady = this.documentReady.bind(this); + this.on("document-ready", this.documentReady); + } + + destroy() { + this.off("document-ready", this.documentReady); + super.destroy(); + } + + form(json) { + this.actorID = json.actor; + } + + documentReady() { + if (this.targetFront.isTopLevel) { + this.emit("top-level-document-ready"); + } + } + + pick(doFocus) { + if (doFocus) { + return this.pickAndFocus(); + } + + return super.pick(); + } + + /** + * Get the accessible object ancestry starting from the given accessible to + * the top level document. The top level document is in the top level content process. + * @param {Object} accessible + * Accessible front to determine the ancestry for. + * + * @return {Array} ancestry + * List of ancestry objects which consist of an accessible with its + * children. + */ + async getAncestry(accessible) { + const ancestry = await super.getAncestry(accessible); + + const parentTarget = await this.targetFront.getParentTarget(); + if (!parentTarget) { + return ancestry; + } + + // Get an accessible front for the parent frame. We go through the + // inspector's walker to keep both inspector and accessibility trees in + // sync. + const { walker: domWalkerFront } = await this.targetFront.getFront( + "inspector" + ); + const frameNodeFront = (await domWalkerFront.getRootNode()).parentNode(); + const accessibilityFront = await parentTarget.getFront("accessibility"); + const { accessibleWalkerFront } = accessibilityFront; + const frameAccessibleFront = await accessibleWalkerFront.getAccessibleFor( + frameNodeFront + ); + + if (!frameAccessibleFront) { + // Most likely we are inside a hidden frame. + return Promise.reject( + `Can't get the ancestry for an accessible front ${accessible.actorID}. It is in the detached tree.` + ); + } + + // Compose the final ancestry out of ancestry for the given accessible in + // the current process and recursively get the ancestry for the frame + // accessible. + ancestry.push( + { + accessible: frameAccessibleFront, + children: await frameAccessibleFront.children(), + }, + ...(await accessibleWalkerFront.getAncestry(frameAccessibleFront)) + ); + + return ancestry; + } + + /** + * Run an accessibility audit for a document that accessibility walker is + * responsible for (in process). In addition to plainly running an audit (in + * cases when the document is in the OOP frame), this method also updates + * relative ancestries of audited accessible objects all the way up to the top + * level document for the toolbox. + * @param {Object} options + * - {Array} types + * types of the accessibility issues to audit for + * - {Function} onProgress + * callback function for a progress audit-event + * - {Boolean} retrieveAncestries (defaults to true) + * Set to false to _not_ retrieve ancestries of audited accessible objects. + * This is used when a specific document is selected in the iframe picker + * and we want to treat it as the root of the accessibility panel tree. + */ + async audit({ types, onProgress, retrieveAncestries = true }) { + const onAudit = new Promise(resolve => { + const auditEventHandler = ({ type, ancestries, progress }) => { + switch (type) { + case "error": + this.off("audit-event", auditEventHandler); + resolve({ error: true }); + break; + case "completed": + this.off("audit-event", auditEventHandler); + resolve({ ancestries }); + break; + case "progress": + onProgress(progress); + break; + default: + break; + } + }; + + this.on("audit-event", auditEventHandler); + super.startAudit({ types }); + }); + + const audit = await onAudit; + // If audit resulted in an error, if there's nothing to report or if the callsite + // explicitly asked to not retrieve ancestries, we are done. + // (no need to check for ancestry across the remote frame hierarchy). + // See also https://bugzilla.mozilla.org/show_bug.cgi?id=1641551 why the rest of + // the code path is only supported when content toolbox fission is enabled. + if (audit.error || audit.ancestries.length === 0 || !retrieveAncestries) { + return audit; + } + + const parentTarget = await this.targetFront.getParentTarget(); + // If there is no parent target, we do not need to update ancestries as we + // are in the top level document. + if (!parentTarget) { + return audit; + } + + // Retrieve an ancestry (cross process) for a current root document and make + // audit report ancestries relative to it. + const [docAccessibleFront] = await this.children(); + let docAccessibleAncestry; + try { + docAccessibleAncestry = await this.getAncestry(docAccessibleFront); + } catch (e) { + // We are in a detached subtree. We do not consider this an error, instead + // we need to ignore the audit for this frame and return an empty report. + return { ancestries: [] }; + } + for (const ancestry of audit.ancestries) { + // Compose the final ancestries out of the ones in the audit report + // relative to this document and the ancestry of the document itself + // (cross process). + ancestry.push(...docAccessibleAncestry); + } + + return audit; + } + + /** + * A helper wrapper function to show tabbing order overlay for a given target. + * The only additional work done is resolving domnode front from a + * ContentDOMReference received from a remote target. + * + * @param {Object} startElm + * domnode front to be used as the starting point for generating the + * tabbing order. + * @param {Number} startIndex + * Starting index for the tabbing order. + */ + async _showTabbingOrder(startElm, startIndex) { + const { contentDOMReference, index } = await super.showTabbingOrder( + startElm, + startIndex + ); + let elm; + if (contentDOMReference) { + const inspectorFront = await this.targetFront.getFront("inspector"); + elm = await inspectorFront.getNodeActorFromContentDomReference( + contentDOMReference + ); + } + + return { elm, index }; + } + + /** + * Show tabbing order overlay for a given target. + * + * @param {Object} startElm + * domnode front to be used as the starting point for generating the + * tabbing order. + * @param {Number} startIndex + * Starting index for the tabbing order. + * + * @return {JSON} + * Tabbing order information for the last element in the tabbing + * order. It includes a domnode front and a tabbing index. If we are + * at the end of the tabbing order for the top level content document, + * the domnode front will be null. If focus manager discovered a + * remote IFRAME, then the domnode front is for the IFRAME itself. + */ + async showTabbingOrder(startElm, startIndex) { + let { elm: currentElm, index: currentIndex } = await this._showTabbingOrder( + startElm, + startIndex + ); + + // If no remote frames were found, currentElm will be null. + while (currentElm) { + // Safety check to ensure that the currentElm is a remote frame. + if (currentElm.useChildTargetToFetchChildren) { + const { walker: domWalkerFront } = + await currentElm.targetFront.getFront("inspector"); + const { + nodes: [childDocumentNodeFront], + } = await domWalkerFront.children(currentElm); + const { accessibleWalkerFront } = + await childDocumentNodeFront.targetFront.getFront("accessibility"); + // Show tabbing order in the remote target, while updating the tabbing + // index. + ({ index: currentIndex } = await accessibleWalkerFront.showTabbingOrder( + childDocumentNodeFront, + currentIndex + )); + } + + // Finished with the remote frame, continue in tabbing order, from the + // remote frame. + ({ elm: currentElm, index: currentIndex } = await this._showTabbingOrder( + currentElm, + currentIndex + )); + } + + return { elm: currentElm, index: currentIndex }; + } +} + +class AccessibilityFront extends FrontClassWithSpec(accessibilitySpec) { + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); + + this.before("init", this.init.bind(this)); + this.before("shutdown", this.shutdown.bind(this)); + + // Attribute name from which to retrieve the actorID out of the target + // actor's form + this.formAttributeName = "accessibilityActor"; + } + + async initialize() { + this.accessibleWalkerFront = await super.getWalker(); + this.simulatorFront = await super.getSimulator(); + const { enabled } = await super.bootstrap(); + this.enabled = enabled; + + try { + this._traits = await this.getTraits(); + } catch (e) { + // @backward-compat { version 84 } getTraits isn't available on older server. + this._traits = {}; + } + } + + get traits() { + return this._traits; + } + + init() { + this.enabled = true; + } + + shutdown() { + this.enabled = false; + } +} + +class ParentAccessibilityFront extends FrontClassWithSpec( + parentAccessibilitySpec +) { + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); + this.before("can-be-enabled-change", this.canBeEnabled.bind(this)); + this.before("can-be-disabled-change", this.canBeDisabled.bind(this)); + + // Attribute name from which to retrieve the actorID out of the target + // actor's form + this.formAttributeName = "parentAccessibilityActor"; + } + + async initialize() { + ({ canBeEnabled: this.canBeEnabled, canBeDisabled: this.canBeDisabled } = + await super.bootstrap()); + } + + canBeEnabled(canBeEnabled) { + this.canBeEnabled = canBeEnabled; + } + + canBeDisabled(canBeDisabled) { + this.canBeDisabled = canBeDisabled; + } +} + +const SimulatorFront = FrontClassWithSpec(simulatorSpec); + +exports.AccessibleFront = AccessibleFront; +registerFront(AccessibleFront); +exports.AccessibleWalkerFront = AccessibleWalkerFront; +registerFront(AccessibleWalkerFront); +exports.AccessibilityFront = AccessibilityFront; +registerFront(AccessibilityFront); +exports.ParentAccessibilityFront = ParentAccessibilityFront; +registerFront(ParentAccessibilityFront); +exports.SimulatorFront = SimulatorFront; +registerFront(SimulatorFront); diff --git a/devtools/client/fronts/addon/addons.js b/devtools/client/fronts/addon/addons.js new file mode 100644 index 0000000000..84d495b850 --- /dev/null +++ b/devtools/client/fronts/addon/addons.js @@ -0,0 +1,25 @@ +/* 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/. */ + +"use strict"; + +const { + addonsSpec, +} = require("resource://devtools/shared/specs/addon/addons.js"); +const { + FrontClassWithSpec, + registerFront, +} = require("resource://devtools/shared/protocol.js"); + +class AddonsFront extends FrontClassWithSpec(addonsSpec) { + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); + + // Attribute name from which to retrieve the actorID out of the target actor's form + this.formAttributeName = "addonsActor"; + } +} + +exports.AddonsFront = AddonsFront; +registerFront(AddonsFront); diff --git a/devtools/client/fronts/addon/moz.build b/devtools/client/fronts/addon/moz.build new file mode 100644 index 0000000000..e382173641 --- /dev/null +++ b/devtools/client/fronts/addon/moz.build @@ -0,0 +1,10 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +DevToolsModules( + "addons.js", + "webextension-inspected-window.js", +) diff --git a/devtools/client/fronts/addon/webextension-inspected-window.js b/devtools/client/fronts/addon/webextension-inspected-window.js new file mode 100644 index 0000000000..cf0674dfa8 --- /dev/null +++ b/devtools/client/fronts/addon/webextension-inspected-window.js @@ -0,0 +1,31 @@ +/* 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/. */ + +"use strict"; + +const { + webExtensionInspectedWindowSpec, +} = require("resource://devtools/shared/specs/addon/webextension-inspected-window.js"); + +const { + FrontClassWithSpec, + registerFront, +} = require("resource://devtools/shared/protocol.js"); + +/** + * The corresponding Front object for the WebExtensionInspectedWindowActor. + */ +class WebExtensionInspectedWindowFront extends FrontClassWithSpec( + webExtensionInspectedWindowSpec +) { + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); + + // Attribute name from which to retrieve the actorID out of the target actor's form + this.formAttributeName = "webExtensionInspectedWindowActor"; + } +} + +exports.WebExtensionInspectedWindowFront = WebExtensionInspectedWindowFront; +registerFront(WebExtensionInspectedWindowFront); diff --git a/devtools/client/fronts/animation.js b/devtools/client/fronts/animation.js new file mode 100644 index 0000000000..7dd17fc6e5 --- /dev/null +++ b/devtools/client/fronts/animation.js @@ -0,0 +1,214 @@ +/* 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/. */ + +"use strict"; + +const { + FrontClassWithSpec, + registerFront, +} = require("resource://devtools/shared/protocol.js"); +const { + animationPlayerSpec, + animationsSpec, +} = require("resource://devtools/shared/specs/animation.js"); + +class AnimationPlayerFront extends FrontClassWithSpec(animationPlayerSpec) { + constructor(conn, targetFront, parentFront) { + super(conn, targetFront, parentFront); + + this.state = {}; + this.before("changed", this.onChanged.bind(this)); + } + + form(form) { + this._form = form; + this.state = this.initialState; + } + + /** + * If the AnimationsActor was given a reference to the WalkerActor previously + * then calling this getter will return the animation target NodeFront. + */ + get animationTargetNodeFront() { + if (!this._form.animationTargetNodeActorID) { + return null; + } + + return this.conn.getFrontByID(this._form.animationTargetNodeActorID); + } + + /** + * Getter for the initial state of the player. Up to date states can be + * retrieved by calling the getCurrentState method. + */ + get initialState() { + return { + type: this._form.type, + startTime: this._form.startTime, + currentTime: this._form.currentTime, + playState: this._form.playState, + playbackRate: this._form.playbackRate, + name: this._form.name, + duration: this._form.duration, + delay: this._form.delay, + endDelay: this._form.endDelay, + iterationCount: this._form.iterationCount, + iterationStart: this._form.iterationStart, + easing: this._form.easing, + fill: this._form.fill, + direction: this._form.direction, + animationTimingFunction: this._form.animationTimingFunction, + isRunningOnCompositor: this._form.isRunningOnCompositor, + propertyState: this._form.propertyState, + documentCurrentTime: this._form.documentCurrentTime, + createdTime: this._form.createdTime, + currentTimeAtCreated: this._form.currentTimeAtCreated, + absoluteValues: this.calculateAbsoluteValues(this._form), + properties: this._form.properties, + }; + } + + /** + * Executed when the AnimationPlayerActor emits a "changed" event. Used to + * update the local knowledge of the state. + */ + onChanged(partialState) { + const { state } = this.reconstructState(partialState); + this.state = state; + } + + /** + * Refresh the current state of this animation on the client from information + * found on the server. Doesn't return anything, just stores the new state. + */ + async refreshState() { + const data = await this.getCurrentState(); + if (this.currentStateHasChanged) { + this.state = data; + } + } + + /** + * getCurrentState interceptor re-constructs incomplete states since the actor + * only sends the values that have changed. + */ + getCurrentState() { + this.currentStateHasChanged = false; + return super.getCurrentState().then(partialData => { + const { state, hasChanged } = this.reconstructState(partialData); + this.currentStateHasChanged = hasChanged; + return state; + }); + } + + reconstructState(data) { + let hasChanged = false; + + for (const key in this.state) { + if (typeof data[key] === "undefined") { + data[key] = this.state[key]; + } else if (data[key] !== this.state[key]) { + hasChanged = true; + } + } + + data.absoluteValues = this.calculateAbsoluteValues(data); + return { state: data, hasChanged }; + } + + calculateAbsoluteValues(data) { + const { + createdTime, + currentTime, + currentTimeAtCreated, + delay, + duration, + endDelay = 0, + fill, + iterationCount, + playbackRate, + } = data; + + const toRate = v => v / Math.abs(playbackRate); + const isPositivePlaybackRate = playbackRate > 0; + let absoluteDelay = 0; + let absoluteEndDelay = 0; + let isDelayFilled = false; + let isEndDelayFilled = false; + + if (isPositivePlaybackRate) { + absoluteDelay = toRate(delay); + absoluteEndDelay = toRate(endDelay); + isDelayFilled = fill === "both" || fill === "backwards"; + isEndDelayFilled = fill === "both" || fill === "forwards"; + } else { + absoluteDelay = toRate(endDelay); + absoluteEndDelay = toRate(delay); + isDelayFilled = fill === "both" || fill === "forwards"; + isEndDelayFilled = fill === "both" || fill === "backwards"; + } + + let endTime = 0; + + if (duration === Infinity) { + // Set endTime so as to enable the scrubber with keeping the consinstency of UI + // even the duration was Infinity. In case of delay is longer than zero, handle + // the graph duration as double of the delay amount. In case of no delay, handle + // the duration as 1ms which is short enough so as to make the scrubber movable + // and the limited duration is prioritized. + endTime = absoluteDelay > 0 ? absoluteDelay * 2 : 1; + } else { + endTime = + absoluteDelay + + toRate(duration * (iterationCount || 1)) + + absoluteEndDelay; + } + + const absoluteCreatedTime = isPositivePlaybackRate + ? createdTime + : createdTime - endTime; + const absoluteCurrentTimeAtCreated = isPositivePlaybackRate + ? currentTimeAtCreated + : endTime - currentTimeAtCreated; + const animationCurrentTime = isPositivePlaybackRate + ? currentTime + : endTime - currentTime; + const absoluteCurrentTime = + absoluteCreatedTime + toRate(animationCurrentTime); + const absoluteStartTime = absoluteCreatedTime + Math.min(absoluteDelay, 0); + const absoluteStartTimeAtCreated = + absoluteCreatedTime + absoluteCurrentTimeAtCreated; + // To show whole graph with endDelay, we add negative endDelay amount to endTime. + const endTimeWithNegativeEndDelay = endTime - Math.min(absoluteEndDelay, 0); + const absoluteEndTime = absoluteCreatedTime + endTimeWithNegativeEndDelay; + + return { + createdTime: absoluteCreatedTime, + currentTime: absoluteCurrentTime, + currentTimeAtCreated: absoluteCurrentTimeAtCreated, + delay: absoluteDelay, + endDelay: absoluteEndDelay, + endTime: absoluteEndTime, + isDelayFilled, + isEndDelayFilled, + startTime: absoluteStartTime, + startTimeAtCreated: absoluteStartTimeAtCreated, + }; + } +} + +exports.AnimationPlayerFront = AnimationPlayerFront; +registerFront(AnimationPlayerFront); + +class AnimationsFront extends FrontClassWithSpec(animationsSpec) { + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); + + // Attribute name from which to retrieve the actorID out of the target actor's form + this.formAttributeName = "animationsActor"; + } +} + +exports.AnimationsFront = AnimationsFront; +registerFront(AnimationsFront); diff --git a/devtools/client/fronts/array-buffer.js b/devtools/client/fronts/array-buffer.js new file mode 100644 index 0000000000..c7741ffe3f --- /dev/null +++ b/devtools/client/fronts/array-buffer.js @@ -0,0 +1,26 @@ +/* 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/. */ + +"use strict"; + +const { + arrayBufferSpec, +} = require("resource://devtools/shared/specs/array-buffer.js"); +const { + FrontClassWithSpec, + registerFront, +} = require("resource://devtools/shared/protocol.js"); + +/** + * A ArrayBufferClient provides a way to access ArrayBuffer from the + * devtools server. + */ +class ArrayBufferFront extends FrontClassWithSpec(arrayBufferSpec) { + form(json) { + this.length = json.length; + } +} + +exports.ArrayBufferFront = ArrayBufferFront; +registerFront(ArrayBufferFront); diff --git a/devtools/client/fronts/blackboxing.js b/devtools/client/fronts/blackboxing.js new file mode 100644 index 0000000000..ab26c484cc --- /dev/null +++ b/devtools/client/fronts/blackboxing.js @@ -0,0 +1,17 @@ +/* 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/. */ + +"use strict"; + +const { + FrontClassWithSpec, + registerFront, +} = require("resource://devtools/shared/protocol.js"); +const { + blackboxingSpec, +} = require("resource://devtools/shared/specs/blackboxing.js"); + +class BlackboxingFront extends FrontClassWithSpec(blackboxingSpec) {} + +registerFront(BlackboxingFront); diff --git a/devtools/client/fronts/breakpoint-list.js b/devtools/client/fronts/breakpoint-list.js new file mode 100644 index 0000000000..c8fc57226b --- /dev/null +++ b/devtools/client/fronts/breakpoint-list.js @@ -0,0 +1,17 @@ +/* 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/. */ + +"use strict"; + +const { + FrontClassWithSpec, + registerFront, +} = require("resource://devtools/shared/protocol.js"); +const { + breakpointListSpec, +} = require("resource://devtools/shared/specs/breakpoint-list.js"); + +class BreakpointListFront extends FrontClassWithSpec(breakpointListSpec) {} + +registerFront(BreakpointListFront); diff --git a/devtools/client/fronts/changes.js b/devtools/client/fronts/changes.js new file mode 100644 index 0000000000..4737156dcf --- /dev/null +++ b/devtools/client/fronts/changes.js @@ -0,0 +1,33 @@ +/* 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/. */ + +"use strict"; + +const { + FrontClassWithSpec, + registerFront, +} = require("resource://devtools/shared/protocol.js"); +const { changesSpec } = require("resource://devtools/shared/specs/changes.js"); + +/** + * ChangesFront, the front object for the ChangesActor + */ +class ChangesFront extends FrontClassWithSpec(changesSpec) { + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); + + // Attribute name from which to retrieve the actorID out of the target actor's form + this.formAttributeName = "changesActor"; + } + + async initialize() { + // Ensure the corresponding ChangesActor is immediately available and ready to track + // changes by calling a method on it. Actors are lazy and won't be created until + // actually used. + await super.start(); + } +} + +exports.ChangesFront = ChangesFront; +registerFront(ChangesFront); diff --git a/devtools/client/fronts/compatibility.js b/devtools/client/fronts/compatibility.js new file mode 100644 index 0000000000..292411e6af --- /dev/null +++ b/devtools/client/fronts/compatibility.js @@ -0,0 +1,18 @@ +/* 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/. */ + +"use strict"; + +const { + FrontClassWithSpec, + registerFront, +} = require("resource://devtools/shared/protocol.js"); +const { + compatibilitySpec, +} = require("resource://devtools/shared/specs/compatibility.js"); + +class CompatibilityFront extends FrontClassWithSpec(compatibilitySpec) {} + +exports.CompatibilityFront = CompatibilityFront; +registerFront(CompatibilityFront); diff --git a/devtools/client/fronts/css-properties.js b/devtools/client/fronts/css-properties.js new file mode 100644 index 0000000000..93e9d52900 --- /dev/null +++ b/devtools/client/fronts/css-properties.js @@ -0,0 +1,203 @@ +/* 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/. */ + +"use strict"; + +const { + FrontClassWithSpec, + registerFront, +} = require("resource://devtools/shared/protocol.js"); +const { + cssPropertiesSpec, +} = require("resource://devtools/shared/specs/css-properties.js"); + +loader.lazyRequireGetter( + this, + "cssColors", + "resource://devtools/shared/css/color-db.js", + true +); +loader.lazyRequireGetter( + this, + "CSS_TYPES", + "resource://devtools/shared/css/constants.js", + true +); +loader.lazyRequireGetter( + this, + "isCssVariable", + "resource://devtools/shared/inspector/css-logic.js", + true +); + +/** + * The CssProperties front provides a mechanism to have a one-time asynchronous + * load of a CSS properties database. This is then fed into the CssProperties + * interface that provides synchronous methods for finding out what CSS + * properties the current server supports. + */ +class CssPropertiesFront extends FrontClassWithSpec(cssPropertiesSpec) { + constructor(client, targetFront) { + super(client, targetFront); + + // Attribute name from which to retrieve the actorID out of the target actor's form + this.formAttributeName = "cssPropertiesActor"; + } + + async initialize() { + const db = await super.getCSSDatabase(); + this.cssProperties = new CssProperties(normalizeCssData(db)); + } + + destroy() { + this.cssProperties = null; + super.destroy(); + } +} + +/** + * Ask questions to a CSS database. This class does not care how the database + * gets loaded in, only the questions that you can ask to it. + * Prototype functions are bound to 'this' so they can be passed around as helper + * functions. + * + * @param {Object} db + * A database of CSS properties + * @param {Object} inheritedList + * The key is the property name, the value is whether or not + * that property is inherited. + */ +function CssProperties(db) { + this.properties = db.properties; + + this.isKnown = this.isKnown.bind(this); + this.isInherited = this.isInherited.bind(this); + this.supportsType = this.supportsType.bind(this); +} + +CssProperties.prototype = { + /** + * Checks to see if the property is known by the browser. This function has + * `this` already bound so that it can be passed around by reference. + * + * @param {String} property The property name to be checked. + * @return {Boolean} + */ + isKnown(property) { + // Custom Property Names (aka CSS Variables) are case-sensitive; do not lowercase. + property = property.startsWith("--") ? property : property.toLowerCase(); + return !!this.properties[property] || isCssVariable(property); + }, + + /** + * Checks to see if the property is an inherited one. + * + * @param {String} property The property name to be checked. + * @return {Boolean} + */ + isInherited(property) { + return this.properties[property]?.isInherited; + }, + + /** + * Checks if the property supports the given CSS type. + * + * @param {String} property The property to be checked. + * @param {String} type One of the values from InspectorPropertyType. + * @return {Boolean} + */ + supportsType(property, type) { + const id = CSS_TYPES[type]; + return ( + this.properties[property] && + (this.properties[property].supports.includes(type) || + this.properties[property].supports.includes(id)) + ); + }, + + /** + * Gets the CSS values for a given property name. + * + * @param {String} property The property to use. + * @return {Array} An array of strings. + */ + getValues(property) { + return this.properties[property] ? this.properties[property].values : []; + }, + + /** + * Gets the CSS property names. + * + * @return {Array} An array of strings. + */ + getNames(property) { + return Object.keys(this.properties); + }, + + /** + * Return a list of subproperties for the given property. If |name| + * does not name a valid property, an empty array is returned. If + * the property is not a shorthand property, then array containing + * just the property itself is returned. + * + * @param {String} name The property to query + * @return {Array} An array of subproperty names. + */ + getSubproperties(name) { + // Custom Property Names (aka CSS Variables) are case-sensitive; do not lowercase. + name = name.startsWith("--") ? name : name.toLowerCase(); + if (this.isKnown(name)) { + if (this.properties[name] && this.properties[name].subproperties) { + return this.properties[name].subproperties; + } + return [name]; + } + return []; + }, +}; + +/** + * Even if the target has the cssProperties actor, the returned data may not be in the + * same shape or have all of the data we need. This normalizes the data and fills in + * any missing information like color values. + * + * @return {Object} The normalized CSS database. + */ +function normalizeCssData(db) { + // If there is a `from` attributes, it means that it comes from RDP + // and it is not the client `generateCssProperties()` object passed by tests. + if (typeof db.from == "string") { + // This is where to put backward compat tweaks here to support old runtimes. + } + + reattachCssColorValues(db); + + return db; +} + +/** + * Color values are omitted to save on space. Add them back here. + * @param {Object} The CSS database. + */ +function reattachCssColorValues(db) { + if (db.properties.color.values[0] === "COLOR") { + const colors = Object.keys(cssColors); + + for (const name in db.properties) { + const property = db.properties[name]; + // "values" can be undefined if {name} was not found in CSS_PROPERTIES_DB. + if (property.values && property.values[0] === "COLOR") { + property.values.shift(); + property.values = property.values.concat(colors).sort(); + } + } + } +} + +module.exports = { + CssPropertiesFront, + CssProperties, + normalizeCssData, +}; +registerFront(CssPropertiesFront); diff --git a/devtools/client/fronts/descriptors/descriptor-mixin.js b/devtools/client/fronts/descriptors/descriptor-mixin.js new file mode 100644 index 0000000000..ac38e104b5 --- /dev/null +++ b/devtools/client/fronts/descriptors/descriptor-mixin.js @@ -0,0 +1,60 @@ +/* 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/. */ + +"use strict"; + +/** + * A Descriptor represents a debuggable context. It can be a browser tab, a tab on + * a remote device, like a tab on Firefox for Android. But it can also be an add-on, + * as well as firefox parent process, or just one of its content process. + * It can be very similar to a Target. The key difference is the lifecycle of these two classes. + * The descriptor is meant to be always alive and meaningful/usable until the end of the RDP connection. + * Typically a Tab Descriptor will describe the tab and not the one document currently loaded in this tab, + * while the Target, will describe this one document and a new Target may be created on each navigation. + */ +function DescriptorMixin(parentClass) { + class Descriptor extends parentClass { + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); + + this._client = client; + + // Pass a true value in order to distinguish this event reception + // from any manual destroy caused by the frontend + this.on( + "descriptor-destroyed", + this.destroy.bind(this, { isServerDestroyEvent: true }) + ); + } + + get client() { + return this._client; + } + + async destroy({ isServerDestroyEvent } = {}) { + if (this.isDestroyed()) { + return; + } + // This workaround is mostly done for Workers, as WorkerDescriptor + // extends the Target class, which causes some issue down the road: + // In Target.destroy, we call WorkerDescriptorActor.detach *before* calling super.destroy(), + // and so hold on before calling Front.destroy() which would reject all pending requests, including detach(). + // When calling detach, the server will emit "descriptor-destroyed", which will call Target.destroy again, + // but will still be blocked on detach resolution and won't call Front.destroy, and won't reject pending requests either. + // + // So call Front.baseFrontClassDestroyed manually from here, so that we ensure rejecting the pending detach request + // and unblock Target.destroy resolution. + // + // Here is the inheritance chain for WorkerDescriptor: + // WorkerDescriptor -> Descriptor (from descriptor-mixin.js) -> Target (from target-mixin.js) -> Front (protocol.js) -> Pool (protocol.js) -> EventEmitter + if (isServerDestroyEvent) { + this.baseFrontClassDestroy(); + } + + await super.destroy(); + } + } + return Descriptor; +} +exports.DescriptorMixin = DescriptorMixin; diff --git a/devtools/client/fronts/descriptors/descriptor-types.js b/devtools/client/fronts/descriptors/descriptor-types.js new file mode 100644 index 0000000000..00164509ab --- /dev/null +++ b/devtools/client/fronts/descriptors/descriptor-types.js @@ -0,0 +1,17 @@ +/* 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/. */ + +"use strict"; + +/** + * List of all descriptor types. + * + * This will be expose via `descriptorType` on all descriptor fronts. + */ +module.exports = { + PROCESS: "process", + WORKER: "worker", + TAB: "tab", + EXTENSION: "extension", +}; diff --git a/devtools/client/fronts/descriptors/moz.build b/devtools/client/fronts/descriptors/moz.build new file mode 100644 index 0000000000..665a0f252a --- /dev/null +++ b/devtools/client/fronts/descriptors/moz.build @@ -0,0 +1,14 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +DevToolsModules( + "descriptor-mixin.js", + "descriptor-types.js", + "process.js", + "tab.js", + "webextension.js", + "worker.js", +) diff --git a/devtools/client/fronts/descriptors/process.js b/devtools/client/fronts/descriptors/process.js new file mode 100644 index 0000000000..6efc1bd4d7 --- /dev/null +++ b/devtools/client/fronts/descriptors/process.js @@ -0,0 +1,142 @@ +/* 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/. */ +"use strict"; + +const { + processDescriptorSpec, +} = require("resource://devtools/shared/specs/descriptors/process.js"); +const { + WindowGlobalTargetFront, +} = require("resource://devtools/client/fronts/targets/window-global.js"); +const { + ContentProcessTargetFront, +} = require("resource://devtools/client/fronts/targets/content-process.js"); +const { + FrontClassWithSpec, + registerFront, +} = require("resource://devtools/shared/protocol.js"); +const { + DescriptorMixin, +} = require("resource://devtools/client/fronts/descriptors/descriptor-mixin.js"); +const DESCRIPTOR_TYPES = require("resource://devtools/client/fronts/descriptors/descriptor-types.js"); + +class ProcessDescriptorFront extends DescriptorMixin( + FrontClassWithSpec(processDescriptorSpec) +) { + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); + this._isParent = false; + this._processTargetFront = null; + this._targetFrontPromise = null; + } + + descriptorType = DESCRIPTOR_TYPES.PROCESS; + + form(json) { + this.id = json.id; + this._isParent = json.isParent; + this._isWindowlessParent = json.isWindowlessParent; + this.traits = json.traits || {}; + } + + async _createProcessTargetFront(form) { + let front = null; + // the request to getTarget may return a ContentProcessTargetActor or a + // ParentProcessTargetActor. In most cases getProcess(0) will return the + // main process target actor, which is a ParentProcessTargetActor, but + // not in xpcshell, which uses a ContentProcessTargetActor. So select + // the right front based on the actor ID. + if (form.actor.includes("parentProcessTarget")) { + // ParentProcessTargetActor doesn't have a specific front, instead it uses + // WindowGlobalTargetFront on the client side. + front = new WindowGlobalTargetFront(this._client, null, this); + } else { + front = new ContentProcessTargetFront(this._client, null, this); + } + // As these fronts aren't instantiated by protocol.js, we have to set their actor ID + // manually like that: + front.actorID = form.actor; + front.form(form); + + // @backward-compat { version 84 } Older server don't send the processID in the form + if (!front.processID) { + front.processID = this.id; + } + + this.manage(front); + return front; + } + + /** + * This flag should be true for parent process descriptors of a regular + * browser instance, where you can expect the target to be associated with a + * window global. + * + * This will typically be true for the descriptor used by the Browser Toolbox + * or the Browser Console opened against a regular Firefox instance. + * + * On the contrary this will be false for parent process descriptors created + * for xpcshell debugging or for background task debugging. + */ + get isBrowserProcessDescriptor() { + return this._isParent && !this._isWindowlessParent; + } + + get isParentProcessDescriptor() { + return this._isParent; + } + + get isProcessDescriptor() { + return true; + } + + getCachedTarget() { + return this._processTargetFront; + } + + async getTarget() { + // Only return the cached Target if it is still alive. + if (this._processTargetFront && !this._processTargetFront.isDestroyed()) { + return this._processTargetFront; + } + // Otherwise, ensure that we don't try to spawn more than one Target by + // returning the pending promise + if (this._targetFrontPromise) { + return this._targetFrontPromise; + } + this._targetFrontPromise = (async () => { + let targetFront = null; + try { + const targetForm = await super.getTarget(); + targetFront = await this._createProcessTargetFront(targetForm); + } catch (e) { + // This is likely to happen if we get a lot of events which drop previous + // processes. + console.log( + `Request to connect to ProcessDescriptor "${this.id}" failed: ${e}` + ); + } + // Save the reference to the target only after the call to attach + // so that getTarget always returns the attached target in case of concurrent calls + this._processTargetFront = targetFront; + // clear the promise if we are finished so that we can re-connect if + // necessary + this._targetFrontPromise = null; + return targetFront; + })(); + return this._targetFrontPromise; + } + + destroy() { + if (this._processTargetFront) { + this._processTargetFront.destroy(); + this._processTargetFront = null; + } + this._targetFrontPromise = null; + super.destroy(); + } +} + +exports.ProcessDescriptorFront = ProcessDescriptorFront; +registerFront(ProcessDescriptorFront); diff --git a/devtools/client/fronts/descriptors/tab.js b/devtools/client/fronts/descriptors/tab.js new file mode 100644 index 0000000000..097aed7674 --- /dev/null +++ b/devtools/client/fronts/descriptors/tab.js @@ -0,0 +1,330 @@ +/* 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/. */ +"use strict"; + +const { + tabDescriptorSpec, +} = require("resource://devtools/shared/specs/descriptors/tab.js"); +const DESCRIPTOR_TYPES = require("resource://devtools/client/fronts/descriptors/descriptor-types.js"); + +loader.lazyRequireGetter( + this, + "gDevTools", + "resource://devtools/client/framework/devtools.js", + true +); +loader.lazyRequireGetter( + this, + "WindowGlobalTargetFront", + "resource://devtools/client/fronts/targets/window-global.js", + true +); +const { + FrontClassWithSpec, + registerFront, +} = require("resource://devtools/shared/protocol.js"); +const { + DescriptorMixin, +} = require("resource://devtools/client/fronts/descriptors/descriptor-mixin.js"); + +const POPUP_DEBUG_PREF = "devtools.popups.debug"; + +/** + * DescriptorFront for tab targets. + * + * @fires remoteness-change + * Fired only for target switching, when the debugged tab is a local tab. + * TODO: This event could move to the server in order to support + * remoteness change for remote debugging. + */ +class TabDescriptorFront extends DescriptorMixin( + FrontClassWithSpec(tabDescriptorSpec) +) { + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); + + // The tab descriptor can be configured to create either local tab targets + // (eg, regular tab toolbox) or browsing context targets (eg tab remote + // debugging). + this._localTab = null; + + // Flag to prevent the server from trying to spawn targets by the watcher actor. + this._disableTargetSwitching = false; + + this._onTargetDestroyed = this._onTargetDestroyed.bind(this); + this._handleTabEvent = this._handleTabEvent.bind(this); + + // When the target is created from the server side, + // it is not created via TabDescriptor.getTarget. + // Instead, it is retrieved by the TargetCommand which + // will call TabDescriptor.setTarget from TargetCommand.onTargetAvailable + if (this.isServerTargetSwitchingEnabled()) { + this._targetFrontPromise = new Promise( + r => (this._resolveTargetFrontPromise = r) + ); + } + } + + descriptorType = DESCRIPTOR_TYPES.TAB; + + form(json) { + this.actorID = json.actor; + this._form = json; + this.traits = json.traits || {}; + } + + /** + * Destroy the front. + * + * @param Boolean If true, it means that we destroy the front when receiving the descriptor-destroyed + * event from the server. + */ + destroy({ isServerDestroyEvent = false } = {}) { + if (this.isDestroyed()) { + return; + } + + // The descriptor may be destroyed first by the frontend. + // When closing the tab, the toolbox document is almost immediately removed from the DOM. + // The `unload` event fires and toolbox destroys itself, as well as its related client. + // + // In such case, we emit the descriptor-destroyed event + if (!isServerDestroyEvent) { + this.emit("descriptor-destroyed"); + } + if (this.isLocalTab) { + this._teardownLocalTabListeners(); + } + super.destroy(); + } + + getWatcher() { + const isPopupDebuggingEnabled = Services.prefs.getBoolPref( + POPUP_DEBUG_PREF, + false + ); + return super.getWatcher({ + isServerTargetSwitchingEnabled: this.isServerTargetSwitchingEnabled(), + isPopupDebuggingEnabled, + }); + } + + setLocalTab(localTab) { + this._localTab = localTab; + this._setupLocalTabListeners(); + } + + get isTabDescriptor() { + return true; + } + + get isLocalTab() { + return !!this._localTab; + } + + get localTab() { + return this._localTab; + } + + _setupLocalTabListeners() { + this.localTab.addEventListener("TabClose", this._handleTabEvent); + this.localTab.addEventListener("TabRemotenessChange", this._handleTabEvent); + } + + _teardownLocalTabListeners() { + this.localTab.removeEventListener("TabClose", this._handleTabEvent); + this.localTab.removeEventListener( + "TabRemotenessChange", + this._handleTabEvent + ); + } + + isServerTargetSwitchingEnabled() { + return !this._disableTargetSwitching; + } + + /** + * Called by CommandsFactory, when the WebExtension codebase instantiates + * a commands. We have to flag the TabDescriptor for them as they don't support + * target switching and gets severely broken when enabling server target which + * introduce target switching for all navigations and reloads + */ + setIsForWebExtension() { + this.disableTargetSwitching(); + } + + /** + * Method used by the WebExtension which still need to disable server side targets, + * and also a few xpcshell tests which are using legacy API and don't support watcher actor. + */ + disableTargetSwitching() { + this._disableTargetSwitching = true; + // Delete these two attributes which have to be set early from the constructor, + // but we don't know yet if target switch should be disabled. + delete this._targetFrontPromise; + delete this._resolveTargetFrontPromise; + } + + get isZombieTab() { + return this._form.isZombieTab; + } + + get browserId() { + return this._form.browserId; + } + + get selected() { + return this._form.selected; + } + + get title() { + return this._form.title; + } + + get url() { + return this._form.url; + } + + get favicon() { + // Note: the favicon is not part of the default form() payload, it will be + // added in `retrieveFavicon`. + return this._form.favicon; + } + + _createTabTarget(form) { + const front = new WindowGlobalTargetFront(this._client, null, this); + + // As these fronts aren't instantiated by protocol.js, we have to set their actor ID + // manually like that: + front.actorID = form.actor; + front.form(form); + this.manage(front); + return front; + } + + _onTargetDestroyed() { + // Clear the cached targetFront when the target is destroyed. + // Note that we are also checking that _targetFront has a valid actorID + // in getTarget, this acts as an additional security to avoid races. + this._targetFront = null; + } + + /** + * Safely retrieves the favicon via getFavicon() and populates this._form.favicon. + * + * We could let callers explicitly retrieve the favicon instead of inserting it in the + * form dynamically. + */ + async retrieveFavicon() { + try { + this._form.favicon = await this.getFavicon(); + } catch (e) { + // We might request the data for a tab which is going to be destroyed. + // In this case the TargetFront will be destroyed. Otherwise log an error. + if (!this.isDestroyed()) { + console.error("Failed to retrieve the favicon for " + this.url, e); + } + } + } + + /** + * Top-level targets created on the server will not be created and managed + * by a descriptor front. Instead they are created by the Watcher actor. + * On the client side we manually re-establish a link between the descriptor + * and the new top-level target. + */ + setTarget(targetFront) { + // Completely ignore the previous target. + // We might nullify the _targetFront unexpectely due to previous target + // being destroyed after the new is created + if (this._targetFront) { + this._targetFront.off("target-destroyed", this._onTargetDestroyed); + } + this._targetFront = targetFront; + + targetFront.on("target-destroyed", this._onTargetDestroyed); + + if (this.isServerTargetSwitchingEnabled()) { + this._resolveTargetFrontPromise(targetFront); + + // Set a new promise in order to: + // 1) Avoid leaking the targetFront we just resolved into the previous promise. + // 2) Never return an empty target from `getTarget` + // + // About the second point: + // There is a race condition where we call `onTargetDestroyed` (which clears `this.targetFront`) + // a bit before calling `setTarget`. So that `this.targetFront` could be null, + // while we now a new target will eventually come when calling `setTarget`. + // Setting a new promise will help wait for the next target while `_targetFront` is null. + // Note that `getTarget` first look into `_targetFront` before checking for `_targetFrontPromise`. + this._targetFrontPromise = new Promise( + r => (this._resolveTargetFrontPromise = r) + ); + } + } + getCachedTarget() { + return this._targetFront; + } + async getTarget() { + if (this._targetFront && !this._targetFront.isDestroyed()) { + return this._targetFront; + } + + if (this._targetFrontPromise) { + return this._targetFrontPromise; + } + + this._targetFrontPromise = (async () => { + let newTargetFront = null; + try { + const targetForm = await super.getTarget(); + newTargetFront = this._createTabTarget(targetForm); + this.setTarget(newTargetFront); + } catch (e) { + console.log( + `Request to connect to TabDescriptor "${this.id}" failed: ${e}` + ); + } + + this._targetFrontPromise = null; + return newTargetFront; + })(); + return this._targetFrontPromise; + } + + /** + * Handle tabs events. + */ + async _handleTabEvent(event) { + switch (event.type) { + case "TabClose": + // Always destroy the toolbox opened for this local tab descriptor. + // When the toolbox is in a Window Host, it won't be removed from the + // DOM when the tab is closed. + const toolbox = gDevTools.getToolboxForDescriptorFront(this); + if (toolbox) { + // Toolbox.destroy will call target.destroy eventually. + await toolbox.destroy(); + } + break; + case "TabRemotenessChange": + this._onRemotenessChange(); + break; + } + } + + /** + * Automatically respawn the toolbox when the tab changes between being + * loaded within the parent process and loaded from a content process. + * Process change can go in both ways. + */ + async _onRemotenessChange() { + // In a near future, this client side code should be replaced by actor code, + // notifying about new tab targets. + this.emit("remoteness-change", this._targetFront); + } +} + +exports.TabDescriptorFront = TabDescriptorFront; +registerFront(TabDescriptorFront); diff --git a/devtools/client/fronts/descriptors/webextension.js b/devtools/client/fronts/descriptors/webextension.js new file mode 100644 index 0000000000..5db86ee5f8 --- /dev/null +++ b/devtools/client/fronts/descriptors/webextension.js @@ -0,0 +1,173 @@ +/* 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/. */ +"use strict"; + +const { + webExtensionDescriptorSpec, +} = require("resource://devtools/shared/specs/descriptors/webextension.js"); +const { + FrontClassWithSpec, + registerFront, +} = require("resource://devtools/shared/protocol.js"); +const { + DescriptorMixin, +} = require("resource://devtools/client/fronts/descriptors/descriptor-mixin.js"); +const DESCRIPTOR_TYPES = require("resource://devtools/client/fronts/descriptors/descriptor-types.js"); +loader.lazyRequireGetter( + this, + "WindowGlobalTargetFront", + "resource://devtools/client/fronts/targets/window-global.js", + true +); + +class WebExtensionDescriptorFront extends DescriptorMixin( + FrontClassWithSpec(webExtensionDescriptorSpec) +) { + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); + this.traits = {}; + } + + descriptorType = DESCRIPTOR_TYPES.EXTENSION; + + form(json) { + this.actorID = json.actor; + + // Do not use `form` name to avoid colliding with protocol.js's `form` method + this._form = json; + this.traits = json.traits || {}; + } + + get backgroundScriptStatus() { + return this._form.backgroundScriptStatus; + } + + get debuggable() { + return this._form.debuggable; + } + + get hidden() { + return this._form.hidden; + } + + get iconDataURL() { + return this._form.iconDataURL; + } + + get iconURL() { + return this._form.iconURL; + } + + get id() { + return this._form.id; + } + + get isSystem() { + return this._form.isSystem; + } + + get isWebExtensionDescriptor() { + return true; + } + + get isWebExtension() { + return this._form.isWebExtension; + } + + get manifestURL() { + return this._form.manifestURL; + } + + get name() { + return this._form.name; + } + + get persistentBackgroundScript() { + return this._form.persistentBackgroundScript; + } + + get temporarilyInstalled() { + return this._form.temporarilyInstalled; + } + + get url() { + return this._form.url; + } + + get warnings() { + return this._form.warnings; + } + + isServerTargetSwitchingEnabled() { + // For now, we don't expose any target out of the WatcherActor. + // And the top level target is still manually instantiated by the descriptor. + // We most likely need to wait for full enabling of EFT before being able to spawn + // the extension target from the server side as doing this would most likely break + // the iframe dropdown. It would break it as spawning the targets from the server + // would probably mean getting rid of the usage of WindowGlobalTargetActor._setWindow + // and instead spawn one target per extension document. + // That, instead of having a unique target for all the documents. + return false; + } + + getWatcher() { + return super.getWatcher({ + isServerTargetSwitchingEnabled: this.isServerTargetSwitchingEnabled(), + }); + } + + _createWebExtensionTarget(form) { + const front = new WindowGlobalTargetFront(this.conn, null, this); + front.form(form); + this.manage(front); + return front; + } + + /** + * Retrieve the WindowGlobalTargetFront representing a + * WebExtensionTargetActor if this addon is a webextension. + * + * WebExtensionDescriptors will be created for any type of addon type + * (webextension, search plugin, themes). Only webextensions can be targets. + * This method will throw for other addon types. + * + * TODO: We should filter out non-webextension & non-debuggable addons on the + * server to avoid the isWebExtension check here. See Bug 1644355. + */ + async getTarget() { + if (!this.isWebExtension) { + throw new Error( + "Tried to create a target for an addon which is not a webextension: " + + this.actorID + ); + } + + if (this._targetFront && !this._targetFront.isDestroyed()) { + return this._targetFront; + } + + if (this._targetFrontPromise) { + return this._targetFrontPromise; + } + + this._targetFrontPromise = (async () => { + let targetFront = null; + try { + const targetForm = await super.getTarget(); + targetFront = this._createWebExtensionTarget(targetForm); + } catch (e) { + console.log( + `Request to connect to WebExtensionDescriptor "${this.id}" failed: ${e}` + ); + } + this._targetFront = targetFront; + this._targetFrontPromise = null; + return targetFront; + })(); + return this._targetFrontPromise; + } +} + +exports.WebExtensionDescriptorFront = WebExtensionDescriptorFront; +registerFront(WebExtensionDescriptorFront); diff --git a/devtools/client/fronts/descriptors/worker.js b/devtools/client/fronts/descriptors/worker.js new file mode 100644 index 0000000000..47f016f15f --- /dev/null +++ b/devtools/client/fronts/descriptors/worker.js @@ -0,0 +1,146 @@ +/* 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/. */ +"use strict"; + +const { + workerDescriptorSpec, +} = require("resource://devtools/shared/specs/descriptors/worker.js"); +const { + FrontClassWithSpec, + registerFront, +} = require("resource://devtools/shared/protocol.js"); +const { + TargetMixin, +} = require("resource://devtools/client/fronts/targets/target-mixin.js"); +const { + DescriptorMixin, +} = require("resource://devtools/client/fronts/descriptors/descriptor-mixin.js"); +const DESCRIPTOR_TYPES = require("resource://devtools/client/fronts/descriptors/descriptor-types.js"); + +class WorkerDescriptorFront extends DescriptorMixin( + TargetMixin(FrontClassWithSpec(workerDescriptorSpec)) +) { + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); + + this.traits = {}; + } + + descriptorType = DESCRIPTOR_TYPES.WORKER; + + form(json) { + this.actorID = json.actor; + this.id = json.id; + + // Save the full form for Target class usage. + // Do not use `form` name to avoid colliding with protocol.js's `form` method + this.targetForm = json; + this._url = json.url; + this.type = json.type; + this.scope = json.scope; + this.fetch = json.fetch; + this.traits = json.traits; + } + + get name() { + // this._url is nullified in TargetMixin#destroy. + if (!this.url) { + return null; + } + + return this.url.split("/").pop(); + } + + get isWorkerDescriptor() { + return true; + } + + get isDedicatedWorker() { + return this.type === Ci.nsIWorkerDebugger.TYPE_DEDICATED; + } + + get isSharedWorker() { + return this.type === Ci.nsIWorkerDebugger.TYPE_SHARED; + } + + get isServiceWorker() { + return this.type === Ci.nsIWorkerDebugger.TYPE_SERVICE; + } + + // For now, WorkerDescriptor is morphed into a WorkerTarget when calling this method. + // Ideally, we would split this into two distinct classes. + async morphWorkerDescriptorIntoWorkerTarget() { + // temporary, will be moved once we have a target actor + return this.getTarget(); + } + + async getTarget() { + if (this._attach) { + return this._attach; + } + + this._attach = (async () => { + if (this.isDestroyedOrBeingDestroyed()) { + return this; + } + + if (this.isServiceWorker) { + this.registration = await this._getRegistrationIfActive(); + if (this.registration) { + await this.registration.preventShutdown(); + } + } + + if (this.isDestroyedOrBeingDestroyed()) { + return this; + } + + const workerTargetForm = await super.getTarget(); + + // Set the console and thread actor IDs on the form so it is accessible by TargetMixin.getFront + this.targetForm.consoleActor = workerTargetForm.consoleActor; + this.targetForm.threadActor = workerTargetForm.threadActor; + this.targetForm.tracerActor = workerTargetForm.tracerActor; + + if (this.isDestroyedOrBeingDestroyed()) { + return this; + } + + return this; + })(); + return this._attach; + } + + async detach() { + try { + await super.detach(); + + if (this.registration) { + // Bug 1644772 - Sometimes, the Browser Toolbox fails opening with a connection timeout + // with an exception related to this call to allowShutdown and its usage of detachDebugger API. + await this.registration.allowShutdown(); + this.registration = null; + } + } catch (e) { + this.logDetachError(e, "worker"); + } + } + + async _getRegistrationIfActive() { + const { registrations } = + await this.client.mainRoot.listServiceWorkerRegistrations(); + return registrations.find(({ activeWorker }) => { + return activeWorker && this.id === activeWorker.id; + }); + } + + reconfigure() { + // Toolbox and options panel are calling this method but Worker Target can't be + // reconfigured. So we ignore this call here. + return Promise.resolve(); + } +} + +exports.WorkerDescriptorFront = WorkerDescriptorFront; +registerFront(exports.WorkerDescriptorFront); diff --git a/devtools/client/fronts/device.js b/devtools/client/fronts/device.js new file mode 100644 index 0000000000..47f57c30b8 --- /dev/null +++ b/devtools/client/fronts/device.js @@ -0,0 +1,23 @@ +/* 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/. */ + +"use strict"; + +const { deviceSpec } = require("resource://devtools/shared/specs/device.js"); +const { + FrontClassWithSpec, + registerFront, +} = require("resource://devtools/shared/protocol.js"); + +class DeviceFront extends FrontClassWithSpec(deviceSpec) { + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); + + // Attribute name from which to retrieve the actorID out of the target actor's form + this.formAttributeName = "deviceActor"; + } +} + +exports.DeviceFront = DeviceFront; +registerFront(DeviceFront); diff --git a/devtools/client/fronts/frame.js b/devtools/client/fronts/frame.js new file mode 100644 index 0000000000..525f0d1170 --- /dev/null +++ b/devtools/client/fronts/frame.js @@ -0,0 +1,27 @@ +/* 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/. */ + +"use strict"; + +const { frameSpec } = require("resource://devtools/shared/specs/frame.js"); +const { + FrontClassWithSpec, + registerFront, +} = require("resource://devtools/shared/protocol.js"); + +class FrameFront extends FrontClassWithSpec(frameSpec) { + form(json) { + this.displayName = json.displayName; + this.arguments = json.arguments; + this.type = json.type; + this.where = json.where; + this.this = json.this; + this.data = json; + this.asyncCause = json.asyncCause; + this.state = json.state; + } +} + +module.exports = FrameFront; +registerFront(FrameFront); diff --git a/devtools/client/fronts/highlighters.js b/devtools/client/fronts/highlighters.js new file mode 100644 index 0000000000..b1e159e5fc --- /dev/null +++ b/devtools/client/fronts/highlighters.js @@ -0,0 +1,53 @@ +/* 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/. */ + +"use strict"; + +const { + FrontClassWithSpec, + registerFront, +} = require("resource://devtools/shared/protocol.js"); +const { + customHighlighterSpec, +} = require("resource://devtools/shared/specs/highlighters.js"); +const { + safeAsyncMethod, +} = require("resource://devtools/shared/async-utils.js"); + +class CustomHighlighterFront extends FrontClassWithSpec(customHighlighterSpec) { + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); + + // show/hide requests can be triggered while DevTools are closing. + this.show = safeAsyncMethod(this.show.bind(this), () => this.isDestroyed()); + this.hide = safeAsyncMethod(this.hide.bind(this), () => this.isDestroyed()); + + this._isShown = false; + } + + show(...args) { + this._isShown = true; + return super.show(...args); + } + + hide() { + this._isShown = false; + return super.hide(); + } + + isShown() { + return this._isShown; + } + + destroy() { + if (this.isDestroyed()) { + return; + } + super.finalize(); // oneway call, doesn't expect a response. + super.destroy(); + } +} + +exports.CustomHighlighterFront = CustomHighlighterFront; +registerFront(CustomHighlighterFront); diff --git a/devtools/client/fronts/inspector.js b/devtools/client/fronts/inspector.js new file mode 100644 index 0000000000..116993078b --- /dev/null +++ b/devtools/client/fronts/inspector.js @@ -0,0 +1,282 @@ +/* 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/. */ + +"use strict"; + +const Telemetry = require("resource://devtools/client/shared/telemetry.js"); +const { + FrontClassWithSpec, + registerFront, +} = require("resource://devtools/shared/protocol.js"); +const { + inspectorSpec, +} = require("resource://devtools/shared/specs/inspector.js"); + +loader.lazyRequireGetter( + this, + "captureScreenshot", + "resource://devtools/client/shared/screenshot.js", + true +); + +const TELEMETRY_EYEDROPPER_OPENED = "DEVTOOLS_EYEDROPPER_OPENED_COUNT"; +const TELEMETRY_EYEDROPPER_OPENED_MENU = + "DEVTOOLS_MENU_EYEDROPPER_OPENED_COUNT"; +const SHOW_ALL_ANONYMOUS_CONTENT_PREF = + "devtools.inspector.showAllAnonymousContent"; + +const telemetry = new Telemetry(); + +/** + * Client side of the inspector actor, which is used to create + * inspector-related actors, including the walker. + */ +class InspectorFront extends FrontClassWithSpec(inspectorSpec) { + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); + + this._client = client; + this._highlighters = new Map(); + + // Attribute name from which to retrieve the actorID out of the target actor's form + this.formAttributeName = "inspectorActor"; + + // Map of highlighter types to unsettled promises to create a highlighter of that type + this._pendingGetHighlighterMap = new Map(); + + this.noopStylesheetListener = () => {}; + } + + // async initialization + async initialize() { + if (this.initialized) { + return this.initialized; + } + + // Watch STYLESHEET resources to fill the ResourceCommand cache. + // StyleRule front's `get parentStyleSheet()` will query the cache to + // retrieve the resource corresponding to the parent stylesheet of a rule. + const { resourceCommand } = this.targetFront.commands; + // Backup resourceCommand, targetFront.commands might be null in `destroy`. + this.resourceCommand = resourceCommand; + await resourceCommand.watchResources([resourceCommand.TYPES.STYLESHEET], { + onAvailable: this.noopStylesheetListener, + }); + + // Bail out if the inspector is closed while watchResources was pending + if (this.isDestroyed()) { + return null; + } + + this.initialized = await Promise.all([ + this._getWalker(), + this._getPageStyle(), + ]); + + return this.initialized; + } + + async _getWalker() { + const showAllAnonymousContent = Services.prefs.getBoolPref( + SHOW_ALL_ANONYMOUS_CONTENT_PREF + ); + this.walker = await this.getWalker({ + showAllAnonymousContent, + }); + + // We need to reparent the RootNode of remote iframe Walkers + // so that their parent is the NodeFront of the