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/. */
7 const Telemetry
= require("resource://devtools/client/shared/telemetry.js");
11 } = require("resource://devtools/shared/protocol.js");
14 } = require("resource://devtools/shared/specs/inspector.js");
16 loader
.lazyRequireGetter(
19 "resource://devtools/client/shared/screenshot.js",
24 ChromeUtils
.defineESModuleGetters(lazy
, {
25 TYPES
: "resource://devtools/shared/highlighters.mjs",
28 const TELEMETRY_EYEDROPPER_OPENED
= "DEVTOOLS_EYEDROPPER_OPENED_COUNT";
29 const TELEMETRY_EYEDROPPER_OPENED_MENU
=
30 "DEVTOOLS_MENU_EYEDROPPER_OPENED_COUNT";
31 const SHOW_ALL_ANONYMOUS_CONTENT_PREF
=
32 "devtools.inspector.showAllAnonymousContent";
34 const telemetry
= new Telemetry();
37 * Client side of the inspector actor, which is used to create
38 * inspector-related actors, including the walker.
40 class InspectorFront
extends FrontClassWithSpec(inspectorSpec
) {
41 constructor(client
, targetFront
, parentFront
) {
42 super(client
, targetFront
, parentFront
);
44 this._client
= client
;
45 this._highlighters
= new Map();
47 // Attribute name from which to retrieve the actorID out of the target actor's form
48 this.formAttributeName
= "inspectorActor";
50 // Map of highlighter types to unsettled promises to create a highlighter of that type
51 this._pendingGetHighlighterMap
= new Map();
53 this.noopStylesheetListener
= () => {};
56 // async initialization
58 if (this.initialized
) {
59 return this.initialized
;
62 // Watch STYLESHEET resources to fill the ResourceCommand cache.
63 // StyleRule front's `get parentStyleSheet()` will query the cache to
64 // retrieve the resource corresponding to the parent stylesheet of a rule.
65 const { resourceCommand
} = this.targetFront
.commands
;
66 // Backup resourceCommand, targetFront.commands might be null in `destroy`.
67 this.resourceCommand
= resourceCommand
;
68 await resourceCommand
.watchResources([resourceCommand
.TYPES
.STYLESHEET
], {
69 onAvailable
: this.noopStylesheetListener
,
72 // Bail out if the inspector is closed while watchResources was pending
73 if (this.isDestroyed()) {
77 const promises
= [this._getWalker(), this._getPageStyle()];
78 if (this.targetFront
.commands
.descriptorFront
.isTabDescriptor
) {
79 promises
.push(this._enableViewportSizeOnResizeHighlighter());
82 this.initialized
= await Promise
.all(promises
);
84 return this.initialized
;
88 const showAllAnonymousContent
= Services
.prefs
.getBoolPref(
89 SHOW_ALL_ANONYMOUS_CONTENT_PREF
91 this.walker
= await
this.getWalker({
92 showAllAnonymousContent
,
95 // We need to reparent the RootNode of remote iframe Walkers
96 // so that their parent is the NodeFront of the <iframe>
97 // element, coming from another process/target/WalkerFront.
98 await
this.walker
.reparentRemoteFrame();
101 hasHighlighter(type
) {
102 return this._highlighters
.has(type
);
105 async
_getPageStyle() {
106 this.pageStyle
= await
super.getPageStyle();
109 async
_enableViewportSizeOnResizeHighlighter() {
110 const highlighter
= await
this.getOrCreateHighlighterByType(
111 lazy
.TYPES
.VIEWPORT_SIZE_ON_RESIZE
113 await highlighter
.show(this);
116 async
getCompatibilityFront() {
117 if (!this._compatibility
) {
118 this._compatibility
= await
super.getCompatibility();
121 return this._compatibility
;
125 if (this.isDestroyed()) {
128 this._compatibility
= null;
130 const { resourceCommand
} = this;
131 resourceCommand
.unwatchResources([resourceCommand
.TYPES
.STYLESHEET
], {
132 onAvailable
: this.noopStylesheetListener
,
134 this.resourceCommand
= null;
138 // CustomHighlighter fronts are managed by InspectorFront and so will be
139 // automatically destroyed. But we have to clear the `_highlighters`
140 // Map as well as explicitly call `finalize` request on all of them.
141 this.destroyHighlighters();
145 destroyHighlighters() {
146 for (const type
of this._highlighters
.keys()) {
147 if (this._highlighters
.has(type
)) {
148 const highlighter
= this._highlighters
.get(type
);
149 if (!highlighter
.isDestroyed()) {
150 highlighter
.finalize();
152 this._highlighters
.delete(type
);
157 async
getHighlighterByType(typeName
) {
158 let highlighter
= null;
160 highlighter
= await
super.getHighlighterByType(typeName
);
163 "The target doesn't support " +
164 `creating highlighters by types or ${typeName} is unknown`
170 getKnownHighlighter(type
) {
171 return this._highlighters
.get(type
);
175 * Return a highlighter instance of the given type.
176 * If an instance was previously created, return it. Else, create and return a new one.
178 * Store a promise for the request to create a new highlighter. If another request
179 * comes in before that promise is resolved, wait for it to resolve and return the
180 * highlighter instance it resolved with instead of creating a new request.
182 * @param {String} type
185 * Promise which resolves with a highlighter instance of the given type
187 async
getOrCreateHighlighterByType(type
) {
188 let front
= this._highlighters
.get(type
);
189 let pendingGetHighlighter
= this._pendingGetHighlighterMap
.get(type
);
191 if (!front
&& !pendingGetHighlighter
) {
192 pendingGetHighlighter
= (async () => {
193 const highlighter
= await
this.getHighlighterByType(type
);
194 this._highlighters
.set(type
, highlighter
);
195 this._pendingGetHighlighterMap
.delete(type
);
199 this._pendingGetHighlighterMap
.set(type
, pendingGetHighlighter
);
202 if (pendingGetHighlighter
) {
203 front
= await pendingGetHighlighter
;
209 async
pickColorFromPage(options
) {
210 let screenshot
= null;
212 // @backward-compat { version 87 } ScreenshotContentActor was only added in 87.
213 // When connecting to older server, the eyedropper will use drawWindow
214 // to retrieve the screenshot of the page (that's a decent fallback,
215 // even if it doesn't handle remote frames).
216 if (this.targetFront
.hasActor("screenshotContent")) {
218 // We use the screenshot actors as it can retrieve an image of the current viewport,
219 // handling remote frame if need be.
220 const { data
} = await
captureScreenshot(this.targetFront
, {
221 browsingContextID
: this.targetFront
.browsingContextID
,
223 ignoreDprForFileScale
: true,
227 // We simply log the error and still call pickColorFromPage as it will default to
228 // use drawWindow in order to get the screenshot of the page (that's a decent
229 // fallback, even if it doesn't handle remote frames).
231 "Error occured when taking a screenshot for the eyedropper",
237 await
super.pickColorFromPage({
242 if (options
?.fromMenu
) {
243 telemetry
.getHistogramById(TELEMETRY_EYEDROPPER_OPENED_MENU
).add(true);
245 telemetry
.getHistogramById(TELEMETRY_EYEDROPPER_OPENED
).add(true);
250 * Given a node grip, return a NodeFront on the right context.
252 * @param {Object} grip: The node grip.
253 * @returns {Promise<NodeFront|null>} A promise that resolves with a NodeFront or null
254 * if the NodeFront couldn't be created/retrieved.
256 async
getNodeFrontFromNodeGrip(grip
) {
257 return this.getNodeActorFromContentDomReference(grip
.contentDomReference
);
260 async
getNodeActorFromContentDomReference(contentDomReference
) {
261 const { browsingContextId
} = contentDomReference
;
262 // If the contentDomReference lives in the same browsing context id than the
263 // current one, we can directly use the current walker.
264 if (this.targetFront
.browsingContextID
=== browsingContextId
) {
265 return this.walker
.getNodeActorFromContentDomReference(
270 // If the contentDomReference has a different browsing context than the current one,
271 // we are either in Fission or in the Multiprocess Browser Toolbox, so we need to
272 // retrieve the walker of the WindowGlobalTarget.
273 // Get the target for this remote frame element
275 // Tab and Process Descriptors expose a Watcher, which should be used to
276 // fetch the node's target.
278 const { watcherFront
} = this.targetFront
.commands
;
280 target
= await watcherFront
.getWindowGlobalTarget(browsingContextId
);
282 // For descriptors which don't expose a watcher (e.g. WebExtension)
283 // we used to call RootActor::getBrowsingContextDescriptor, but it was
285 // Support for watcher in WebExtension descriptors is Bug 1644341.
287 `Unable to call getNodeActorFromContentDomReference for ${this.targetFront.actorID}`
290 const { walker
} = await target
.getFront("inspector");
291 return walker
.getNodeActorFromContentDomReference(contentDomReference
);
295 exports
.InspectorFront
= InspectorFront
;
296 registerFront(InspectorFront
);