From 998e3796d790738da305d44333a58da0f6380cf6 Mon Sep 17 00:00:00 2001
From: TrickyPR <23250792+trickypr@users.noreply.github.com>
Date: Thu, 25 Apr 2024 15:36:53 +1000
Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=9A=A7=20Provide=20a=20TabBase=20and?=
=?UTF-8?q?=20TabManagerBase=20implementation?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/extensions/lib/parent/ext-browser.js | 108 ++++
apps/extensions/lib/types/utils.d.ts | 609 +++++++++++++++++++++-
libs/link/types/schemaTypes/index.d.ts | 4 +-
3 files changed, 714 insertions(+), 7 deletions(-)
diff --git a/apps/extensions/lib/parent/ext-browser.js b/apps/extensions/lib/parent/ext-browser.js
index 85754dc..afc79e7 100644
--- a/apps/extensions/lib/parent/ext-browser.js
+++ b/apps/extensions/lib/parent/ext-browser.js
@@ -89,3 +89,111 @@ class TabTracker extends TabTrackerBase {
/** @global */
let tabTracker = new TabTracker()
Object.assign(global, { tabTracker })
+
+class Tab extends TabBase {
+ get _favIconUrl() {
+ return this.nativeTab.view.iconUrl
+ }
+
+ get lastAccessed() {
+ return undefined
+ }
+ get audible() {
+ return undefined
+ }
+ get autoDiscardable() {
+ return false
+ }
+ get browser() {
+ return this.nativeTab.view.browser
+ }
+ get cookieStoreId() {
+ return undefined
+ }
+ get discarded() {
+ return undefined
+ }
+ get height() {
+ return this.nativeTab.view.browser.clientHeight
+ }
+ get hidden() {
+ return false
+ }
+ get index() {
+ const window = this.window
+ return (
+ window
+ ?.windowTabs()
+ .findIndex(
+ (tab) => tab.view.browserId == this.nativeTab.view.browserId,
+ ) || -1
+ )
+ }
+ get mutedInfo() {
+ return undefined
+ }
+ get sharingState() {
+ return undefined
+ }
+ get pinned() {
+ return false
+ }
+ get active() {
+ const window = this.window
+ return window?.activeTabId() == this.nativeTab.view.windowBrowserId
+ }
+ get highlighted() {
+ const window = this.window
+ return (
+ window
+ ?.selectedTabIds()
+ .some((tab) => tab == this.nativeTab.view.windowBrowserId) ?? false
+ )
+ }
+ get status() {
+ return this.nativeTab.view.websiteState
+ }
+ get width() {
+ return this.browser.clientWidth
+ }
+ get window() {
+ return lazy.WindowTracker.getWindowWithBrowser(this.nativeTab.view.browser)
+ ?.window
+ }
+ get windowId() {
+ return this.window?.windowId || -1
+ }
+ get attention() {
+ return false
+ }
+ get isArticle() {
+ return false
+ }
+ get isInReaderMode() {
+ return false
+ }
+ get successorTabId() {
+ return undefined
+ }
+}
+
+class TabManager extends TabManagerBase {
+ canAccessTab(_nativeTab) {
+ throw new Error('Method ')
+ }
+ get(tabId) {
+ const results = lazy.WindowTracker.getWindowWithBrowserId(tabId)
+ if (!results) return null
+ return this.wrapTab(results.tab)
+ }
+ /**
+ * @param {NativeTab} nativeTab
+ */
+ wrapTab(nativeTab) {
+ return new Tab(this.extension, nativeTab, nativeTab.view.browserId || -1)
+ }
+}
+
+extensions.on('startup', (type, extension) => {
+ defineLazyGetter(extension, 'tabManager', () => new TabManager(extension))
+})
diff --git a/apps/extensions/lib/types/utils.d.ts b/apps/extensions/lib/types/utils.d.ts
index 42ffd57..5b937bb 100644
--- a/apps/extensions/lib/types/utils.d.ts
+++ b/apps/extensions/lib/types/utils.d.ts
@@ -4,8 +4,6 @@
/* eslint-disable @typescript-eslint/ban-types */
///
-import { Module } from 'module'
-
import { ConduitAddress } from 'resource://gre/modules/ConduitsParent.sys.mjs'
import { Extension } from 'resource://gre/modules/Extension.sys.mjs'
import { SchemaRoot } from 'resource://gre/modules/Schemas.sys.mjs'
@@ -14,6 +12,8 @@ import { PointConduit } from './ConduitChild'
declare global {
type SavedFrame = unknown
+ type NativeTab = import('@browser/tabs').WindowTab
+ type XULElement = Element
/* eslint-disable @typescript-eslint/no-explicit-any */
function getConsole(): any
@@ -897,9 +897,6 @@ declare global {
windowId: number
}
- type NativeTab = any
- type XULElement = any
-
/**
* A platform-independent base class for the platform-specific TabTracker
* classes, which track the opening and closing of tabs, and manage the mapping
@@ -950,4 +947,606 @@ declare global {
*/
abstract get activeTab(): NativeTab | null
}
+
+ /**
+ * 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.
+ */
+ class WindowTrackerBase extends EventEmitter {
+ constructor()
+ isBrowserWindow(window: Window): boolean
+
+ /**
+ * Returns an iterator for all currently active browser windows.
+ *
+ * @param includeIncomplete If true, include browser windows which are not yet fully loaded.
+ * Otherwise, only include windows which are.
+ *
+ * @returns An iterator for all currently active browser windows.
+ */
+ browserWindows(includeIncomplete?: boolean): IterableIterator
+
+ /**
+ * The currently active, or topmost, browser window, or null if no
+ * browser window is currently open.
+ * @readonly
+ */
+ get topWindow(): Window | null
+
+ /**
+ * The currently active, or topmost, browser window that is not
+ * private browsing, or null if no browser window is currently open.
+ * @readonly
+ */
+ get topNonPBWindow(): Window | null
+
+ /**
+ * Returns the top window accessible by the extension.
+ *
+ * @param context The extension context for which to return the current window.
+ *
+ * @returns The top window accessible by the extension.
+ */
+ getTopWindow(context: BaseContext): Window | null
+
+ /**
+ * Returns the numeric ID for the given browser window.
+ *
+ * @param window The DOM window for which to return an ID.
+ *
+ * @returns The window's numeric ID.
+ */
+ getId(window: Window): number
+
+ /**
+ * 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 context The extension context for which to return the current window.
+ *
+ * @returns The browser window to which the given context belongs, or the top browser window.
+ */
+ getCurrentWindow(context: BaseContext): Window | null
+
+ /**
+ * Returns the browser window with the given ID.
+ *
+ * @param id The ID of the window to return.
+ * @param context The extension context for which the matching is being performed.
+ * @param strict If false, undefined will be returned instead of throwing an error in case no window exists with the given ID.
+ *
+ * @returns The browser window with the given ID.
+ * @throws ExtensionError If no window exists with the given ID and `strict` is true.
+ */
+ getWindow(
+ id: number,
+ context: BaseContext,
+ strict?: boolean,
+ ): Window | undefined
+
+ /**
+ * Returns true if any window open or close listeners are currently registered.
+ * @private
+ */
+ get _haveListeners(): boolean
+
+ /**
+ * Register the given listener function to be called whenever a new browser window is opened.
+ *
+ * @param listener The listener function to register.
+ */
+ addOpenListener(listener: (window: Window) => void): void
+ /**
+ * Unregister a listener function registered in a previous addOpenListener call.
+ *
+ * @param listener The listener function to unregister.
+ */
+ removeOpenListener(listener: (window: Window) => void): void
+
+ /**
+ * Register the given listener function to be called whenever a browser window is closed.
+ *
+ * @param listener The listener function to register.
+ */
+ addCloseListener(listener: (window: Window) => void): void
+
+ /**
+ * Unregister a listener function registered in a previous addCloseListener call.
+ *
+ * @param listener The listener function to unregister.
+ */
+ removeCloseListener(listener: (window: Window) => 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 A DOM event to handle.
+ * @private
+ */
+ handleEvent(event: Event): void
+
+ /**
+ * Observes "domwindowopened" and "domwindowclosed" events, notifies the appropriate listeners, and adds necessary additional listeners to the new windows.
+ *
+ * @param window A DOM window.
+ * @param topic The topic being observed.
+ * @private
+ */
+ observe(window: Window, topic: string): void
+
+ /**
+ * Add an event listener to be called whenever the given DOM event is received at the top level of any browser window.
+ *
+ * @param type The type of event to listen for.
+ * @param listener The listener to invoke in response to the given events.
+ */
+ addListener(type: string, listener: Function | object): void
+
+ /**
+ * Removes an event listener previously registered via an addListener call.
+ *
+ * @param type The type of event to stop listening for.
+ * @param listener The listener to remove.
+ */
+ removeListener(type: string, listener: Function | object): void
+
+ /**
+ * Adds a listener for the given event to the given window.
+ *
+ * @param window The browser window to which to add the listener.
+ * @param eventType The type of DOM event to listen for, or "progress" to add a tab progress listener.
+ * @param listener The listener to add.
+ * @private
+ */
+ _addWindowListener(
+ window: Window,
+ eventType: string,
+ listener: Function | object,
+ ): void
+
+ /**
+ * A private method which is called whenever a new browser window is opened, and adds the necessary listeners to it.
+ *
+ * @param window The window being opened.
+ * @private
+ */
+ _handleWindowOpened(window: Window): void
+
+ /**
+ * Adds a tab progress listener to the given browser window.
+ *
+ * @param _window The browser window to which to add the listener.
+ * @param _listener The tab progress listener to add.
+ * @abstract
+ */
+ addProgressListener(_window: Window, _listener: object): void
+
+ /**
+ * Removes a tab progress listener from the given browser window.
+ *
+ * @param _window The browser window from which to remove the listener.
+ * @param _listener The tab progress listener to remove.
+ * @abstract
+ */
+ removeProgressListener(_window: Window, _listener: object): void
+ }
+
+ /**
+ * Manages native tabs, their wrappers, and their dynamic permissions for a
+ * particular extension.
+ */
+ abstract class TabManagerBase {
+ protected extension: Extension
+
+ constructor(extension: Extension)
+
+ /**
+ * If the extension has requested activeTab permission, grant it those permissions for the current inner window in the given native tab.
+ *
+ * @param 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 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 The native tab for which to check permissions.
+ * @returns 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 The native tab.
+ */
+ activateScripts(nativeTab: NativeTab): void
+
+ /**
+ * Returns true if the extension has permissions to access restricted properties of the given native tab.
+ *
+ * @param nativeTab The native tab for which to check permissions.
+ * @returns True if the extension has permissions for this tab.
+ */
+ hasTabPermission(nativeTab: NativeTab): boolean
+
+ /**
+ * Returns this extension's TabBase wrapper for the given native tab.
+ *
+ * @param nativeTab The tab for which to return a wrapper.
+ * @returns The wrapper for this tab.
+ */
+ getWrapper(nativeTab: NativeTab): TabBase | undefined
+
+ /**
+ * Determines access using extension context.
+ *
+ * @param _nativeTab The tab to check access on.
+ * @returns True if the extension has permissions for this tab.
+ */
+ protected abstract canAccessTab(_nativeTab: NativeTab): boolean
+
+ /**
+ * Converts the given native tab to a JSON-compatible object.
+ *
+ * @param nativeTab The native tab to convert.
+ * @param 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 queryInfo An object containing properties on which to filter.
+ * @param context The extension context for which the matching is being performed.
+ * @returns Iterator
+ */
+ query(
+ queryInfo?: object | null,
+ context?: BaseContext | null,
+ ): Iterator
+
+ /**
+ * Returns a TabBase wrapper for the tab with the given ID.
+ *
+ * @param _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: number): TabBase
+
+ /**
+ * Returns a new TabBase instance wrapping the given native tab.
+ *
+ * @param _nativeTab The native tab for which to return a wrapper.
+ * @returns TabBase
+ */
+ protected abstract wrapTab(_nativeTab: NativeTab): TabBase
+ }
+
+ interface QueryInfo {
+ active?: boolean
+ audible?: boolean
+ autoDiscardable?: boolean
+ cookieStoreId?: string
+ discarded?: boolean
+ hidden?: boolean
+ highlighted?: boolean
+ index?: number
+ muted?: boolean
+ pinned?: boolean
+ status?: string
+ title?: string
+ screen?: string | boolean
+ camera?: boolean
+ microphone?: boolean
+ url?: MatchPattern
+ }
+
+ interface Options {
+ frameIds?: number[]
+ returnResultsWithFrameIds?: boolean
+ }
+
+ /**
+ * A platform-independent base class for extension-specific wrappers around
+ * native tab objects.
+ */
+ abstract class TabBase {
+ protected nativeTab: NativeTab
+
+ constructor(extension: Extension, nativeTab: NativeTab, id: number)
+
+ /**
+ * Capture the visible area of this tab, and return the result as a data: URI.
+ *
+ * @param context The extension context for which to perform the capture.
+ * @param zoom The current zoom for the page.
+ * @param options The options with which to perform the capture.
+ * @returns Promise
+ */
+ capture(
+ context: BaseContext,
+ zoom: number,
+ options?: {
+ format?: string
+ quality?: number
+ rect?: DOMRectInit
+ scale?: number
+ },
+ ): Promise
+
+ /**
+ * @property innerWindowID
+ * @readonly
+ */
+ readonly innerWindowID: number | null
+
+ /**
+ * @property hasTabPermission
+ * @readonly
+ */
+ readonly hasTabPermission: boolean
+
+ /**
+ * @property hasActiveTabPermission
+ * @readonly
+ */
+ readonly hasActiveTabPermission: boolean
+
+ /**
+ * @property matchesHostPermission
+ * @readonly
+ */
+ readonly matchesHostPermission: boolean
+
+ /**
+ * @property incognito
+ * @readonly
+ */
+ readonly _incognito: boolean
+
+ /**
+ * @property _url
+ * @readonly
+ */
+ readonly _url: string
+
+ /**
+ * @property url
+ * @readonly
+ */
+ readonly url: string | undefined
+
+ /**
+ * @property _uri
+ * @readonly
+ */
+ readonly _uri: nsIURIType
+
+ /**
+ * @property _title
+ * @readonly
+ */
+ readonly _title: string
+
+ /**
+ * @property title
+ * @readonly
+ */
+ readonly title: nsIURIType | undefined
+
+ /**
+ * @property _favIconUrl
+ * @readonly
+ * @abstract
+ */
+ abstract readonly _favIconUrl: string | undefined
+
+ /**
+ * @property faviconUrl
+ * @readonly
+ */
+ readonly favIconUrl: nsIURIType | undefined
+
+ /**
+ * @property lastAccessed
+ * @readonly
+ * @abstract
+ */
+ abstract readonly lastAccessed: number | undefined
+
+ /**
+ * @property audible
+ * @readonly
+ * @abstract
+ */
+ abstract readonly audible: boolean | undefined
+
+ /**
+ * @property autoDiscardable
+ * @readonly
+ * @abstract
+ */
+ abstract readonly autoDiscardable: boolean
+
+ /**
+ * @property browser
+ * @readonly
+ * @abstract
+ */
+ abstract readonly browser: XULElement
+
+ /**
+ * @property browsingContext
+ * @readonly
+ */
+ readonly browsingContext: BrowsingContext
+
+ /**
+ * @property frameLoader
+ * @readonly
+ */
+ readonly frameLoader: FrameLoader
+
+ /**
+ * @property cookieStoreId
+ * @readonly
+ * @abstract
+ */
+ abstract readonly cookieStoreId: string | undefined
+
+ /**
+ * @property openerTabId
+ * @readonly
+ */
+ readonly openerTabId: number
+
+ /**
+ * @property discarded
+ * @readonly
+ * @abstract
+ */
+ abstract readonly discarded: number | undefined
+
+ /**
+ * @property height
+ * @readonly
+ * @abstract
+ */
+ abstract readonly height: number
+
+ /**
+ * @property hidden
+ * @readonly
+ * @abstract
+ */
+ abstract readonly hidden: boolean
+
+ /**
+ * @property index
+ * @readonly
+ * @abstract
+ */
+ abstract readonly index: number
+
+ /**
+ * @property mutedInfo
+ * @readonly
+ * @abstract
+ */
+ abstract readonly mutedInfo: MutedInfo
+
+ /**
+ * @property sharingState
+ * @readonly
+ * @abstract
+ */
+ abstract readonly sharingState: SharingState
+
+ /**
+ * @property pinned
+ * @readonly
+ * @abstract
+ */
+ abstract readonly pinned: boolean
+
+ /**
+ * @property active
+ * @readonly
+ * @abstract
+ */
+ abstract readonly active: boolean
+
+ /**
+ * @property highlighted
+ * @readonly
+ * @abstract
+ */
+ abstract readonly highlighted: boolean
+
+ /**
+ * @property status
+ * @readonly
+ * @abstract
+ */
+ abstract readonly status: string
+
+ /**
+ * @property width
+ * @readonly
+ * @abstract
+ */
+ abstract readonly width: number
+
+ /**
+ * @property window
+ * @readonly
+ * @abstract
+ */
+ abstract readonly window: DOMWindow
+
+ /**
+ * @property windowId
+ * @readonly
+ * @abstract
+ */
+ abstract readonly windowId: number
+
+ /**
+ * @property attention
+ * @readonly
+ * @abstract
+ */
+ abstract readonly attention: boolean
+
+ /**
+ * @property isArticle
+ * @readonly
+ * @abstract
+ */
+ abstract readonly isArticle: boolean
+
+ /**
+ * @property isInReaderMode
+ * @readonly
+ * @abstract
+ */
+ abstract readonly isInReaderMode: boolean
+
+ /**
+ * @property successorTabId
+ * @readonly
+ * @abstract
+ */
+ abstract readonly successorTabId: number | undefined
+
+ matches(queryInfo: QueryInfo): boolean
+
+ convert(fallbackTabSize?: any): any
+
+ queryContent(message: string, options: Options): Promise[]
+
+ private _execute(
+ context: BaseContext,
+ details: InjectDetails,
+ kind: string,
+ method: string,
+ ): Promise
+
+ executeScript(context: BaseContext, details: InjectDetails): Promise
+
+ insertCSS(context: BaseContext, details: InjectDetails): Promise
+
+ removeCSS(context: BaseContext, details: InjectDetails): Promise
+ }
}
diff --git a/libs/link/types/schemaTypes/index.d.ts b/libs/link/types/schemaTypes/index.d.ts
index 24201db..6dfd373 100644
--- a/libs/link/types/schemaTypes/index.d.ts
+++ b/libs/link/types/schemaTypes/index.d.ts
@@ -1,3 +1,3 @@
-// @not-mpl
+// @not-mpl
///
-///
+///
\ No newline at end of file
From c79fc64cdedec773069c5e71e57bdf43aa14efb8 Mon Sep 17 00:00:00 2001
From: TrickyPR <23250792+trickypr@users.noreply.github.com>
Date: Fri, 26 Apr 2024 13:44:17 +1000
Subject: [PATCH 2/4] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Move=20browserAction?=
=?UTF-8?q?=20to=20tabManager?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/extensions/lib/parent/ext-browserAction.js | 16 +++-------------
apps/extensions/lib/types/utils.d.ts | 17 ++++++++++++++++-
2 files changed, 19 insertions(+), 14 deletions(-)
diff --git a/apps/extensions/lib/parent/ext-browserAction.js b/apps/extensions/lib/parent/ext-browserAction.js
index e675983..7ce10e5 100644
--- a/apps/extensions/lib/parent/ext-browserAction.js
+++ b/apps/extensions/lib/parent/ext-browserAction.js
@@ -13,7 +13,6 @@ this.browserAction = class extends ExtensionAPIPersistent {
async onManifestEntry() {
const { extension } = this
- /** @type {browser_action__manifest.WebExtensionManifest__extended['browser_action']} */
const options = extension.manifest.browser_action
if (!options) {
@@ -67,25 +66,16 @@ this.browserAction = class extends ExtensionAPIPersistent {
* running extension context.
*/
onClicked({ fire }) {
- const { extension } = this
+ const /** @type {Extension} */ extension = this.extension
/**
* @param {import("resource://app/modules/EBrowserActions.sys.mjs").IBrowserActionEvents['click']} clickInfo
*/
const callback = async (_name, clickInfo) => {
if (fire.wakeup) await fire.wakeup()
- const { tab, window } = lazy.WindowTracker.getWindowWithBrowserId(
- clickInfo.tabId,
- ) || { tab: null, window: null }
- if (!tab || !window) {
- return fire.sync(null, clickInfo.clickData)
- }
-
- fire.sync(
- tabTracker.serializeTab(extension, tab, window),
- clickInfo.clickData,
- )
+ const tab = extension.tabManager.get(clickInfo.tabId)
+ fire.sync(tab.convert(), clickInfo.clickData)
}
this.on('click', callback)
diff --git a/apps/extensions/lib/types/utils.d.ts b/apps/extensions/lib/types/utils.d.ts
index 5b937bb..476b1b4 100644
--- a/apps/extensions/lib/types/utils.d.ts
+++ b/apps/extensions/lib/types/utils.d.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/ban-types */
///
import { ConduitAddress } from 'resource://gre/modules/ConduitsParent.sys.mjs'
-import { Extension } from 'resource://gre/modules/Extension.sys.mjs'
+import { Extension as ToolkitExtension } from 'resource://gre/modules/Extension.sys.mjs'
import { SchemaRoot } from 'resource://gre/modules/Schemas.sys.mjs'
import { PointConduit } from './ConduitChild'
@@ -15,6 +15,12 @@ declare global {
type NativeTab = import('@browser/tabs').WindowTab
type XULElement = Element
+ interface Extension extends ToolkitExtension {
+ tabManager: TabManagerBase
+ manifest: Omit &
+ browser_action__manifest.WebExtensionManifest__extended
+ }
+
/* eslint-disable @typescript-eslint/no-explicit-any */
function getConsole(): any
function runSafeSyncWithoutClone(f: any, ...args: any[]): any
@@ -1532,6 +1538,15 @@ declare global {
matches(queryInfo: QueryInfo): 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 [fallbackTabSize]
+ * A geometry data if the lazy geometry data for this tab hasn't been
+ * initialized yet.
+ */
convert(fallbackTabSize?: any): any
queryContent(message: string, options: Options): Promise[]
From 60c8a4188640663757b0e958ca39801baab00ed1 Mon Sep 17 00:00:00 2001
From: TrickyPR <23250792+trickypr@users.noreply.github.com>
Date: Fri, 26 Apr 2024 14:38:23 +1000
Subject: [PATCH 3/4] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Use=20TabManager=20in?=
=?UTF-8?q?=20ext-tabs?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/extensions/lib/parent/ext-browser.js | 8 ++
apps/extensions/lib/parent/ext-tabs.js | 155 ++++++++--------------
apps/extensions/lib/types/utils.d.ts | 11 +-
3 files changed, 67 insertions(+), 107 deletions(-)
diff --git a/apps/extensions/lib/parent/ext-browser.js b/apps/extensions/lib/parent/ext-browser.js
index afc79e7..5058fed 100644
--- a/apps/extensions/lib/parent/ext-browser.js
+++ b/apps/extensions/lib/parent/ext-browser.js
@@ -192,6 +192,14 @@ class TabManager extends TabManagerBase {
wrapTab(nativeTab) {
return new Tab(this.extension, nativeTab, nativeTab.view.browserId || -1)
}
+
+ /**
+ * @param {NativeTab} nativeTab
+ * @public
+ */
+ publicWrapTab(nativeTab) {
+ return this.wrapTab(nativeTab)
+ }
}
extensions.on('startup', (type, extension) => {
diff --git a/apps/extensions/lib/parent/ext-tabs.js b/apps/extensions/lib/parent/ext-tabs.js
index ab00470..d9f869f 100644
--- a/apps/extensions/lib/parent/ext-tabs.js
+++ b/apps/extensions/lib/parent/ext-tabs.js
@@ -67,6 +67,7 @@ this.tabs = class extends ExtensionAPIPersistent {
PERSISTENT_EVENTS = {}
/**
+ * @param {BaseContext} context
* @returns {tabs__tabs.ApiGetterReturn}
*/
getAPI(context) {
@@ -76,140 +77,90 @@ this.tabs = class extends ExtensionAPIPersistent {
* @param {number} tabId
*/
async function get(tabId) {
- const window = [...lazy.WindowTracker.registeredWindows.values()].find(
- (window) =>
- window.windowTabs().some((tab) => tab.view.browserId === tabId),
- )
+ const tab = extension.tabManager.get(tabId)
- if (!window) {
+ if (!tab) {
return Promise.reject({
message: `Cannot find tab matching the id ${tabId}`,
})
}
- const tab = window
- .windowTabs()
- .find((tab) => tab.view.browserId === tabId)
+ return tab
+ }
- if (!tab) {
- return Promise.reject({
- message: `Cannot find tab matching the id ${tabId}`,
- })
+ /**
+ * @param {number} [tabId]
+ */
+ async function getTabOrActive(tabId) {
+ /** @type {TabBase} */
+ let tab
+
+ if (tabId) {
+ tab = extension.tabManager.get(tabId)
+ } else {
+ const nativeTab = lazy.WindowTracker.getActiveWindow()?.activeTab()
+ if (!nativeTab) {
+ return Promise.reject({
+ message: 'Could not find active tab',
+ })
+ }
+ tab = extension.tabManager.publicWrapTab(nativeTab)
}
- return { tab, window }
+ return tab
}
return {
tabs: {
async get(tabId) {
- const { tab, window } = await get(tabId)
- return serialize(extension)([tab, window])
+ const tab = await get(tabId)
+ return tab.convert()
},
async goBack(tabId) {
- let tab
-
- if (tabId) {
- tab = await get(tabId).then((all) => all.tab)
- } else {
- tab = lazy.WindowTracker.getActiveWindow()?.activeTab()
- if (!tab) {
- return
- }
- }
- const complete = new Promise((res) => {
- /** @param {boolean} isLoading */
- function complete(isLoading) {
- if (isLoading) {
- return
- }
- tab.view.events.off('loadingChange', complete)
- res(undefined)
- }
-
- tab.view.events.on('loadingChange', complete)
- })
- tab.view.browser.goBack()
- return complete
+ const tab = await getTabOrActive(tabId)
+ tab.browser.goBack()
},
async goForward(tabId) {
- let tab
-
- if (tabId) {
- tab = await get(tabId).then((all) => all.tab)
- } else {
- tab = lazy.WindowTracker.getActiveWindow()?.activeTab()
- if (!tab) {
- return
- }
- }
-
- const complete = new Promise((res) => {
- /** @param {boolean} isLoading */
- function complete(isLoading) {
- if (isLoading) {
- return
- }
- tab.view.events.off('loadingChange', complete)
- res(undefined)
- }
-
- tab.view.events.on('loadingChange', complete)
- })
- tab.view.browser.goForward()
- return complete
+ const tab = await getTabOrActive(tabId)
+ tab.browser.goForward()
},
async query(queryInfo) {
- return query(queryInfo).map(serialize(extension))
+ return Array.from(extension.tabManager.query(queryInfo, context)).map(
+ (tab) => tab.convert(),
+ )
},
- async remove(tabIds) {
- const windows = [...lazy.WindowTracker.registeredWindows.entries()]
-
- if (typeof tabIds === 'number') {
- for (const window of windows.map((w) => w[1])) {
- const tabs = window.windowTabs()
- for (const tab of tabs) {
- if (tab.view.browserId === tabIds) {
- return window.windowTabs.update((tabs) =>
- tabs.filter((tab) => tab.view.browserId !== tabIds),
- )
- }
- }
- }
+ async remove(tabSelector) {
+ const tabIds =
+ typeof tabSelector == 'number' ? [tabSelector] : tabSelector
- return
- }
+ const windows = [...lazy.WindowTracker.registeredWindows.entries()]
for (const window of windows.map((w) => w[1])) {
const tabs = window.windowTabs()
- for (const tab of tabs) {
- if (tabIds.includes(tab.view.browserId || -1)) {
- window.windowTabs.update((tabs) =>
- tabs.filter(
- (tab) => !tabIds.includes(tab.view.browserId || -1),
- ),
- )
- break
- }
+
+ if (tabs.some((tab) => tabIds.includes(tab.view.browserId || -1))) {
+ window.windowTabs.update((tabs) =>
+ tabs.filter(
+ (tab) => !tabIds.includes(tab.view.browserId || -1),
+ ),
+ )
}
}
},
- async reload(tabIds) {
- if (typeof tabIds === 'number') {
- const { tab } = await get(tabIds)
- tab.view.browser.reload()
- return
- }
+ async reload(tabSelector) {
+ const tabIds =
+ typeof tabSelector == 'number' ? [tabSelector] : tabSelector
- for (const id of tabIds) {
- const { tab } = await get(id)
- tab.view.browser.reload()
- }
+ await Promise.all(
+ tabIds
+ .map((id) => get(id))
+ .map((tab) => tab.then((tab) => tab.browser.reload())),
+ )
},
async update(tabId, updateProperties) {
@@ -223,6 +174,7 @@ this.tabs = class extends ExtensionAPIPersistent {
}
let errors = null
+ /** @type {import("@browser/tabs").WindowTab | undefined} */
let retTab
window.windowTabs.update((tabs) =>
@@ -267,7 +219,8 @@ this.tabs = class extends ExtensionAPIPersistent {
}
if (retTab) {
- return serialize(extension)([retTab, window])
+ const tab = extension.tabManager.getWrapper(retTab)
+ return tab?.convert()
}
return
diff --git a/apps/extensions/lib/types/utils.d.ts b/apps/extensions/lib/types/utils.d.ts
index 476b1b4..97b7b17 100644
--- a/apps/extensions/lib/types/utils.d.ts
+++ b/apps/extensions/lib/types/utils.d.ts
@@ -16,7 +16,7 @@ declare global {
type XULElement = Element
interface Extension extends ToolkitExtension {
- tabManager: TabManagerBase
+ tabManager: TabManagerBase & { publicWrapTab(nativeTab: NativeTab): Tab }
manifest: Omit &
browser_action__manifest.WebExtensionManifest__extended
}
@@ -174,7 +174,7 @@ declare global {
extension: Extension
destroy(): void
onManifestEntry(entry: any): void
- getAPI(context: any): void
+ getAPI(context: BaseContext): unknown
}
/**
* Subclass to add APIs commonly used with persistent events.
@@ -250,7 +250,7 @@ declare global {
_lastError: any
contextId: any
unloaded: boolean
- extension: any
+ extension: Extension
manifestVersion: any
jsonSandbox: any
active: boolean
@@ -1214,12 +1214,11 @@ declare global {
*
* @param queryInfo An object containing properties on which to filter.
* @param context The extension context for which the matching is being performed.
- * @returns Iterator
*/
query(
queryInfo?: object | null,
context?: BaseContext | null,
- ): Iterator
+ ): Iterable
/**
* Returns a TabBase wrapper for the tab with the given ID.
@@ -1390,7 +1389,7 @@ declare global {
* @readonly
* @abstract
*/
- abstract readonly browser: XULElement
+ abstract readonly browser: XULBrowserElement
/**
* @property browsingContext
From 3e1a9b33c16b3818ec4da9470936ba13de43751b Mon Sep 17 00:00:00 2001
From: TrickyPR <23250792+trickypr@users.noreply.github.com>
Date: Fri, 26 Apr 2024 14:43:12 +1000
Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=94=A5=20Remove=20now-dead=20code?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/extensions/lib/parent/ext-browser.js | 24 +---------
apps/extensions/lib/parent/ext-tabs.js | 56 -----------------------
2 files changed, 1 insertion(+), 79 deletions(-)
diff --git a/apps/extensions/lib/parent/ext-browser.js b/apps/extensions/lib/parent/ext-browser.js
index 5058fed..227c7f0 100644
--- a/apps/extensions/lib/parent/ext-browser.js
+++ b/apps/extensions/lib/parent/ext-browser.js
@@ -21,7 +21,7 @@ const lazy = lazyESModuleGetters({
class TabTracker extends TabTrackerBase {
get activeTab() {
const window = lazy.WindowTracker.getActiveWindow()
- return window?.activeTab()
+ return window?.activeTab() || null
}
init() {
@@ -50,28 +50,6 @@ class TabTracker extends TabTrackerBase {
return tab
}
- /**
- * @param {import('resource://gre/modules/Extension.sys.mjs').Extension} extension
- * @param {import('@browser/tabs').WindowTab} tab
- * @param {Window} window
- *
- * @returns {tabs__tabs.Tab}
- */
- serializeTab(extension, tab, window) {
- // TODO: Active tab & host permissions
- const hasTabPermission = extension.hasPermission('tabs')
-
- return {
- id: tab.view.browserId,
- index: window.windowTabs().findIndex((wTab) => wTab === tab),
- active: window.activeTab() === tab,
- highlighted: false, // TODO
- title: (hasTabPermission && tab.view.title) || undefined,
- url: (hasTabPermission && tab.view.uri.asciiSpec) || undefined,
- windowId: window.windowId,
- }
- }
-
/**
* @param {XULBrowserElement} browser
*/
diff --git a/apps/extensions/lib/parent/ext-tabs.js b/apps/extensions/lib/parent/ext-tabs.js
index d9f869f..2bf80c1 100644
--- a/apps/extensions/lib/parent/ext-tabs.js
+++ b/apps/extensions/lib/parent/ext-tabs.js
@@ -7,62 +7,6 @@
///
///
-/**
- * @param {tabs__tabs.QueryInfo} queryInfo
- * @returns {[import("@browser/tabs").WindowTab, Window][]}
- */
-function query(queryInfo) {
- const windows = [...lazy.WindowTracker.registeredWindows.entries()]
-
- const urlMatchSet =
- (queryInfo.url &&
- (Array.isArray(queryInfo.url)
- ? new MatchPatternSet(queryInfo.url)
- : new MatchPatternSet([queryInfo.url]))) ||
- null
-
- return windows.flatMap(([windowId, window]) => {
- const tabs = window.windowTabs()
- const activeTab = window.activeTab()
-
- return tabs
- .filter((tab) => {
- const active =
- queryInfo.active !== null
- ? queryInfo.active
- ? tab === activeTab
- : tab !== activeTab
- : true
- const title = queryInfo.title
- ? queryInfo.title === tab.view.title
- : true
- const url =
- urlMatchSet === null
- ? true
- : urlMatchSet.matches(tab.view.uri.asciiSpec)
- const window =
- queryInfo.windowId === null ? true : queryInfo.windowId === windowId
-
- return active && title && url && window
- })
- .map(
- /** @returns {[import("@browser/tabs").WindowTab, Window]} */ (tab) => [
- tab,
- window,
- ],
- )
- })
-}
-
-const serialize =
- (extension) =>
- /**
- * @param {[import("@browser/tabs").WindowTab, Window]} in
- * @returns {tabs__tabs.Tab}
- */
- ([tab, window]) =>
- tabTracker.serializeTab(extension, tab, window)
-
this.tabs = class extends ExtensionAPIPersistent {
PERSISTENT_EVENTS = {}