gecko-dev/devtools/shared/builtin-modules.js

326 строки
8.8 KiB
JavaScript

/* 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";
/**
* This module defines custom globals injected in all our modules and also
* pseudo modules that aren't separate files but just dynamically set values.
*
* As it does so, the module itself doesn't have access to these globals,
* nor the pseudo modules. Be careful to avoid loading any other js module as
* they would also miss them.
*/
const { Cu, Cc, Ci } = require("chrome");
const promise = require("resource://gre/modules/Promise.jsm").Promise;
const jsmScope = require("resource://devtools/shared/Loader.jsm");
const { Services } = require("resource://gre/modules/Services.jsm");
const systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
// Steal various globals only available in JSM scope (and not Sandbox one)
const {
BrowsingContext,
console,
DebuggerNotificationObserver,
DOMPoint,
DOMQuad,
DOMRect,
HeapSnapshot,
L10nRegistry,
NamedNodeMap,
NodeFilter,
StructuredCloneHolder,
TelemetryStopwatch,
} = Cu.getGlobalForObject(jsmScope);
// Create a single Sandbox to access global properties needed in this module.
// Sandbox are memory expensive, so we should create as little as possible.
const debuggerSandbox = (exports.internalSandbox = Cu.Sandbox(systemPrincipal, {
// This sandbox is also reused for ChromeDebugger implementation.
// As we want to load the `Debugger` API for debugging chrome contexts,
// we have to ensure loading it in a distinct compartment from its debuggee.
freshCompartment: true,
wantGlobalProperties: [
"AbortController",
"atob",
"btoa",
"Blob",
"ChromeUtils",
"crypto",
"CSS",
"CSSRule",
"DOMParser",
"Element",
"Event",
"FileReader",
"FormData",
"indexedDB",
"InspectorUtils",
"Node",
"TextDecoder",
"TextEncoder",
"URL",
"XMLHttpRequest",
],
}));
const {
AbortController,
atob,
btoa,
Blob,
ChromeUtils,
crypto,
CSS,
CSSRule,
DOMParser,
Element,
Event,
FileReader,
FormData,
indexedDB,
InspectorUtils,
Node,
TextDecoder,
TextEncoder,
URL,
XMLHttpRequest,
} = debuggerSandbox;
/**
* Defines a getter on a specified object that will be created upon first use.
*
* @param object
* The object to define the lazy getter on.
* @param name
* The name of the getter to define on object.
* @param lambda
* A function that returns what the getter should return. This will
* only ever be called once.
*/
function defineLazyGetter(object, name, lambda) {
Object.defineProperty(object, name, {
get: function() {
// Redefine this accessor property as a data property.
// Delete it first, to rule out "too much recursion" in case object is
// a proxy whose defineProperty handler might unwittingly trigger this
// getter again.
delete object[name];
const value = lambda.apply(object);
Object.defineProperty(object, name, {
value,
writable: true,
configurable: true,
enumerable: true,
});
return value;
},
configurable: true,
enumerable: true,
});
}
/**
* Defines a getter on a specified object for a service. The service will not
* be obtained until first use.
*
* @param object
* The object to define the lazy getter on.
* @param name
* The name of the getter to define on object for the service.
* @param contract
* The contract used to obtain the service.
* @param interfaceName
* The name of the interface to query the service to.
*/
function defineLazyServiceGetter(object, name, contract, interfaceName) {
defineLazyGetter(object, name, function() {
return Cc[contract].getService(Ci[interfaceName]);
});
}
/**
* Defines a getter on a specified object for a module. The module will not
* be imported until first use.
*
* @param object
* The object to define the lazy getter on.
* @param name
* The name of the getter to define on object for the module.
* @param resource
* The URL used to obtain the module.
*/
function defineLazyModuleGetter(object, name, resource) {
defineLazyGetter(object, name, function() {
try {
return ChromeUtils.import(resource)[name];
} catch (ex) {
Cu.reportError("Failed to load module " + resource + ".");
throw ex;
}
});
}
/**
* Define a getter property on the given object that requires the given
* module. This enables delaying importing modules until the module is
* actually used.
*
* Several getters can be defined at once by providing an array of
* properties and enabling destructuring.
*
* @param { Object } obj
* The object to define the property on.
* @param { String | Array<String> } properties
* String: Name of the property for the getter.
* Array<String>: When destructure is true, properties can be an array of
* strings to create several getters at once.
* @param { String } module
* The module path.
* @param { Boolean } destructure
* Pass true if the property name is a member of the module's exports.
*/
function lazyRequireGetter(obj, properties, module, destructure) {
if (Array.isArray(properties) && !destructure) {
throw new Error(
"Pass destructure=true to call lazyRequireGetter with an array of properties"
);
}
if (!Array.isArray(properties)) {
properties = [properties];
}
for (const property of properties) {
defineLazyGetter(obj, property, () => {
return destructure
? require(module)[property]
: require(module || property);
});
}
}
// List of pseudo modules exposed to all devtools modules.
exports.modules = {
ChromeUtils,
DebuggerNotificationObserver,
HeapSnapshot,
InspectorUtils,
promise,
// Expose "chrome" Promise, which aren't related to any document
// and so are never frozen, even if the browser loader module which
// pull it is destroyed. See bug 1402779.
Promise,
Services: Object.create(Services),
TelemetryStopwatch,
};
defineLazyGetter(exports.modules, "Debugger", () => {
const global = Cu.getGlobalForObject(this);
// Debugger may already have been added.
if (global.Debugger) {
return global.Debugger;
}
const { addDebuggerToGlobal } = ChromeUtils.import(
"resource://gre/modules/jsdebugger.jsm"
);
addDebuggerToGlobal(global);
return global.Debugger;
});
defineLazyGetter(exports.modules, "ChromeDebugger", () => {
const { addDebuggerToGlobal } = ChromeUtils.import(
"resource://gre/modules/jsdebugger.jsm"
);
addDebuggerToGlobal(debuggerSandbox);
return debuggerSandbox.Debugger;
});
defineLazyGetter(exports.modules, "xpcInspector", () => {
return Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector);
});
// List of all custom globals exposed to devtools modules.
// Changes here should be mirrored to devtools/.eslintrc.
exports.globals = {
AbortController,
atob,
Blob,
btoa,
BrowsingContext,
console,
crypto,
CSS,
CSSRule,
DOMParser,
DOMPoint,
DOMQuad,
Event,
NamedNodeMap,
NodeFilter,
DOMRect,
Element,
FileReader,
FormData,
isWorker: false,
L10nRegistry,
loader: {
lazyGetter: defineLazyGetter,
lazyImporter: defineLazyModuleGetter,
lazyServiceGetter: defineLazyServiceGetter,
lazyRequireGetter: lazyRequireGetter,
// Defined by Loader.jsm
id: null,
},
Node,
reportError: Cu.reportError,
StructuredCloneHolder,
TextDecoder,
TextEncoder,
URL,
XMLHttpRequest,
};
// DevTools loader copy globals property descriptors on each module global
// object so that we have to memoize them from here in order to instantiate each
// global only once.
// `globals` is a cache object on which we put all global values
// and we set getters on `exports.globals` returning `globals` values.
const globals = {};
function lazyGlobal(name, getter) {
defineLazyGetter(globals, name, getter);
Object.defineProperty(exports.globals, name, {
get: function() {
return globals[name];
},
configurable: true,
enumerable: true,
});
}
// Lazily define a few things so that the corresponding jsms are only loaded
// when used.
lazyGlobal("clearTimeout", () => {
return require("resource://gre/modules/Timer.jsm").clearTimeout;
});
lazyGlobal("setTimeout", () => {
return require("resource://gre/modules/Timer.jsm").setTimeout;
});
lazyGlobal("clearInterval", () => {
return require("resource://gre/modules/Timer.jsm").clearInterval;
});
lazyGlobal("setInterval", () => {
return require("resource://gre/modules/Timer.jsm").setInterval;
});
lazyGlobal("WebSocket", () => {
return Services.appShell.hiddenDOMWindow.WebSocket;
});
lazyGlobal("indexedDB", () => {
return require("devtools/shared/indexed-db").createDevToolsIndexedDB(
indexedDB
);
});