1 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
2 /* vim: set sts=2 sw=2 et tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 /* globals EventEmitter */
10 ChromeUtils.defineESModuleGetters(this, {
11 PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
14 XPCOMUtils.defineLazyPreferenceGetter(
17 "privacy.userContext.enabled"
20 var { DefaultMap, DefaultWeakMap, ExtensionError, parseMatchPatterns } =
23 var { defineLazyGetter } = ExtensionCommon;
26 * The platform-specific type of native tab objects, which are wrapped by
29 * @typedef {object | XULElement} NativeTab
33 * @typedef {object} MutedInfo
34 * @property {boolean} muted
35 * True if the tab is currently muted, false otherwise.
36 * @property {string} [reason]
37 * The reason the tab is muted. Either "user", if the tab was muted by a
38 * user, or "extension", if it was muted by an extension.
39 * @property {string} [extensionId]
40 * If the tab was muted by an extension, contains the internal ID of that
45 * A platform-independent base class for extension-specific wrappers around
48 * @param {Extension} extension
49 * The extension object for which this wrapper is being created. Used to
50 * determine permissions for access to certain properties and
52 * @param {NativeTab} nativeTab
53 * The native tab object which is being wrapped. The type of this object
56 * The numeric ID of this tab object. This ID should be the same for
57 * every extension, and for the lifetime of the tab.
60 constructor(extension, nativeTab, id) {
61 this.extension = extension;
62 this.tabManager = extension.tabManager;
64 this.nativeTab = nativeTab;
65 this.activeTabWindowID = null;
67 if (!extension.privateBrowsingAllowed && this._incognito) {
68 throw new ExtensionError(`Invalid tab ID: ${id}`);
73 * Capture the visible area of this tab, and return the result as a data: URI.
75 * @param {BaseContext} context
76 * The extension context for which to perform the capture.
77 * @param {number} zoom
78 * The current zoom for the page.
79 * @param {object} [options]
80 * The options with which to perform the capture.
81 * @param {string} [options.format = "png"]
82 * The image format in which to encode the captured data. May be one of
84 * @param {integer} [options.quality = 92]
85 * The quality at which to encode the captured image data, ranging from
86 * 0 to 100. Has no effect for the "png" format.
87 * @param {DOMRectInit} [options.rect]
88 * Area of the document to render, in CSS pixels, relative to the page.
89 * If null, the currently visible viewport is rendered.
90 * @param {number} [options.scale]
91 * The scale to render at, defaults to devicePixelRatio.
92 * @returns {Promise<string>}
94 async capture(context, zoom, options) {
95 let win = this.browser.ownerGlobal;
96 let scale = options?.scale || win.devicePixelRatio;
97 let rect = options?.rect && win.DOMRect.fromRect(options.rect);
99 // We only allow mozilla addons to use the resetScrollPosition option,
100 // since it's not standardized.
101 let resetScrollPosition = false;
102 if (!context.extension.restrictSchemes) {
103 resetScrollPosition = !!options?.resetScrollPosition;
106 let wgp = this.browsingContext.currentWindowGlobal;
107 let image = await wgp.drawSnapshot(
114 let doc = Services.appShell.hiddenDOMWindow.document;
115 let canvas = doc.createElement("canvas");
116 canvas.width = image.width;
117 canvas.height = image.height;
119 let ctx = canvas.getContext("2d", { alpha: false });
120 ctx.drawImage(image, 0, 0);
123 return canvas.toDataURL(`image/${options?.format}`, options?.quality / 100);
127 * @property {integer | null} innerWindowID
128 * The last known innerWindowID loaded into this tab's docShell. This
129 * property must remain in sync with the last known values of
130 * properties such as `url` and `title`. Any operations on the content
131 * of an out-of-process tab will automatically fail if the
132 * innerWindowID of the tab when the message is received does not match
133 * the value of this property when the message was sent.
136 get innerWindowID() {
137 return this.browser.innerWindowID;
141 * @property {boolean} hasTabPermission
142 * Returns true if the extension has permission to access restricted
143 * properties of this tab, such as `url`, `title`, and `favIconUrl`.
146 get hasTabPermission() {
148 this.extension.hasPermission("tabs") ||
149 this.hasActiveTabPermission ||
150 this.matchesHostPermission
155 * @property {boolean} hasActiveTabPermission
156 * Returns true if the extension has the "activeTab" permission, and
157 * has been granted access to this tab due to a user executing an
160 * If true, the extension may load scripts and CSS into this tab, and
161 * access restricted properties, such as its `url`.
164 get hasActiveTabPermission() {
166 (this.extension.originControls ||
167 this.extension.hasPermission("activeTab")) &&
168 this.activeTabWindowID != null &&
169 this.activeTabWindowID === this.innerWindowID
174 * @property {boolean} matchesHostPermission
175 * Returns true if the extensions host permissions match the current tab url.
178 get matchesHostPermission() {
179 return this.extension.allowedOrigins.matches(this._uri);
183 * @property {boolean} incognito
184 * Returns true if this is a private browsing tab, false otherwise.
188 return PrivateBrowsingUtils.isBrowserPrivate(this.browser);
192 * @property {string} _url
193 * Returns the current URL of this tab. Does not do any permission
198 return this.browser.currentURI.spec;
202 * @property {string | null} url
203 * Returns the current URL of this tab if the extension has permission
204 * to read it, or null otherwise.
208 if (this.hasTabPermission) {
214 * @property {nsIURI} _uri
215 * Returns the current URI of this tab.
219 return this.browser.currentURI;
223 * @property {string} _title
224 * Returns the current title of this tab. Does not do any permission
229 return this.browser.contentTitle || this.nativeTab.label;
233 * @property {nsIURI | null} title
234 * Returns the current title of this tab if the extension has permission
235 * to read it, or null otherwise.
239 if (this.hasTabPermission) {
245 * @property {string} _favIconUrl
246 * Returns the current favicon URL of this tab. Does not do any permission
252 throw new Error("Not implemented");
256 * @property {nsIURI | null} faviconUrl
257 * Returns the current faviron URL of this tab if the extension has permission
258 * to read it, or null otherwise.
262 if (this.hasTabPermission) {
263 return this._favIconUrl;
268 * @property {integer} lastAccessed
269 * Returns the last time the tab was accessed as the number of
270 * milliseconds since epoch.
275 throw new Error("Not implemented");
279 * @property {boolean} audible
280 * Returns true if the tab is currently playing audio, false otherwise.
285 throw new Error("Not implemented");
289 * @property {boolean} autoDiscardable
290 * Returns true if the tab can be discarded on memory pressure, false otherwise.
294 get autoDiscardable() {
295 throw new Error("Not implemented");
299 * @property {XULElement} browser
300 * Returns the XUL browser for the given tab.
305 throw new Error("Not implemented");
309 * @property {BrowsingContext} browsingContext
310 * Returns the BrowsingContext for the given tab.
313 get browsingContext() {
314 return this.browser?.browsingContext;
318 * @property {FrameLoader} frameLoader
319 * Returns the frameloader for the given tab.
323 return this.browser && this.browser.frameLoader;
327 * @property {string} cookieStoreId
328 * Returns the cookie store identifier for the given tab.
332 get cookieStoreId() {
333 throw new Error("Not implemented");
337 * @property {integer} openerTabId
338 * Returns the ID of the tab which opened this one.
346 * @property {integer} discarded
347 * Returns true if the tab is discarded.
352 throw new Error("Not implemented");
356 * @property {integer} height
357 * Returns the pixel height of the visible area of the tab.
362 throw new Error("Not implemented");
366 * @property {integer} hidden
367 * Returns true if the tab is hidden.
372 throw new Error("Not implemented");
376 * @property {integer} index
377 * Returns the index of the tab in its window's tab list.
382 throw new Error("Not implemented");
386 * @property {MutedInfo} mutedInfo
387 * Returns information about the tab's current audio muting status.
392 throw new Error("Not implemented");
396 * @property {SharingState} sharingState
397 * Returns object with tab sharingState.
402 throw new Error("Not implemented");
406 * @property {boolean} pinned
407 * Returns true if the tab is pinned, false otherwise.
412 throw new Error("Not implemented");
416 * @property {boolean} active
417 * Returns true if the tab is the currently-selected tab, false
423 throw new Error("Not implemented");
427 * @property {boolean} highlighted
428 * Returns true if the tab is highlighted.
433 throw new Error("Not implemented");
437 * @property {string} status
438 * Returns the current loading status of the tab. May be either
439 * "loading" or "complete".
444 throw new Error("Not implemented");
448 * @property {integer} height
449 * Returns the pixel height of the visible area of the tab.
454 throw new Error("Not implemented");
458 * @property {DOMWindow} window
459 * Returns the browser window to which the tab belongs.
464 throw new Error("Not implemented");
468 * @property {integer} window
469 * Returns the numeric ID of the browser window to which the tab belongs.
474 throw new Error("Not implemented");
478 * @property {boolean} attention
479 * Returns true if the tab is drawing attention.
484 throw new Error("Not implemented");
488 * @property {boolean} isArticle
489 * Returns true if the document in the tab can be rendered in reader
495 throw new Error("Not implemented");
499 * @property {boolean} isInReaderMode
500 * Returns true if the document in the tab is being rendered in reader
505 get isInReaderMode() {
506 throw new Error("Not implemented");
510 * @property {integer} successorTabId
514 get successorTabId() {
515 throw new Error("Not implemented");
519 * Returns true if this tab matches the the given query info object. Omitted
520 * or null have no effect on the match.
522 * @param {object} queryInfo
523 * The query info against which to match.
524 * @param {boolean} [queryInfo.active]
525 * Matches against the exact value of the tab's `active` attribute.
526 * @param {boolean} [queryInfo.audible]
527 * Matches against the exact value of the tab's `audible` attribute.
528 * @param {boolean} [queryInfo.autoDiscardable]
529 * Matches against the exact value of the tab's `autoDiscardable` attribute.
530 * @param {string} [queryInfo.cookieStoreId]
531 * Matches against the exact value of the tab's `cookieStoreId` attribute.
532 * @param {boolean} [queryInfo.discarded]
533 * Matches against the exact value of the tab's `discarded` attribute.
534 * @param {boolean} [queryInfo.hidden]
535 * Matches against the exact value of the tab's `hidden` attribute.
536 * @param {boolean} [queryInfo.highlighted]
537 * Matches against the exact value of the tab's `highlighted` attribute.
538 * @param {integer} [queryInfo.index]
539 * Matches against the exact value of the tab's `index` attribute.
540 * @param {boolean} [queryInfo.muted]
541 * Matches against the exact value of the tab's `mutedInfo.muted` attribute.
542 * @param {boolean} [queryInfo.pinned]
543 * Matches against the exact value of the tab's `pinned` attribute.
544 * @param {string} [queryInfo.status]
545 * Matches against the exact value of the tab's `status` attribute.
546 * @param {string} [queryInfo.title]
547 * Matches against the exact value of the tab's `title` attribute.
548 * @param {string|boolean } [queryInfo.screen]
549 * Matches against the exact value of the tab's `sharingState.screen` attribute, or use true to match any screen sharing tab.
550 * @param {boolean} [queryInfo.camera]
551 * Matches against the exact value of the tab's `sharingState.camera` attribute.
552 * @param {boolean} [queryInfo.microphone]
553 * Matches against the exact value of the tab's `sharingState.microphone` attribute.
555 * Note: Per specification, this should perform a pattern match, rather
556 * than an exact value match, and will do so in the future.
557 * @param {MatchPattern} [queryInfo.url]
558 * Requires the tab's URL to match the given MatchPattern object.
561 * True if the tab matches the query.
577 function checkProperty(prop, obj) {
578 return queryInfo[prop] != null && queryInfo[prop] !== obj[prop];
581 if (PROPS.some(prop => checkProperty(prop, this))) {
585 if (checkProperty("muted", this.mutedInfo)) {
589 let state = this.sharingState;
590 if (["camera", "microphone"].some(prop => checkProperty(prop, state))) {
593 // query for screen can be boolean (ie. any) or string (ie. specific).
594 if (queryInfo.screen !== null) {
596 typeof queryInfo.screen == "boolean"
597 ? queryInfo.screen === !!state.screen
598 : queryInfo.screen === state.screen;
604 if (queryInfo.cookieStoreId) {
605 if (!queryInfo.cookieStoreId.includes(this.cookieStoreId)) {
610 if (queryInfo.url || queryInfo.title) {
611 if (!this.hasTabPermission) {
614 // Using _uri and _title instead of url/title to avoid repeated permission checks.
615 if (queryInfo.url && !queryInfo.url.matches(this._uri)) {
618 if (queryInfo.title && !queryInfo.title.matches(this._title)) {
627 * Converts this tab object to a JSON-compatible object containing the values
628 * of its properties which the extension is permitted to access, in the format
629 * required to be returned by WebExtension APIs.
631 * @param {object} [fallbackTabSize]
632 * A geometry data if the lazy geometry data for this tab hasn't been
636 convert(fallbackTabSize = null) {
640 windowId: this.windowId,
641 highlighted: this.highlighted,
643 attention: this.attention,
647 discarded: this.discarded,
648 incognito: this.incognito,
651 lastAccessed: this.lastAccessed,
652 audible: this.audible,
653 autoDiscardable: this.autoDiscardable,
654 mutedInfo: this.mutedInfo,
655 isArticle: this.isArticle,
656 isInReaderMode: this.isInReaderMode,
657 sharingState: this.sharingState,
658 successorTabId: this.successorTabId,
659 cookieStoreId: this.cookieStoreId,
662 // If the tab has not been fully layed-out yet, fallback to the geometry
663 // from a different tab (usually the currently active tab).
664 if (fallbackTabSize && (!result.width || !result.height)) {
665 result.width = fallbackTabSize.width;
666 result.height = fallbackTabSize.height;
669 let opener = this.openerTabId;
671 result.openerTabId = opener;
674 if (this.hasTabPermission) {
675 for (let prop of ["url", "title", "favIconUrl"]) {
676 // We use the underscored variants here to avoid the redundant
677 // permissions checks imposed on the public properties.
678 let val = this[`_${prop}`];
689 * Query each content process hosting subframes of the tab, return results.
691 * @param {string} message
692 * @param {object} options
693 * These options are also sent to the message handler in the
694 * `ExtensionContentChild`.
695 * @param {number[]} options.frameIds
696 * When omitted, all frames will be queried.
697 * @param {boolean} options.returnResultsWithFrameIds
698 * @returns {Promise[]}
700 async queryContent(message, options) {
701 let { frameIds } = options;
703 /** @type {Map<nsIDOMProcessParent, innerWindowId[]>} */
704 let byProcess = new DefaultMap(() => []);
705 // We use this set to know which frame IDs are potentially invalid (as in
706 // not found when visiting the tab's BC tree below) when frameIds is a
707 // non-empty list of frame IDs.
708 let frameIdsSet = new Set(frameIds);
710 // Recursively walk the tab's BC tree, find all frames, group by process.
712 let win = bc.currentWindowGlobal;
713 let frameId = bc.parent ? bc.id : 0;
715 if (win?.domProcess && (!frameIds || frameIdsSet.has(frameId))) {
716 byProcess.get(win.domProcess).push(win.innerWindowId);
717 frameIdsSet.delete(frameId);
720 if (!frameIds || frameIdsSet.size > 0) {
721 bc.children.forEach(visit);
724 visit(this.browsingContext);
726 if (frameIdsSet.size > 0) {
727 throw new ExtensionError(
728 `Invalid frame IDs: [${Array.from(frameIdsSet).join(", ")}].`
732 let promises = Array.from(byProcess.entries(), ([proc, windows]) =>
733 proc.getActor("ExtensionContent").sendQuery(message, { windows, options })
736 let results = await Promise.all(promises).catch(err => {
737 if (err.name === "DataCloneError") {
738 let fileName = options.jsPaths.slice(-1)[0] || "<anonymous code>";
739 let message = `Script '${fileName}' result is non-structured-clonable data`;
740 return Promise.reject({ message, fileName });
744 results = results.flat();
746 if (!results.length) {
747 let errorMessage = "Missing host permission for the tab";
748 if (!frameIds || frameIds.length > 1 || frameIds[0] !== 0) {
749 errorMessage += " or frames";
752 throw new ExtensionError(errorMessage);
755 if (frameIds && frameIds.length === 1 && results.length > 1) {
756 throw new ExtensionError("Internal error: multiple windows matched");
763 * Inserts a script or stylesheet in the given tab, and returns a promise
764 * which resolves when the operation has completed.
766 * @param {BaseContext} context
767 * The extension context for which to perform the injection.
768 * @param {InjectDetails} details
769 * The InjectDetails object, specifying what to inject, where, and
771 * @param {string} kind
772 * The kind of data being injected. Either "script" or "css".
773 * @param {string} method
774 * The name of the method which was called to trigger the injection.
775 * Used to generate appropriate error messages on failure.
778 * Resolves to the result of the execution, once it has completed.
781 _execute(context, details, kind, method) {
785 removeCSS: method == "removeCSS",
786 extensionId: context.extension.id,
789 // We require a `code` or a `file` property, but we can't accept both.
790 if ((details.code === null) == (details.file === null)) {
791 return Promise.reject({
792 message: `${method} requires either a 'code' or a 'file' property, but not both`,
796 if (details.frameId !== null && details.allFrames) {
797 return Promise.reject({
798 message: `'frameId' and 'allFrames' are mutually exclusive`,
802 options.hasActiveTabPermission = this.hasActiveTabPermission;
803 options.matches = this.extension.allowedOrigins.patterns.map(
807 if (details.code !== null) {
808 options[`${kind}Code`] = details.code;
810 if (details.file !== null) {
811 let url = context.uri.resolve(details.file);
812 if (!this.extension.isExtensionURL(url)) {
813 return Promise.reject({
814 message: "Files to be injected must be within the extension",
817 options[`${kind}Paths`].push(url);
820 if (details.allFrames) {
821 options.allFrames = true;
822 } else if (details.frameId !== null) {
823 options.frameIds = [details.frameId];
824 } else if (!details.allFrames) {
825 options.frameIds = [0];
828 if (details.matchAboutBlank) {
829 options.matchAboutBlank = details.matchAboutBlank;
831 if (details.runAt !== null) {
832 options.runAt = details.runAt;
834 options.runAt = "document_idle";
836 if (details.cssOrigin !== null) {
837 options.cssOrigin = details.cssOrigin;
839 options.cssOrigin = "author";
842 options.wantReturnValue = true;
844 // The scripting API (defined in `parent/ext-scripting.js`) has its own
845 // `execute()` function that calls `queryContent()` as well. Make sure to
846 // keep both in sync when relevant.
847 return this.queryContent("Execute", options);
851 * Executes a script in the tab's content window, and returns a Promise which
852 * resolves to the result of the evaluation, or rejects to the value of any
853 * error the injection generates.
855 * @param {BaseContext} context
856 * The extension context for which to inject the script.
857 * @param {InjectDetails} details
858 * The InjectDetails object, specifying what to inject, where, and
862 * Resolves to the result of the evaluation of the given script, once
863 * it has completed, or rejects with any error the evaluation
866 executeScript(context, details) {
867 return this._execute(context, details, "js", "executeScript");
871 * Injects CSS into the tab's content window, and returns a Promise which
872 * resolves when the injection is complete.
874 * @param {BaseContext} context
875 * The extension context for which to inject the script.
876 * @param {InjectDetails} details
877 * The InjectDetails object, specifying what to inject, and where.
880 * Resolves when the injection has completed.
882 insertCSS(context, details) {
883 return this._execute(context, details, "css", "insertCSS").then(() => {});
887 * Removes CSS which was previously into the tab's content window via
888 * `insertCSS`, and returns a Promise which resolves when the operation is
891 * @param {BaseContext} context
892 * The extension context for which to remove the CSS.
893 * @param {InjectDetails} details
894 * The InjectDetails object, specifying what to remove, and from where.
897 * Resolves when the operation has completed.
899 removeCSS(context, details) {
900 return this._execute(context, details, "css", "removeCSS").then(() => {});
904 defineLazyGetter(TabBase.prototype, "incognito", function () {
905 return this._incognito;
908 // Note: These must match the values in windows.json.
909 const WINDOW_ID_NONE = -1;
910 const WINDOW_ID_CURRENT = -2;
913 * A platform-independent base class for extension-specific wrappers around
914 * native browser windows
916 * @param {Extension} extension
917 * The extension object for which this wrapper is being created.
918 * @param {DOMWindow} window
919 * The browser DOM window which is being wrapped.
920 * @param {integer} id
921 * The numeric ID of this DOM window object. This ID should be the same for
922 * every extension, and for the lifetime of the window.
925 constructor(extension, window, id) {
926 if (!extension.canAccessWindow(window)) {
927 throw new ExtensionError("extension cannot access window");
929 this.extension = extension;
930 this.window = window;
935 * @property {nsIAppWindow} appWindow
936 * The nsIAppWindow object for this browser window.
940 return this.window.docShell.treeOwner
941 .QueryInterface(Ci.nsIInterfaceRequestor)
942 .getInterface(Ci.nsIAppWindow);
946 * Returns true if this window is the current window for the given extension
947 * context, false otherwise.
949 * @param {BaseContext} context
950 * The extension context for which to perform the check.
954 isCurrentFor(context) {
955 if (context && context.currentWindow) {
956 return this.window === context.currentWindow;
958 return this.isLastFocused;
962 * @property {string} type
963 * The type of the window, as defined by the WebExtension API. May be
964 * either "normal" or "popup".
968 let { chromeFlags } = this.appWindow;
970 if (chromeFlags & Ci.nsIWebBrowserChrome.CHROME_OPENAS_DIALOG) {
978 * Converts this window object to a JSON-compatible object which may be
979 * returned to an extension, in the format required to be returned by
982 * @param {object} [getInfo]
983 * An optional object, the properties of which determine what data is
984 * available on the result object.
985 * @param {boolean} [getInfo.populate]
986 * Of true, the result object will contain a `tabs` property,
987 * containing an array of converted Tab objects, one for each tab in
995 focused: this.focused,
1000 incognito: this.incognito,
1003 alwaysOnTop: this.alwaysOnTop,
1007 if (getInfo && getInfo.populate) {
1008 result.tabs = Array.from(this.getTabs(), tab => tab.convert());
1015 * Returns true if this window matches the the given query info object. Omitted
1016 * or null have no effect on the match.
1018 * @param {object} queryInfo
1019 * The query info against which to match.
1020 * @param {boolean} [queryInfo.currentWindow]
1021 * Matches against against the return value of `isCurrentFor()` for the
1023 * @param {boolean} [queryInfo.lastFocusedWindow]
1024 * Matches against the exact value of the window's `isLastFocused` attribute.
1025 * @param {boolean} [queryInfo.windowId]
1026 * Matches against the exact value of the window's ID, taking into
1027 * account the special WINDOW_ID_CURRENT value.
1028 * @param {string} [queryInfo.windowType]
1029 * Matches against the exact value of the window's `type` attribute.
1030 * @param {BaseContext} context
1031 * The extension context for which the matching is being performed.
1032 * Used to determine the current window for relevant properties.
1034 * @returns {boolean}
1035 * True if the window matches the query.
1037 matches(queryInfo, context) {
1039 queryInfo.lastFocusedWindow !== null &&
1040 queryInfo.lastFocusedWindow !== this.isLastFocused
1045 if (queryInfo.windowType !== null && queryInfo.windowType !== this.type) {
1049 if (queryInfo.windowId !== null) {
1050 if (queryInfo.windowId === WINDOW_ID_CURRENT) {
1051 if (!this.isCurrentFor(context)) {
1054 } else if (queryInfo.windowId !== this.id) {
1060 queryInfo.currentWindow !== null &&
1061 queryInfo.currentWindow !== this.isCurrentFor(context)
1070 * @property {boolean} focused
1071 * Returns true if the browser window is currently focused.
1076 throw new Error("Not implemented");
1080 * @property {integer} top
1081 * Returns the pixel offset of the top of the window from the top of
1087 throw new Error("Not implemented");
1091 * @property {integer} left
1092 * Returns the pixel offset of the left of the window from the left of
1098 throw new Error("Not implemented");
1102 * @property {integer} width
1103 * Returns the pixel width of the window.
1108 throw new Error("Not implemented");
1112 * @property {integer} height
1113 * Returns the pixel height of the window.
1118 throw new Error("Not implemented");
1122 * @property {boolean} incognito
1123 * Returns true if this is a private browsing window, false otherwise.
1128 throw new Error("Not implemented");
1132 * @property {boolean} alwaysOnTop
1133 * Returns true if this window is constrained to always remain above
1139 throw new Error("Not implemented");
1143 * @property {boolean} isLastFocused
1144 * Returns true if this is the browser window which most recently had
1149 get isLastFocused() {
1150 throw new Error("Not implemented");
1154 * @property {string} state
1155 * Returns or sets the current state of this window, as determined by
1160 throw new Error("Not implemented");
1164 throw new Error("Not implemented");
1168 * @property {nsIURI | null} title
1169 * Returns the current title of this window if the extension has permission
1170 * to read it, or null otherwise.
1174 // activeTab may be null when a new window is adopting an existing tab as its first tab
1175 // (See Bug 1458918 for rationale).
1176 if (this.activeTab && this.activeTab.hasTabPermission) {
1181 // The JSDoc validator does not support @returns tags in abstract functions or
1182 // star functions without return statements.
1183 /* eslint-disable valid-jsdoc */
1185 * Returns the window state of the given window.
1187 * @param {DOMWindow} window
1188 * The window for which to return a state.
1191 * The window's state. One of "normal", "minimized", "maximized",
1192 * "fullscreen", or "docked".
1196 static getState(window) {
1197 throw new Error("Not implemented");
1201 * Returns an iterator of TabBase objects for each tab in this window.
1203 * @returns {Iterator<TabBase>}
1206 throw new Error("Not implemented");
1210 * Returns an iterator of TabBase objects for each highlighted tab in this window.
1212 * @returns {Iterator<TabBase>}
1214 getHighlightedTabs() {
1215 throw new Error("Not implemented");
1219 * @property {TabBase} The window's currently active tab.
1222 throw new Error("Not implemented");
1226 * Returns the window's tab at the specified index.
1228 * @param {integer} index
1229 * The index of the desired tab.
1231 * @returns {TabBase|undefined}
1233 getTabAtIndex(index) {
1234 throw new Error("Not implemented");
1236 /* eslint-enable valid-jsdoc */
1239 Object.assign(WindowBase, { WINDOW_ID_NONE, WINDOW_ID_CURRENT });
1242 * The parameter type of "tab-attached" events, which are emitted when a
1243 * pre-existing tab is attached to a new window.
1245 * @typedef {object} TabAttachedEvent
1246 * @property {NativeTab} tab
1247 * The native tab object in the window to which the tab is being
1248 * attached. This may be a different object than was used to represent
1249 * the tab in the old window.
1250 * @property {integer} tabId
1251 * The ID of the tab being attached.
1252 * @property {integer} newWindowId
1253 * The ID of the window to which the tab is being attached.
1254 * @property {integer} newPosition
1255 * The position of the tab in the tab list of the new window.
1259 * The parameter type of "tab-detached" events, which are emitted when a
1260 * pre-existing tab is detached from a window, in order to be attached to a new
1263 * @typedef {object} TabDetachedEvent
1264 * @property {NativeTab} tab
1265 * The native tab object in the window from which the tab is being
1266 * detached. This may be a different object than will be used to
1267 * represent the tab in the new window.
1268 * @property {NativeTab} adoptedBy
1269 * The native tab object in the window to which the tab will be attached,
1270 * and is adopting the contents of this tab. This may be a different
1271 * object than the tab in the previous window.
1272 * @property {integer} tabId
1273 * The ID of the tab being detached.
1274 * @property {integer} oldWindowId
1275 * The ID of the window from which the tab is being detached.
1276 * @property {integer} oldPosition
1277 * The position of the tab in the tab list of the window from which it is
1282 * The parameter type of "tab-created" events, which are emitted when a
1283 * new tab is created.
1285 * @typedef {object} TabCreatedEvent
1286 * @property {NativeTab} tab
1287 * The native tab object for the tab which is being created.
1291 * The parameter type of "tab-removed" events, which are emitted when a
1292 * tab is removed and destroyed.
1294 * @typedef {object} TabRemovedEvent
1295 * @property {NativeTab} tab
1296 * The native tab object for the tab which is being removed.
1297 * @property {integer} tabId
1298 * The ID of the tab being removed.
1299 * @property {integer} windowId
1300 * The ID of the window from which the tab is being removed.
1301 * @property {boolean} isWindowClosing
1302 * True if the tab is being removed because the window is closing.
1306 * An object containing basic, extension-independent information about the window
1307 * and tab that a XUL <browser> belongs to.
1309 * @typedef {object} BrowserData
1310 * @property {integer} tabId
1311 * The numeric ID of the tab that a <browser> belongs to, or -1 if it
1312 * does not belong to a tab.
1313 * @property {integer} windowId
1314 * The numeric ID of the browser window that a <browser> belongs to, or -1
1315 * if it does not belong to a browser window.
1319 * A platform-independent base class for the platform-specific TabTracker
1320 * classes, which track the opening and closing of tabs, and manage the mapping
1321 * of them between numeric IDs and native tab objects.
1323 * Instances of this class are EventEmitters which emit the following events,
1324 * each with an argument of the given type:
1326 * - "tab-attached" {@link TabAttacheEvent}
1327 * - "tab-detached" {@link TabDetachedEvent}
1328 * - "tab-created" {@link TabCreatedEvent}
1329 * - "tab-removed" {@link TabRemovedEvent}
1331 class TabTrackerBase extends EventEmitter {
1333 if (!this.initialized) {
1337 return super.on(...args); // eslint-disable-line mozilla/balanced-listeners
1341 * Called to initialize the tab tracking listeners the first time that an
1342 * event listener is added.
1348 throw new Error("Not implemented");
1351 // The JSDoc validator does not support @returns tags in abstract functions or
1352 // star functions without return statements.
1353 /* eslint-disable valid-jsdoc */
1355 * Returns the numeric ID for the given native tab.
1357 * @param {NativeTab} nativeTab
1358 * The native tab for which to return an ID.
1360 * @returns {integer}
1361 * The tab's numeric ID.
1365 throw new Error("Not implemented");
1369 * Returns the native tab with the given numeric ID.
1371 * @param {integer} tabId
1372 * The numeric ID of the tab to return.
1373 * @param {*} default_
1374 * The value to return if no tab exists with the given ID.
1376 * @returns {NativeTab}
1377 * @throws {ExtensionError}
1378 * If no tab exists with the given ID and a default return value is not
1382 getTab(tabId, default_ = undefined) {
1383 throw new Error("Not implemented");
1387 * Returns basic information about the tab and window that the given browser
1390 * @param {XULElement} browser
1391 * The XUL browser element for which to return data.
1393 * @returns {BrowserData}
1396 /* eslint-enable valid-jsdoc */
1397 getBrowserData(browser) {
1398 throw new Error("Not implemented");
1402 * @property {NativeTab} activeTab
1403 * Returns the native tab object for the active tab in the
1404 * most-recently focused window, or null if no live tabs currently
1409 throw new Error("Not implemented");
1414 * A browser progress listener instance which calls a given listener function
1415 * whenever the status of the given browser changes.
1417 * @param {function(object)} listener
1418 * A function to be called whenever the status of a tab's top-level
1419 * browser. It is passed an object with a `browser` property pointing to
1420 * the XUL browser, and a `status` property with a string description of
1421 * the browser's status.
1424 class StatusListener {
1425 constructor(listener) {
1426 this.listener = listener;
1429 onStateChange(browser, webProgress, request, stateFlags, statusCode) {
1430 if (!webProgress.isTopLevel) {
1435 if (stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
1436 if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
1438 } else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
1439 status = "complete";
1442 stateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
1443 statusCode == Cr.NS_BINDING_ABORTED
1445 status = "complete";
1449 this.listener({ browser, status });
1453 onLocationChange(browser, webProgress, request, locationURI, flags) {
1454 if (webProgress.isTopLevel) {
1455 let status = webProgress.isLoadingDocument ? "loading" : "complete";
1456 this.listener({ browser, status, url: locationURI.spec });
1462 * A platform-independent base class for the platform-specific WindowTracker
1463 * classes, which track the opening and closing of windows, and manage the
1464 * mapping of them between numeric IDs and native tab objects.
1466 class WindowTrackerBase extends EventEmitter {
1470 this._handleWindowOpened = this._handleWindowOpened.bind(this);
1472 this._openListeners = new Set();
1473 this._closeListeners = new Set();
1475 this._listeners = new DefaultMap(() => new Set());
1477 this._statusListeners = new DefaultWeakMap(listener => {
1478 return new StatusListener(listener);
1481 this._windowIds = new DefaultWeakMap(window => {
1482 return window.docShell.outerWindowID;
1486 isBrowserWindow(window) {
1487 let { documentElement } = window.document;
1489 return documentElement.getAttribute("windowtype") === "navigator:browser";
1492 // The JSDoc validator does not support @returns tags in abstract functions or
1493 // star functions without return statements.
1494 /* eslint-disable valid-jsdoc */
1496 * Returns an iterator for all currently active browser windows.
1498 * @param {boolean} [includeInomplete = false]
1499 * If true, include browser windows which are not yet fully loaded.
1500 * Otherwise, only include windows which are.
1502 * @returns {Iterator<DOMWindow>}
1504 /* eslint-enable valid-jsdoc */
1505 *browserWindows(includeIncomplete = false) {
1506 // The window type parameter is only available once the window's document
1507 // element has been created. This means that, when looking for incomplete
1508 // browser windows, we need to ignore the type entirely for windows which
1509 // haven't finished loading, since we would otherwise skip browser windows
1510 // in their early loading stages.
1511 // This is particularly important given that the "domwindowcreated" event
1512 // fires for browser windows when they're in that in-between state, and just
1513 // before we register our own "domwindowcreated" listener.
1515 for (let window of Services.wm.getEnumerator("")) {
1516 let ok = includeIncomplete;
1517 if (window.document.readyState === "complete") {
1518 ok = this.isBrowserWindow(window);
1528 * @property {DOMWindow|null} topWindow
1529 * The currently active, or topmost, browser window, or null if no
1530 * browser window is currently open.
1534 return Services.wm.getMostRecentWindow("navigator:browser");
1538 * @property {DOMWindow|null} topWindow
1539 * The currently active, or topmost, browser window that is not
1540 * private browsing, or null if no browser window is currently open.
1543 get topNonPBWindow() {
1544 return Services.wm.getMostRecentNonPBWindow("navigator:browser");
1548 * Returns the top window accessible by the extension.
1550 * @param {BaseContext} context
1551 * The extension context for which to return the current window.
1553 * @returns {DOMWindow|null}
1555 getTopWindow(context) {
1556 if (context && !context.privateBrowsingAllowed) {
1557 return this.topNonPBWindow;
1559 return this.topWindow;
1563 * Returns the numeric ID for the given browser window.
1565 * @param {DOMWindow} window
1566 * The DOM window for which to return an ID.
1568 * @returns {integer}
1569 * The window's numeric ID.
1572 return this._windowIds.get(window);
1576 * Returns the browser window to which the given context belongs, or the top
1577 * browser window if the context does not belong to a browser window.
1579 * @param {BaseContext} context
1580 * The extension context for which to return the current window.
1582 * @returns {DOMWindow|null}
1584 getCurrentWindow(context) {
1585 return (context && context.currentWindow) || this.getTopWindow(context);
1589 * Returns the browser window with the given ID.
1591 * @param {integer} id
1592 * The ID of the window to return.
1593 * @param {BaseContext} context
1594 * The extension context for which the matching is being performed.
1595 * Used to determine the current window for relevant properties.
1596 * @param {boolean} [strict = true]
1597 * If false, undefined will be returned instead of throwing an error
1598 * in case no window exists with the given ID.
1600 * @returns {DOMWindow|undefined}
1601 * @throws {ExtensionError}
1602 * If no window exists with the given ID and `strict` is true.
1604 getWindow(id, context, strict = true) {
1605 if (id === WINDOW_ID_CURRENT) {
1606 return this.getCurrentWindow(context);
1609 let window = Services.wm.getOuterWindowWithId(id);
1613 (window.document.readyState !== "complete" ||
1614 this.isBrowserWindow(window))
1616 if (!context || context.canAccessWindow(window)) {
1617 // Tolerate incomplete windows because isBrowserWindow is only reliable
1618 // once the window is fully loaded.
1624 throw new ExtensionError(`Invalid window ID: ${id}`);
1629 * @property {boolean} _haveListeners
1630 * Returns true if any window open or close listeners are currently
1634 get _haveListeners() {
1635 return this._openListeners.size > 0 || this._closeListeners.size > 0;
1639 * Register the given listener function to be called whenever a new browser
1642 * @param {function(DOMWindow)} listener
1643 * The listener function to register.
1645 addOpenListener(listener) {
1646 if (!this._haveListeners) {
1647 Services.ww.registerNotification(this);
1650 this._openListeners.add(listener);
1652 for (let window of this.browserWindows(true)) {
1653 if (window.document.readyState !== "complete") {
1654 window.addEventListener("load", this);
1660 * Unregister a listener function registered in a previous addOpenListener
1663 * @param {function(DOMWindow)} listener
1664 * The listener function to unregister.
1666 removeOpenListener(listener) {
1667 this._openListeners.delete(listener);
1669 if (!this._haveListeners) {
1670 Services.ww.unregisterNotification(this);
1675 * Register the given listener function to be called whenever a browser
1678 * @param {function(DOMWindow)} listener
1679 * The listener function to register.
1681 addCloseListener(listener) {
1682 if (!this._haveListeners) {
1683 Services.ww.registerNotification(this);
1686 this._closeListeners.add(listener);
1690 * Unregister a listener function registered in a previous addCloseListener
1693 * @param {function(DOMWindow)} listener
1694 * The listener function to unregister.
1696 removeCloseListener(listener) {
1697 this._closeListeners.delete(listener);
1699 if (!this._haveListeners) {
1700 Services.ww.unregisterNotification(this);
1705 * Handles load events for recently-opened windows, and adds additional
1706 * listeners which may only be safely added when the window is fully loaded.
1708 * @param {Event} event
1709 * A DOM event to handle.
1712 handleEvent(event) {
1713 if (event.type === "load") {
1714 event.currentTarget.removeEventListener(event.type, this);
1716 let window = event.target.defaultView;
1717 if (!this.isBrowserWindow(window)) {
1721 for (let listener of this._openListeners) {
1732 * Observes "domwindowopened" and "domwindowclosed" events, notifies the
1733 * appropriate listeners, and adds necessary additional listeners to the new
1736 * @param {DOMWindow} window
1738 * @param {string} topic
1739 * The topic being observed.
1742 observe(window, topic) {
1743 if (topic === "domwindowclosed") {
1744 if (!this.isBrowserWindow(window)) {
1748 window.removeEventListener("load", this);
1749 for (let listener of this._closeListeners) {
1756 } else if (topic === "domwindowopened") {
1757 window.addEventListener("load", this);
1762 * Add an event listener to be called whenever the given DOM event is received
1763 * at the top level of any browser window.
1765 * @param {string} type
1766 * The type of event to listen for. May be any valid DOM event name, or
1767 * one of the following special cases:
1769 * - "progress": Adds a tab progress listener to every browser window.
1770 * - "status": Adds a StatusListener to every tab of every browser
1772 * - "domwindowopened": Acts as an alias for addOpenListener.
1773 * - "domwindowclosed": Acts as an alias for addCloseListener.
1774 * @param {Function | object} listener
1775 * The listener to invoke in response to the given events.
1777 * @returns {undefined}
1779 addListener(type, listener) {
1780 if (type === "domwindowopened") {
1781 return this.addOpenListener(listener);
1782 } else if (type === "domwindowclosed") {
1783 return this.addCloseListener(listener);
1786 if (this._listeners.size === 0) {
1787 this.addOpenListener(this._handleWindowOpened);
1790 if (type === "status") {
1791 listener = this._statusListeners.get(listener);
1795 this._listeners.get(type).add(listener);
1797 // Register listener on all existing windows.
1798 for (let window of this.browserWindows()) {
1799 this._addWindowListener(window, type, listener);
1804 * Removes an event listener previously registered via an addListener call.
1806 * @param {string} type
1807 * The type of event to stop listening for.
1808 * @param {Function | object} listener
1809 * The listener to remove.
1811 * @returns {undefined}
1813 removeListener(type, listener) {
1814 if (type === "domwindowopened") {
1815 return this.removeOpenListener(listener);
1816 } else if (type === "domwindowclosed") {
1817 return this.removeCloseListener(listener);
1820 if (type === "status") {
1821 listener = this._statusListeners.get(listener);
1825 let listeners = this._listeners.get(type);
1826 listeners.delete(listener);
1828 if (listeners.size === 0) {
1829 this._listeners.delete(type);
1830 if (this._listeners.size === 0) {
1831 this.removeOpenListener(this._handleWindowOpened);
1835 // Unregister listener from all existing windows.
1836 let useCapture = type === "focus" || type === "blur";
1837 for (let window of this.browserWindows()) {
1838 if (type === "progress") {
1839 this.removeProgressListener(window, listener);
1841 window.removeEventListener(type, listener, useCapture);
1847 * Adds a listener for the given event to the given window.
1849 * @param {DOMWindow} window
1850 * The browser window to which to add the listener.
1851 * @param {string} eventType
1852 * The type of DOM event to listen for, or "progress" to add a tab
1853 * progress listener.
1854 * @param {Function | object} listener
1855 * The listener to add.
1858 _addWindowListener(window, eventType, listener) {
1859 let useCapture = eventType === "focus" || eventType === "blur";
1861 if (eventType === "progress") {
1862 this.addProgressListener(window, listener);
1864 window.addEventListener(eventType, listener, useCapture);
1869 * A private method which is called whenever a new browser window is opened,
1870 * and adds the necessary listeners to it.
1872 * @param {DOMWindow} window
1873 * The window being opened.
1876 _handleWindowOpened(window) {
1877 for (let [eventType, listeners] of this._listeners) {
1878 for (let listener of listeners) {
1879 this._addWindowListener(window, eventType, listener);
1885 * Adds a tab progress listener to the given browser window.
1887 * @param {DOMWindow} window
1888 * The browser window to which to add the listener.
1889 * @param {object} listener
1890 * The tab progress listener to add.
1893 addProgressListener(window, listener) {
1894 throw new Error("Not implemented");
1898 * Removes a tab progress listener from the given browser window.
1900 * @param {DOMWindow} window
1901 * The browser window from which to remove the listener.
1902 * @param {object} listener
1903 * The tab progress listener to remove.
1906 removeProgressListener(window, listener) {
1907 throw new Error("Not implemented");
1912 * Manages native tabs, their wrappers, and their dynamic permissions for a
1913 * particular extension.
1915 * @param {Extension} extension
1916 * The extension for which to manage tabs.
1918 class TabManagerBase {
1919 constructor(extension) {
1920 this.extension = extension;
1922 this._tabs = new DefaultWeakMap(tab => this.wrapTab(tab));
1926 * If the extension has requested activeTab permission, grant it those
1927 * permissions for the current inner window in the given native tab.
1929 * @param {NativeTab} nativeTab
1930 * The native tab for which to grant permissions.
1932 addActiveTabPermission(nativeTab) {
1933 let tab = this.getWrapper(nativeTab);
1935 this.extension.hasPermission("activeTab") ||
1936 (this.extension.originControls &&
1937 this.extension.optionalOrigins.matches(tab._uri))
1939 // Note that, unlike Chrome, we don't currently clear this permission with
1940 // the tab navigates. If the inner window is revived from BFCache before
1941 // we've granted this permission to a new inner window, the extension
1942 // maintains its permissions for it.
1943 tab.activeTabWindowID = tab.innerWindowID;
1948 * Revoke the extension's activeTab permissions for the current inner window
1949 * of the given native tab.
1951 * @param {NativeTab} nativeTab
1952 * The native tab for which to revoke permissions.
1954 revokeActiveTabPermission(nativeTab) {
1955 this.getWrapper(nativeTab).activeTabWindowID = null;
1959 * Returns true if the extension has requested activeTab permission, and has
1960 * been granted permissions for the current inner window if this tab.
1962 * @param {NativeTab} nativeTab
1963 * The native tab for which to check permissions.
1964 * @returns {boolean}
1965 * True if the extension has activeTab permissions for this tab.
1967 hasActiveTabPermission(nativeTab) {
1968 return this.getWrapper(nativeTab).hasActiveTabPermission;
1972 * Activate MV3 content scripts if the extension has activeTab or an
1973 * (ungranted) host permission.
1975 * @param {NativeTab} nativeTab
1977 activateScripts(nativeTab) {
1978 let tab = this.getWrapper(nativeTab);
1980 this.extension.originControls &&
1981 !tab.matchesHostPermission &&
1982 (this.extension.optionalOrigins.matches(tab._uri) ||
1983 this.extension.hasPermission("activeTab")) &&
1984 (this.extension.contentScripts.length ||
1985 this.extension.registeredContentScripts.size)
1987 tab.queryContent("ActivateScripts", { id: this.extension.id });
1992 * Returns true if the extension has permissions to access restricted
1993 * properties of the given native tab. In practice, this means that it has
1994 * either requested the "tabs" permission or has activeTab permissions for the
1997 * NOTE: Never use this method on an object that is not a native tab
1998 * for the current platform: this method implicitly generates a wrapper
1999 * for the passed nativeTab parameter and the platform-specific tabTracker
2000 * instance is likely to store it in a map which is cleared only when the
2001 * tab is closed (and so, if nativeTab is not a real native tab, it will
2002 * never be cleared from the platform-specific tabTracker instance),
2003 * See Bug 1458918 for a rationale.
2005 * @param {NativeTab} nativeTab
2006 * The native tab for which to check permissions.
2007 * @returns {boolean}
2008 * True if the extension has permissions for this tab.
2010 hasTabPermission(nativeTab) {
2011 return this.getWrapper(nativeTab).hasTabPermission;
2015 * Returns this extension's TabBase wrapper for the given native tab. This
2016 * method will always return the same wrapper object for any given native tab.
2018 * @param {NativeTab} nativeTab
2019 * The tab for which to return a wrapper.
2021 * @returns {TabBase|undefined}
2022 * The wrapper for this tab.
2024 getWrapper(nativeTab) {
2025 if (this.canAccessTab(nativeTab)) {
2026 return this._tabs.get(nativeTab);
2031 * Determines access using extension context.
2033 * @param {NativeTab} nativeTab
2034 * The tab to check access on.
2035 * @returns {boolean}
2036 * True if the extension has permissions for this tab.
2040 canAccessTab(nativeTab) {
2041 throw new Error("Not implemented");
2045 * Converts the given native tab to a JSON-compatible object, in the format
2046 * required to be returned by WebExtension APIs, which may be safely passed to
2049 * @param {NativeTab} nativeTab
2050 * The native tab to convert.
2051 * @param {object} [fallbackTabSize]
2052 * A geometry data if the lazy geometry data for this tab hasn't been
2057 convert(nativeTab, fallbackTabSize = null) {
2058 return this.getWrapper(nativeTab).convert(fallbackTabSize);
2061 // The JSDoc validator does not support @returns tags in abstract functions or
2062 // star functions without return statements.
2063 /* eslint-disable valid-jsdoc */
2065 * Returns an iterator of TabBase objects which match the given query info.
2067 * @param {object | null} [queryInfo = null]
2068 * An object containing properties on which to filter. May contain any
2069 * properties which are recognized by {@link TabBase#matches} or
2070 * {@link WindowBase#matches}. Unknown properties will be ignored.
2071 * @param {BaseContext|null} [context = null]
2072 * The extension context for which the matching is being performed.
2073 * Used to determine the current window for relevant properties.
2075 * @returns {Iterator<TabBase>}
2077 *query(queryInfo = null, context = null) {
2079 if (queryInfo.url !== null) {
2080 queryInfo.url = parseMatchPatterns([].concat(queryInfo.url), {
2081 restrictSchemes: false,
2085 if (queryInfo.cookieStoreId !== null) {
2086 queryInfo.cookieStoreId = [].concat(queryInfo.cookieStoreId);
2089 if (queryInfo.title !== null) {
2091 queryInfo.title = new MatchGlob(queryInfo.title);
2093 throw new ExtensionError(`Invalid title: ${queryInfo.title}`);
2097 function* candidates(windowWrapper) {
2099 let { active, highlighted, index } = queryInfo;
2100 if (active === true) {
2101 let { activeTab } = windowWrapper;
2107 if (index != null) {
2108 let tabWrapper = windowWrapper.getTabAtIndex(index);
2114 if (highlighted === true) {
2115 yield* windowWrapper.getHighlightedTabs();
2119 yield* windowWrapper.getTabs();
2121 let windowWrappers = this.extension.windowManager.query(queryInfo, context);
2122 for (let windowWrapper of windowWrappers) {
2123 for (let tabWrapper of candidates(windowWrapper)) {
2124 if (!queryInfo || tabWrapper.matches(queryInfo)) {
2132 * Returns a TabBase wrapper for the tab with the given ID.
2134 * @param {integer} tabId
2135 * The ID of the tab for which to return a wrapper.
2137 * @returns {TabBase}
2138 * @throws {ExtensionError}
2139 * If no tab exists with the given ID.
2143 throw new Error("Not implemented");
2147 * Returns a new TabBase instance wrapping the given native tab.
2149 * @param {NativeTab} nativeTab
2150 * The native tab for which to return a wrapper.
2152 * @returns {TabBase}
2156 /* eslint-enable valid-jsdoc */
2157 wrapTab(nativeTab) {
2158 throw new Error("Not implemented");
2163 * Manages native browser windows and their wrappers for a particular extension.
2165 * @param {Extension} extension
2166 * The extension for which to manage windows.
2168 class WindowManagerBase {
2169 constructor(extension) {
2170 this.extension = extension;
2172 this._windows = new DefaultWeakMap(window => this.wrapWindow(window));
2176 * Converts the given browser window to a JSON-compatible object, in the
2177 * format required to be returned by WebExtension APIs, which may be safely
2178 * passed to extension code.
2180 * @param {DOMWindow} window
2181 * The browser window to convert.
2183 * Additional arguments to be passed to {@link WindowBase#convert}.
2187 convert(window, ...args) {
2188 return this.getWrapper(window).convert(...args);
2192 * Returns this extension's WindowBase wrapper for the given browser window.
2193 * This method will always return the same wrapper object for any given
2196 * @param {DOMWindow} window
2197 * The browser window for which to return a wrapper.
2199 * @returns {WindowBase|undefined}
2200 * The wrapper for this tab.
2202 getWrapper(window) {
2203 if (this.extension.canAccessWindow(window)) {
2204 return this._windows.get(window);
2209 * Returns whether this window can be accessed by the extension in the given
2212 * @param {DOMWindow} window
2213 * The browser window that is being tested
2214 * @param {BaseContext|null} context
2215 * The extension context for which this test is being performed.
2216 * @returns {boolean}
2218 canAccessWindow(window, context) {
2220 (context && context.canAccessWindow(window)) ||
2221 this.extension.canAccessWindow(window)
2225 // The JSDoc validator does not support @returns tags in abstract functions or
2226 // star functions without return statements.
2227 /* eslint-disable valid-jsdoc */
2229 * Returns an iterator of WindowBase objects which match the given query info.
2231 * @param {object | null} [queryInfo = null]
2232 * An object containing properties on which to filter. May contain any
2233 * properties which are recognized by {@link WindowBase#matches}.
2234 * Unknown properties will be ignored.
2235 * @param {BaseContext|null} [context = null]
2236 * The extension context for which the matching is being performed.
2237 * Used to determine the current window for relevant properties.
2239 * @returns {Iterator<WindowBase>}
2241 *query(queryInfo = null, context = null) {
2242 function* candidates(windowManager) {
2244 let { currentWindow, windowId, lastFocusedWindow } = queryInfo;
2245 if (currentWindow === true && windowId == null) {
2246 windowId = WINDOW_ID_CURRENT;
2248 if (windowId != null) {
2249 let window = global.windowTracker.getWindow(windowId, context, false);
2251 yield windowManager.getWrapper(window);
2255 if (lastFocusedWindow === true) {
2256 let window = global.windowTracker.getTopWindow(context);
2258 yield windowManager.getWrapper(window);
2263 yield* windowManager.getAll(context);
2265 for (let windowWrapper of candidates(this)) {
2266 if (!queryInfo || windowWrapper.matches(queryInfo, context)) {
2267 yield windowWrapper;
2273 * Returns a WindowBase wrapper for the browser window with the given ID.
2275 * @param {integer} windowId
2276 * The ID of the browser window for which to return a wrapper.
2277 * @param {BaseContext} context
2278 * The extension context for which the matching is being performed.
2279 * Used to determine the current window for relevant properties.
2281 * @returns {WindowBase}
2282 * @throws {ExtensionError}
2283 * If no window exists with the given ID.
2286 get(windowId, context) {
2287 throw new Error("Not implemented");
2291 * Returns an iterator of WindowBase wrappers for each currently existing
2294 * @returns {Iterator<WindowBase>}
2298 throw new Error("Not implemented");
2302 * Returns a new WindowBase instance wrapping the given browser window.
2304 * @param {DOMWindow} window
2305 * The browser window for which to return a wrapper.
2307 * @returns {WindowBase}
2311 wrapWindow(window) {
2312 throw new Error("Not implemented");
2314 /* eslint-enable valid-jsdoc */
2317 function getUserContextIdForCookieStoreId(
2322 if (!extension.hasPermission("cookies")) {
2323 throw new ExtensionError(
2324 `No permission for cookieStoreId: ${cookieStoreId}`
2328 if (!isValidCookieStoreId(cookieStoreId)) {
2329 throw new ExtensionError(`Illegal cookieStoreId: ${cookieStoreId}`);
2332 if (isPrivateBrowsing && !isPrivateCookieStoreId(cookieStoreId)) {
2333 throw new ExtensionError(
2334 `Illegal to set non-private cookieStoreId in a private window`
2338 if (!isPrivateBrowsing && isPrivateCookieStoreId(cookieStoreId)) {
2339 throw new ExtensionError(
2340 `Illegal to set private cookieStoreId in a non-private window`
2344 if (isContainerCookieStoreId(cookieStoreId)) {
2345 if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
2346 // Container tabs are not supported in perma-private browsing mode - bug 1320757
2347 throw new ExtensionError(
2348 `Contextual identities are unavailable in permanent private browsing mode`
2351 if (!containersEnabled) {
2352 throw new ExtensionError(`Contextual identities are currently disabled`);
2354 let userContextId = getContainerForCookieStoreId(cookieStoreId);
2355 if (!userContextId) {
2356 throw new ExtensionError(
2357 `No cookie store exists with ID ${cookieStoreId}`
2360 if (!extension.canAccessContainer(userContextId)) {
2361 throw new ExtensionError(`Cannot access ${cookieStoreId}`);
2363 return userContextId;
2366 return Services.scriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
2369 Object.assign(global, {
2376 getUserContextIdForCookieStoreId,