diff --git a/package-lock.json b/package-lock.json index 8f3a5a7..b1a0cd3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,6 @@ "@sindresorhus/tsconfig": "^7.0.0", "@types/chrome": "^0.0.307", "@types/tape": "^5.8.1", - "@types/webextension-polyfill": "^0.12.2", "buffer": "^6.0.3", "eslint": "^8.57.0", "eslint-config-pixiebrix": "^0.41.1", @@ -32,8 +31,7 @@ "stream-browserify": "^3.0.0", "tape": "^5.9.0", "typescript": "^5.8.2", - "vitest": "^3.0.7", - "webextension-polyfill": "^0.12.0" + "vitest": "^3.0.7" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -13769,13 +13767,6 @@ "mock-property": "*" } }, - "node_modules/@types/webextension-polyfill": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@types/webextension-polyfill/-/webextension-polyfill-0.12.2.tgz", - "integrity": "sha512-rUIyZfbj1TcxlV0IWyy7MUHnW24nqUfQR0F+vPru/3t4noA9Pd4sdG4sljQgQHLd1vYgmkFLSvEc/furzQPbUw==", - "dev": true, - "license": "MIT" - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "7.18.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", @@ -21590,12 +21581,6 @@ "url": "https://github.com/sponsors/fregante" } }, - "node_modules/webextension-polyfill": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.12.0.tgz", - "integrity": "sha512-97TBmpoWJEE+3nFBQ4VocyCdLKfw54rFaJ6EVQYLBCXqCIpLSZkwGgASpv4oPt9gdKCJ80RJlcmNzNn008Ag6Q==", - "dev": true - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 7db2941..60668f2 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,6 @@ "@sindresorhus/tsconfig": "^7.0.0", "@types/chrome": "^0.0.307", "@types/tape": "^5.8.1", - "@types/webextension-polyfill": "^0.12.2", "buffer": "^6.0.3", "eslint": "^8.57.0", "eslint-config-pixiebrix": "^0.41.1", @@ -52,8 +51,7 @@ "stream-browserify": "^3.0.0", "tape": "^5.9.0", "typescript": "^5.8.2", - "vitest": "^3.0.7", - "webextension-polyfill": "^0.12.0" + "vitest": "^3.0.7" }, "targets": { "main": false, diff --git a/source/logging.ts b/source/logging.ts index 901c69d..bf29c14 100644 --- a/source/logging.ts +++ b/source/logging.ts @@ -1,5 +1,3 @@ -/* Warning: Do not use import browser-polyfill directly or indirectly */ - // .bind preserves the call location in the console const debug = console.debug.bind(console, "Messenger:"); const warn = console.warn.bind(console, "Messenger:"); diff --git a/source/receiver.ts b/source/receiver.ts index c4606bd..05871f1 100644 --- a/source/receiver.ts +++ b/source/receiver.ts @@ -1,4 +1,3 @@ -import browser from "webextension-polyfill"; import { serializeError } from "serialize-error"; import { getContextName } from "webext-detect"; @@ -24,11 +23,11 @@ export function isMessengerMessage(message: unknown): message is Message { ); } -// MUST NOT be `async` or Promise-returning-only function onMessageListener( message: unknown, sender: Sender, -): Promise | undefined { + sendResponse: (response: unknown) => void, +): true | undefined { if (!isMessengerMessage(message)) { // TODO: Add test for this eventuality: ignore unrelated messages return; @@ -45,7 +44,17 @@ function onMessageListener( return; } - return handleMessage(message, sender, action); + (async () => { + try { + sendResponse(await handleMessage(message, sender, action)); + } catch (error) { + sendResponse({ __webextMessenger: true, error: serializeError(error) }); + } + })(); + + // Make `sendMessage` wait for an async response. This stops other `onMessage` listeners from being called. + // TODO: Just return a promise if this is ever implemented https://issues.chromium.org/issues/40753031 + return true; } // This function can only be called when the message *will* be handled locally. @@ -97,7 +106,6 @@ async function handleMessage( (value) => ({ value }), (error: unknown) => ({ // Errors must be serialized because the stack traces are currently lost on Chrome - // and https://github.com/mozilla/webextension-polyfill/issues/210 error: serializeError(error), }), ); @@ -116,7 +124,7 @@ export function registerMethods(methods: Partial): void { handlers.set(type, method as Method); } - browser.runtime.onMessage.addListener(onMessageListener); + chrome.runtime.onMessage.addListener(onMessageListener); } /** Ensure/document that the current function was called via Messenger */ diff --git a/source/sender.ts b/source/sender.ts index de57d5a..9cc2bab 100644 --- a/source/sender.ts +++ b/source/sender.ts @@ -1,4 +1,3 @@ -import browser from "webextension-polyfill"; import pRetry from "p-retry"; import { isBackground } from "webext-detect"; import { deserializeError } from "serialize-error"; @@ -22,7 +21,6 @@ import { events } from "./events.js"; const _errorNonExistingTarget = "Could not establish connection. Receiving end does not exist."; -// https://github.com/mozilla/webextension-polyfill/issues/384 const _errorTargetClosedEarly = "A listener indicated an asynchronous response by returning true, but the message channel closed before a response was received"; @@ -166,9 +164,9 @@ async function manageMessage( throw error; } - if (browser.tabs && typeof target.tabId === "number") { + if (chrome.tabs && typeof target.tabId === "number") { try { - const tabInfo = await browser.tabs.get(target.tabId); + const tabInfo = await chrome.tabs.get(target.tabId); if (tabInfo.discarded) { throw new Error(errorTabWasDiscarded); } @@ -270,7 +268,7 @@ function messenger< "↗️ sending message to runtime", attemptLog(attemptCount), ); - return browser.runtime.sendMessage( + return chrome.runtime.sendMessage( makeMessage(type, args, target, options), ); }; @@ -279,7 +277,7 @@ function messenger< } // Contexts without direct Tab access must go through background - if (!browser.tabs) { + if (!chrome.tabs) { return manageConnection( type, options, @@ -291,7 +289,7 @@ function messenger< "↗️ sending message to runtime", attemptLog(attemptCount), ); - return browser.runtime.sendMessage( + return chrome.runtime.sendMessage( makeMessage(type, args, target, options), ); }, @@ -316,7 +314,7 @@ function messenger< frameId, attemptLog(attemptCount), ); - return browser.tabs.sendMessage( + return chrome.tabs.sendMessage( tabId, makeMessage(type, args, target, options), frameId === "allFrames" diff --git a/source/targetLogic.test.ts b/source/targetLogic.test.ts index 4749e2e..7fceac7 100644 --- a/source/targetLogic.test.ts +++ b/source/targetLogic.test.ts @@ -1,6 +1,5 @@ import { assert, describe, test, vi } from "vitest"; import { getActionForMessage } from "./targetLogic.js"; -import { type Tabs } from "webextension-polyfill"; import { isContentScript, isBackground } from "webext-detect"; vi.mock("webext-detect"); @@ -14,7 +13,12 @@ const tab = { pinned: false, highlighted: true, incognito: false, -} satisfies Tabs.Tab; + discarded: false, + frozen: false, + selected: true, + autoDiscardable: false, + groupId: -1, +} satisfies chrome.tabs.Tab; const senders = { background: { page: "background" }, diff --git a/source/test/background/testingApi.ts b/source/test/background/testingApi.ts index e5b5e9c..19e30c9 100644 --- a/source/test/background/testingApi.ts +++ b/source/test/background/testingApi.ts @@ -1,8 +1,7 @@ -import browser from "webextension-polyfill"; import { once } from "webext-messenger/shared.js"; export async function ensureScripts(tabId: number): Promise { - await browser.scripting.executeScript({ + await chrome.scripting.executeScript({ target: { tabId }, files: ["contentscript/registration.js"], }); @@ -34,7 +33,7 @@ export async function createTargets(): Promise { let frames; while (limit--) { // eslint-disable-next-line no-await-in-loop -- It's a retry loop - frames = (await browser.webNavigation.getAllFrames({ + frames = (await chrome.webNavigation.getAllFrames({ tabId, }))!; @@ -60,7 +59,7 @@ export async function createTargets(): Promise { } const getHiddenWindow = once(async (): Promise => { - const { id } = await browser.windows.create({ + const { id } = await chrome.windows.create({ focused: false, state: "minimized", }); @@ -68,7 +67,7 @@ const getHiddenWindow = once(async (): Promise => { }); export async function openTab(url: string): Promise { - const tab = await browser.tabs.create({ + const tab = await chrome.tabs.create({ windowId: await getHiddenWindow(), active: false, url, @@ -77,9 +76,9 @@ export async function openTab(url: string): Promise { } export async function closeHiddenWindow(): Promise { - return browser.windows.remove(await getHiddenWindow()); + return chrome.windows.remove(await getHiddenWindow()); } export async function closeTab(tabId: number): Promise { - await browser.tabs.remove(tabId); + await chrome.tabs.remove(tabId); } diff --git a/source/test/contentscript/api.test.ts b/source/test/contentscript/api.test.ts index 7ee8242..5541362 100644 --- a/source/test/contentscript/api.test.ts +++ b/source/test/contentscript/api.test.ts @@ -1,4 +1,3 @@ -import browser from "webextension-polyfill"; import test from "tape"; import { isBackground, isContentScript, isWebPage } from "webext-detect"; import { type PageTarget, type Target } from "webext-messenger"; @@ -184,7 +183,7 @@ async function testEveryTarget() { await closeSelf({ tabId, frameId: parentFrame }); try { // Since the tab was closed, this is expected to throw - t.notOk(await browser.tabs.get(tabId), "The tab should not be open"); + t.notOk(await chrome.tabs.get(tabId), "The tab should not be open"); } catch { t.pass("The tab was closed"); } diff --git a/source/test/contentscript/fixtures/unrelatedMessageListener.ts b/source/test/contentscript/fixtures/unrelatedMessageListener.ts index d420344..9e541a9 100644 --- a/source/test/contentscript/fixtures/unrelatedMessageListener.ts +++ b/source/test/contentscript/fixtures/unrelatedMessageListener.ts @@ -1,14 +1,15 @@ -import browser from "webextension-polyfill"; - -browser.runtime.onMessage.addListener( - (message: unknown): Promise | undefined => { +chrome.runtime.onMessage.addListener( + (message: unknown, _sender, sendResponse): boolean | undefined => { // eslint-disable-next-line @typescript-eslint/no-explicit-any if ((message as any)?.type === "sleep") { console.log( "I’m an unrelated message listener, but I'm replying anyway to", { message }, ); - return Promise.resolve("/r/nosleep"); + + void Promise.resolve("Buonanotte").then(sendResponse); + + return true; } console.log("I’m an unrelated message listener. I’ve seen", { message }); diff --git a/source/types.ts b/source/types.ts index 5eb14cf..f7d08a4 100644 --- a/source/types.ts +++ b/source/types.ts @@ -1,4 +1,3 @@ -import { type Runtime } from "webextension-polyfill"; import { type Asyncify, type ValueOf } from "type-fest"; import { type ErrorObject } from "serialize-error"; @@ -83,7 +82,7 @@ export type Message = { options?: Options; }; -export type Sender = Runtime.MessageSender & { origin?: string }; // Chrome includes the origin +export type Sender = chrome.runtime.MessageSender; export type MessengerMessage = Message & { /** Guarantees that a message is meant to be handled by this library */