Bug 1945464 - Design type-friendly XPCOM.declareLazy() r=Standard8,arai,mossop
Prototype of a type-friendly design for handling all of the different kinds of lazy imports and getters. Differential Revision: https://phabricator.services.mozilla.com/D238131
This commit is contained in:
@@ -119,13 +119,16 @@ export var XPCOMUtils = {
|
||||
* The name of the getter to define on aObject for the service.
|
||||
* @param {string} aContract
|
||||
* The contract used to obtain the service.
|
||||
* @param {string} aInterfaceName
|
||||
* The name of the interface to query the service to.
|
||||
* @param {nsID|string} aInterface
|
||||
* The interface or name of interface to query the service to.
|
||||
*/
|
||||
defineLazyServiceGetter(aObject, aName, aContract, aInterfaceName) {
|
||||
defineLazyServiceGetter(aObject, aName, aContract, aInterface) {
|
||||
ChromeUtils.defineLazyGetter(aObject, aName, () => {
|
||||
if (aInterfaceName) {
|
||||
return Cc[aContract].getService(Ci[aInterfaceName]);
|
||||
if (aInterface) {
|
||||
if (typeof aInterface === "string") {
|
||||
aInterface = Ci[aInterface];
|
||||
}
|
||||
return Cc[aContract].getService(aInterface);
|
||||
}
|
||||
return Cc[aContract].getService().wrappedJSObject;
|
||||
});
|
||||
@@ -283,6 +286,121 @@ export var XPCOMUtils = {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Defines properties on the given object which lazily import
|
||||
* an ES module or run another utility getter when accessed.
|
||||
*
|
||||
* Use this version when you need to define getters on the
|
||||
* global `this`, or any other object you can't assign to:
|
||||
*
|
||||
* @example
|
||||
* XPCOMUtils.defineLazy(this, {
|
||||
* AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
|
||||
* verticalTabs: { pref: "sidebar.verticalTabs", default: false },
|
||||
* MIME: { service: "@mozilla.org/mime;1", iid: Ci.nsInsIMIMEService },
|
||||
* expensiveThing: () => fetch_or_compute(),
|
||||
* });
|
||||
*
|
||||
* Additionally, the given object is also returned, which enables
|
||||
* type-friendly composition:
|
||||
*
|
||||
* @example
|
||||
* const existing = {
|
||||
* someProps: new Widget(),
|
||||
* };
|
||||
* const combined = XPCOMUtils.defineLazy(existing, {
|
||||
* expensiveThing: () => fetch_or_compute(),
|
||||
* });
|
||||
*
|
||||
* The `combined` variable is the same object reference as `existing`,
|
||||
* but TypeScript also knows about lazy getters defined on it.
|
||||
*
|
||||
* Since you probably don't want aliases, you can use it like this to,
|
||||
* for example, define (static) lazy getters on a class:
|
||||
*
|
||||
* @example
|
||||
* const Widget = XPCOMUtils.defineLazy(
|
||||
* class Widget {
|
||||
* static normalProp = 3;
|
||||
* },
|
||||
* {
|
||||
* verticalTabs: { pref: "sidebar.verticalTabs", default: false },
|
||||
* }
|
||||
* );
|
||||
*
|
||||
* @template {LazyDefinition} const L, T
|
||||
*
|
||||
* @param {T} lazy
|
||||
* The object to define the getters on.
|
||||
*
|
||||
* @param {L} definition
|
||||
* Each key:value property defines type and parameters for getters.
|
||||
*
|
||||
* - "resource://module" string
|
||||
* @see {ChromeUtils.defineESModuleGetters}
|
||||
*
|
||||
* - () => value
|
||||
* @see {ChromeUtils.defineLazyGetter}
|
||||
*
|
||||
* - { service: "contract", iid?: nsIID }
|
||||
* @see {XPCOMUtils.defineLazyServiceGetter}
|
||||
*
|
||||
* - { pref: "name", default?, onUpdate?, transform? }
|
||||
* @see {XPCOMUtils.defineLazyPreferenceGetter}
|
||||
*
|
||||
* @param {ImportESModuleOptionsDictionary} [options]
|
||||
* When importing ESModules in devtools and worker contexts,
|
||||
* the third parameter is required.
|
||||
*/
|
||||
defineLazy(lazy, definition, options) {
|
||||
let modules = {};
|
||||
|
||||
for (let [key, val] of Object.entries(definition)) {
|
||||
if (typeof val === "string") {
|
||||
modules[key] = val;
|
||||
} else if (typeof val === "function") {
|
||||
ChromeUtils.defineLazyGetter(lazy, key, val);
|
||||
} else if ("service" in val) {
|
||||
XPCOMUtils.defineLazyServiceGetter(lazy, key, val.service, val.iid);
|
||||
} else if ("pref" in val) {
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
key,
|
||||
val.pref,
|
||||
val.default,
|
||||
val.onUpdate,
|
||||
val.transform
|
||||
);
|
||||
} else {
|
||||
throw new Error(`Unkown LazyDefinition for ${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, modules, options);
|
||||
return /** @type {T & DeclaredLazy<L>} */ (lazy);
|
||||
},
|
||||
|
||||
/**
|
||||
* @see {XPCOMUtils.defineLazy}
|
||||
* A shorthand for above which always returns a new lazy object.
|
||||
* Use this version if you have a global `lazy` const with all the getters:
|
||||
*
|
||||
* @example
|
||||
* const lazy = XPCOMUtils.declareLazy({
|
||||
* AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
|
||||
* verticalTabs: { pref: "sidebar.verticalTabs", default: false },
|
||||
* MIME: { service: "@mozilla.org/mime;1", iid: Ci.nsInsIMIMEService },
|
||||
* expensiveThing: () => fetch_or_compute(),
|
||||
* });
|
||||
*
|
||||
* @template {LazyDefinition} const L
|
||||
* @param {L} declaration
|
||||
* @param {ImportESModuleOptionsDictionary} [options]
|
||||
*/
|
||||
declareLazy(declaration, options) {
|
||||
return XPCOMUtils.defineLazy({}, declaration, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Defines a non-writable property on an object.
|
||||
*
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
* reloaded by the user, we have to ensure that the new extension pages are going
|
||||
* to run in the same process of the existing addon debugging browser element).
|
||||
*/
|
||||
/* eslint-disable mozilla/valid-lazy */
|
||||
|
||||
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
|
||||
@@ -33,10 +34,7 @@ import { ExtensionParent } from "resource://gre/modules/ExtensionParent.sys.mjs"
|
||||
import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";
|
||||
import { Log } from "resource://gre/modules/Log.sys.mjs";
|
||||
|
||||
/** @type {Lazy} */
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
const lazy = XPCOMUtils.declareLazy({
|
||||
AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
|
||||
AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs",
|
||||
AddonSettings: "resource://gre/modules/addons/AddonSettings.sys.mjs",
|
||||
@@ -68,118 +66,99 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
permissionToL10nId:
|
||||
"resource://gre/modules/ExtensionPermissionMessages.sys.mjs",
|
||||
QuarantinedDomains: "resource://gre/modules/ExtensionPermissions.sys.mjs",
|
||||
});
|
||||
|
||||
ChromeUtils.defineLazyGetter(lazy, "resourceProtocol", () =>
|
||||
Services.io
|
||||
.getProtocolHandler("resource")
|
||||
.QueryInterface(Ci.nsIResProtocolHandler)
|
||||
);
|
||||
resourceProtocol: () =>
|
||||
Services.io
|
||||
.getProtocolHandler("resource")
|
||||
.QueryInterface(Ci.nsIResProtocolHandler),
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetters(lazy, {
|
||||
aomStartup: [
|
||||
"@mozilla.org/addons/addon-manager-startup;1",
|
||||
"amIAddonManagerStartup",
|
||||
],
|
||||
spellCheck: ["@mozilla.org/spellchecker/engine;1", "mozISpellCheckingEngine"],
|
||||
});
|
||||
aomStartup: {
|
||||
service: "@mozilla.org/addons/addon-manager-startup;1",
|
||||
iid: Ci.amIAddonManagerStartup,
|
||||
},
|
||||
spellCheck: {
|
||||
service: "@mozilla.org/spellchecker/engine;1",
|
||||
iid: Ci.mozISpellCheckingEngine,
|
||||
},
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"processCount",
|
||||
"dom.ipc.processCount.extension"
|
||||
);
|
||||
processCount: { pref: "dom.ipc.processCount.extension", default: 1 },
|
||||
|
||||
// Temporary pref to be turned on when ready.
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"userContextIsolation",
|
||||
"extensions.userContextIsolation.enabled",
|
||||
false
|
||||
);
|
||||
userContextIsolation: {
|
||||
pref: "extensions.userContextIsolation.enabled",
|
||||
default: false,
|
||||
},
|
||||
userContextIsolationDefaultRestricted: {
|
||||
pref: "extensions.userContextIsolation.defaults.restricted",
|
||||
default: "[]",
|
||||
},
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"userContextIsolationDefaultRestricted",
|
||||
"extensions.userContextIsolation.defaults.restricted",
|
||||
"[]"
|
||||
);
|
||||
dnrEnabled: { pref: "extensions.dnr.enabled", default: true },
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"dnrEnabled",
|
||||
"extensions.dnr.enabled",
|
||||
true
|
||||
);
|
||||
// All functionality is gated by the "userScripts" permission, and forgetting
|
||||
// about its existence is enough to hide all userScripts functionality.
|
||||
// MV3 userScripts API in development (bug 1875475), off by default.
|
||||
// Not to be confused with MV2 and extensions.webextensions.userScripts.enabled!
|
||||
userScriptsMV3Enabled: {
|
||||
pref: "extensions.userScripts.mv3.enabled",
|
||||
default: false,
|
||||
},
|
||||
|
||||
// All functionality is gated by the "userScripts" permission, and forgetting
|
||||
// about its existence is enough to hide all userScripts functionality.
|
||||
// MV3 userScripts API in development (bug 1875475), off by default.
|
||||
// Not to be confused with MV2 and extensions.webextensions.userScripts.enabled!
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"userScriptsMV3Enabled",
|
||||
"extensions.userScripts.mv3.enabled",
|
||||
false
|
||||
);
|
||||
// This pref modifies behavior for MV2. MV3 is enabled regardless.
|
||||
eventPagesEnabled: { pref: "extensions.eventPages.enabled", default: true },
|
||||
|
||||
// This pref modifies behavior for MV2. MV3 is enabled regardless.
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"eventPagesEnabled",
|
||||
"extensions.eventPages.enabled"
|
||||
);
|
||||
// This pref is used to check if storage.sync is still the Kinto-based backend
|
||||
// (GeckoView should be the only one still using it).
|
||||
storageSyncOldKintoBackend: {
|
||||
pref: "webextensions.storage.sync.kinto",
|
||||
default: true,
|
||||
},
|
||||
|
||||
// This pref is used to check if storage.sync is still the Kinto-based backend
|
||||
// (GeckoView should be the only one still using it).
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"storageSyncOldKintoBackend",
|
||||
"webextensions.storage.sync.kinto",
|
||||
false
|
||||
);
|
||||
// Deprecation of browser_style, through .supported & .same_as_mv2 prefs:
|
||||
// - true true = warn only: deprecation message only (no behavioral changes).
|
||||
// - true false = deprecate: default to false, even if default was true in MV2.
|
||||
// - false = remove: always use false, even when true is specified.
|
||||
// (if .same_as_mv2 is set, also warn if the default changed)
|
||||
// Deprecation plan: https://bugzilla.mozilla.org/show_bug.cgi?id=1827910#c1
|
||||
browserStyleMV3supported: {
|
||||
pref: "extensions.browser_style_mv3.supported",
|
||||
default: false,
|
||||
},
|
||||
browserStyleMV3sameAsMV2: {
|
||||
pref: "extensions.browser_style_mv3.same_as_mv2",
|
||||
default: false,
|
||||
},
|
||||
|
||||
// Deprecation of browser_style, through .supported & .same_as_mv2 prefs:
|
||||
// - true true = warn only: deprecation message only (no behavioral changes).
|
||||
// - true false = deprecate: default to false, even if default was true in MV2.
|
||||
// - false = remove: always use false, even when true is specified.
|
||||
// (if .same_as_mv2 is set, also warn if the default changed)
|
||||
// Deprecation plan: https://bugzilla.mozilla.org/show_bug.cgi?id=1827910#c1
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"browserStyleMV3supported",
|
||||
"extensions.browser_style_mv3.supported",
|
||||
false
|
||||
);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"browserStyleMV3sameAsMV2",
|
||||
"extensions.browser_style_mv3.same_as_mv2",
|
||||
false
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"processCrashThreshold",
|
||||
"extensions.webextensions.crash.threshold",
|
||||
// The default number of times an extension process is allowed to crash
|
||||
// within a timeframe.
|
||||
5
|
||||
);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"processCrashTimeframe",
|
||||
"extensions.webextensions.crash.timeframe",
|
||||
processCrashThreshold: {
|
||||
pref: "extensions.webextensions.crash.threshold",
|
||||
default: 5,
|
||||
},
|
||||
// The default timeframe used to count crashes, in milliseconds.
|
||||
30 * 1000
|
||||
);
|
||||
processCrashTimeframe: {
|
||||
pref: "extensions.webextensions.crash.timeframe",
|
||||
default: 30 * 1000,
|
||||
},
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"installIncludesOrigins",
|
||||
"extensions.originControls.grantByDefault",
|
||||
false
|
||||
);
|
||||
installIncludesOrigins: {
|
||||
pref: "extensions.originControls.grantByDefault",
|
||||
default: false,
|
||||
},
|
||||
|
||||
LocaleData: () => ExtensionCommon.LocaleData,
|
||||
|
||||
async NO_PROMPT_PERMISSIONS() {
|
||||
// Wait until all extension API schemas have been loaded and parsed.
|
||||
await Management.lazyInit();
|
||||
return new Set(
|
||||
lazy.Schemas.getPermissionNames([
|
||||
"PermissionNoPrompt",
|
||||
"OptionalPermissionNoPrompt",
|
||||
"PermissionPrivileged",
|
||||
])
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
var {
|
||||
GlobalManager,
|
||||
@@ -192,27 +171,7 @@ var {
|
||||
export { Management };
|
||||
|
||||
const { getUniqueId, promiseTimeout } = ExtensionUtils;
|
||||
|
||||
const { EventEmitter, redefineGetter, updateAllowedOrigins } = ExtensionCommon;
|
||||
|
||||
ChromeUtils.defineLazyGetter(
|
||||
lazy,
|
||||
"LocaleData",
|
||||
() => ExtensionCommon.LocaleData
|
||||
);
|
||||
|
||||
ChromeUtils.defineLazyGetter(lazy, "NO_PROMPT_PERMISSIONS", async () => {
|
||||
// Wait until all extension API schemas have been loaded and parsed.
|
||||
await Management.lazyInit();
|
||||
return new Set(
|
||||
lazy.Schemas.getPermissionNames([
|
||||
"PermissionNoPrompt",
|
||||
"OptionalPermissionNoPrompt",
|
||||
"PermissionPrivileged",
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
const { sharedData } = Services.ppmm;
|
||||
|
||||
const PRIVATE_ALLOWED_PERMISSION = "internal:privateBrowsingAllowed";
|
||||
|
||||
@@ -69,6 +69,22 @@ declare global {
|
||||
// https://github.com/microsoft/TypeScript/issues/56634
|
||||
function ConduitGen<const Send>(_, init: Init<Send>, _actor?): Conduit<Send>;
|
||||
type Items<A> = A extends ReadonlyArray<infer U extends string> ? U : never;
|
||||
|
||||
type LazyDefinition = Record<string,
|
||||
string |
|
||||
(() => any) |
|
||||
{ service: string, iid: nsIID } |
|
||||
{ pref: string, default?, onUpdate?, transform? }
|
||||
>;
|
||||
|
||||
type DeclaredLazy<T> = {
|
||||
[P in keyof T]:
|
||||
T[P] extends (() => infer U) ? U :
|
||||
T[P] extends keyof LazyModules ? Exports<T[P], P> :
|
||||
T[P] extends { pref: string, default?: infer U } ? Widen<U> :
|
||||
T[P] extends { service: string, iid?: infer U } ? nsQIResult<U> :
|
||||
never;
|
||||
}
|
||||
}
|
||||
|
||||
import { PointConduit, ProcessConduitsChild } from "ConduitsChild.sys.mjs";
|
||||
@@ -76,3 +92,37 @@ import { ConduitAddress } from "ConduitsParent.sys.mjs";
|
||||
|
||||
type Conduit<Send> = PointConduit & { [s in `send${Items<Send>}`]: callback };
|
||||
type Init<Send> = ConduitAddress & { send: Send; };
|
||||
|
||||
type IfKey<T, K> = K extends keyof T ? T[K] : never;
|
||||
type Exports<M, P> = M extends keyof LazyModules ? IfKey<LazyModules[M], P> : never;
|
||||
|
||||
type Widen<T> =
|
||||
T extends boolean ? boolean :
|
||||
T extends number ? number :
|
||||
T extends string ? string :
|
||||
never;
|
||||
|
||||
type LazyModules = {
|
||||
"resource://gre/modules/AddonManager.sys.mjs": typeof import("resource://gre/modules/AddonManager.sys.mjs"),
|
||||
"resource://gre/modules/addons/AddonSettings.sys.mjs": typeof import("resource://gre/modules/addons/AddonSettings.sys.mjs"),
|
||||
"resource://gre/modules/addons/siteperms-addon-utils.sys.mjs": typeof import("resource://gre/modules/addons/siteperms-addon-utils.sys.mjs"),
|
||||
"resource://gre/modules/AsyncShutdown.sys.mjs": typeof import("resource://gre/modules/AsyncShutdown.sys.mjs"),
|
||||
"resource://gre/modules/E10SUtils.sys.mjs": typeof import("resource://gre/modules/E10SUtils.sys.mjs"),
|
||||
"resource://gre/modules/ExtensionDNR.sys.mjs": typeof import("resource://gre/modules/ExtensionDNR.sys.mjs"),
|
||||
"resource://gre/modules/ExtensionDNRStore.sys.mjs": typeof import("resource://gre/modules/ExtensionDNRStore.sys.mjs"),
|
||||
"resource://gre/modules/ExtensionMenus.sys.mjs": typeof import("resource://gre/modules/ExtensionMenus.sys.mjs"),
|
||||
"resource://gre/modules/ExtensionPermissionMessages.sys.mjs": typeof import("resource://gre/modules/ExtensionPermissionMessages.sys.mjs"),
|
||||
"resource://gre/modules/ExtensionPermissions.sys.mjs": typeof import("resource://gre/modules/ExtensionPermissions.sys.mjs"),
|
||||
"resource://gre/modules/ExtensionPreferencesManager.sys.mjs": typeof import("resource://gre/modules/ExtensionPreferencesManager.sys.mjs"),
|
||||
"resource://gre/modules/ExtensionProcessScript.sys.mjs": typeof import("resource://gre/modules/ExtensionProcessScript.sys.mjs"),
|
||||
"resource://gre/modules/ExtensionScriptingStore.sys.mjs": typeof import("resource://gre/modules/ExtensionScriptingStore.sys.mjs"),
|
||||
"resource://gre/modules/ExtensionStorage.sys.mjs": typeof import("resource://gre/modules/ExtensionStorage.sys.mjs"),
|
||||
"resource://gre/modules/ExtensionStorageIDB.sys.mjs": typeof import("resource://gre/modules/ExtensionStorageIDB.sys.mjs"),
|
||||
"resource://gre/modules/ExtensionStorageSync.sys.mjs": typeof import("resource://gre/modules/ExtensionStorageSync.sys.mjs"),
|
||||
"resource://gre/modules/ExtensionTelemetry.sys.mjs": typeof import("resource://gre/modules/ExtensionTelemetry.sys.mjs"),
|
||||
"resource://gre/modules/ExtensionUserScripts.sys.mjs": typeof import("resource://gre/modules/ExtensionUserScripts.sys.mjs"),
|
||||
"resource://gre/modules/LightweightThemeManager.sys.mjs": typeof import("resource://gre/modules/LightweightThemeManager.sys.mjs"),
|
||||
"resource://gre/modules/NetUtil.sys.mjs": typeof import("resource://gre/modules/NetUtil.sys.mjs"),
|
||||
"resource://gre/modules/Schemas.sys.mjs": typeof import("resource://gre/modules/Schemas.sys.mjs"),
|
||||
"resource://gre/modules/ServiceWorkerCleanUp.sys.mjs": typeof import("resource://gre/modules/ServiceWorkerCleanUp.sys.mjs"),
|
||||
};
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
|
||||
// Exports for all modules redirected here by a catch-all rule in tsconfig.json.
|
||||
export var
|
||||
AddonWrapper, GeckoViewConnection, GeckoViewWebExtension,
|
||||
IndexedDB, JSONFile, Log, UrlbarUtils, WebExtensionDescriptorActor;
|
||||
AddonManager, AddonManagerPrivate, AddonSettings, AddonWrapper, AsyncShutdown,
|
||||
ExtensionMenus, ExtensionProcessScript, ExtensionScriptingStore, ExtensionUserScripts,
|
||||
NetUtil, E10SUtils, LightweightThemeManager, ServiceWorkerCleanUp, GeckoViewConnection,
|
||||
GeckoViewWebExtension, IndexedDB, JSONFile, Log, UrlbarUtils, WebExtensionDescriptorActor;
|
||||
|
||||
/**
|
||||
* A stub type for the "class" from EventEmitter.sys.mjs.
|
||||
|
||||
Reference in New Issue
Block a user