Bug 1471102: Move more code out of ExtensionUtils.jsm. r=aswan

MozReview-Commit-ID: Fqlv5BRuuW8

--HG--
extra : rebase_source : 348f037abd9cecfa080183bc365e5f005eac1bd6
extra : amend_source : 05dbfd12f553fc3f2a93374402e34d271e26d767
This commit is contained in:
Kris Maglione 2018-06-25 19:30:21 -07:00
parent 766cc497e9
commit fcedebb912
27 changed files with 521 additions and 479 deletions

View File

@ -21,6 +21,7 @@ var EXPORTED_SYMBOLS = ["ExtensionControlledPopup"];
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm");
ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.defineModuleGetter(this, "AddonManager",
@ -32,7 +33,9 @@ ChromeUtils.defineModuleGetter(this, "CustomizableUI",
ChromeUtils.defineModuleGetter(this, "ExtensionSettingsStore",
"resource://gre/modules/ExtensionSettingsStore.jsm");
let {makeWidgetId} = ExtensionUtils;
let {
makeWidgetId,
} = ExtensionCommon;
XPCOMUtils.defineLazyGetter(this, "strBundle", function() {
return Services.strings.createBundle("chrome://global/locale/extensions.properties");

View File

@ -18,14 +18,18 @@ ChromeUtils.defineModuleGetter(this, "setTimeout",
"resource://gre/modules/Timer.jsm");
ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm");
var {
DefaultWeakMap,
makeWidgetId,
promiseEvent,
} = ExtensionUtils;
const {
makeWidgetId,
} = ExtensionCommon;
const POPUP_LOAD_TIMEOUT_MS = 200;

View File

@ -18,7 +18,7 @@ var {
* @param {string} panelOptions.id
* The id of the addon devtools panel registered in the main process.
*/
class ChildDevToolsPanel extends ExtensionUtils.EventEmitter {
class ChildDevToolsPanel extends ExtensionCommon.EventEmitter {
constructor(context, {id}) {
super();
@ -144,7 +144,7 @@ class ChildDevToolsPanel extends ExtensionUtils.EventEmitter {
* @param {string} sidebarOptions.id
* The id of the addon devtools sidebar registered in the main process.
*/
class ChildDevToolsInspectorSidebar extends ExtensionUtils.EventEmitter {
class ChildDevToolsInspectorSidebar extends ExtensionCommon.EventEmitter {
constructor(context, {id}) {
super();

View File

@ -4,7 +4,7 @@
var {
withHandlingUserInput,
} = ExtensionUtils;
} = ExtensionCommon;
// If id is not specified for an item we use an integer.
// This ID need only be unique within a single addon. Since all addon code that

View File

@ -13,9 +13,12 @@ ChromeUtils.defineModuleGetter(this, "BrowserWindowTracker",
var {
ExtensionError,
defineLazyGetter,
} = ExtensionUtils;
var {
defineLazyGetter,
} = ExtensionCommon;
const READER_MODE_PREFIX = "about:reader";
let tabTracker;
@ -31,8 +34,8 @@ const getSender = (extension, target, sender) => {
// page-open listener below).
tabId = sender.tabId;
delete sender.tabId;
} else if (ExtensionUtils.instanceOf(target, "XULElement") ||
ExtensionUtils.instanceOf(target, "HTMLIFrameElement")) {
} else if (ExtensionCommon.instanceOf(target, "XULElement") ||
ExtensionCommon.instanceOf(target, "HTMLIFrameElement")) {
tabId = tabTracker.getBrowserData(target).tabId;
}

View File

@ -9,7 +9,7 @@ ChromeUtils.defineModuleGetter(this, "Services",
var {
normalizeTime,
} = ExtensionUtils;
} = ExtensionCommon;
let nsINavHistoryService = Ci.nsINavHistoryService;
const TRANSITION_TO_TRANSITION_TYPES_MAP = new Map([

View File

@ -6,8 +6,8 @@ ChromeUtils.defineModuleGetter(this, "PlacesTestUtils",
"resource://testing-common/PlacesTestUtils.jsm");
ChromeUtils.defineModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
ChromeUtils.defineModuleGetter(this, "ExtensionUtils",
"resource://gre/modules/ExtensionUtils.jsm");
ChromeUtils.defineModuleGetter(this, "ExtensionCommon",
"resource://gre/modules/ExtensionCommon.jsm");
ChromeUtils.import("resource://testing-common/PromiseTestUtils.jsm");
@ -308,7 +308,7 @@ add_task(async function test_add_url() {
equal(results.result.title, results.details.title, "URL was added with the correct title");
if (results.details.visitTime) {
equal(results.result.lastVisitTime,
Number(ExtensionUtils.normalizeTime(results.details.visitTime)),
Number(ExtensionCommon.normalizeTime(results.details.visitTime)),
"URL was added with the correct date");
}
}

View File

@ -9,14 +9,18 @@ ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
/* globals EventDispatcher */
ChromeUtils.import("resource://gre/modules/Messaging.jsm");
ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm");
var {
DefaultWeakMap,
ExtensionError,
defineLazyGetter,
} = ExtensionUtils;
var {
defineLazyGetter,
} = ExtensionCommon;
global.GlobalEventDispatcher = EventDispatcher.instance;
const BrowserStatusFilter = Components.Constructor(

View File

@ -42,7 +42,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
AppConstants: "resource://gre/modules/AppConstants.jsm",
AsyncShutdown: "resource://gre/modules/AsyncShutdown.jsm",
ContextualIdentityService: "resource://gre/modules/ContextualIdentityService.jsm",
ExtensionCommon: "resource://gre/modules/ExtensionCommon.jsm",
ExtensionPermissions: "resource://gre/modules/ExtensionPermissions.jsm",
ExtensionStorage: "resource://gre/modules/ExtensionStorage.jsm",
ExtensionTestCommon: "resource://testing-common/ExtensionTestCommon.jsm",
@ -77,6 +76,7 @@ XPCOMUtils.defineLazyGetter(
() => Services.io.getProtocolHandler("resource")
.QueryInterface(Ci.nsIResProtocolHandler));
ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
ChromeUtils.import("resource://gre/modules/ExtensionParent.jsm");
ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm");
@ -96,12 +96,15 @@ var {
} = ExtensionParent;
const {
EventEmitter,
getUniqueId,
promiseTimeout,
} = ExtensionUtils;
XPCOMUtils.defineLazyGetter(this, "console", ExtensionUtils.getConsole);
const {
EventEmitter,
} = ExtensionCommon;
XPCOMUtils.defineLazyGetter(this, "console", ExtensionCommon.getConsole);
XPCOMUtils.defineLazyGetter(this, "LocaleData", () => ExtensionCommon.LocaleData);
@ -1430,7 +1433,7 @@ class Extension extends ExtensionData {
return true;
}
return ExtensionUtils.checkLoadURL(url, this.principal, options);
return ExtensionCommon.checkLoadURL(url, this.principal, options);
}
async promiseLocales(locale) {

View File

@ -42,21 +42,21 @@ ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm");
const {
DefaultMap,
EventEmitter,
LimitedSet,
defineLazyGetter,
getMessageManager,
getUniqueId,
getWinUtils,
withHandlingUserInput,
} = ExtensionUtils;
const {
EventEmitter,
EventManager,
LocalAPIImplementation,
LocaleData,
NoCloneSpreadArgs,
SchemaAPIInterface,
defineLazyGetter,
withHandlingUserInput,
} = ExtensionCommon;
const isContentProcess = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;

View File

@ -22,6 +22,7 @@ XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
XPCOMUtils.defineLazyModuleGetters(this, {
AppConstants: "resource://gre/modules/AppConstants.jsm",
ConsoleAPI: "resource://gre/modules/Console.jsm",
MessageChannel: "resource://gre/modules/MessageChannel.jsm",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
Schemas: "resource://gre/modules/Schemas.jsm",
@ -37,16 +38,20 @@ ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm");
var {
DefaultMap,
DefaultWeakMap,
EventEmitter,
ExtensionError,
defineLazyGetter,
filterStack,
getConsole,
getInnerWindowID,
getUniqueId,
getWinUtils,
} = ExtensionUtils;
function getConsole() {
return new ConsoleAPI({
maxLogLevelPref: "extensions.webextensions.log.level",
prefix: "WebExtensions",
});
}
XPCOMUtils.defineLazyGetter(this, "console", getConsole);
XPCOMUtils.defineLazyPreferenceGetter(this, "DELAYED_BG_STARTUP",
@ -54,6 +59,121 @@ XPCOMUtils.defineLazyPreferenceGetter(this, "DELAYED_BG_STARTUP",
var ExtensionCommon;
// Run a function and report exceptions.
function runSafeSyncWithoutClone(f, ...args) {
try {
return f(...args);
} catch (e) {
dump(`Extension error: ${e} ${e.fileName} ${e.lineNumber}\n[[Exception stack\n${filterStack(e)}Current stack\n${filterStack(Error())}]]\n`);
Cu.reportError(e);
}
}
// Return true if the given value is an instance of the given
// native type.
function instanceOf(value, type) {
return (value && typeof value === "object" &&
ChromeUtils.getClassName(value) === type);
}
/**
* Convert any of several different representations of a date/time to a Date object.
* Accepts several formats:
* a Date object, an ISO8601 string, or a number of milliseconds since the epoch as
* either a number or a string.
*
* @param {Date|string|number} date
* The date to convert.
* @returns {Date}
* A Date object
*/
function normalizeTime(date) {
// Of all the formats we accept the "number of milliseconds since the epoch as a string"
// is an outlier, everything else can just be passed directly to the Date constructor.
return new Date((typeof date == "string" && /^\d+$/.test(date))
? parseInt(date, 10) : date);
}
function withHandlingUserInput(window, callable) {
let handle = getWinUtils(window).setHandlingUserInput(true);
try {
return callable();
} finally {
handle.destruct();
}
}
/**
* Defines a lazy getter for the given property on the given object. The
* first time the property is accessed, the return value of the getter
* is defined on the current `this` object with the given property name.
* Importantly, this means that a lazy getter defined on an object
* prototype will be invoked separately for each object instance that
* it's accessed on.
*
* @param {object} object
* The prototype object on which to define the getter.
* @param {string|Symbol} prop
* The property name for which to define the getter.
* @param {function} getter
* The function to call in order to generate the final property
* value.
*/
function defineLazyGetter(object, prop, getter) {
let redefine = (obj, value) => {
Object.defineProperty(obj, prop, {
enumerable: true,
configurable: true,
writable: true,
value,
});
return value;
};
Object.defineProperty(object, prop, {
enumerable: true,
configurable: true,
get() {
return redefine(this, getter.call(this));
},
set(value) {
redefine(this, value);
},
});
}
function checkLoadURL(url, principal, options) {
let ssm = Services.scriptSecurityManager;
let flags = ssm.STANDARD;
if (!options.allowScript) {
flags |= ssm.DISALLOW_SCRIPT;
}
if (!options.allowInheritsPrincipal) {
flags |= ssm.DISALLOW_INHERIT_PRINCIPAL;
}
if (options.dontReportErrors) {
flags |= ssm.DONT_REPORT_ERRORS;
}
try {
ssm.checkLoadURIWithPrincipal(principal,
Services.io.newURI(url),
flags);
} catch (e) {
return false;
}
return true;
}
function makeWidgetId(id) {
id = id.toLowerCase();
// FIXME: This allows for collisions.
return id.replace(/[^a-z0-9_-]/g, "_");
}
/**
* A sentinel class to indicate that an array of values should be
* treated as an array when used as a promise resolution value, but as a
@ -85,12 +205,119 @@ class NoCloneSpreadArgs {
}
}
const LISTENERS = Symbol("listeners");
const ONCE_MAP = Symbol("onceMap");
class EventEmitter {
constructor() {
this[LISTENERS] = new Map();
this[ONCE_MAP] = new WeakMap();
}
/**
* Adds the given function as a listener for the given event.
*
* The listener function may optionally return a Promise which
* resolves when it has completed all operations which event
* dispatchers may need to block on.
*
* @param {string} event
* The name of the event to listen for.
* @param {function(string, ...any)} listener
* The listener to call when events are emitted.
*/
on(event, listener) {
let listeners = this[LISTENERS].get(event);
if (!listeners) {
listeners = new Set();
this[LISTENERS].set(event, listeners);
}
listeners.add(listener);
}
/**
* Removes the given function as a listener for the given event.
*
* @param {string} event
* The name of the event to stop listening for.
* @param {function(string, ...any)} listener
* The listener function to remove.
*/
off(event, listener) {
let set = this[LISTENERS].get(event);
if (set) {
set.delete(listener);
set.delete(this[ONCE_MAP].get(listener));
if (!set.size) {
this[LISTENERS].delete(event);
}
}
}
/**
* Adds the given function as a listener for the given event once.
*
* @param {string} event
* The name of the event to listen for.
* @param {function(string, ...any)} listener
* The listener to call when events are emitted.
*/
once(event, listener) {
let wrapper = (...args) => {
this.off(event, wrapper);
this[ONCE_MAP].delete(listener);
return listener(...args);
};
this[ONCE_MAP].set(listener, wrapper);
this.on(event, wrapper);
}
/**
* Triggers all listeners for the given event. If any listeners return
* a value, returns a promise which resolves when all returned
* promises have resolved. Otherwise, returns undefined.
*
* @param {string} event
* The name of the event to emit.
* @param {any} args
* Arbitrary arguments to pass to the listener functions, after
* the event name.
* @returns {Promise?}
*/
emit(event, ...args) {
let listeners = this[LISTENERS].get(event);
if (listeners) {
let promises = [];
for (let listener of listeners) {
try {
let result = listener(event, ...args);
if (result !== undefined) {
promises.push(result);
}
} catch (e) {
Cu.reportError(e);
}
}
if (promises.length) {
return Promise.all(promises);
}
}
}
}
/**
* Base class for WebExtension APIs. Each API creates a new class
* that inherits from this class, the derived class is instantiated
* once for each extension that uses the API.
*/
class ExtensionAPI extends ExtensionUtils.EventEmitter {
class ExtensionAPI extends EventEmitter {
constructor(extension) {
super();
@ -246,7 +473,7 @@ class BaseContext {
return true;
}
return ExtensionUtils.checkLoadURL(url, this.principal, options);
return checkLoadURL(url, this.principal, options);
}
/**
@ -2074,14 +2301,23 @@ ExtensionCommon = {
CanOfAPIs,
EventManager,
ExtensionAPI,
EventEmitter,
LocalAPIImplementation,
LocaleData,
NoCloneSpreadArgs,
SchemaAPIInterface,
SchemaAPIManager,
SpreadArgs,
checkLoadURL,
defineLazyGetter,
getConsole,
ignoreEvent,
instanceOf,
makeWidgetId,
normalizeTime,
runSafeSyncWithoutClone,
stylesheetMap,
withHandlingUserInput,
MultiAPIManager,
LazyAPIManager,

View File

@ -39,19 +39,19 @@ XPCOMUtils.defineLazyGlobalGetters(this, ["crypto", "TextEncoder"]);
const {
DefaultMap,
DefaultWeakMap,
defineLazyGetter,
getInnerWindowID,
getWinUtils,
promiseDocumentIdle,
promiseDocumentLoaded,
promiseDocumentReady,
runSafeSyncWithoutClone,
} = ExtensionUtils;
const {
BaseContext,
CanOfAPIs,
SchemaAPIManager,
defineLazyGetter,
runSafeSyncWithoutClone,
} = ExtensionCommon;
const {
@ -60,7 +60,7 @@ const {
Messenger,
} = ExtensionChild;
XPCOMUtils.defineLazyGetter(this, "console", ExtensionUtils.getConsole);
XPCOMUtils.defineLazyGetter(this, "console", ExtensionCommon.getConsole);
var DocumentManager;

View File

@ -37,7 +37,6 @@ ChromeUtils.import("resource://gre/modules/ExtensionChild.jsm");
ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm");
const {
defineLazyGetter,
getInnerWindowID,
promiseEvent,
} = ExtensionUtils;
@ -46,6 +45,7 @@ const {
BaseContext,
CanOfAPIs,
SchemaAPIManager,
defineLazyGetter,
} = ExtensionCommon;
const {

View File

@ -25,8 +25,9 @@ XPCOMUtils.defineLazyModuleGetters(this, {
E10SUtils: "resource://gre/modules/E10SUtils.jsm",
ExtensionData: "resource://gre/modules/Extension.jsm",
MessageChannel: "resource://gre/modules/MessageChannel.jsm",
OS: "resource://gre/modules/osfile.jsm",
MessageManagerProxy: "resource://gre/modules/MessageManagerProxy.jsm",
NativeApp: "resource://gre/modules/NativeMessaging.jsm",
OS: "resource://gre/modules/osfile.jsm",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
Schemas: "resource://gre/modules/Schemas.jsm",
});
@ -43,14 +44,13 @@ var {
CanOfAPIs,
SchemaAPIManager,
SpreadArgs,
defineLazyGetter,
} = ExtensionCommon;
var {
DefaultMap,
DefaultWeakMap,
ExtensionError,
MessageManagerProxy,
defineLazyGetter,
promiseDocumentLoaded,
promiseEvent,
promiseObserved,

View File

@ -48,6 +48,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
CollectionKeyManager: "resource://services-sync/record.js",
CommonUtils: "resource://services-common/utils.js",
CryptoUtils: "resource://services-crypto/utils.js",
ExtensionCommon: "resource://gre/modules/ExtensionCommon.jsm",
fxAccounts: "resource://gre/modules/FxAccounts.jsm",
KintoHttpClient: "resource://services-common/kinto-http-client.js",
Kinto: "resource://services-common/kinto-offline-client.js",
@ -68,7 +69,6 @@ XPCOMUtils.defineLazyGetter(this, "WeaveCrypto", function() {
const {
DefaultMap,
runSafeSyncWithoutClone,
} = ExtensionUtils;
// Map of Extensions to Set<Contexts> to track contexts that are still
@ -1249,7 +1249,7 @@ class ExtensionStorageSync {
let listeners = this.listeners.get(extension) || new Set();
if (listeners) {
for (let listener of listeners) {
runSafeSyncWithoutClone(listener, changes);
ExtensionCommon.runSafeSyncWithoutClone(listener, changes);
}
}
}

View File

@ -33,6 +33,7 @@ ChromeUtils.defineModuleGetter(this, "OS",
XPCOMUtils.defineLazyGetter(this, "apiManager",
() => ExtensionParent.apiManager);
ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "uuidGen",
@ -41,10 +42,13 @@ XPCOMUtils.defineLazyServiceGetter(this, "uuidGen",
const {
flushJarCache,
instanceOf,
} = ExtensionUtils;
XPCOMUtils.defineLazyGetter(this, "console", ExtensionUtils.getConsole);
const {
instanceOf,
} = ExtensionCommon;
XPCOMUtils.defineLazyGetter(this, "console", () => ExtensionCommon.getConsole());
/**

View File

@ -10,20 +10,9 @@ var EXPORTED_SYMBOLS = ["ExtensionUtils"];
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.defineModuleGetter(this, "ConsoleAPI",
"resource://gre/modules/Console.jsm");
ChromeUtils.defineModuleGetter(this, "setTimeout",
"resource://gre/modules/Timer.jsm");
function getConsole() {
return new ConsoleAPI({
maxLogLevelPref: "extensions.webextensions.log.level",
prefix: "WebExtensions",
});
}
XPCOMUtils.defineLazyGetter(this, "console", getConsole);
// xpcshell doesn't handle idle callbacks well.
XPCOMUtils.defineLazyGetter(this, "idleTimeout",
() => Services.appinfo.name === "XPCShell" ? 500 : undefined);
@ -62,23 +51,6 @@ function filterStack(error) {
return String(error.stack).replace(/(^.*(Task\.jsm|Promise-backend\.js).*\n)+/gm, "<Promise Chain>\n");
}
// Run a function and report exceptions.
function runSafeSyncWithoutClone(f, ...args) {
try {
return f(...args);
} catch (e) {
dump(`Extension error: ${e} ${e.fileName} ${e.lineNumber}\n[[Exception stack\n${filterStack(e)}Current stack\n${filterStack(Error())}]]\n`);
Cu.reportError(e);
}
}
// Return true if the given value is an instance of the given
// native type.
function instanceOf(value, type) {
return (value && typeof value === "object" &&
ChromeUtils.getClassName(value) === type);
}
/**
* Similar to a WeakMap, but creates a new key with the given
* constructor if one is not present.
@ -129,122 +101,6 @@ function getInnerWindowID(window) {
return getWinUtils(window).currentInnerWindowID;
}
function withHandlingUserInput(window, callable) {
let handle = getWinUtils(window).setHandlingUserInput(true);
try {
return callable();
} finally {
handle.destruct();
}
}
const LISTENERS = Symbol("listeners");
const ONCE_MAP = Symbol("onceMap");
class EventEmitter {
constructor() {
this[LISTENERS] = new Map();
this[ONCE_MAP] = new WeakMap();
}
/**
* Adds the given function as a listener for the given event.
*
* The listener function may optionally return a Promise which
* resolves when it has completed all operations which event
* dispatchers may need to block on.
*
* @param {string} event
* The name of the event to listen for.
* @param {function(string, ...any)} listener
* The listener to call when events are emitted.
*/
on(event, listener) {
let listeners = this[LISTENERS].get(event);
if (!listeners) {
listeners = new Set();
this[LISTENERS].set(event, listeners);
}
listeners.add(listener);
}
/**
* Removes the given function as a listener for the given event.
*
* @param {string} event
* The name of the event to stop listening for.
* @param {function(string, ...any)} listener
* The listener function to remove.
*/
off(event, listener) {
let set = this[LISTENERS].get(event);
if (set) {
set.delete(listener);
set.delete(this[ONCE_MAP].get(listener));
if (!set.size) {
this[LISTENERS].delete(event);
}
}
}
/**
* Adds the given function as a listener for the given event once.
*
* @param {string} event
* The name of the event to listen for.
* @param {function(string, ...any)} listener
* The listener to call when events are emitted.
*/
once(event, listener) {
let wrapper = (...args) => {
this.off(event, wrapper);
this[ONCE_MAP].delete(listener);
return listener(...args);
};
this[ONCE_MAP].set(listener, wrapper);
this.on(event, wrapper);
}
/**
* Triggers all listeners for the given event. If any listeners return
* a value, returns a promise which resolves when all returned
* promises have resolved. Otherwise, returns undefined.
*
* @param {string} event
* The name of the event to emit.
* @param {any} args
* Arbitrary arguments to pass to the listener functions, after
* the event name.
* @returns {Promise?}
*/
emit(event, ...args) {
let listeners = this[LISTENERS].get(event);
if (listeners) {
let promises = [];
for (let listener of listeners) {
try {
let result = listener(event, ...args);
if (result !== undefined) {
promises.push(result);
}
} catch (e) {
Cu.reportError(e);
}
}
if (promises.length) {
return Promise.all(promises);
}
}
}
}
/**
* A set with a limited number of slots, which flushes older entries as
* newer ones are added.
@ -404,303 +260,21 @@ function flushJarCache(jarPath) {
Services.obs.notifyObservers(null, "flush-cache-entry", jarPath);
}
/**
* Convert any of several different representations of a date/time to a Date object.
* Accepts several formats:
* a Date object, an ISO8601 string, or a number of milliseconds since the epoch as
* either a number or a string.
*
* @param {Date|string|number} date
* The date to convert.
* @returns {Date}
* A Date object
*/
function normalizeTime(date) {
// Of all the formats we accept the "number of milliseconds since the epoch as a string"
// is an outlier, everything else can just be passed directly to the Date constructor.
return new Date((typeof date == "string" && /^\d+$/.test(date))
? parseInt(date, 10) : date);
}
/**
* Defines a lazy getter for the given property on the given object. The
* first time the property is accessed, the return value of the getter
* is defined on the current `this` object with the given property name.
* Importantly, this means that a lazy getter defined on an object
* prototype will be invoked separately for each object instance that
* it's accessed on.
*
* @param {object} object
* The prototype object on which to define the getter.
* @param {string|Symbol} prop
* The property name for which to define the getter.
* @param {function} getter
* The function to call in order to generate the final property
* value.
*/
function defineLazyGetter(object, prop, getter) {
let redefine = (obj, value) => {
Object.defineProperty(obj, prop, {
enumerable: true,
configurable: true,
writable: true,
value,
});
return value;
};
Object.defineProperty(object, prop, {
enumerable: true,
configurable: true,
get() {
return redefine(this, getter.call(this));
},
set(value) {
redefine(this, value);
},
});
}
/**
* Acts as a proxy for a message manager or message manager owner, and
* tracks docShell swaps so that messages are always sent to the same
* receiver, even if it is moved to a different <browser>.
*
* @param {nsIMessageSender|Element} target
* The target message manager on which to send messages, or the
* <browser> element which owns it.
*/
class MessageManagerProxy {
constructor(target) {
this.listeners = new DefaultMap(() => new Map());
this.closed = false;
if (target instanceof Ci.nsIMessageSender) {
this.messageManager = target;
} else {
this.addListeners(target);
}
Services.obs.addObserver(this, "message-manager-close");
}
/**
* Disposes of the proxy object, removes event listeners, and drops
* all references to the underlying message manager.
*
* Must be called before the last reference to the proxy is dropped,
* unless the underlying message manager or <browser> is also being
* destroyed.
*/
dispose() {
if (this.eventTarget) {
this.removeListeners(this.eventTarget);
this.eventTarget = null;
}
this.messageManager = null;
Services.obs.removeObserver(this, "message-manager-close");
}
observe(subject, topic, data) {
if (topic === "message-manager-close") {
if (subject === this.messageManager) {
this.closed = true;
}
}
}
/**
* Returns true if the given target is the same as, or owns, the given
* message manager.
*
* @param {nsIMessageSender|MessageManagerProxy|Element} target
* The message manager, MessageManagerProxy, or <browser>
* element against which to match.
* @param {nsIMessageSender} messageManager
* The message manager against which to match `target`.
*
* @returns {boolean}
* True if `messageManager` is the same object as `target`, or
* `target` is a MessageManagerProxy or <browser> element that
* is tied to it.
*/
static matches(target, messageManager) {
return target === messageManager || target.messageManager === messageManager;
}
/**
* @property {nsIMessageSender|null} messageManager
* The message manager that is currently being proxied. This
* may change during the life of the proxy object, so should
* not be stored elsewhere.
*/
/**
* Sends a message on the proxied message manager.
*
* @param {array} args
* Arguments to be passed verbatim to the underlying
* sendAsyncMessage method.
* @returns {undefined}
*/
sendAsyncMessage(...args) {
if (this.messageManager) {
return this.messageManager.sendAsyncMessage(...args);
}
Cu.reportError(`Cannot send message: Other side disconnected: ${uneval(args)}`);
}
get isDisconnected() {
return this.closed || !this.messageManager;
}
/**
* Adds a message listener to the current message manager, and
* transfers it to the new message manager after a docShell swap.
*
* @param {string} message
* The name of the message to listen for.
* @param {nsIMessageListener} listener
* The listener to add.
* @param {boolean} [listenWhenClosed = false]
* If true, the listener will receive messages which were sent
* after the remote side of the listener began closing.
*/
addMessageListener(message, listener, listenWhenClosed = false) {
this.messageManager.addMessageListener(message, listener, listenWhenClosed);
this.listeners.get(message).set(listener, listenWhenClosed);
}
/**
* Adds a message listener from the current message manager.
*
* @param {string} message
* The name of the message to stop listening for.
* @param {nsIMessageListener} listener
* The listener to remove.
*/
removeMessageListener(message, listener) {
this.messageManager.removeMessageListener(message, listener);
let listeners = this.listeners.get(message);
listeners.delete(listener);
if (!listeners.size) {
this.listeners.delete(message);
}
}
/**
* @private
* Iterates over all of the currently registered message listeners.
*/
* iterListeners() {
for (let [message, listeners] of this.listeners) {
for (let [listener, listenWhenClosed] of listeners) {
yield {message, listener, listenWhenClosed};
}
}
}
/**
* @private
* Adds docShell swap listeners to the message manager owner.
*
* @param {Element} target
* The target element.
*/
addListeners(target) {
target.addEventListener("SwapDocShells", this);
this.eventTarget = target;
this.messageManager = target.messageManager;
for (let {message, listener, listenWhenClosed} of this.iterListeners()) {
this.messageManager.addMessageListener(message, listener, listenWhenClosed);
}
}
/**
* @private
* Removes docShell swap listeners to the message manager owner.
*
* @param {Element} target
* The target element.
*/
removeListeners(target) {
target.removeEventListener("SwapDocShells", this);
for (let {message, listener} of this.iterListeners()) {
this.messageManager.removeMessageListener(message, listener);
}
}
handleEvent(event) {
if (event.type == "SwapDocShells") {
this.removeListeners(this.eventTarget);
this.addListeners(event.detail);
}
}
}
function checkLoadURL(url, principal, options) {
let ssm = Services.scriptSecurityManager;
let flags = ssm.STANDARD;
if (!options.allowScript) {
flags |= ssm.DISALLOW_SCRIPT;
}
if (!options.allowInheritsPrincipal) {
flags |= ssm.DISALLOW_INHERIT_PRINCIPAL;
}
if (options.dontReportErrors) {
flags |= ssm.DONT_REPORT_ERRORS;
}
try {
ssm.checkLoadURIWithPrincipal(principal,
Services.io.newURI(url),
flags);
} catch (e) {
return false;
}
return true;
}
function makeWidgetId(id) {
id = id.toLowerCase();
// FIXME: This allows for collisions.
return id.replace(/[^a-z0-9_-]/g, "_");
}
var ExtensionUtils = {
checkLoadURL,
defineLazyGetter,
flushJarCache,
getConsole,
getInnerWindowID,
getMessageManager,
getUniqueId,
filterStack,
getWinUtils,
instanceOf,
makeWidgetId,
normalizeTime,
promiseDocumentIdle,
promiseDocumentLoaded,
promiseDocumentReady,
promiseEvent,
promiseObserved,
promiseTimeout,
runSafeSyncWithoutClone,
withHandlingUserInput,
DefaultMap,
DefaultWeakMap,
EventEmitter,
ExtensionError,
LimitedSet,
MessageManagerProxy,
};

View File

@ -105,9 +105,19 @@ ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");
const {
MessageManagerProxy,
} = ExtensionUtils;
ChromeUtils.defineModuleGetter(this, "MessageManagerProxy",
"resource://gre/modules/MessageManagerProxy.jsm");
function getMessageManager(target) {
if (typeof target.sendAsyncMessage === "function") {
return target;
}
return new MessageManagerProxy(target);
}
function matches(target, messageManager) {
return target === messageManager || target.messageManager === messageManager;
}
const {DEBUG} = AppConstants;
@ -919,7 +929,7 @@ this.MessageChannel = {
return;
}
let target = new MessageManagerProxy(data.target);
let target = getMessageManager(data.target);
let deferred = {
sender: data.sender,
@ -930,7 +940,9 @@ this.MessageChannel = {
let cleanup = () => {
this.pendingResponses.delete(deferred);
target.dispose();
if (target.dispose) {
target.dispose();
}
};
this.pendingResponses.add(deferred);
@ -1078,7 +1090,7 @@ this.MessageChannel = {
*/
abortMessageManager(target, reason) {
for (let response of this.pendingResponses) {
if (MessageManagerProxy.matches(response.messageManager, target)) {
if (matches(response.messageManager, target)) {
this.abortedResponses.add(response.channelId);
response.reject(reason);
}

View File

@ -0,0 +1,198 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* 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";
var EXPORTED_SYMBOLS = ["MessageManagerProxy"];
ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");
const {
DefaultMap,
} = ExtensionUtils;
/**
* Acts as a proxy for a message manager or message manager owner, and
* tracks docShell swaps so that messages are always sent to the same
* receiver, even if it is moved to a different <browser>.
*
* @param {nsIMessageSender|Element} target
* The target message manager on which to send messages, or the
* <browser> element which owns it.
*/
class MessageManagerProxy {
constructor(target) {
this.listeners = new DefaultMap(() => new Map());
this.closed = false;
if (target instanceof Ci.nsIMessageSender) {
this.messageManager = target;
} else {
this.addListeners(target);
}
Services.obs.addObserver(this, "message-manager-close");
}
/**
* Disposes of the proxy object, removes event listeners, and drops
* all references to the underlying message manager.
*
* Must be called before the last reference to the proxy is dropped,
* unless the underlying message manager or <browser> is also being
* destroyed.
*/
dispose() {
if (this.eventTarget) {
this.removeListeners(this.eventTarget);
this.eventTarget = null;
}
this.messageManager = null;
Services.obs.removeObserver(this, "message-manager-close");
}
observe(subject, topic, data) {
if (topic === "message-manager-close") {
if (subject === this.messageManager) {
this.closed = true;
}
}
}
/**
* Returns true if the given target is the same as, or owns, the given
* message manager.
*
* @param {nsIMessageSender|MessageManagerProxy|Element} target
* The message manager, MessageManagerProxy, or <browser>
* element against which to match.
* @param {nsIMessageSender} messageManager
* The message manager against which to match `target`.
*
* @returns {boolean}
* True if `messageManager` is the same object as `target`, or
* `target` is a MessageManagerProxy or <browser> element that
* is tied to it.
*/
static matches(target, messageManager) {
return target === messageManager || target.messageManager === messageManager;
}
/**
* @property {nsIMessageSender|null} messageManager
* The message manager that is currently being proxied. This
* may change during the life of the proxy object, so should
* not be stored elsewhere.
*/
/**
* Sends a message on the proxied message manager.
*
* @param {array} args
* Arguments to be passed verbatim to the underlying
* sendAsyncMessage method.
* @returns {undefined}
*/
sendAsyncMessage(...args) {
if (this.messageManager) {
return this.messageManager.sendAsyncMessage(...args);
}
Cu.reportError(`Cannot send message: Other side disconnected: ${uneval(args)}`);
}
get isDisconnected() {
return this.closed || !this.messageManager;
}
/**
* Adds a message listener to the current message manager, and
* transfers it to the new message manager after a docShell swap.
*
* @param {string} message
* The name of the message to listen for.
* @param {nsIMessageListener} listener
* The listener to add.
* @param {boolean} [listenWhenClosed = false]
* If true, the listener will receive messages which were sent
* after the remote side of the listener began closing.
*/
addMessageListener(message, listener, listenWhenClosed = false) {
this.messageManager.addMessageListener(message, listener, listenWhenClosed);
this.listeners.get(message).set(listener, listenWhenClosed);
}
/**
* Adds a message listener from the current message manager.
*
* @param {string} message
* The name of the message to stop listening for.
* @param {nsIMessageListener} listener
* The listener to remove.
*/
removeMessageListener(message, listener) {
this.messageManager.removeMessageListener(message, listener);
let listeners = this.listeners.get(message);
listeners.delete(listener);
if (!listeners.size) {
this.listeners.delete(message);
}
}
/**
* @private
* Iterates over all of the currently registered message listeners.
*/
* iterListeners() {
for (let [message, listeners] of this.listeners) {
for (let [listener, listenWhenClosed] of listeners) {
yield {message, listener, listenWhenClosed};
}
}
}
/**
* @private
* Adds docShell swap listeners to the message manager owner.
*
* @param {Element} target
* The target element.
*/
addListeners(target) {
target.addEventListener("SwapDocShells", this);
this.eventTarget = target;
this.messageManager = target.messageManager;
for (let {message, listener, listenWhenClosed} of this.iterListeners()) {
this.messageManager.addMessageListener(message, listener, listenWhenClosed);
}
}
/**
* @private
* Removes docShell swap listeners to the message manager owner.
*
* @param {Element} target
* The target element.
*/
removeListeners(target) {
target.removeEventListener("SwapDocShells", this);
for (let {message, listener} of this.iterListeners()) {
this.messageManager.removeMessageListener(message, listener);
}
}
handleEvent(event) {
if (event.type == "SwapDocShells") {
this.removeListeners(this.eventTarget);
this.addListeners(event.detail);
}
}
}

View File

@ -38,7 +38,6 @@ const PROXY_TIMEOUT_SEC = 10;
const {
ExtensionError,
defineLazyGetter,
} = ExtensionUtils;
const {
@ -46,6 +45,7 @@ const {
CanOfAPIs,
LocalAPIImplementation,
SchemaAPIManager,
defineLazyGetter,
} = ExtensionCommon;
const PROXY_TYPES = Object.freeze({

View File

@ -16,13 +16,14 @@ ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
ExtensionChild: "resource://gre/modules/ExtensionChild.jsm",
ExtensionCommon: "resource://gre/modules/ExtensionCommon.jsm",
ExtensionContent: "resource://gre/modules/ExtensionContent.jsm",
ExtensionPageChild: "resource://gre/modules/ExtensionPageChild.jsm",
});
ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "console", () => ExtensionUtils.getConsole());
XPCOMUtils.defineLazyGetter(this, "console", () => ExtensionCommon.getConsole());
const {
DefaultWeakMap,

View File

@ -26,6 +26,7 @@ EXTRA_JS_MODULES += [
'FindContent.jsm',
'LegacyExtensionsUtils.jsm',
'MessageChannel.jsm',
'MessageManagerProxy.jsm',
'NativeManifests.jsm',
'NativeMessaging.jsm',
'ProxyScriptContext.jsm',

View File

@ -13,10 +13,6 @@ ChromeUtils.defineModuleGetter(this, "FileUtils",
var {
EventEmitter,
normalizeTime,
} = ExtensionUtils;
var {
ignoreEvent,
} = ExtensionCommon;
@ -262,7 +258,7 @@ const downloadQuery = query => {
if (arg == null) {
return before ? Number.MAX_VALUE : 0;
}
return normalizeTime(arg).getTime();
return ExtensionCommon.normalizeTime(arg).getTime();
}
const startedBefore = normalizeDownloadTime(query.startedBefore, true);

View File

@ -32,7 +32,7 @@ const getIdleObserver = (extension, context) => {
let observerInfo = getIdleObserverInfo(extension, context);
let {observer, detectionInterval} = observerInfo;
if (!observer) {
observer = new class extends ExtensionUtils.EventEmitter {
observer = new class extends ExtensionCommon.EventEmitter {
observe(subject, topic, data) {
if (topic == "idle" || topic == "active") {
this.emit("stateChanged", topic);

View File

@ -95,7 +95,7 @@ function checkAllowedAddon(addon) {
return allowedTypes.includes(addon.type);
}
class AddonListener extends ExtensionUtils.EventEmitter {
class AddonListener extends ExtensionCommon.EventEmitter {
constructor() {
super();
AddonManager.addAddonListener(this);

View File

@ -18,10 +18,13 @@ var {
DefaultMap,
DefaultWeakMap,
ExtensionError,
defineLazyGetter,
getWinUtils,
} = ExtensionUtils;
var {
defineLazyGetter,
} = ExtensionCommon;
/**
* The platform-specific type of native tab objects, which are wrapped by
* TabBase instances.

View File

@ -19,7 +19,7 @@ XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
global.EventEmitter = ExtensionUtils.EventEmitter;
global.EventEmitter = ExtensionCommon.EventEmitter;
global.EventManager = ExtensionCommon.EventManager;
/* globals DEFAULT_STORE, PRIVATE_STORE, CONTAINER_STORE */