diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /browser/components/extensions/parent/ext-windows.js | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/extensions/parent/ext-windows.js')
-rw-r--r-- | browser/components/extensions/parent/ext-windows.js | 443 |
1 files changed, 443 insertions, 0 deletions
diff --git a/browser/components/extensions/parent/ext-windows.js b/browser/components/extensions/parent/ext-windows.js new file mode 100644 index 0000000000..dde88dc880 --- /dev/null +++ b/browser/components/extensions/parent/ext-windows.js @@ -0,0 +1,443 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +ChromeUtils.defineModuleGetter( + this, + "HomePage", + "resource:///modules/HomePage.jsm" +); +ChromeUtils.defineModuleGetter( + this, + "PrivateBrowsingUtils", + "resource://gre/modules/PrivateBrowsingUtils.jsm" +); + +var { promiseObserved } = ExtensionUtils; + +/** + * An event manager API provider which listens for a DOM event in any browser + * window, and calls the given listener function whenever an event is received. + * That listener function receives a `fire` object, which it can use to dispatch + * events to the extension, and a DOM event object. + * + * @param {BaseContext} context + * The extension context which the event manager belongs to. + * @param {string} name + * The API name of the event manager, e.g.,"runtime.onMessage". + * @param {string} event + * The name of the DOM event to listen for. + * @param {function} listener + * The listener function to call when a DOM event is received. + * + * @returns {object} An injectable api for the new event. + */ +function WindowEventManager(context, name, event, listener) { + let register = fire => { + let listener2 = (window, ...args) => { + if (context.canAccessWindow(window)) { + listener(fire, window, ...args); + } + }; + + windowTracker.addListener(event, listener2); + return () => { + windowTracker.removeListener(event, listener2); + }; + }; + + return new EventManager({ context, name, register }).api(); +} + +this.windows = class extends ExtensionAPI { + getAPI(context) { + let { extension } = context; + + const { windowManager } = extension; + + return { + windows: { + onCreated: WindowEventManager( + context, + "windows.onCreated", + "domwindowopened", + (fire, window) => { + fire.async(windowManager.convert(window)); + } + ), + + onRemoved: WindowEventManager( + context, + "windows.onRemoved", + "domwindowclosed", + (fire, window) => { + fire.async(windowTracker.getId(window)); + } + ), + + onFocusChanged: new EventManager({ + context, + name: "windows.onFocusChanged", + register: fire => { + // Keep track of the last windowId used to fire an onFocusChanged event + let lastOnFocusChangedWindowId; + + let listener = event => { + // Wait a tick to avoid firing a superfluous WINDOW_ID_NONE + // event when switching focus between two Firefox windows. + Promise.resolve().then(() => { + let windowId = Window.WINDOW_ID_NONE; + let window = Services.focus.activeWindow; + if (window && context.canAccessWindow(window)) { + windowId = windowTracker.getId(window); + } + if (windowId !== lastOnFocusChangedWindowId) { + fire.async(windowId); + lastOnFocusChangedWindowId = windowId; + } + }); + }; + windowTracker.addListener("focus", listener); + windowTracker.addListener("blur", listener); + return () => { + windowTracker.removeListener("focus", listener); + windowTracker.removeListener("blur", listener); + }; + }, + }).api(), + + get: function(windowId, getInfo) { + let window = windowTracker.getWindow(windowId, context); + if (!window || !context.canAccessWindow(window)) { + return Promise.reject({ + message: `Invalid window ID: ${windowId}`, + }); + } + return Promise.resolve(windowManager.convert(window, getInfo)); + }, + + getCurrent: function(getInfo) { + let window = context.currentWindow || windowTracker.topWindow; + if (!context.canAccessWindow(window)) { + return Promise.reject({ message: `Invalid window` }); + } + return Promise.resolve(windowManager.convert(window, getInfo)); + }, + + getLastFocused: function(getInfo) { + let window = windowTracker.topWindow; + if (!context.canAccessWindow(window)) { + return Promise.reject({ message: `Invalid window` }); + } + return Promise.resolve(windowManager.convert(window, getInfo)); + }, + + getAll: function(getInfo) { + let doNotCheckTypes = + getInfo === null || getInfo.windowTypes === null; + let windows = []; + // incognito access is checked in getAll + for (let win of windowManager.getAll()) { + if (doNotCheckTypes || getInfo.windowTypes.includes(win.type)) { + windows.push(win.convert(getInfo)); + } + } + return windows; + }, + + create: function(createData) { + let needResize = + createData.left !== null || + createData.top !== null || + createData.width !== null || + createData.height !== null; + if (createData.incognito && !context.privateBrowsingAllowed) { + return Promise.reject({ + message: "Extension does not have permission for incognito mode", + }); + } + + if (needResize) { + if (createData.state !== null && createData.state != "normal") { + return Promise.reject({ + message: `"state": "${createData.state}" may not be combined with "left", "top", "width", or "height"`, + }); + } + createData.state = "normal"; + } + + function mkstr(s) { + let result = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString + ); + result.data = s; + return result; + } + + let args = Cc["@mozilla.org/array;1"].createInstance( + Ci.nsIMutableArray + ); + + let principal = context.principal; + if (createData.tabId !== null) { + if (createData.url !== null) { + return Promise.reject({ + message: "`tabId` may not be used in conjunction with `url`", + }); + } + + if (createData.allowScriptsToClose) { + return Promise.reject({ + message: + "`tabId` may not be used in conjunction with `allowScriptsToClose`", + }); + } + + let tab = tabTracker.getTab(createData.tabId); + if (!context.canAccessWindow(tab.ownerGlobal)) { + return Promise.reject({ + message: `Invalid tab ID: ${createData.tabId}`, + }); + } + // Private browsing tabs can only be moved to private browsing + // windows. + let incognito = PrivateBrowsingUtils.isBrowserPrivate( + tab.linkedBrowser + ); + if ( + createData.incognito !== null && + createData.incognito != incognito + ) { + return Promise.reject({ + message: + "`incognito` property must match the incognito state of tab", + }); + } + createData.incognito = incognito; + + if ( + createData.cookieStoreId && + createData.cookieStoreId !== + getCookieStoreIdForTab(createData, tab) + ) { + return Promise.reject({ + message: "`cookieStoreId` must match the tab's cookieStoreId", + }); + } + + args.appendElement(tab); + } else if (createData.url !== null) { + if (Array.isArray(createData.url)) { + let array = Cc["@mozilla.org/array;1"].createInstance( + Ci.nsIMutableArray + ); + for (let url of createData.url) { + array.appendElement(mkstr(url)); + } + args.appendElement(array); + } else { + args.appendElement(mkstr(createData.url)); + } + } else { + let url = + createData.incognito && + !PrivateBrowsingUtils.permanentPrivateBrowsing + ? "about:privatebrowsing" + : HomePage.get().split("|", 1)[0]; + args.appendElement(mkstr(url)); + + if ( + url.startsWith("about:") && + !context.checkLoadURL(url, { dontReportErrors: true }) + ) { + // The extension principal cannot directly load about:-URLs, + // except for about:blank. So use the system principal instead. + principal = Services.scriptSecurityManager.getSystemPrincipal(); + } + } + + args.appendElement(null); // unused + args.appendElement(null); // referrerInfo + args.appendElement(null); // postData + args.appendElement(null); // allowThirdPartyFixup + + if (createData.cookieStoreId) { + let userContextIdSupports = Cc[ + "@mozilla.org/supports-PRUint32;1" + ].createInstance(Ci.nsISupportsPRUint32); + // May throw if validation fails. + userContextIdSupports.data = getUserContextIdForCookieStoreId( + extension, + createData.cookieStoreId, + createData.incognito + ); + args.appendElement(userContextIdSupports); // userContextId + } else { + args.appendElement(null); + } + + args.appendElement(context.principal); // originPrincipal - not important. + args.appendElement(context.principal); // originStoragePrincipal - not important. + args.appendElement(principal); // triggeringPrincipal + args.appendElement( + Cc["@mozilla.org/supports-PRBool;1"].createInstance( + Ci.nsISupportsPRBool + ) + ); // allowInheritPrincipal + // There is no CSP associated with this extension, hence we explicitly pass null as the CSP argument. + args.appendElement(null); // csp + + let features = ["chrome"]; + + if (createData.type === null || createData.type == "normal") { + features.push("dialog=no", "all"); + } else { + // All other types create "popup"-type windows by default. + features.push( + "dialog", + "resizable", + "minimizable", + "centerscreen", + "titlebar", + "close" + ); + } + + if (createData.incognito !== null) { + if (createData.incognito) { + if (!PrivateBrowsingUtils.enabled) { + return Promise.reject({ + message: + "`incognito` cannot be used if incognito mode is disabled", + }); + } + features.push("private"); + } else { + features.push("non-private"); + } + } + + let { allowScriptsToClose, url } = createData; + if (allowScriptsToClose === null) { + allowScriptsToClose = + typeof url === "string" && url.startsWith("moz-extension://"); + } + + let window = Services.ww.openWindow( + null, + AppConstants.BROWSER_CHROME_URL, + "_blank", + features.join(","), + args + ); + + let win = windowManager.getWrapper(window); + win.updateGeometry(createData); + + // TODO: focused, type + + return new Promise(resolve => { + window.addEventListener( + "DOMContentLoaded", + function() { + if (allowScriptsToClose) { + window.gBrowserAllowScriptsToCloseInitialTabs = true; + } + resolve( + promiseObserved( + "browser-delayed-startup-finished", + win => win == window + ) + ); + }, + { once: true } + ); + }).then(() => { + if ( + [ + "minimized", + "fullscreen", + "docked", + "normal", + "maximized", + ].includes(createData.state) + ) { + win.state = createData.state; + } + if (createData.titlePreface !== null) { + win.setTitlePreface(createData.titlePreface); + } + return win.convert({ populate: true }); + }); + }, + + update: function(windowId, updateInfo) { + if (updateInfo.state !== null && updateInfo.state != "normal") { + if ( + updateInfo.left !== null || + updateInfo.top !== null || + updateInfo.width !== null || + updateInfo.height !== null + ) { + return Promise.reject({ + message: `"state": "${updateInfo.state}" may not be combined with "left", "top", "width", or "height"`, + }); + } + } + + let win = windowManager.get(windowId, context); + if (!win) { + return Promise.reject({ + message: `Invalid window ID: ${windowId}`, + }); + } + if (updateInfo.focused) { + win.window.focus(); + } + + if (updateInfo.state !== null) { + win.state = updateInfo.state; + } + + if (updateInfo.drawAttention) { + // Bug 1257497 - Firefox can't cancel attention actions. + win.window.getAttention(); + } + + win.updateGeometry(updateInfo); + + if (updateInfo.titlePreface !== null) { + win.setTitlePreface(updateInfo.titlePreface); + win.window.gBrowser.updateTitlebar(); + } + + // TODO: All the other properties, focused=false... + + return Promise.resolve(win.convert()); + }, + + remove: function(windowId) { + let window = windowTracker.getWindow(windowId, context); + if (!context.canAccessWindow(window)) { + return Promise.reject({ + message: `Invalid window ID: ${windowId}`, + }); + } + window.close(); + + return new Promise(resolve => { + let listener = () => { + windowTracker.removeListener("domwindowclosed", listener); + resolve(); + }; + windowTracker.addListener("domwindowclosed", listener); + }); + }, + }, + }; + } +}; |