diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:34:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:34:50 +0000 |
commit | def92d1b8e9d373e2f6f27c366d578d97d8960c6 (patch) | |
tree | 2ef34b9ad8bb9a9220e05d60352558b15f513894 /toolkit/components/extensions | |
parent | Adding debian version 125.0.3-1. (diff) | |
download | firefox-def92d1b8e9d373e2f6f27c366d578d97d8960c6.tar.xz firefox-def92d1b8e9d373e2f6f27c366d578d97d8960c6.zip |
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/extensions')
90 files changed, 2652 insertions, 588 deletions
diff --git a/toolkit/components/extensions/.eslintrc.js b/toolkit/components/extensions/.eslintrc.js index 8cc2d2a16f..2009e44ec9 100644 --- a/toolkit/components/extensions/.eslintrc.js +++ b/toolkit/components/extensions/.eslintrc.js @@ -50,7 +50,7 @@ module.exports = { "no-unused-vars": [ "error", { - args: "none", + argsIgnorePattern: "^_", vars: "all", varsIgnorePattern: "^console$", }, @@ -223,7 +223,7 @@ module.exports = { "no-unused-vars": [ "error", { - args: "none", + argsIgnorePattern: "^_", vars: "local", }, ], diff --git a/toolkit/components/extensions/ConduitsChild.sys.mjs b/toolkit/components/extensions/ConduitsChild.sys.mjs index 598804f74a..d69a57cc19 100644 --- a/toolkit/components/extensions/ConduitsChild.sys.mjs +++ b/toolkit/components/extensions/ConduitsChild.sys.mjs @@ -12,6 +12,9 @@ * @property {ConduitID} [sender] * @property {boolean} query * @property {object} arg + * + * @typedef {import("ConduitsParent.sys.mjs").ConduitAddress} ConduitAddress + * @typedef {import("ConduitsParent.sys.mjs").ConduitID} ConduitID */ /** @@ -46,15 +49,15 @@ export class BaseConduit { } /** - * Internal, partially @abstract, uses the actor to send the message/query. + * Internal, uses the actor to send the message/query. * * @param {string} method * @param {boolean} query Flag indicating a response is expected. - * @param {JSWindowActor} actor + * @param {JSWindowActorChild|JSWindowActorParent} actor * @param {MessageData} data * @returns {Promise?} */ - _send(method, query, actor, data) { + _doSend(method, query, actor, data) { if (query) { return actor.sendQuery(method, data); } @@ -62,6 +65,16 @@ export class BaseConduit { } /** + * Internal @abstract, used by sendX stubs. + * + * @param {string} _name + * @param {boolean} _query + */ + _send(_name, _query, ..._args) { + throw new Error(`_send not implemented for ${this.constructor.name}`); + } + + /** * Internal, calls the specific recvX method based on the message. * * @param {string} name Message/method name. @@ -89,6 +102,7 @@ export class BaseConduit { * one specific ConduitsChild actor. */ export class PointConduit extends BaseConduit { + /** @type {ConduitGen} */ constructor(subject, address, actor) { super(subject, address); this.actor = actor; @@ -108,7 +122,7 @@ export class PointConduit extends BaseConduit { throw new Error(`send${method} on closed conduit ${this.id}`); } let sender = this.id; - return super._send(method, query, this.actor, { arg, query, sender }); + return super._doSend(method, query, this.actor, { arg, query, sender }); } /** @@ -156,9 +170,7 @@ export class ConduitsChild extends JSWindowActorChild { /** * Public entry point a child-side subject uses to open a conduit. * - * @param {object} subject - * @param {ConduitAddress} address - * @returns {PointConduit} + * @type {ConduitGen} */ openConduit(subject, address) { let conduit = new PointConduit(subject, address, this); @@ -211,6 +223,5 @@ export class ProcessConduitsChild extends JSProcessActorChild { openConduit = ConduitsChild.prototype.openConduit; receiveMessage = ConduitsChild.prototype.receiveMessage; - willDestroy = ConduitsChild.prototype.willDestroy; didDestroy = ConduitsChild.prototype.didDestroy; } diff --git a/toolkit/components/extensions/ConduitsParent.sys.mjs b/toolkit/components/extensions/ConduitsParent.sys.mjs index d90bc4afd7..afd81cafc2 100644 --- a/toolkit/components/extensions/ConduitsParent.sys.mjs +++ b/toolkit/components/extensions/ConduitsParent.sys.mjs @@ -29,6 +29,7 @@ * @property {string} [url] * @property {number} [frameId] * @property {string} [workerScriptURL] + * @property {number} [workerDescriptorId] * @property {string} [extensionId] * @property {string} [envType] * @property {string} [instanceId] @@ -299,7 +300,7 @@ export class BroadcastConduit extends BaseConduit { if (method === "RunListener" && arg.path.startsWith("webRequest.")) { return actor.batch(method, { target, arg, query, sender }); } - return super._send(method, query, actor, { target, arg, query, sender }); + return super._doSend(method, query, actor, { target, arg, query, sender }); } /** @@ -482,6 +483,5 @@ export class ConduitsParent extends JSWindowActorParent { */ export class ProcessConduitsParent extends JSProcessActorParent { receiveMessage = ConduitsParent.prototype.receiveMessage; - willDestroy = ConduitsParent.prototype.willDestroy; didDestroy = ConduitsParent.prototype.didDestroy; } diff --git a/toolkit/components/extensions/Extension.sys.mjs b/toolkit/components/extensions/Extension.sys.mjs index de6d4c8bfd..8ab3c30234 100644 --- a/toolkit/components/extensions/Extension.sys.mjs +++ b/toolkit/components/extensions/Extension.sys.mjs @@ -31,7 +31,9 @@ import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; import { ExtensionCommon } from "resource://gre/modules/ExtensionCommon.sys.mjs"; 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, { @@ -54,7 +56,6 @@ ChromeUtils.defineESModuleGetters(lazy, { ExtensionTelemetry: "resource://gre/modules/ExtensionTelemetry.sys.mjs", LightweightThemeManager: "resource://gre/modules/LightweightThemeManager.sys.mjs", - Log: "resource://gre/modules/Log.sys.mjs", NetUtil: "resource://gre/modules/NetUtil.sys.mjs", SITEPERMS_ADDON_TYPE: "resource://gre/modules/addons/siteperms-addon-utils.sys.mjs", @@ -684,14 +685,13 @@ export var ExtensionProcessCrashObserver = { // `processCrashTimeframe` milliseconds. lastCrashTimestamps: [], + logger: Log.repository.getLogger("addons.process-crash-observer"), + init() { if (!this.initialized) { Services.obs.addObserver(this, "ipc:content-created"); Services.obs.addObserver(this, "process-type-set"); Services.obs.addObserver(this, "ipc:content-shutdown"); - this.logger = lazy.Log.repository.getLogger( - "addons.process-crash-observer" - ); if (this._isAndroid) { Services.obs.addObserver(this, "geckoview-initial-foreground"); Services.obs.addObserver(this, "application-foreground"); @@ -948,7 +948,7 @@ export class ExtensionData { get logger() { let id = this.id || "<unknown>"; - return lazy.Log.repository.getLogger(LOGGER_ID_BASE + id); + return Log.repository.getLogger(LOGGER_ID_BASE + id); } /** @@ -1120,6 +1120,24 @@ export class ExtensionData { return !(this.isPrivileged && this.hasPermission("mozillaAddons")); } + get optionsPageProperties() { + let page = this.manifest.options_ui?.page ?? this.manifest.options_page; + if (!page) { + return null; + } + return { + page, + open_in_tab: this.manifest.options_ui + ? this.manifest.options_ui.open_in_tab ?? false + : true, + // `options_ui.browser_style` is assigned the proper default value + // (true for MV2 and false for MV3 when not explicitly set), + // in `#parseBrowserStyleInManifest` (called when we are loading + // and parse manifest data from the `parseManifest` method). + browser_style: this.manifest.options_ui?.browser_style ?? false, + }; + } + /** * Given an array of host and permissions, generate a structured permissions object * that contains seperate host origins and permissions arrays. @@ -1658,6 +1676,8 @@ export class ExtensionData { ); } + // manifest.options_page opens the extension page in a new tab + // and so we will not need to special handling browser_style. if (manifest.options_ui) { if (manifest.options_ui.open_in_tab) { // browser_style:true has no effect when open_in_tab is true. @@ -2748,10 +2768,10 @@ class DictionaryBootstrapScope extends BootstrapScope { install() {} uninstall() {} - startup(data, reason) { + startup(data) { // eslint-disable-next-line no-use-before-define this.dictionary = new Dictionary(data); - return this.dictionary.startup(BootstrapScope.BOOTSTRAP_REASON_MAP[reason]); + return this.dictionary.startup(); } async shutdown(data, reason) { @@ -2765,10 +2785,10 @@ class LangpackBootstrapScope extends BootstrapScope { uninstall() {} async update() {} - startup(data, reason) { + startup(data) { // eslint-disable-next-line no-use-before-define this.langpack = new Langpack(data); - return this.langpack.startup(BootstrapScope.BOOTSTRAP_REASON_MAP[reason]); + return this.langpack.startup(); } async shutdown(data, reason) { @@ -2782,12 +2802,10 @@ class SitePermissionBootstrapScope extends BootstrapScope { install() {} uninstall() {} - startup(data, reason) { + startup(data) { // eslint-disable-next-line no-use-before-define this.sitepermission = new SitePermission(data); - return this.sitepermission.startup( - BootstrapScope.BOOTSTRAP_REASON_MAP[reason] - ); + return this.sitepermission.startup(); } async shutdown(data, reason) { @@ -2810,6 +2828,15 @@ export class Extension extends ExtensionData { /** @type {Map<string, Map<string, any>>} */ persistentListeners; + /** @type {import("ExtensionShortcuts.sys.mjs").ExtensionShortcuts} */ + shortcuts; + + /** @type {TabManagerBase} */ + tabManager; + + /** @type {(options?: { ignoreDevToolsAttached?: boolean, disableResetIdleForTest?: boolean }) => Promise} */ + terminateBackground; + constructor(addonData, startupReason, updateReason) { super(addonData.resourceURI, addonData.isPrivileged); @@ -3203,9 +3230,11 @@ export class Extension extends ExtensionData { }; } - // Extended serialized data which is only needed in the extensions process, - // and is never deserialized in web content processes. - // Keep in sync with BrowserExtensionContent in ExtensionChild.sys.mjs + /** + * Extended serialized data which is only needed in the extensions process, + * and is never deserialized in web content processes. + * Keep in sync with @see {ExtensionChild}. + */ serializeExtended() { return { backgroundScripts: this.backgroundScripts, @@ -3461,7 +3490,7 @@ export class Extension extends ExtensionData { ignoreQuarantine: this.ignoreQuarantine, temporarilyInstalled: this.temporarilyInstalled, allowedOrigins: new MatchPatternSet([]), - localizeCallback() {}, + localizeCallback: () => "", readyPromise, }); diff --git a/toolkit/components/extensions/ExtensionActions.sys.mjs b/toolkit/components/extensions/ExtensionActions.sys.mjs index 29a286442e..92ea9865e3 100644 --- a/toolkit/components/extensions/ExtensionActions.sys.mjs +++ b/toolkit/components/extensions/ExtensionActions.sys.mjs @@ -79,8 +79,8 @@ class PanelActionBase { /** * Set a global, window specific or tab specific property. * - * @param {XULElement|ChromeWindow|null} target - * A XULElement tab, a ChromeWindow, or null for the global data. + * @param {NativeTab|ChromeWindow|null} target + * A NativeTab tab, a ChromeWindow, or null for the global data. * @param {string} prop * String property to set. Should should be one of "icon", "title", "badgeText", * "popup", "badgeBackgroundColor", "badgeTextColor" or "enabled". @@ -104,8 +104,8 @@ class PanelActionBase { /** * Gets the data associated with a tab, window, or the global one. * - * @param {XULElement|ChromeWindow|null} target - * A XULElement tab, a ChromeWindow, or null for the global data. + * @param {NativeTab|ChromeWindow|null} target + * A NativeTab tab, a ChromeWindow, or null for the global data. * @returns {object} * The icon, title, badge, etc. associated with the target. */ @@ -119,8 +119,8 @@ class PanelActionBase { /** * Retrieve the value of a global, window specific or tab specific property. * - * @param {XULElement|ChromeWindow|null} target - * A XULElement tab, a ChromeWindow, or null for the global data. + * @param {NativeTab|ChromeWindow|null} target + * A NativeTab tab, a ChromeWindow, or null for the global data. * @param {string} prop * Name of property to retrieve. Should should be one of "icon", * "title", "badgeText", "popup", "badgeBackgroundColor" or "enabled". @@ -161,7 +161,7 @@ class PanelActionBase { * * @param {string} eventType * The type of the event, should be "location-change". - * @param {XULElement} tab + * @param {NativeTab} tab * The tab whose location changed, or which has become selected. * @param {boolean} [fromBrowse] * - `true` if navigation occurred in `tab`. @@ -178,7 +178,7 @@ class PanelActionBase { /** * Gets the popup url for a given tab. * - * @param {XULElement} tab + * @param {NativeTab} tab * The tab the popup refers to. * @param {boolean} strict * If errors should be thrown if a URL is not available. @@ -208,7 +208,7 @@ class PanelActionBase { * Will clear any existing activeTab permissions previously granted for any * other tab. * - * @param {XULElement} tab + * @param {NativeTab} tab * The tab that should be granted activeTab permission for. Set to * null to clear previously granted activeTab permission. */ @@ -229,7 +229,7 @@ class PanelActionBase { /** * Triggers this action and sends the appropriate event if needed. * - * @param {XULElement} tab + * @param {NativeTab} tab * The tab on which the action was fired. * @param {object} clickInfo * Extra data passed to the second parameter to the action API's @@ -322,7 +322,7 @@ class PanelActionBase { * If it only changes a parameter for a single window, `target` will be that window. * Otherwise `target` will be null. * - * @param {XULElement|ChromeWindow|null} _target + * @param {NativeTab|ChromeWindow} [_target] * Browser tab or browser chrome window, may be null. */ updateOnChange(_target) {} @@ -332,16 +332,22 @@ class PanelActionBase { * * @param {string} _tabId * Internal id of the tab to get. + * @returns {NativeTab} */ - getTab(_tabId) {} + getTab(_tabId) { + throw new Error("Not implemented."); + } /** * Get window object from windowId * * @param {string} _windowId * Internal id of the window to get. + * @returns {ChromeWindow} */ - getWindow(_windowId) {} + getWindow(_windowId) { + throw new Error("Not implemented."); + } /** * Gets the target object corresponding to the `details` parameter of the various @@ -352,19 +358,19 @@ class PanelActionBase { * @param {number} [_details.tabId] * @param {number} [_details.windowId] * @throws if both `tabId` and `windowId` are specified, or if they are invalid. - * @returns {XULElement|ChromeWindow|null} - * If a `tabId` was specified, the corresponding XULElement tab. + * @returns {NativeTab|ChromeWindow|null} + * If a `tabId` was specified, the corresponding NativeTab tab. * If a `windowId` was specified, the corresponding ChromeWindow. * Otherwise, `null`. */ getTargetFromDetails(_details) { - return null; + throw new Error("Not Implemented"); } /** * Triggers a click event. * - * @param {XULElement} _tab + * @param {NativeTab} _tab * The tab where this event should be fired. * @param {object} _clickInfo * Extra data passed to the second parameter to the action API's @@ -375,7 +381,7 @@ class PanelActionBase { /** * Checks whether this action is shown. * - * @param {XULElement} _tab + * @param {NativeTab} _tab * The tab to be checked * @returns {boolean} */ @@ -442,7 +448,7 @@ export class PageActionBase extends PanelActionBase { // Checks whether the tab action is shown when the specified tab becomes active. // Does pattern matching if necessary, and caches the result as a tab-specific value. - // @param {XULElement} tab + // @param {NativeTab} tab // The tab to be checked // @return boolean isShownForTab(tab) { @@ -571,6 +577,8 @@ export class BrowserActionBase extends PanelActionBase { /** * Determines the text badge color to be used in a tab, window, or globally. * + * @typedef {number[]} ColorArray from schemas/browser_action.json. + * * @param {object} values * The values associated with the tab or window, or global values. * @returns {ColorArray} diff --git a/toolkit/components/extensions/ExtensionChild.sys.mjs b/toolkit/components/extensions/ExtensionChild.sys.mjs index 70774db395..232d5cc659 100644 --- a/toolkit/components/extensions/ExtensionChild.sys.mjs +++ b/toolkit/components/extensions/ExtensionChild.sys.mjs @@ -15,6 +15,7 @@ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; +/** @type {Lazy} */ const lazy = {}; XPCOMUtils.defineLazyServiceGetter( @@ -144,7 +145,7 @@ const StrongPromise = { Services.obs.addObserver(StrongPromise, "extensions-onMessage-witness"); // Simple single-event emitter-like helper, exposes the EventManager api. -class SimpleEventAPI extends EventManager { +export class SimpleEventAPI extends EventManager { constructor(context, name) { let fires = new Set(); let register = fire => { @@ -162,7 +163,7 @@ class SimpleEventAPI extends EventManager { } // runtime.OnMessage event helper, handles custom async/sendResponse logic. -class MessageEvent extends SimpleEventAPI { +export class MessageEvent extends SimpleEventAPI { emit(holder, sender) { if (!this.fires.size || !this.context.active) { return { received: false }; @@ -229,7 +230,7 @@ function holdMessage(name, anonymizedName, data, native = null) { } // Implements the runtime.Port extension API object. -class Port { +export class Port { /** * @param {BaseContext} context The context that owns this port. * @param {number} portId Uniquely identifies this port's channel. @@ -310,7 +311,7 @@ class Port { * Each extension context gets its own Messenger object. It handles the * basics of sendMessage, onMessage, connect and onConnect. */ -class Messenger { +export class Messenger { constructor(context) { this.context = context; this.conduit = context.openConduit(this, { @@ -382,8 +383,11 @@ var ExtensionManager = { extensions: new Map(), }; -// Represents a browser extension in the content process. -class BrowserExtensionContent extends EventEmitter { +/** + * Represents an extension instance in the child process. + * Corresponds to the @see {Extension} instance in the parent. + */ +export class ExtensionChild extends EventEmitter { constructor(policy) { super(); @@ -580,7 +584,7 @@ class BrowserExtensionContent extends EventEmitter { /** * An object that runs an remote implementation of an API. */ -class ProxyAPIImplementation extends SchemaAPIInterface { +export class ProxyAPIImplementation extends SchemaAPIInterface { /** * @param {string} namespace The full path to the namespace that contains the * `name` member. This may contain dots, e.g. "storage.local". @@ -680,7 +684,7 @@ class ProxyAPIImplementation extends SchemaAPIInterface { } } -class ChildLocalAPIImplementation extends LocalAPIImplementation { +export class ChildLocalAPIImplementation extends LocalAPIImplementation { constructor(pathObj, namespace, name, childApiManager) { super(pathObj, name, childApiManager.context); this.childApiManagerId = childApiManager.id; @@ -730,7 +734,7 @@ class ChildLocalAPIImplementation extends LocalAPIImplementation { // JSProcessActor Conduits actors (see ConduitsChild.sys.mjs) to communicate // with the ParentAPIManager singleton in ExtensionParent.sys.mjs. // It handles asynchronous function calls as well as event listeners. -class ChildAPIManager { +export class ChildAPIManager { constructor(context, messageManager, localAPICan, contextData) { this.context = context; this.messageManager = messageManager; @@ -1012,14 +1016,3 @@ class ChildAPIManager { this.permissionsChangedCallbacks.add(callback); } } - -export var ExtensionChild = { - BrowserExtensionContent, - ChildAPIManager, - ChildLocalAPIImplementation, - MessageEvent, - Messenger, - Port, - ProxyAPIImplementation, - SimpleEventAPI, -}; diff --git a/toolkit/components/extensions/ExtensionCommon.sys.mjs b/toolkit/components/extensions/ExtensionCommon.sys.mjs index 512d1444a5..c06cf37a6a 100644 --- a/toolkit/components/extensions/ExtensionCommon.sys.mjs +++ b/toolkit/components/extensions/ExtensionCommon.sys.mjs @@ -14,6 +14,7 @@ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; +/** @type {Lazy} */ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { @@ -234,7 +235,7 @@ class NoCloneSpreadArgs { const LISTENERS = Symbol("listeners"); const ONCE_MAP = Symbol("onceMap"); -class EventEmitter { +export class EventEmitter { constructor() { this[LISTENERS] = new Map(); this[ONCE_MAP] = new WeakMap(); @@ -353,7 +354,7 @@ class EventEmitter { * that inherits from this class, the derived class is instantiated * once for each extension that uses the API. */ -class ExtensionAPI extends EventEmitter { +export class ExtensionAPI extends EventEmitter { constructor(extension) { super(); @@ -388,7 +389,7 @@ class ExtensionAPI extends EventEmitter { /** * @param {string} _id - * @param {Record<string, JSONValue>} _manifest + * @param {object} _manifest */ static onUpdate(_id, _manifest) {} } @@ -468,7 +469,7 @@ class ExtensionAPIPersistent extends ExtensionAPI { * * @abstract */ -class BaseContext { +export class BaseContext { /** @type {boolean} */ isTopContext; /** @type {string} */ @@ -553,10 +554,7 @@ class BaseContext { * Opens a conduit linked to this context, populating related address fields. * Only available in child contexts with an associated contentWindow. * - * @param {object} subject - * @param {ConduitAddress} address - * @returns {import("ConduitsChild.sys.mjs").PointConduit} - * @type {ConduitOpen} + * @type {ConduitGen} */ openConduit(subject, address) { let wgc = this.contentWindow.windowGlobalChild; @@ -614,7 +612,7 @@ class BaseContext { // All child contexts must implement logActivity. This is handled if the child // context subclasses ExtensionBaseContextChild. ProxyContextParent overrides // this with a noop for parent contexts. - logActivity() { + logActivity(_type, _name, _data) { throw new Error(`Not implemented for ${this.envType}`); } @@ -822,7 +820,7 @@ class BaseContext { * exception error. * * @param {Error|object} error - * @param {SavedFrame?} [caller] + * @param {nsIStackFrame?} [caller] * @returns {Error} */ normalizeError(error, caller) { @@ -864,7 +862,7 @@ class BaseContext { * * @param {object} error An object with a `message` property. May * optionally be an `Error` object belonging to the target scope. - * @param {SavedFrame?} caller + * @param {nsIStackFrame?} caller * The optional caller frame which triggered this callback, to be used * in error reporting. * @param {Function} callback The callback to call. @@ -885,7 +883,7 @@ class BaseContext { /** * Captures the most recent stack frame which belongs to the extension. * - * @returns {SavedFrame?} + * @returns {nsIStackFrame?} */ getCaller() { return ChromeUtils.getCallerLocation(this.principal); @@ -1037,7 +1035,7 @@ class BaseContext { * * @interface */ -class SchemaAPIInterface { +export class SchemaAPIInterface { /** * Calls this as a function that returns its return value. * @@ -1483,7 +1481,7 @@ class SchemaAPIManager extends EventEmitter { * "addon" - An addon process. * "content" - A content process. * "devtools" - A devtools process. - * @param {import("Schemas.sys.mjs").SchemaRoot} [schema] + * @param {import("Schemas.sys.mjs").SchemaInject} [schema] */ constructor(processType, schema) { super(); @@ -2023,10 +2021,14 @@ export function LocaleData(data) { this.locales = data.locales || new Map(); this.warnedMissingKeys = new Set(); - // Map(locale-name -> Map(message-key -> localized-string)) - // - // Contains a key for each loaded locale, each of which is a - // Map of message keys to their localized strings. + /** + * Map(locale-name -> Map(message-key -> localized-string)) + * + * Contains a key for each loaded locale, each of which is a + * Map of message keys to their localized strings. + * + * @type {Map<string, Map<string, string>>} + */ this.messages = data.messages || new Map(); if (data.builtinMessages) { diff --git a/toolkit/components/extensions/ExtensionContent.sys.mjs b/toolkit/components/extensions/ExtensionContent.sys.mjs index 015d1bc7c6..a2fce282ee 100644 --- a/toolkit/components/extensions/ExtensionContent.sys.mjs +++ b/toolkit/components/extensions/ExtensionContent.sys.mjs @@ -7,6 +7,7 @@ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; +/** @type {Lazy} */ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { @@ -39,8 +40,10 @@ const ScriptError = Components.Constructor( ); import { + ChildAPIManager, ExtensionChild, ExtensionActivityLogChild, + Messenger, } from "resource://gre/modules/ExtensionChild.sys.mjs"; import { ExtensionCommon } from "resource://gre/modules/ExtensionCommon.sys.mjs"; import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs"; @@ -63,8 +66,6 @@ const { runSafeSyncWithoutClone, } = ExtensionCommon; -const { BrowserExtensionContent, ChildAPIManager, Messenger } = ExtensionChild; - ChromeUtils.defineLazyGetter(lazy, "isContentScriptProcess", () => { return ( Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT || @@ -164,6 +165,7 @@ class ScriptCache extends CacheMap { super( SCRIPT_EXPIRY_TIMEOUT_MS, url => { + /** @type {Promise<PrecompiledScript> & { script?: PrecompiledScript }} */ let promise = ChromeUtils.compileScript(url, options); promise.then(script => { promise.script = script; @@ -282,49 +284,37 @@ class CSSCodeCache extends BaseCSSCache { } } -defineLazyGetter( - BrowserExtensionContent.prototype, - "staticScripts", - function () { - return new ScriptCache({ hasReturnValue: false }, this); - } -); +defineLazyGetter(ExtensionChild.prototype, "staticScripts", function () { + return new ScriptCache({ hasReturnValue: false }, this); +}); -defineLazyGetter( - BrowserExtensionContent.prototype, - "dynamicScripts", - function () { - return new ScriptCache({ hasReturnValue: true }, this); - } -); +defineLazyGetter(ExtensionChild.prototype, "dynamicScripts", function () { + return new ScriptCache({ hasReturnValue: true }, this); +}); -defineLazyGetter(BrowserExtensionContent.prototype, "userCSS", function () { +defineLazyGetter(ExtensionChild.prototype, "userCSS", function () { return new CSSCache(Ci.nsIStyleSheetService.USER_SHEET, this); }); -defineLazyGetter(BrowserExtensionContent.prototype, "authorCSS", function () { +defineLazyGetter(ExtensionChild.prototype, "authorCSS", function () { return new CSSCache(Ci.nsIStyleSheetService.AUTHOR_SHEET, this); }); // These two caches are similar to the above but specialized to cache the cssCode // using an hash computed from the cssCode string as the key (instead of the generated data // URI which can be pretty long for bigger injected cssCode). -defineLazyGetter(BrowserExtensionContent.prototype, "userCSSCode", function () { +defineLazyGetter(ExtensionChild.prototype, "userCSSCode", function () { return new CSSCodeCache(Ci.nsIStyleSheetService.USER_SHEET, this); }); -defineLazyGetter( - BrowserExtensionContent.prototype, - "authorCSSCode", - function () { - return new CSSCodeCache(Ci.nsIStyleSheetService.AUTHOR_SHEET, this); - } -); +defineLazyGetter(ExtensionChild.prototype, "authorCSSCode", function () { + return new CSSCodeCache(Ci.nsIStyleSheetService.AUTHOR_SHEET, this); +}); // Represents a content script. class Script { /** - * @param {BrowserExtensionContent} extension + * @param {ExtensionChild} extension * @param {WebExtensionContentScript|object} matcher * An object with a "matchesWindowGlobal" method and content script * execution details. This is usually a plain WebExtensionContentScript @@ -611,7 +601,7 @@ class Script { * @param {ContentScriptContextChild} context * The document to block the parsing on, if the scripts are not yet precompiled and cached. * - * @returns {Array<PreloadedScript> | Promise<Array<PreloadedScript>>} + * @returns {PrecompiledScript[] | Promise<PrecompiledScript[]>} * Returns an array of preloaded scripts if they are already available, or a promise which * resolves to the array of the preloaded scripts once they are precompiled and cached. */ @@ -667,7 +657,7 @@ class Script { // Represents a user script. class UserScript extends Script { /** - * @param {BrowserExtensionContent} extension + * @param {ExtensionChild} extension * @param {WebExtensionContentScript|object} matcher * An object with a "matchesWindowGlobal" method and content script * execution details. @@ -1014,7 +1004,7 @@ class ContentScriptContextChild extends BaseContext { // Responsible for creating ExtensionContexts and injecting content // scripts into them when new documents are created. DocumentManager = { - // Map[windowId -> Map[ExtensionChild -> ContentScriptContextChild]] + /** @type {Map<number, Map<ExtensionChild, ContentScriptContextChild>>} */ contexts: new Map(), initialized: false, @@ -1115,8 +1105,6 @@ DocumentManager = { }; export var ExtensionContent = { - BrowserExtensionContent, - contentScripts, shutdownExtension(extension) { diff --git a/toolkit/components/extensions/ExtensionDNR.sys.mjs b/toolkit/components/extensions/ExtensionDNR.sys.mjs index d18856a2b8..afc7d30751 100644 --- a/toolkit/components/extensions/ExtensionDNR.sys.mjs +++ b/toolkit/components/extensions/ExtensionDNR.sys.mjs @@ -71,6 +71,7 @@ const gRuleManagers = []; import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; +/** @type {Lazy} */ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { @@ -182,6 +183,8 @@ export class Rule { class Ruleset { /** + * @typedef {number} integer + * * @param {string} rulesetId - extension-defined ruleset ID. * @param {integer} rulesetPrecedence * @param {Rule[]} rules - extension-defined rules @@ -1316,7 +1319,7 @@ class RequestDetails { * @param {string} options.type - ResourceType (MozContentPolicyType). * @param {string} [options.method] - HTTP method * @param {integer} [options.tabId] - * @param {BrowsingContext} [options.browsingContext] - The BrowsingContext + * @param {CanonicalBrowsingContext} [options.browsingContext] - The CBC * associated with the request. Typically the bc for which the subresource * request is initiated, if any. For document requests, this is the parent * (i.e. the parent frame for sub_frame, null for main_frame). @@ -1975,7 +1978,10 @@ const NetworkIntegration = { /** * Applies the actions of the DNR rules. * - * @param {ChannelWrapper} channel + * @typedef {ChannelWrapper & { _dnrMatchedRules?: MatchedRule[] }} + * ChannelWrapperViaDNR + * + * @param {ChannelWrapperViaDNR} channel * @returns {boolean} Whether to ignore any responses from the webRequest API. */ onBeforeRequest(channel) { @@ -1993,7 +1999,7 @@ const NetworkIntegration = { this.applyRedirect(channel, finalMatch); return true; case "upgradeScheme": - this.applyUpgradeScheme(channel, finalMatch); + this.applyUpgradeScheme(channel); return true; } // If there are multiple rules, then it may be a combination of allow, @@ -2294,7 +2300,7 @@ function beforeWebRequestEvent(channel, kind) { /** * Applies matching DNR rules, some of which may potentially cancel the request. * - * @param {ChannelWrapper} channel + * @param {ChannelWrapperViaDNR} channel * @param {string} kind - The name of the webRequest event. * @returns {boolean} Whether to ignore any responses from the webRequest API. */ diff --git a/toolkit/components/extensions/ExtensionPageChild.sys.mjs b/toolkit/components/extensions/ExtensionPageChild.sys.mjs index 17c208572b..f0ac5ed229 100644 --- a/toolkit/components/extensions/ExtensionPageChild.sys.mjs +++ b/toolkit/components/extensions/ExtensionPageChild.sys.mjs @@ -22,8 +22,9 @@ const CATEGORY_EXTENSION_SCRIPTS_DEVTOOLS = "webextension-scripts-devtools"; import { ExtensionCommon } from "resource://gre/modules/ExtensionCommon.sys.mjs"; import { - ExtensionChild, + ChildAPIManager, ExtensionActivityLogChild, + Messenger, } from "resource://gre/modules/ExtensionChild.sys.mjs"; import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs"; @@ -32,8 +33,6 @@ const { getInnerWindowID, promiseEvent } = ExtensionUtils; const { BaseContext, CanOfAPIs, SchemaAPIManager, redefineGetter } = ExtensionCommon; -const { ChildAPIManager, Messenger } = ExtensionChild; - const initializeBackgroundPage = context => { // Override the `alert()` method inside background windows; // we alias it to console.log(). @@ -188,7 +187,7 @@ export class ExtensionBaseContextChild extends BaseContext { * This ExtensionBaseContextChild represents an addon execution environment * that is running in an addon or devtools child process. * - * @param {BrowserExtensionContent} extension This context's owner. + * @param {ExtensionChild} extension This context's owner. * @param {object} params * @param {string} params.envType One of "addon_child" or "devtools_child". * @param {nsIDOMWindow} params.contentWindow The window where the addon runs. @@ -301,7 +300,7 @@ class ExtensionPageContextChild extends ExtensionBaseContextChild { * This is the child side of the ExtensionPageContextParent class * defined in ExtensionParent.sys.mjs. * - * @param {BrowserExtensionContent} extension This context's owner. + * @param {ExtensionChild} extension This context's owner. * @param {object} params * @param {nsIDOMWindow} params.contentWindow The window where the addon runs. * @param {string} params.viewType One of "background", "popup", "sidebar" or "tab". @@ -340,7 +339,7 @@ export class DevToolsContextChild extends ExtensionBaseContextChild { * environment that has access to the devtools API namespace and to the same subset * of APIs available in a content script execution environment. * - * @param {BrowserExtensionContent} extension This context's owner. + * @param {ExtensionChild} extension This context's owner. * @param {object} params * @param {nsIDOMWindow} params.contentWindow The window where the addon runs. * @param {string} params.viewType One of "devtools_page" or "devtools_panel". @@ -424,7 +423,7 @@ export var ExtensionPageChild = { /** * Create a privileged context at initial-document-element-inserted. * - * @param {BrowserExtensionContent} extension + * @param {ExtensionChild} extension * The extension for which the context should be created. * @param {nsIDOMWindow} contentWindow The global of the page. */ diff --git a/toolkit/components/extensions/ExtensionParent.sys.mjs b/toolkit/components/extensions/ExtensionParent.sys.mjs index b4812a702a..f951433713 100644 --- a/toolkit/components/extensions/ExtensionParent.sys.mjs +++ b/toolkit/components/extensions/ExtensionParent.sys.mjs @@ -14,6 +14,7 @@ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; +/** @type {Lazy} */ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { @@ -245,8 +246,10 @@ let apiManager = new (class extends SchemaAPIManager { // to relevant child messengers. Also handles Native messaging and GeckoView. /** @typedef {typeof ProxyMessenger} NativeMessenger */ const ProxyMessenger = { - /** @type {Map<number, Partial<ParentPort>&Promise<ParentPort>>} */ + /** @type {Map<number, ParentPort>} */ ports: new Map(), + /** @type {Map<number, Promise>} */ + portPromises: new Map(), init() { this.conduit = new lazy.BroadcastConduit(ProxyMessenger, { @@ -300,7 +303,8 @@ const ProxyMessenger = { }; if (JSWindowActorParent.isInstance(source.actor)) { - let browser = source.actor.browsingContext.top.embedderElement; + let { currentWindowContext, top } = source.actor.browsingContext; + let browser = top.embedderElement; let data = browser && apiManager.global.tabTracker.getBrowserData(browser); if (data?.tabId > 0) { @@ -308,6 +312,13 @@ const ProxyMessenger = { // frameId is documented to only be set if sender.tab is set. sender.frameId = source.frameId; } + + let principal = currentWindowContext.documentPrincipal; + // We intend the serialization of null principals *and* file scheme to be + // "null". + sender.origin = new URL(principal.originNoSuffix).origin; + } else if (source.verified) { + sender.origin = `moz-extension://${extension.uuid}`; } return sender; @@ -363,17 +374,24 @@ const ProxyMessenger = { } // PortMessages that follow will need to wait for the port to be opened. - /** @type {callback} */ - let resolvePort; - this.ports.set(arg.portId, new Promise(res => (resolvePort = res))); + let { promise, resolve, reject } = Promise.withResolvers(); + this.portPromises.set(arg.portId, promise); - let kind = await this.normalizeArgs(arg, sender); - let all = await this.conduit.castPortConnect(kind, arg); - resolvePort(); + try { + let kind = await this.normalizeArgs(arg, sender); + let all = await this.conduit.castPortConnect(kind, arg); + resolve(); - // If there are no active onConnect listeners. - if (!all.some(x => x.value)) { - throw new ExtensionError(ERROR_NO_RECEIVERS); + // If there are no active onConnect listeners. + if (!all.some(x => x.value)) { + throw new ExtensionError(ERROR_NO_RECEIVERS); + } + } catch (err) { + // Throw _and_ reject with error, so everything awaiting this port fails. + reject(err); + throw err; + } finally { + this.portPromises.delete(arg.portId); } }, @@ -387,7 +405,7 @@ const ProxyMessenger = { // NOTE: the following await make sure we await for promised ports // (ports that were not yet open when added to the Map, // see recvPortConnect). - await this.ports.get(sender.portId); + await this.portPromises.get(sender.portId); this.sendPortMessage(sender.portId, holder, !sender.source); }, @@ -448,7 +466,7 @@ GlobalManager = { extensionMap: new Map(), initialized: false, - /** @type {WeakMap<Browser, object>} Extension Context init data. */ + /** @type {WeakMap<XULBrowserElement, object>} Extension Context init data. */ frameData: new WeakMap(), init(extension) { @@ -961,7 +979,6 @@ ParentAPIManager = { throw new Error(`Bad sender context envType: ${sender.envType}`); } - let isBackgroundWorker = false; if (JSWindowActorParent.isInstance(actor)) { const target = actor.browsingContext.top.embedderElement; let processMessageManager = @@ -979,6 +996,22 @@ ParentAPIManager = { "Attempt to create privileged extension parent from incorrect child process" ); } + + if (envType == "addon_parent") { + context = new ExtensionPageContextParent( + envType, + extension, + data, + actor.browsingContext + ); + } else if (envType == "devtools_parent") { + context = new DevToolsExtensionPageContextParent( + envType, + extension, + data, + actor.browsingContext + ); + } } else if (JSProcessActorParent.isInstance(actor)) { if (actor.manager.remoteType !== extension.remoteType) { throw new Error( @@ -996,7 +1029,7 @@ ParentAPIManager = { `Unexpected viewType ${data.viewType} on an extension process actor` ); } - isBackgroundWorker = true; + context = new BackgroundWorkerContextParent(envType, extension, data); } else { // Unreacheable: JSWindowActorParent and JSProcessActorParent are the // only actors. @@ -1004,24 +1037,6 @@ ParentAPIManager = { "Attempt to create privileged extension parent via incorrect actor" ); } - - if (isBackgroundWorker) { - context = new BackgroundWorkerContextParent(envType, extension, data); - } else if (envType == "addon_parent") { - context = new ExtensionPageContextParent( - envType, - extension, - data, - actor.browsingContext - ); - } else if (envType == "devtools_parent") { - context = new DevToolsExtensionPageContextParent( - envType, - extension, - data, - actor.browsingContext - ); - } } else if (envType == "content_parent") { // Note: actor is always a JSWindowActorParent, with a browsingContext. context = new ContentScriptContextParent( @@ -1340,11 +1355,9 @@ class HiddenXULWindow { // The windowless browser is a thin wrapper around a docShell that keeps // its related resources alive. It implements nsIWebNavigation and - // forwards its methods to the underlying docShell. That .docShell - // needs `QueryInterface(nsIWebNavigation)` to give us access to the - // webNav methods that are already available on the windowless browser. + // forwards its methods to the underlying docShell. let chromeShell = windowlessBrowser.docShell; - chromeShell.QueryInterface(Ci.nsIWebNavigation); + let webNav = chromeShell.QueryInterface(Ci.nsIWebNavigation); if (lazy.PrivateBrowsingUtils.permanentPrivateBrowsing) { let attrs = chromeShell.getOriginAttributes(); @@ -1353,13 +1366,13 @@ class HiddenXULWindow { } windowlessBrowser.browsingContext.useGlobalHistory = false; - chromeShell.loadURI(DUMMY_PAGE_URI, { + webNav.loadURI(DUMMY_PAGE_URI, { triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), }); await promiseObserved( "chrome-document-global-created", - win => win.document == chromeShell.document + win => win.document == webNav.document ); await promiseDocumentLoaded(windowlessBrowser.document); if (this.unloaded) { @@ -1376,7 +1389,7 @@ class HiddenXULWindow { * An object that contains the xul attributes to set of the newly * created browser XUL element. * - * @returns {Promise<XULElement>} + * @returns {Promise<XULBrowserElement>} * A Promise which resolves to the newly created browser XUL element. */ async createBrowserElement(xulAttributes) { @@ -1458,16 +1471,17 @@ const SharedWindow = { * to inherits the shared boilerplate code needed to create a parent document for the hidden * extension pages (e.g. the background page, the devtools page) in the BackgroundPage and * DevToolsPage classes. - * - * @param {Extension} extension - * The Extension which owns the hidden extension page created (used to decide - * if the hidden extension page parent doc is going to be a windowlessBrowser or - * a visible XUL window). - * @param {string} viewType - * The viewType of the WebExtension page that is going to be loaded - * in the created browser element (e.g. "background" or "devtools_page"). */ class HiddenExtensionPage { + /** + * @param {Extension} extension + * The Extension which owns the hidden extension page created (used to decide + * if the hidden extension page parent doc is going to be a windowlessBrowser or + * a visible XUL window). + * @param {string} viewType + * The viewType of the WebExtension page that is going to be loaded + * in the created browser element (e.g. "background" or "devtools_page"). + */ constructor(extension, viewType) { if (!extension || !viewType) { throw new Error("extension and viewType parameters are mandatory"); @@ -1535,6 +1549,9 @@ class HiddenExtensionPage { } } +/** @typedef {import("resource://devtools/server/actors/descriptors/webextension.js") + .WebExtensionDescriptorActor} WebExtensionDescriptorActor */ + /** * This object provides utility functions needed by the devtools actors to * be able to connect and debug an extension (which can run in the main or in @@ -1545,9 +1562,9 @@ const DebugUtils = { // which are used to connect the webextension patent actor to the extension process. hiddenXULWindow: null, - // Map<extensionId, Promise<XULElement>> + /** @type {Map<string, Promise<XULBrowserElement> & { browser: XULBrowserElement }>} */ debugBrowserPromises: new Map(), - // DefaultWeakMap<Promise<browser XULElement>, Set<WebExtensionParentActor>> + /** @type {WeakMap<Promise<XULBrowserElement>, Set<WebExtensionDescriptorActor>>} */ debugActors: new DefaultWeakMap(() => new Set()), _extensionUpdatedWatcher: null, @@ -1696,10 +1713,10 @@ const DebugUtils = { * Retrieve a XUL browser element which has been configured to be able to connect * the devtools actor with the process where the extension is running. * - * @param {WebExtensionParentActor} webExtensionParentActor + * @param {WebExtensionDescriptorActor} webExtensionParentActor * The devtools actor that is retrieving the browser element. * - * @returns {Promise<XULElement>} + * @returns {Promise<XULBrowserElement>} * A promise which resolves to the configured browser XUL element. */ async getExtensionProcessBrowser(webExtensionParentActor) { @@ -1753,7 +1770,7 @@ const DebugUtils = { * it destroys the XUL browser element, and it also destroy the hidden XUL window * if it is not currently needed. * - * @param {WebExtensionParentActor} webExtensionParentActor + * @param {WebExtensionDescriptorActor} webExtensionParentActor * The devtools actor that has retrieved an addon debug browser element. */ async releaseExtensionProcessBrowser(webExtensionParentActor) { @@ -1783,7 +1800,7 @@ const DebugUtils = { * was received by the message manager. The promise is rejected if the message * manager was closed before a message was received. * - * @param {nsIMessageListenerManager} messageManager + * @param {MessageListenerManager} messageManager * The message manager on which to listen for messages. * @param {string} messageName * The message to listen for. diff --git a/toolkit/components/extensions/ExtensionPermissions.sys.mjs b/toolkit/components/extensions/ExtensionPermissions.sys.mjs index 1ee9afdc32..964503d8f6 100644 --- a/toolkit/components/extensions/ExtensionPermissions.sys.mjs +++ b/toolkit/components/extensions/ExtensionPermissions.sys.mjs @@ -8,11 +8,13 @@ import { computeSha1HashAsString } from "resource://gre/modules/addons/crypto-ut import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; +/** @type {Lazy} */ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { AddonManager: "resource://gre/modules/AddonManager.sys.mjs", AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs", + Extension: "resource://gre/modules/Extension.sys.mjs", ExtensionParent: "resource://gre/modules/ExtensionParent.sys.mjs", FileUtils: "resource://gre/modules/FileUtils.sys.mjs", JSONFile: "resource://gre/modules/JSONFile.sys.mjs", @@ -347,6 +349,50 @@ export var ExtensionPermissions = { return this._getCached(extensionId); }, + /** + * Validate and normalize passed in `perms`, including a fixup to + * include all possible "all sites" permissions when appropriate. + * + * @throws if an origin or permission is not part of optional permissions. + * + * @typedef {object} Perms + * @property {string[]} origins + * @property {string[]} permissions + * + * @param {Perms} perms api permissions and origins to be added/removed. + * @param {Perms} optional permissions and origins from the manifest. + * @returns {Perms} normalized + */ + normalizeOptional(perms, optional) { + let allSites = false; + let patterns = new MatchPatternSet(optional.origins, { ignorePath: true }); + let normalized = Object.assign({}, perms); + + for (let o of perms.origins) { + if (!patterns.subsumes(new MatchPattern(o))) { + throw new Error(`${o} was not declared in the manifest`); + } + // If this is one of the "all sites" permissions + allSites ||= lazy.Extension.isAllSitesPermission(o); + } + + if (allSites) { + // Grant/revoke ALL "all sites" optional permissions from the manifest. + let origins = perms.origins.concat( + optional.origins.filter(o => lazy.Extension.isAllSitesPermission(o)) + ); + normalized.origins = Array.from(new Set(origins)); + } + + for (let p of perms.permissions) { + if (!optional.permissions.includes(p)) { + throw new Error(`${p} was not declared in optional_permissions`); + } + } + + return normalized; + }, + _fixupAllUrlsPerms(perms) { // Unfortunately, we treat <all_urls> as an API permission as well. // If it is added to either, ensure it is added to both. @@ -361,8 +407,10 @@ export var ExtensionPermissions = { * Add new permissions for the given extension. `permissions` is * in the format that is passed to browser.permissions.request(). * + * @typedef {import("ExtensionCommon.sys.mjs").EventEmitter} EventEmitter + * * @param {string} extensionId The extension id - * @param {object} perms Object with permissions and origins array. + * @param {Perms} perms Object with permissions and origins array. * @param {EventEmitter} [emitter] optional object implementing emitter interfaces */ async add(extensionId, perms, emitter) { @@ -401,7 +449,7 @@ export var ExtensionPermissions = { * in the format that is passed to browser.permissions.request(). * * @param {string} extensionId The extension id - * @param {object} perms Object with permissions and origins array. + * @param {Perms} perms Object with permissions and origins array. * @param {EventEmitter} [emitter] optional object implementing emitter interfaces */ async remove(extensionId, perms, emitter) { diff --git a/toolkit/components/extensions/ExtensionProcessScript.sys.mjs b/toolkit/components/extensions/ExtensionProcessScript.sys.mjs index 93746cb0ca..ef5694be9f 100644 --- a/toolkit/components/extensions/ExtensionProcessScript.sys.mjs +++ b/toolkit/components/extensions/ExtensionProcessScript.sys.mjs @@ -11,6 +11,7 @@ import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; +/** @type {Lazy} */ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { @@ -49,7 +50,7 @@ ChromeUtils.defineLazyGetter(lazy, "isContentScriptProcess", () => { }); var extensions = new DefaultWeakMap(policy => { - return new lazy.ExtensionChild.BrowserExtensionContent(policy); + return new lazy.ExtensionChild(policy); }); var pendingExtensions = new Map(); @@ -342,7 +343,7 @@ ExtensionManager = { perms.delete(perm); } } - policy.permissions = perms; + policy.permissions = Array.from(perms); } } diff --git a/toolkit/components/extensions/ExtensionShortcuts.sys.mjs b/toolkit/components/extensions/ExtensionShortcuts.sys.mjs index 7b30fa2cdf..17cff67eb9 100644 --- a/toolkit/components/extensions/ExtensionShortcuts.sys.mjs +++ b/toolkit/components/extensions/ExtensionShortcuts.sys.mjs @@ -6,6 +6,7 @@ import { ExtensionCommon } from "resource://gre/modules/ExtensionCommon.sys.mjs" import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs"; +/** @type {Lazy} */ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { @@ -462,7 +463,6 @@ export class ExtensionShortcuts { let win = event.target.ownerGlobal; action.triggerAction(win); } else { - this.extension.tabManager.addActiveTabPermission(); this.onCommand(name); } }); diff --git a/toolkit/components/extensions/ExtensionStorage.sys.mjs b/toolkit/components/extensions/ExtensionStorage.sys.mjs index b1b09d137f..5317fb2a91 100644 --- a/toolkit/components/extensions/ExtensionStorage.sys.mjs +++ b/toolkit/components/extensions/ExtensionStorage.sys.mjs @@ -8,6 +8,7 @@ import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs"; const { DefaultWeakMap, ExtensionError } = ExtensionUtils; +/** @type {Lazy} */ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { @@ -89,7 +90,7 @@ function serialize(name, anonymizedName, value) { } export var ExtensionStorage = { - // Map<extension-id, Promise<JSONFile>> + /** @type {Map<string, Promise<typeof lazy.JSONFile>>} */ jsonFilePromises: new Map(), listeners: new Map(), @@ -157,7 +158,7 @@ export var ExtensionStorage = { * * @param {any} value * The value to sanitize. - * @param {Context} context + * @param {BaseContext} context * The extension context in which to sanitize the value * @returns {value} * The sanitized value. diff --git a/toolkit/components/extensions/ExtensionStorageIDB.sys.mjs b/toolkit/components/extensions/ExtensionStorageIDB.sys.mjs index 604d29b4cf..5223d57466 100644 --- a/toolkit/components/extensions/ExtensionStorageIDB.sys.mjs +++ b/toolkit/components/extensions/ExtensionStorageIDB.sys.mjs @@ -5,6 +5,7 @@ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; import { IndexedDB } from "resource://gre/modules/IndexedDB.sys.mjs"; +/** @type {Lazy} */ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { @@ -801,6 +802,8 @@ export var ExtensionStorageIDB = { * from the internal IndexedDB operations have to be converted into an ExtensionError * to be accessible to the extension code). * + * @typedef {import("ExtensionUtils.sys.mjs").ExtensionError} ExtensionError + * * @param {object} params * @param {Error|ExtensionError|DOMException} params.error * The error object to normalize. diff --git a/toolkit/components/extensions/ExtensionStorageSync.sys.mjs b/toolkit/components/extensions/ExtensionStorageSync.sys.mjs index 3f82d91fac..06f0cc4310 100644 --- a/toolkit/components/extensions/ExtensionStorageSync.sys.mjs +++ b/toolkit/components/extensions/ExtensionStorageSync.sys.mjs @@ -10,6 +10,7 @@ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; const NS_ERROR_DOM_QUOTA_EXCEEDED_ERR = 0x80530016; +/** @type {Lazy} */ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { @@ -55,6 +56,7 @@ ExtensionStorageApiCallback.prototype = { }, handleError(code, message) { + /** @type {Error & { code?: number }} */ let e = new Error(message); e.code = code; Cu.reportError(e); diff --git a/toolkit/components/extensions/ExtensionStorageSyncKinto.sys.mjs b/toolkit/components/extensions/ExtensionStorageSyncKinto.sys.mjs index ace6e16c2c..62493e3b07 100644 --- a/toolkit/components/extensions/ExtensionStorageSyncKinto.sys.mjs +++ b/toolkit/components/extensions/ExtensionStorageSyncKinto.sys.mjs @@ -32,6 +32,7 @@ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs"; +/** @type {Lazy} */ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { @@ -120,11 +121,6 @@ function throwIfNoFxA(fxAccounts, action) { } } -// Global ExtensionStorageSyncKinto instance that extensions and Fx Sync use. -// On Android, because there's no FXAccounts instance, any syncing -// operations will fail. -export var extensionStorageSyncKinto = null; - /** * Utility function to enforce an order of fields when computing an HMAC. * @@ -558,6 +554,8 @@ class CryptoCollection { * "characters" are values, each within [0, 255]. You can produce * such a bytestring using e.g. CommonUtils.encodeUTF8. * + * @typedef {string} bytestring + * * The returned value is a base64url-encoded string of the hash. * * @param {bytestring} value The value to be hashed. @@ -696,7 +694,7 @@ let CollectionKeyEncryptionRemoteTransformer = class extends EncryptionRemoteTra * * @param {Extension} extension * The extension whose context just ended. - * @param {Context} context + * @param {BaseContext} context * The context that just ended. */ function cleanUpForContext(extension, context) { @@ -1199,7 +1197,7 @@ export class ExtensionStorageSyncKinto { * @param {Extension} extension * The extension for which we are seeking * a collection. - * @param {Context} context + * @param {BaseContext} context * The context of the extension, so that we can * stop syncing the collection when the extension ends. * @returns {Promise<Collection>} @@ -1370,7 +1368,14 @@ export class ExtensionStorageSyncKinto { } } -extensionStorageSyncKinto = new ExtensionStorageSyncKinto(_fxaService); +/** + * Global ExtensionStorageSyncKinto instance that extensions and Fx Sync use. + * On Android, because there's no FXAccounts instance, any syncing + * operations will fail. + */ +export const extensionStorageSyncKinto = new ExtensionStorageSyncKinto( + _fxaService +); // For test use only. export const KintoStorageTestUtils = { diff --git a/toolkit/components/extensions/ExtensionTelemetry.sys.mjs b/toolkit/components/extensions/ExtensionTelemetry.sys.mjs index 57f052372c..95b71ce007 100644 --- a/toolkit/components/extensions/ExtensionTelemetry.sys.mjs +++ b/toolkit/components/extensions/ExtensionTelemetry.sys.mjs @@ -68,7 +68,7 @@ export function getTrimmedString(str) { * If the resulting string is longer than 80 characters it is going to be * trimmed using the `getTrimmedString` helper function. * - * @param {Error | DOMException | Components.Exception} error + * @param {Error | DOMException | ReturnType<typeof Components.Exception>} error * The error object to convert into a string representation. * * @returns {string} @@ -146,7 +146,7 @@ class ExtensionTelemetryMetric { * @param {string} metric * The Glean timing_distribution metric to record (used to retrieve the Glean metric type from the * GLEAN_METRICS_TYPES map). - * @param {Extension | BrowserExtensionContent} extension + * @param {Extension | ExtensionChild} extension * The extension to record the telemetry for. * @param {any | undefined} [obj = extension] * An optional object the timing_distribution method call should be related to @@ -207,7 +207,7 @@ class ExtensionTelemetryMetric { * The stopwatch method to call ("start", "finish" or "cancel"). * @param {string} metric * The stopwatch metric to record (used to retrieve the base histogram id from the HISTOGRAMS_IDS object). - * @param {Extension | BrowserExtensionContent} extension + * @param {Extension | ExtensionChild} extension * The extension to record the telemetry for. * @param {any | undefined} [obj = extension] * An optional telemetry stopwatch object (which defaults to the extension parameter when missing). @@ -242,7 +242,7 @@ class ExtensionTelemetryMetric { * @param {string} metric * The metric to record (used to retrieve the base histogram id from the _histogram object). * @param {object} options - * @param {Extension | BrowserExtensionContent} options.extension + * @param {Extension | ExtensionChild} options.extension * The extension to record the telemetry for. * @param {string | undefined} [options.category] * An optional histogram category. @@ -290,7 +290,7 @@ class ExtensionTelemetryMetric { // NOTE: extensionsTiming may become a property of the GLEAN_METRICS_TYPES // map once we may introduce new histograms that are not part of the // extensionsTiming Glean metrics category. - Glean.extensionsTiming[metric].accumulateSamples([value]); + Glean.extensionsTiming[metric].accumulateSingleSample(value); break; } case "labeled_counter": { @@ -324,6 +324,8 @@ const metricsCache = new Map(); * ExtensionTelemetry.extensionStartup.stopwatchStart(extension); * ExtensionTelemetry.browserActionPreloadResult.histogramAdd({category: "Shown", extension}); */ +/** @type {Record<string, ExtensionTelemetryMetric>} */ +// @ts-ignore no easy way in TS to say Proxy is a different type from target. export var ExtensionTelemetry = new Proxy(metricsCache, { get(target, prop) { // NOTE: if we would be start adding glean probes that do not have a unified diff --git a/toolkit/components/extensions/ExtensionUtils.sys.mjs b/toolkit/components/extensions/ExtensionUtils.sys.mjs index 45f22aa530..84f7b25c01 100644 --- a/toolkit/components/extensions/ExtensionUtils.sys.mjs +++ b/toolkit/components/extensions/ExtensionUtils.sys.mjs @@ -43,7 +43,7 @@ function promiseTimeout(delay) { * An Error subclass for which complete error messages are always passed * to extensions, rather than being interpreted as an unknown error. */ -class ExtensionError extends DOMException { +export class ExtensionError extends DOMException { constructor(message) { super(message, "ExtensionError"); } @@ -67,7 +67,7 @@ function filterStack(error) { * only logged internally and raised to the worker script as * the generic unexpected error). */ -class WorkerExtensionError extends DOMException { +export class WorkerExtensionError extends DOMException { constructor(message) { super(message, "Error"); } @@ -122,9 +122,9 @@ function getInnerWindowID(window) { * A set with a limited number of slots, which flushes older entries as * newer ones are added. * - * @param {integer} limit + * @param {number} limit * The maximum size to trim the set to after it grows too large. - * @param {integer} [slop = limit * .25] + * @param {number} [slop = limit * .25] * The number of extra entries to allow in the set after it * reaches the size limit, before it is truncated to the limit. * @param {Iterable} [iterable] @@ -345,5 +345,4 @@ export var ExtensionUtils = { DefaultWeakMap, ExtensionError, LimitedSet, - WorkerExtensionError, }; diff --git a/toolkit/components/extensions/ExtensionWorkerChild.sys.mjs b/toolkit/components/extensions/ExtensionWorkerChild.sys.mjs index 44188d7ddb..d3fd477b10 100644 --- a/toolkit/components/extensions/ExtensionWorkerChild.sys.mjs +++ b/toolkit/components/extensions/ExtensionWorkerChild.sys.mjs @@ -10,8 +10,14 @@ */ import { - ExtensionChild, + ChildAPIManager, + ChildLocalAPIImplementation, ExtensionActivityLogChild, + MessageEvent, + Messenger, + Port, + ProxyAPIImplementation, + SimpleEventAPI, } from "resource://gre/modules/ExtensionChild.sys.mjs"; import { ExtensionCommon } from "resource://gre/modules/ExtensionCommon.sys.mjs"; @@ -19,20 +25,13 @@ import { ExtensionPageChild, getContextChildManagerGetter, } from "resource://gre/modules/ExtensionPageChild.sys.mjs"; -import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs"; +import { + ExtensionUtils, + WorkerExtensionError, +} from "resource://gre/modules/ExtensionUtils.sys.mjs"; const { BaseContext, redefineGetter } = ExtensionCommon; -const { - ChildAPIManager, - ChildLocalAPIImplementation, - MessageEvent, - Messenger, - Port, - ProxyAPIImplementation, - SimpleEventAPI, -} = ExtensionChild; - const { DefaultMap, getUniqueId } = ExtensionUtils; /** @@ -341,7 +340,7 @@ class WebIDLChildAPIManager extends ChildAPIManager { * The object that represents the API request received * (including arguments, an event listener wrapper etc) * - * @returns {mozIExtensionAPIRequestResult} + * @returns {Partial<mozIExtensionAPIRequestResult>} * Result for the API request, either a value to be returned * (which has to be a value that can be structure cloned * if the request was originated from the worker thread) or @@ -373,9 +372,8 @@ class WebIDLChildAPIManager extends ChildAPIManager { * into the expected mozIExtensionAPIRequestResult. * * @param {Error | WorkerExtensionError} error - * @returns {mozIExtensionAPIRequestResult} + * @returns {Partial<mozIExtensionAPIRequestResult>} */ - handleExtensionError(error) { // Propagate an extension error to the caller on the worker thread. if (error instanceof this.context.Error) { @@ -402,7 +400,6 @@ class WebIDLChildAPIManager extends ChildAPIManager { * @returns {any} * @throws {Error | WorkerExtensionError} */ - callAPIImplementation(request, impl) { const { requestType, normalizedArgs } = request; @@ -480,7 +477,7 @@ class WebIDLChildAPIManager extends ChildAPIManager { * Return an ExtensionAPI class instance given its namespace. * * @param {string} namespace - * @returns {ExtensionAPI} + * @returns {import("ExtensionCommon.sys.mjs").ExtensionAPI} */ getExtensionAPIInstance(namespace) { return this.apiCan.apis.get(namespace); @@ -569,7 +566,7 @@ class WorkerContextChild extends BaseContext { * This WorkerContextChild represents an addon execution environment * that is running on the worker thread in an extension child process. * - * @param {BrowserExtensionContent} extension This context's owner. + * @param {ExtensionChild} extension This context's owner. * @param {object} params * @param {mozIExtensionServiceWorkerInfo} params.serviceWorkerInfo */ @@ -607,7 +604,7 @@ class WorkerContextChild extends BaseContext { // ExtensionAPIRequestHandler as errors that should be propagated to // the worker thread and received by extension code that originated // the API request. - Error: ExtensionUtils.WorkerExtensionError, + Error: WorkerExtensionError, }; } @@ -616,6 +613,7 @@ class WorkerContextChild extends BaseContext { return { workerDescriptorId }; } + /** @type {ConduitGen} */ openConduit(subject, address) { let proc = ChromeUtils.domProcessChild; let conduit = proc.getActor("ProcessConduits").openConduit(subject, { @@ -658,7 +656,7 @@ class WorkerContextChild extends BaseContext { * Captures the most recent stack frame from the WebIDL API request being * processed. * - * @returns {SavedFrame?} + * @returns {nsIStackFrame} */ getCaller() { return this.webidlAPIRequest?.callerSavedFrame; @@ -719,7 +717,7 @@ export var ExtensionWorkerChild = { * Create an extension worker context (on a mozExtensionAPIRequest with * requestType "initWorkerContext"). * - * @param {BrowserExtensionContent} extension + * @param {ExtensionChild} extension * The extension for which the context should be created. * @param {mozIExtensionServiceWorkerInfo} serviceWorkerInfo */ @@ -751,7 +749,7 @@ export var ExtensionWorkerChild = { * Get an existing extension worker context for the given extension and * service worker. * - * @param {BrowserExtensionContent} extension + * @param {ExtensionChild} extension * The extension for which the context should be created. * @param {mozIExtensionServiceWorkerInfo} serviceWorkerInfo * diff --git a/toolkit/components/extensions/ExtensionXPCShellUtils.sys.mjs b/toolkit/components/extensions/ExtensionXPCShellUtils.sys.mjs index 27323dc8b3..3489a2caba 100644 --- a/toolkit/components/extensions/ExtensionXPCShellUtils.sys.mjs +++ b/toolkit/components/extensions/ExtensionXPCShellUtils.sys.mjs @@ -7,6 +7,7 @@ import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; import { XPCShellContentUtils } from "resource://testing-common/XPCShellContentUtils.sys.mjs"; +/** @type {Lazy} */ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { @@ -32,9 +33,9 @@ let BASE_MANIFEST = Object.freeze({ }); class ExtensionWrapper { - /** @type {AddonWrapper} */ + /** @type {import("resource://gre/modules/addons/XPIDatabase.sys.mjs").AddonWrapper} */ addon; - /** @type {Promise<AddonWrapper>} */ + /** @type {Promise} */ addonPromise; /** @type {nsIFile[]} */ cleanupFiles; diff --git a/toolkit/components/extensions/Schemas.sys.mjs b/toolkit/components/extensions/Schemas.sys.mjs index e98dfb36f0..b107036355 100644 --- a/toolkit/components/extensions/Schemas.sys.mjs +++ b/toolkit/components/extensions/Schemas.sys.mjs @@ -11,6 +11,7 @@ import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs"; var { DefaultMap, DefaultWeakMap } = ExtensionUtils; +/** @type {Lazy} */ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { @@ -262,6 +263,23 @@ const POSTPROCESSORS = { return string; }, + webRequestBlockingOrAuthProviderPermissionRequired(string, context) { + if ( + string === "blocking" && + !( + context.hasPermission("webRequestBlocking") || + context.hasPermission("webRequestAuthProvider") + ) + ) { + throw new context.cloneScope.Error( + "Using webRequest.onAuthRequired.addListener with the " + + "blocking option requires either the 'webRequestBlocking' " + + "or 'webRequestAuthProvider' permission." + ); + } + + return string; + }, requireBackgroundServiceWorkerEnabled(value, context) { if (WebExtensionPolicy.backgroundServiceWorkerEnabled) { return value; @@ -904,7 +922,8 @@ class InjectionContext extends Context { * @param {string} _namespace The full path to the namespace of the API, minus * the name of the method or property. E.g. "storage.local". * @param {string} _name The name of the method, property or event. - * @returns {SchemaAPIInterface} The implementation of the API. + * @returns {import("ExtensionCommon.sys.mjs").SchemaAPIInterface} + * The implementation of the API. */ getImplementation(_namespace, _name) { throw new Error("Not implemented"); @@ -1125,7 +1144,7 @@ const FORMATS = { }, strictRelativeUrl(string, context) { - void FORMATS.unresolvedRelativeUrl(string, context); + void FORMATS.unresolvedRelativeUrl(string); return FORMATS.relativeUrl(string, context); }, @@ -1220,8 +1239,8 @@ const FORMATS = { throw new Error(errorMessage); }, - manifestShortcutKeyOrEmpty(string, context) { - return string === "" ? "" : FORMATS.manifestShortcutKey(string, context); + manifestShortcutKeyOrEmpty(string) { + return string === "" ? "" : FORMATS.manifestShortcutKey(string); }, versionString(string, context) { @@ -1470,26 +1489,33 @@ class Type extends Entry { } } - // Takes a value, checks that it has the correct type, and returns a - // "normalized" version of the value. The normalized version will - // include "nulls" in place of omitted optional properties. The - // result of this function is either {error: "Some type error"} or - // {value: <normalized-value>}. + /** + * Takes a value, checks that it has the correct type, and returns a + * "normalized" version of the value. The normalized version will + * include "nulls" in place of omitted optional properties. The + * result of this function is either {error: "Some type error"} or + * {value: <normalized-value>}. + */ normalize(value, context) { return context.error("invalid type"); } - // Unlike normalize, this function does a shallow check to see if - // |baseType| (one of the possible getValueBaseType results) is - // valid for this type. It returns true or false. It's used to fill - // in optional arguments to functions before actually type checking - - checkBaseType() { + /** + * Unlike normalize, this function does a shallow check to see if + * |baseType| (one of the possible getValueBaseType results) is + * valid for this type. It returns true or false. It's used to fill + * in optional arguments to functions before actually type checking + * + * @param {string} _baseType + */ + checkBaseType(_baseType) { return false; } - // Helper method that simply relies on checkBaseType to implement - // normalize. Subclasses can choose to use it or not. + /** + * Helper method that simply relies on checkBaseType to implement + * normalize. Subclasses can choose to use it or not. + */ normalizeBase(type, value, context) { if (this.checkBaseType(getValueBaseType(value))) { this.checkDeprecated(context, value); @@ -3458,22 +3484,26 @@ class SchemaRoots extends Namespaces { * other schema roots. May extend a base namespace, in which case schemas in * this root may refer to types in a base, but not vice versa. * - * @param {SchemaRoot|Array<SchemaRoot>|null} base - * A base schema root (or roots) from which to derive, or null. - * @param {Map<string, Array|StructuredCloneHolder>} schemaJSON - * A map of schema URLs and corresponding JSON blobs from which to - * populate this root namespace. + * @implements {SchemaInject} */ export class SchemaRoot extends Namespace { + /** + * @param {SchemaRoot|SchemaRoot[]} base + * A base schema root (or roots) from which to derive, or null. + * @param {Map<string, Array|StructuredCloneHolder>} schemaJSON + * A map of schema URLs and corresponding JSON blobs from which to + * populate this root namespace. + */ constructor(base, schemaJSON) { super(null, "", []); if (Array.isArray(base)) { - base = new SchemaRoots(this, base); + this.base = new SchemaRoots(this, base); + } else { + this.base = base; } this.root = this; - this.base = base; this.schemaJSON = schemaJSON; } @@ -3555,7 +3585,7 @@ export class SchemaRoot extends Namespace { parseSchemas() { for (let [key, schema] of this.schemaJSON.entries()) { try { - if (typeof schema.deserialize === "function") { + if (StructuredCloneHolder.isInstance(schema)) { schema = schema.deserialize(globalThis, isParentProcess); // If we're in the parent process, we need to keep the @@ -3607,7 +3637,7 @@ export class SchemaRoot extends Namespace { * * @param {object} dest The root namespace for the APIs. * This object is usually exposed to extensions as "chrome" or "browser". - * @param {object} wrapperFuncs An implementation of the InjectionContext + * @param {InjectionContext} wrapperFuncs An implementation of the InjectionContext * interface, which runs the actual functionality of the generated API. */ inject(dest, wrapperFuncs) { @@ -3651,6 +3681,11 @@ export class SchemaRoot extends Namespace { } } +/** + * @typedef {{ inject: typeof Schemas.inject }} SchemaInject + * Interface SchemaInject as used by SchemaApiManager, + * with the one method shared across Schemas and SchemaRoot. + */ export var Schemas = { initialized: false, @@ -3676,6 +3711,7 @@ export var Schemas = { extContext => new Context(extContext) ), + /** @returns {SchemaRoot} */ get rootSchema() { if (!this.initialized) { this.init(); @@ -3833,7 +3869,7 @@ export var Schemas = { * * @param {object} dest The root namespace for the APIs. * This object is usually exposed to extensions as "chrome" or "browser". - * @param {object} wrapperFuncs An implementation of the InjectionContext + * @param {InjectionContext} wrapperFuncs An implementation of the InjectionContext * interface, which runs the actual functionality of the generated API. */ inject(dest, wrapperFuncs) { @@ -3898,6 +3934,7 @@ export var Schemas = { ? `${apiNamespace}.${apiName}.${requestType}` : `${apiNamespace}.${apiName}` ).split("."); + /** @type {Namespace|CallEntry} */ let apiSchema = this.getNamespace(ns); // Keep track of the current schema path, populated while navigating the nested API schema @@ -3926,7 +3963,7 @@ export var Schemas = { throw new Error(`API Schema not found for ${schemaPath.join(".")}`); } - if (!apiSchema.checkParameters) { + if (!(apiSchema instanceof CallEntry)) { throw new Error( `Unexpected API Schema type for ${schemaPath.join( "." diff --git a/toolkit/components/extensions/WebNavigation.sys.mjs b/toolkit/components/extensions/WebNavigation.sys.mjs index 7235aaeb4e..c33a45db81 100644 --- a/toolkit/components/extensions/WebNavigation.sys.mjs +++ b/toolkit/components/extensions/WebNavigation.sys.mjs @@ -4,6 +4,7 @@ import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; +/** @type {Lazy} */ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { @@ -23,9 +24,12 @@ function getBrowser(bc) { } export var WebNavigationManager = { - // Map[string -> Map[listener -> URLFilter]] + /** @type {Map<string, Set<callback>>} */ listeners: new Map(), + /** @type {WeakMap<XULBrowserElement, object>} */ + recentTabTransitionData: new WeakMap(), + init() { // Collect recent tab transition data in a WeakMap: // browser -> tabTransitionData @@ -123,9 +127,9 @@ export var WebNavigationManager = { * The data for the autocompleted item. * @param {object} [acData.result] * The result information associated with the navigation action. - * @param {UrlbarUtils.RESULT_TYPE} [acData.result.type] + * @param {typeof lazy.UrlbarUtils.RESULT_TYPE} [acData.result.type] * The result type associated with the navigation action. - * @param {UrlbarUtils.RESULT_SOURCE} [acData.result.source] + * @param {typeof lazy.UrlbarUtils.RESULT_SOURCE} [acData.result.source] * The result source associated with the navigation action. */ onURLBarUserStartNavigation(acData) { diff --git a/toolkit/components/extensions/WebNavigationFrames.sys.mjs b/toolkit/components/extensions/WebNavigationFrames.sys.mjs index 211698a88e..20db413bc0 100644 --- a/toolkit/components/extensions/WebNavigationFrames.sys.mjs +++ b/toolkit/components/extensions/WebNavigationFrames.sys.mjs @@ -45,7 +45,7 @@ function getParentFrameId(bc) { /** * Convert a BrowsingContext into internal FrameDetail json. * - * @param {BrowsingContext} bc + * @param {CanonicalBrowsingContext} bc * @returns {FrameDetail} */ function getFrameDetail(bc) { diff --git a/toolkit/components/extensions/extIWebNavigation.idl b/toolkit/components/extensions/extIWebNavigation.idl index 3095d93d9f..1db9e73808 100644 --- a/toolkit/components/extensions/extIWebNavigation.idl +++ b/toolkit/components/extensions/extIWebNavigation.idl @@ -17,8 +17,8 @@ interface extIWebNavigation : nsISupports void onHistoryChange(in BrowsingContext bc, in jsval transitionData, in nsIURI location, - in bool isHistoryStateUpdated, - in bool isReferenceFragmentUpdated); + in boolean isHistoryStateUpdated, + in boolean isReferenceFragmentUpdated); void onStateChange(in BrowsingContext bc, in nsIURI requestURI, diff --git a/toolkit/components/extensions/mozIExtensionAPIRequestHandling.idl b/toolkit/components/extensions/mozIExtensionAPIRequestHandling.idl index 0a2e3c7a5d..d7eb40345b 100644 --- a/toolkit/components/extensions/mozIExtensionAPIRequestHandling.idl +++ b/toolkit/components/extensions/mozIExtensionAPIRequestHandling.idl @@ -35,7 +35,7 @@ interface mozIExtensionListenerCallOptions : nsISupports // An optional boolean to be set to true if the api object should be // prepended to the rest of the call arguments (by default it is appended). - readonly attribute bool apiObjectPrepended; + readonly attribute boolean apiObjectPrepended; cenum CallbackType: 8 { // Default: no callback argument is passed to the call to the event listener. diff --git a/toolkit/components/extensions/mozIExtensionProcessScript.idl b/toolkit/components/extensions/mozIExtensionProcessScript.idl index 84b33a9d02..47e442149c 100644 --- a/toolkit/components/extensions/mozIExtensionProcessScript.idl +++ b/toolkit/components/extensions/mozIExtensionProcessScript.idl @@ -17,5 +17,5 @@ interface mozIExtensionProcessScript : nsISupports in mozIDOMWindow window); void initExtensionDocument(in nsISupports extension, in Document doc, - in bool privileged); + in boolean privileged); }; diff --git a/toolkit/components/extensions/parent/ext-identity.js b/toolkit/components/extensions/parent/ext-identity.js index bd53163305..f0f63dbf34 100644 --- a/toolkit/components/extensions/parent/ext-identity.js +++ b/toolkit/components/extensions/parent/ext-identity.js @@ -12,7 +12,7 @@ var { promiseDocumentLoaded } = ExtensionUtils; const checkRedirected = (url, redirectURI) => { return new Promise((resolve, reject) => { - let xhr = new XMLHttpRequest(); + let xhr = new XMLHttpRequest({ mozAnon: false }); xhr.open("GET", url); // We expect this if the user has not authenticated. xhr.onload = () => { diff --git a/toolkit/components/extensions/parent/ext-runtime.js b/toolkit/components/extensions/parent/ext-runtime.js index d1c03d9e0d..3f9c0f8857 100644 --- a/toolkit/components/extensions/parent/ext-runtime.js +++ b/toolkit/components/extensions/parent/ext-runtime.js @@ -248,7 +248,7 @@ this.runtime = class extends ExtensionAPIPersistent { }, openOptionsPage: function () { - if (!extension.manifest.options_ui) { + if (!extension.optionsPageProperties) { return Promise.reject({ message: "No `options_ui` declared" }); } diff --git a/toolkit/components/extensions/parent/ext-webRequest.js b/toolkit/components/extensions/parent/ext-webRequest.js index 4f0ea90abd..f94c773e2e 100644 --- a/toolkit/components/extensions/parent/ext-webRequest.js +++ b/toolkit/components/extensions/parent/ext-webRequest.js @@ -64,7 +64,11 @@ function registerEvent( filter2.incognito = filter.incognito; } - let blockingAllowed = extension.hasPermission("webRequestBlocking"); + let blockingAllowed = + eventName == "onAuthRequired" + ? extension.hasPermission("webRequestBlocking") || + extension.hasPermission("webRequestAuthProvider") + : extension.hasPermission("webRequestBlocking"); let info2 = []; if (info) { diff --git a/toolkit/components/extensions/schemas/manifest.json b/toolkit/components/extensions/schemas/manifest.json index 14f78ba564..384b168e39 100644 --- a/toolkit/components/extensions/schemas/manifest.json +++ b/toolkit/components/extensions/schemas/manifest.json @@ -173,11 +173,15 @@ "optional": true }, + "options_page": { + "$ref": "ExtensionURL", + "optional": true, + "description": "Alias property for options_ui.page, ignored when options_ui.page is set. When using this property the options page is always opened in a new tab." + }, + "options_ui": { "type": "object", - "optional": true, - "properties": { "page": { "$ref": "ExtensionURL" }, "browser_style": { diff --git a/toolkit/components/extensions/schemas/web_request.json b/toolkit/components/extensions/schemas/web_request.json index e4405f24c3..a1528d87f6 100644 --- a/toolkit/components/extensions/schemas/web_request.json +++ b/toolkit/components/extensions/schemas/web_request.json @@ -9,6 +9,7 @@ "type": "string", "enum": [ "webRequest", + "webRequestAuthProvider", "webRequestBlocking", "webRequestFilterResponse", "webRequestFilterResponse.serviceWorkerScript" @@ -82,7 +83,7 @@ "id": "OnAuthRequiredOptions", "type": "string", "enum": ["responseHeaders", "blocking", "asyncBlocking"], - "postprocess": "webRequestBlockingPermissionRequired" + "postprocess": "webRequestBlockingOrAuthProviderPermissionRequired" }, { "id": "OnResponseStartedOptions", diff --git a/toolkit/components/extensions/storage/webext_storage_bridge/src/area.rs b/toolkit/components/extensions/storage/webext_storage_bridge/src/area.rs index 1418ccca29..ec4f2057b9 100644 --- a/toolkit/components/extensions/storage/webext_storage_bridge/src/area.rs +++ b/toolkit/components/extensions/storage/webext_storage_bridge/src/area.rs @@ -13,7 +13,7 @@ use std::{ }; use golden_gate::{ApplyTask, BridgedEngine, FerryTask}; -use moz_task::{self, DispatchOptions, TaskRunnable}; +use moz_task::{DispatchOptions, TaskRunnable}; use nserror::{nsresult, NS_OK}; use nsstring::{nsACString, nsCString, nsString}; use thin_vec::ThinVec; diff --git a/toolkit/components/extensions/test/browser/browser.toml b/toolkit/components/extensions/test/browser/browser.toml index 33d54bddc2..33a3a6dc64 100644 --- a/toolkit/components/extensions/test/browser/browser.toml +++ b/toolkit/components/extensions/test/browser/browser.toml @@ -9,7 +9,6 @@ support-files = [ ["browser_ext_downloads_filters.js"] ["browser_ext_downloads_referrer.js"] -https_first_disabled = true ["browser_ext_eventpage_disableResetIdleForTest.js"] diff --git a/toolkit/components/extensions/test/browser/browser_ext_extension_page_tab_navigated.js b/toolkit/components/extensions/test/browser/browser_ext_extension_page_tab_navigated.js index 429d584a17..f85bbfe595 100644 --- a/toolkit/components/extensions/test/browser/browser_ext_extension_page_tab_navigated.js +++ b/toolkit/components/extensions/test/browser/browser_ext_extension_page_tab_navigated.js @@ -3,10 +3,6 @@ "use strict"; -const { AddonTestUtils } = ChromeUtils.importESModule( - "resource://testing-common/AddonTestUtils.sys.mjs" -); - // The test tasks in this test file tends to trigger an intermittent // exception raised from JSActor::AfterDestroy, because of a race between // when the WebExtensions API event is being emitted from the parent process @@ -18,23 +14,6 @@ PromiseTestUtils.allowMatchingRejectionsGlobally( /Actor 'Conduits' destroyed before query 'RunListener' was resolved/ ); -AddonTestUtils.initMochitest(this); - -const server = AddonTestUtils.createHttpServer({ - hosts: ["example.com", "anotherwebpage.org"], -}); - -server.registerPathHandler("/", (request, response) => { - response.write(`<!DOCTYPE html> - <html> - <head> - <meta charset="utf-8"> - <title>test webpage</title> - </head> - </html> - `); -}); - function createTestExtPage({ script }) { return `<!DOCTYPE html> <html> @@ -55,7 +34,7 @@ function createTestExtPageScript(name) { ); browser.test.sendMessage(`event-received:${pageName}`); }, - { types: ["main_frame"], urls: ["http://example.com/*"] } + { types: ["main_frame"], urls: ["https://example.com/*"] } ); /* eslint-disable mozilla/balanced-listeners */ window.addEventListener("pageshow", () => { @@ -93,7 +72,7 @@ async function triggerWebRequestListener(webPageURL) { add_task(async function test_extension_page_sameprocess_navigation() { const extension = ExtensionTestUtils.loadExtension({ manifest: { - permissions: ["webRequest", "http://example.com/*"], + permissions: ["webRequest", "https://example.com/*"], }, files: { "extpage1.html": createTestExtPage({ script: "extpage1.js" }), @@ -116,7 +95,7 @@ add_task(async function test_extension_page_sameprocess_navigation() { info("Wait for the extension page to be loaded"); await extension.awaitMessage("pageshow:extpage1"); - await triggerWebRequestListener("http://example.com"); + await triggerWebRequestListener("https://example.com"); await extension.awaitMessage("event-received:extpage1"); ok(true, "extpage1 got a webRequest event as expected"); @@ -131,7 +110,7 @@ add_task(async function test_extension_page_sameprocess_navigation() { info( "Trigger a web request event and expect extpage2 to be the only one receiving it" ); - await triggerWebRequestListener("http://example.com"); + await triggerWebRequestListener("https://example.com"); await extension.awaitMessage("event-received:extpage2"); ok(true, "extpage2 got a webRequest event as expected"); @@ -146,7 +125,7 @@ add_task(async function test_extension_page_sameprocess_navigation() { await extension.awaitMessage("pagehide:extpage2"); // We only expect extpage1 to be able to receive API events. - await triggerWebRequestListener("http://example.com"); + await triggerWebRequestListener("https://example.com"); await extension.awaitMessage("event-received:extpage1"); ok(true, "extpage1 got a webRequest event as expected"); @@ -159,7 +138,7 @@ add_task(async function test_extension_page_sameprocess_navigation() { add_task(async function test_extension_page_context_navigated_to_web_page() { const extension = ExtensionTestUtils.loadExtension({ manifest: { - permissions: ["webRequest", "http://example.com/*"], + permissions: ["webRequest", "https://example.com/*"], }, files: { "extpage.html": createTestExtPage({ script: "extpage.js" }), @@ -178,8 +157,8 @@ add_task(async function test_extension_page_context_navigated_to_web_page() { // navigated will be intermittently able to receive an event before it // is navigated to the webpage url (and moved into the BFCache or destroyed) // and trigger an intermittent failure of this test. - const webPageURL = "http://anotherwebpage.org/"; - const triggerWebRequestURL = "http://example.com/"; + const webPageURL = "https://example.net/"; + const triggerWebRequestURL = "https://example.com/"; info("Opening extension page in a new tab"); const extPageTab1 = await BrowserTestUtils.addTab(gBrowser, extPageURL); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors.js b/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors.js index 8e2f5446c9..4ee92bf798 100644 --- a/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors.js +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors.js @@ -26,17 +26,20 @@ async function test_ntp_theme(theme, isBrightText) { }); let browser = gBrowser.selectedBrowser; - let { originalBackground, originalCardBackground, originalColor } = await SpecialPowers.spawn(browser, [], function () { let doc = content.document; ok( !doc.documentElement.hasAttribute("lwt-newtab"), - "New tab page should not have lwt-newtab attribute" + `New tab page should not have lwt-newtab attribute` + ); + ok( + !doc.documentElement.hasAttribute("lwtheme"), + `New tab page should not have lwtheme attribute` ); ok( !doc.documentElement.hasAttribute("lwt-newtab-brighttext"), - `New tab page should not have lwt-newtab-brighttext attribute` + `New tab page not should have lwt-newtab-brighttext attribute` ); return { @@ -64,22 +67,39 @@ async function test_ntp_theme(theme, isBrightText) { Services.ppmm.sharedData.flush(); + let hasNtpColors = !!( + theme.colors.ntp_background || + theme.colors.ntp_text || + theme.colors.ntp_card_background + ); await SpecialPowers.spawn( browser, [ { isBrightText, + hasNtpColors, background: hexToCSS(theme.colors.ntp_background), card_background: hexToCSS(theme.colors.ntp_card_background), color: hexToCSS(theme.colors.ntp_text), }, ], - async function ({ isBrightText, background, card_background, color }) { + async function ({ + isBrightText, + hasNtpColors, + background, + card_background, + color, + }) { let doc = content.document; - ok( + is( doc.documentElement.hasAttribute("lwt-newtab"), + hasNtpColors, "New tab page should have lwt-newtab attribute" ); + ok( + doc.documentElement.hasAttribute("lwtheme"), + "New tab page should have lwtheme attribute" + ); is( doc.documentElement.hasAttribute("lwt-newtab-brighttext"), isBrightText, @@ -88,22 +108,24 @@ async function test_ntp_theme(theme, isBrightText) { } have lwt-newtab-brighttext attribute` ); - is( - content.getComputedStyle(doc.body).backgroundColor, - background, - "New tab page background should be set." - ); - is( - content.getComputedStyle(doc.querySelector(".top-site-outer .tile")) - .backgroundColor, - card_background, - "New tab page card background should be set." - ); - is( - content.getComputedStyle(doc.querySelector(".outer-wrapper")).color, - color, - "New tab page text color should be set." - ); + if (hasNtpColors) { + is( + content.getComputedStyle(doc.body).backgroundColor, + background, + "New tab page background should be set." + ); + is( + content.getComputedStyle(doc.querySelector(".top-site-outer .tile")) + .backgroundColor, + card_background, + "New tab page card background should be set." + ); + is( + content.getComputedStyle(doc.querySelector(".outer-wrapper")).color, + color, + "New tab page text color should be set." + ); + } } ); @@ -127,6 +149,10 @@ async function test_ntp_theme(theme, isBrightText) { "New tab page should not have lwt-newtab attribute" ); ok( + !doc.documentElement.hasAttribute("lwtheme"), + "New tab page should not have lwtheme attribute" + ); + ok( !doc.documentElement.hasAttribute("lwt-newtab-brighttext"), `New tab page should not have lwt-newtab-brighttext attribute` ); @@ -151,6 +177,17 @@ async function test_ntp_theme(theme, isBrightText) { ); } +async function waitForDarkMode(value) { + info(`waiting for dark mode: ${value}`); + const mq = matchMedia("(prefers-color-scheme: dark)"); + if (mq.matches == value) { + return; + } + await new Promise(r => { + mq.addEventListener("change", r, { once: true }); + }); +} + add_task(async function test_support_ntp_colors() { await SpecialPowers.pushPrefEnv({ set: [ @@ -163,11 +200,13 @@ add_task(async function test_support_ntp_colors() { ["layout.css.prefers-color-scheme.content-override", 1], // Override the system color scheme to light so this test passes on // machines with dark system color scheme. + // FIXME(emilio): This doesn't seem working reliably, at least on macOS. ["ui.systemUsesDarkTheme", 0], ], }); NewTabPagePreloading.removePreloadedBrowser(window); for (let url of ["about:newtab", "about:home"]) { + await waitForDarkMode(false); info("Opening url: " + url); await BrowserTestUtils.withNewTab({ gBrowser, url }, async browser => { await waitForAboutNewTabReady(browser, url); @@ -185,6 +224,7 @@ add_task(async function test_support_ntp_colors() { url ); + await waitForDarkMode(false); await test_ntp_theme( { colors: { @@ -198,6 +238,19 @@ add_task(async function test_support_ntp_colors() { true, url ); + + // Test a theme without any new tab page colors + await waitForDarkMode(false); + await test_ntp_theme( + { + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + }, + }, + false, + url + ); }); } }); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors_perwindow.js b/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors_perwindow.js index 3b739322d6..54152d005c 100644 --- a/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors_perwindow.js +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors_perwindow.js @@ -39,6 +39,10 @@ function test_ntp_theme(browser, theme, isBrightText) { doc.documentElement.hasAttribute("lwt-newtab"), "New tab page should have lwt-newtab attribute" ); + ok( + doc.documentElement.hasAttribute("lwtheme"), + "New tab page should have lwtheme attribute" + ); is( doc.documentElement.hasAttribute("lwt-newtab-brighttext"), isBrightText, @@ -90,6 +94,10 @@ function test_ntp_default_theme(browser) { "New tab page should not have lwt-newtab attribute" ); ok( + !doc.documentElement.hasAttribute("lwtheme"), + "New tab page should not have lwtheme attribute" + ); + ok( !doc.documentElement.hasAttribute("lwt-newtab-brighttext"), `New tab page should not have lwt-newtab-brighttext attribute` ); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_pbm.js b/toolkit/components/extensions/test/browser/browser_ext_themes_pbm.js index 3b36a256d0..d2dfb16e72 100644 --- a/toolkit/components/extensions/test/browser/browser_ext_themes_pbm.js +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_pbm.js @@ -336,7 +336,7 @@ add_task(async function test_pbm_dark_page_info() { await BrowserTestUtils.withNewTab( { gBrowser: win.gBrowser, url: "https://example.com" }, async () => { - let pageInfo = win.BrowserPageInfo(null, "securityTab"); + let pageInfo = win.BrowserCommands.pageInfo(null, "securityTab"); await BrowserTestUtils.waitForEvent(pageInfo, "page-info-init"); let prefersColorScheme = await getPrefersColorSchemeInfo({ diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_sanitization.js b/toolkit/components/extensions/test/browser/browser_ext_themes_sanitization.js index 89ebd3ae68..fdee1eb72d 100644 --- a/toolkit/components/extensions/test/browser/browser_ext_themes_sanitization.js +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_sanitization.js @@ -114,8 +114,9 @@ add_task(async function test_sanitization_transparent() { await extension.startup(); let navbar = document.querySelector("#nav-bar"); - Assert.ok( - window.getComputedStyle(navbar).boxShadow.includes("rgba(0, 0, 0, 0)"), + Assert.equal( + window.getComputedStyle(navbar).borderTopColor, + "rgba(0, 0, 0, 0)", "Top separator should be transparent" ); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_separators.js b/toolkit/components/extensions/test/browser/browser_ext_themes_separators.js index 4da4927ccf..1b953269b6 100644 --- a/toolkit/components/extensions/test/browser/browser_ext_themes_separators.js +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_separators.js @@ -48,10 +48,9 @@ add_task(async function test_support_separator_properties() { await deprecatedMessagePromise; let navbar = document.querySelector("#nav-bar"); - Assert.ok( - window - .getComputedStyle(navbar) - .boxShadow.includes(`rgb(${hexToRGB(SEPARATOR_TOP_COLOR).join(", ")})`), + Assert.equal( + window.getComputedStyle(navbar).borderTopColor, + `rgb(${hexToRGB(SEPARATOR_TOP_COLOR).join(", ")})`, "Top separator color properly set" ); diff --git a/toolkit/components/extensions/test/mochitest/test_check_startupcache.html b/toolkit/components/extensions/test/mochitest/test_check_startupcache.html index 8cb529d18d..d1157472ec 100644 --- a/toolkit/components/extensions/test/mochitest/test_check_startupcache.html +++ b/toolkit/components/extensions/test/mochitest/test_check_startupcache.html @@ -41,7 +41,7 @@ add_task(async function check_ExtensionParent_StartupCache_is_non_empty() { let map = await chromeScript.promiseOneMessage("StartupCache_data"); chromeScript.destroy(); - // "manifests" is populated by Extension's parseManifest in Extension.jsm. + // "manifests" is populated by Extension's parseManifest in Extension.sys.mjs. const keys = ["manifests", "mochikit@mozilla.org", "2.0", "en-US"]; for (let key of keys) { map = map.get(key); diff --git a/toolkit/components/extensions/test/mochitest/test_ext_async_clipboard.html b/toolkit/components/extensions/test/mochitest/test_ext_async_clipboard.html index 708b5522c3..e7fdd6aad0 100644 --- a/toolkit/components/extensions/test/mochitest/test_ext_async_clipboard.html +++ b/toolkit/components/extensions/test/mochitest/test_ext_async_clipboard.html @@ -354,11 +354,11 @@ add_task(async function test_contentscript_clipboard_nocontents_readtext() { await extension.unload(); }); -// Test that performing read(...) when the clipboard is empty returns an empty ClipboardItem +// Test that performing read(...) when the clipboard is empty returns no ClipboardItem add_task(async function test_contentscript_clipboard_nocontents_read() { function contentScript() { clipboardRead().then(function(items) { - if (items[0].types.length) { + if (items.length) { browser.test.fail("Read read the wrong thing from clipboard, " + "ClipboardItem has this many entries: " + items[0].types.length); } else { diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_securecontext.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_securecontext.html index 093c26898f..f9230c9a5f 100644 --- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_securecontext.html +++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_securecontext.html @@ -14,6 +14,8 @@ await SpecialPowers.pushPrefEnv({ "set": [ ["dom.w3c_pointer_events.getcoalescedevents_only_in_securecontext", true], + // Test is intentionally testing in non-secure contexts. + ["dom.security.https_first", false] ] }); }); diff --git a/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect.html b/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect.html index 85f98d5034..d474a7caee 100644 --- a/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect.html +++ b/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect.html @@ -18,6 +18,7 @@ function background() { browser.test.assertTrue(port.sender.url.endsWith("file_sample.html"), "URL correct"); browser.test.assertTrue(port.sender.tab.url.endsWith("file_sample.html"), "tab URL correct"); browser.test.assertEq(port.sender.frameId, 0, "frameId of top frame"); + browser.test.assertEq(new URL(port.sender.url).origin, port.sender.origin, "sender origin correct"); let expected = "message 1"; port.onMessage.addListener(msg => { diff --git a/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect2.html b/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect2.html index 13b9029c48..32620b5b3b 100644 --- a/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect2.html +++ b/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect2.html @@ -21,6 +21,7 @@ function backgroundScript(token) { browser.runtime.onConnect.addListener(port => { browser.test.assertTrue(port.sender.url.endsWith("file_sample.html"), "sender url correct"); browser.test.assertTrue(port.sender.tab.url.endsWith("file_sample.html"), "sender url correct"); + browser.test.assertEq(new URL(port.sender.url).origin, port.sender.origin, "sender origin correct"); let tabId = port.sender.tab.id; browser.tabs.connect(tabId, {name: token}); diff --git a/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect_iframe.html b/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect_iframe.html index 9c64635063..e3e0e80f2a 100644 --- a/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect_iframe.html +++ b/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect_iframe.html @@ -30,12 +30,15 @@ add_task(async function connect_from_background_frame() { } async function background() { const FRAME_URL = "https://example.com/tests/toolkit/components/extensions/test/mochitest/file_sample.html"; + const FRAME_ORIGIN = new URL(FRAME_URL).origin; + browser.runtime.onConnect.addListener(port => { // The next two assertions are the reason for this being a mochitest // instead of a xpcshell test. browser.test.assertEq(port.sender.tab, undefined, "Sender is not a tab"); browser.test.assertEq(port.sender.frameId, undefined, "frameId unset"); browser.test.assertEq(port.sender.url, FRAME_URL, "Expected sender URL"); + browser.test.assertEq(port.sender.origin, FRAME_ORIGIN, "Expected sender origin"); port.onMessage.addListener(msg => { browser.test.assertEq("pong", msg, "Reply from content script"); port.disconnect(); @@ -88,6 +91,8 @@ add_task(async function connect_from_content_script_in_frame() { async function background() { const TAB_URL = "https://example.org/tests/toolkit/components/extensions/test/mochitest/file_contains_iframe.html"; const FRAME_URL = "https://example.org/tests/toolkit/components/extensions/test/mochitest/file_contains_img.html"; + const FRAME_ORIGIN = new URL(FRAME_URL).origin; + let createdTab; browser.runtime.onConnect.addListener(port => { // The next two assertions are the reason for this being a mochitest @@ -95,6 +100,7 @@ add_task(async function connect_from_content_script_in_frame() { browser.test.assertEq(port.sender.tab.url, TAB_URL, "Sender is the tab"); browser.test.assertTrue(port.sender.frameId > 0, "frameId is set"); browser.test.assertEq(port.sender.url, FRAME_URL, "Expected sender URL"); + browser.test.assertEq(port.sender.origin, FRAME_ORIGIN, "Expected sender origin"); browser.test.assertEq(createdTab.id, port.sender.tab.id, "Tab to close"); browser.tabs.remove(port.sender.tab.id).then(() => { diff --git a/toolkit/components/extensions/test/mochitest/test_ext_tabs_captureTab.html b/toolkit/components/extensions/test/mochitest/test_ext_tabs_captureTab.html index ab06a965ed..20f368a0ae 100644 --- a/toolkit/components/extensions/test/mochitest/test_ext_tabs_captureTab.html +++ b/toolkit/components/extensions/test/mochitest/test_ext_tabs_captureTab.html @@ -319,6 +319,141 @@ add_task(async function testCaptureVisibleTabPermissions() { await extension.awaitFinish("captureVisibleTabPermissions"); await extension.unload(); }); + +add_task(async function testCaptureVisibleTabWithActiveTab() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + browser_action: { + default_area: "navbar", + }, + permissions: ["webNavigation", "tabs", "activeTab"], + }, + + async background() { + // Wait for the page (in the test window) to load. + await new Promise(resolve => { + browser.webNavigation.onCompleted.addListener( + () => resolve(), + {url: [{schemes: ["data"]}]}); + }); + + browser.browserAction.onClicked.addListener(async tab => { + await browser.tabs.captureVisibleTab(tab.windowId); + browser.test.notifyPass("captureVisibleTabPermissions"); + }); + + browser.test.sendMessage("ready"); + }, + }); + + let html = ` + <!DOCTYPE html> + <html lang="en"> + <head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + </head> + <body><h1>hello</h1></body> + </html> + `; + + await extension.startup(); + + let testWindow = window.open(`data:text/html,${encodeURIComponent(html)}#scroll`); + await extension.awaitMessage("ready"); + await AppTestDelegate.clickBrowserAction(testWindow, extension); + await extension.awaitFinish("captureVisibleTabPermissions"); + await AppTestDelegate.closeBrowserAction(testWindow, extension); + testWindow.close(); + + await extension.unload(); +}); + +add_task(async function testCaptureVisibleTabWithActiveTabAndNotUserInteraction() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["webNavigation", "tabs", "activeTab"], + }, + + async background() { + // Wait for the page (in the test window) to load. + await new Promise(resolve => { + browser.webNavigation.onCompleted.addListener( + () => resolve(), + {url: [{schemes: ["data"]}]}); + }); + + let [tab] = await browser.tabs.query({ currentWindow: true, active: true }); + await browser.test.assertRejects( + browser.tabs.captureVisibleTab(tab.windowId), + /Missing activeTab permission/, + "Expected rejection because activeTab permission isn't set" + ); + + browser.test.notifyPass("captureVisibleTabPermissions"); + }, + }); + + let html = ` + <!DOCTYPE html> + <html lang="en"> + <head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + </head> + <body><h1>hello</h1></body> + </html> + `; + + await extension.startup(); + + let testWindow = window.open(`data:text/html,${encodeURIComponent(html)}#scroll`); + await extension.awaitFinish("captureVisibleTabPermissions"); + testWindow.close(); + + await extension.unload(); +}); + +add_task(async function testCaptureVisibleTabWithActiveTabAndAllURLs() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["webNavigation", "tabs", "activeTab", "<all_urls>"], + }, + + async background() { + // Wait for the page (in the test window) to load. + await new Promise(resolve => { + browser.webNavigation.onCompleted.addListener( + () => resolve(), + {url: [{schemes: ["data"]}]}); + }); + + let [tab] = await browser.tabs.query({ currentWindow: true, active: true }); + await browser.tabs.captureVisibleTab(tab.windowId); + + browser.test.notifyPass("captureVisibleTabPermissions"); + }, + }); + + let html = ` + <!DOCTYPE html> + <html lang="en"> + <head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + </head> + <body><h1>hello</h1></body> + </html> + `; + + await extension.startup(); + + let testWindow = window.open(`data:text/html,${encodeURIComponent(html)}#scroll`); + await extension.awaitFinish("captureVisibleTabPermissions"); + testWindow.close(); + + await extension.unload(); +}); </script> </body> </html> diff --git a/toolkit/components/extensions/test/mochitest/test_ext_tabs_sendMessage.html b/toolkit/components/extensions/test/mochitest/test_ext_tabs_sendMessage.html index 4b230c258c..8c6dfeee7c 100644 --- a/toolkit/components/extensions/test/mochitest/test_ext_tabs_sendMessage.html +++ b/toolkit/components/extensions/test/mochitest/test_ext_tabs_sendMessage.html @@ -11,6 +11,10 @@ <script> "use strict"; +const { + WebExtensionPolicy +} = SpecialPowers.Cu.getGlobalForObject(SpecialPowers.Services); + add_task(async function test_tabs_sendMessage_to_extension_page_frame() { let extension = ExtensionTestUtils.loadExtension({ manifest: { @@ -27,6 +31,10 @@ add_task(async function test_tabs_sendMessage_to_extension_page_frame() { browser.runtime.onMessage.addListener(async (msg, sender) => { browser.test.assertEq(msg, "page-script-ready"); browser.test.assertEq(sender.url, browser.runtime.getURL("page.html")); + browser.test.assertEq( + sender.origin, + new URL(browser.runtime.getURL("/")).origin + ); let tabId = sender.tab.id; let response = await browser.tabs.sendMessage(tabId, "tab-sendMessage"); @@ -147,6 +155,178 @@ add_task(async function test_tabs_sendMessage_using_frameId() { await extension.unload(); }); +add_task(async function test_tabs_sendMessage_aboutBlank() { + const PATH = "tests/toolkit/components/extensions/test/mochitest/file_with_about_blank.html"; + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + content_scripts: [ + { + match_about_blank: true, + matches: ["*://mochi.test/*/file_with_about_blank.html"], + all_frames: true, + js: ["cs.js"], + }, + ], + }, + + background() { + browser.runtime.onMessage.addListener((msg, { url, origin }) => { + browser.test.assertEq("cs", msg, "expected message from cs.js"); + + const kind = url.startsWith("about:") ? url : "top"; + switch (kind) { + case "top": + browser.test.assertTrue( + url.endsWith("/file_with_about_blank.html"), + "expected correct url" + ); + browser.test.assertEq( + "http://mochi.test:8888", + origin, + "expected correct origin" + ); + break; + + case "about:blank": + browser.test.assertEq("about:blank", url, "expected correct url"); + browser.test.assertEq( + "http://mochi.test:8888", + origin, + "expected correct origin" + ); + break; + + case "about:srcdoc": + browser.test.assertEq("about:srcdoc", url, "expected correct url"); + browser.test.assertEq( + "http://mochi.test:8888", + origin, + "expected correct origin" + ); + break; + + default: + browser.test.fail(`Unexpected kind: ${kind}`); + } + + browser.test.sendMessage(`done:${kind}`); + }); + + browser.test.sendMessage("ready"); + }, + + files: { + "cs.js"() { + browser.runtime.sendMessage("cs"); + }, + }, + }); + + await extension.startup(); + await extension.awaitMessage("ready"); + + let win = window.open("http://mochi.test:8888/" + PATH); + await extension.awaitMessage("done:top"); + await extension.awaitMessage("done:about:blank"); + await extension.awaitMessage("done:about:srcdoc"); + win.close(); + + await extension.unload(); +}); + +add_task(async function test_tabs_sendMessage_opaqueOrigin() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + content_scripts: [ + { + match_about_blank: true, + // The combination of `matches` and `exclude_matches` below allows us + // to only inject in the top-level about:blank. + matches: ["*://*/*"], + exclude_matches: ["<all_urls>"], + js: ["cs.js"], + }, + ], + }, + + async background() { + let tab; + + browser.runtime.onMessage.addListener(async (msg, { url, origin }) => { + browser.test.assertEq("cs", msg, "expected message from cs.js"); + browser.test.assertEq("about:blank", url, "expected correct url"); + browser.test.assertEq("null", origin, "expected correct origin"); + + await browser.tabs.remove(tab.id); + browser.test.sendMessage("done"); + }); + + tab = await browser.tabs.create({ url: "about:blank" }); + }, + + files: { + "cs.js"() { + browser.runtime.sendMessage("cs"); + }, + }, + }); + + await extension.startup(); + await extension.awaitMessage("done"); + await extension.unload(); +}); + +add_task(async function test_tabs_sendMessage_blob() { + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1724099 + if (!WebExtensionPolicy.useRemoteWebExtensions) { + await SpecialPowers.pushPrefEnv({ + set: [["security.allow_unsafe_parent_loads", true]], + }); + } + + let extension = ExtensionTestUtils.loadExtension({ + async background() { + browser.runtime.onMessage.addListener(async (msg, { url, origin }) => { + browser.test.assertEq("script", msg, "expected message from script.js"); + browser.test.assertTrue( + url.startsWith("blob:moz-extension://"), + "expected correct url" + ); + browser.test.assertEq( + new URL(browser.runtime.getURL("/")).origin, + origin, + "expected correct origin" + ); + + browser.test.sendMessage("done"); + }); + + const blob = new Blob( + [`<script src="${browser.runtime.getURL("script.js")}"><\/script>`], + { type: "text/html" } + ); + const iframe = document.createElement("iframe"); + iframe.src = URL.createObjectURL(blob); + document.body.appendChild(iframe); + }, + + files: { + "script.js"() { + browser.runtime.sendMessage("script"); + }, + }, + }); + + await extension.startup(); + await extension.awaitMessage("done"); + await extension.unload(); + + if (!WebExtensionPolicy.useRemoteWebExtensions) { + await SpecialPowers.popPrefEnv(); + } +}); + </script> </body> </html> diff --git a/toolkit/components/extensions/test/mochitest/test_ext_test.html b/toolkit/components/extensions/test/mochitest/test_ext_test.html index bf68786465..21093c9abf 100644 --- a/toolkit/components/extensions/test/mochitest/test_ext_test.html +++ b/toolkit/components/extensions/test/mochitest/test_ext_test.html @@ -126,7 +126,7 @@ function testScript() { // The WebIDL version of assertDeepEq structurally clones before sending the // params to the main thread. This check verifies that the behavior is - // consistent between the WebIDL and Schemas.jsm-generated API bindings. + // consistent between the WebIDL and Schemas.sys.mjs-generated API bindings. browser.test.assertThrows( () => browser.test.assertDeepEq(obj, obj, "obj with func"), /An unexpected error occurred/, diff --git a/toolkit/components/extensions/test/xpcshell/test_csp_validator.js b/toolkit/components/extensions/test/xpcshell/test_csp_validator.js index 011628f027..0e665b9730 100644 --- a/toolkit/components/extensions/test/xpcshell/test_csp_validator.js +++ b/toolkit/components/extensions/test/xpcshell/test_csp_validator.js @@ -202,8 +202,8 @@ add_task(async function test_csp_validator_extension_pages() { let checkPolicy = (policy, expectedResult) => { info(`Checking policy: ${policy}`); - // While Schemas.jsm uses Ci.nsIAddonContentPolicy.CSP_ALLOW_WASM, we don't - // pass that here because we are only verifying that remote scripts are + // While Schemas.sys.mjs uses Ci.nsIAddonContentPolicy.CSP_ALLOW_WASM, we + // don't pass that here because we are only verifying that remote scripts are // blocked here. let result = cps.validateAddonCSP(policy, 0); equal(result, expectedResult); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_adoption_with_private_field_xrays.js b/toolkit/components/extensions/test/xpcshell/test_ext_adoption_with_private_field_xrays.js index 2d8b02bcd9..76b6644d44 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_adoption_with_private_field_xrays.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_adoption_with_private_field_xrays.js @@ -1,6 +1,6 @@ "use strict"; -// ExtensionContent.jsm needs to know when it's running from xpcshell, +// ExtensionContent.sys.mjs needs to know when it's running from xpcshell, // to use the right timeout for content scripts executed at document_idle. ExtensionTestUtils.mockAppInfo(); const server = createHttpServer(); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_adoption_with_xrays.js b/toolkit/components/extensions/test/xpcshell/test_ext_adoption_with_xrays.js index 9655c157d1..b909223302 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_adoption_with_xrays.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_adoption_with_xrays.js @@ -1,6 +1,6 @@ "use strict"; -// ExtensionContent.jsm needs to know when it's running from xpcshell, +// ExtensionContent.sys.mjs needs to know when it's running from xpcshell, // to use the right timeout for content scripts executed at document_idle. ExtensionTestUtils.mockAppInfo(); const server = createHttpServer(); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_clear_cached_resources.js b/toolkit/components/extensions/test/xpcshell/test_ext_clear_cached_resources.js index 95bef23383..bd05398736 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_clear_cached_resources.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_clear_cached_resources.js @@ -297,7 +297,7 @@ add_task( // This temporary directory is going to be removed from the // cleanup function, but also make it unique as we do for the // other temporary files (e.g. like getTemporaryFile as defined - // in XPInstall.jsm). + // in XPIInstall.sys.mjs). const random = Math.round(Math.random() * 36 ** 3).toString(36); const tmpDirName = `xpcshelltest_unpacked_addons_${random}`; let tmpExtPath = FileUtils.getDir("TmpD", [tmpDirName]); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_dynamic_registration.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_dynamic_registration.js index 0133b5d86c..20f6ece95a 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_dynamic_registration.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_dynamic_registration.js @@ -5,7 +5,7 @@ server.registerDirectory("/data/", do_get_file("data")); const BASE_URL = `http://localhost:${server.identity.primaryPort}/data`; -// ExtensionContent.jsm needs to know when it's running from xpcshell, to use +// ExtensionContent.sys.mjs needs to know when it's running from xpcshell, to use // the right timeout for content scripts executed at document_idle. ExtensionTestUtils.mockAppInfo(); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_triggeringPrincipal.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_triggeringPrincipal.js index 4ebe6df636..ce7f293142 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_triggeringPrincipal.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_triggeringPrincipal.js @@ -30,7 +30,7 @@ Services.prefs.setBoolPref( false ); -// ExtensionContent.jsm needs to know when it's running from xpcshell, +// ExtensionContent.sys.mjs needs to know when it's running from xpcshell, // to use the right timeout for content scripts executed at document_idle. ExtensionTestUtils.mockAppInfo(); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_xorigin_frame.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_xorigin_frame.js index 8a58b2475c..420fa7689c 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_xorigin_frame.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_xorigin_frame.js @@ -5,6 +5,12 @@ const server = createHttpServer({ }); server.registerDirectory("/data/", do_get_file("data")); +// By default, fission.webContentIsolationStrategy=1 (IsolateHighValue). When +// Fission is enabled on Android, the pref value 2 (IsolateHighValue) will be +// used instead. Set to 1 (IsolateEverything) to make sure the subframe in this +// test gets its own process, independently of the default prefs on Android. +Services.prefs.setIntPref("fission.webContentIsolationStrategy", 1); + add_task(async function test_process_switch_cross_origin_frame() { const extension = ExtensionTestUtils.loadExtension({ manifest: { diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_xrays.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_xrays.js index 7b92d5c4b7..30ec8ceced 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_xrays.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_xrays.js @@ -1,6 +1,6 @@ "use strict"; -// ExtensionContent.jsm needs to know when it's running from xpcshell, +// ExtensionContent.sys.mjs needs to know when it's running from xpcshell, // to use the right timeout for content scripts executed at document_idle. ExtensionTestUtils.mockAppInfo(); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contexts_gc.js b/toolkit/components/extensions/test/xpcshell/test_ext_contexts_gc.js index 2a36f51637..b6f955f7ba 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_contexts_gc.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contexts_gc.js @@ -5,7 +5,7 @@ server.registerDirectory("/data/", do_get_file("data")); const BASE_URL = `http://localhost:${server.identity.primaryPort}/data`; -// ExtensionContent.jsm needs to know when it's running from xpcshell, +// ExtensionContent.sys.mjs needs to know when it's running from xpcshell, // to use the right timeout for content scripts executed at document_idle. ExtensionTestUtils.mockAppInfo(); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_dnr_modifyHeaders.js b/toolkit/components/extensions/test/xpcshell/test_ext_dnr_modifyHeaders.js index 4b7bebe188..df9e9d77ef 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_dnr_modifyHeaders.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_dnr_modifyHeaders.js @@ -29,6 +29,7 @@ server.registerPathHandler("/echoheaders", (req, res) => { dropDefaultHeader("accept-language"); dropDefaultHeader("accept-encoding"); dropDefaultHeader("connection"); + dropDefaultHeader("priority"); res.write(JSON.stringify(headers)); }); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_file_access.js b/toolkit/components/extensions/test/xpcshell/test_ext_file_access.js index c05188cd38..bea1e76c0f 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_file_access.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_file_access.js @@ -4,7 +4,7 @@ const FILE_DUMMY_URL = Services.io.newFileURI( do_get_file("data/dummy_page.html") ).spec; -// ExtensionContent.jsm needs to know when it's running from xpcshell, +// ExtensionContent.sys.mjs needs to know when it's running from xpcshell, // to use the right timeout for content scripts executed at document_idle. ExtensionTestUtils.mockAppInfo(); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_i18n.js b/toolkit/components/extensions/test/xpcshell/test_ext_i18n.js index 1e46e19527..69ba326f75 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_i18n.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_i18n.js @@ -4,7 +4,7 @@ const { Preferences } = ChromeUtils.importESModule( "resource://gre/modules/Preferences.sys.mjs" ); -// ExtensionContent.jsm needs to know when it's running from xpcshell, +// ExtensionContent.sys.mjs needs to know when it's running from xpcshell, // to use the right timeout for content scripts executed at document_idle. ExtensionTestUtils.mockAppInfo(); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_ipcBlob.js b/toolkit/components/extensions/test/xpcshell/test_ext_ipcBlob.js index dd90d9bbc8..2c5b3378fd 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_ipcBlob.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_ipcBlob.js @@ -5,7 +5,7 @@ server.registerDirectory("/data/", do_get_file("data")); const BASE_URL = `http://localhost:${server.identity.primaryPort}/data`; -// ExtensionContent.jsm needs to know when it's running from xpcshell, +// ExtensionContent.sys.mjs needs to know when it's running from xpcshell, // to use the right timeout for content scripts executed at document_idle. ExtensionTestUtils.mockAppInfo(); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js b/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js index 948b75978a..ae6ce3d27e 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js @@ -12,8 +12,8 @@ const { ExtensionPermissions } = ChromeUtils.importESModule( Services.prefs.setBoolPref("extensions.manifestV3.enabled", true); -// ExtensionParent.jsm is being imported lazily because when it is imported Services.appinfo will be -// retrieved and cached (as a side-effect of Schemas.jsm being imported), and so Services.appinfo +// ExtensionParent.sys.mjs is being imported lazily because when it is imported Services.appinfo will be +// retrieved and cached (as a side-effect of Schemas.sys.mjs being imported), and so Services.appinfo // will not be returning the version set by AddonTestUtils.createAppInfo and this test will // fail on non-nightly builds (because the cached appinfo.version will be undefined and // AddonManager startup will fail). @@ -704,6 +704,7 @@ const GRANTED_WITHOUT_USER_PROMPT = [ "theme", "unlimitedStorage", "webRequest", + "webRequestAuthProvider", "webRequestBlocking", "webRequestFilterResponse", "webRequestFilterResponse.serviceWorkerScript", @@ -1058,3 +1059,47 @@ add_task(async function test_internal_permissions() { await extension.unload(); }); + +add_task(function test_normalizeOptional() { + const optional1 = { + origins: ["*://site.com/", "*://*.domain.com/"], + permissions: ["downloads", "tabs"], + }; + + function normalize(perms, optional) { + perms = { origins: [], permissions: [], ...perms }; + optional = { origins: [], permissions: [], ...optional }; + return ExtensionPermissions.normalizeOptional(perms, optional); + } + + normalize({ origins: ["http://site.com/"] }, optional1); + normalize({ origins: ["https://site.com/"] }, optional1); + normalize({ origins: ["*://blah.domain.com/"] }, optional1); + normalize({ permissions: ["downloads", "tabs"] }, optional1); + + Assert.throws( + () => normalize({ origins: ["http://www.example.com/"] }, optional1), + /was not declared in the manifest/ + ); + Assert.throws( + () => normalize({ permissions: ["proxy"] }, optional1), + /was not declared in optional_permissions/ + ); + + const optional2 = { + origins: ["<all_urls>", "*://*/*"], + permissions: ["idle", "clipboardWrite"], + }; + + normalize({ origins: ["http://site.com/"] }, optional2); + normalize({ origins: ["https://site.com/"] }, optional2); + normalize({ origins: ["*://blah.domain.com/"] }, optional2); + normalize({ permissions: ["idle", "clipboardWrite"] }, optional2); + + let perms = normalize({ origins: ["<all_urls>"] }, optional2); + equal( + perms.origins.sort().join(), + optional2.origins.sort().join(), + `Expect both "all sites" permissions` + ); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_permissions_api.js b/toolkit/components/extensions/test/xpcshell/test_ext_permissions_api.js index 0211787fee..97db78cf81 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_permissions_api.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_permissions_api.js @@ -58,6 +58,7 @@ add_task(async function setup() { "search", "tabHide", "tabs", + "webRequestAuthProvider", "webRequestBlocking", "webRequestFilterResponse", "webRequestFilterResponse.serviceWorkerScript", diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_proxy_onauthrequired.js b/toolkit/components/extensions/test/xpcshell/test_ext_proxy_onauthrequired.js index 4b1128a349..719fd219fe 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_proxy_onauthrequired.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_proxy_onauthrequired.js @@ -251,7 +251,7 @@ add_task(async function test_webRequest_auth_proxy_system() { () => { browser.test.sendMessage("onAuthRequired"); // cancel is silently ignored, if it were not (e.g someone messes up in - // WebRequest.jsm and allows cancel) this test would fail. + // WebRequest.sys.mjs and allows cancel) this test would fail. return { cancel: true, authCredentials: { username: "puser", password: "ppass" }, diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_schemas_manifest_permissions.js b/toolkit/components/extensions/test/xpcshell/test_ext_schemas_manifest_permissions.js index 85e645e67b..e5fc746e60 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_schemas_manifest_permissions.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_schemas_manifest_permissions.js @@ -86,7 +86,7 @@ add_task(async function () { a_manifest_property: {}, }, background() { - // Test hasPermission method implemented in ExtensionChild.jsm. + // Test hasPermission method implemented in ExtensionChild.sys.mjs. browser.test.assertTrue( "testManifestPermission" in browser, "The API namespace is defined as expected" @@ -105,7 +105,7 @@ add_task(async function () { "test-extension-manifest-without-nested-prop" ); - // Test hasPermission method implemented in Extension.jsm. + // Test hasPermission method implemented in Extension.sys.mjs. equal( extension.extension.hasPermission("manifest:a_manifest_property"), true, @@ -129,7 +129,7 @@ add_task(async function () { }, }, background() { - // Test hasPermission method implemented in ExtensionChild.jsm. + // Test hasPermission method implemented in ExtensionChild.sys.mjs. browser.test.assertTrue( "testManifestPermission" in browser, "The API namespace is defined as expected" @@ -146,7 +146,7 @@ add_task(async function () { async extension => { await extension.awaitFinish("test-extension-manifest-with-nested-prop"); - // Test hasPermission method implemented in Extension.jsm. + // Test hasPermission method implemented in Extension.sys.mjs. equal( extension.extension.hasPermission("manifest:a_manifest_property"), true, diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_schemas_privileged.js b/toolkit/components/extensions/test/xpcshell/test_ext_schemas_privileged.js index 14cbca7443..496607968e 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_schemas_privileged.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_schemas_privileged.js @@ -144,7 +144,7 @@ add_task( } ); -// Test that Extension.jsm and schema correctly match. +// Test that Extension.sys.mjs and schema correctly match. add_task(function test_privileged_permissions_match() { const { PRIVILEGED_PERMS } = ChromeUtils.importESModule( "resource://gre/modules/Extension.sys.mjs" diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_scripting_contentScripts.js b/toolkit/components/extensions/test/xpcshell/test_ext_scripting_contentScripts.js index a06f34a1b4..c47cdcad4d 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_scripting_contentScripts.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_scripting_contentScripts.js @@ -5,7 +5,7 @@ server.registerDirectory("/data/", do_get_file("data")); const BASE_URL = `http://localhost:${server.identity.primaryPort}/data`; -// ExtensionContent.jsm needs to know when it's running from xpcshell, to use +// ExtensionContent.sys.mjs needs to know when it's running from xpcshell, to use // the right timeout for content scripts executed at document_idle. ExtensionTestUtils.mockAppInfo(); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_scripting_contentScripts_css.js b/toolkit/components/extensions/test/xpcshell/test_ext_scripting_contentScripts_css.js index 21190d2d59..a3c79b1cc0 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_scripting_contentScripts_css.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_scripting_contentScripts_css.js @@ -5,7 +5,7 @@ server.registerDirectory("/data/", do_get_file("data")); const BASE_URL = `http://localhost:${server.identity.primaryPort}/data`; -// ExtensionContent.jsm needs to know when it's running from xpcshell, to use +// ExtensionContent.sys.mjs needs to know when it's running from xpcshell, to use // the right timeout for content scripts executed at document_idle. ExtensionTestUtils.mockAppInfo(); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_scripting_contentScripts_file.js b/toolkit/components/extensions/test/xpcshell/test_ext_scripting_contentScripts_file.js index 3c806439ce..02ef9b0fa5 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_scripting_contentScripts_file.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_scripting_contentScripts_file.js @@ -4,7 +4,7 @@ const FILE_DUMMY_URL = Services.io.newFileURI( do_get_file("data/dummy_page.html") ).spec; -// ExtensionContent.jsm needs to know when it's running from xpcshell, to use +// ExtensionContent.sys.mjs needs to know when it's running from xpcshell, to use // the right timeout for content scripts executed at document_idle. ExtensionTestUtils.mockAppInfo(); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_scripting_updateContentScripts.js b/toolkit/components/extensions/test/xpcshell/test_ext_scripting_updateContentScripts.js index 9d3bf1576c..85c2376715 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_scripting_updateContentScripts.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_scripting_updateContentScripts.js @@ -5,7 +5,7 @@ server.registerDirectory("/data/", do_get_file("data")); const BASE_URL = `http://localhost:${server.identity.primaryPort}/data`; -// ExtensionContent.jsm needs to know when it's running from xpcshell, to use +// ExtensionContent.sys.mjs needs to know when it's running from xpcshell, to use // the right timeout for content scripts executed at document_idle. ExtensionTestUtils.mockAppInfo(); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_shadowdom.js b/toolkit/components/extensions/test/xpcshell/test_ext_shadowdom.js index 626d8de22d..e8316aa652 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_shadowdom.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_shadowdom.js @@ -1,6 +1,6 @@ "use strict"; -// ExtensionContent.jsm needs to know when it's running from xpcshell, +// ExtensionContent.sys.mjs needs to know when it's running from xpcshell, // to use the right timeout for content scripts executed at document_idle. ExtensionTestUtils.mockAppInfo(); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_userScripts.js b/toolkit/components/extensions/test/xpcshell/test_ext_userScripts.js index 3108c7b9b4..57ca08aca3 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_userScripts.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_userScripts.js @@ -200,7 +200,7 @@ add_task(async function test_userScripts_no_webext_apis() { let script = await browser.userScripts.register(userScriptOptions); // Unregister and then register the same js code again, to verify that the last registered - // userScript doesn't get assigned a revoked blob url (otherwise Extensioncontent.jsm + // userScript doesn't get assigned a revoked blob url (otherwise Extensioncontent.sys.mjs // ScriptCache raises an error because it fails to compile the revoked blob url and the user // script will never be loaded). script.unregister(); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_auth.js b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_auth.js index 578e69ebdf..ef07817d00 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_auth.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_auth.js @@ -63,7 +63,7 @@ server.registerPathHandler("/authenticate.sjs", (request, response) => { } }); -function getExtension(bgConfig) { +function getExtension(bgConfig, permissions = ["webRequestBlocking"]) { function background(config) { let path = config.path; browser.webRequest.onBeforeRequest.addListener( @@ -125,18 +125,18 @@ function getExtension(bgConfig) { return ExtensionTestUtils.loadExtension({ manifest: { - permissions: ["webRequest", "webRequestBlocking", bgConfig.path], + permissions: [bgConfig.path, "webRequest", ...permissions], }, background: `(${background})(${JSON.stringify(bgConfig)})`, }); } -add_task(async function test_webRequest_auth() { +async function test_webRequest_auth(permissions) { let config = { path: `${BASE_URL}/*`, realm: `webRequest_auth${Math.random()}`, onBeforeRequest: { - extra: ["blocking"], + extra: permissions.includes("webRequestBlocking") ? ["blocking"] : [], }, onAuthRequired: { extra: ["blocking"], @@ -149,7 +149,7 @@ add_task(async function test_webRequest_auth() { }, }; - let extension = getExtension(config); + let extension = getExtension(config, permissions); await extension.startup(); let requestUrl = `${BASE_URL}/authenticate.sjs?realm=${config.realm}`; @@ -174,6 +174,14 @@ add_task(async function test_webRequest_auth() { await contentPage.close(); await extension.unload(); +} + +add_task(async function test_webRequest_auth_with_webRequestBlocking() { + await test_webRequest_auth(["webRequestBlocking"]); +}); + +add_task(async function test_webRequest_auth_with_webRequestAuthProvider() { + await test_webRequest_auth(["webRequestAuthProvider"]); }); add_task(async function test_webRequest_auth_cancelled() { diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_permission.js b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_permission.js index 17c22e156d..97e44498c1 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_permission.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_permission.js @@ -18,6 +18,18 @@ function sendMessage(page, msg, data) { return MessageChannel.sendMessage(page.browser.messageManager, msg, data); } +add_setup(() => { + // Make sure to invalidate WebExtensions API schemas that may be cached + // in the StartupCache when this test runs with conditioned-profiles. + // + // These tests are subject to be hitting failures consistently on + // landing API schema changes to the WebExtensions API permissions. + // or other API schema properties that are explicitly covered by + // this tests (e.g. errors expected to be emitted by postprocess + // helper functions). + Services.obs.notifyObservers(null, "startupcache-invalidate"); +}); + add_task(async function test_permissions() { function background() { browser.webRequest.onBeforeRequest.addListener( @@ -113,11 +125,14 @@ add_task(async function test_permissions() { await contentPage.close(); }); -add_task(async function test_no_webRequestBlocking_error() { +add_task(async function test_missing_required_perm_for_blocking_error() { function background() { const expectedError = "Using webRequest.addListener with the blocking option " + "requires the 'webRequestBlocking' permission."; + const expectedErrorOnAuthRequired = + "Using webRequest.onAuthRequired.addListener with the blocking option " + + "requires either the 'webRequestBlocking' or 'webRequestAuthProvider' permission."; const blockingEvents = [ "onBeforeRequest", @@ -135,7 +150,9 @@ add_task(async function test_no_webRequestBlocking_error() { ["blocking"] ); }, - expectedError, + eventName === "onAuthRequired" + ? expectedErrorOnAuthRequired + : expectedError, `Got the expected exception for a blocking webRequest.${eventName} listener` ); } diff --git a/toolkit/components/extensions/test/xpcshell/test_load_all_api_modules.js b/toolkit/components/extensions/test/xpcshell/test_load_all_api_modules.js index 32299fb04e..3d28971352 100644 --- a/toolkit/components/extensions/test/xpcshell/test_load_all_api_modules.js +++ b/toolkit/components/extensions/test/xpcshell/test_load_all_api_modules.js @@ -18,7 +18,7 @@ let schemaURLs = new Set(); schemaURLs.add("chrome://extensions/content/schemas/experiments.json"); // Helper class used to load the API modules similarly to the apiManager -// defined in ExtensionParent.jsm. +// defined in ExtensionParent.sys.mjs. class FakeAPIManager extends ExtensionCommon.SchemaAPIManager { constructor(processType = "main") { super(processType, Schemas); @@ -102,7 +102,8 @@ class FakeAPIManager extends ExtensionCommon.SchemaAPIManager { } // Specialized helper class used to test loading "child process" modules (similarly to the -// SchemaAPIManagers sub-classes defined in ExtensionPageChild.jsm and ExtensionContent.jsm). +// SchemaAPIManagers sub-classes defined in ExtensionPageChild.sys.mjs and +// ExtensionContent.sys.mjs). class FakeChildProcessAPIManager extends FakeAPIManager { constructor({ processType, categoryScripts }) { super(processType, Schemas); diff --git a/toolkit/components/extensions/test/xpcshell/test_native_manifests.js b/toolkit/components/extensions/test/xpcshell/test_native_manifests.js index 1f5bc88740..d4f3ae7243 100644 --- a/toolkit/components/extensions/test/xpcshell/test_native_manifests.js +++ b/toolkit/components/extensions/test/xpcshell/test_native_manifests.js @@ -248,7 +248,7 @@ add_task( "lookupApplication returns the correct path with platform-native slash" ); // Side note: manifest.path does not contain a platform-native path, - // but it is normalized when used in NativeMessaging.jsm. + // but it is normalized when used in NativeMessaging.sys.mjs. deepEqual( result.manifest, manifest, diff --git a/toolkit/components/extensions/test/xpcshell/test_webRequest_ancestors.js b/toolkit/components/extensions/test/xpcshell/test_webRequest_ancestors.js index 509f821828..abbb814ac7 100644 --- a/toolkit/components/extensions/test/xpcshell/test_webRequest_ancestors.js +++ b/toolkit/components/extensions/test/xpcshell/test_webRequest_ancestors.js @@ -11,7 +11,7 @@ const server = createHttpServer({ hosts: ["example.com"] }); server.registerDirectory("/data/", do_get_file("data")); add_task(async function setup() { - // When WebRequest.jsm is used directly instead of through ext-webRequest.js, + // When WebRequest.sys.mjs is used directly instead of through ext-webRequest.js, // ExtensionParent.apiManager is not automatically initialized. Do it here. await ExtensionParent.apiManager.lazyInit(); }); diff --git a/toolkit/components/extensions/test/xpcshell/test_webRequest_cookies.js b/toolkit/components/extensions/test/xpcshell/test_webRequest_cookies.js index 53ed465786..f4c8d75690 100644 --- a/toolkit/components/extensions/test/xpcshell/test_webRequest_cookies.js +++ b/toolkit/components/extensions/test/xpcshell/test_webRequest_cookies.js @@ -75,7 +75,7 @@ function onResponseStarted(details) { } add_task(async function setup() { - // When WebRequest.jsm is used directly instead of through ext-webRequest.js, + // When WebRequest.sys.mjs is used directly instead of through ext-webRequest.js, // ExtensionParent.apiManager is not automatically initialized. Do it here. await ExtensionParent.apiManager.lazyInit(); }); diff --git a/toolkit/components/extensions/test/xpcshell/test_webRequest_filtering.js b/toolkit/components/extensions/test/xpcshell/test_webRequest_filtering.js index 46a72a5926..86b2410e33 100644 --- a/toolkit/components/extensions/test/xpcshell/test_webRequest_filtering.js +++ b/toolkit/components/extensions/test/xpcshell/test_webRequest_filtering.js @@ -89,7 +89,7 @@ add_task(async function setup() { // Disable rcwn to make cache behavior deterministic. Services.prefs.setBoolPref("network.http.rcwn.enabled", false); - // When WebRequest.jsm is used directly instead of through ext-webRequest.js, + // When WebRequest.sys.mjs is used directly instead of through ext-webRequest.js, // ExtensionParent.apiManager is not automatically initialized. Do it here. await ExtensionParent.apiManager.lazyInit(); }); diff --git a/toolkit/components/extensions/test/xpcshell/xpcshell-common.toml b/toolkit/components/extensions/test/xpcshell/xpcshell-common.toml index 7cf8d79409..6d47012eca 100644 --- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.toml +++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.toml @@ -533,7 +533,7 @@ skip-if = ["os == 'android'"] # Bug 1564871 ["test_ext_storage_sanitizer.js"] skip-if = [ "appname == 'thunderbird'", - "os == 'android'", # Sanitizer.jsm is not in toolkit. + "os == 'android'", # Sanitizer.sys.mjs is not in toolkit. ] ["test_ext_storage_session.js"] @@ -587,7 +587,9 @@ skip-if = [ ["test_ext_wasm.js"] ["test_ext_webRequest_auth.js"] -skip-if = ["os == 'android' && debug"] +skip-if = [ + "os == 'android' && debug", +] ["test_ext_webRequest_cached.js"] skip-if = ["os == 'android'"] # Bug 1573511 @@ -616,7 +618,9 @@ skip-if = ["os == 'android' && debug"] skip-if = ["tsan"] # Bug 1683730 ["test_ext_webRequest_permission.js"] -skip-if = ["os == 'android' && debug"] +skip-if = [ + "os == 'android' && debug", +] ["test_ext_webRequest_redirectProperty.js"] skip-if = ["os == 'android' && processor == 'x86_64'"] # Bug 1683253 diff --git a/toolkit/components/extensions/tsconfig.json b/toolkit/components/extensions/tsconfig.json index d569ba9eca..992018f4a8 100644 --- a/toolkit/components/extensions/tsconfig.json +++ b/toolkit/components/extensions/tsconfig.json @@ -1,15 +1,11 @@ { - "include": ["*.mjs", "types/globals.ts"], - "exclude": [], + "include": ["*.mjs", "types/*.ts"], "compilerOptions": { "checkJs": true, - "target": "ESNEXT", - - "declaration": true, - "outDir": "./types", - "typeRoots": [], "noEmit": true, + "target": "es2022", + "types": ["gecko"], // prettier-ignore "paths": { @@ -39,11 +35,12 @@ "resource://gre/modules/Schemas.sys.mjs": ["./Schemas.sys.mjs"], "resource://gre/modules/WebNavigationFrames.sys.mjs": ["./WebNavigationFrames.sys.mjs"], "resource://gre/modules/WebRequest.sys.mjs": ["./webrequest/WebRequest.sys.mjs"], + "resource://testing-common/ExtensionTestCommon.sys.mjs": ["./ExtensionTestCommon.sys.mjs"], // External. "resource://gre/modules/addons/crypto-utils.sys.mjs": ["../../mozapps/extensions/internal/crypto-utils.sys.mjs"], "resource://gre/modules/XPCOMUtils.sys.mjs": ["../../../js/xpconnect/loader/XPCOMUtils.sys.mjs"], - "resource://testing-common/ExtensionTestCommon.sys.mjs": ["./ExtensionTestCommon.sys.mjs"], + "resource://devtools/server/actors/descriptors/webextension.js": ["./types/globals.ts"], // Types for external modules which need fixing, but we don't wanna touch. "resource://testing-common/XPCShellContentUtils.sys.mjs": ["./types/XPCShellContentUtils.sys.d.mts"], diff --git a/toolkit/components/extensions/types/README.md b/toolkit/components/extensions/types/README.md index ebd01dec60..1d4588389c 100644 --- a/toolkit/components/extensions/types/README.md +++ b/toolkit/components/extensions/types/README.md @@ -21,11 +21,11 @@ viability and benefits of doing this for a "typical" component. ## How to use and expectations -Use [npm or yarn to install][download] TypeScript. -Then run `tsc` in the extensions directory to check types: +Mach now comes with a `ts` command for type checking code using the built-in +gecko typelibs: ``` -mozilla-central/toolkit/components/extensions $ tsc +mozilla-central $ ./mach ts check toolkit/components/extensions/ ``` You can also use an editor which supports the [language server][langserv]. @@ -64,6 +64,9 @@ These fall under 5 main categories: 5) Don't re-use local variables unnecessarily with different types. * (general good practice, local variables are "free") + 6) Use `export` on individual classes instead of grouping into "namespaces". + * (idiomatic/ergonomic/modern JS, grouping was a leftover from JSMs) + ### @ts-ignore recommendations *Don't* use `@ts-ignore` for class fields and function or method signatures. @@ -75,7 +78,7 @@ These fall under 5 main categories: parts of the codebase. -[handbook]: https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html +[handbook]: https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html [jsdoc]: https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html diff --git a/toolkit/components/extensions/types/ext-tabs-base.d.ts b/toolkit/components/extensions/types/ext-tabs-base.d.ts new file mode 100644 index 0000000000..c889fd6620 --- /dev/null +++ b/toolkit/components/extensions/types/ext-tabs-base.d.ts @@ -0,0 +1,1447 @@ +// @ts-nocheck + +declare namespace tabs_base { + +declare function getUserContextIdForCookieStoreId(extension: any, cookieStoreId: any, isPrivateBrowsing: any): any; +declare var DefaultMap: any; +declare var DefaultWeakMap: any; +declare var ExtensionError: any; +declare var parseMatchPatterns: any; +declare var defineLazyGetter: any; +/** + * The platform-specific type of native tab objects, which are wrapped by + * TabBase instances. + * + * @typedef {object | XULElement} NativeTab + */ +/** + * @typedef {object} MutedInfo + * @property {boolean} muted + * True if the tab is currently muted, false otherwise. + * @property {string} [reason] + * The reason the tab is muted. Either "user", if the tab was muted by a + * user, or "extension", if it was muted by an extension. + * @property {string} [extensionId] + * If the tab was muted by an extension, contains the internal ID of that + * extension. + */ +/** + * A platform-independent base class for extension-specific wrappers around + * native tab objects. + * + * @param {Extension} extension + * The extension object for which this wrapper is being created. Used to + * determine permissions for access to certain properties and + * functionality. + * @param {NativeTab} nativeTab + * The native tab object which is being wrapped. The type of this object + * varies by platform. + * @param {integer} id + * The numeric ID of this tab object. This ID should be the same for + * every extension, and for the lifetime of the tab. + */ +declare class TabBase { + constructor(extension: any, nativeTab: any, id: any); + extension: any; + tabManager: any; + id: any; + nativeTab: any; + activeTabWindowID: any; + /** + * Capture the visible area of this tab, and return the result as a data: URI. + * + * @param {BaseContext} context + * The extension context for which to perform the capture. + * @param {number} zoom + * The current zoom for the page. + * @param {object} [options] + * The options with which to perform the capture. + * @param {string} [options.format = "png"] + * The image format in which to encode the captured data. May be one of + * "png" or "jpeg". + * @param {integer} [options.quality = 92] + * The quality at which to encode the captured image data, ranging from + * 0 to 100. Has no effect for the "png" format. + * @param {DOMRectInit} [options.rect] + * Area of the document to render, in CSS pixels, relative to the page. + * If null, the currently visible viewport is rendered. + * @param {number} [options.scale] + * The scale to render at, defaults to devicePixelRatio. + * @returns {Promise<string>} + */ + capture(context: BaseContext, zoom: number, options?: { + format?: string; + quality?: integer; + rect?: DOMRectInit; + scale?: number; + }): Promise<string>; + /** + * @property {integer | null} innerWindowID + * The last known innerWindowID loaded into this tab's docShell. This + * property must remain in sync with the last known values of + * properties such as `url` and `title`. Any operations on the content + * of an out-of-process tab will automatically fail if the + * innerWindowID of the tab when the message is received does not match + * the value of this property when the message was sent. + * @readonly + */ + readonly get innerWindowID(): any; + /** + * @property {boolean} hasTabPermission + * Returns true if the extension has permission to access restricted + * properties of this tab, such as `url`, `title`, and `favIconUrl`. + * @readonly + */ + readonly get hasTabPermission(): any; + /** + * @property {boolean} hasActiveTabPermission + * Returns true if the extension has the "activeTab" permission, and + * has been granted access to this tab due to a user executing an + * extension action. + * + * If true, the extension may load scripts and CSS into this tab, and + * access restricted properties, such as its `url`. + * @readonly + */ + readonly get hasActiveTabPermission(): boolean; + /** + * @property {boolean} matchesHostPermission + * Returns true if the extensions host permissions match the current tab url. + * @readonly + */ + readonly get matchesHostPermission(): any; + /** + * @property {boolean} incognito + * Returns true if this is a private browsing tab, false otherwise. + * @readonly + */ + readonly get _incognito(): any; + /** + * @property {string} _url + * Returns the current URL of this tab. Does not do any permission + * checks. + * @readonly + */ + readonly get _url(): any; + /** + * @property {string | null} url + * Returns the current URL of this tab if the extension has permission + * to read it, or null otherwise. + * @readonly + */ + readonly get url(): any; + /** + * @property {nsIURI} _uri + * Returns the current URI of this tab. + * @readonly + */ + readonly get _uri(): any; + /** + * @property {string} _title + * Returns the current title of this tab. Does not do any permission + * checks. + * @readonly + */ + readonly get _title(): any; + /** + * @property {nsIURI | null} title + * Returns the current title of this tab if the extension has permission + * to read it, or null otherwise. + * @readonly + */ + readonly get title(): any; + /** + * @property {string} _favIconUrl + * Returns the current favicon URL of this tab. Does not do any permission + * checks. + * @readonly + * @abstract + */ + readonly get _favIconUrl(): void; + /** + * @property {nsIURI | null} faviconUrl + * Returns the current faviron URL of this tab if the extension has permission + * to read it, or null otherwise. + * @readonly + */ + readonly get favIconUrl(): void; + /** + * @property {integer} lastAccessed + * Returns the last time the tab was accessed as the number of + * milliseconds since epoch. + * @readonly + * @abstract + */ + readonly get lastAccessed(): void; + /** + * @property {boolean} audible + * Returns true if the tab is currently playing audio, false otherwise. + * @readonly + * @abstract + */ + readonly get audible(): void; + /** + * @property {boolean} autoDiscardable + * Returns true if the tab can be discarded on memory pressure, false otherwise. + * @readonly + * @abstract + */ + readonly get autoDiscardable(): void; + /** + * @property {XULElement} browser + * Returns the XUL browser for the given tab. + * @readonly + * @abstract + */ + readonly get browser(): XULBrowserElement; + /** + * @property {BrowsingContext} browsingContext + * Returns the BrowsingContext for the given tab. + * @readonly + */ + readonly get browsingContext(): any; + /** + * @property {FrameLoader} frameLoader + * Returns the frameloader for the given tab. + * @readonly + */ + readonly get frameLoader(): void; + /** + * @property {string} cookieStoreId + * Returns the cookie store identifier for the given tab. + * @readonly + * @abstract + */ + readonly get cookieStoreId(): void; + /** + * @property {integer} openerTabId + * Returns the ID of the tab which opened this one. + * @readonly + */ + readonly get openerTabId(): any; + /** + * @property {integer} discarded + * Returns true if the tab is discarded. + * @readonly + * @abstract + */ + readonly get discarded(): void; + /** + * @property {integer} height + * Returns the pixel height of the visible area of the tab. + * @readonly + * @abstract + */ + readonly get height(): void; + /** + * @property {integer} hidden + * Returns true if the tab is hidden. + * @readonly + * @abstract + */ + readonly get hidden(): void; + /** + * @property {integer} index + * Returns the index of the tab in its window's tab list. + * @readonly + * @abstract + */ + readonly get index(): void; + /** + * @property {MutedInfo} mutedInfo + * Returns information about the tab's current audio muting status. + * @readonly + * @abstract + */ + readonly get mutedInfo(): void; + /** + * @property {SharingState} sharingState + * Returns object with tab sharingState. + * @readonly + * @abstract + */ + readonly get sharingState(): void; + /** + * @property {boolean} pinned + * Returns true if the tab is pinned, false otherwise. + * @readonly + * @abstract + */ + readonly get pinned(): void; + /** + * @property {boolean} active + * Returns true if the tab is the currently-selected tab, false + * otherwise. + * @readonly + * @abstract + */ + readonly get active(): void; + /** + * @property {boolean} highlighted + * Returns true if the tab is highlighted. + * @readonly + * @abstract + */ + readonly get highlighted(): void; + /** + * @property {string} status + * Returns the current loading status of the tab. May be either + * "loading" or "complete". + * @readonly + * @abstract + */ + readonly get status(): void; + /** + * @property {integer} height + * Returns the pixel height of the visible area of the tab. + * @readonly + * @abstract + */ + readonly get width(): void; + /** + * @property {DOMWindow} window + * Returns the browser window to which the tab belongs. + * @readonly + * @abstract + */ + readonly get window(): void; + /** + * @property {integer} window + * Returns the numeric ID of the browser window to which the tab belongs. + * @readonly + * @abstract + */ + readonly get windowId(): void; + /** + * @property {boolean} attention + * Returns true if the tab is drawing attention. + * @readonly + * @abstract + */ + readonly get attention(): void; + /** + * @property {boolean} isArticle + * Returns true if the document in the tab can be rendered in reader + * mode. + * @readonly + * @abstract + */ + readonly get isArticle(): void; + /** + * @property {boolean} isInReaderMode + * Returns true if the document in the tab is being rendered in reader + * mode. + * @readonly + * @abstract + */ + readonly get isInReaderMode(): void; + /** + * @property {integer} successorTabId + * @readonly + * @abstract + */ + readonly get successorTabId(): void; + /** + * Returns true if this tab matches the the given query info object. Omitted + * or null have no effect on the match. + * + * @param {object} queryInfo + * The query info against which to match. + * @param {boolean} [queryInfo.active] + * Matches against the exact value of the tab's `active` attribute. + * @param {boolean} [queryInfo.audible] + * Matches against the exact value of the tab's `audible` attribute. + * @param {boolean} [queryInfo.autoDiscardable] + * Matches against the exact value of the tab's `autoDiscardable` attribute. + * @param {string} [queryInfo.cookieStoreId] + * Matches against the exact value of the tab's `cookieStoreId` attribute. + * @param {boolean} [queryInfo.discarded] + * Matches against the exact value of the tab's `discarded` attribute. + * @param {boolean} [queryInfo.hidden] + * Matches against the exact value of the tab's `hidden` attribute. + * @param {boolean} [queryInfo.highlighted] + * Matches against the exact value of the tab's `highlighted` attribute. + * @param {integer} [queryInfo.index] + * Matches against the exact value of the tab's `index` attribute. + * @param {boolean} [queryInfo.muted] + * Matches against the exact value of the tab's `mutedInfo.muted` attribute. + * @param {boolean} [queryInfo.pinned] + * Matches against the exact value of the tab's `pinned` attribute. + * @param {string} [queryInfo.status] + * Matches against the exact value of the tab's `status` attribute. + * @param {string} [queryInfo.title] + * Matches against the exact value of the tab's `title` attribute. + * @param {string|boolean } [queryInfo.screen] + * Matches against the exact value of the tab's `sharingState.screen` attribute, or use true to match any screen sharing tab. + * @param {boolean} [queryInfo.camera] + * Matches against the exact value of the tab's `sharingState.camera` attribute. + * @param {boolean} [queryInfo.microphone] + * Matches against the exact value of the tab's `sharingState.microphone` attribute. + * + * Note: Per specification, this should perform a pattern match, rather + * than an exact value match, and will do so in the future. + * @param {MatchPattern} [queryInfo.url] + * Requires the tab's URL to match the given MatchPattern object. + * + * @returns {boolean} + * True if the tab matches the query. + */ + matches(queryInfo: { + active?: boolean; + audible?: boolean; + autoDiscardable?: boolean; + cookieStoreId?: string; + discarded?: boolean; + hidden?: boolean; + highlighted?: boolean; + index?: integer; + muted?: boolean; + pinned?: boolean; + status?: string; + title?: string; + screen?: string | boolean; + camera?: boolean; + microphone?: boolean; + url?: MatchPattern; + }): boolean; + /** + * Converts this tab object to a JSON-compatible object containing the values + * of its properties which the extension is permitted to access, in the format + * required to be returned by WebExtension APIs. + * + * @param {object} [fallbackTabSize] + * A geometry data if the lazy geometry data for this tab hasn't been + * initialized yet. + * @returns {object} + */ + convert(fallbackTabSize?: object): object; + /** + * Query each content process hosting subframes of the tab, return results. + * + * @param {string} message + * @param {object} options + * These options are also sent to the message handler in the + * `ExtensionContentChild`. + * @param {number[]} options.frameIds + * When omitted, all frames will be queried. + * @param {boolean} options.returnResultsWithFrameIds + * @returns {Promise[]} + */ + queryContent(message: string, options: { + frameIds: number[]; + returnResultsWithFrameIds: boolean; + }): Promise<any>[]; + /** + * Inserts a script or stylesheet in the given tab, and returns a promise + * which resolves when the operation has completed. + * + * @param {BaseContext} context + * The extension context for which to perform the injection. + * @param {InjectDetails} details + * The InjectDetails object, specifying what to inject, where, and + * when. + * @param {string} kind + * The kind of data being injected. Either "script" or "css". + * @param {string} method + * The name of the method which was called to trigger the injection. + * Used to generate appropriate error messages on failure. + * + * @returns {Promise} + * Resolves to the result of the execution, once it has completed. + * @private + */ + private _execute; + /** + * Executes a script in the tab's content window, and returns a Promise which + * resolves to the result of the evaluation, or rejects to the value of any + * error the injection generates. + * + * @param {BaseContext} context + * The extension context for which to inject the script. + * @param {InjectDetails} details + * The InjectDetails object, specifying what to inject, where, and + * when. + * + * @returns {Promise} + * Resolves to the result of the evaluation of the given script, once + * it has completed, or rejects with any error the evaluation + * generates. + */ + executeScript(context: BaseContext, details: InjectDetails): Promise<any>; + /** + * Injects CSS into the tab's content window, and returns a Promise which + * resolves when the injection is complete. + * + * @param {BaseContext} context + * The extension context for which to inject the script. + * @param {InjectDetails} details + * The InjectDetails object, specifying what to inject, and where. + * + * @returns {Promise} + * Resolves when the injection has completed. + */ + insertCSS(context: BaseContext, details: InjectDetails): Promise<any>; + /** + * Removes CSS which was previously into the tab's content window via + * `insertCSS`, and returns a Promise which resolves when the operation is + * complete. + * + * @param {BaseContext} context + * The extension context for which to remove the CSS. + * @param {InjectDetails} details + * The InjectDetails object, specifying what to remove, and from where. + * + * @returns {Promise} + * Resolves when the operation has completed. + */ + removeCSS(context: BaseContext, details: InjectDetails): Promise<any>; +} +declare const WINDOW_ID_NONE: -1; +declare const WINDOW_ID_CURRENT: -2; +/** + * A platform-independent base class for extension-specific wrappers around + * native browser windows + * + * @param {Extension} extension + * The extension object for which this wrapper is being created. + * @param {DOMWindow} window + * The browser DOM window which is being wrapped. + * @param {integer} id + * The numeric ID of this DOM window object. This ID should be the same for + * every extension, and for the lifetime of the window. + */ +declare class WindowBase { + /** + * Returns the window state of the given window. + * + * @param {DOMWindow} window + * The window for which to return a state. + * + * @returns {string} + * The window's state. One of "normal", "minimized", "maximized", + * "fullscreen", or "docked". + * @static + * @abstract + */ + static getState(window: DOMWindow): string; + constructor(extension: any, window: any, id: any); + extension: any; + window: any; + id: any; + /** + * @property {nsIAppWindow} appWindow + * The nsIAppWindow object for this browser window. + * @readonly + */ + readonly get appWindow(): any; + /** + * Returns true if this window is the current window for the given extension + * context, false otherwise. + * + * @param {BaseContext} context + * The extension context for which to perform the check. + * + * @returns {boolean} + */ + isCurrentFor(context: BaseContext): boolean; + /** + * @property {string} type + * The type of the window, as defined by the WebExtension API. May be + * either "normal" or "popup". + * @readonly + */ + readonly get type(): "popup" | "normal"; + /** + * Converts this window object to a JSON-compatible object which may be + * returned to an extension, in the format required to be returned by + * WebExtension APIs. + * + * @param {object} [getInfo] + * An optional object, the properties of which determine what data is + * available on the result object. + * @param {boolean} [getInfo.populate] + * Of true, the result object will contain a `tabs` property, + * containing an array of converted Tab objects, one for each tab in + * the window. + * + * @returns {object} + */ + convert(getInfo?: { + populate?: boolean; + }): object; + /** + * Returns true if this window matches the the given query info object. Omitted + * or null have no effect on the match. + * + * @param {object} queryInfo + * The query info against which to match. + * @param {boolean} [queryInfo.currentWindow] + * Matches against against the return value of `isCurrentFor()` for the + * given context. + * @param {boolean} [queryInfo.lastFocusedWindow] + * Matches against the exact value of the window's `isLastFocused` attribute. + * @param {boolean} [queryInfo.windowId] + * Matches against the exact value of the window's ID, taking into + * account the special WINDOW_ID_CURRENT value. + * @param {string} [queryInfo.windowType] + * Matches against the exact value of the window's `type` attribute. + * @param {BaseContext} context + * The extension context for which the matching is being performed. + * Used to determine the current window for relevant properties. + * + * @returns {boolean} + * True if the window matches the query. + */ + matches(queryInfo: { + currentWindow?: boolean; + lastFocusedWindow?: boolean; + windowId?: boolean; + windowType?: string; + }, context: BaseContext): boolean; + /** + * @property {boolean} focused + * Returns true if the browser window is currently focused. + * @readonly + * @abstract + */ + readonly get focused(): void; + /** + * @property {integer} top + * Returns the pixel offset of the top of the window from the top of + * the screen. + * @readonly + * @abstract + */ + readonly get top(): void; + /** + * @property {integer} left + * Returns the pixel offset of the left of the window from the left of + * the screen. + * @readonly + * @abstract + */ + readonly get left(): void; + /** + * @property {integer} width + * Returns the pixel width of the window. + * @readonly + * @abstract + */ + readonly get width(): void; + /** + * @property {integer} height + * Returns the pixel height of the window. + * @readonly + * @abstract + */ + readonly get height(): void; + /** + * @property {boolean} incognito + * Returns true if this is a private browsing window, false otherwise. + * @readonly + * @abstract + */ + readonly get incognito(): void; + /** + * @property {boolean} alwaysOnTop + * Returns true if this window is constrained to always remain above + * other windows. + * @readonly + * @abstract + */ + readonly get alwaysOnTop(): void; + /** + * @property {boolean} isLastFocused + * Returns true if this is the browser window which most recently had + * focus. + * @readonly + * @abstract + */ + readonly get isLastFocused(): void; + set state(state: void); + /** + * @property {string} state + * Returns or sets the current state of this window, as determined by + * `getState()`. + * @abstract + */ + get state(): void; + /** + * @property {nsIURI | null} title + * Returns the current title of this window if the extension has permission + * to read it, or null otherwise. + * @readonly + */ + readonly get title(): any; + /** + * Returns an iterator of TabBase objects for each tab in this window. + * + * @returns {Iterator<TabBase>} + */ + getTabs(): Iterator<TabBase>; + /** + * Returns an iterator of TabBase objects for each highlighted tab in this window. + * + * @returns {Iterator<TabBase>} + */ + getHighlightedTabs(): Iterator<TabBase>; + /** + * @property {TabBase} The window's currently active tab. + */ + get activeTab(): void; + /** + * Returns the window's tab at the specified index. + * + * @param {integer} index + * The index of the desired tab. + * + * @returns {TabBase|undefined} + */ + getTabAtIndex(index: integer): TabBase | undefined; +} +/** + * The parameter type of "tab-attached" events, which are emitted when a + * pre-existing tab is attached to a new window. + * + * @typedef {object} TabAttachedEvent + * @property {NativeTab} tab + * The native tab object in the window to which the tab is being + * attached. This may be a different object than was used to represent + * the tab in the old window. + * @property {integer} tabId + * The ID of the tab being attached. + * @property {integer} newWindowId + * The ID of the window to which the tab is being attached. + * @property {integer} newPosition + * The position of the tab in the tab list of the new window. + */ +/** + * The parameter type of "tab-detached" events, which are emitted when a + * pre-existing tab is detached from a window, in order to be attached to a new + * window. + * + * @typedef {object} TabDetachedEvent + * @property {NativeTab} tab + * The native tab object in the window from which the tab is being + * detached. This may be a different object than will be used to + * represent the tab in the new window. + * @property {NativeTab} adoptedBy + * The native tab object in the window to which the tab will be attached, + * and is adopting the contents of this tab. This may be a different + * object than the tab in the previous window. + * @property {integer} tabId + * The ID of the tab being detached. + * @property {integer} oldWindowId + * The ID of the window from which the tab is being detached. + * @property {integer} oldPosition + * The position of the tab in the tab list of the window from which it is + * being detached. + */ +/** + * The parameter type of "tab-created" events, which are emitted when a + * new tab is created. + * + * @typedef {object} TabCreatedEvent + * @property {NativeTab} tab + * The native tab object for the tab which is being created. + */ +/** + * The parameter type of "tab-removed" events, which are emitted when a + * tab is removed and destroyed. + * + * @typedef {object} TabRemovedEvent + * @property {NativeTab} tab + * The native tab object for the tab which is being removed. + * @property {integer} tabId + * The ID of the tab being removed. + * @property {integer} windowId + * The ID of the window from which the tab is being removed. + * @property {boolean} isWindowClosing + * True if the tab is being removed because the window is closing. + */ +/** + * An object containing basic, extension-independent information about the window + * and tab that a XUL <browser> belongs to. + * + * @typedef {object} BrowserData + * @property {integer} tabId + * The numeric ID of the tab that a <browser> belongs to, or -1 if it + * does not belong to a tab. + * @property {integer} windowId + * The numeric ID of the browser window that a <browser> belongs to, or -1 + * if it does not belong to a browser window. + */ +/** + * A platform-independent base class for the platform-specific TabTracker + * classes, which track the opening and closing of tabs, and manage the mapping + * of them between numeric IDs and native tab objects. + * + * Instances of this class are EventEmitters which emit the following events, + * each with an argument of the given type: + * + * - "tab-attached" {@link TabAttacheEvent} + * - "tab-detached" {@link TabDetachedEvent} + * - "tab-created" {@link TabCreatedEvent} + * - "tab-removed" {@link TabRemovedEvent} + */ +declare class TabTrackerBase { + on(...args: any[]): any; + /** + * Called to initialize the tab tracking listeners the first time that an + * event listener is added. + * + * @protected + * @abstract + */ + protected init(): void; + /** + * Returns the numeric ID for the given native tab. + * + * @param {NativeTab} nativeTab + * The native tab for which to return an ID. + * + * @returns {integer} + * The tab's numeric ID. + * @abstract + */ + getId(nativeTab: NativeTab): integer; + /** + * Returns the native tab with the given numeric ID. + * + * @param {integer} tabId + * The numeric ID of the tab to return. + * @param {*} default_ + * The value to return if no tab exists with the given ID. + * + * @returns {NativeTab} + * @throws {ExtensionError} + * If no tab exists with the given ID and a default return value is not + * provided. + * @abstract + */ + getTab(tabId: integer, default_?: any): NativeTab; + /** + * Returns basic information about the tab and window that the given browser + * belongs to. + * + * @param {XULElement} browser + * The XUL browser element for which to return data. + * + * @returns {BrowserData} + * @abstract + */ + getBrowserData(browser: XULElement): BrowserData; + /** + * @property {NativeTab} activeTab + * Returns the native tab object for the active tab in the + * most-recently focused window, or null if no live tabs currently + * exist. + * @abstract + */ + get activeTab(): void; +} +/** + * A browser progress listener instance which calls a given listener function + * whenever the status of the given browser changes. + * + * @param {function(object): void} listener + * A function to be called whenever the status of a tab's top-level + * browser. It is passed an object with a `browser` property pointing to + * the XUL browser, and a `status` property with a string description of + * the browser's status. + * @private + */ +declare class StatusListener { + constructor(listener: any); + listener: any; + onStateChange(browser: any, webProgress: any, request: any, stateFlags: any, statusCode: any): void; + onLocationChange(browser: any, webProgress: any, request: any, locationURI: any, flags: any): void; +} +/** + * A platform-independent base class for the platform-specific WindowTracker + * classes, which track the opening and closing of windows, and manage the + * mapping of them between numeric IDs and native tab objects. + */ +declare class WindowTrackerBase { + /** + * A private method which is called whenever a new browser window is opened, + * and adds the necessary listeners to it. + * + * @param {DOMWindow} window + * The window being opened. + * @private + */ + private _handleWindowOpened; + _openListeners: Set<any>; + _closeListeners: Set<any>; + _listeners: any; + _statusListeners: any; + _windowIds: any; + isBrowserWindow(window: any): boolean; + /** + * Returns an iterator for all currently active browser windows. + * + * @param {boolean} [includeInomplete = false] + * If true, include browser windows which are not yet fully loaded. + * Otherwise, only include windows which are. + * + * @returns {Iterator<DOMWindow>} + */ + browserWindows(includeIncomplete?: boolean): Iterator<DOMWindow>; + /** + * @property {DOMWindow|null} topWindow + * The currently active, or topmost, browser window, or null if no + * browser window is currently open. + * @readonly + */ + readonly get topWindow(): any; + /** + * @property {DOMWindow|null} topWindow + * The currently active, or topmost, browser window that is not + * private browsing, or null if no browser window is currently open. + * @readonly + */ + readonly get topNonPBWindow(): any; + /** + * Returns the top window accessible by the extension. + * + * @param {BaseContext} context + * The extension context for which to return the current window. + * + * @returns {DOMWindow|null} + */ + getTopWindow(context: BaseContext): DOMWindow | null; + /** + * Returns the numeric ID for the given browser window. + * + * @param {DOMWindow} window + * The DOM window for which to return an ID. + * + * @returns {integer} + * The window's numeric ID. + */ + getId(window: DOMWindow): integer; + /** + * Returns the browser window to which the given context belongs, or the top + * browser window if the context does not belong to a browser window. + * + * @param {BaseContext} context + * The extension context for which to return the current window. + * + * @returns {DOMWindow|null} + */ + getCurrentWindow(context: BaseContext): DOMWindow | null; + /** + * Returns the browser window with the given ID. + * + * @param {integer} id + * The ID of the window to return. + * @param {BaseContext} context + * The extension context for which the matching is being performed. + * Used to determine the current window for relevant properties. + * @param {boolean} [strict = true] + * If false, undefined will be returned instead of throwing an error + * in case no window exists with the given ID. + * + * @returns {DOMWindow|undefined} + * @throws {ExtensionError} + * If no window exists with the given ID and `strict` is true. + */ + getWindow(id: integer, context: BaseContext, strict?: boolean): DOMWindow | undefined; + /** + * @property {boolean} _haveListeners + * Returns true if any window open or close listeners are currently + * registered. + * @private + */ + private get _haveListeners(); + /** + * Register the given listener function to be called whenever a new browser + * window is opened. + * + * @param {function(DOMWindow): void} listener + * The listener function to register. + */ + addOpenListener(listener: (arg0: DOMWindow) => void): void; + /** + * Unregister a listener function registered in a previous addOpenListener + * call. + * + * @param {function(DOMWindow): void} listener + * The listener function to unregister. + */ + removeOpenListener(listener: (arg0: DOMWindow) => void): void; + /** + * Register the given listener function to be called whenever a browser + * window is closed. + * + * @param {function(DOMWindow): void} listener + * The listener function to register. + */ + addCloseListener(listener: (arg0: DOMWindow) => void): void; + /** + * Unregister a listener function registered in a previous addCloseListener + * call. + * + * @param {function(DOMWindow): void} listener + * The listener function to unregister. + */ + removeCloseListener(listener: (arg0: DOMWindow) => void): void; + /** + * Handles load events for recently-opened windows, and adds additional + * listeners which may only be safely added when the window is fully loaded. + * + * @param {Event} event + * A DOM event to handle. + * @private + */ + private handleEvent; + /** + * Observes "domwindowopened" and "domwindowclosed" events, notifies the + * appropriate listeners, and adds necessary additional listeners to the new + * windows. + * + * @param {DOMWindow} window + * A DOM window. + * @param {string} topic + * The topic being observed. + * @private + */ + private observe; + /** + * Add an event listener to be called whenever the given DOM event is received + * at the top level of any browser window. + * + * @param {string} type + * The type of event to listen for. May be any valid DOM event name, or + * one of the following special cases: + * + * - "progress": Adds a tab progress listener to every browser window. + * - "status": Adds a StatusListener to every tab of every browser + * window. + * - "domwindowopened": Acts as an alias for addOpenListener. + * - "domwindowclosed": Acts as an alias for addCloseListener. + * @param {Function | object} listener + * The listener to invoke in response to the given events. + * + * @returns {undefined} + */ + addListener(type: string, listener: Function | object): undefined; + /** + * Removes an event listener previously registered via an addListener call. + * + * @param {string} type + * The type of event to stop listening for. + * @param {Function | object} listener + * The listener to remove. + * + * @returns {undefined} + */ + removeListener(type: string, listener: Function | object): undefined; + /** + * Adds a listener for the given event to the given window. + * + * @param {DOMWindow} window + * The browser window to which to add the listener. + * @param {string} eventType + * The type of DOM event to listen for, or "progress" to add a tab + * progress listener. + * @param {Function | object} listener + * The listener to add. + * @private + */ + private _addWindowListener; + /** + * Adds a tab progress listener to the given browser window. + * + * @param {DOMWindow} window + * The browser window to which to add the listener. + * @param {object} listener + * The tab progress listener to add. + * @abstract + */ + addProgressListener(window: DOMWindow, listener: object): void; + /** + * Removes a tab progress listener from the given browser window. + * + * @param {DOMWindow} window + * The browser window from which to remove the listener. + * @param {object} listener + * The tab progress listener to remove. + * @abstract + */ + removeProgressListener(window: DOMWindow, listener: object): void; +} +/** + * Manages native tabs, their wrappers, and their dynamic permissions for a + * particular extension. + * + * @param {Extension} extension + * The extension for which to manage tabs. + */ +declare class TabManagerBase { + constructor(extension: any); + extension: any; + _tabs: any; + /** + * If the extension has requested activeTab permission, grant it those + * permissions for the current inner window in the given native tab. + * + * @param {NativeTab} nativeTab + * The native tab for which to grant permissions. + */ + addActiveTabPermission(nativeTab: NativeTab): void; + /** + * Revoke the extension's activeTab permissions for the current inner window + * of the given native tab. + * + * @param {NativeTab} nativeTab + * The native tab for which to revoke permissions. + */ + revokeActiveTabPermission(nativeTab: NativeTab): void; + /** + * Returns true if the extension has requested activeTab permission, and has + * been granted permissions for the current inner window if this tab. + * + * @param {NativeTab} nativeTab + * The native tab for which to check permissions. + * @returns {boolean} + * True if the extension has activeTab permissions for this tab. + */ + hasActiveTabPermission(nativeTab: NativeTab): boolean; + /** + * Activate MV3 content scripts if the extension has activeTab or an + * (ungranted) host permission. + * + * @param {NativeTab} nativeTab + */ + activateScripts(nativeTab: NativeTab): void; + /** + * Returns true if the extension has permissions to access restricted + * properties of the given native tab. In practice, this means that it has + * either requested the "tabs" permission or has activeTab permissions for the + * given tab. + * + * NOTE: Never use this method on an object that is not a native tab + * for the current platform: this method implicitly generates a wrapper + * for the passed nativeTab parameter and the platform-specific tabTracker + * instance is likely to store it in a map which is cleared only when the + * tab is closed (and so, if nativeTab is not a real native tab, it will + * never be cleared from the platform-specific tabTracker instance), + * See Bug 1458918 for a rationale. + * + * @param {NativeTab} nativeTab + * The native tab for which to check permissions. + * @returns {boolean} + * True if the extension has permissions for this tab. + */ + hasTabPermission(nativeTab: NativeTab): boolean; + /** + * Returns this extension's TabBase wrapper for the given native tab. This + * method will always return the same wrapper object for any given native tab. + * + * @param {NativeTab} nativeTab + * The tab for which to return a wrapper. + * + * @returns {TabBase|undefined} + * The wrapper for this tab. + */ + getWrapper(nativeTab: NativeTab): TabBase | undefined; + /** + * Determines access using extension context. + * + * @param {NativeTab} nativeTab + * The tab to check access on. + * @returns {boolean} + * True if the extension has permissions for this tab. + * @protected + * @abstract + */ + protected canAccessTab(nativeTab: NativeTab): boolean; + /** + * Converts the given native tab to a JSON-compatible object, in the format + * required to be returned by WebExtension APIs, which may be safely passed to + * extension code. + * + * @param {NativeTab} nativeTab + * The native tab to convert. + * @param {object} [fallbackTabSize] + * A geometry data if the lazy geometry data for this tab hasn't been + * initialized yet. + * + * @returns {object} + */ + convert(nativeTab: NativeTab, fallbackTabSize?: object): object; + /** + * Returns an iterator of TabBase objects which match the given query info. + * + * @param {object | null} [queryInfo = null] + * An object containing properties on which to filter. May contain any + * properties which are recognized by {@link TabBase#matches} or + * {@link WindowBase#matches}. Unknown properties will be ignored. + * @param {BaseContext|null} [context = null] + * The extension context for which the matching is being performed. + * Used to determine the current window for relevant properties. + * + * @returns {Iterator<TabBase>} + */ + query(queryInfo?: object | null, context?: BaseContext | null): Iterator<TabBase>; + /** + * Returns a TabBase wrapper for the tab with the given ID. + * + * @param {integer} tabId + * The ID of the tab for which to return a wrapper. + * + * @returns {TabBase} + * @throws {ExtensionError} + * If no tab exists with the given ID. + * @abstract + */ + get(tabId: integer): TabBase; + /** + * Returns a new TabBase instance wrapping the given native tab. + * + * @param {NativeTab} nativeTab + * The native tab for which to return a wrapper. + * + * @returns {TabBase} + * @protected + * @abstract + */ + protected wrapTab(nativeTab: NativeTab): TabBase; +} +/** + * Manages native browser windows and their wrappers for a particular extension. + * + * @param {Extension} extension + * The extension for which to manage windows. + */ +declare class WindowManagerBase { + constructor(extension: any); + extension: any; + _windows: any; + /** + * Converts the given browser window to a JSON-compatible object, in the + * format required to be returned by WebExtension APIs, which may be safely + * passed to extension code. + * + * @param {DOMWindow} window + * The browser window to convert. + * @param {*} args + * Additional arguments to be passed to {@link WindowBase#convert}. + * + * @returns {object} + */ + convert(window: DOMWindow, ...args: any): object; + /** + * Returns this extension's WindowBase wrapper for the given browser window. + * This method will always return the same wrapper object for any given + * browser window. + * + * @param {DOMWindow} window + * The browser window for which to return a wrapper. + * + * @returns {WindowBase|undefined} + * The wrapper for this tab. + */ + getWrapper(window: DOMWindow): WindowBase | undefined; + /** + * Returns whether this window can be accessed by the extension in the given + * context. + * + * @param {DOMWindow} window + * The browser window that is being tested + * @param {BaseContext|null} context + * The extension context for which this test is being performed. + * @returns {boolean} + */ + canAccessWindow(window: DOMWindow, context: BaseContext | null): boolean; + /** + * Returns an iterator of WindowBase objects which match the given query info. + * + * @param {object | null} [queryInfo = null] + * An object containing properties on which to filter. May contain any + * properties which are recognized by {@link WindowBase#matches}. + * Unknown properties will be ignored. + * @param {BaseContext|null} [context = null] + * The extension context for which the matching is being performed. + * Used to determine the current window for relevant properties. + * + * @returns {Iterator<WindowBase>} + */ + query(queryInfo?: object | null, context?: BaseContext | null): Iterator<WindowBase>; + /** + * Returns a WindowBase wrapper for the browser window with the given ID. + * + * @param {integer} windowId + * The ID of the browser window for which to return a wrapper. + * @param {BaseContext} context + * The extension context for which the matching is being performed. + * Used to determine the current window for relevant properties. + * + * @returns {WindowBase} + * @throws {ExtensionError} + * If no window exists with the given ID. + * @abstract + */ + get(windowId: integer, context: BaseContext): WindowBase; + /** + * Returns an iterator of WindowBase wrappers for each currently existing + * browser window. + * + * @returns {Iterator<WindowBase>} + * @abstract + */ + getAll(): Iterator<WindowBase>; + /** + * Returns a new WindowBase instance wrapping the given browser window. + * + * @param {DOMWindow} window + * The browser window for which to return a wrapper. + * + * @returns {WindowBase} + * @protected + * @abstract + */ + protected wrapWindow(window: DOMWindow): WindowBase; +} +/** + * The platform-specific type of native tab objects, which are wrapped by + * TabBase instances. + */ +type NativeTab = object | XULElement; +type MutedInfo = { + /** + * True if the tab is currently muted, false otherwise. + */ + muted: boolean; + /** + * The reason the tab is muted. Either "user", if the tab was muted by a + * user, or "extension", if it was muted by an extension. + */ + reason?: string; + /** + * If the tab was muted by an extension, contains the internal ID of that + * extension. + */ + extensionId?: string; +}; +/** + * The parameter type of "tab-attached" events, which are emitted when a + * pre-existing tab is attached to a new window. + */ +type TabAttachedEvent = { + /** + * The native tab object in the window to which the tab is being + * attached. This may be a different object than was used to represent + * the tab in the old window. + */ + tab: NativeTab; + /** + * The ID of the tab being attached. + */ + tabId: integer; + /** + * The ID of the window to which the tab is being attached. + */ + newWindowId: integer; + /** + * The position of the tab in the tab list of the new window. + */ + newPosition: integer; +}; +/** + * The parameter type of "tab-detached" events, which are emitted when a + * pre-existing tab is detached from a window, in order to be attached to a new + * window. + */ +type TabDetachedEvent = { + /** + * The native tab object in the window from which the tab is being + * detached. This may be a different object than will be used to + * represent the tab in the new window. + */ + tab: NativeTab; + /** + * The native tab object in the window to which the tab will be attached, + * and is adopting the contents of this tab. This may be a different + * object than the tab in the previous window. + */ + adoptedBy: NativeTab; + /** + * The ID of the tab being detached. + */ + tabId: integer; + /** + * The ID of the window from which the tab is being detached. + */ + oldWindowId: integer; + /** + * The position of the tab in the tab list of the window from which it is + * being detached. + */ + oldPosition: integer; +}; +/** + * The parameter type of "tab-created" events, which are emitted when a + * new tab is created. + */ +type TabCreatedEvent = { + /** + * The native tab object for the tab which is being created. + */ + tab: NativeTab; +}; +/** + * The parameter type of "tab-removed" events, which are emitted when a + * tab is removed and destroyed. + */ +type TabRemovedEvent = { + /** + * The native tab object for the tab which is being removed. + */ + tab: NativeTab; + /** + * The ID of the tab being removed. + */ + tabId: integer; + /** + * The ID of the window from which the tab is being removed. + */ + windowId: integer; + /** + * True if the tab is being removed because the window is closing. + */ + isWindowClosing: boolean; +}; +/** + * An object containing basic, extension-independent information about the window + * and tab that a XUL <browser> belongs to. + */ +type BrowserData = { + /** + * The numeric ID of the tab that a <browser> belongs to, or -1 if it + * does not belong to a tab. + */ + tabId: integer; + /** + * The numeric ID of the browser window that a <browser> belongs to, or -1 + * if it does not belong to a browser window. + */ + windowId: integer; +}; + +} // namespace tabs_base + +declare global { + type TabTrackerBase = tabs_base.TabTrackerBase; + type TabManagerBase = tabs_base.TabManagerBase; + type TabBase = tabs_base.TabBase; + type WindowTrackerBase = tabs_base.WindowTrackerBase; + type WindowManagerBase = tabs_base.WindowManagerBase; + type WindowBase = tabs_base.WindowBase; + type getUserContextIdForCookieStoreId = tabs_base.getUserContextIdForCookieStoreId; + type NativeTab = tabs_base.NativeTab; +} + +export {}; diff --git a/toolkit/components/extensions/types/extensions.ts b/toolkit/components/extensions/types/extensions.ts index 8f9555421b..e232ab4a3f 100644 --- a/toolkit/components/extensions/types/extensions.ts +++ b/toolkit/components/extensions/types/extensions.ts @@ -1,5 +1,5 @@ /** - * Type declarations for WebExtensions framework code. + * Types specific to toolkit/extensions code. */ // This has every possible property we import from all modules, which is not @@ -42,39 +42,37 @@ type LazyAll = { getTrimmedString: typeof import("ExtensionTelemetry.sys.mjs").getTrimmedString, }; -// Utility type to extract all strings from a const array, to use as keys. -type Items<A> = A extends ReadonlyArray<infer U extends string> ? U : never; - declare global { - type Lazy<Keys extends keyof LazyAll = keyof LazyAll> = Pick<LazyAll, Keys> & { [k: string]: any }; + type Lazy = Partial<LazyAll> & { [k: string]: any }; - // Export JSDoc types, and make other classes available globally. - type ConduitAddress = import("ConduitsParent.sys.mjs").ConduitAddress; - type ConduitID = import("ConduitsParent.sys.mjs").ConduitID; + type BaseContext = import("ExtensionCommon.sys.mjs").BaseContext; + type ExtensionChild = import("ExtensionChild.sys.mjs").ExtensionChild; type Extension = import("Extension.sys.mjs").Extension; + type callback = (...any) => any; + + interface nsIDOMProcessChild { + getActor(name: "ProcessConduits"): ProcessConduitsChild; + } - // Something about Class type not being exported when nested in a namespace? - type BaseContext = InstanceType<typeof import("ExtensionCommon.sys.mjs").ExtensionCommon.BaseContext>; - type BrowserExtensionContent = InstanceType<typeof import("ExtensionContent.sys.mjs").ExtensionContent.BrowserExtensionContent>; - type EventEmitter = InstanceType<typeof import("ExtensionCommon.sys.mjs").ExtensionCommon.EventEmitter>; - type ExtensionAPI = InstanceType<typeof import("ExtensionCommon.sys.mjs").ExtensionCommon.ExtensionAPI>; - type ExtensionError = InstanceType<typeof import("ExtensionUtils.sys.mjs").ExtensionUtils.ExtensionError>; - type LocaleData = InstanceType<typeof import("ExtensionCommon.sys.mjs").ExtensionCommon.LocaleData>; - type ProxyAPIImplementation = InstanceType<typeof import("ExtensionChild.sys.mjs").ExtensionChild.ProxyAPIImplementation>; - type SchemaAPIInterface = InstanceType<typeof import("ExtensionCommon.sys.mjs").ExtensionCommon.SchemaAPIInterface>; - type WorkerExtensionError = InstanceType<typeof import("ExtensionUtils.sys.mjs").ExtensionUtils.WorkerExtensionError>; + interface WebExtensionContentScript { + userScriptOptions: { scriptMetadata: object }; + } - // Other misc types. - type AddonWrapper = any; - type Context = BaseContext; - type NativeTab = Element; - type SavedFrame = object; + interface WebExtensionPolicy { + extension: Extension; + debugName: string; + instanceId: string; + optionalPermissions: string[]; + } // Can't define a const generic parameter in jsdocs yet. // https://github.com/microsoft/TypeScript/issues/56634 - type ConduitInit<Send> = ConduitAddress & { send: Send; }; - type Conduit<Send> = import("../ConduitsChild.sys.mjs").PointConduit & { [s in `send${Items<Send>}`]: callback }; - type ConduitOpen = <const Send>(subject: object, address: ConduitInit<Send>) => Conduit<Send>; + function ConduitGen<const Send>(_, init: Init<Send>, _actor?): Conduit<Send>; + type Items<A> = A extends ReadonlyArray<infer U extends string> ? U : never; } -export {} +import { PointConduit, ProcessConduitsChild } from "ConduitsChild.sys.mjs"; +import { ConduitAddress } from "ConduitsParent.sys.mjs"; + +type Conduit<Send> = PointConduit & { [s in `send${Items<Send>}`]: callback }; +type Init<Send> = ConduitAddress & { send: Send; }; diff --git a/toolkit/components/extensions/types/gecko.ts b/toolkit/components/extensions/types/gecko.ts index f6b5190f8d..720919d794 100644 --- a/toolkit/components/extensions/types/gecko.ts +++ b/toolkit/components/extensions/types/gecko.ts @@ -1,163 +1,94 @@ /** - * Global Gecko type declarations. + * Gecko generic/specialized adjustments for xpcom and webidl types. */ -// @ts-ignore -import type { CiClass } from "lib.gecko.xpidl" - -declare global { - // Other misc types. - type Browser = InstanceType<typeof XULBrowserElement>; - type bytestring = string; - type callback = (...args: any[]) => any; - type ColorArray = number[]; - type integer = number; - type JSONValue = null | boolean | number | string | JSONValue[] | { [key: string]: JSONValue }; - - interface Document { - createXULElement(name: string): Element; - documentReadyForIdle: Promise<void>; - } - interface EventTarget { - ownerGlobal: Window; - } - interface Error { - code; - } - interface ErrorConstructor { - new (message?: string, options?: ErrorOptions, lineNo?: number): Error; - } - interface Window { - gBrowser; - } - // HACK to get the static isInstance for DOMException and Window? - interface Object { - isInstance(object: any): boolean; - } +// More specific types for parent process browsing contexts. +interface CanonicalBrowsingContext extends LoadContextMixin { + embedderElement: XULBrowserElement; + currentWindowContext: WindowGlobalParent; + parent: CanonicalBrowsingContext; + parentWindowContext: WindowGlobalParent; + top: CanonicalBrowsingContext; + topWindowContext: WindowGlobalParent; +} - // XPIDL additions/overrides. +interface ChromeWindow extends Window { + isChromeWindow: true; +} - interface nsISupports { - // OMG it works! - QueryInterface?<T extends CiClass<nsISupports>>(aCiClass: T): T['prototype']; - wrappedJSObject?: object; - } - interface nsIProperties { - get<T extends CiClass<nsISupports>>(prop: string, aCiClass: T): T['prototype']; - } - interface nsIPrefBranch { - getComplexValue<T extends CiClass<nsISupports>>(aPrefName: string, aCiClass: T): T['prototype']; - } - // TODO: incorporate above into lib.xpidl.d.ts generation, somehow? +interface Document { + createXULElement(name: "browser"): XULBrowserElement; +} - type Sandbox = typeof globalThis; - interface nsIXPCComponents_utils_Sandbox { - (principal: nsIPrincipal | nsIPrincipal[], options: object): Sandbox; - } - interface nsIXPCComponents_Utils { - cloneInto<T>(obj: T, ...args: any[]): T; - createObjectIn<T>(Sandbox, options?: T): T; - exportFunction<T extends callback>(f: T, ...args: any[]): T; - getWeakReference<T extends object>(T): { get(): T }; - readonly Sandbox: nsIXPCComponents_utils_Sandbox; - waiveXrays<T>(obj: T): T; - } - interface nsIDOMWindow extends Window { - docShell: nsIDocShell; - } - interface Document { - documentURIObject: nsIURI; - createXULElement(name: string): Element; - } +interface MessageListenerManagerMixin { + // Overloads that define `data` arg as required, since it's ~always expected. + addMessageListener(msg: string, listener: { receiveMessage(_: ReceiveMessageArgument & { data })}); + removeMessageListener(msg: string, listener: { receiveMessage(_: ReceiveMessageArgument & { data })}); +} + +interface MozQueryInterface { + <T>(iid: T): nsQIResult<T>; +} - // nsDocShell is the only thing implementing nsIDocShell, but it also - // implements nsIWebNavigation, and a few others, so this is "ok". - interface nsIDocShell extends nsIWebNavigation {} - interface nsISimpleEnumerator extends Iterable<any> {} +interface nsICryptoHash extends nsISupports { + // Accepts a TypedArray. + update(aData: ArrayLike<number>, aLen: number): void; +} - namespace Components { - type Exception = Error; - } - namespace UrlbarUtils { - type RESULT_TYPE = any; - type RESULT_SOURCE = any; +interface nsIDOMWindow extends Window {} + +interface nsISimpleEnumerator extends Iterable<any> {} + +interface nsISupports { + wrappedJSObject?: object; +} + +interface nsIXPCComponents_Constructor { + <const T, IIDs = nsIXPCComponents_Interfaces>(cid, id: T, init?): { + new (...any): nsQIResult<T extends keyof IIDs ? IIDs[T] : T>; + (...any): nsQIResult<T extends keyof IIDs ? IIDs[T] : T>; } +} + +interface nsIXPCComponents_Exception { + (...args: ConstructorParameters<typeof Error>): Error; +} + +interface nsIXPCComponents_utils_Sandbox { + (principal: nsIPrincipal | nsIPrincipal[], options: object): typeof globalThis; +} - // Various mozilla globals. - var Cc, Cr, ChromeUtils, Components, dump, uneval; - - // [ChromeOnly] WebIDL, to be generated. - var BrowsingContext, ChannelWrapper, ChromeWindow, ChromeWorker, - ClonedErrorHolder, Glean, InspectorUtils, IOUtils, JSProcessActorChild, - JSProcessActorParent, JSWindowActor, JSWindowActorChild, - JSWindowActorParent, L10nRegistry, L10nFileSource, Localization, - MatchGlob, MatchPattern, MatchPatternSet, PathUtils, PreloadedScript, - StructuredCloneHolder, TelemetryStopwatch, WindowGlobalChild, - WebExtensionContentScript, WebExtensionParentActor, WebExtensionPolicy, - XULBrowserElement, nsIMessageListenerManager; - - interface XULElement extends Element {} - - // nsIServices is not a thing. - interface nsIServices { - scriptloader: mozIJSSubScriptLoader; - locale: mozILocaleService; - intl: mozIMozIntl; - storage: mozIStorageService; - appShell: nsIAppShellService; - startup: nsIAppStartup; - blocklist: nsIBlocklistService; - cache2: nsICacheStorageService; - catMan: nsICategoryManager; - clearData: nsIClearDataService; - clipboard: nsIClipboard; - console: nsIConsoleService; - cookieBanners: nsICookieBannerService; - cookies: nsICookieManager & nsICookieService; - appinfo: nsICrashReporter & nsIXULAppInfo & nsIXULRuntime; - DAPTelemetry: nsIDAPTelemetry; - DOMRequest: nsIDOMRequestService; - dns: nsIDNSService; - dirsvc: nsIDirectoryService & nsIProperties; - droppedLinkHandler: nsIDroppedLinkHandler; - eTLD: nsIEffectiveTLDService; - policies: nsIEnterprisePolicies; - env: nsIEnvironment; - els: nsIEventListenerService; - fog: nsIFOG; - focus: nsIFocusManager; - io: nsIIOService & nsINetUtil & nsISpeculativeConnect; - loadContextInfo: nsILoadContextInfoFactory; - domStorageManager: nsIDOMStorageManager & nsILocalStorageManager; - logins: nsILoginManager; - obs: nsIObserverService; - perms: nsIPermissionManager; - prefs: nsIPrefBranch & nsIPrefService; - profiler: nsIProfiler; - prompt: nsIPromptService; - sysinfo: nsISystemInfo & nsIPropertyBag2; - qms: nsIQuotaManagerService; - rfp: nsIRFPService; - scriptSecurityManager: nsIScriptSecurityManager; - search: nsISearchService; - sessionStorage: nsISessionStorageService; - strings: nsIStringBundleService; - telemetry: nsITelemetry; - textToSubURI: nsITextToSubURI; - tm: nsIThreadManager; - uriFixup: nsIURIFixup; - urlFormatter: nsIURLFormatter; - uuid: nsIUUIDGenerator; - vc: nsIVersionComparator; - wm: nsIWindowMediator; - ww: nsIWindowWatcher; - xulStore: nsIXULStore; - ppmm: any; - cpmm: any; - mm: any; +interface nsXPCComponents_Classes { + [cid: string]: { + createInstance<T>(aID: T): nsQIResult<T>; + getService<T>(aID?: T): unknown extends T ? nsISupports : nsQIResult<T>; } +} - var Ci: nsIXPCComponents_Interfaces; - var Cu: nsIXPCComponents_Utils; - var Services: nsIServices; +// Generic overloads. +interface nsXPCComponents_Utils { + cloneInto<T>(value: T, ...any): T; + createObjectIn<T = object>(_, object?: T): T; + exportFunction<T>(func: T, ...any): T; + getWeakReference<T>(value: T): { get(): T }; + waiveXrays<T>(object: T): T; } + +// TODO: remove after next TS update. +interface PromiseConstructor { + withResolvers<T>(): { + promise: Promise<T>; + resolve: (value: T | PromiseLike<T>) => void; + reject: (reason?: any) => void; + }; +} + +// Hand-crafted artisanal types. +interface XULBrowserElement extends XULFrameElement, FrameLoader { + currentURI: nsIURI; + docShellIsActive: boolean; + isRemoteBrowser: boolean; + remoteType: string; +} + +type nsQIResult<iid> = import("gecko/lib.gecko.xpcom").nsQIResult<iid>; diff --git a/toolkit/components/extensions/types/glean.d.ts b/toolkit/components/extensions/types/glean.d.ts new file mode 100644 index 0000000000..842a5a56de --- /dev/null +++ b/toolkit/components/extensions/types/glean.d.ts @@ -0,0 +1,79 @@ +/** + * NOTE: Do not modify this file by hand. + * Content was generated from source metrics.yaml files. + */ + +interface GleanImpl { + + // toolkit/mozapps/extensions/metrics.yaml + + addonsManager: { + install: GleanEvent; + update: GleanEvent; + installStats: GleanEvent; + manage: GleanEvent; + report: GleanEvent; + reportSuspiciousSite: GleanEvent; + } + + blocklist: { + lastModifiedRsAddonsMblf: GleanDatetime; + mlbfSource: GleanString; + mlbfGenerationTime: GleanDatetime; + mlbfStashTimeOldest: GleanDatetime; + mlbfStashTimeNewest: GleanDatetime; + addonBlockChange: GleanEvent; + } + + // toolkit/components/extensions/metrics.yaml + + extensions: { + useRemotePref: GleanBoolean; + useRemotePolicy: GleanBoolean; + startupCacheLoadTime: GleanTimespan; + startupCacheReadErrors: Record<string, GleanCounter>; + startupCacheWriteBytelength: GleanQuantity; + processEvent: Record<string, GleanCounter>; + } + + extensionsApisDnr: { + startupCacheReadSize: GleanMemoryDistribution; + startupCacheReadTime: GleanTimingDistribution; + startupCacheWriteSize: GleanMemoryDistribution; + startupCacheWriteTime: GleanTimingDistribution; + startupCacheEntries: Record<string, GleanCounter>; + validateRulesTime: GleanTimingDistribution; + evaluateRulesTime: GleanTimingDistribution; + evaluateRulesCountMax: GleanQuantity; + } + + extensionsData: { + migrateResult: GleanEvent; + storageLocalError: GleanEvent; + } + + extensionsQuarantinedDomains: { + listsize: GleanQuantity; + listhash: GleanString; + remotehash: GleanString; + } + + extensionsCounters: { + browserActionPreloadResult: Record<string, GleanCounter>; + eventPageIdleResult: Record<string, GleanCounter>; + } + + extensionsTiming: { + backgroundPageLoad: GleanTimingDistribution; + browserActionPopupOpen: GleanTimingDistribution; + contentScriptInjection: GleanTimingDistribution; + eventPageRunningTime: GleanCustomDistribution; + extensionStartup: GleanTimingDistribution; + pageActionPopupOpen: GleanTimingDistribution; + storageLocalGetJson: GleanTimingDistribution; + storageLocalSetJson: GleanTimingDistribution; + storageLocalGetIdb: GleanTimingDistribution; + storageLocalSetIdb: GleanTimingDistribution; + } + +} diff --git a/toolkit/components/extensions/types/globals.ts b/toolkit/components/extensions/types/globals.ts index 45722828e2..dee9de0cf4 100644 --- a/toolkit/components/extensions/types/globals.ts +++ b/toolkit/components/extensions/types/globals.ts @@ -1,33 +1,32 @@ /** - * Support types for toolkit/components/extensions code. + * Gecko globals. */ +declare global { + const Cc: nsXPCComponents_Classes; + const Ci: nsIXPCComponents_Interfaces; + const Cr: nsIXPCComponents_Results; + const Components: nsIXPCComponents; -/// <reference lib="dom" /> -/// <reference path="./gecko.ts" /> -/// <reference path="./extensions.ts" /> + // Resolve typed generic overloads before the generated ones. + const Cu: nsXPCComponents_Utils & nsIXPCComponents_Utils; -// This now relies on types generated in bug 1872918, or get the built -// artifact tslib directly and put it in your src/node_modules/@types: -// https://phabricator.services.mozilla.com/D197620 -/// <reference types="lib.gecko.xpidl" /> + const Glean: GleanImpl; + const Services: JSServices; + const uneval: (any) => string; +} -// Exports for all other external modules redirected to globals.ts. -export var AppConstants, - GeckoViewConnection, GeckoViewWebExtension, IndexedDB, JSONFile, Log; +// Exports for all modules redirected here by a catch-all rule in tsconfig.json. +export var + AddonWrapper, AppConstants, GeckoViewConnection, GeckoViewWebExtension, + IndexedDB, JSONFile, Log, UrlbarUtils, WebExtensionDescriptorActor; /** - * This is a mock for the "class" from EventEmitter.sys.mjs. When we import - * it in extensions code using resource://gre/modules/EventEmitter.sys.mjs, - * the catch-all rule from tsconfig.json redirects it to this file. The export - * of the class below fulfills the import. The mock is needed when we subclass - * that EventEmitter, typescript gets confused because it's an old style - * function-and-prototype-based "class", and some types don't match up. - * + * A stub type for the "class" from EventEmitter.sys.mjs. * TODO: Convert EventEmitter.sys.mjs into a proper class. */ export declare class EventEmitter { + emit(event: string, ...args: any[]): void; on(event: string, listener: callback): void; once(event: string, listener: callback): Promise<any>; off(event: string, listener: callback): void; - emit(event: string, ...args: any[]): void; } diff --git a/toolkit/components/extensions/webrequest/StreamFilterParent.cpp b/toolkit/components/extensions/webrequest/StreamFilterParent.cpp index 467616ed0f..ba8700e7f1 100644 --- a/toolkit/components/extensions/webrequest/StreamFilterParent.cpp +++ b/toolkit/components/extensions/webrequest/StreamFilterParent.cpp @@ -230,7 +230,7 @@ StreamFilterParent::CheckListenerChain() { if (trsl) { return trsl->CheckListenerChain(); } - return NS_ERROR_FAILURE; + return NS_ERROR_NO_INTERFACE; } NS_IMETHODIMP |