From fc55569c625018691c357134d8cee44e603995a0 Mon Sep 17 00:00:00 2001 From: trickypr <23250792+trickypr@users.noreply.github.com> Date: Sun, 24 Dec 2023 20:59:12 +1100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Click=20api=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/webext/background.md | 5 + docs/webext/pageAction.md | 5 + scripts/lib/files.ts | 4 +- .../components/omnibox/PageAction.svelte | 17 ++- src/content/browser/lib/window/api.ts | 12 +- src/content/browser/lib/window/initialize.ts | 39 +----- src/content/browser/lib/window/tabs.ts | 4 +- src/content/browser/lib/window/window.ts | 43 ++++++- src/content/shared/domUtils.ts | 27 +++++ src/content/shared/lazy.ts | 1 + src/content/shared/xul/messageReciver.ts | 4 + src/link.d.ts | 8 +- src/modules/BrowserWindowTracker.ts | 40 ++++++- src/modules/EPageActions.ts | 1 + src/prefs.js | 10 ++ src/types/AppConstants.d.ts | 98 +++++++++++++++ src/types/MatchPattern.d.ts | 4 + src/types/MessageManager.d.ts | 4 + static/extensions/ext-browser.json | 3 +- static/extensions/parent/ext-browser.js | 58 +++++++++ static/extensions/parent/ext-pageAction.js | 75 ++++++++++-- static/extensions/types/utils.d.ts | 112 ++++++++++++++++++ 22 files changed, 510 insertions(+), 64 deletions(-) create mode 100644 docs/webext/background.md create mode 100644 docs/webext/pageAction.md create mode 100644 src/content/shared/domUtils.ts create mode 100644 src/types/AppConstants.d.ts create mode 100644 static/extensions/parent/ext-browser.js diff --git a/docs/webext/background.md b/docs/webext/background.md new file mode 100644 index 0000000..7c30912 --- /dev/null +++ b/docs/webext/background.md @@ -0,0 +1,5 @@ +# Background pages + +## Differences + +- Non-persistent background pages will not restart diff --git a/docs/webext/pageAction.md b/docs/webext/pageAction.md new file mode 100644 index 0000000..f9759d5 --- /dev/null +++ b/docs/webext/pageAction.md @@ -0,0 +1,5 @@ +# PageActions + +## Differences + +- `onClicked`: The first argument is the click info, not the `Tab` info diff --git a/scripts/lib/files.ts b/scripts/lib/files.ts index f23336e..430b2b6 100644 --- a/scripts/lib/files.ts +++ b/scripts/lib/files.ts @@ -15,8 +15,10 @@ export const CHANGES: { file: string; append?: string | string[] }[] = [ }, { file: 'components/components.manifest', - append: + append: [ 'category webextension-modules browser chrome://browser/content/extensions/ext-browser.json', + 'category webextension-scripts c-browser chrome://browser/content/extensions/parent/ext-browser.js', + ], }, ] diff --git a/src/content/browser/components/omnibox/PageAction.svelte b/src/content/browser/components/omnibox/PageAction.svelte index 6c015cc..081d21c 100644 --- a/src/content/browser/components/omnibox/PageAction.svelte +++ b/src/content/browser/components/omnibox/PageAction.svelte @@ -18,6 +18,7 @@ getIconUrlForPreferredSize, pageActionIcons, } from '@browser/lib/modules/EPageActionsBindings' + import { clickModifiersFromEvent } from '@shared/domUtils' export let pageAction: PageAction const icons = pageActionIcons(pageAction) @@ -41,7 +42,6 @@ browser.remove() } - console.log(pageAction.popupUrl) if (!pageAction.popupUrl) return const uri = resource.NetUtil.newURI(pageAction.popupUrl) @@ -80,9 +80,18 @@ setURI(lBrowser, uri) } - const OPEN_PANEL = async () => { + const handleClick = async (event: MouseEvent) => { + // Send the event to the extension + pageAction.events.emit('click', { + clickData: { + modifiers: clickModifiersFromEvent(event), + button: event.button, + }, + }) + await buildPanelBrowser() - panel.openPopup(button, 'bottomright topright') + // Panel may not exist if there is no popupUrl + if (panel) panel.openPopup(button, 'bottomright topright') } onMount(() => () => { @@ -99,7 +108,7 @@ {#if $icons} diff --git a/src/content/browser/lib/window/api.ts b/src/content/browser/lib/window/api.ts index b9c463e..45094d4 100644 --- a/src/content/browser/lib/window/api.ts +++ b/src/content/browser/lib/window/api.ts @@ -11,13 +11,14 @@ import { } from './contextMenu' import { closeTab, + getCurrentTab, getTabById, openTab, runOnCurrentTab, setCurrentTab, tabs, } from './tabs' -import { id } from './window' +import { id, setId } from './window' export type WindowTriggers = { bookmarkCurrentPage: undefined @@ -27,9 +28,17 @@ export const windowApi = { /** * Identify which window this is. This should be used for actions like tab * moving that go across windows + * + * Note: You need to wait for the window watcher to register this window + * before you get a valid id */ id, + /** + * Sets the window ID. You should only use this if you are the WindowWatcher + */ + setId, + windowTriggers: mitt(), window: { /** @@ -50,6 +59,7 @@ export const windowApi = { openTab, runOnCurrentTab, setCurrentTab, + getCurrentTab, getTabById, get tabs() { return tabs.readOnce() diff --git a/src/content/browser/lib/window/initialize.ts b/src/content/browser/lib/window/initialize.ts index 1ac079c..2ddbfc5 100644 --- a/src/content/browser/lib/window/initialize.ts +++ b/src/content/browser/lib/window/initialize.ts @@ -1,11 +1,13 @@ /* 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/. */ -import { TAB_DATA_TYPE } from '@browser/components/tabs/tabDrag' - import { resource } from '../resources' import { type WindowArguments, getFullWindowConfiguration } from './arguments' import { openTab } from './tabs' +import { + initializeWindowDragOverHandler, + registerWithWindowTracker, +} from './window' export function initializeWindow(args: WindowArguments | undefined) { // When opened via nsIWindowWatcher.openWindow, the arguments are @@ -26,36 +28,3 @@ export function initializeWindow(args: WindowArguments | undefined) { initializeWindowDragOverHandler() registerWithWindowTracker() } - -/** - * If we want to detect drops outside of the window, we need to ensure that all - * drops **within** a browser window are handled. - * - * This listens for all events with a type equivalent to {@link TAB_DATA_TYPE} - * and makes sure they have an attached drop type. - */ -function initializeWindowDragOverHandler() { - const handleDragEvent = (event: DragEvent) => { - const rawDragRepresentation = event.dataTransfer?.getData(TAB_DATA_TYPE) - if (!rawDragRepresentation) return - - // Set this to some drop event other than 'none' so we can detect drops - // outside of the window in the tab's drag handler - if (event.dataTransfer) event.dataTransfer.dropEffect = 'link' - event.preventDefault() - } - - window.addEventListener('dragover', handleDragEvent) - window.addEventListener('drop', handleDragEvent) -} - -/** - * Ensures that the window tracker is aware of this window & that when it is - * closed, the correct cleanup is performed. - */ -function registerWithWindowTracker() { - resource.WindowTracker.registerWindow(window) - window.addEventListener('unload', () => - resource.WindowTracker.removeWindow(window), - ) -} diff --git a/src/content/browser/lib/window/tabs.ts b/src/content/browser/lib/window/tabs.ts index 43a2397..6f24b30 100644 --- a/src/content/browser/lib/window/tabs.ts +++ b/src/content/browser/lib/window/tabs.ts @@ -53,7 +53,7 @@ export function getTabById(id: number): Tab | undefined { return tabs.readOnce().find((tab) => tab.getId() == id) } -function getCurrent(): Tab | undefined { +export function getCurrentTab(): Tab | undefined { return getTabById(internalSelectedTab) } @@ -63,7 +63,7 @@ export function setCurrentTab(tab: Tab) { } export function runOnCurrentTab(method: (tab: Tab) => R): R | void { - const currentTab = getCurrent() + const currentTab = getCurrentTab() if (currentTab) return method(currentTab) } diff --git a/src/content/browser/lib/window/window.ts b/src/content/browser/lib/window/window.ts index eac26f9..fe69bf9 100644 --- a/src/content/browser/lib/window/window.ts +++ b/src/content/browser/lib/window/window.ts @@ -1,12 +1,47 @@ /* 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/. */ - -import { nanoid } from 'nanoid' +import { TAB_DATA_TYPE } from '@browser/components/tabs/tabDrag' import { resource } from '../resources' -export const id = nanoid() +export let id = -1 +export const setId = (newId: number) => (id = newId) -export const getWindowById = (id: string) => +export const getWindowById = (id: number) => resource.WindowTracker.getWindowById(id) + +/** + * If we want to detect drops outside of the window, we need to ensure that all + * drops **within** a browser window are handled. + * + * This listens for all events with a type equivalent to {@link TAB_DATA_TYPE} + * and makes sure they have an attached drop type. + */ +export function initializeWindowDragOverHandler() { + const handleDragEvent = (event: DragEvent) => { + const rawDragRepresentation = event.dataTransfer?.getData(TAB_DATA_TYPE) + if (!rawDragRepresentation) return + + // Set this to some drop event other than 'none' so we can detect drops + // outside of the window in the tab's drag handler + if (event.dataTransfer) event.dataTransfer.dropEffect = 'link' + event.preventDefault() + } + + window.addEventListener('dragover', handleDragEvent) + window.addEventListener('drop', handleDragEvent) +} + +/** + * Ensures that the window tracker is aware of this window & that when it is + * closed, the correct cleanup is performed. + */ +export function registerWithWindowTracker() { + resource.WindowTracker.registerWindow(window) + window.addEventListener('unload', () => + resource.WindowTracker.removeWindow(window), + ) + + window.addEventListener('focus', () => resource.WindowTracker.focusWindow(id)) +} diff --git a/src/content/shared/domUtils.ts b/src/content/shared/domUtils.ts new file mode 100644 index 0000000..22579e7 --- /dev/null +++ b/src/content/shared/domUtils.ts @@ -0,0 +1,27 @@ +/* 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/. */ + +import { lazy } from './lazy' + +export type ClickModifiers = 'Shift' | 'Alt' | 'Command' | 'Ctrl' | 'MacCtrl' +export const clickModifiersFromEvent = ( + event: MouseEvent, +): ClickModifiers[] => { + const map = { + shiftKey: 'Shift', + altKey: 'Alt', + metaKey: 'Command', + ctrlKey: 'Ctrl', + } as const + + const modifiers: ClickModifiers[] = (Object.keys(map) as (keyof typeof map)[]) + .filter((key) => event[key]) + .map((key) => map[key]) + + if (event.ctrlKey && lazy.AppConstants.platform === 'macosx') { + modifiers.push('MacCtrl') + } + + return modifiers +} diff --git a/src/content/shared/lazy.ts b/src/content/shared/lazy.ts index ba8c3b2..0d6702c 100644 --- a/src/content/shared/lazy.ts +++ b/src/content/shared/lazy.ts @@ -10,6 +10,7 @@ import { lazyESModuleGetters } from './TypedImportUtilities' export const lazy = lazyESModuleGetters({ + AppConstants: 'resource://gre/modules/AppConstants.sys.mjs', PlacesUtils: 'resource://gre/modules/PlacesUtils.sys.mjs', Bookmarks: 'resource://gre/modules/Bookmarks.sys.mjs', History: 'resource://gre/modules/History.sys.mjs', diff --git a/src/content/shared/xul/messageReciver.ts b/src/content/shared/xul/messageReciver.ts index ee6e90f..d1a3ec8 100644 --- a/src/content/shared/xul/messageReciver.ts +++ b/src/content/shared/xul/messageReciver.ts @@ -1,3 +1,7 @@ +/* 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/. */ + export class MessageReviver< Cb extends (argument: ReceiveMessageArgument) => unknown, > implements MessageListener diff --git a/src/link.d.ts b/src/link.d.ts index cc7a2db..9f2e81e 100644 --- a/src/link.d.ts +++ b/src/link.d.ts @@ -6,9 +6,14 @@ /// +/// /// /// +declare type LazyImportType> = + | { [Key in keyof Modules]: MozESMExportType[Key] } + | {} + declare module 'resource://app/modules/FaviconLoader.sys.mjs' { export const FaviconLoader: typeof import('./modules/FaviconLoader').FaviconLoader } @@ -29,12 +34,14 @@ declare module 'resource://app/modules/EPageActions.sys.mjs' { } declare interface MozESMExportFile { + AppConstants: 'resource://gre/modules/AppConstants.sys.mjs' TypedImportUtils: 'resource://app/modules/TypedImportUtils.sys.mjs' WindowTracker: 'resource://app/modules/BrowserWindowTracker.sys.mjs' EPageActions: 'resource://app/modules/EPageActions.sys.mjs' } declare interface MozESMExportType { + AppConstants: typeof import('resource://gre/modules/AppConstants.sys.mjs').AppConstants TypedImportUtils: typeof import('./modules/TypedImportUtils') WindowTracker: typeof import('./modules/BrowserWindowTracker').WindowTracker EPageActions: typeof import('./modules/EPageActions').EPageActions @@ -636,4 +643,3 @@ declare module ChromeUtils { path: Path, ): T } - diff --git a/src/modules/BrowserWindowTracker.ts b/src/modules/BrowserWindowTracker.ts index c94ddf4..bb5ed6d 100644 --- a/src/modules/BrowserWindowTracker.ts +++ b/src/modules/BrowserWindowTracker.ts @@ -1,17 +1,29 @@ /* 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/. */ + +/* eslint-disable @typescript-eslint/no-explicit-any */ import mitt from 'resource://app/modules/mitt.sys.mjs' +type Tab = unknown + export type WindowTrackerEvents = { windowCreated: Window & typeof globalThis windowDestroyed: Window & typeof globalThis + + focus: Window & typeof globalThis } export const WindowTracker = { + /** + * @private + */ + nextWindowId: 0, + events: mitt(), - registeredWindows: new Map(), + activeWindow: null as number | null, + registeredWindows: new Map(), /** * Registers a new browser window to be tracked @@ -19,18 +31,38 @@ export const WindowTracker = { * @param w The window to register */ registerWindow(w: typeof window) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(w as any).windowApi.id = this.nextWindowId++ this.registeredWindows.set((w as any).windowApi.id, w) this.events.emit('windowCreated', w) }, removeWindow(w: typeof window) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any this.registeredWindows.delete((w as any).windowApi.id) this.events.emit('windowDestroyed', w) }, - getWindowById(wid: string) { + getWindowById(wid: number) { return this.registeredWindows.get(wid) }, + + getWindowWithBrowser( + browser: XULBrowserElement, + ): { window: Window & typeof globalThis; tab: Tab } | null { + for (const window of this.registeredWindows.values()) { + const tab = (window as any).windowApi.tabs.tabs.find( + (t: Tab) => (t as any).getTabId() === browser.browserId, + ) + if (tab) return { window, tab } + } + return null + }, + + focusWindow(id: number) { + this.activeWindow = id + this.events.emit('focus', this.getActiveWindow()!) + }, + + getActiveWindow(): (typeof window & typeof globalThis) | undefined { + return this.registeredWindows.get(this.activeWindow ?? -1) + }, } diff --git a/src/modules/EPageActions.ts b/src/modules/EPageActions.ts index 39fa986..aada345 100644 --- a/src/modules/EPageActions.ts +++ b/src/modules/EPageActions.ts @@ -57,6 +57,7 @@ export interface PageActionOptions { export type PageActionEvents = { updateIcon: Record | undefined + click: { clickData: { modifiers: string[]; button: number } } } export class PageAction implements PageActionOptions { diff --git a/src/prefs.js b/src/prefs.js index eef66c0..a84b280 100644 --- a/src/prefs.js +++ b/src/prefs.js @@ -108,3 +108,13 @@ pref('browser.download.alwaysOpenPanel', true); // 1 - Remove the download from session list, but not history. // 2 - Remove the download from both session list and history. pref('browser.download.clearHistoryOnDelete', 0); + +// ============================================================================= +// Multithreading + +pref('browser.tabs.remote.autostart', true); +pref('browser.tabs.remote.separatePrivilegedContentProcess', true); +pref('browser.tabs.remote.separatePrivilegedMozillaWebContentProcess', true); + +pref('extensions.webextensions.remote', true); +pref('extensions.webextensions.protocol.remote', true); diff --git a/src/types/AppConstants.d.ts b/src/types/AppConstants.d.ts new file mode 100644 index 0000000..4f65153 --- /dev/null +++ b/src/types/AppConstants.d.ts @@ -0,0 +1,98 @@ +/* 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/. */ + +type AppConstantsContents = { + readonly NIGHTLY_BUILD: boolean + readonly RELEASE_OR_BETA: boolean + readonly EARLY_BETA_OR_EARLIER: boolean + readonly IS_ESR: boolean + readonly ACCESSIBILITY: boolean + readonly MOZILLA_OFFICIAL: boolean + readonly MOZ_OFFICIAL_BRANDING: boolean + readonly MOZ_DEV_EDITION: boolean + readonly MOZ_SERVICES_SYNC: boolean + readonly MOZ_SERVICES_HEALTHREPORT: boolean + readonly MOZ_DATA_REPORTING: boolean + readonly MOZ_SANDBOX: boolean + readonly MOZ_TELEMETRY_REPORTING: boolean + readonly MOZ_TELEMETRY_ON_BY_DEFAULT: boolean + readonly MOZ_UPDATER: boolean + readonly MOZ_SWITCHBOARD: boolean + readonly MOZ_WEBRTC: boolean + readonly MOZ_WIDGET_GTK: boolean + readonly MOZ_WMF_CDM: boolean + readonly XP_UNIX: boolean + readonly platform: string + readonly unixstyle: boolean + + isPlatformAndVersionAtLeast(platform: string, version: string): boolean + isPlatformAndVersionAtMost(platform: string, version: string): boolean + + readonly MOZ_CRASHREPORTER: boolean + readonly MOZ_NORMANDY: boolean + readonly MOZ_MAINTENANCE_SERVICE: boolean + readonly MOZ_BACKGROUNDTASKS: boolean + readonly MOZ_UPDATE_AGENT: boolean + readonly MOZ_BITS_DOWNLOAD: boolean + readonly DEBUG: boolean + readonly ASAN: boolean + readonly ASAN_REPORTER: boolean + readonly TSAN: boolean + readonly MOZ_SYSTEM_NSS: boolean + readonly MOZ_PLACES: boolean + readonly MOZ_REQUIRE_SIGNING: boolean + readonly MOZ_UNSIGNED_SCOPES: number + readonly MOZ_ALLOW_ADDON_SIDELOAD: boolean + readonly MOZ_WEBEXT_WEBIDL_ENABLED: boolean + readonly MENUBAR_CAN_AUTOHIDE: boolean + readonly MOZ_ANDRIOD_HISTORY: boolean + readonly MOZ_GECKO_PROFILER: boolean + + readonly DLL_PREFIX: string + readonly DLL_SUFFIX: string + readonly MOZ_APP_NAME: string + readonly MOZ_APP_BASENAME: string + /** + * @deprecated use brandShortName/brand-short-name instead + */ + readonly MOZ_APP_DISPLAYNAME_DO_NOT_USE: string + readonly MOZ_APP_VERSION: string + readonly MOZ_APP_VERSION_DISPLAY: string + readonly MOZ_BUILDID: string + readonly MOZ_BUILD_APP: string + readonly MOZ_MACBUNDLE_ID: string + readonly MOZ_MACBUNDLE_NAME: string + readonly MOZ_UPDATE_CHANNEL: string + readonly MOZ_WIDGET_TOOLKIT: string + readonly ANDROID_PACKAGE_NAME: string + + readonly DEBUG_JS_MODULES: string + + readonly MOZ_BING_API_CLIENTID: string + readonly MOZ_BING_API_KEY: string + readonly MOZ_GOOGLE_LOCATION_SERVICE_API_KEY: string + readonly MOZ_GOOGLE_SAFEBROWSING_API_KEY: string + readonly MOZ_MOZILLA_API_KEY: string + + readonly BROWSER_CHROME_URL: string + + readonly OMNIJAR_NAME: string + + readonly HAVE_SHELL_SERVICE: boolean + readonly MOZ_CODE_COVERAGE: boolean + readonly TELEMETRY_PING_FORMAT_VERSION: string + readonly MOZ_NEW_NOTIFICATION_STORE: boolean + readonly ENABLE_WEBDRIVER: boolean + readonly REMOTE_SETTINGS_SERVER_URL: string + readonly REMOTE_SETTINGS_VERIFY_SIGNATURE: boolean + readonly REMOTE_SETTINGS_DEFAULT_BUCKET: string + readonly MOZ_GLEAN_ANDROID: boolean + readonly MOZ_JXL: boolean + readonly MOZ_CAN_FOLLOW_SYSTEM_TIME: boolean + readonly MOZ_SYSTEM_POLICIES: boolean +} + +declare module 'resource://gre/modules/AppConstants.sys.mjs' { + export const AppConstants: AppConstantsContents +} diff --git a/src/types/MatchPattern.d.ts b/src/types/MatchPattern.d.ts index 00d8e66..398c4ed 100644 --- a/src/types/MatchPattern.d.ts +++ b/src/types/MatchPattern.d.ts @@ -1,3 +1,7 @@ +/* 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/. */ + interface MatchPatternOptions { /** * If true, the path portion of the pattern is ignored, and replaced with a diff --git a/src/types/MessageManager.d.ts b/src/types/MessageManager.d.ts index daac8e1..fffbc5b 100644 --- a/src/types/MessageManager.d.ts +++ b/src/types/MessageManager.d.ts @@ -1,3 +1,7 @@ +/* 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/. */ + /* eslint-disable @typescript-eslint/no-explicit-any */ /// diff --git a/static/extensions/ext-browser.json b/static/extensions/ext-browser.json index 2ca58e3..946e893 100644 --- a/static/extensions/ext-browser.json +++ b/static/extensions/ext-browser.json @@ -3,6 +3,7 @@ "url": "chrome://browser/content/extensions/parent/ext-pageAction.js", "schema": "chrome://extensions/content/schemas/page_action.json", "scopes": ["addon_parent"], - "manifest": ["page_action"] + "manifest": ["page_action"], + "paths": [["pageAction"]] } } diff --git a/static/extensions/parent/ext-browser.js b/static/extensions/parent/ext-browser.js new file mode 100644 index 0000000..9cd757f --- /dev/null +++ b/static/extensions/parent/ext-browser.js @@ -0,0 +1,58 @@ +// @ts-check +/* eslint-disable no-undef */ +/// + +/** @type {typeof import('resource://app/modules/TypedImportUtils.sys.mjs')} */ +const typedImportUtils = ChromeUtils.importESModule( + 'resource://app/modules/TypedImportUtils.sys.mjs', +) +const { lazyESModuleGetters } = typedImportUtils + +const lazy = lazyESModuleGetters({ + WindowTracker: 'resource://app/modules/BrowserWindowTracker.sys.mjs', + EPageActions: 'resource://app/modules/EPageActions.sys.mjs', + ExtensionParent: 'resource://gre/modules/ExtensionParent.sys.mjs', +}) + +class TabTracker extends TabTrackerBase { + get activeTab() { + /** @type {any | null} */ + const window = lazy.WindowTracker.getActiveWindow() + return window?.windowApi?.tabs?.getCurrentTab() + } + + init() { + if (this.initialized) return + this.initialized = true + } + + /** + * @param {*} nativeTab + */ + getId(nativeTab) { + return nativeTab.getTabId() + } + + getTab(tabId, default_) { + const { tab } = lazy.WindowTracker.getWindowWithBrowser(tabId) || { + tab: default_, + } + + return tab + } + + getBrowserData(browser) { + const data = lazy.WindowTracker.getWindowWithBrowser(browser) + if (!data) return { windowId: -1, tabId: -1 } + + return { + /** @type {number} */ + // @ts-expect-error bad imported types + windowId: data.window.windowApi.id, + /** @type {number} */ + tabId: data.tab.getTabId(), + } + } +} + +Object.assign(global, { tabTracker: new TabTracker() }) diff --git a/static/extensions/parent/ext-pageAction.js b/static/extensions/parent/ext-pageAction.js index d00dc63..19279a9 100644 --- a/static/extensions/parent/ext-pageAction.js +++ b/static/extensions/parent/ext-pageAction.js @@ -2,17 +2,6 @@ // @ts-check /// -/** @type {typeof import('resource://app/modules/TypedImportUtils.sys.mjs')} */ -const typedImportUtils = ChromeUtils.importESModule( - 'resource://app/modules/TypedImportUtils.sys.mjs', -) -const { lazyESModuleGetters } = typedImportUtils - -const lazy = lazyESModuleGetters({ - EPageActions: 'resource://app/modules/EPageActions.sys.mjs', - ExtensionParent: 'resource://gre/modules/ExtensionParent.sys.mjs', -}) - this.pageAction = class extends ExtensionAPIPersistent { async onManifestEntry(entryName) { const { extension } = this @@ -38,6 +27,11 @@ this.pageAction = class extends ExtensionAPIPersistent { ), ) + pageAction.events.on('click', (v) => { + console.log('Click Value', v) + this.emit('click', v) + }) + lazy.EPageActions.registerPageAction(extension.id, pageAction) } @@ -45,6 +39,65 @@ this.pageAction = class extends ExtensionAPIPersistent { const { extension } = this lazy.EPageActions.removePageAction(extension.id) } + + PERSISTENT_EVENTS = { + /** + * @param {object} options + * @param {object} options.fire + * @param {function} options.fire.async + * @param {function} options.fire.sync + * @param {function} options.fire.raw + * For primed listeners `fire.async`/`fire.sync`/`fire.raw` will + * collect the pending events to be send to the background context + * and implicitly wake up the background context (Event Page or + * Background Service Worker), or forward the event right away if + * the background context is running. + * @param {function} [options.fire.wakeup = undefined] + * For primed listeners, the `fire` object also provide a `wakeup` method + * which can be used by the primed listener to explicitly `wakeup` the + * background context (Event Page or Background Service Worker) and wait for + * it to be running (by awaiting on the Promise returned by wakeup to be + * resolved). + * @param {ProxyContextParent} [options.context=undefined] + * This property is expected to be undefined for primed listeners (which + * are created while the background extension context does not exist) and + * to be set to a ProxyContextParent instance (the same got by the getAPI + * method) when the method is called for a listener registered by a + * running extension context. + */ + onClicked({ fire }) { + const callback = async (_name, clickInfo) => { + console.log(fire, fire.wakeup, !!fire.wakeup) + if (fire.wakeup) await fire.wakeup() + console.log('fire') + fire.sync(clickInfo) + } + + this.on('click', callback) + return { + unregister: () => { + this.off('click', callback) + }, + convert(newFire) { + fire = newFire + }, + } + }, + } + + getAPI(context) { + return { + pageAction: { + onClicked: new EventManager({ + context, + module: 'pageAction', + event: 'onClicked', + inputHandling: true, + extensionApi: this, + }).api(), + }, + } + } } // global.pageActionFor = this.pageAction.for diff --git a/static/extensions/types/utils.d.ts b/static/extensions/types/utils.d.ts index ec0d85a..3f2f1df 100644 --- a/static/extensions/types/utils.d.ts +++ b/static/extensions/types/utils.d.ts @@ -821,4 +821,116 @@ declare global { } const LISTENERS: unique symbol const ONCE_MAP: unique symbol + + interface TabAttachedEvent { + /** The native tab object in the window to which the tab is being attached. This may be a different object than was used to represent the tab in the old window. */ + tab: NativeTab + + /** The ID of the tab being attached. */ + tabId: number + + /** The ID of the window to which the tab is being attached. */ + newWindowId: number + + /** The position of the tab in the tab list of the new window. */ + newPosition: number + } + + interface TabDetachedEvent { + /** The native tab object in the window from which the tab is being detached. This may be a different object than will be used to represent the tab in the new window. */ + tab: NativeTab + + /** The native tab object in the window to which the tab will be attached, and is adopting the contents of this tab. This may be a different object than the tab in the previous window. */ + adoptedBy: NativeTab + + /** The ID of the tab being detached. */ + tabId: number + + /** The ID of the window from which the tab is being detached. */ + oldWindowId: number + + /** The position of the tab in the tab list of the window from which it is being detached. */ + oldPosition: number + } + + interface TabCreatedEvent { + /** The native tab object for the tab which is being created. */ + tab: NativeTab + } + + interface TabRemovedEvent { + /** The native tab object for the tab which is being removed. */ + tab: NativeTab + + /** The ID of the tab being removed. */ + tabId: number + + /** The ID of the window from which the tab is being removed. */ + windowId: number + + /** True if the tab is being removed because the window is closing. */ + isWindowClosing: boolean + } + + interface BrowserData { + /** The numeric ID of the tab that a belongs to, or -1 if it does not belong to a tab. */ + tabId: number + + /** The numeric ID of the browser window that a belongs to, or -1 if it does not belong to a browser window. */ + 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 + * of them between numeric IDs and native tab objects. + */ + abstract class TabTrackerBase extends EventEmitter { + protected initialized: boolean + + on(...args: any[]): void + + /** + * Called to initialize the tab tracking listeners the first time that an + * event listener is added. + */ + protected abstract init(): void + + /** + * Returns the numeric ID for the given native tab. + * + * @param nativeTab The native tab for which to return an ID. + * @returns The tab's numeric ID. + */ + abstract getId(nativeTab: NativeTab): number + + /** + * Returns the native tab with the given numeric ID. + * + * @param tabId The numeric ID of the tab to return. + * @param default_ The value to return if no tab exists with the given ID. + * @returns The native tab. + * @throws If no tab exists with the given ID and a default return value is not provided. + */ + abstract getTab(tabId: number, default_?: any): NativeTab + + /** + * Returns basic information about the tab and window that the given browser + * belongs to. + * + * @param browser The XUL browser element for which to return data. + * @returns Browser data. + */ + abstract getBrowserData(browser: XULElement): BrowserData + + /** + * Returns the native tab object for the active tab in the + * most-recently focused window, or null if no live tabs currently + * exist. + */ + abstract get activeTab(): NativeTab | null + } }