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/. */
6 ChromeUtils.defineLazyGetter(lazy, "DevToolsStartup", () => {
7 return Cc["@mozilla.org/devtools/startup-clh;1"].getService(
8 Ci.nsICommandLineHandler
12 // We don't want to spend time initializing the full loader here so we create
13 // our own lazy require.
14 ChromeUtils.defineLazyGetter(lazy, "Telemetry", function () {
15 const { require } = ChromeUtils.importESModule(
16 "resource://devtools/shared/loader/Loader.sys.mjs"
18 // eslint-disable-next-line no-shadow
19 const Telemetry = require("devtools/client/shared/telemetry");
24 const DEVTOOLS_POLICY_DISABLED_PREF = "devtools.policy.disabled";
26 function removeItem(array, callback) {
27 const index = array.findIndex(callback);
29 array.splice(index, 1);
34 * DevToolsShim is a singleton that provides a set of helpers to interact with DevTools,
35 * that work whether Devtools are enabled or not.
37 * It can be used to start listening to devtools events before DevTools are ready. As soon
38 * as DevTools are ready, the DevToolsShim will forward all the requests received until
39 * then to the real DevTools instance.
41 export const DevToolsShim = {
46 if (!this._telemetry) {
47 this._telemetry = new lazy.Telemetry();
48 this._telemetry.setEventRecordingEnabled(true);
50 return this._telemetry;
54 * Returns true if DevTools are enabled. This now only depends on the policy.
55 * TODO: Merge isEnabled and isDisabledByPolicy.
58 return !this.isDisabledByPolicy();
62 * Returns true if the devtools are completely disabled and can not be enabled. All
63 * entry points should return without throwing, initDevTools should never be called.
65 isDisabledByPolicy() {
66 return Services.prefs.getBoolPref(DEVTOOLS_POLICY_DISABLED_PREF, false);
70 * Check if DevTools have already been initialized.
72 * @return {Boolean} true if DevTools are initialized.
75 return !!this._gDevTools;
79 * Returns the array of the existing toolboxes. This method is part of the compatibility
80 * layer for webextensions.
82 * @return {Array<Toolbox>}
83 * An array of toolboxes.
86 if (this.isInitialized()) {
87 return this._gDevTools.getToolboxes();
94 * Register an instance of gDevTools. Should be called by DevTools during startup.
96 * @param {DevTools} a devtools instance (from client/framework/devtools)
99 this._gDevTools = gDevTools;
100 this._onDevToolsRegistered();
101 this._gDevTools.emit("devtools-registered");
105 * Unregister the current instance of gDevTools. Should be called by DevTools during
109 if (this.isInitialized()) {
110 this._gDevTools.emit("devtools-unregistered");
111 this._gDevTools = null;
116 * The following methods can be called before DevTools are initialized:
120 * If DevTools are not initialized when calling the method, DevToolsShim will call the
121 * appropriate method as soon as a gDevTools instance is registered.
125 * This method is used by browser/components/extensions/ext-devtools.js for the events:
127 * - toolbox-destroyed
129 on(event, listener) {
130 if (this.isInitialized()) {
131 this._gDevTools.on(event, listener);
133 this.listeners.push([event, listener]);
138 * This method is currently only used by devtools code, but is kept here for consistency
141 off(event, listener) {
142 if (this.isInitialized()) {
143 this._gDevTools.off(event, listener);
145 removeItem(this.listeners, ([e, l]) => e === event && l === listener);
150 * Called from SessionStore.sys.mjs in mozilla-central when saving the current state.
152 * @param {Object} state
153 * A SessionStore state object that gets modified by reference
155 saveDevToolsSession(state) {
156 if (!this.isInitialized()) {
160 this._gDevTools.saveDevToolsSession(state);
164 * Called from SessionStore.sys.mjs in mozilla-central when restoring a previous session.
165 * Will always be called, even if the session does not contain DevTools related items.
167 restoreDevToolsSession(session) {
168 if (!this.isEnabled()) {
172 const { browserConsole, browserToolbox } = session;
173 const hasDevToolsData = browserConsole || browserToolbox;
174 if (!hasDevToolsData) {
175 // Do not initialize DevTools unless there is DevTools specific data in the session.
179 this.initDevTools("SessionRestore");
180 this._gDevTools.restoreDevToolsSession(session);
184 return lazy.DevToolsStartup.isDevToolsUser();
188 * Called from nsContextMenu.js in mozilla-central when using the Inspect Accessibility
191 * @param {XULTab} tab
192 * The browser tab on which inspect accessibility was used.
193 * @param {ElementIdentifier} domReference
194 * Identifier generated by ContentDOMReference. It is a unique pair of
195 * BrowsingContext ID and a numeric ID.
196 * @return {Promise} a promise that resolves when the accessible node is selected in the
197 * accessibility inspector or that resolves immediately if DevTools are not
200 inspectA11Y(tab, domReference) {
201 if (!this.isEnabled()) {
202 return Promise.resolve();
205 // Record the timing at which this event started in order to compute later in
206 // gDevTools.showToolbox, the complete time it takes to open the toolbox.
207 // i.e. especially take `DevToolsStartup.initDevTools` into account.
208 const startTime = Cu.now();
210 this.initDevTools("ContextMenu");
212 return this._gDevTools.inspectA11Y(tab, domReference, startTime);
216 * Called from nsContextMenu.js in mozilla-central when using the Inspect Element
219 * @param {XULTab} tab
220 * The browser tab on which inspect node was used.
221 * @param {ElementIdentifier} domReference
222 * Identifier generated by ContentDOMReference. It is a unique pair of
223 * BrowsingContext ID and a numeric ID.
224 * @return {Promise} a promise that resolves when the node is selected in the inspector
225 * markup view or that resolves immediately if DevTools are not enabled.
227 inspectNode(tab, domReference) {
228 if (!this.isEnabled()) {
229 return Promise.resolve();
232 // Record the timing at which this event started in order to compute later in
233 // gDevTools.showToolbox, the complete time it takes to open the toolbox.
234 // i.e. especially take `DevToolsStartup.initDevTools` into account.
235 const startTime = Cu.now();
237 this.initDevTools("ContextMenu");
239 return this._gDevTools.inspectNode(tab, domReference, startTime);
242 _onDevToolsRegistered() {
243 // Register all pending event listeners on the real gDevTools object.
244 for (const [event, listener] of this.listeners) {
245 this._gDevTools.on(event, listener);
252 * Initialize DevTools via DevToolsStartup if needed. This method throws if DevTools are
255 * @param {String} reason
256 * optional, if provided should be a valid entry point for DEVTOOLS_ENTRY_POINT
257 * in toolkit/components/telemetry/Histograms.json
259 initDevTools(reason) {
260 if (!this.isEnabled()) {
261 throw new Error("DevTools are not enabled and can not be initialized.");
265 const window = Services.wm.getMostRecentWindow("navigator:browser");
267 this.telemetry.addEventProperty(
275 this.telemetry.addEventProperty(
285 if (!this.isInitialized()) {
286 lazy.DevToolsStartup.initDevTools(reason);
292 * Compatibility layer for webextensions.
294 * Those methods are called only after a DevTools webextension was loaded in DevTools,
295 * therefore DevTools should always be available when they are called.
297 const webExtensionsMethods = [
298 "createCommandsForTabForWebExtension",
300 "openBrowserConsole",
304 * Compatibility layer for other third parties.
306 const otherToolMethods = [
307 // gDevTools.showToolboxForTab is used by wptrunner to start devtools
308 // https://github.com/web-platform-tests/wpt
309 // And also, Quick Actions on URL bar.
311 // Used for Quick Actions on URL bar.
313 // Used for Quick Actions test.
317 for (const method of [...webExtensionsMethods, ...otherToolMethods]) {
318 DevToolsShim[method] = function () {
319 if (!this.isEnabled()) {
321 "Could not call a DevToolsShim webextension method ('" +
323 "'): DevTools are not initialized."
328 return this._gDevTools[method].apply(this._gDevTools, arguments);