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 { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
9 const { XPCOMUtils } = ChromeUtils.import(
10 "resource://gre/modules/XPCOMUtils.jsm"
12 XPCOMUtils.defineLazyGetter(this, "DevtoolsStartup", () => {
13 return Cc["@mozilla.org/devtools/startup-clh;1"].getService(
14 Ci.nsICommandLineHandler
18 // We don't want to spend time initializing the full loader here so we create
19 // our own lazy require.
20 XPCOMUtils.defineLazyGetter(this, "Telemetry", function() {
21 const { require } = ChromeUtils.import(
22 "resource://devtools/shared/Loader.jsm"
24 // eslint-disable-next-line no-shadow
25 const Telemetry = require("devtools/client/shared/telemetry");
30 const DEVTOOLS_ENABLED_PREF = "devtools.enabled";
31 const DEVTOOLS_POLICY_DISABLED_PREF = "devtools.policy.disabled";
33 const EXPORTED_SYMBOLS = ["DevToolsShim"];
35 function removeItem(array, callback) {
36 const index = array.findIndex(callback);
38 array.splice(index, 1);
43 * DevToolsShim is a singleton that provides a set of helpers to interact with DevTools,
44 * that work whether Devtools are enabled or not.
46 * It can be used to start listening to devtools events before DevTools are ready. As soon
47 * as DevTools are enabled, the DevToolsShim will forward all the requests received until
48 * then to the real DevTools instance.
50 const DevToolsShim = {
55 if (!this._telemetry) {
56 this._telemetry = new Telemetry();
57 this._telemetry.setEventRecordingEnabled(true);
59 return this._telemetry;
63 * Returns true if DevTools are enabled for the current profile. If devtools are not
64 * enabled, initializing DevTools will open the onboarding page. Some entry points
65 * should no-op in this case.
67 isEnabled: function() {
68 const enabled = Services.prefs.getBoolPref(DEVTOOLS_ENABLED_PREF);
69 return enabled && !this.isDisabledByPolicy();
73 * Returns true if the devtools are completely disabled and can not be enabled. All
74 * entry points should return without throwing, initDevTools should never be called.
76 isDisabledByPolicy: function() {
77 return Services.prefs.getBoolPref(DEVTOOLS_POLICY_DISABLED_PREF, false);
81 * Check if DevTools have already been initialized.
83 * @return {Boolean} true if DevTools are initialized.
85 isInitialized: function() {
86 return !!this._gDevTools;
90 * Returns the array of the existing toolboxes. This method is part of the compatibility
91 * layer for webextensions.
93 * @return {Array<Toolbox>}
94 * An array of toolboxes.
96 getToolboxes: function() {
97 if (this.isInitialized()) {
98 return this._gDevTools.getToolboxes();
105 * Register an instance of gDevTools. Should be called by DevTools during startup.
107 * @param {DevTools} a devtools instance (from client/framework/devtools)
109 register: function(gDevTools) {
110 this._gDevTools = gDevTools;
111 this._onDevToolsRegistered();
112 this._gDevTools.emit("devtools-registered");
116 * Unregister the current instance of gDevTools. Should be called by DevTools during
119 unregister: function() {
120 if (this.isInitialized()) {
121 this._gDevTools.emit("devtools-unregistered");
122 this._gDevTools = null;
127 * The following methods can be called before DevTools are initialized:
131 * If DevTools are not initialized when calling the method, DevToolsShim will call the
132 * appropriate method as soon as a gDevTools instance is registered.
136 * This method is used by browser/components/extensions/ext-devtools.js for the events:
138 * - toolbox-destroyed
140 on: function(event, listener) {
141 if (this.isInitialized()) {
142 this._gDevTools.on(event, listener);
144 this.listeners.push([event, listener]);
149 * This method is currently only used by devtools code, but is kept here for consistency
152 off: function(event, listener) {
153 if (this.isInitialized()) {
154 this._gDevTools.off(event, listener);
156 removeItem(this.listeners, ([e, l]) => e === event && l === listener);
161 * Called from SessionStore.jsm in mozilla-central when saving the current state.
163 * @param {Object} state
164 * A SessionStore state object that gets modified by reference
166 saveDevToolsSession: function(state) {
167 if (!this.isInitialized()) {
171 this._gDevTools.saveDevToolsSession(state);
175 * Called from SessionStore.jsm in mozilla-central when restoring a previous session.
176 * Will always be called, even if the session does not contain DevTools related items.
178 restoreDevToolsSession: function(session) {
179 if (!this.isEnabled()) {
183 const { browserConsole, browserToolbox } = session;
184 const hasDevToolsData = browserConsole || browserToolbox;
185 if (!hasDevToolsData) {
186 // Do not initialize DevTools unless there is DevTools specific data in the session.
190 this.initDevTools("SessionRestore");
191 this._gDevTools.restoreDevToolsSession(session);
195 * Called from nsContextMenu.js in mozilla-central when using the Inspect Accessibility
198 * @param {XULTab} tab
199 * The browser tab on which inspect accessibility was used.
200 * @param {ElementIdentifier} domReference
201 * Identifier generated by ContentDOMReference. It is a unique pair of
202 * BrowsingContext ID and a numeric ID.
203 * @return {Promise} a promise that resolves when the accessible node is selected in the
204 * accessibility inspector or that resolves immediately if DevTools are not
207 inspectA11Y: function(tab, domReference) {
208 if (!this.isEnabled()) {
209 if (!this.isDisabledByPolicy()) {
210 DevtoolsStartup.openInstallPage("ContextMenu");
212 return Promise.resolve();
215 // Record the timing at which this event started in order to compute later in
216 // gDevTools.showToolbox, the complete time it takes to open the toolbox.
217 // i.e. especially take `DevtoolsStartup.initDevTools` into account.
218 const startTime = Cu.now();
220 this.initDevTools("ContextMenu");
222 return this._gDevTools.inspectA11Y(tab, domReference, startTime);
226 * Called from nsContextMenu.js in mozilla-central when using the Inspect Element
229 * @param {XULTab} tab
230 * The browser tab on which inspect node was used.
231 * @param {ElementIdentifier} domReference
232 * Identifier generated by ContentDOMReference. It is a unique pair of
233 * BrowsingContext ID and a numeric ID.
234 * @return {Promise} a promise that resolves when the node is selected in the inspector
235 * markup view or that resolves immediately if DevTools are not enabled.
237 inspectNode: function(tab, domReference) {
238 if (!this.isEnabled()) {
239 if (!this.isDisabledByPolicy()) {
240 DevtoolsStartup.openInstallPage("ContextMenu");
242 return Promise.resolve();
245 // Record the timing at which this event started in order to compute later in
246 // gDevTools.showToolbox, the complete time it takes to open the toolbox.
247 // i.e. especially take `DevtoolsStartup.initDevTools` into account.
248 const startTime = Cu.now();
250 this.initDevTools("ContextMenu");
252 return this._gDevTools.inspectNode(tab, domReference, startTime);
255 _onDevToolsRegistered: function() {
256 // Register all pending event listeners on the real gDevTools object.
257 for (const [event, listener] of this.listeners) {
258 this._gDevTools.on(event, listener);
265 * Initialize DevTools via DevToolsStartup if needed. This method throws if DevTools are
266 * not enabled.. If the entry point is supposed to trigger the onboarding, call it
267 * explicitly via DevtoolsStartup.openInstallPage().
269 * @param {String} reason
270 * optional, if provided should be a valid entry point for DEVTOOLS_ENTRY_POINT
271 * in toolkit/components/telemetry/Histograms.json
273 initDevTools: function(reason) {
274 if (!this.isEnabled()) {
275 throw new Error("DevTools are not enabled and can not be initialized.");
279 const window = Services.wm.getMostRecentWindow("navigator:browser");
281 this.telemetry.addEventProperty(
289 this.telemetry.addEventProperty(
299 if (!this.isInitialized()) {
300 DevtoolsStartup.initDevTools(reason);
306 * Compatibility layer for webextensions.
308 * Those methods are called only after a DevTools webextension was loaded in DevTools,
309 * therefore DevTools should always be available when they are called.
311 const webExtensionsMethods = [
312 "createDescriptorForTab",
313 "createWebExtensionInspectedWindowFront",
316 "openBrowserConsole",
319 for (const method of webExtensionsMethods) {
320 DevToolsShim[method] = function() {
321 if (!this.isEnabled()) {
323 "Could not call a DevToolsShim webextension method ('" +
325 "'): DevTools are not initialized."
330 return this._gDevTools[method].apply(this._gDevTools, arguments);