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] =?UTF-8?q?=F0=9F=9A=A7=20Provide=20a=20TabBase=20and=20Ta?= =?UTF-8?q?bManagerBase=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