Backed out 2 changesets (bug 1943998) for causing wd failures @ phases.py CLOSED...
[gecko.git] / devtools / client / fronts / inspector.js
blob48a00cee24252645cfa04a92be7f72368ca6b16b
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 const Telemetry = require("resource://devtools/client/shared/telemetry.js");
8 const {
9 FrontClassWithSpec,
10 registerFront,
11 } = require("resource://devtools/shared/protocol.js");
12 const {
13 inspectorSpec,
14 } = require("resource://devtools/shared/specs/inspector.js");
16 loader.lazyRequireGetter(
17 this,
18 "captureScreenshot",
19 "resource://devtools/client/shared/screenshot.js",
20 true
22 const lazy = {};
24 ChromeUtils.defineESModuleGetters(lazy, {
25 TYPES: "resource://devtools/shared/highlighters.mjs",
26 });
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();
36 /**
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
57 async initialize() {
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,
70 });
72 // Bail out if the inspector is closed while watchResources was pending
73 if (this.isDestroyed()) {
74 return null;
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;
87 async _getWalker() {
88 const showAllAnonymousContent = Services.prefs.getBoolPref(
89 SHOW_ALL_ANONYMOUS_CONTENT_PREF
91 this.walker = await this.getWalker({
92 showAllAnonymousContent,
93 });
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;
124 destroy() {
125 if (this.isDestroyed()) {
126 return;
128 this._compatibility = null;
130 const { resourceCommand } = this;
131 resourceCommand.unwatchResources([resourceCommand.TYPES.STYLESHEET], {
132 onAvailable: this.noopStylesheetListener,
134 this.resourceCommand = null;
136 this.walker = 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();
142 super.destroy();
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;
159 try {
160 highlighter = await super.getHighlighterByType(typeName);
161 } catch (_) {
162 throw new Error(
163 "The target doesn't support " +
164 `creating highlighters by types or ${typeName} is unknown`
167 return highlighter;
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
183 * Highlighter type
184 * @return {Promise}
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);
196 return highlighter;
197 })();
199 this._pendingGetHighlighterMap.set(type, pendingGetHighlighter);
202 if (pendingGetHighlighter) {
203 front = await pendingGetHighlighter;
206 return front;
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")) {
217 try {
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,
222 disableFlash: true,
223 ignoreDprForFileScale: true,
225 screenshot = data;
226 } catch (e) {
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).
230 console.error(
231 "Error occured when taking a screenshot for the eyedropper",
237 await super.pickColorFromPage({
238 ...options,
239 screenshot,
242 if (options?.fromMenu) {
243 telemetry.getHistogramById(TELEMETRY_EYEDROPPER_OPENED_MENU).add(true);
244 } else {
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(
266 contentDomReference
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.
277 let target;
278 const { watcherFront } = this.targetFront.commands;
279 if (watcherFront) {
280 target = await watcherFront.getWindowGlobalTarget(browsingContextId);
281 } else {
282 // For descriptors which don't expose a watcher (e.g. WebExtension)
283 // we used to call RootActor::getBrowsingContextDescriptor, but it was
284 // removed in FF77.
285 // Support for watcher in WebExtension descriptors is Bug 1644341.
286 throw new Error(
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);