mirror of
https://github.com/primedigitaltech/azon_seeker.git
synced 2026-10-05 15:22:59 +08:00
fix: handle extension ports across BFCache navigation
This commit is contained in:
parent
0b99643100
commit
793c343fb6
@ -11,8 +11,9 @@
|
|||||||
"dev:web": "vite",
|
"dev:web": "vite",
|
||||||
"dev:background": "npm run build:background -- --mode development",
|
"dev:background": "npm run build:background -- --mode development",
|
||||||
"dev:js": "npm run build:js -- --mode development",
|
"dev:js": "npm run build:js -- --mode development",
|
||||||
"build": "cross-env NODE_ENV=production run-s clear build:web build:prepare build:background build:js",
|
"build": "cross-env NODE_ENV=production run-s clear build:bridge build:web build:prepare build:background build:js",
|
||||||
"build-firefox": "cross-env NODE_ENV=production EXTENSION=firefox run-s clear-firefox build:web build:prepare build:background build:js",
|
"build-firefox": "cross-env NODE_ENV=production EXTENSION=firefox run-s clear-firefox build:bridge build:web build:prepare build:background build:js",
|
||||||
|
"build:bridge": "npm --prefix webext-bridge run build",
|
||||||
"build:prepare": "esno scripts/prepare.ts",
|
"build:prepare": "esno scripts/prepare.ts",
|
||||||
"build:background": "vite build --config vite.config.background.mts",
|
"build:background": "vite build --config vite.config.background.mts",
|
||||||
"build:web": "vite build",
|
"build:web": "vite build",
|
||||||
@ -34,6 +35,8 @@
|
|||||||
"test:extension-runtime": "node scripts/verify-extension-runtime.mjs",
|
"test:extension-runtime": "node scripts/verify-extension-runtime.mjs",
|
||||||
"test:remote-endpoint": "esno scripts/test-remote-endpoint.ts",
|
"test:remote-endpoint": "esno scripts/test-remote-endpoint.ts",
|
||||||
"test:production-endpoint": "node scripts/verify-production-endpoint.mjs",
|
"test:production-endpoint": "node scripts/verify-production-endpoint.mjs",
|
||||||
|
"test:port-disconnect": "esno scripts/test-port-disconnect.ts",
|
||||||
|
"test:port-disconnect-build": "node scripts/verify-port-disconnect-build.mjs",
|
||||||
"prepare": "husky"
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
13
scripts/test-port-disconnect.ts
Normal file
13
scripts/test-port-disconnect.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { isBackForwardCachePortError } from '../webext-bridge/src/internal/port-disconnect';
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
isBackForwardCachePortError(
|
||||||
|
'The page keeping the extension port is moved into back/forward cache, so the message channel is closed.',
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
assert.equal(isBackForwardCachePortError('Extension context invalidated.'), false);
|
||||||
|
assert.equal(isBackForwardCachePortError(), false);
|
||||||
|
|
||||||
|
console.log('Port disconnect tests passed.');
|
||||||
16
scripts/verify-port-disconnect-build.mjs
Normal file
16
scripts/verify-port-disconnect-build.mjs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { readFile } from 'node:fs/promises';
|
||||||
|
|
||||||
|
const background = await readFile('extension/dist/background/index.mjs', 'utf8');
|
||||||
|
const contentScript = await readFile('extension/dist/contentScripts/index.global.js', 'utf8');
|
||||||
|
|
||||||
|
if (!background.includes('lastError')) {
|
||||||
|
throw new Error('Background bundle does not consume runtime.lastError on port disconnect.');
|
||||||
|
}
|
||||||
|
if (!contentScript.includes('back/forward cache')) {
|
||||||
|
throw new Error('Content script bundle does not recognize BFCache port closures.');
|
||||||
|
}
|
||||||
|
if (!contentScript.includes('pagehide') || !contentScript.includes('pageshow')) {
|
||||||
|
throw new Error('Content script bundle does not suspend and resume the BFCache port lifecycle.');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Production BFCache port handling verification passed.');
|
||||||
@ -279,6 +279,10 @@ browser.runtime.onConnect.addListener((incomingPort) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
incomingPort.onDisconnect.addListener(() => {
|
incomingPort.onDisconnect.addListener(() => {
|
||||||
|
// Reading lastError prevents Chrome from reporting an expected BFCache port closure as
|
||||||
|
// an unchecked runtime error in the extension service worker console.
|
||||||
|
void browser.runtime.lastError;
|
||||||
|
|
||||||
// sometimes previous content script's onDisconnect is called *after* the fresh content-script's
|
// sometimes previous content script's onDisconnect is called *after* the fresh content-script's
|
||||||
// onConnect. So without this fingerprint equality check, we would remove the new port from map
|
// onConnect. So without this fingerprint equality check, we would remove the new port from map
|
||||||
if (connMap.get(connArgs.endpointName)?.fingerprint === connArgs.fingerprint)
|
if (connMap.get(connArgs.endpointName)?.fingerprint === connArgs.fingerprint)
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { encodeConnectionArgs } from './connection-args';
|
|||||||
import { createDeliveryLogger } from './delivery-logger';
|
import { createDeliveryLogger } from './delivery-logger';
|
||||||
import type { StatusMessage } from './port-message';
|
import type { StatusMessage } from './port-message';
|
||||||
import { PortMessage } from './port-message';
|
import { PortMessage } from './port-message';
|
||||||
|
import { isBackForwardCachePortError } from './port-disconnect';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manfiest V3 extensions can have their service worker terminated at any point
|
* Manfiest V3 extensions can have their service worker terminated at any point
|
||||||
@ -18,6 +19,8 @@ import { PortMessage } from './port-message';
|
|||||||
export const createPersistentPort = (name = '') => {
|
export const createPersistentPort = (name = '') => {
|
||||||
const fingerprint = createFingerprint();
|
const fingerprint = createFingerprint();
|
||||||
let port: Runtime.Port;
|
let port: Runtime.Port;
|
||||||
|
let connected = false;
|
||||||
|
let inBackForwardCache = false;
|
||||||
let undeliveredQueue: ReadonlyArray<QueuedMessage> = [];
|
let undeliveredQueue: ReadonlyArray<QueuedMessage> = [];
|
||||||
const pendingResponses = createDeliveryLogger();
|
const pendingResponses = createDeliveryLogger();
|
||||||
const onMessageListeners = new Set<(message: InternalMessage, port: Runtime.Port) => void>();
|
const onMessageListeners = new Set<(message: InternalMessage, port: Runtime.Port) => void>();
|
||||||
@ -77,14 +80,28 @@ export const createPersistentPort = (name = '') => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const connect = () => {
|
const connect = () => {
|
||||||
|
if (connected || inBackForwardCache) return;
|
||||||
|
|
||||||
port = browser.runtime.connect({
|
port = browser.runtime.connect({
|
||||||
name: encodeConnectionArgs({
|
name: encodeConnectionArgs({
|
||||||
endpointName: name,
|
endpointName: name,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
connected = true;
|
||||||
|
const connectedPort = port;
|
||||||
port.onMessage.addListener(handleMessage);
|
port.onMessage.addListener(handleMessage);
|
||||||
port.onDisconnect.addListener(connect);
|
port.onDisconnect.addListener(() => {
|
||||||
|
const disconnectMessage = browser.runtime.lastError?.message;
|
||||||
|
if (port !== connectedPort) return;
|
||||||
|
|
||||||
|
connected = false;
|
||||||
|
if (isBackForwardCachePortError(disconnectMessage)) {
|
||||||
|
inBackForwardCache = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
connect();
|
||||||
|
});
|
||||||
|
|
||||||
PortMessage.toBackground(port, {
|
PortMessage.toBackground(port, {
|
||||||
type: 'sync',
|
type: 'sync',
|
||||||
@ -95,6 +112,17 @@ export const createPersistentPort = (name = '') => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.addEventListener('pagehide', (event) => {
|
||||||
|
if (event.persisted) inBackForwardCache = true;
|
||||||
|
});
|
||||||
|
window.addEventListener('pageshow', (event) => {
|
||||||
|
if (!event.persisted) return;
|
||||||
|
inBackForwardCache = false;
|
||||||
|
connect();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
connect();
|
connect();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
3
webext-bridge/src/internal/port-disconnect.ts
Normal file
3
webext-bridge/src/internal/port-disconnect.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export function isBackForwardCachePortError(message?: string): boolean {
|
||||||
|
return message?.includes('back/forward cache') ?? false;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user