extension-develop 4.1.28 → 4.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/616~0.mjs +3 -1
- package/dist/832~0.mjs +10 -2
- package/dist/840~0.mjs +7 -2
- package/dist/852~0.mjs +1 -1
- package/dist/bridge-entry.d.ts +1 -1
- package/dist/bridge.mjs +1 -1
- package/dist/contracts.mjs +4 -1
- package/dist/dev-server/control-bridge/broker.d.ts +3 -0
- package/dist/dev-server/control-bridge/contracts.d.ts +5 -2
- package/dist/dev-server/control-bridge/producer-runtime.d.ts +2 -2
- package/dist/dev-server/lifecycle-stream.d.ts +1 -1
- package/dist/dev-server~0.mjs +31 -2
- package/dist/extension-js-devtools/chrome/background/service_worker.js +2 -2
- package/dist/extension-js-devtools/chromium/background/service_worker.js +2 -2
- package/dist/extension-js-devtools/edge/background/service_worker.js +2 -2
- package/dist/extension-js-devtools/firefox/background/scripts.js +2 -2
- package/dist/lib/extensions-to-load.d.ts +1 -0
- package/dist/plugin-playwright/index.d.ts +5 -1
- package/dist/rspack-config~0.mjs +151 -20
- package/package.json +1 -1
package/dist/616~0.mjs
CHANGED
|
@@ -7,6 +7,8 @@ const REFUSAL_NEEDS_HEADED_WINDOW = 'needs_headed_window';
|
|
|
7
7
|
const REFUSAL_NEEDS_USER_GESTURE = 'needs_user_gesture';
|
|
8
8
|
const REFUSAL_SURFACE_NOT_OPEN = 'surface_not_open';
|
|
9
9
|
const REFUSAL_API_UNAVAILABLE = 'api_unavailable';
|
|
10
|
+
const REFUSAL_URL_REFUSED = 'url_refused';
|
|
11
|
+
const REFUSAL_TAB_NOT_FOUND = 'tab_not_found';
|
|
10
12
|
const CONTROL_WS_PATH = '/extjs-control';
|
|
11
13
|
function readPersistedControlPort(filePath) {
|
|
12
14
|
try {
|
|
@@ -73,4 +75,4 @@ function clearControlToken(projectPath, browser) {
|
|
|
73
75
|
});
|
|
74
76
|
} catch {}
|
|
75
77
|
}
|
|
76
|
-
export { CONTROL_WS_PATH, REFUSAL_API_UNAVAILABLE, REFUSAL_NEEDS_HEADED_WINDOW, REFUSAL_NEEDS_USER_GESTURE, REFUSAL_SURFACE_NOT_OPEN, clearControlToken, readControlToken, readPersistedControlPort, writeControlToken, writePersistedControlPort };
|
|
78
|
+
export { CONTROL_WS_PATH, REFUSAL_API_UNAVAILABLE, REFUSAL_NEEDS_HEADED_WINDOW, REFUSAL_NEEDS_USER_GESTURE, REFUSAL_SURFACE_NOT_OPEN, REFUSAL_TAB_NOT_FOUND, REFUSAL_URL_REFUSED, clearControlToken, readControlToken, readPersistedControlPort, writeControlToken, writePersistedControlPort };
|
package/dist/832~0.mjs
CHANGED
|
@@ -18,7 +18,7 @@ import * as __rspack_external_node_fs_1b05aee1 from "node:fs";
|
|
|
18
18
|
import * as __rspack_external_node_os_4f3c9d58 from "node:os";
|
|
19
19
|
import * as __rspack_external_node_path_806ed179 from "node:path";
|
|
20
20
|
import * as __rspack_external_node_vm_ef240610 from "node:vm";
|
|
21
|
-
var package_namespaceObject = /*#__PURE__*/ JSON.parse('{"rE":"4.1.
|
|
21
|
+
var package_namespaceObject = /*#__PURE__*/ JSON.parse('{"rE":"4.1.30","El":{"@prefresh/core":"1.5.9","@prefresh/utils":"1.2.1","@rspack/core":"2.2.3","@rspack/dev-server":"2.1.0","@rspack/plugin-preact-refresh":"2.0.1","@rspack/plugin-react-refresh":"2.0.2","@vue/compiler-sfc":"3.5.26","acorn":"^8.16.0","browser-extension-manifest-fields":"^3.0.2","case-sensitive-paths-webpack-plugin":"^2.4.0","content-security-policy-parser":"^0.6.0","cross-spawn":"^7.0.6","dotenv":"^18.0.0","es-module-lexer":"^3.0.2","extension-from-store":"^0.2.5","fflate":"^0.8.3","go-git-it":"^5.1.5","ignore":"^7.0.5","less":"4.9.1","less-loader":"13.0.0","parse5-utilities":"^1.0.0","pintor":"0.3.0","postcss":"8.5.23","postcss-loader":"8.2.1","postcss-preset-env":"11.1.1","postcss-scss":"4.0.9","preact":"10.27.3","prefers-yarn":"2.0.1","react-refresh":"0.18.0","sass-loader":"17.0.0","schema-utils":"^4.3.3","svelte-loader":"3.2.4","tiny-glob":"^0.2.9","vue":"3.5.26","vue-loader":"17.4.2","webextension-polyfill":"^0.12.0","webpack-merge":"^6.0.1","webpack-target-webextension":"^2.1.3","ws":"^8.20.1"}}');
|
|
22
22
|
const preloadedEnvKeys = new Set();
|
|
23
23
|
function getPreloadedEnvKeys() {
|
|
24
24
|
return preloadedEnvKeys;
|
|
@@ -697,6 +697,7 @@ function createPlaywrightMetadataWriter(options) {
|
|
|
697
697
|
if ('string' == typeof prev.browserExitedAt) {
|
|
698
698
|
payload.browserExitedAt = prev.browserExitedAt;
|
|
699
699
|
payload.browserExitCode = prev.browserExitCode ?? null;
|
|
700
|
+
payload.browserExitSignal = prev.browserExitSignal ?? null;
|
|
700
701
|
}
|
|
701
702
|
if ('string' == typeof prev.executorAttachedAt) {
|
|
702
703
|
payload.executorAttachedAt = prev.executorAttachedAt;
|
|
@@ -1662,6 +1663,13 @@ function resolveBuiltInExtensionForBrowser(input) {
|
|
|
1662
1663
|
if (__rspack_external_node_fs_1b05aee1.existsSync(candidate)) return candidate;
|
|
1663
1664
|
}
|
|
1664
1665
|
}
|
|
1666
|
+
const THEME_REFUSING_BROWSERS = new Set([
|
|
1667
|
+
'opera',
|
|
1668
|
+
'yandex'
|
|
1669
|
+
]);
|
|
1670
|
+
function browserAcceptsThemeExtension(browser) {
|
|
1671
|
+
return !THEME_REFUSING_BROWSERS.has(String(browser || '').toLowerCase());
|
|
1672
|
+
}
|
|
1665
1673
|
const RESERVED_BUILT_IN_NAMES = new Set([
|
|
1666
1674
|
'extension-js-devtools',
|
|
1667
1675
|
'extension-js-theme'
|
|
@@ -1701,7 +1709,7 @@ function computeExtensionsToLoad(baseDir, mode, browser, userExtensionOutputPath
|
|
|
1701
1709
|
const devtoolsHasNewTabOverride = devtoolsForBrowser ? hasNewTabOverride(devtoolsForBrowser) : false;
|
|
1702
1710
|
const shouldSkipDevtoolsForNtpConflict = userHasNewTabOverride && devtoolsHasNewTabOverride;
|
|
1703
1711
|
if (devtoolsForBrowser && !shouldSkipDevtoolsForNtpConflict) list.push(devtoolsForBrowser);
|
|
1704
|
-
if (themeForBrowser) list.push(themeForBrowser);
|
|
1712
|
+
if (themeForBrowser && browserAcceptsThemeExtension(browser)) list.push(themeForBrowser);
|
|
1705
1713
|
} catch {}
|
|
1706
1714
|
const resolvedUserOutputPath = __rspack_external_node_path_806ed179.resolve(userExtensionOutputPath);
|
|
1707
1715
|
for (const p of extraExtensionDirs)if (!isReservedBuiltInPath(p)) {
|
package/dist/840~0.mjs
CHANGED
|
@@ -175,6 +175,9 @@ class LifecycleStream {
|
|
|
175
175
|
});
|
|
176
176
|
frame.value = this.sessionValue({
|
|
177
177
|
exitCode: toFiniteNumber(args.exitCode) ?? toFiniteNumber(ready?.browserExitCode),
|
|
178
|
+
...'string' == typeof ready?.browserExitSignal ? {
|
|
179
|
+
exitSignal: ready.browserExitSignal
|
|
180
|
+
} : {},
|
|
178
181
|
...'string' == typeof ready?.browserExitedAt ? {
|
|
179
182
|
browserExitedAt: ready.browserExitedAt
|
|
180
183
|
} : {}
|
|
@@ -189,16 +192,18 @@ class LifecycleStream {
|
|
|
189
192
|
frame.value = this.sessionValue();
|
|
190
193
|
return this.emit(frame);
|
|
191
194
|
}
|
|
192
|
-
watchBrowserExit(intervalMs = 1000) {
|
|
195
|
+
watchBrowserExit(intervalMs = 1000, onExit) {
|
|
193
196
|
const stop = ()=>{
|
|
194
197
|
if (this.exitWatcher) clearInterval(this.exitWatcher);
|
|
195
198
|
this.exitWatcher = void 0;
|
|
196
199
|
};
|
|
197
|
-
if (!this.
|
|
200
|
+
if (!this.options.readyPath) return stop;
|
|
201
|
+
if (!this.enabled && !onExit) return stop;
|
|
198
202
|
if (this.exitWatcher) return stop;
|
|
199
203
|
this.exitWatcher = setInterval(()=>{
|
|
200
204
|
const ready = readReadyContract(this.options.readyPath);
|
|
201
205
|
if ('string' != typeof ready?.browserExitedAt) return;
|
|
206
|
+
onExit?.(ready);
|
|
202
207
|
this.browserExited();
|
|
203
208
|
stop();
|
|
204
209
|
}, intervalMs);
|
package/dist/852~0.mjs
CHANGED
|
@@ -95,7 +95,7 @@ function browserRowValue(browser, versionLine) {
|
|
|
95
95
|
const display = browserDisplayName(browser);
|
|
96
96
|
const line = String(versionLine || '').trim();
|
|
97
97
|
if (!line) return display;
|
|
98
|
-
if (
|
|
98
|
+
if (/^v?\d+(?:\.\d+)*(?:[a-z]+\d*)?$/i.test(line)) return `${display} ${line}`;
|
|
99
99
|
return line.charAt(0).toUpperCase() + line.slice(1);
|
|
100
100
|
}
|
|
101
101
|
const CARD_KEYS_ENV = 'EXTENSION_CLI_CARD_KEYS';
|
package/dist/bridge-entry.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { BridgeConsumer, type ConsumerCloseInfo, type ConsumerOptions, type ReadyContractDocument, type ReadyContractInfo, readReadyContract, readReadyContractDocument } from './dev-server/control-bridge/consumer-client.js';
|
|
2
2
|
export type { BridgeRefusalCode, BridgeTarget, CommandOp, GapFrame, LogContext, LogEvent, LogLevel, ReadyFrame, ResultFrame } from './dev-server/control-bridge/contracts.js';
|
|
3
|
-
export { CLOSE_BAD_HELLO, CLOSE_BAD_INSTANCE, CLOSE_CONTROL_UNAVAILABLE, CLOSE_SLOW_CONSUMER, CONTROL_ENVELOPE_VERSION, CONTROL_WS_PATH, LOG_EVENT_VERSION, REFUSAL_API_UNAVAILABLE, REFUSAL_NEEDS_HEADED_WINDOW, REFUSAL_NEEDS_USER_GESTURE, REFUSAL_SURFACE_NOT_OPEN } from './dev-server/control-bridge/contracts.js';
|
|
3
|
+
export { CLOSE_BAD_HELLO, CLOSE_BAD_INSTANCE, CLOSE_CONTROL_UNAVAILABLE, CLOSE_SLOW_CONSUMER, CONTROL_ENVELOPE_VERSION, CONTROL_WS_PATH, LOG_EVENT_VERSION, REFUSAL_API_UNAVAILABLE, REFUSAL_NEEDS_HEADED_WINDOW, REFUSAL_NEEDS_USER_GESTURE, REFUSAL_SURFACE_NOT_OPEN, REFUSAL_TAB_NOT_FOUND, REFUSAL_URL_REFUSED } from './dev-server/control-bridge/contracts.js';
|
|
4
4
|
export { controlPortFilePath, readPersistedControlPort } from './dev-server/control-bridge/control-port-store.js';
|
|
5
5
|
export { BridgeController, type CommandInput, type ControllerOptions } from './dev-server/control-bridge/controller-client.js';
|
|
6
6
|
export { isAfterSince, LOG_LEVEL_ORDER, type LogQuery, type LogSince, logLevelRank, matchesLogQuery, parseLogSince, readLogEvents } from './dev-server/control-bridge/logs-query.js';
|
package/dist/bridge.mjs
CHANGED
|
@@ -386,6 +386,6 @@ var bridge_entry_CLOSE_CONTROL_UNAVAILABLE = 4003;
|
|
|
386
386
|
var bridge_entry_CLOSE_SLOW_CONSUMER = 4008;
|
|
387
387
|
var bridge_entry_CONTROL_ENVELOPE_VERSION = 1;
|
|
388
388
|
var bridge_entry_LOG_EVENT_VERSION = 1;
|
|
389
|
-
export { CONTROL_WS_PATH, REFUSAL_API_UNAVAILABLE, REFUSAL_NEEDS_HEADED_WINDOW, REFUSAL_NEEDS_USER_GESTURE, REFUSAL_SURFACE_NOT_OPEN, readControlToken, readPersistedControlPort } from "./616~0.mjs";
|
|
389
|
+
export { CONTROL_WS_PATH, REFUSAL_API_UNAVAILABLE, REFUSAL_NEEDS_HEADED_WINDOW, REFUSAL_NEEDS_USER_GESTURE, REFUSAL_SURFACE_NOT_OPEN, REFUSAL_TAB_NOT_FOUND, REFUSAL_URL_REFUSED, readControlToken, readPersistedControlPort } from "./616~0.mjs";
|
|
390
390
|
export { actionsPath, browserArtifactsDir, browserDistDir, browserProfileRootDir, buildSummaryPath, controlPortFilePath, controlTokenPath, eventsPath, logsPath, readyContractPath, sessionArtifactsRootDir, sessionStateDir } from "./950~0.mjs";
|
|
391
391
|
export { BridgeConsumer, BridgeController, LOG_LEVEL_ORDER, bridge_entry_CLOSE_BAD_HELLO as CLOSE_BAD_HELLO, bridge_entry_CLOSE_BAD_INSTANCE as CLOSE_BAD_INSTANCE, bridge_entry_CLOSE_CONTROL_UNAVAILABLE as CLOSE_CONTROL_UNAVAILABLE, bridge_entry_CLOSE_SLOW_CONSUMER as CLOSE_SLOW_CONSUMER, bridge_entry_CONTROL_ENVELOPE_VERSION as CONTROL_ENVELOPE_VERSION, bridge_entry_LOG_EVENT_VERSION as LOG_EVENT_VERSION, isAfterSince, logLevelRank, matchesLogQuery, parseLogSince, readLogEvents, readReadyContract, readReadyContractDocument, resolveSessionProjectRoot };
|
package/dist/contracts.mjs
CHANGED
|
@@ -3,10 +3,13 @@ const CONTROL_ENVELOPE_VERSION = 1;
|
|
|
3
3
|
const REFUSAL_NEEDS_HEADED_WINDOW = 'needs_headed_window';
|
|
4
4
|
const REFUSAL_NEEDS_USER_GESTURE = 'needs_user_gesture';
|
|
5
5
|
const REFUSAL_SURFACE_NOT_OPEN = 'surface_not_open';
|
|
6
|
+
const REFUSAL_SURFACE_REPLY_FAILED = 'surface_reply_failed';
|
|
6
7
|
const REFUSAL_API_UNAVAILABLE = 'api_unavailable';
|
|
8
|
+
const REFUSAL_URL_REFUSED = 'url_refused';
|
|
9
|
+
const REFUSAL_TAB_NOT_FOUND = 'tab_not_found';
|
|
7
10
|
const CONTROL_WS_PATH = '/extjs-control';
|
|
8
11
|
const CLOSE_BAD_INSTANCE = 4001;
|
|
9
12
|
const CLOSE_BAD_HELLO = 4002;
|
|
10
13
|
const CLOSE_CONTROL_UNAVAILABLE = 4003;
|
|
11
14
|
const CLOSE_SLOW_CONSUMER = 4008;
|
|
12
|
-
export { CLOSE_BAD_HELLO, CLOSE_BAD_INSTANCE, CLOSE_CONTROL_UNAVAILABLE, CLOSE_SLOW_CONSUMER, CONTROL_ENVELOPE_VERSION, CONTROL_WS_PATH, LOG_EVENT_VERSION, REFUSAL_API_UNAVAILABLE, REFUSAL_NEEDS_HEADED_WINDOW, REFUSAL_NEEDS_USER_GESTURE, REFUSAL_SURFACE_NOT_OPEN };
|
|
15
|
+
export { CLOSE_BAD_HELLO, CLOSE_BAD_INSTANCE, CLOSE_CONTROL_UNAVAILABLE, CLOSE_SLOW_CONSUMER, CONTROL_ENVELOPE_VERSION, CONTROL_WS_PATH, LOG_EVENT_VERSION, REFUSAL_API_UNAVAILABLE, REFUSAL_NEEDS_HEADED_WINDOW, REFUSAL_NEEDS_USER_GESTURE, REFUSAL_SURFACE_NOT_OPEN, REFUSAL_SURFACE_REPLY_FAILED, REFUSAL_TAB_NOT_FOUND, REFUSAL_URL_REFUSED };
|
|
@@ -55,6 +55,8 @@ export declare class BridgeBroker {
|
|
|
55
55
|
private readonly onExecutorAttached?;
|
|
56
56
|
private readonly onExecutorDetached?;
|
|
57
57
|
private lastProducerDisconnectedAt;
|
|
58
|
+
private browserExitedAt;
|
|
59
|
+
private browserExitHow;
|
|
58
60
|
private lastStaleHelloAt;
|
|
59
61
|
private pendingReload?;
|
|
60
62
|
constructor(options: BridgeBrokerOptions);
|
|
@@ -79,6 +81,7 @@ export declare class BridgeBroker {
|
|
|
79
81
|
ingestLog(incoming: IncomingLogEvent): void;
|
|
80
82
|
private allowStaleProducerResync;
|
|
81
83
|
private onHello;
|
|
84
|
+
noteBrowserExited(at: string, how?: string | null): void;
|
|
82
85
|
private diagnoseExecutorAbsence;
|
|
83
86
|
private gateEval;
|
|
84
87
|
private controllerReady;
|
|
@@ -40,7 +40,7 @@ export interface BridgeTarget {
|
|
|
40
40
|
url?: string;
|
|
41
41
|
tabId?: number;
|
|
42
42
|
}
|
|
43
|
-
export type CommandOp = 'eval' | 'storage.get' | 'storage.set' | 'reload' | 'open' | 'tabs.query' | 'inspect';
|
|
43
|
+
export type CommandOp = 'eval' | 'storage.get' | 'storage.set' | 'reload' | 'open' | 'tabs.query' | 'tabs.navigate' | 'inspect';
|
|
44
44
|
/**
|
|
45
45
|
* Granularity of a dev-loop reload. Mirrors `ReloadType` in
|
|
46
46
|
* plugin-reload/classify-reload.ts: the launched-browser path feeds this
|
|
@@ -99,8 +99,11 @@ export interface CommandFrame {
|
|
|
99
99
|
export declare const REFUSAL_NEEDS_HEADED_WINDOW = "needs_headed_window";
|
|
100
100
|
export declare const REFUSAL_NEEDS_USER_GESTURE = "needs_user_gesture";
|
|
101
101
|
export declare const REFUSAL_SURFACE_NOT_OPEN = "surface_not_open";
|
|
102
|
+
export declare const REFUSAL_SURFACE_REPLY_FAILED = "surface_reply_failed";
|
|
102
103
|
export declare const REFUSAL_API_UNAVAILABLE = "api_unavailable";
|
|
103
|
-
export
|
|
104
|
+
export declare const REFUSAL_URL_REFUSED = "url_refused";
|
|
105
|
+
export declare const REFUSAL_TAB_NOT_FOUND = "tab_not_found";
|
|
106
|
+
export type BridgeRefusalCode = typeof REFUSAL_NEEDS_HEADED_WINDOW | typeof REFUSAL_NEEDS_USER_GESTURE | typeof REFUSAL_SURFACE_NOT_OPEN | typeof REFUSAL_SURFACE_REPLY_FAILED | typeof REFUSAL_API_UNAVAILABLE | typeof REFUSAL_URL_REFUSED | typeof REFUSAL_TAB_NOT_FOUND;
|
|
104
107
|
export interface ResultFrame {
|
|
105
108
|
type: 'result';
|
|
106
109
|
cmdId: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare const CONTROL_PORT_ASSET_NAME = "extension-js-control.json";
|
|
2
|
-
export declare const BRIDGE_PRODUCER_SOURCE = ";(function () {\n try {\n var g = (typeof globalThis === \"object\" && globalThis) ? globalThis : this;\n if (!g || g.__extjsBridgeProducerInstalled) return;\n\n var PORT = parseInt(\"__EXTJS_CONTROL_PORT__\", 10);\n var INSTANCE_ID = \"__EXTJS_INSTANCE_ID__\";\n var CONTEXT = \"__EXTJS_CONTEXT__\";\n // Connectable host of the dev-server control WS. Baked from the resolved\n // connectable host (loopback locally; the public host for remote/devcontainer).\n var HOST = \"__EXTJS_CONTROL_HOST__\";\n if (!HOST || HOST.indexOf(\"__EXTJS\") === 0) HOST = \"127.0.0.1\";\n if (!PORT || PORT < 1) return;\n\n var WS = g.WebSocket;\n if (typeof WS !== \"function\") return;\n\n var consoleRef = g.console || {};\n g.__extjsBridgeProducerInstalled = true;\n\n // Capture extension event listeners at install time: these wraps run\n // BEFORE user code, and no platform API exists to dispatch these events.\n function captureEvent(event, sink) {\n try {\n if (!event || typeof event.addListener !== \"function\") return;\n var origAdd = event.addListener.bind(event);\n event.addListener = function (cb) {\n if (typeof cb === \"function\" && sink.indexOf(cb) === -1) sink.push(cb);\n return origAdd(cb);\n };\n if (typeof event.removeListener === \"function\") {\n var origRemove = event.removeListener.bind(event);\n event.removeListener = function (cb) {\n var i = sink.indexOf(cb);\n if (i !== -1) sink.splice(i, 1);\n return origRemove(cb);\n };\n }\n } catch (e) { /* non-fatal: trigger falls back to its no-listener reply */ }\n }\n\n // Use the raw globals (not the hoisted chrome var below). This runs first.\n // On Gecko browser.* is a DISTINCT wrapper object: hook BOTH namespaces.\n var actionClickedListeners = [];\n var commandListeners = [];\n var capturedEvents = [];\n function captureEventOnce(event, sink) {\n // Polyfilled extensions expose ONE event object on both namespaces:\n // wrap it once, and the sink dedupes callbacks shared across wrappers.\n if (!event || capturedEvents.indexOf(event) !== -1) return;\n capturedEvents.push(event);\n captureEvent(event, sink);\n }\n var captureNamespaces = [g.chrome, g.browser];\n for (var cn = 0; cn < captureNamespaces.length; cn++) {\n var capNS = captureNamespaces[cn];\n if (!capNS) continue;\n captureEventOnce(capNS.action && capNS.action.onClicked, actionClickedListeners);\n captureEventOnce(capNS.commands && capNS.commands.onCommand, commandListeners);\n }\n\n var LEVELS = [\"log\", \"info\", \"warn\", \"error\", \"debug\", \"trace\"];\n var socket = null;\n var open = false;\n var queue = [];\n var backoff = 250;\n var MAX_BACKOFF = 5000;\n var connectFailures = 0;\n var MAX_QUEUE = 1000;\n var MAX_RESULT_BYTES = 256 * 1024;\n // Where error frames are parked so a context that dies before its socket\n // opens does not take them along. Capped, and old entries are dropped.\n var PENDING_ERRORS_KEY = \"__extjsPendingErrors\";\n var MAX_PENDING_ERRORS = 20;\n var PENDING_ERROR_TTL = 300000;\n\n function nowId() {\n return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);\n }\n\n // The extension's own URL scheme names the engine outright, which beats a\n // user agent where Chrome and Safari both say \"Safari\".\n function engineName() {\n try {\n var scheme = (typeof location !== \"undefined\" && location.protocol) || \"\";\n if (scheme.indexOf(\"safari-web-extension\") === 0) return \"webkit\";\n if (scheme.indexOf(\"moz-extension\") === 0) return \"firefox\";\n if (scheme.indexOf(\"chrome-extension\") === 0) return \"chromium\";\n var ua = (typeof navigator !== \"undefined\" && navigator.userAgent) || \"\";\n if (/firefox/i.test(ua)) return \"firefox\";\n if (/safari/i.test(ua) && !/chrome|chromium|edg/i.test(ua)) return \"webkit\";\n return \"chromium\";\n } catch (e) { return \"chromium\"; }\n }\n\n function safeValue(v) {\n try {\n var s = JSON.stringify(v);\n if (s === undefined) return {value: undefined, truncated: false};\n if (s.length > MAX_RESULT_BYTES) {\n return {value: {__type: \"truncated\", preview: s.slice(0, 1024)}, truncated: true};\n }\n return {value: JSON.parse(s), truncated: false};\n } catch (e) {\n return {value: {__type: \"unserializable\", preview: String(v).slice(0, 512)}, truncated: false};\n }\n }\n\n function replyOk(cmdId, value) {\n var s = safeValue(value);\n var frame = {type: \"result\", cmdId: cmdId, ok: true, value: s.value};\n if (s.truncated) frame.truncated = true;\n try { socket && socket.send(JSON.stringify(frame)); } catch (e) {\n // Ignore\n }\n }\n\n function replyErr(cmdId, name, message, code) {\n var frame = {type: \"result\", cmdId: cmdId, ok: false, error: {name: name, message: String(message), engine: engineName()}};\n if (code) frame.error.code = code;\n try { socket && socket.send(JSON.stringify(frame)); } catch (e) {\n // Ignore\n }\n }\n\n // Name the refusal the browser wrote as prose, so a consumer branches on\n // error.code instead of matching the engine's sentence.\n function openRefusalCode(message) {\n var m = String(message == null ? \"\" : message);\n if (/user gesture|user input|user interaction|user action/i.test(m)) return \"needs_user_gesture\";\n if (/browser window|active window/i.test(m)) return \"needs_headed_window\";\n return undefined;\n }\n\n // Resolve a numeric tab id when the caller did not pass one: match\n // target.url against open tabs, else default to the active tab. (#51)\n function resolveTargetTab(target, cb) {\n var chrome = g.chrome;\n if (!chrome || !chrome.tabs) { cb(null, \"chrome.tabs is not available\"); return; }\n var url = target && target.url;\n if (url) {\n var substringMatch = function () {\n try {\n chrome.tabs.query({}, function (all) {\n var hit = (all || []).filter(function (t) { return t.url && String(t.url).indexOf(url) !== -1; })[0];\n if (hit && typeof hit.id === \"number\") { cb(hit.id, null); return; }\n cb(null, \"no open tab matches url: \" + url);\n });\n } catch (e2) { cb(null, \"tab query failed\"); }\n };\n try {\n // chrome.tabs.query accepts URL match patterns; a bare url/glob may\n // not be a valid pattern, so fall back to a substring scan.\n chrome.tabs.query({url: url}, function (tabs) {\n if (chrome.runtime && chrome.runtime.lastError) { substringMatch(); return; }\n if (tabs && tabs.length && typeof tabs[0].id === \"number\") { cb(tabs[0].id, null); return; }\n substringMatch();\n });\n } catch (e) { substringMatch(); }\n return;\n }\n try {\n chrome.tabs.query({active: true, lastFocusedWindow: true}, function (tabs) {\n var t = tabs && tabs[0];\n if (t && typeof t.id === \"number\") { cb(t.id, null); return; }\n cb(null, \"no active tab to target\");\n });\n } catch (e3) { cb(null, \"tab query failed\"); }\n }\n\n // chrome.* async APIs are callback-only on Gecko. Prefer promisified\n // browser.* when present, else the chrome.* callback form. cb(err, result).\n function nsCall(nsName, method, callArgs, cb) {\n var chromeG = g.chrome;\n var done = false;\n var once = function (err, r) { if (!done) { done = true; cb(err, r); } };\n var viaPromise = function (ns) {\n try {\n var ret = ns[method].apply(ns, callArgs);\n if (ret && typeof ret.then === \"function\") {\n ret.then(function (r) { once(null, r); }, function (e) { once(e); });\n return true;\n }\n } catch (e) { once(e); return true; }\n return false;\n };\n var bNS = g.browser && g.browser[nsName];\n if (bNS && typeof bNS[method] === \"function\" && viaPromise(bNS)) return;\n var cNS = chromeG && chromeG[nsName];\n if (!cNS || typeof cNS[method] !== \"function\") {\n once(new Error(\"chrome.\" + nsName + \".\" + method + \" is not available (engine: \" + engineName() + \")\"));\n return;\n }\n try {\n var ret2 = cNS[method].apply(cNS, callArgs.concat([function (r) {\n var le = chromeG.runtime && chromeG.runtime.lastError;\n if (le) once(le); else once(null, r);\n }]));\n // A promise-only implementation ignores the trailing callback and\n // returns a thenable instead; resolve through it (once() dedupes).\n if (ret2 && typeof ret2.then === \"function\") {\n ret2.then(function (r) { once(null, r); }, function (e) { once(e); });\n }\n } catch (e2) {\n if (!viaPromise(cNS)) once(e2);\n }\n }\n\n function executeCommand(cmd) {\n var op = cmd.op, target = cmd.target || {}, args = cmd.args || {};\n var ctx = target.context || \"background\";\n var chrome = g.chrome;\n var cmdId = cmd.cmdId;\n // content/page eval & inspect need a numeric tab id: resolve --url (or\n // nothing) to a tab and re-dispatch with target.tabId filled in. (#51)\n if ((op === \"eval\" || op === \"inspect\") && (ctx === \"content\" || ctx === \"page\") && target.tabId == null) {\n resolveTargetTab(target, function (tabId, err) {\n if (tabId == null) {\n replyErr(cmdId, \"Unsupported\", err || (\"eval/inspect in context \" + ctx + \" needs a --tab id, a --url to match, or an active tab\"));\n return;\n }\n var next = {};\n for (var k in cmd) { if (Object.prototype.hasOwnProperty.call(cmd, k)) next[k] = cmd[k]; }\n next.target = {};\n for (var tk in target) { if (Object.prototype.hasOwnProperty.call(target, tk)) next.target[tk] = target[tk]; }\n next.target.tabId = tabId;\n executeCommand(next);\n });\n return;\n }\n try {\n if (!chrome) { replyErr(cmdId, \"Unsupported\", \"chrome.* not available in this context\"); return; }\n if (op === \"eval\") {\n if (ctx === \"background\") {\n Promise.resolve().then(function () { return (0, eval)(args.expression); })\n .then(function (r) { replyOk(cmdId, r); }, function (e) {\n var msg = (e && e.message) || String(e);\n // MV3 forbids eval of strings in the SW/extension pages and\n // rejects 'unsafe-eval'. Surface it honestly with an alternative.\n if (/Content Security Policy|unsafe-eval/i.test(msg)) {\n replyErr(cmdId, \"Unsupported\", \"eval is blocked in the extension background by CSP, which allows no unsafe-eval. Use --context page --tab <id> (eval runs in the page's MAIN world), or run on a build whose CSP permits it. Engine: \" + engineName());\n } else {\n replyErr(cmdId, (e && e.name) || \"EvalError\", msg);\n }\n });\n } else if ((ctx === \"content\" || ctx === \"page\") && target.tabId && chrome.scripting) {\n // Wrap the eval so a throw INSIDE the tab comes back as data:\n // Chrome swallows an injected function's exception (null result).\n nsCall(\"scripting\", \"executeScript\", [{\n target: {tabId: target.tabId},\n world: ctx === \"page\" ? \"MAIN\" : \"ISOLATED\",\n func: function (src) {\n try { return {__extjsEval: 1, ok: true, value: eval(src)}; }\n catch (e) { return {__extjsEval: 1, ok: false, name: (e && e.name) || \"EvalError\", message: (e && e.message) || String(e)}; }\n },\n args: [String(args.expression)]\n }], function (err, res) {\n if (err) { replyErr(cmdId, \"TargetNotFound\", (err && err.message) || err); return; }\n var frame = res && res[0] ? res[0].result : undefined;\n if (!frame || frame.__extjsEval !== 1) {\n replyErr(cmdId, \"TargetNotFound\", \"the expression never executed in tab \" + target.tabId + \": no injectable frame returned a result (restricted page, or outside host_permissions)\");\n return;\n }\n if (frame.ok) { replyOk(cmdId, frame.value); return; }\n if (ctx === \"content\" && /Content Security Policy|unsafe-eval|EvalError/i.test(String(frame.name) + \" \" + String(frame.message))) {\n replyErr(cmdId, \"Unsupported\", \"eval of a string is blocked in the ISOLATED (content) world by the extension CSP. Use --context page (runs in the page's MAIN world). Original: \" + frame.message);\n return;\n }\n replyErr(cmdId, frame.name || \"EvalError\", frame.message);\n });\n } else if (ctx === \"popup\" || ctx === \"options\" || ctx === \"sidebar\" || ctx === \"devtools\" || ctx === \"newtab\" || ctx === \"history\" || ctx === \"bookmarks\") {\n // The SW can't eval in another extension page; ask the surface's\n // own in-bundle relay (only the open, matching-context page responds).\n chrome.runtime.sendMessage(\n {__extjsEvalRequest: true, target: target, args: {expression: String(args.expression)}},\n function (resp) {\n if ((chrome.runtime && chrome.runtime.lastError) || !resp) {\n replyErr(cmdId, \"Unsupported\", \"surface '\" + ctx + \"' is not open (open it first: extension open \" + ctx + \")\", \"surface_not_open\");\n } else if (resp.ok) {\n replyOk(cmdId, resp.value);\n } else {\n replyErr(cmdId, (resp.error && resp.error.name) || \"EvalError\", (resp.error && resp.error.message) || \"eval failed\");\n }\n }\n );\n } else if (ctx === \"content\" || ctx === \"page\") {\n replyErr(cmdId, \"Unsupported\", target.tabId\n ? \"chrome.scripting is not available on this engine (MV2 has no scripting API); use --context background\"\n : \"eval in context \" + ctx + \" requires a tabId\");\n } else {\n replyErr(cmdId, \"BadRequest\", \"unknown eval context: \" + ctx);\n }\n return;\n }\n if (op === \"storage.get\" || op === \"storage.set\") {\n // chrome.* storage is callback-only on Gecko and older Chromium.\n // Prefer promisified browser.*, then fall back to the callback shape.\n var storageNS = (g.browser && g.browser.storage) ? g.browser.storage : chrome.storage;\n var storageArea = storageNS && storageNS[args.area || \"local\"];\n if (!storageArea) { replyErr(cmdId, \"StorageError\", \"storage.\" + (args.area || \"local\") + \" unavailable\"); return; }\n var isSet = op === \"storage.set\";\n var callArgs = isSet ? [args.items || {}] : [args.key != null ? args.key : null];\n var onStorageOk = function (r) { replyOk(cmdId, isSet ? {set: Object.keys(args.items || {})} : r); };\n var onStorageErr = function (e) { replyErr(cmdId, \"StorageError\", (e && e.message) || String(e)); };\n var storageFn = isSet ? storageArea.set : storageArea.get;\n try {\n var storageRet = storageFn.apply(storageArea, callArgs);\n if (storageRet && typeof storageRet.then === \"function\") {\n storageRet.then(onStorageOk, onStorageErr);\n return;\n }\n } catch (e) { /* not thenable / threw synchronously: fall back to callback */ }\n try {\n storageFn.apply(storageArea, callArgs.concat([function (r) {\n var le = chrome.runtime && chrome.runtime.lastError;\n if (le) onStorageErr(le); else onStorageOk(r);\n }]));\n } catch (e) { onStorageErr(e); }\n return;\n }\n if (op === \"reload\") {\n if (ctx === \"background\") {\n replyOk(cmdId, {reloading: true});\n setTimeout(function () { try { chrome.runtime.reload(); } catch (e) {\n // Ignore\n } }, 50);\n } else if (target.tabId) {\n nsCall(\"tabs\", \"reload\", [target.tabId], function (err) {\n if (err) replyErr(cmdId, \"ReloadError\", (err && err.message) || err);\n else replyOk(cmdId, {reloaded: target.tabId});\n });\n } else {\n replyErr(cmdId, \"Unsupported\", \"reload needs background or a tabId\");\n }\n return;\n }\n if (op === \"open\") {\n var surface = args.surface || ctx;\n // getPopup is promise-style on Gecko and (MV3) Chromium; fall back to\n // callback-style for older Chromium. Always resolves to a string.\n var getActionPopup = function (cb) {\n try {\n var r = chrome.action && chrome.action.getPopup && chrome.action.getPopup({});\n if (r && typeof r.then === \"function\") {\n r.then(function (p) { cb(p || \"\"); }, function () { cb(\"\"); });\n return;\n }\n } catch (e) {\n // Ignore\n }\n try { chrome.action.getPopup({}, function (p) { cb(p || \"\"); }); }\n catch (e) { cb(\"\"); }\n };\n var resolveActiveTab = function (a, cb) {\n if (a && typeof a.tabId === \"number\") {\n try { chrome.tabs.get(a.tabId, function (t) { cb(t || {id: a.tabId}); }); return; }\n catch (e) {\n // Ignore\n }\n }\n try { chrome.tabs.query({active: true, lastFocusedWindow: true}, function (tabs) { cb((tabs && tabs[0]) || undefined); }); }\n catch (e) { cb(undefined); }\n };\n // Replaying a listener carries no user gesture, so activeTab is never\n // granted. Warn when the manifest declares it.\n var activeTabWarning = function () {\n try {\n var m = chrome.runtime.getManifest();\n var perms = (m && m.permissions) || [];\n if (perms.indexOf(\"activeTab\") !== -1) {\n return \"replayed without a user gesture: activeTab is NOT granted, so APIs that depend on it (scripting on the active tab, captureVisibleTab) behave differently than a real click\";\n }\n } catch (e) {\n // Ignore\n }\n return null;\n };\n if (surface === \"popup\") {\n if (chrome.action && chrome.action.openPopup) {\n nsCall(\"action\", \"openPopup\", [], function (err) {\n if (err) {\n var openMsg = (err && err.message) || err;\n replyErr(cmdId, \"Unsupported\", \"openPopup: \" + openMsg, openRefusalCode(openMsg));\n } else replyOk(cmdId, {opened: \"popup\"});\n });\n } else { replyErr(cmdId, \"Unsupported\", \"action.openPopup not available\", \"api_unavailable\"); }\n } else if (surface === \"options\") {\n try { chrome.runtime.openOptionsPage(function () { replyOk(cmdId, {opened: \"options\"}); }); }\n catch (e) { replyErr(cmdId, \"Unsupported\", \"openOptionsPage: \" + e); }\n } else if (surface === \"sidebar\") {\n // The two member reads below are exactly what the Safari\n // unsupported-API scan looks for, and this runtime is prepended to\n // the background asset at PROCESS_ASSETS_STAGE_REPORT + 101, after\n // that scan runs at SUMMARIZE + 1. Move the injection earlier and\n // every Safari dev session warns about this line.\n if (chrome.sidePanel && chrome.sidePanel.open && chrome.windows) {\n chrome.windows.getCurrent(function (w) {\n nsCall(\"sidePanel\", \"open\", [{windowId: w.id}], function (err) {\n if (err) {\n var sideMsg = (err && err.message) || err;\n replyErr(cmdId, \"Unsupported\", \"sidePanel.open: \" + sideMsg, openRefusalCode(sideMsg));\n } else replyOk(cmdId, {opened: \"sidebar\"});\n });\n });\n } else { replyErr(cmdId, \"Unsupported\", \"sidePanel not available (engine: \" + engineName() + \")\", \"api_unavailable\"); }\n } else if (surface === \"action\") {\n // With a default_popup the click opens it; without one, replay the\n // onClicked listeners captured at install time.\n if (chrome.action) {\n getActionPopup(function (popup) {\n if (popup) {\n nsCall(\"action\", \"openPopup\", [], function (err) {\n if (err) {\n var actMsg = (err && err.message) || err;\n replyErr(cmdId, \"Unsupported\", \"openPopup: \" + actMsg, openRefusalCode(actMsg));\n } else replyOk(cmdId, {triggered: \"popup\"});\n });\n } else if (actionClickedListeners.length) {\n resolveActiveTab(args, function (tab) {\n var fired = 0;\n for (var i = 0; i < actionClickedListeners.length; i++) {\n try { actionClickedListeners[i](tab); fired++; } catch (e) {\n // Ignore\n }\n }\n var reply = {triggered: \"onClicked\", listeners: fired, gesture: false};\n var warning = activeTabWarning();\n if (warning) reply.warning = warning;\n replyOk(cmdId, reply);\n });\n } else {\n replyErr(cmdId, \"Unsupported\", \"action has no popup and no onClicked listener registered\");\n }\n });\n } else { replyErr(cmdId, \"Unsupported\", \"chrome.action not available (engine: \" + engineName() + \")\", \"api_unavailable\"); }\n } else if (surface === \"command\") {\n var commandName = (args && args.name) || undefined;\n if (!commandListeners.length) {\n replyErr(cmdId, \"Unsupported\", \"no chrome.commands.onCommand listener registered\");\n } else {\n resolveActiveTab(args, function (tab) {\n var fired = 0;\n for (var i = 0; i < commandListeners.length; i++) {\n try { commandListeners[i](commandName, tab); fired++; } catch (e) {\n // Ignore\n }\n }\n replyOk(cmdId, {triggered: \"command\", command: commandName || null, listeners: fired, gesture: false});\n });\n }\n } else { replyErr(cmdId, \"BadRequest\", \"unknown surface: \" + surface); }\n return;\n }\n if (op === \"tabs.query\") {\n nsCall(\"tabs\", \"query\", [args || {}], function (err, tabs) {\n if (err) { replyErr(cmdId, \"TabsError\", (err && err.message) || err); return; }\n replyOk(cmdId, (tabs || []).map(function (t) { return {id: t.id, url: t.url, title: t.title, active: t.active, windowId: t.windowId}; }));\n });\n return;\n }\n if (op === \"inspect\") {\n // Extract a DOM snapshot from the target page via chrome.scripting\n // (CDP-free). Closed shadow roots need CDP; here we read open ones.\n if ((ctx === \"content\" || ctx === \"page\") && target.tabId && chrome.scripting) {\n var maxBytes = (args && args.maxBytes) || 262144;\n var includeHtml = !args || !args.include || args.include.indexOf(\"html\") !== -1;\n nsCall(\"scripting\", \"executeScript\", [{\n target: {tabId: target.tabId},\n world: ctx === \"page\" ? \"MAIN\" : \"ISOLATED\",\n func: function (wantHtml, cap) {\n function countShadow(root) {\n var n = 0, els = root.querySelectorAll(\"*\");\n for (var i = 0; i < els.length; i++) { if (els[i].shadowRoot) { n++; n += countShadow(els[i].shadowRoot); } }\n return n;\n }\n var doc = document;\n var roots = doc.querySelectorAll('#extension-root,[data-extension-root]');\n var out = {\n url: location.href,\n title: doc.title,\n summary: {\n htmlLength: doc.documentElement.outerHTML.length,\n scriptCount: doc.querySelectorAll(\"script\").length,\n styleCount: doc.querySelectorAll(\"style,link[rel=stylesheet]\").length,\n extensionRootCount: roots.length,\n openShadowRoots: countShadow(doc),\n bodyChildCount: doc.body ? doc.body.children.length : 0\n }\n };\n if (wantHtml) {\n var html = doc.documentElement.outerHTML;\n if (cap > 0 && html.length > cap) { out.html = html.slice(0, cap); out.htmlTruncated = true; }\n else { out.html = html; }\n }\n return out;\n },\n args: [includeHtml, maxBytes]\n }], function (err, res) {\n if (err) { replyErr(cmdId, \"TargetNotFound\", (err && err.message) || err); return; }\n var snap = res && res[0] ? res[0].result : undefined;\n if (snap == null) { replyErr(cmdId, \"TargetNotFound\", \"no injectable frame returned a snapshot for tab \" + target.tabId + \" (restricted page, or outside host_permissions)\"); return; }\n replyOk(cmdId, snap);\n });\n } else if (ctx === \"popup\" || ctx === \"options\" || ctx === \"sidebar\" || ctx === \"devtools\" || ctx === \"newtab\" || ctx === \"history\" || ctx === \"bookmarks\") {\n // The SW can't read a surface page's DOM; ask the surface's own\n // in-bundle relay (only the open, matching-context page responds).\n chrome.runtime.sendMessage(\n {__extjsInspectRequest: true, target: target, args: args},\n function (resp) {\n if (chrome.runtime.lastError || !resp) {\n replyErr(cmdId, \"Unsupported\", \"surface '\" + ctx + \"' is not open (open it first: extension open \" + ctx + \")\", \"surface_not_open\");\n } else if (resp.ok) {\n replyOk(cmdId, resp.value);\n } else {\n replyErr(cmdId, (resp.error && resp.error.name) || \"InspectError\", (resp.error && resp.error.message) || \"inspect failed\");\n }\n }\n );\n } else if (ctx === \"background\") {\n replyErr(cmdId, \"Unsupported\", \"the service worker has no DOM; inspect a content/page (with --tab) or an open surface\");\n } else {\n replyErr(cmdId, \"Unsupported\", \"inspect of \" + ctx + \" requires a tabId (content/page) or an open surface\");\n }\n return;\n }\n replyErr(cmdId, \"BadRequest\", \"unknown op: \" + op);\n } catch (e) {\n replyErr(cmdId, \"ExecutorError\", e);\n }\n }\n\n function noopLastError() { try { void g.chrome.runtime.lastError; } catch (e) {\n // Ignore\n } }\n\n // Only http(s)/file/ftp tabs can run a content script; skip chrome://, the\n // extension's own pages, about:blank, etc.\n function isInjectableUrl(url) {\n return typeof url === \"string\" && /^(https?|file|ftp):/i.test(url);\n }\n\n // Re-inject one declared entry's fresh files into every matching open tab.\n // exclude_matches MUST be honored: query the excludes and subtract them.\n // opts.completeOnly skips a tab still loading: Chrome injects the static\n // script into it when it lands, and a second copy here would run twice.\n function reinjectContentScriptEntry(entry, opts) {\n var chrome = g.chrome;\n var completeOnly = !!(opts && opts.completeOnly);\n var matches = (entry && entry.matches) || [];\n if (!Array.isArray(matches) || !matches.length) return;\n var excludeMatches = (entry && entry.exclude_matches) || [];\n var jsFiles = ((entry && entry.js) || []).filter(function (f) { return typeof f === \"string\"; });\n var cssFiles = ((entry && entry.css) || []).filter(function (f) { return typeof f === \"string\"; });\n if (!jsFiles.length && !cssFiles.length) return;\n var world = entry.world === \"MAIN\" ? \"MAIN\" : \"ISOLATED\";\n var allFrames = !!entry.all_frames;\n function injectInto(excludedTabIds) {\n try {\n chrome.tabs.query({url: matches}, function (tabs) {\n var err = null;\n try { err = chrome.runtime.lastError; } catch (e) {\n // Ignore\n }\n if (err || !tabs) return;\n for (var i = 0; i < tabs.length; i++) {\n (function (tab) {\n if (!tab || tab.id == null || !isInjectableUrl(tab.url)) return;\n if (excludedTabIds[tab.id]) return;\n if (completeOnly && tab.status === \"loading\") return;\n var target = {tabId: tab.id, allFrames: allFrames};\n if (cssFiles.length && chrome.scripting.insertCSS) {\n try { chrome.scripting.insertCSS({target: target, files: cssFiles}, noopLastError); } catch (e) {\n // Ignore\n }\n }\n if (jsFiles.length && chrome.scripting.executeScript) {\n try {\n chrome.scripting.executeScript(\n {target: target, files: jsFiles, world: world, injectImmediately: true},\n noopLastError\n );\n } catch (e) {\n // Ignore\n }\n }\n })(tabs[i]);\n }\n });\n } catch (e) {\n // Ignore\n }\n }\n if (Array.isArray(excludeMatches) && excludeMatches.length) {\n try {\n chrome.tabs.query({url: excludeMatches}, function (excludedTabs) {\n try { void chrome.runtime.lastError; } catch (e) {\n // Ignore\n }\n var excluded = {};\n if (excludedTabs) {\n for (var i = 0; i < excludedTabs.length; i++) {\n if (excludedTabs[i] && excludedTabs[i].id != null) excluded[excludedTabs[i].id] = true;\n }\n }\n injectInto(excluded);\n });\n return;\n } catch (e) {\n // Ignore\n }\n }\n injectInto({});\n }\n\n // Re-inject all content scripts into their open tabs. Reads the manifest\n // FROM DISK: getManifest() is frozen and dev filenames are content-hashed.\n // opts.register false skips the dynamic registration: at boot the static\n // manifest already names the current files, and a copy injects twice.\n function reinjectContentScripts(onDone, opts) {\n var chrome = g.chrome;\n var register = !opts || opts.register !== false;\n try {\n if (typeof g.fetch !== \"function\" || !chrome.scripting) return false;\n g.fetch(chrome.runtime.getURL(\"manifest.json\"), {cache: \"no-store\"})\n .then(function (r) { return r.json(); })\n .then(function (manifest) {\n var entries = (manifest && manifest.content_scripts) || [];\n for (var i = 0; i < entries.length; i++) reinjectContentScriptEntry(entries[i], opts);\n if (register) reregisterForFutureNavigations(entries);\n if (onDone) { try { onDone(); } catch (e) {\n // Ignore\n } }\n })\n .catch(function () {\n // Ignore\n });\n return true;\n } catch (e) { return false; }\n }\n\n function mapRunAt(runAt) {\n if (runAt === \"document_start\") return \"document_start\";\n if (runAt === \"document_end\") return \"document_end\";\n return \"document_idle\";\n }\n\n // Re-register content scripts dynamically at the fresh files, so tabs\n // opened AFTER an edit also get the new build. Idempotent per entry id.\n function reregisterForFutureNavigations(entries) {\n var chrome = g.chrome;\n if (\n !chrome.scripting ||\n !chrome.scripting.registerContentScripts ||\n !chrome.scripting.getRegisteredContentScripts\n ) return;\n\n var scripts = [];\n for (var i = 0; i < entries.length; i++) {\n var e = entries[i] || {};\n if (!Array.isArray(e.matches) || !e.matches.length) continue;\n var js = (e.js || []).filter(function (f) { return typeof f === \"string\"; });\n var css = (e.css || []).filter(function (f) { return typeof f === \"string\"; });\n if (!js.length && !css.length) continue;\n var s = {\n id: \"extjs-dev-cs-\" + i,\n matches: e.matches,\n runAt: mapRunAt(e.run_at),\n allFrames: !!e.all_frames,\n world: e.world === \"MAIN\" ? \"MAIN\" : \"ISOLATED\",\n // A browser restart loads the static manifest fresh; a copy that\n // outlived the session would inject every page twice.\n persistAcrossSessions: false\n };\n // Dev registration must not be BROADER than the static one: dropping\n // exclude_matches injects into pages the browser itself would skip.\n if (Array.isArray(e.exclude_matches) && e.exclude_matches.length) {\n s.excludeMatches = e.exclude_matches;\n }\n if (js.length) s.js = js;\n if (css.length) s.css = css;\n scripts.push(s);\n }\n if (!scripts.length) return;\n\n try {\n chrome.scripting.getRegisteredContentScripts(function (existing) {\n try { void chrome.runtime.lastError; } catch (e) {\n // Ignore\n }\n var have = {};\n if (existing) for (var k = 0; k < existing.length; k++) have[existing[k].id] = true;\n var toRegister = [], toUpdate = [];\n for (var j = 0; j < scripts.length; j++) {\n (have[scripts[j].id] ? toUpdate : toRegister).push(scripts[j]);\n }\n if (toRegister.length) {\n try { chrome.scripting.registerContentScripts(toRegister, noopLastError); } catch (e) {\n // Ignore\n }\n }\n if (toUpdate.length) {\n try { chrome.scripting.updateContentScripts(toUpdate, noopLastError); } catch (e) {\n // Ignore\n }\n }\n });\n } catch (e) {\n // Ignore\n }\n }\n\n // The dev content-script runtime (plugin-reload) owns registration, the\n // boot heal and the tab reloads when its registry exists; absent, the\n // manifest-based paths below stay in charge.\n function devContentScripts() {\n try { return g.__extjsDevContentScripts || null; } catch (e) { return null; }\n }\n\n // Dev-loop reload without the CDP controller: content-scripts reload the\n // matching tabs at the new bundle, or re-inject in place where no registry\n // exists; service-worker/full/manifest restart the extension.\n function performDevReload(type, onDone, frame) {\n var chrome = g.chrome;\n if (!chrome) return;\n\n var fullReload = function () {\n // Flag a pending reinject for the NEXT producer generation: the\n // boot-time heal is what converges open tabs after a SW/full reload.\n try {\n if (chrome.storage && chrome.storage.local) {\n chrome.storage.local.set({__extjsDevPendingReinject: Date.now()}, noopLastError);\n }\n } catch (e) {\n // Ignore\n }\n // Deferred so in-flight frames and the tab console announcement flush\n // before the SW dies; the devtools companion confirms completion.\n try { setTimeout(function () { try { chrome.runtime.reload(); } catch (e) {\n // Ignore\n } }, 150); } catch (e) {}\n };\n\n if (type === \"content-scripts\" && chrome.scripting && chrome.tabs && chrome.tabs.query) {\n var dev = devContentScripts();\n if (dev && typeof dev.reload === \"function\") {\n try {\n dev.reload(frame && frame.changedContentScriptEntries, function (handled) {\n if (handled) { if (onDone) { try { onDone(); } catch (e) {\n // Ignore\n } } return; }\n if (reinjectContentScripts(onDone)) return;\n fullReload();\n });\n return;\n } catch (e) {\n // Ignore\n }\n }\n if (reinjectContentScripts(onDone)) return;\n }\n\n fullReload();\n }\n\n // One server-built label travels the ReloadFrame; the announcement\n // fires HERE, next to the actual reload action, so it stays true.\n\n // console.log the reload line into every open injectable tab; the patched\n // ISOLATED-world console also lands it in the dev-server log stream.\n function announceReloadInTabs(text) {\n var chrome = g.chrome;\n if (!chrome || !chrome.tabs || !chrome.tabs.query) return;\n try {\n chrome.tabs.query({}, function (tabs) {\n noopLastError();\n if (!tabs) return;\n for (var i = 0; i < tabs.length; i++) {\n (function (tab) {\n if (!tab || tab.id == null || !isInjectableUrl(tab.url)) return;\n if (chrome.scripting && chrome.scripting.executeScript) {\n try {\n chrome.scripting.executeScript(\n {target: {tabId: tab.id}, func: function (t) { console.log(t); }, args: [text]},\n noopLastError\n );\n return;\n } catch (e) {\n // Ignore\n }\n }\n // MV2 fallback (Firefox without the scripting API).\n if (chrome.tabs.executeScript) {\n try {\n chrome.tabs.executeScript(tab.id, {code: \"console.log(\" + JSON.stringify(text) + \");\"}, noopLastError);\n } catch (e) {\n // Ignore\n }\n }\n })(tabs[i]);\n }\n });\n } catch (e) {\n // Ignore\n }\n }\n\n // The scripts-replay shim (plugin-reload) keeps the SW's own\n // executeScript registry; the replay is fire-and-forget by design.\n function replayProgrammaticScripts(changedScriptFiles) {\n if (!Array.isArray(changedScriptFiles) || changedScriptFiles.length === 0) return;\n var replay = g.__extjsScriptsReplay;\n if (typeof replay !== \"function\") return;\n try {\n var outcome = replay(changedScriptFiles);\n if (outcome && typeof outcome.then === \"function\") {\n outcome.then(null, function () {});\n }\n } catch (e) {\n // Ignore\n }\n }\n\n function handleDevReloadFrame(frame) {\n var kind = frame.reloadType || \"full\";\n var label = typeof frame.label === \"string\" ? frame.label : \"\";\n // The server always builds the label; the kind-derived fallback only\n // covers a malformed frame so the announcement stays truthful.\n var fallback = kind === \"content-scripts\" ? \"content_script\"\n : kind === \"service-worker\" ? \"service_worker\"\n : \"extension\";\n var announced = \"[Extension.js] Reloading \" + (label || fallback) + \"\u2026\";\n\n // A scripts/ bundle edit: re-run the executeScript calls that named it\n // on their recorded tabs, before any reload decision below.\n replayProgrammaticScripts(frame.changedScriptFiles);\n\n if (kind === \"page\") {\n // Notify-only: rspack-dev-server's livereload refreshes the open\n // surface; reloading the extension here would race it.\n return;\n }\n\n // Delivery ack for the broker: proves this SW's message pump processed\n // the frame (a socket write proves nothing). Sent on receipt, by design.\n send({type: \"reload-ack\", reloadType: kind, label: label});\n\n announceReloadInTabs(announced);\n performDevReload(kind, function () {}, frame);\n }\n\n function sanitize(args) {\n var out = [];\n for (var i = 0; i < args.length; i++) {\n var p = args[i];\n try {\n if (typeof p === \"string\") out.push(p.length > 2048 ? p.slice(0, 2048) + \"...\" : p);\n else if (p instanceof Error) out.push(p.name + \": \" + p.message);\n else out.push(JSON.stringify(p));\n } catch (e) {\n out.push(String(p));\n }\n if (out.join(\" \").length > 8192) break;\n }\n return out;\n }\n\n function flush() {\n while (queue.length && open && socket) {\n var f = queue.shift();\n try { socket.send(JSON.stringify(f)); } catch (e) { break; }\n }\n }\n\n function send(frame) {\n if (open && socket) {\n try { socket.send(JSON.stringify(frame)); return; } catch (e) {\n // Ignore\n }\n }\n if (queue.length < MAX_QUEUE) queue.push(frame);\n parkPendingError(frame);\n }\n\n // The queue above lives in this context's memory only, so a background\n // that throws while it evaluates dies with the error still queued: the\n // socket has not finished its handshake yet. Park error frames in\n // extension storage too, where the NEXT context to connect can ship them.\n // Best effort by construction: the write is asynchronous, so a teardown\n // can cut it before the browser records anything.\n var parkedErrors = [];\n function storageLocalArea() {\n try {\n var ns = (g.chrome && g.chrome.storage) || (g.browser && g.browser.storage);\n var area = ns && ns.local;\n if (!area || typeof area.get !== \"function\" || typeof area.set !== \"function\") return null;\n return area;\n } catch (e) { return null; }\n }\n function parkPendingError(frame) {\n if (open) return;\n if (!frame || frame.type !== \"log\" || !frame.event || frame.event.level !== \"error\") return;\n var area = storageLocalArea();\n if (!area) return;\n // The whole list is rewritten on every park, so two errors in one turn\n // cannot lose each other to a read-modify-write, and the cap holds.\n parkedErrors.push(frame);\n while (parkedErrors.length > MAX_PENDING_ERRORS) parkedErrors.shift();\n var items = {};\n items[PENDING_ERRORS_KEY] = parkedErrors;\n try { area.set(items, noopLastError); } catch (e) {\n // Ignore\n }\n }\n function parkedHere(id) {\n for (var i = 0; i < parkedErrors.length; i++) {\n var ev = parkedErrors[i] && parkedErrors[i].event;\n if (ev && ev.id === id) return true;\n }\n return false;\n }\n // Ship what an earlier context parked and never got to send. Entries from\n // another build (a different instance id) or from an older run are dropped,\n // and the key is cleared on read so one failure is reported once.\n function flushPendingErrors() {\n var area = storageLocalArea();\n if (!area) return;\n var read = false;\n function onRead(res) {\n if (read) return;\n read = true;\n var list = res && res[PENDING_ERRORS_KEY];\n if (!list || !list.length) return;\n try {\n if (typeof area.remove === \"function\") area.remove(PENDING_ERRORS_KEY, noopLastError);\n } catch (e) {\n // Ignore\n }\n var now = Date.now();\n for (var i = 0; i < list.length; i++) {\n var frame = list[i];\n var ev = frame && frame.event;\n if (!ev || ev.runId !== INSTANCE_ID) continue;\n // This context parked it, so the queue flush above already has it.\n if (parkedHere(ev.id)) continue;\n if (typeof ev.timestamp !== \"number\" || now - ev.timestamp > PENDING_ERROR_TTL) continue;\n send(frame);\n }\n }\n try {\n var ret = area.get(PENDING_ERRORS_KEY, function (res) { noopLastError(); onRead(res); });\n if (ret && typeof ret.then === \"function\") ret.then(onRead, function () {});\n } catch (e) {\n // Ignore\n }\n }\n\n // The baked PORT is a snapshot of an ephemeral port. After a dev-server\n // restart, the port file shipped in the unpacked extension names the\n // live one, and unpacked resources are read from disk on every fetch.\n function refreshControlPort(done) {\n try {\n var runtime = (g.chrome && g.chrome.runtime) || (g.browser && g.browser.runtime);\n if (!runtime || typeof runtime.getURL !== \"function\" || typeof g.fetch !== \"function\") { done(); return; }\n g.fetch(runtime.getURL(\"extension-js-control.json\"), {cache: \"no-store\"})\n .then(function (res) { return res.json(); })\n .then(function (data) {\n var next = data && parseInt(data.port, 10);\n if (next && next > 0 && next < 65536 && next !== PORT) PORT = next;\n done();\n })\n .catch(function () { done(); });\n } catch (e) { done(); }\n }\n\n function schedule() {\n var delay = backoff;\n backoff = Math.min(backoff * 2, MAX_BACKOFF);\n connectFailures++;\n try {\n setTimeout(function () {\n // Two straight failures make the baked port suspect (a restarted\n // server dials out on a new port): re-resolve before re-dialing.\n if (connectFailures >= 2) refreshControlPort(connect);\n else connect();\n }, delay);\n } catch (e) {\n // Ignore\n }\n }\n\n function connect() {\n try {\n socket = new WS(\"ws://\" + HOST + \":\" + PORT + \"/extjs-control\");\n } catch (e) {\n schedule();\n return;\n }\n socket.onopen = function () {\n open = true;\n backoff = 250;\n connectFailures = 0;\n try {\n socket.send(JSON.stringify({type: \"hello\", v: 1, role: \"producer\", instanceId: INSTANCE_ID}));\n } catch (e) {\n // Ignore\n }\n flush();\n // Hello went first and this context's own queue is out; now replay any\n // error a previous context parked before it died.\n flushPendingErrors();\n };\n socket.onmessage = function (ev) {\n var frame;\n try { frame = JSON.parse(typeof ev.data === \"string\" ? ev.data : \"\"); } catch (e) { return; }\n if (frame && frame.type === \"command\") {\n try { executeCommand(frame); } catch (e) { replyErr(frame.cmdId, \"ExecutorError\", e); }\n } else if (frame && frame.type === \"reload\") {\n // Dev-loop reload broadcast (see broker.broadcastReload).\n // Fire-and-forget: no result frame is expected.\n try { handleDevReloadFrame(frame); } catch (e) {\n // Ignore\n }\n } else if (frame && frame.type === \"ping\") {\n // Server keepalive: merely RECEIVING this resets the MV3 SW's ~30s\n // idle timer (Chrome 116+), keeping it reachable. Nothing to do.\n }\n };\n socket.onclose = function () {\n open = false;\n socket = null;\n schedule();\n };\n socket.onerror = function () {\n try { socket && socket.close(); } catch (e) {\n // Ignore\n }\n };\n }\n\n // Uncaught errors don't route through the patched console; the handlers\n // below forward them, using these signatures to skip already-logged throws.\n var recentErrorSigs = [];\n function errorSig(message, stack) {\n return String(message == null ? \"\" : message) + \"::\" + String(stack == null ? \"\" : stack).slice(0, 200);\n }\n function noteErrorSig(sig) {\n recentErrorSigs.push({sig: sig, t: Date.now()});\n if (recentErrorSigs.length > 50) recentErrorSigs.shift();\n }\n function errorSigSeenRecently(sig) {\n var now = Date.now();\n for (var i = recentErrorSigs.length - 1; i >= 0; i--) {\n if (now - recentErrorSigs[i].t > 3000) { recentErrorSigs.splice(i, 1); continue; }\n if (recentErrorSigs[i].sig === sig) return true;\n }\n return false;\n }\n\n LEVELS.forEach(function (level) {\n var orig = typeof consoleRef[level] === \"function\"\n ? consoleRef[level].bind(consoleRef)\n : function () {};\n consoleRef[level] = function () {\n try {\n if (level === \"error\") {\n for (var ai = 0; ai < arguments.length; ai++) {\n if (arguments[ai] instanceof Error) noteErrorSig(errorSig(arguments[ai].message, arguments[ai].stack));\n }\n }\n send({\n type: \"log\",\n event: {\n v: 1,\n id: nowId(),\n timestamp: Date.now(),\n level: level,\n context: CONTEXT,\n messageParts: sanitize([].slice.call(arguments)),\n runId: INSTANCE_ID\n }\n });\n } catch (e) {\n // Ignore\n }\n return orig.apply(consoleRef, arguments);\n };\n });\n\n // Forward uncaught throws / unhandled rejections as level:\"error\", deduped\n // so a logged-then-rethrown Error emits once.\n function shipUncaughtError(message, stack, url) {\n try {\n var sig = errorSig(message, stack);\n if (errorSigSeenRecently(sig)) return;\n noteErrorSig(sig);\n send({\n type: \"log\",\n event: {\n v: 1,\n id: nowId(),\n timestamp: Date.now(),\n level: \"error\",\n context: CONTEXT,\n messageParts: sanitize([String(message) + (stack ? \"\\n\" + stack : \"\")]),\n url: url,\n runId: INSTANCE_ID\n }\n });\n } catch (e) {\n // Ignore\n }\n }\n try {\n if (typeof g.addEventListener === \"function\") {\n g.addEventListener(\"error\", function (ev) {\n var err = ev && ev.error;\n var message = (err && err.message) || (ev && ev.message) || \"Uncaught error\";\n shipUncaughtError(message, err && err.stack, ev && ev.filename);\n });\n g.addEventListener(\"unhandledrejection\", function (ev) {\n var reason = ev && ev.reason;\n var message = (reason && reason.message) || (reason != null ? String(reason) : \"Unhandled rejection\");\n shipUncaughtError(\"Unhandled promise rejection: \" + message, reason && reason.stack, undefined);\n });\n }\n } catch (e) {\n // Ignore\n }\n\n // Other contexts relay console logs to this SW over a NAMED runtime.Port:\n // they can't open the WS under page CSP, and sendMessage can echo-loop.\n function shipRelayedLog(ev, sender) {\n try {\n send({\n type: \"log\",\n event: {\n v: 1,\n id: nowId(),\n timestamp: Date.now(),\n level: ev.level || \"log\",\n context: ev.context || \"content\",\n messageParts: Array.isArray(ev.messageParts) ? ev.messageParts : [],\n url: ev.url || (sender ? sender.url : undefined),\n tabId: sender && sender.tab ? sender.tab.id : undefined,\n frameId: sender ? sender.frameId : undefined,\n runId: INSTANCE_ID\n }\n });\n } catch (e) {\n // Ignore\n }\n }\n try {\n var rtPort = g.chrome;\n if (rtPort && rtPort.runtime && rtPort.runtime.onConnect) {\n rtPort.runtime.onConnect.addListener(function (port) {\n if (!port || port.name !== \"__extjs-bridge-log__\") return;\n try {\n port.onMessage.addListener(function (msg) {\n if (!msg || !msg.__extjsBridgeLog) return;\n shipRelayedLog(msg.__extjsBridgeLog, port.sender);\n });\n } catch (e) {\n // Ignore\n }\n });\n }\n } catch (e) {\n // Ignore\n }\n // Legacy sendMessage path: kept for any surface still relaying the old way\n // (harmless one-predicate listener; new relays never use it).\n try {\n var rt = g.chrome;\n if (rt && rt.runtime && rt.runtime.onMessage) {\n rt.runtime.onMessage.addListener(function (msg, sender) {\n if (!msg || !msg.__extjsBridgeLog) return;\n shipRelayedLog(msg.__extjsBridgeLog, sender);\n });\n }\n } catch (e) {\n // Ignore\n }\n\n // Dev registrations outlive runtime.reload(), while the new generation's\n // static manifest already names the current files: drop them first, or\n // every navigation from here on runs the content script twice.\n function unregisterDevContentScripts(onDone) {\n var chrome = g.chrome;\n var done = function () { if (onDone) { try { onDone(); } catch (e) {\n // Ignore\n } } };\n try {\n if (\n !chrome.scripting ||\n !chrome.scripting.getRegisteredContentScripts ||\n !chrome.scripting.unregisterContentScripts\n ) return done();\n chrome.scripting.getRegisteredContentScripts(function (existing) {\n try { void chrome.runtime.lastError; } catch (e) {\n // Ignore\n }\n var ids = [];\n if (existing) {\n for (var i = 0; i < existing.length; i++) {\n var id = existing[i] && existing[i].id;\n if (typeof id === \"string\" && id.indexOf(\"extjs-dev-cs-\") === 0) ids.push(id);\n }\n }\n if (!ids.length) return done();\n try {\n chrome.scripting.unregisterContentScripts({ids: ids}, function () { noopLastError(); done(); });\n } catch (e) { done(); }\n });\n } catch (e) { done(); }\n }\n\n // One heal per producer generation: onInstalled and the pre-reload storage\n // flag can both fire on the same boot, and each would inject once more.\n var bootHealDone = false;\n function healOpenTabsAtBoot() {\n if (bootHealDone) return;\n bootHealDone = true;\n setTimeout(function () {\n var dev = devContentScripts();\n if (dev && typeof dev.heal === \"function\") {\n try { dev.heal(); return; } catch (e) {\n // Ignore\n }\n }\n unregisterDevContentScripts(function () {\n try { reinjectContentScripts(undefined, {register: false, completeOnly: true}); } catch (e) {\n // Ignore\n }\n });\n }, 250);\n }\n\n // Chrome never injects manifest content scripts into already-open tabs, so\n // fire one reinject on onInstalled (which skips idle-stop SW wakes). Not\n // on the first install: the tabs the browser launched with load beside\n // the extension and Chrome injects them itself.\n try {\n var rtBoot = g.chrome;\n if (rtBoot && rtBoot.runtime && rtBoot.runtime.onInstalled) {\n rtBoot.runtime.onInstalled.addListener(function (details) {\n if (details && details.reason === \"install\") return;\n try { healOpenTabsAtBoot(); } catch (e) {\n // Ignore\n }\n });\n }\n } catch (e) {\n // Ignore\n }\n\n // runtime.reload() does NOT fire onInstalled (verified Chromium 146), so\n // consume the pre-reload storage flag here (fresh only) to heal open tabs.\n try {\n var stBoot = g.chrome;\n if (stBoot && stBoot.storage && stBoot.storage.local) {\n stBoot.storage.local.get(\"__extjsDevPendingReinject\", function (res) {\n noopLastError();\n var ts = res && res.__extjsDevPendingReinject;\n if (ts == null) return;\n try { stBoot.storage.local.remove(\"__extjsDevPendingReinject\", noopLastError); } catch (e) {\n // Ignore\n }\n if (typeof ts !== \"number\" || Date.now() - ts > 30000) return;\n healOpenTabsAtBoot();\n });\n }\n } catch (e) {\n // Ignore\n }\n\n connect();\n } catch (e) {\n // Ignore\n }\n})();\n";
|
|
3
|
-
export declare const BRIDGE_RELAY_SOURCE = ";(function () {\n try {\n var g = (typeof globalThis === \"object\" && globalThis) ? globalThis : this;\n if (!g || g.__extjsBridgeRelayInstalled) return;\n\n var CONTEXT = \"__EXTJS_CONTEXT__\";\n var chrome = g.chrome;\n if (!chrome || !chrome.runtime) return;\n var canRelay = typeof chrome.runtime.connect === \"function\";\n\n g.__extjsBridgeRelayInstalled = true;\n var consoleRef = g.console || {};\n var LEVELS = [\"log\", \"info\", \"warn\", \"error\", \"debug\", \"trace\"];\n\n function sanitize(args) {\n var out = [];\n for (var i = 0; i < args.length; i++) {\n var p = args[i];\n try {\n if (typeof p === \"string\") out.push(p.length > 2048 ? p.slice(0, 2048) + \"...\" : p);\n else if (p instanceof Error) out.push(p.name + \": \" + p.message);\n else out.push(JSON.stringify(p));\n } catch (e) { out.push(String(p)); }\n if (out.join(\" \").length > 8192) break;\n }\n return out;\n }\n\n function here() { try { return g.location ? g.location.href : undefined; } catch (e) { return undefined; } }\n\n // Lazy named port to the SW producer: connecting wakes an idle SW, but\n // port frames never reach the extension's own onMessage listeners.\n var logPort = null;\n function getLogPort() {\n if (!canRelay) return null;\n if (logPort) return logPort;\n try {\n logPort = chrome.runtime.connect({name: \"__extjs-bridge-log__\"});\n logPort.onDisconnect.addListener(function () {\n try { void chrome.runtime.lastError; } catch (e) {\n // Ignore\n }\n logPort = null;\n });\n } catch (e) { logPort = null; }\n return logPort;\n }\n\n // Relay one log payload to the SW producer over the named port, redialing\n // once if the port went stale (SW restarted).\n function postLog(payload) {\n var p = getLogPort();\n if (!p) return;\n try {\n p.postMessage({__extjsBridgeLog: payload});\n } catch (e) {\n logPort = null;\n var p2 = getLogPort();\n if (p2) { try { p2.postMessage({__extjsBridgeLog: payload}); } catch (e2) {\n // Ignore\n } }\n }\n }\n\n // Uncaught throws / unhandled rejections never route through the patched\n // console; forward them, deduped against console.error Error signatures.\n var recentErrorSigs = [];\n function errorSig(message, stack) {\n return String(message == null ? \"\" : message) + \"::\" + String(stack == null ? \"\" : stack).slice(0, 200);\n }\n function noteErrorSig(sig) {\n recentErrorSigs.push({sig: sig, t: Date.now()});\n if (recentErrorSigs.length > 50) recentErrorSigs.shift();\n }\n function errorSigSeenRecently(sig) {\n var now = Date.now();\n for (var i = recentErrorSigs.length - 1; i >= 0; i--) {\n if (now - recentErrorSigs[i].t > 3000) { recentErrorSigs.splice(i, 1); continue; }\n if (recentErrorSigs[i].sig === sig) return true;\n }\n return false;\n }\n\n LEVELS.forEach(function (level) {\n var orig = typeof consoleRef[level] === \"function\" ? consoleRef[level].bind(consoleRef) : function () {};\n consoleRef[level] = function () {\n try {\n if (level === \"error\") {\n for (var ai = 0; ai < arguments.length; ai++) {\n if (arguments[ai] instanceof Error) noteErrorSig(errorSig(arguments[ai].message, arguments[ai].stack));\n }\n }\n postLog({level: level, context: CONTEXT, messageParts: sanitize([].slice.call(arguments)), url: here()});\n } catch (e) {\n // Ignore\n }\n return orig.apply(consoleRef, arguments);\n };\n });\n\n function shipUncaughtError(message, stack, url) {\n try {\n var sig = errorSig(message, stack);\n if (errorSigSeenRecently(sig)) return;\n noteErrorSig(sig);\n postLog({level: \"error\", context: CONTEXT, messageParts: sanitize([String(message) + (stack ? \"\\n\" + stack : \"\")]), url: url || here()});\n } catch (e) {\n // Ignore\n }\n }\n try {\n if (typeof g.addEventListener === \"function\") {\n g.addEventListener(\"error\", function (ev) {\n var err = ev && ev.error;\n var message = (err && err.message) || (ev && ev.message) || \"Uncaught error\";\n shipUncaughtError(message, err && err.stack, (ev && ev.filename) || undefined);\n });\n g.addEventListener(\"unhandledrejection\", function (ev) {\n var reason = ev && ev.reason;\n var message = (reason && reason.message) || (reason != null ? String(reason) : \"Unhandled rejection\");\n shipUncaughtError(\"Unhandled promise rejection: \" + message, reason && reason.stack, undefined);\n });\n }\n } catch (e) {\n // Ignore\n }\n\n // The SW executor can't reach another extension page's DOM or eval there,\n // so THIS surface answers inspect/eval requests for its own context.\n try {\n if (chrome.runtime && chrome.runtime.onMessage) {\n chrome.runtime.onMessage.addListener(function (msg, _sender, sendResponse) {\n if (!msg || !msg.__extjsEvalRequest) return;\n if (!msg.target || msg.target.context !== CONTEXT) return; // not for me\n
|
|
2
|
+
export declare const BRIDGE_PRODUCER_SOURCE = ";(function () {\n try {\n var g = (typeof globalThis === \"object\" && globalThis) ? globalThis : this;\n if (!g || g.__extjsBridgeProducerInstalled) return;\n\n var PORT = parseInt(\"__EXTJS_CONTROL_PORT__\", 10);\n var INSTANCE_ID = \"__EXTJS_INSTANCE_ID__\";\n var CONTEXT = \"__EXTJS_CONTEXT__\";\n // Connectable host of the dev-server control WS. Baked from the resolved\n // connectable host (loopback locally; the public host for remote/devcontainer).\n var HOST = \"__EXTJS_CONTROL_HOST__\";\n if (!HOST || HOST.indexOf(\"__EXTJS\") === 0) HOST = \"127.0.0.1\";\n if (!PORT || PORT < 1) return;\n\n var WS = g.WebSocket;\n if (typeof WS !== \"function\") return;\n\n var consoleRef = g.console || {};\n g.__extjsBridgeProducerInstalled = true;\n\n // Capture extension event listeners at install time: these wraps run\n // BEFORE user code, and no platform API exists to dispatch these events.\n function captureEvent(event, sink) {\n try {\n if (!event || typeof event.addListener !== \"function\") return;\n var origAdd = event.addListener.bind(event);\n event.addListener = function (cb) {\n if (typeof cb === \"function\" && sink.indexOf(cb) === -1) sink.push(cb);\n return origAdd(cb);\n };\n if (typeof event.removeListener === \"function\") {\n var origRemove = event.removeListener.bind(event);\n event.removeListener = function (cb) {\n var i = sink.indexOf(cb);\n if (i !== -1) sink.splice(i, 1);\n return origRemove(cb);\n };\n }\n } catch (e) { /* non-fatal: trigger falls back to its no-listener reply */ }\n }\n\n // Use the raw globals (not the hoisted chrome var below). This runs first.\n // On Gecko browser.* is a DISTINCT wrapper object: hook BOTH namespaces.\n var actionClickedListeners = [];\n var commandListeners = [];\n var capturedEvents = [];\n function captureEventOnce(event, sink) {\n // Polyfilled extensions expose ONE event object on both namespaces:\n // wrap it once, and the sink dedupes callbacks shared across wrappers.\n if (!event || capturedEvents.indexOf(event) !== -1) return;\n capturedEvents.push(event);\n captureEvent(event, sink);\n }\n var captureNamespaces = [g.chrome, g.browser];\n for (var cn = 0; cn < captureNamespaces.length; cn++) {\n var capNS = captureNamespaces[cn];\n if (!capNS) continue;\n captureEventOnce(capNS.action && capNS.action.onClicked, actionClickedListeners);\n captureEventOnce(capNS.commands && capNS.commands.onCommand, commandListeners);\n }\n\n var LEVELS = [\"log\", \"info\", \"warn\", \"error\", \"debug\", \"trace\"];\n var socket = null;\n var open = false;\n var queue = [];\n var backoff = 250;\n var MAX_BACKOFF = 5000;\n var connectFailures = 0;\n var MAX_QUEUE = 1000;\n var MAX_RESULT_BYTES = 256 * 1024;\n // Where error frames are parked so a context that dies before its socket\n // opens does not take them along. Capped, and old entries are dropped.\n var PENDING_ERRORS_KEY = \"__extjsPendingErrors\";\n var MAX_PENDING_ERRORS = 20;\n var PENDING_ERROR_TTL = 300000;\n\n function nowId() {\n return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);\n }\n\n // The extension's own URL scheme names the engine outright, which beats a\n // user agent where Chrome and Safari both say \"Safari\".\n function engineName() {\n try {\n var scheme = (typeof location !== \"undefined\" && location.protocol) || \"\";\n if (scheme.indexOf(\"safari-web-extension\") === 0) return \"webkit\";\n if (scheme.indexOf(\"moz-extension\") === 0) return \"firefox\";\n if (scheme.indexOf(\"chrome-extension\") === 0) return \"chromium\";\n var ua = (typeof navigator !== \"undefined\" && navigator.userAgent) || \"\";\n if (/firefox/i.test(ua)) return \"firefox\";\n if (/safari/i.test(ua) && !/chrome|chromium|edg/i.test(ua)) return \"webkit\";\n return \"chromium\";\n } catch (e) { return \"chromium\"; }\n }\n\n function safeValue(v) {\n try {\n var s = JSON.stringify(v);\n if (s === undefined) return {value: undefined, truncated: false};\n if (s.length > MAX_RESULT_BYTES) {\n return {value: {__type: \"truncated\", preview: s.slice(0, 1024)}, truncated: true};\n }\n return {value: JSON.parse(s), truncated: false};\n } catch (e) {\n return {value: {__type: \"unserializable\", preview: String(v).slice(0, 512)}, truncated: false};\n }\n }\n\n function replyOk(cmdId, value) {\n var s = safeValue(value);\n var frame = {type: \"result\", cmdId: cmdId, ok: true, value: s.value};\n if (s.truncated) frame.truncated = true;\n try { socket && socket.send(JSON.stringify(frame)); } catch (e) {\n // Ignore\n }\n }\n\n function replyErr(cmdId, name, message, code) {\n var frame = {type: \"result\", cmdId: cmdId, ok: false, error: {name: name, message: String(message), engine: engineName()}};\n if (code) frame.error.code = code;\n try { socket && socket.send(JSON.stringify(frame)); } catch (e) {\n // Ignore\n }\n }\n\n // A tabs.* failure the caller can act on gets a name and a refusal code:\n // a tab id nobody has is TargetNotFound, and a url the engine refuses by\n // rule (Gecko's \"Illegal URL\", Chromium's javascript: ban) is BadRequest.\n // Anything else keeps the op's own error name.\n function replyTabsFailure(cmdId, err, fallbackName, url) {\n var msg = String((err && err.message) || err);\n if (/no tab with id|invalid tab id/i.test(msg)) {\n replyErr(cmdId, \"TargetNotFound\", msg, \"tab_not_found\");\n return;\n }\n if (url && /illegal url|javascript urls are not allowed|invalid url/i.test(msg)) {\n replyErr(cmdId, \"BadRequest\", engineName() + \" refuses to open \" + url + \" from the extension's tabs API (\" + msg + \")\", \"url_refused\");\n return;\n }\n replyErr(cmdId, fallbackName, msg);\n }\n\n // A surface relay that answered nothing is closed only when the engine\n // says no receiver exists. Any other lastError (a reply the engine could\n // not clone, a channel that closed) means the surface WAS open and ran the\n // expression, so the message quotes the engine instead of \"not open\".\n function surfaceReplyFailure(ctx, resp) {\n var le = null;\n try { le = g.chrome && g.chrome.runtime && g.chrome.runtime.lastError; } catch (e) {\n // Ignore\n }\n if (!le && resp) return null;\n var lem = le ? String((le && le.message) || le) : \"\";\n if (!lem || /receiving end does not exist|could not establish connection/i.test(lem)) {\n return {code: \"surface_not_open\", message: \"surface '\" + ctx + \"' is not open (open it first: extension open \" + ctx + \")\"};\n }\n return {code: \"surface_reply_failed\", message: \"surface '\" + ctx + \"' answered but the reply did not arrive: \" + lem};\n }\n\n // The page a surface lives in, as the manifest names it, so the background\n // can ask the engine whether that page is open at all.\n function surfacePagePath(ctx) {\n var m = null;\n try { m = g.chrome.runtime.getManifest(); } catch (e) { return null; }\n if (!m) return null;\n var p = null;\n if (ctx === \"popup\") p = (m.action && m.action.default_popup) || (m.browser_action && m.browser_action.default_popup);\n else if (ctx === \"options\") p = (m.options_ui && m.options_ui.page) || m.options_page;\n else if (ctx === \"sidebar\") p = (m.side_panel && m.side_panel.default_path) || (m.sidebar_action && m.sidebar_action.default_panel);\n else if (ctx === \"newtab\" || ctx === \"history\" || ctx === \"bookmarks\") p = m.chrome_url_overrides && m.chrome_url_overrides[ctx];\n // Character classes on purpose: this source is a template literal, and\n // a backslash escape in a regex would be eaten before it ever ran.\n return typeof p === \"string\" && p ? p.replace(/^[.]?[/]/, \"\") : null;\n }\n\n // Chrome says \"message port closed\" both when the surface died mid-op and\n // when a bystander extension page (the options page, a new tab override)\n // received the message and passed, since a relay returns nothing for a\n // context that is not its own. Only the engine's open-context list tells\n // the two apart, so it is asked before a reply gets the blame. Engines\n // without getContexts keep the plain classification.\n function classifySurfaceFailure(ctx, resp, cb) {\n var relayErr = surfaceReplyFailure(ctx, resp);\n if (!relayErr || relayErr.code !== \"surface_reply_failed\" || !/message port closed/i.test(relayErr.message)) { cb(relayErr); return; }\n var rt = g.chrome && g.chrome.runtime;\n var page = surfacePagePath(ctx);\n if (!page || !rt || typeof rt.getContexts !== \"function\") { cb(relayErr); return; }\n var wanted = \"\";\n try { wanted = String(rt.getURL(page)).split(/[?#]/)[0]; } catch (e) { cb(relayErr); return; }\n var notOpen = {code: \"surface_not_open\", message: \"surface '\" + ctx + \"' is not open (open it first: extension open \" + ctx + \")\"};\n try {\n Promise.resolve(rt.getContexts({})).then(function (list) {\n var open = false;\n for (var i = 0; i < (list || []).length; i++) {\n var u = list[i] && list[i].documentUrl;\n if (u && String(u).split(/[?#]/)[0] === wanted) { open = true; break; }\n }\n cb(open ? relayErr : notOpen);\n }, function () { cb(relayErr); });\n } catch (e) { cb(relayErr); }\n }\n\n // Name the refusal the browser wrote as prose, so a consumer branches on\n // error.code instead of matching the engine's sentence.\n function openRefusalCode(message) {\n var m = String(message == null ? \"\" : message);\n if (/user gesture|user input|user interaction|user action/i.test(m)) return \"needs_user_gesture\";\n if (/browser window|active window/i.test(m)) return \"needs_headed_window\";\n return undefined;\n }\n\n // Resolve a numeric tab id when the caller did not pass one: match\n // target.url against open tabs, else default to the active tab. (#51)\n function resolveTargetTab(target, cb) {\n var chrome = g.chrome;\n if (!chrome || !chrome.tabs) { cb(null, \"chrome.tabs is not available\"); return; }\n var url = target && target.url;\n if (url) {\n var substringMatch = function () {\n try {\n chrome.tabs.query({}, function (all) {\n var hit = (all || []).filter(function (t) { return t.url && String(t.url).indexOf(url) !== -1; })[0];\n if (hit && typeof hit.id === \"number\") { cb(hit.id, null); return; }\n cb(null, \"no open tab matches url: \" + url);\n });\n } catch (e2) { cb(null, \"tab query failed\"); }\n };\n try {\n // chrome.tabs.query accepts URL match patterns; a bare url/glob may\n // not be a valid pattern, so fall back to a substring scan.\n chrome.tabs.query({url: url}, function (tabs) {\n if (chrome.runtime && chrome.runtime.lastError) { substringMatch(); return; }\n if (tabs && tabs.length && typeof tabs[0].id === \"number\") { cb(tabs[0].id, null); return; }\n substringMatch();\n });\n } catch (e) { substringMatch(); }\n return;\n }\n try {\n chrome.tabs.query({active: true, lastFocusedWindow: true}, function (tabs) {\n var t = tabs && tabs[0];\n if (t && typeof t.id === \"number\") { cb(t.id, null); return; }\n cb(null, \"no active tab to target\");\n });\n } catch (e3) { cb(null, \"tab query failed\"); }\n }\n\n // chrome.* async APIs are callback-only on Gecko. Prefer promisified\n // browser.* when present, else the chrome.* callback form. cb(err, result).\n function nsCall(nsName, method, callArgs, cb) {\n var chromeG = g.chrome;\n var done = false;\n var once = function (err, r) { if (!done) { done = true; cb(err, r); } };\n var viaPromise = function (ns) {\n try {\n var ret = ns[method].apply(ns, callArgs);\n if (ret && typeof ret.then === \"function\") {\n ret.then(function (r) { once(null, r); }, function (e) { once(e); });\n return true;\n }\n } catch (e) { once(e); return true; }\n return false;\n };\n var bNS = g.browser && g.browser[nsName];\n if (bNS && typeof bNS[method] === \"function\" && viaPromise(bNS)) return;\n var cNS = chromeG && chromeG[nsName];\n if (!cNS || typeof cNS[method] !== \"function\") {\n once(new Error(\"chrome.\" + nsName + \".\" + method + \" is not available (engine: \" + engineName() + \")\"));\n return;\n }\n try {\n var ret2 = cNS[method].apply(cNS, callArgs.concat([function (r) {\n var le = chromeG.runtime && chromeG.runtime.lastError;\n if (le) once(le); else once(null, r);\n }]));\n // A promise-only implementation ignores the trailing callback and\n // returns a thenable instead; resolve through it (once() dedupes).\n if (ret2 && typeof ret2.then === \"function\") {\n ret2.then(function (r) { once(null, r); }, function (e) { once(e); });\n }\n } catch (e2) {\n if (!viaPromise(cNS)) once(e2);\n }\n }\n\n function executeCommand(cmd) {\n var op = cmd.op, target = cmd.target || {}, args = cmd.args || {};\n var ctx = target.context || \"background\";\n var chrome = g.chrome;\n var cmdId = cmd.cmdId;\n // content/page eval & inspect need a numeric tab id: resolve --url (or\n // nothing) to a tab and re-dispatch with target.tabId filled in. (#51)\n if ((op === \"eval\" || op === \"inspect\" || op === \"reload\") && (ctx === \"content\" || ctx === \"page\") && target.tabId == null) {\n resolveTargetTab(target, function (tabId, err) {\n if (tabId == null) {\n // A filter that matched nothing, or no active tab, is a missing\n // target the caller can fix, not a missing feature.\n if (err && /^no open tab matches|^no active tab/i.test(String(err))) {\n replyErr(cmdId, \"TargetNotFound\", err, \"tab_not_found\");\n return;\n }\n replyErr(cmdId, \"Unsupported\", err || (\"eval/inspect in context \" + ctx + \" needs a --tab id, a --url to match, or an active tab\"));\n return;\n }\n var next = {};\n for (var k in cmd) { if (Object.prototype.hasOwnProperty.call(cmd, k)) next[k] = cmd[k]; }\n next.target = {};\n for (var tk in target) { if (Object.prototype.hasOwnProperty.call(target, tk)) next.target[tk] = target[tk]; }\n next.target.tabId = tabId;\n executeCommand(next);\n });\n return;\n }\n try {\n if (!chrome) { replyErr(cmdId, \"Unsupported\", \"chrome.* not available in this context\"); return; }\n if (op === \"eval\") {\n if (ctx === \"background\") {\n Promise.resolve().then(function () { return (0, eval)(args.expression); })\n .then(function (r) { replyOk(cmdId, r); }, function (e) {\n var msg = (e && e.message) || String(e);\n // MV3 forbids eval of strings in the SW/extension pages and\n // rejects 'unsafe-eval'. Surface it honestly with an alternative.\n if (/Content Security Policy|unsafe-eval/i.test(msg)) {\n replyErr(cmdId, \"Unsupported\", \"eval is blocked in the extension background by CSP, which allows no unsafe-eval. Use --context page --tab <id> (eval runs in the page's MAIN world), or run on a build whose CSP permits it. Engine: \" + engineName());\n } else {\n replyErr(cmdId, (e && e.name) || \"EvalError\", msg);\n }\n });\n } else if ((ctx === \"content\" || ctx === \"page\") && target.tabId && chrome.scripting) {\n // Wrap the eval so a throw INSIDE the tab comes back as data:\n // Chrome swallows an injected function's exception (null result).\n nsCall(\"scripting\", \"executeScript\", [{\n target: {tabId: target.tabId},\n world: ctx === \"page\" ? \"MAIN\" : \"ISOLATED\",\n func: function (src) {\n try { return {__extjsEval: 1, ok: true, value: eval(src)}; }\n catch (e) { return {__extjsEval: 1, ok: false, name: (e && e.name) || \"EvalError\", message: (e && e.message) || String(e)}; }\n },\n args: [String(args.expression)]\n }], function (err, res) {\n if (err) { replyErr(cmdId, \"TargetNotFound\", (err && err.message) || err); return; }\n var frame = res && res[0] ? res[0].result : undefined;\n if (!frame || frame.__extjsEval !== 1) {\n replyErr(cmdId, \"TargetNotFound\", \"the expression never executed in tab \" + target.tabId + \": no injectable frame returned a result (restricted page, or outside host_permissions)\");\n return;\n }\n if (frame.ok) { replyOk(cmdId, frame.value); return; }\n if (ctx === \"content\" && /Content Security Policy|unsafe-eval|EvalError/i.test(String(frame.name) + \" \" + String(frame.message))) {\n replyErr(cmdId, \"Unsupported\", \"eval of a string is blocked in the ISOLATED (content) world by the extension CSP. Use --context page (runs in the page's MAIN world). Original: \" + frame.message);\n return;\n }\n replyErr(cmdId, frame.name || \"EvalError\", frame.message);\n });\n } else if (ctx === \"popup\" || ctx === \"options\" || ctx === \"sidebar\" || ctx === \"devtools\" || ctx === \"newtab\" || ctx === \"history\" || ctx === \"bookmarks\") {\n // The SW can't eval in another extension page; ask the surface's\n // own in-bundle relay (only the open, matching-context page responds).\n chrome.runtime.sendMessage(\n {__extjsEvalRequest: true, target: target, args: {expression: String(args.expression)}},\n function (resp) {\n classifySurfaceFailure(ctx, resp, function (relayErr) {\n if (relayErr) {\n replyErr(cmdId, \"Unsupported\", relayErr.message, relayErr.code);\n } else if (resp.ok) {\n replyOk(cmdId, resp.value);\n } else {\n replyErr(cmdId, (resp.error && resp.error.name) || \"EvalError\", (resp.error && resp.error.message) || \"eval failed\");\n }\n });\n }\n );\n } else if (ctx === \"content\" || ctx === \"page\") {\n replyErr(cmdId, \"Unsupported\", target.tabId\n ? \"chrome.scripting is not available on this engine (MV2 has no scripting API); use --context background\"\n : \"eval in context \" + ctx + \" requires a tabId\");\n } else {\n replyErr(cmdId, \"BadRequest\", \"unknown eval context: \" + ctx);\n }\n return;\n }\n if (op === \"storage.get\" || op === \"storage.set\") {\n // chrome.* storage is callback-only on Gecko and older Chromium.\n // Prefer promisified browser.*, then fall back to the callback shape.\n var storageNS = (g.browser && g.browser.storage) ? g.browser.storage : chrome.storage;\n var storageArea = storageNS && storageNS[args.area || \"local\"];\n if (!storageArea) { replyErr(cmdId, \"StorageError\", \"storage.\" + (args.area || \"local\") + \" unavailable\"); return; }\n var isSet = op === \"storage.set\";\n var callArgs = isSet ? [args.items || {}] : [args.key != null ? args.key : null];\n var onStorageOk = function (r) { replyOk(cmdId, isSet ? {set: Object.keys(args.items || {})} : r); };\n var onStorageErr = function (e) { replyErr(cmdId, \"StorageError\", (e && e.message) || String(e)); };\n var storageFn = isSet ? storageArea.set : storageArea.get;\n try {\n var storageRet = storageFn.apply(storageArea, callArgs);\n if (storageRet && typeof storageRet.then === \"function\") {\n storageRet.then(onStorageOk, onStorageErr);\n return;\n }\n } catch (e) { /* not thenable / threw synchronously: fall back to callback */ }\n try {\n storageFn.apply(storageArea, callArgs.concat([function (r) {\n var le = chrome.runtime && chrome.runtime.lastError;\n if (le) onStorageErr(le); else onStorageOk(r);\n }]));\n } catch (e) { onStorageErr(e); }\n return;\n }\n if (op === \"reload\") {\n if (ctx === \"background\") {\n replyOk(cmdId, {reloading: true});\n setTimeout(function () { try { chrome.runtime.reload(); } catch (e) {\n // Ignore\n } }, 50);\n } else if (target.tabId) {\n nsCall(\"tabs\", \"reload\", [target.tabId], function (err) {\n if (err) replyTabsFailure(cmdId, err, \"ReloadError\");\n else replyOk(cmdId, {reloaded: target.tabId});\n });\n } else {\n replyErr(cmdId, \"Unsupported\", \"reload needs background or a tabId\");\n }\n return;\n }\n if (op === \"open\") {\n var surface = args.surface || ctx;\n // getPopup is promise-style on Gecko and (MV3) Chromium; fall back to\n // callback-style for older Chromium. Always resolves to a string.\n var getActionPopup = function (cb) {\n try {\n var r = chrome.action && chrome.action.getPopup && chrome.action.getPopup({});\n if (r && typeof r.then === \"function\") {\n r.then(function (p) { cb(p || \"\"); }, function () { cb(\"\"); });\n return;\n }\n } catch (e) {\n // Ignore\n }\n try { chrome.action.getPopup({}, function (p) { cb(p || \"\"); }); }\n catch (e) { cb(\"\"); }\n };\n var resolveActiveTab = function (a, cb) {\n if (a && typeof a.tabId === \"number\") {\n try { chrome.tabs.get(a.tabId, function (t) { cb(t || {id: a.tabId}); }); return; }\n catch (e) {\n // Ignore\n }\n }\n try { chrome.tabs.query({active: true, lastFocusedWindow: true}, function (tabs) { cb((tabs && tabs[0]) || undefined); }); }\n catch (e) { cb(undefined); }\n };\n // Replaying a listener carries no user gesture, so activeTab is never\n // granted. Warn when the manifest declares it.\n var activeTabWarning = function () {\n try {\n var m = chrome.runtime.getManifest();\n var perms = (m && m.permissions) || [];\n if (perms.indexOf(\"activeTab\") !== -1) {\n return \"replayed without a user gesture: activeTab is NOT granted, so APIs that depend on it (scripting on the active tab, captureVisibleTab) behave differently than a real click\";\n }\n } catch (e) {\n // Ignore\n }\n return null;\n };\n if (surface === \"popup\") {\n if (chrome.action && chrome.action.openPopup) {\n nsCall(\"action\", \"openPopup\", [], function (err) {\n if (err) {\n var openMsg = (err && err.message) || err;\n replyErr(cmdId, \"Unsupported\", \"openPopup: \" + openMsg, openRefusalCode(openMsg));\n } else replyOk(cmdId, {opened: \"popup\"});\n });\n } else { replyErr(cmdId, \"Unsupported\", \"action.openPopup not available\", \"api_unavailable\"); }\n } else if (surface === \"options\") {\n try { chrome.runtime.openOptionsPage(function () { replyOk(cmdId, {opened: \"options\"}); }); }\n catch (e) { replyErr(cmdId, \"Unsupported\", \"openOptionsPage: \" + e); }\n } else if (surface === \"sidebar\") {\n // The two member reads below are exactly what the Safari\n // unsupported-API scan looks for, and this runtime is prepended to\n // the background asset at PROCESS_ASSETS_STAGE_REPORT + 101, after\n // that scan runs at SUMMARIZE + 1. Move the injection earlier and\n // every Safari dev session warns about this line.\n if (chrome.sidePanel && chrome.sidePanel.open && chrome.windows) {\n chrome.windows.getCurrent(function (w) {\n nsCall(\"sidePanel\", \"open\", [{windowId: w.id}], function (err) {\n if (err) {\n var sideMsg = (err && err.message) || err;\n replyErr(cmdId, \"Unsupported\", \"sidePanel.open: \" + sideMsg, openRefusalCode(sideMsg));\n } else replyOk(cmdId, {opened: \"sidebar\"});\n });\n });\n } else { replyErr(cmdId, \"Unsupported\", \"sidePanel not available (engine: \" + engineName() + \")\", \"api_unavailable\"); }\n } else if (surface === \"action\") {\n // With a default_popup the click opens it; without one, replay the\n // onClicked listeners captured at install time.\n if (chrome.action) {\n getActionPopup(function (popup) {\n if (popup) {\n nsCall(\"action\", \"openPopup\", [], function (err) {\n if (err) {\n var actMsg = (err && err.message) || err;\n replyErr(cmdId, \"Unsupported\", \"openPopup: \" + actMsg, openRefusalCode(actMsg));\n } else replyOk(cmdId, {triggered: \"popup\"});\n });\n } else if (actionClickedListeners.length) {\n resolveActiveTab(args, function (tab) {\n var fired = 0;\n for (var i = 0; i < actionClickedListeners.length; i++) {\n try { actionClickedListeners[i](tab); fired++; } catch (e) {\n // Ignore\n }\n }\n var reply = {triggered: \"onClicked\", listeners: fired, gesture: false};\n var warning = activeTabWarning();\n if (warning) reply.warning = warning;\n replyOk(cmdId, reply);\n });\n } else {\n replyErr(cmdId, \"Unsupported\", \"action has no popup and no onClicked listener registered\");\n }\n });\n } else { replyErr(cmdId, \"Unsupported\", \"chrome.action not available (engine: \" + engineName() + \")\", \"api_unavailable\"); }\n } else if (surface === \"command\") {\n var commandName = (args && args.name) || undefined;\n if (!commandListeners.length) {\n replyErr(cmdId, \"Unsupported\", \"no chrome.commands.onCommand listener registered\");\n } else {\n resolveActiveTab(args, function (tab) {\n var fired = 0;\n for (var i = 0; i < commandListeners.length; i++) {\n try { commandListeners[i](commandName, tab); fired++; } catch (e) {\n // Ignore\n }\n }\n replyOk(cmdId, {triggered: \"command\", command: commandName || null, listeners: fired, gesture: false});\n });\n }\n } else { replyErr(cmdId, \"BadRequest\", \"unknown surface: \" + surface); }\n return;\n }\n if (op === \"tabs.query\") {\n nsCall(\"tabs\", \"query\", [args || {}], function (err, tabs) {\n if (err) { replyErr(cmdId, \"TabsError\", (err && err.message) || err); return; }\n replyOk(cmdId, (tabs || []).map(function (t) { return {id: t.id, url: t.url, title: t.title, active: t.active, windowId: t.windowId}; }));\n });\n return;\n }\n // Navigation is a static tabs.* call, never an eval: an MV3 background\n // (Safari's included) refuses eval by CSP, so a url must reach a tab\n // through code that shipped with the bridge. A tabId targets that\n // tab, newTab opens one, and otherwise the active tab is reused.\n if (op === \"tabs.navigate\") {\n var navUrl = typeof args.url === \"string\" ? args.url : \"\";\n if (!navUrl) { replyErr(cmdId, \"BadRequest\", \"tabs.navigate needs a url\"); return; }\n var navDone = function (err, tab, created) {\n if (err) { replyTabsFailure(cmdId, err, \"TabsError\", navUrl); return; }\n replyOk(cmdId, {tabId: tab && tab.id != null ? tab.id : null, url: navUrl, created: !!created});\n };\n if (target.tabId != null) {\n nsCall(\"tabs\", \"update\", [target.tabId, {url: navUrl}], function (err, tab) { navDone(err, tab, false); });\n return;\n }\n if (args.newTab) {\n nsCall(\"tabs\", \"create\", [{url: navUrl, active: args.active !== false}], function (err, tab) { navDone(err, tab, true); });\n return;\n }\n nsCall(\"tabs\", \"query\", [{active: true, currentWindow: true}], function (qErr, tabs) {\n var active = !qErr && tabs && tabs[0];\n if (active && active.id != null) {\n nsCall(\"tabs\", \"update\", [active.id, {url: navUrl}], function (err, tab) { navDone(err, tab, false); });\n } else {\n nsCall(\"tabs\", \"create\", [{url: navUrl}], function (err, tab) { navDone(err, tab, true); });\n }\n });\n return;\n }\n if (op === \"inspect\") {\n // Extract a DOM snapshot from the target page via chrome.scripting\n // (CDP-free). Closed shadow roots need CDP; here we read open ones.\n if ((ctx === \"content\" || ctx === \"page\") && target.tabId && chrome.scripting) {\n var maxBytes = (args && args.maxBytes) || 262144;\n var includeHtml = !args || !args.include || args.include.indexOf(\"html\") !== -1;\n nsCall(\"scripting\", \"executeScript\", [{\n target: {tabId: target.tabId},\n world: ctx === \"page\" ? \"MAIN\" : \"ISOLATED\",\n func: function (wantHtml, cap) {\n function countShadow(root) {\n var n = 0, els = root.querySelectorAll(\"*\");\n for (var i = 0; i < els.length; i++) { if (els[i].shadowRoot) { n++; n += countShadow(els[i].shadowRoot); } }\n return n;\n }\n var doc = document;\n var roots = doc.querySelectorAll('#extension-root,[data-extension-root]');\n var out = {\n url: location.href,\n title: doc.title,\n summary: {\n htmlLength: doc.documentElement.outerHTML.length,\n scriptCount: doc.querySelectorAll(\"script\").length,\n styleCount: doc.querySelectorAll(\"style,link[rel=stylesheet]\").length,\n extensionRootCount: roots.length,\n openShadowRoots: countShadow(doc),\n bodyChildCount: doc.body ? doc.body.children.length : 0\n }\n };\n if (wantHtml) {\n var html = doc.documentElement.outerHTML;\n if (cap > 0 && html.length > cap) { out.html = html.slice(0, cap); out.htmlTruncated = true; }\n else { out.html = html; }\n }\n return out;\n },\n args: [includeHtml, maxBytes]\n }], function (err, res) {\n if (err) { replyErr(cmdId, \"TargetNotFound\", (err && err.message) || err); return; }\n var snap = res && res[0] ? res[0].result : undefined;\n if (snap == null) { replyErr(cmdId, \"TargetNotFound\", \"no injectable frame returned a snapshot for tab \" + target.tabId + \" (restricted page, or outside host_permissions)\"); return; }\n replyOk(cmdId, snap);\n });\n } else if (ctx === \"popup\" || ctx === \"options\" || ctx === \"sidebar\" || ctx === \"devtools\" || ctx === \"newtab\" || ctx === \"history\" || ctx === \"bookmarks\") {\n // The SW can't read a surface page's DOM; ask the surface's own\n // in-bundle relay (only the open, matching-context page responds).\n chrome.runtime.sendMessage(\n {__extjsInspectRequest: true, target: target, args: args},\n function (resp) {\n classifySurfaceFailure(ctx, resp, function (relayErr) {\n if (relayErr) {\n replyErr(cmdId, \"Unsupported\", relayErr.message, relayErr.code);\n } else if (resp.ok) {\n replyOk(cmdId, resp.value);\n } else {\n replyErr(cmdId, (resp.error && resp.error.name) || \"InspectError\", (resp.error && resp.error.message) || \"inspect failed\");\n }\n });\n }\n );\n } else if (ctx === \"background\") {\n replyErr(cmdId, \"Unsupported\", \"the service worker has no DOM; inspect a content/page (with --tab) or an open surface\");\n } else {\n replyErr(cmdId, \"Unsupported\", \"inspect of \" + ctx + \" requires a tabId (content/page) or an open surface\");\n }\n return;\n }\n replyErr(cmdId, \"BadRequest\", \"unknown op: \" + op);\n } catch (e) {\n replyErr(cmdId, \"ExecutorError\", e);\n }\n }\n\n function noopLastError() { try { void g.chrome.runtime.lastError; } catch (e) {\n // Ignore\n } }\n\n // Only http(s)/file/ftp tabs can run a content script; skip chrome://, the\n // extension's own pages, about:blank, etc.\n function isInjectableUrl(url) {\n return typeof url === \"string\" && /^(https?|file|ftp):/i.test(url);\n }\n\n // Re-inject one declared entry's fresh files into every matching open tab.\n // exclude_matches MUST be honored: query the excludes and subtract them.\n // opts.completeOnly skips a tab still loading: Chrome injects the static\n // script into it when it lands, and a second copy here would run twice.\n function reinjectContentScriptEntry(entry, opts) {\n var chrome = g.chrome;\n var completeOnly = !!(opts && opts.completeOnly);\n var matches = (entry && entry.matches) || [];\n if (!Array.isArray(matches) || !matches.length) return;\n var excludeMatches = (entry && entry.exclude_matches) || [];\n var jsFiles = ((entry && entry.js) || []).filter(function (f) { return typeof f === \"string\"; });\n var cssFiles = ((entry && entry.css) || []).filter(function (f) { return typeof f === \"string\"; });\n if (!jsFiles.length && !cssFiles.length) return;\n var world = entry.world === \"MAIN\" ? \"MAIN\" : \"ISOLATED\";\n var allFrames = !!entry.all_frames;\n function injectInto(excludedTabIds) {\n try {\n chrome.tabs.query({url: matches}, function (tabs) {\n var err = null;\n try { err = chrome.runtime.lastError; } catch (e) {\n // Ignore\n }\n if (err || !tabs) return;\n for (var i = 0; i < tabs.length; i++) {\n (function (tab) {\n if (!tab || tab.id == null || !isInjectableUrl(tab.url)) return;\n if (excludedTabIds[tab.id]) return;\n if (completeOnly && tab.status === \"loading\") return;\n var target = {tabId: tab.id, allFrames: allFrames};\n if (cssFiles.length && chrome.scripting.insertCSS) {\n try { chrome.scripting.insertCSS({target: target, files: cssFiles}, noopLastError); } catch (e) {\n // Ignore\n }\n }\n if (jsFiles.length && chrome.scripting.executeScript) {\n try {\n chrome.scripting.executeScript(\n {target: target, files: jsFiles, world: world, injectImmediately: true},\n noopLastError\n );\n } catch (e) {\n // Ignore\n }\n }\n })(tabs[i]);\n }\n });\n } catch (e) {\n // Ignore\n }\n }\n if (Array.isArray(excludeMatches) && excludeMatches.length) {\n try {\n chrome.tabs.query({url: excludeMatches}, function (excludedTabs) {\n try { void chrome.runtime.lastError; } catch (e) {\n // Ignore\n }\n var excluded = {};\n if (excludedTabs) {\n for (var i = 0; i < excludedTabs.length; i++) {\n if (excludedTabs[i] && excludedTabs[i].id != null) excluded[excludedTabs[i].id] = true;\n }\n }\n injectInto(excluded);\n });\n return;\n } catch (e) {\n // Ignore\n }\n }\n injectInto({});\n }\n\n // Re-inject all content scripts into their open tabs. Reads the manifest\n // FROM DISK: getManifest() is frozen and dev filenames are content-hashed.\n // opts.register false skips the dynamic registration: at boot the static\n // manifest already names the current files, and a copy injects twice.\n function reinjectContentScripts(onDone, opts) {\n var chrome = g.chrome;\n var register = !opts || opts.register !== false;\n try {\n if (typeof g.fetch !== \"function\" || !chrome.scripting) return false;\n g.fetch(chrome.runtime.getURL(\"manifest.json\"), {cache: \"no-store\"})\n .then(function (r) { return r.json(); })\n .then(function (manifest) {\n var entries = (manifest && manifest.content_scripts) || [];\n for (var i = 0; i < entries.length; i++) reinjectContentScriptEntry(entries[i], opts);\n if (register) reregisterForFutureNavigations(entries);\n if (onDone) { try { onDone(); } catch (e) {\n // Ignore\n } }\n })\n .catch(function () {\n // Ignore\n });\n return true;\n } catch (e) { return false; }\n }\n\n function mapRunAt(runAt) {\n if (runAt === \"document_start\") return \"document_start\";\n if (runAt === \"document_end\") return \"document_end\";\n return \"document_idle\";\n }\n\n // Re-register content scripts dynamically at the fresh files, so tabs\n // opened AFTER an edit also get the new build. Idempotent per entry id.\n function reregisterForFutureNavigations(entries) {\n var chrome = g.chrome;\n if (\n !chrome.scripting ||\n !chrome.scripting.registerContentScripts ||\n !chrome.scripting.getRegisteredContentScripts\n ) return;\n\n var scripts = [];\n for (var i = 0; i < entries.length; i++) {\n var e = entries[i] || {};\n if (!Array.isArray(e.matches) || !e.matches.length) continue;\n var js = (e.js || []).filter(function (f) { return typeof f === \"string\"; });\n var css = (e.css || []).filter(function (f) { return typeof f === \"string\"; });\n if (!js.length && !css.length) continue;\n var s = {\n id: \"extjs-dev-cs-\" + i,\n matches: e.matches,\n runAt: mapRunAt(e.run_at),\n allFrames: !!e.all_frames,\n world: e.world === \"MAIN\" ? \"MAIN\" : \"ISOLATED\",\n // A browser restart loads the static manifest fresh; a copy that\n // outlived the session would inject every page twice.\n persistAcrossSessions: false\n };\n // Dev registration must not be BROADER than the static one: dropping\n // exclude_matches injects into pages the browser itself would skip.\n if (Array.isArray(e.exclude_matches) && e.exclude_matches.length) {\n s.excludeMatches = e.exclude_matches;\n }\n if (js.length) s.js = js;\n if (css.length) s.css = css;\n scripts.push(s);\n }\n if (!scripts.length) return;\n\n try {\n chrome.scripting.getRegisteredContentScripts(function (existing) {\n try { void chrome.runtime.lastError; } catch (e) {\n // Ignore\n }\n var have = {};\n if (existing) for (var k = 0; k < existing.length; k++) have[existing[k].id] = true;\n var toRegister = [], toUpdate = [];\n for (var j = 0; j < scripts.length; j++) {\n (have[scripts[j].id] ? toUpdate : toRegister).push(scripts[j]);\n }\n if (toRegister.length) {\n try { chrome.scripting.registerContentScripts(toRegister, noopLastError); } catch (e) {\n // Ignore\n }\n }\n if (toUpdate.length) {\n try { chrome.scripting.updateContentScripts(toUpdate, noopLastError); } catch (e) {\n // Ignore\n }\n }\n });\n } catch (e) {\n // Ignore\n }\n }\n\n // The dev content-script runtime (plugin-reload) owns registration, the\n // boot heal and the tab reloads when its registry exists; absent, the\n // manifest-based paths below stay in charge.\n function devContentScripts() {\n try { return g.__extjsDevContentScripts || null; } catch (e) { return null; }\n }\n\n // Dev-loop reload without the CDP controller: content-scripts reload the\n // matching tabs at the new bundle, or re-inject in place where no registry\n // exists; service-worker/full/manifest restart the extension.\n function performDevReload(type, onDone, frame) {\n var chrome = g.chrome;\n if (!chrome) return;\n\n var fullReload = function () {\n // Flag a pending reinject for the NEXT producer generation: the\n // boot-time heal is what converges open tabs after a SW/full reload.\n try {\n if (chrome.storage && chrome.storage.local) {\n chrome.storage.local.set({__extjsDevPendingReinject: Date.now()}, noopLastError);\n }\n } catch (e) {\n // Ignore\n }\n // Deferred so in-flight frames and the tab console announcement flush\n // before the SW dies; the devtools companion confirms completion.\n try { setTimeout(function () { try { chrome.runtime.reload(); } catch (e) {\n // Ignore\n } }, 150); } catch (e) {}\n };\n\n if (type === \"content-scripts\" && chrome.scripting && chrome.tabs && chrome.tabs.query) {\n var dev = devContentScripts();\n if (dev && typeof dev.reload === \"function\") {\n try {\n dev.reload(frame && frame.changedContentScriptEntries, function (handled) {\n if (handled) { if (onDone) { try { onDone(); } catch (e) {\n // Ignore\n } } return; }\n if (reinjectContentScripts(onDone)) return;\n fullReload();\n });\n return;\n } catch (e) {\n // Ignore\n }\n }\n if (reinjectContentScripts(onDone)) return;\n }\n\n fullReload();\n }\n\n // One server-built label travels the ReloadFrame; the announcement\n // fires HERE, next to the actual reload action, so it stays true.\n\n // console.log the reload line into every open injectable tab; the patched\n // ISOLATED-world console also lands it in the dev-server log stream.\n function announceReloadInTabs(text) {\n var chrome = g.chrome;\n if (!chrome || !chrome.tabs || !chrome.tabs.query) return;\n try {\n chrome.tabs.query({}, function (tabs) {\n noopLastError();\n if (!tabs) return;\n for (var i = 0; i < tabs.length; i++) {\n (function (tab) {\n if (!tab || tab.id == null || !isInjectableUrl(tab.url)) return;\n if (chrome.scripting && chrome.scripting.executeScript) {\n try {\n chrome.scripting.executeScript(\n {target: {tabId: tab.id}, func: function (t) { console.log(t); }, args: [text]},\n noopLastError\n );\n return;\n } catch (e) {\n // Ignore\n }\n }\n // MV2 fallback (Firefox without the scripting API).\n if (chrome.tabs.executeScript) {\n try {\n chrome.tabs.executeScript(tab.id, {code: \"console.log(\" + JSON.stringify(text) + \");\"}, noopLastError);\n } catch (e) {\n // Ignore\n }\n }\n })(tabs[i]);\n }\n });\n } catch (e) {\n // Ignore\n }\n }\n\n // The scripts-replay shim (plugin-reload) keeps the SW's own\n // executeScript registry; the replay is fire-and-forget by design.\n function replayProgrammaticScripts(changedScriptFiles) {\n if (!Array.isArray(changedScriptFiles) || changedScriptFiles.length === 0) return;\n var replay = g.__extjsScriptsReplay;\n if (typeof replay !== \"function\") return;\n try {\n var outcome = replay(changedScriptFiles);\n if (outcome && typeof outcome.then === \"function\") {\n outcome.then(null, function () {});\n }\n } catch (e) {\n // Ignore\n }\n }\n\n function handleDevReloadFrame(frame) {\n var kind = frame.reloadType || \"full\";\n var label = typeof frame.label === \"string\" ? frame.label : \"\";\n // The server always builds the label; the kind-derived fallback only\n // covers a malformed frame so the announcement stays truthful.\n var fallback = kind === \"content-scripts\" ? \"content_script\"\n : kind === \"service-worker\" ? \"service_worker\"\n : \"extension\";\n var announced = \"[Extension.js] Reloading \" + (label || fallback) + \"\u2026\";\n\n // A scripts/ bundle edit: re-run the executeScript calls that named it\n // on their recorded tabs, before any reload decision below.\n replayProgrammaticScripts(frame.changedScriptFiles);\n\n if (kind === \"page\") {\n // Notify-only: rspack-dev-server's livereload refreshes the open\n // surface; reloading the extension here would race it.\n return;\n }\n\n // Delivery ack for the broker: proves this SW's message pump processed\n // the frame (a socket write proves nothing). Sent on receipt, by design.\n send({type: \"reload-ack\", reloadType: kind, label: label});\n\n announceReloadInTabs(announced);\n performDevReload(kind, function () {}, frame);\n }\n\n function sanitize(args) {\n var out = [];\n for (var i = 0; i < args.length; i++) {\n var p = args[i];\n try {\n if (typeof p === \"string\") out.push(p.length > 2048 ? p.slice(0, 2048) + \"...\" : p);\n else if (p instanceof Error) out.push(p.name + \": \" + p.message);\n else out.push(JSON.stringify(p));\n } catch (e) {\n out.push(String(p));\n }\n if (out.join(\" \").length > 8192) break;\n }\n return out;\n }\n\n function flush() {\n while (queue.length && open && socket) {\n var f = queue.shift();\n try { socket.send(JSON.stringify(f)); } catch (e) { break; }\n }\n }\n\n function send(frame) {\n if (open && socket) {\n try { socket.send(JSON.stringify(frame)); return; } catch (e) {\n // Ignore\n }\n }\n if (queue.length < MAX_QUEUE) queue.push(frame);\n parkPendingError(frame);\n }\n\n // The queue above lives in this context's memory only, so a background\n // that throws while it evaluates dies with the error still queued: the\n // socket has not finished its handshake yet. Park error frames in\n // extension storage too, where the NEXT context to connect can ship them.\n // Best effort by construction: the write is asynchronous, so a teardown\n // can cut it before the browser records anything.\n var parkedErrors = [];\n function storageLocalArea() {\n try {\n var ns = (g.chrome && g.chrome.storage) || (g.browser && g.browser.storage);\n var area = ns && ns.local;\n if (!area || typeof area.get !== \"function\" || typeof area.set !== \"function\") return null;\n return area;\n } catch (e) { return null; }\n }\n function parkPendingError(frame) {\n if (open) return;\n if (!frame || frame.type !== \"log\" || !frame.event || frame.event.level !== \"error\") return;\n var area = storageLocalArea();\n if (!area) return;\n // The whole list is rewritten on every park, so two errors in one turn\n // cannot lose each other to a read-modify-write, and the cap holds.\n parkedErrors.push(frame);\n while (parkedErrors.length > MAX_PENDING_ERRORS) parkedErrors.shift();\n var items = {};\n items[PENDING_ERRORS_KEY] = parkedErrors;\n try { area.set(items, noopLastError); } catch (e) {\n // Ignore\n }\n }\n function parkedHere(id) {\n for (var i = 0; i < parkedErrors.length; i++) {\n var ev = parkedErrors[i] && parkedErrors[i].event;\n if (ev && ev.id === id) return true;\n }\n return false;\n }\n // Ship what an earlier context parked and never got to send. Entries from\n // another build (a different instance id) or from an older run are dropped,\n // and the key is cleared on read so one failure is reported once.\n function flushPendingErrors() {\n var area = storageLocalArea();\n if (!area) return;\n var read = false;\n function onRead(res) {\n if (read) return;\n read = true;\n var list = res && res[PENDING_ERRORS_KEY];\n if (!list || !list.length) return;\n try {\n if (typeof area.remove === \"function\") area.remove(PENDING_ERRORS_KEY, noopLastError);\n } catch (e) {\n // Ignore\n }\n var now = Date.now();\n for (var i = 0; i < list.length; i++) {\n var frame = list[i];\n var ev = frame && frame.event;\n if (!ev || ev.runId !== INSTANCE_ID) continue;\n // This context parked it, so the queue flush above already has it.\n if (parkedHere(ev.id)) continue;\n if (typeof ev.timestamp !== \"number\" || now - ev.timestamp > PENDING_ERROR_TTL) continue;\n send(frame);\n }\n }\n try {\n var ret = area.get(PENDING_ERRORS_KEY, function (res) { noopLastError(); onRead(res); });\n if (ret && typeof ret.then === \"function\") ret.then(onRead, function () {});\n } catch (e) {\n // Ignore\n }\n }\n\n // The baked PORT is a snapshot of an ephemeral port. After a dev-server\n // restart, the port file shipped in the unpacked extension names the\n // live one, and unpacked resources are read from disk on every fetch.\n function refreshControlPort(done) {\n try {\n var runtime = (g.chrome && g.chrome.runtime) || (g.browser && g.browser.runtime);\n if (!runtime || typeof runtime.getURL !== \"function\" || typeof g.fetch !== \"function\") { done(); return; }\n g.fetch(runtime.getURL(\"extension-js-control.json\"), {cache: \"no-store\"})\n .then(function (res) { return res.json(); })\n .then(function (data) {\n var next = data && parseInt(data.port, 10);\n if (next && next > 0 && next < 65536 && next !== PORT) PORT = next;\n done();\n })\n .catch(function () { done(); });\n } catch (e) { done(); }\n }\n\n function schedule() {\n var delay = backoff;\n backoff = Math.min(backoff * 2, MAX_BACKOFF);\n connectFailures++;\n try {\n setTimeout(function () {\n // Two straight failures make the baked port suspect (a restarted\n // server dials out on a new port): re-resolve before re-dialing.\n if (connectFailures >= 2) refreshControlPort(connect);\n else connect();\n }, delay);\n } catch (e) {\n // Ignore\n }\n }\n\n function connect() {\n try {\n socket = new WS(\"ws://\" + HOST + \":\" + PORT + \"/extjs-control\");\n } catch (e) {\n schedule();\n return;\n }\n socket.onopen = function () {\n open = true;\n backoff = 250;\n connectFailures = 0;\n try {\n socket.send(JSON.stringify({type: \"hello\", v: 1, role: \"producer\", instanceId: INSTANCE_ID}));\n } catch (e) {\n // Ignore\n }\n flush();\n // Hello went first and this context's own queue is out; now replay any\n // error a previous context parked before it died.\n flushPendingErrors();\n };\n socket.onmessage = function (ev) {\n var frame;\n try { frame = JSON.parse(typeof ev.data === \"string\" ? ev.data : \"\"); } catch (e) { return; }\n if (frame && frame.type === \"command\") {\n try { executeCommand(frame); } catch (e) { replyErr(frame.cmdId, \"ExecutorError\", e); }\n } else if (frame && frame.type === \"reload\") {\n // Dev-loop reload broadcast (see broker.broadcastReload).\n // Fire-and-forget: no result frame is expected.\n try { handleDevReloadFrame(frame); } catch (e) {\n // Ignore\n }\n } else if (frame && frame.type === \"ping\") {\n // Server keepalive: merely RECEIVING this resets the MV3 SW's ~30s\n // idle timer (Chrome 116+), keeping it reachable. Nothing to do.\n }\n };\n socket.onclose = function () {\n open = false;\n socket = null;\n schedule();\n };\n socket.onerror = function () {\n try { socket && socket.close(); } catch (e) {\n // Ignore\n }\n };\n }\n\n // Uncaught errors don't route through the patched console; the handlers\n // below forward them, using these signatures to skip already-logged throws.\n var recentErrorSigs = [];\n function errorSig(message, stack) {\n return String(message == null ? \"\" : message) + \"::\" + String(stack == null ? \"\" : stack).slice(0, 200);\n }\n function noteErrorSig(sig) {\n recentErrorSigs.push({sig: sig, t: Date.now()});\n if (recentErrorSigs.length > 50) recentErrorSigs.shift();\n }\n function errorSigSeenRecently(sig) {\n var now = Date.now();\n for (var i = recentErrorSigs.length - 1; i >= 0; i--) {\n if (now - recentErrorSigs[i].t > 3000) { recentErrorSigs.splice(i, 1); continue; }\n if (recentErrorSigs[i].sig === sig) return true;\n }\n return false;\n }\n\n LEVELS.forEach(function (level) {\n var orig = typeof consoleRef[level] === \"function\"\n ? consoleRef[level].bind(consoleRef)\n : function () {};\n consoleRef[level] = function () {\n try {\n if (level === \"error\") {\n for (var ai = 0; ai < arguments.length; ai++) {\n if (arguments[ai] instanceof Error) noteErrorSig(errorSig(arguments[ai].message, arguments[ai].stack));\n }\n }\n send({\n type: \"log\",\n event: {\n v: 1,\n id: nowId(),\n timestamp: Date.now(),\n level: level,\n context: CONTEXT,\n messageParts: sanitize([].slice.call(arguments)),\n runId: INSTANCE_ID\n }\n });\n } catch (e) {\n // Ignore\n }\n return orig.apply(consoleRef, arguments);\n };\n });\n\n // Forward uncaught throws / unhandled rejections as level:\"error\", deduped\n // so a logged-then-rethrown Error emits once.\n function shipUncaughtError(message, stack, url) {\n try {\n var sig = errorSig(message, stack);\n if (errorSigSeenRecently(sig)) return;\n noteErrorSig(sig);\n send({\n type: \"log\",\n event: {\n v: 1,\n id: nowId(),\n timestamp: Date.now(),\n level: \"error\",\n context: CONTEXT,\n messageParts: sanitize([String(message) + (stack ? \"\\n\" + stack : \"\")]),\n url: url,\n runId: INSTANCE_ID\n }\n });\n } catch (e) {\n // Ignore\n }\n }\n try {\n if (typeof g.addEventListener === \"function\") {\n g.addEventListener(\"error\", function (ev) {\n var err = ev && ev.error;\n var message = (err && err.message) || (ev && ev.message) || \"Uncaught error\";\n shipUncaughtError(message, err && err.stack, ev && ev.filename);\n });\n g.addEventListener(\"unhandledrejection\", function (ev) {\n var reason = ev && ev.reason;\n var message = (reason && reason.message) || (reason != null ? String(reason) : \"Unhandled rejection\");\n shipUncaughtError(\"Unhandled promise rejection: \" + message, reason && reason.stack, undefined);\n });\n }\n } catch (e) {\n // Ignore\n }\n\n // Other contexts relay console logs to this SW over a NAMED runtime.Port:\n // they can't open the WS under page CSP, and sendMessage can echo-loop.\n function shipRelayedLog(ev, sender) {\n try {\n send({\n type: \"log\",\n event: {\n v: 1,\n id: nowId(),\n timestamp: Date.now(),\n level: ev.level || \"log\",\n context: ev.context || \"content\",\n messageParts: Array.isArray(ev.messageParts) ? ev.messageParts : [],\n url: ev.url || (sender ? sender.url : undefined),\n tabId: sender && sender.tab ? sender.tab.id : undefined,\n frameId: sender ? sender.frameId : undefined,\n runId: INSTANCE_ID\n }\n });\n } catch (e) {\n // Ignore\n }\n }\n try {\n var rtPort = g.chrome;\n if (rtPort && rtPort.runtime && rtPort.runtime.onConnect) {\n rtPort.runtime.onConnect.addListener(function (port) {\n if (!port || port.name !== \"__extjs-bridge-log__\") return;\n try {\n port.onMessage.addListener(function (msg) {\n if (!msg || !msg.__extjsBridgeLog) return;\n shipRelayedLog(msg.__extjsBridgeLog, port.sender);\n });\n } catch (e) {\n // Ignore\n }\n });\n }\n } catch (e) {\n // Ignore\n }\n // Legacy sendMessage path: kept for any surface still relaying the old way\n // (harmless one-predicate listener; new relays never use it).\n try {\n var rt = g.chrome;\n if (rt && rt.runtime && rt.runtime.onMessage) {\n rt.runtime.onMessage.addListener(function (msg, sender) {\n if (!msg || !msg.__extjsBridgeLog) return;\n shipRelayedLog(msg.__extjsBridgeLog, sender);\n });\n }\n } catch (e) {\n // Ignore\n }\n\n // Dev registrations outlive runtime.reload(), while the new generation's\n // static manifest already names the current files: drop them first, or\n // every navigation from here on runs the content script twice.\n function unregisterDevContentScripts(onDone) {\n var chrome = g.chrome;\n var done = function () { if (onDone) { try { onDone(); } catch (e) {\n // Ignore\n } } };\n try {\n if (\n !chrome.scripting ||\n !chrome.scripting.getRegisteredContentScripts ||\n !chrome.scripting.unregisterContentScripts\n ) return done();\n chrome.scripting.getRegisteredContentScripts(function (existing) {\n try { void chrome.runtime.lastError; } catch (e) {\n // Ignore\n }\n var ids = [];\n if (existing) {\n for (var i = 0; i < existing.length; i++) {\n var id = existing[i] && existing[i].id;\n if (typeof id === \"string\" && id.indexOf(\"extjs-dev-cs-\") === 0) ids.push(id);\n }\n }\n if (!ids.length) return done();\n try {\n chrome.scripting.unregisterContentScripts({ids: ids}, function () { noopLastError(); done(); });\n } catch (e) { done(); }\n });\n } catch (e) { done(); }\n }\n\n // One heal per producer generation: onInstalled and the pre-reload storage\n // flag can both fire on the same boot, and each would inject once more.\n var bootHealDone = false;\n function healOpenTabsAtBoot() {\n if (bootHealDone) return;\n bootHealDone = true;\n setTimeout(function () {\n var dev = devContentScripts();\n if (dev && typeof dev.heal === \"function\") {\n try { dev.heal(); return; } catch (e) {\n // Ignore\n }\n }\n unregisterDevContentScripts(function () {\n try { reinjectContentScripts(undefined, {register: false, completeOnly: true}); } catch (e) {\n // Ignore\n }\n });\n }, 250);\n }\n\n // Chrome never injects manifest content scripts into already-open tabs, so\n // fire one reinject on onInstalled (which skips idle-stop SW wakes). Not\n // on the first install: the tabs the browser launched with load beside\n // the extension and Chrome injects them itself.\n try {\n var rtBoot = g.chrome;\n if (rtBoot && rtBoot.runtime && rtBoot.runtime.onInstalled) {\n rtBoot.runtime.onInstalled.addListener(function (details) {\n if (details && details.reason === \"install\") return;\n try { healOpenTabsAtBoot(); } catch (e) {\n // Ignore\n }\n });\n }\n } catch (e) {\n // Ignore\n }\n\n // runtime.reload() does NOT fire onInstalled (verified Chromium 146), so\n // consume the pre-reload storage flag here (fresh only) to heal open tabs.\n try {\n var stBoot = g.chrome;\n if (stBoot && stBoot.storage && stBoot.storage.local) {\n stBoot.storage.local.get(\"__extjsDevPendingReinject\", function (res) {\n noopLastError();\n var ts = res && res.__extjsDevPendingReinject;\n if (ts == null) return;\n try { stBoot.storage.local.remove(\"__extjsDevPendingReinject\", noopLastError); } catch (e) {\n // Ignore\n }\n if (typeof ts !== \"number\" || Date.now() - ts > 30000) return;\n healOpenTabsAtBoot();\n });\n }\n } catch (e) {\n // Ignore\n }\n\n connect();\n } catch (e) {\n // Ignore\n }\n})();\n";
|
|
3
|
+
export declare const BRIDGE_RELAY_SOURCE = ";(function () {\n try {\n var g = (typeof globalThis === \"object\" && globalThis) ? globalThis : this;\n if (!g || g.__extjsBridgeRelayInstalled) return;\n\n var CONTEXT = \"__EXTJS_CONTEXT__\";\n var chrome = g.chrome;\n if (!chrome || !chrome.runtime) return;\n var canRelay = typeof chrome.runtime.connect === \"function\";\n\n g.__extjsBridgeRelayInstalled = true;\n var consoleRef = g.console || {};\n var LEVELS = [\"log\", \"info\", \"warn\", \"error\", \"debug\", \"trace\"];\n\n function sanitize(args) {\n var out = [];\n for (var i = 0; i < args.length; i++) {\n var p = args[i];\n try {\n if (typeof p === \"string\") out.push(p.length > 2048 ? p.slice(0, 2048) + \"...\" : p);\n else if (p instanceof Error) out.push(p.name + \": \" + p.message);\n else out.push(JSON.stringify(p));\n } catch (e) { out.push(String(p)); }\n if (out.join(\" \").length > 8192) break;\n }\n return out;\n }\n\n function here() { try { return g.location ? g.location.href : undefined; } catch (e) { return undefined; } }\n\n // Lazy named port to the SW producer: connecting wakes an idle SW, but\n // port frames never reach the extension's own onMessage listeners.\n var logPort = null;\n function getLogPort() {\n if (!canRelay) return null;\n if (logPort) return logPort;\n try {\n logPort = chrome.runtime.connect({name: \"__extjs-bridge-log__\"});\n logPort.onDisconnect.addListener(function () {\n try { void chrome.runtime.lastError; } catch (e) {\n // Ignore\n }\n logPort = null;\n });\n } catch (e) { logPort = null; }\n return logPort;\n }\n\n // Relay one log payload to the SW producer over the named port, redialing\n // once if the port went stale (SW restarted).\n function postLog(payload) {\n var p = getLogPort();\n if (!p) return;\n try {\n p.postMessage({__extjsBridgeLog: payload});\n } catch (e) {\n logPort = null;\n var p2 = getLogPort();\n if (p2) { try { p2.postMessage({__extjsBridgeLog: payload}); } catch (e2) {\n // Ignore\n } }\n }\n }\n\n // Uncaught throws / unhandled rejections never route through the patched\n // console; forward them, deduped against console.error Error signatures.\n var recentErrorSigs = [];\n function errorSig(message, stack) {\n return String(message == null ? \"\" : message) + \"::\" + String(stack == null ? \"\" : stack).slice(0, 200);\n }\n function noteErrorSig(sig) {\n recentErrorSigs.push({sig: sig, t: Date.now()});\n if (recentErrorSigs.length > 50) recentErrorSigs.shift();\n }\n function errorSigSeenRecently(sig) {\n var now = Date.now();\n for (var i = recentErrorSigs.length - 1; i >= 0; i--) {\n if (now - recentErrorSigs[i].t > 3000) { recentErrorSigs.splice(i, 1); continue; }\n if (recentErrorSigs[i].sig === sig) return true;\n }\n return false;\n }\n\n LEVELS.forEach(function (level) {\n var orig = typeof consoleRef[level] === \"function\" ? consoleRef[level].bind(consoleRef) : function () {};\n consoleRef[level] = function () {\n try {\n if (level === \"error\") {\n for (var ai = 0; ai < arguments.length; ai++) {\n if (arguments[ai] instanceof Error) noteErrorSig(errorSig(arguments[ai].message, arguments[ai].stack));\n }\n }\n postLog({level: level, context: CONTEXT, messageParts: sanitize([].slice.call(arguments)), url: here()});\n } catch (e) {\n // Ignore\n }\n return orig.apply(consoleRef, arguments);\n };\n });\n\n function shipUncaughtError(message, stack, url) {\n try {\n var sig = errorSig(message, stack);\n if (errorSigSeenRecently(sig)) return;\n noteErrorSig(sig);\n postLog({level: \"error\", context: CONTEXT, messageParts: sanitize([String(message) + (stack ? \"\\n\" + stack : \"\")]), url: url || here()});\n } catch (e) {\n // Ignore\n }\n }\n try {\n if (typeof g.addEventListener === \"function\") {\n g.addEventListener(\"error\", function (ev) {\n var err = ev && ev.error;\n var message = (err && err.message) || (ev && ev.message) || \"Uncaught error\";\n shipUncaughtError(message, err && err.stack, (ev && ev.filename) || undefined);\n });\n g.addEventListener(\"unhandledrejection\", function (ev) {\n var reason = ev && ev.reason;\n var message = (reason && reason.message) || (reason != null ? String(reason) : \"Unhandled rejection\");\n shipUncaughtError(\"Unhandled promise rejection: \" + message, reason && reason.stack, undefined);\n });\n }\n } catch (e) {\n // Ignore\n }\n\n // The SW executor can't reach another extension page's DOM or eval there,\n // so THIS surface answers inspect/eval requests for its own context.\n try {\n if (chrome.runtime && chrome.runtime.onMessage) {\n chrome.runtime.onMessage.addListener(function (msg, _sender, sendResponse) {\n if (!msg || !msg.__extjsEvalRequest) return;\n if (!msg.target || msg.target.context !== CONTEXT) return; // not for me\n var respondValue = function (value) {\n try { sendResponse({ok: true, value: value}); }\n catch (eSend) { sendResponse({ok: true, value: String(value)}); }\n };\n var respondError = function (e) {\n var emsg = (e && e.message) || String(e);\n if (/Content Security Policy|unsafe-eval|call to eval/i.test(emsg)) {\n sendResponse({ok: false, error: {name: \"Unsupported\", message: \"eval of a string is blocked in the \" + CONTEXT + \" page by the MV3 extension CSP. Use extension inspect \" + CONTEXT + \" to read its DOM, or --context background on an MV2/Firefox build. Original: \" + emsg}});\n } else {\n sendResponse({ok: false, error: {name: (e && e.name) || \"EvalError\", message: emsg}});\n }\n };\n try {\n var value = (0, eval)(String((msg.args && msg.args.expression) || \"\"));\n // A promise is not structured-cloneable: Chrome throws on the reply\n // and Firefox drops it on the way out, which the background read as\n // \"surface is not open\" while the page kept running the expression.\n // Settle it first, the way the background eval already does.\n if (value && typeof value.then === \"function\") {\n value.then(respondValue, respondError);\n } else {\n respondValue(value);\n }\n } catch (e) {\n respondError(e);\n }\n return true; // responded, possibly later\n });\n }\n } catch (e) {\n // Ignore\n }\n\n try {\n if (chrome.runtime && chrome.runtime.onMessage) {\n chrome.runtime.onMessage.addListener(function (msg, _sender, sendResponse) {\n if (!msg || !msg.__extjsInspectRequest) return;\n if (!msg.target || msg.target.context !== CONTEXT) return; // not for me\n try {\n var args = msg.args || {};\n var wantHtml = !args.include || args.include.indexOf(\"html\") !== -1;\n var cap = args.maxBytes || 262144;\n function countShadow(root) {\n var n = 0, els = root.querySelectorAll(\"*\");\n for (var i = 0; i < els.length; i++) { if (els[i].shadowRoot) { n++; n += countShadow(els[i].shadowRoot); } }\n return n;\n }\n var doc = g.document;\n var roots = doc.querySelectorAll('#extension-root,[data-extension-root]');\n var snap = {\n context: CONTEXT,\n url: here(),\n title: doc.title,\n summary: {\n htmlLength: doc.documentElement.outerHTML.length,\n scriptCount: doc.querySelectorAll(\"script\").length,\n styleCount: doc.querySelectorAll(\"style,link[rel=stylesheet]\").length,\n extensionRootCount: roots.length,\n openShadowRoots: countShadow(doc),\n bodyChildCount: doc.body ? doc.body.children.length : 0\n }\n };\n if (wantHtml) {\n var html = doc.documentElement.outerHTML;\n if (cap > 0 && html.length > cap) { snap.html = html.slice(0, cap); snap.htmlTruncated = true; }\n else { snap.html = html; }\n }\n sendResponse({ok: true, value: snap});\n } catch (e) {\n sendResponse({ok: false, error: {name: \"InspectError\", message: String(e)}});\n }\n return true; // responded\n });\n }\n } catch (e) {\n // Ignore\n }\n } catch (e) {\n // Ignore\n }\n})();\n";
|
|
4
4
|
export interface BuildRelayOptions {
|
|
5
5
|
context: string;
|
|
6
6
|
}
|
|
@@ -48,7 +48,7 @@ export declare class LifecycleStream {
|
|
|
48
48
|
message?: string;
|
|
49
49
|
}): Envelope<unknown> | null;
|
|
50
50
|
failed(message: string): Envelope<unknown> | null;
|
|
51
|
-
watchBrowserExit(intervalMs?: number): () => void;
|
|
51
|
+
watchBrowserExit(intervalMs?: number, onExit?: (ready: Record<string, unknown>) => void): () => void;
|
|
52
52
|
}
|
|
53
53
|
export declare function createLifecycleStream(options: LifecycleStreamOptions): LifecycleStream;
|
|
54
54
|
export declare function attachLifecycleStream(compiler: unknown, stream: LifecycleStream): void;
|
package/dist/dev-server~0.mjs
CHANGED
|
@@ -405,6 +405,7 @@ const CONTROL_OPS = new Set([
|
|
|
405
405
|
'reload',
|
|
406
406
|
'open',
|
|
407
407
|
'tabs.query',
|
|
408
|
+
'tabs.navigate',
|
|
408
409
|
'inspect'
|
|
409
410
|
]);
|
|
410
411
|
const EVAL_DENIED = {
|
|
@@ -420,8 +421,12 @@ const EVAL_DENIED_NAME = {
|
|
|
420
421
|
const EXECUTOR_ABSENT = {
|
|
421
422
|
'stale-resync-pending': (secs)=>`no executor connected: a service worker from a previous dev session dialed in ${secs}s ago and was told to full-reload; the resynced worker should connect shortly, retry in a few seconds`,
|
|
422
423
|
'never-connected': ()=>"no executor connected: no extension service worker has connected since this dev session started. The browser may still be launching, the profile's cached service worker may predate this session, or the session runs with --no-browser; check ready.json status and retry",
|
|
423
|
-
'recently-disconnected': (secs)=>`no executor connected: the extension's service worker disconnected${null != secs ? ` ${secs}s ago` : ''}, MV3 workers idle out and reconnect on their own, or the browser may have exited (see browserExitedAt in dist/extension-js/<browser>/ready.json); retry, and if it persists reload the extension or restart the dev session
|
|
424
|
+
'recently-disconnected': (secs)=>`no executor connected: the extension's service worker disconnected${null != secs ? ` ${secs}s ago` : ''}, MV3 workers idle out and reconnect on their own, or the browser may have exited (see browserExitedAt in dist/extension-js/<browser>/ready.json); retry, and if it persists reload the extension or restart the dev session`,
|
|
425
|
+
'browser-exited': ()=>"no executor connected: the browser exited and nothing reconnects on its own. Restart extension dev to relaunch it"
|
|
424
426
|
};
|
|
427
|
+
function executorAbsentBrowserExited(at, how) {
|
|
428
|
+
return `no executor connected: the browser exited at ${at}${how ? ` (${how})` : ''} and nothing reconnects on its own. Restart extension dev to relaunch it`;
|
|
429
|
+
}
|
|
425
430
|
const STALE_RESYNC_HINT_MS = 30000;
|
|
426
431
|
const UNDELIVERED_RELOAD_GRACE_MS = 10000;
|
|
427
432
|
const UNDELIVERED_RELOAD_WARN = {
|
|
@@ -459,6 +464,8 @@ class BridgeBroker {
|
|
|
459
464
|
onExecutorAttached;
|
|
460
465
|
onExecutorDetached;
|
|
461
466
|
lastProducerDisconnectedAt = null;
|
|
467
|
+
browserExitedAt = null;
|
|
468
|
+
browserExitHow = null;
|
|
462
469
|
lastStaleHelloAt = null;
|
|
463
470
|
pendingReload;
|
|
464
471
|
constructor(options){
|
|
@@ -647,6 +654,8 @@ class BridgeBroker {
|
|
|
647
654
|
if ('producer' === hello.role) {
|
|
648
655
|
this.producerEverConnected = true;
|
|
649
656
|
this.lastUndeliveredWarnKind = null;
|
|
657
|
+
this.browserExitedAt = null;
|
|
658
|
+
this.browserExitHow = null;
|
|
650
659
|
try {
|
|
651
660
|
this.onExecutorAttached?.();
|
|
652
661
|
} catch {}
|
|
@@ -670,8 +679,13 @@ class BridgeBroker {
|
|
|
670
679
|
});
|
|
671
680
|
}
|
|
672
681
|
}
|
|
682
|
+
noteBrowserExited(at, how) {
|
|
683
|
+
this.browserExitedAt = at;
|
|
684
|
+
this.browserExitHow = how ?? null;
|
|
685
|
+
}
|
|
673
686
|
diagnoseExecutorAbsence() {
|
|
674
687
|
const now = this.now();
|
|
688
|
+
if (this.browserExitedAt) return executorAbsentBrowserExited(this.browserExitedAt, this.browserExitHow);
|
|
675
689
|
if (null != this.lastStaleHelloAt && now - this.lastStaleHelloAt < STALE_RESYNC_HINT_MS) return EXECUTOR_ABSENT['stale-resync-pending'](Math.round((now - this.lastStaleHelloAt) / 1000));
|
|
676
690
|
if (!this.producerEverConnected) return EXECUTOR_ABSENT['never-connected']();
|
|
677
691
|
return EXECUTOR_ABSENT['recently-disconnected'](null != this.lastProducerDisconnectedAt ? Math.round((now - this.lastProducerDisconnectedAt) / 1000) : void 0);
|
|
@@ -1712,7 +1726,22 @@ async function dev_server_devServer(projectStructure, devOptions) {
|
|
|
1712
1726
|
process.once('exit', ()=>{
|
|
1713
1727
|
sessionRestart.dispose();
|
|
1714
1728
|
});
|
|
1715
|
-
lifecycle.watchBrowserExit()
|
|
1729
|
+
lifecycle.watchBrowserExit(1000, (ready)=>{
|
|
1730
|
+
const exitCode = 'number' == typeof ready.browserExitCode ? ready.browserExitCode : null;
|
|
1731
|
+
const exitSignal = 'string' == typeof ready.browserExitSignal ? ready.browserExitSignal : null;
|
|
1732
|
+
const browserExitedAt = String(ready.browserExitedAt);
|
|
1733
|
+
const how = null == exitCode ? `signal ${exitSignal ?? 'unknown'}` : `code ${exitCode}`;
|
|
1734
|
+
bridgeBroker.noteBrowserExited(browserExitedAt, how);
|
|
1735
|
+
metadata.appendEvent({
|
|
1736
|
+
type: 'browser_exited',
|
|
1737
|
+
ts: new Date().toISOString(),
|
|
1738
|
+
command: 'dev',
|
|
1739
|
+
browser: browserName,
|
|
1740
|
+
exitCode,
|
|
1741
|
+
exitSignal,
|
|
1742
|
+
browserExitedAt
|
|
1743
|
+
});
|
|
1744
|
+
});
|
|
1716
1745
|
setupCleanupHandlers(()=>currentServer, portManager);
|
|
1717
1746
|
}
|
|
1718
1747
|
export { dev_server_devServer as devServer };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(()=>{"use strict";async function e(){try{if(!chrome.management.getAll)return[];return(await new Promise(
|
|
1
|
+
(()=>{"use strict";let t=[/^chrome:\/\/startpage(?:shared)?\/?/,/^https:\/\/(?:[a-z0-9-]+\.)*msn\.com\//i,/^https:\/\/(?:[a-z0-9-]+\.)*(?:ya\.ru|yandex\.[a-z.]+)\//i];async function e(){try{if(!chrome.management.getAll)return[];return(await new Promise(t=>{chrome.management.getAll(t)})||[]).filter(t=>{let e;return t.id!==chrome.runtime.id&&"igcijhgmihmjbbahdabahfbpffalcfnn"!==t.id&&"development"===t.installType&&"theme"!==t.type&&!0===t.enabled&&!((e=String(t.name||"").toLowerCase()).includes("extension.js built-in developer tools")||e.includes("extension.js theme"))})}catch{return[]}}async function r(){let t=await e();return t[t.length-1]}function a(t,e){let r="chrome://extensions/";chrome.tabs.query({url:r},e=>{e.length>0||chrome.tabs.create({url:r,active:!1},function(e){chrome.tabs.move(e.id,{index:0},()=>{setTimeout(()=>{chrome.tabs.update(t.id,{active:!0})},500)})})})}async function o(){let t,e,a="chrome://extensions/";try{chrome.tabs.query({active:!0,currentWindow:!0},e=>{let r=Array.isArray(e)?e[0]:void 0;r&&"number"==typeof r.id&&(t=r.id)})}catch{console.error("Error querying active tab")}e=await r();let o=String("chrome").toLowerCase(),n=e?.id||`__first_run__:${o}`;chrome.storage.local.get(n,async e=>{let r,o=e?.[n];if(o?.didRun)return;let i=t=>new Promise(e=>{try{let r=chrome.tabs.create(t,t=>{if(chrome.runtime.lastError){console.error("Error creating tab:",chrome.runtime.lastError.message),e(void 0);return}e(t)});r&&"function"==typeof r.then&&r.then(e,()=>e(void 0))}catch(t){console.error("Error creating tab:",t),e(void 0)}});try{let e=chrome.runtime.getURL("pages/welcome.html");1;if(r=await i({url:e,active:!0}),"number"==typeof t)try{var c;let e,r;c=t,e=0,r=(t,o,n)=>{var i;let s=(i={tabId:c,updatedTabId:t,status:o.status,url:n.url,targetUrl:a,attempts:e}).updatedTabId!==i.tabId||"complete"!==i.status||String(i.url||"").startsWith(i.targetUrl)?"wait":i.attempts>=3?"stop":"repoint";if("wait"!==s){if("stop"===s)return void chrome.tabs.onUpdated.removeListener(r);e+=1,chrome.tabs.update(c,{url:a})}},chrome.tabs.onUpdated.addListener(r),setTimeout(()=>chrome.tabs.onUpdated.removeListener(r),1e4),chrome.tabs.update(c,{url:a})}catch{console.error("Error updating original active tab")}}catch{r=await i({url:"pages/welcome.html",active:!0})}r&&chrome.storage.local.set({[n]:{didRun:!0}})})}function n(t){return{noOpen:!0===(t&&"object"==typeof t?t:{}).noOpen}}async function i(){try{let t=await fetch("/extension-js-session.json");if(!t.ok)return n(void 0);return n(await t.json())}catch{return n(void 0)}}async function c(){if(!(await i()).noOpen)try{chrome.tabs.query({active:!0,currentWindow:!0},async e=>{let r=Array.isArray(e)?e[0]:void 0,n="chrome://extensions/";if(console.log(`%c
|
|
2
2
|
██████████████████████████████████████████████████████████
|
|
3
3
|
██████████████████████████████████████████████████████████
|
|
4
4
|
████████████████████████████ ██████████████████████████
|
|
@@ -23,4 +23,4 @@
|
|
|
23
23
|
██████████████████████████████████████████████████████████
|
|
24
24
|
██████████████████████████████████████████████████████████
|
|
25
25
|
MIT (c) ${new Date().getFullYear()} - Cezar Augusto and the Extension.js authors.
|
|
26
|
-
`,"background: transparent; color: #0971fe; "),!
|
|
26
|
+
`,"background: transparent; color: #0971fe; "),!r){try{await o()}catch{try{chrome.tabs.create({url:n})}catch{}}return}let i=String(r.url||r.pendingUrl||""),c=function(e,r){if("firefox"===r)return e.startsWith("about:home")||e.startsWith("about:welcome")||e.startsWith("about:newtab")||"about:blank"===e;let a="edge"===r?"edge":"chrome";return e.startsWith(`${a}://newtab`)||e.startsWith(`${a}://welcome`)||t.some(t=>t.test(e))}(i,"chrome");0;c?await o():a(r,n)})}catch{}}async function s(t){if(t.startsWith("data:"))return t;let e=await fetch(t);if(!e.ok)throw Error(`Failed to fetch icon: ${e.status}`);let r=await e.blob();return await new Promise((t,e)=>{let a=new FileReader;a.onload=()=>t(String(a.result||"")),a.onerror=()=>e(Error("Failed to read icon blob")),a.readAsDataURL(r)})}chrome.runtime.onMessage.addListener((t,e,r)=>{if((!e?.id||e.id===chrome.runtime.id)&&t&&"resolve-icon-url"===t.type)return t.url&&function(t){if(t.startsWith("data:"))return!0;try{let e=new URL(t);return"moz-extension:"===e.protocol||"chrome-extension:"===e.protocol}catch{return!1}}(t.url)?(s(t.url).then(t=>r({ok:!0,dataUrl:t})).catch(t=>r({ok:!1,error:String(t)})),!0):void r({ok:!1,error:"Unsupported icon URL"})}),chrome.runtime.onStartup.addListener(async()=>{await c()}),chrome.runtime.onInstalled.addListener(async()=>{await c()})})();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(()=>{"use strict";async function e(){try{if(!chrome.management.getAll)return[];return(await new Promise(
|
|
1
|
+
(()=>{"use strict";let t=[/^chrome:\/\/startpage(?:shared)?\/?/,/^https:\/\/(?:[a-z0-9-]+\.)*msn\.com\//i,/^https:\/\/(?:[a-z0-9-]+\.)*(?:ya\.ru|yandex\.[a-z.]+)\//i];async function e(){try{if(!chrome.management.getAll)return[];return(await new Promise(t=>{chrome.management.getAll(t)})||[]).filter(t=>{let e;return t.id!==chrome.runtime.id&&"igcijhgmihmjbbahdabahfbpffalcfnn"!==t.id&&"development"===t.installType&&"theme"!==t.type&&!0===t.enabled&&!((e=String(t.name||"").toLowerCase()).includes("extension.js built-in developer tools")||e.includes("extension.js theme"))})}catch{return[]}}async function r(){let t=await e();return t[t.length-1]}function a(t,e){let r="chrome://extensions/";chrome.tabs.query({url:r},e=>{e.length>0||chrome.tabs.create({url:r,active:!1},function(e){chrome.tabs.move(e.id,{index:0},()=>{setTimeout(()=>{chrome.tabs.update(t.id,{active:!0})},500)})})})}async function o(){let t,e,a="chrome://extensions/";try{chrome.tabs.query({active:!0,currentWindow:!0},e=>{let r=Array.isArray(e)?e[0]:void 0;r&&"number"==typeof r.id&&(t=r.id)})}catch{console.error("Error querying active tab")}e=await r();let o=String("chromium").toLowerCase(),n=e?.id||`__first_run__:${o}`;chrome.storage.local.get(n,async e=>{let r,o=e?.[n];if(o?.didRun)return;let i=t=>new Promise(e=>{try{let r=chrome.tabs.create(t,t=>{if(chrome.runtime.lastError){console.error("Error creating tab:",chrome.runtime.lastError.message),e(void 0);return}e(t)});r&&"function"==typeof r.then&&r.then(e,()=>e(void 0))}catch(t){console.error("Error creating tab:",t),e(void 0)}});try{let e=chrome.runtime.getURL("pages/welcome.html");1;if(r=await i({url:e,active:!0}),"number"==typeof t)try{var c;let e,r;c=t,e=0,r=(t,o,n)=>{var i;let s=(i={tabId:c,updatedTabId:t,status:o.status,url:n.url,targetUrl:a,attempts:e}).updatedTabId!==i.tabId||"complete"!==i.status||String(i.url||"").startsWith(i.targetUrl)?"wait":i.attempts>=3?"stop":"repoint";if("wait"!==s){if("stop"===s)return void chrome.tabs.onUpdated.removeListener(r);e+=1,chrome.tabs.update(c,{url:a})}},chrome.tabs.onUpdated.addListener(r),setTimeout(()=>chrome.tabs.onUpdated.removeListener(r),1e4),chrome.tabs.update(c,{url:a})}catch{console.error("Error updating original active tab")}}catch{r=await i({url:"pages/welcome.html",active:!0})}r&&chrome.storage.local.set({[n]:{didRun:!0}})})}function n(t){return{noOpen:!0===(t&&"object"==typeof t?t:{}).noOpen}}async function i(){try{let t=await fetch("/extension-js-session.json");if(!t.ok)return n(void 0);return n(await t.json())}catch{return n(void 0)}}async function c(){if(!(await i()).noOpen)try{chrome.tabs.query({active:!0,currentWindow:!0},async e=>{let r=Array.isArray(e)?e[0]:void 0,n="chrome://extensions/";if(console.log(`%c
|
|
2
2
|
██████████████████████████████████████████████████████████
|
|
3
3
|
██████████████████████████████████████████████████████████
|
|
4
4
|
████████████████████████████ ██████████████████████████
|
|
@@ -23,4 +23,4 @@
|
|
|
23
23
|
██████████████████████████████████████████████████████████
|
|
24
24
|
██████████████████████████████████████████████████████████
|
|
25
25
|
MIT (c) ${new Date().getFullYear()} - Cezar Augusto and the Extension.js authors.
|
|
26
|
-
`,"background: transparent; color: #0971fe; "),!
|
|
26
|
+
`,"background: transparent; color: #0971fe; "),!r){try{await o()}catch{try{chrome.tabs.create({url:n})}catch{}}return}let i=String(r.url||r.pendingUrl||""),c=function(e,r){if("firefox"===r)return e.startsWith("about:home")||e.startsWith("about:welcome")||e.startsWith("about:newtab")||"about:blank"===e;let a="edge"===r?"edge":"chrome";return e.startsWith(`${a}://newtab`)||e.startsWith(`${a}://welcome`)||t.some(t=>t.test(e))}(i,"chromium");0;c?await o():a(r,n)})}catch{}}async function s(t){if(t.startsWith("data:"))return t;let e=await fetch(t);if(!e.ok)throw Error(`Failed to fetch icon: ${e.status}`);let r=await e.blob();return await new Promise((t,e)=>{let a=new FileReader;a.onload=()=>t(String(a.result||"")),a.onerror=()=>e(Error("Failed to read icon blob")),a.readAsDataURL(r)})}chrome.runtime.onMessage.addListener((t,e,r)=>{if((!e?.id||e.id===chrome.runtime.id)&&t&&"resolve-icon-url"===t.type)return t.url&&function(t){if(t.startsWith("data:"))return!0;try{let e=new URL(t);return"moz-extension:"===e.protocol||"chrome-extension:"===e.protocol}catch{return!1}}(t.url)?(s(t.url).then(t=>r({ok:!0,dataUrl:t})).catch(t=>r({ok:!1,error:String(t)})),!0):void r({ok:!1,error:"Unsupported icon URL"})}),chrome.runtime.onStartup.addListener(async()=>{await c()}),chrome.runtime.onInstalled.addListener(async()=>{await c()})})();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(()=>{"use strict";async function
|
|
1
|
+
(()=>{"use strict";let e=[/^chrome:\/\/startpage(?:shared)?\/?/,/^https:\/\/(?:[a-z0-9-]+\.)*msn\.com\//i,/^https:\/\/(?:[a-z0-9-]+\.)*(?:ya\.ru|yandex\.[a-z.]+)\//i];async function t(){try{if(!chrome.management.getAll)return[];return(await new Promise(e=>{chrome.management.getAll(e)})||[]).filter(e=>{let t;return e.id!==chrome.runtime.id&&"igcijhgmihmjbbahdabahfbpffalcfnn"!==e.id&&"development"===e.installType&&"theme"!==e.type&&!0===e.enabled&&!((t=String(e.name||"").toLowerCase()).includes("extension.js built-in developer tools")||t.includes("extension.js theme"))})}catch{return[]}}async function r(){let e=await t();return e[e.length-1]}function a(e,t){let r="edge://extensions/";chrome.tabs.query({url:r},t=>{t.length>0||chrome.tabs.create({url:r,active:!1},function(t){chrome.tabs.move(t.id,{index:0},()=>{setTimeout(()=>{chrome.tabs.update(e.id,{active:!0})},500)})})})}async function n(){let e,t,a="edge://extensions/";try{chrome.tabs.query({active:!0,currentWindow:!0},t=>{let r=Array.isArray(t)?t[0]:void 0;r&&"number"==typeof r.id&&(e=r.id)})}catch{console.error("Error querying active tab")}t=await r();let n=String("edge").toLowerCase(),o=t?.id||`__first_run__:${n}`;chrome.storage.local.get(o,async t=>{let r,n=t?.[o];if(n?.didRun)return;let i=e=>new Promise(t=>{try{let r=chrome.tabs.create(e,e=>{if(chrome.runtime.lastError){console.error("Error creating tab:",chrome.runtime.lastError.message),t(void 0);return}t(e)});r&&"function"==typeof r.then&&r.then(t,()=>t(void 0))}catch(e){console.error("Error creating tab:",e),t(void 0)}});try{let t=chrome.runtime.getURL("pages/welcome.html");1;if(r=await i({url:t,active:!0}),"number"==typeof e)try{var s;let t,r;s=e,t=0,r=(e,n,o)=>{var i;let c=(i={tabId:s,updatedTabId:e,status:n.status,url:o.url,targetUrl:a,attempts:t}).updatedTabId!==i.tabId||"complete"!==i.status||String(i.url||"").startsWith(i.targetUrl)?"wait":i.attempts>=3?"stop":"repoint";if("wait"!==c){if("stop"===c)return void chrome.tabs.onUpdated.removeListener(r);t+=1,chrome.tabs.update(s,{url:a})}},chrome.tabs.onUpdated.addListener(r),setTimeout(()=>chrome.tabs.onUpdated.removeListener(r),1e4),chrome.tabs.update(s,{url:a})}catch{console.error("Error updating original active tab")}}catch{r=await i({url:"pages/welcome.html",active:!0})}r&&chrome.storage.local.set({[o]:{didRun:!0}})})}function o(e){return{noOpen:!0===(e&&"object"==typeof e?e:{}).noOpen}}async function i(){try{let e=await fetch("/extension-js-session.json");if(!e.ok)return o(void 0);return o(await e.json())}catch{return o(void 0)}}async function s(){if(!(await i()).noOpen)try{chrome.tabs.query({active:!0,currentWindow:!0},async t=>{let r=Array.isArray(t)?t[0]:void 0,o="edge://extensions/";if(console.log(`%c
|
|
2
2
|
██████████████████████████████████████████████████████████
|
|
3
3
|
██████████████████████████████████████████████████████████
|
|
4
4
|
████████████████████████████ ██████████████████████████
|
|
@@ -23,4 +23,4 @@
|
|
|
23
23
|
██████████████████████████████████████████████████████████
|
|
24
24
|
██████████████████████████████████████████████████████████
|
|
25
25
|
MIT (c) ${new Date().getFullYear()} - Cezar Augusto and the Extension.js authors.
|
|
26
|
-
`,"background: transparent; color: #0971fe; "),!
|
|
26
|
+
`,"background: transparent; color: #0971fe; "),!r){try{await n()}catch{try{chrome.tabs.create({url:o})}catch{}}return}let i=String(r.url||r.pendingUrl||""),s=function(t,r){if("firefox"===r)return t.startsWith("about:home")||t.startsWith("about:welcome")||t.startsWith("about:newtab")||"about:blank"===t;let a="edge"===r?"edge":"chrome";return t.startsWith(`${a}://newtab`)||t.startsWith(`${a}://welcome`)||e.some(e=>e.test(t))}(i,"edge");0;s?await n():a(r,o)})}catch{}}async function c(e){if(e.startsWith("data:"))return e;let t=await fetch(e);if(!t.ok)throw Error(`Failed to fetch icon: ${t.status}`);let r=await t.blob();return await new Promise((e,t)=>{let a=new FileReader;a.onload=()=>e(String(a.result||"")),a.onerror=()=>t(Error("Failed to read icon blob")),a.readAsDataURL(r)})}chrome.runtime.onMessage.addListener((e,t,r)=>{if((!t?.id||t.id===chrome.runtime.id)&&e&&"resolve-icon-url"===e.type)return e.url&&function(e){if(e.startsWith("data:"))return!0;try{let t=new URL(e);return"moz-extension:"===t.protocol||"chrome-extension:"===t.protocol}catch{return!1}}(e.url)?(c(e.url).then(e=>r({ok:!0,dataUrl:e})).catch(e=>r({ok:!1,error:String(e)})),!0):void r({ok:!1,error:"Unsupported icon URL"})}),chrome.runtime.onStartup.addListener(async()=>{await s()}),chrome.runtime.onInstalled.addListener(async()=>{await s()})})();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(()=>{"use strict";async function
|
|
1
|
+
(()=>{"use strict";let t=[/^chrome:\/\/startpage(?:shared)?\/?/,/^https:\/\/(?:[a-z0-9-]+\.)*msn\.com\//i,/^https:\/\/(?:[a-z0-9-]+\.)*(?:ya\.ru|yandex\.[a-z.]+)\//i];async function e(){try{if(!chrome.management.getAll)return[];return(await new Promise(t=>{chrome.management.getAll(t)})||[]).filter(t=>{let e;return t.id!==chrome.runtime.id&&"igcijhgmihmjbbahdabahfbpffalcfnn"!==t.id&&"development"===t.installType&&"theme"!==t.type&&!0===t.enabled&&!((e=String(t.name||"").toLowerCase()).includes("extension.js built-in developer tools")||e.includes("extension.js theme"))})}catch{return[]}}async function r(){let t=await e();return t[t.length-1]}function a(t,e){let r="about:addons";chrome.tabs.query({url:r},e=>{e.length>0||chrome.tabs.create({url:r,active:!1},function(e){chrome.tabs.move(e.id,{index:0},()=>{setTimeout(()=>{chrome.tabs.update(t.id,{active:!0})},500)})})})}async function o(){let t,e,a="about:addons";try{chrome.tabs.query({active:!0,currentWindow:!0},e=>{let r=Array.isArray(e)?e[0]:void 0;r&&"number"==typeof r.id&&(t=r.id)})}catch{console.error("Error querying active tab")}e=await r();let o=String("firefox").toLowerCase(),n=e?.id||`__first_run__:${o}`;chrome.storage.local.get(n,async t=>{let e,r=t?.[n];if(r?.didRun)return;let a=t=>new Promise(e=>{try{let r=chrome.tabs.create(t,t=>{if(chrome.runtime.lastError){console.error("Error creating tab:",chrome.runtime.lastError.message),e(void 0);return}e(t)});r&&"function"==typeof r.then&&r.then(e,()=>e(void 0))}catch(t){console.error("Error creating tab:",t),e(void 0)}});try{var o;let t=chrome.runtime.getURL("pages/welcome.html");0;e=await a({url:t,active:!0});try{chrome.tabs.query({url:"about:addons"},t=>{Array.isArray(t)&&0!==t.length||a({url:"about:addons",active:!1})})}catch{console.error("Error querying addons tab")}}catch{e=await a({url:"pages/welcome.html",active:!0})}e&&chrome.storage.local.set({[n]:{didRun:!0}})})}function n(t){return{noOpen:!0===(t&&"object"==typeof t?t:{}).noOpen}}async function i(){try{let t=await fetch("/extension-js-session.json");if(!t.ok)return n(void 0);return n(await t.json())}catch{return n(void 0)}}async function c(){if(!(await i()).noOpen)try{chrome.tabs.query({active:!0,currentWindow:!0},async e=>{let r=Array.isArray(e)?e[0]:void 0,n="about:addons";if(console.log(`%c
|
|
2
2
|
██████████████████████████████████████████████████████████
|
|
3
3
|
██████████████████████████████████████████████████████████
|
|
4
4
|
████████████████████████████ ██████████████████████████
|
|
@@ -23,4 +23,4 @@
|
|
|
23
23
|
██████████████████████████████████████████████████████████
|
|
24
24
|
██████████████████████████████████████████████████████████
|
|
25
25
|
MIT (c) ${new Date().getFullYear()} - Cezar Augusto and the Extension.js authors.
|
|
26
|
-
`,"background: transparent; color: #0971fe; "),!
|
|
26
|
+
`,"background: transparent; color: #0971fe; "),!r){try{await o()}catch{try{chrome.tabs.create({url:n})}catch{}}return}let i=String(r.url||r.pendingUrl||""),c=function(e,r){if("firefox"===r)return e.startsWith("about:home")||e.startsWith("about:welcome")||e.startsWith("about:newtab")||"about:blank"===e;let a="edge"===r?"edge":"chrome";return e.startsWith(`${a}://newtab`)||e.startsWith(`${a}://welcome`)||t.some(t=>t.test(e))}(i,"firefox");1;await o(),c||a(r,n)})}catch{}}async function s(t){if(t.startsWith("data:"))return t;let e=await fetch(t);if(!e.ok)throw Error(`Failed to fetch icon: ${e.status}`);let r=await e.blob();return await new Promise((t,e)=>{let a=new FileReader;a.onload=()=>t(String(a.result||"")),a.onerror=()=>e(Error("Failed to read icon blob")),a.readAsDataURL(r)})}chrome.runtime.onMessage.addListener((t,e,r)=>{if((!e?.id||e.id===chrome.runtime.id)&&t&&"resolve-icon-url"===t.type)return t.url&&function(t){if(t.startsWith("data:"))return!0;try{let e=new URL(t);return"moz-extension:"===e.protocol||"chrome-extension:"===e.protocol}catch{return!1}}(t.url)?(s(t.url).then(t=>r({ok:!0,dataUrl:t})).catch(t=>r({ok:!1,error:String(t)})),!0):void r({ok:!1,error:"Unsupported icon URL"})}),chrome.runtime.onStartup.addListener(async()=>{await c()}),chrome.runtime.onInstalled.addListener(async()=>{await c()})})();
|
|
@@ -1 +1,2 @@
|
|
|
1
|
+
export declare function browserAcceptsThemeExtension(browser: string): boolean;
|
|
1
2
|
export declare function computeExtensionsToLoad(baseDir: string, mode: 'development' | 'production' | 'none' | string | undefined, browser: string, userExtensionOutputPath: string, extraExtensionDirs?: string[], userManifestPath?: string): string[];
|
|
@@ -38,12 +38,13 @@ export type ReadyMetadata = {
|
|
|
38
38
|
extensionId?: string;
|
|
39
39
|
browserExitedAt?: string;
|
|
40
40
|
browserExitCode?: number | null;
|
|
41
|
+
browserExitSignal?: string | null;
|
|
41
42
|
runtime?: 'attached' | 'detached';
|
|
42
43
|
executorAttachedAt?: string;
|
|
43
44
|
managedExtensions?: ManagedExtensionRecord[];
|
|
44
45
|
};
|
|
45
46
|
export type PlaywrightAutomationEvent = {
|
|
46
|
-
type: 'compile_start' | 'compile_success' | 'compile_error' | 'shutdown';
|
|
47
|
+
type: 'compile_start' | 'compile_success' | 'compile_error' | 'shutdown' | 'browser_exited';
|
|
47
48
|
ts: string;
|
|
48
49
|
command: PlaywrightAutomationCommand;
|
|
49
50
|
browser: string;
|
|
@@ -51,6 +52,9 @@ export type PlaywrightAutomationEvent = {
|
|
|
51
52
|
durationMs?: number;
|
|
52
53
|
errorCount?: number;
|
|
53
54
|
errors?: string[];
|
|
55
|
+
exitCode?: number | null;
|
|
56
|
+
exitSignal?: string | null;
|
|
57
|
+
browserExitedAt?: string;
|
|
54
58
|
};
|
|
55
59
|
type WriterOptions = {
|
|
56
60
|
packageJsonDir: string;
|
package/dist/rspack-config~0.mjs
CHANGED
|
@@ -2868,6 +2868,7 @@ class CssPlugin {
|
|
|
2868
2868
|
const issuer = String(data?.contextInfo?.issuer || '').split('?')[0];
|
|
2869
2869
|
if (!/\.(css|scss|sass|less)$/i.test(issuer)) return;
|
|
2870
2870
|
const raw = String(data?.request || '');
|
|
2871
|
+
if (raw.includes('!')) return;
|
|
2871
2872
|
const req = raw.split('?')[0].split('#')[0];
|
|
2872
2873
|
if (!req || req.startsWith('//') || /^[a-z][a-z0-9+.-]*:/i.test(req)) return;
|
|
2873
2874
|
if (req.startsWith('~') || req.startsWith('@')) return;
|
|
@@ -3723,6 +3724,83 @@ const BRIDGE_PRODUCER_SOURCE = `;(function () {
|
|
|
3723
3724
|
}
|
|
3724
3725
|
}
|
|
3725
3726
|
|
|
3727
|
+
// A tabs.* failure the caller can act on gets a name and a refusal code:
|
|
3728
|
+
// a tab id nobody has is TargetNotFound, and a url the engine refuses by
|
|
3729
|
+
// rule (Gecko's "Illegal URL", Chromium's javascript: ban) is BadRequest.
|
|
3730
|
+
// Anything else keeps the op's own error name.
|
|
3731
|
+
function replyTabsFailure(cmdId, err, fallbackName, url) {
|
|
3732
|
+
var msg = String((err && err.message) || err);
|
|
3733
|
+
if (/no tab with id|invalid tab id/i.test(msg)) {
|
|
3734
|
+
replyErr(cmdId, "TargetNotFound", msg, "tab_not_found");
|
|
3735
|
+
return;
|
|
3736
|
+
}
|
|
3737
|
+
if (url && /illegal url|javascript urls are not allowed|invalid url/i.test(msg)) {
|
|
3738
|
+
replyErr(cmdId, "BadRequest", engineName() + " refuses to open " + url + " from the extension's tabs API (" + msg + ")", "url_refused");
|
|
3739
|
+
return;
|
|
3740
|
+
}
|
|
3741
|
+
replyErr(cmdId, fallbackName, msg);
|
|
3742
|
+
}
|
|
3743
|
+
|
|
3744
|
+
// A surface relay that answered nothing is closed only when the engine
|
|
3745
|
+
// says no receiver exists. Any other lastError (a reply the engine could
|
|
3746
|
+
// not clone, a channel that closed) means the surface WAS open and ran the
|
|
3747
|
+
// expression, so the message quotes the engine instead of "not open".
|
|
3748
|
+
function surfaceReplyFailure(ctx, resp) {
|
|
3749
|
+
var le = null;
|
|
3750
|
+
try { le = g.chrome && g.chrome.runtime && g.chrome.runtime.lastError; } catch (e) {
|
|
3751
|
+
// Ignore
|
|
3752
|
+
}
|
|
3753
|
+
if (!le && resp) return null;
|
|
3754
|
+
var lem = le ? String((le && le.message) || le) : "";
|
|
3755
|
+
if (!lem || /receiving end does not exist|could not establish connection/i.test(lem)) {
|
|
3756
|
+
return {code: "surface_not_open", message: "surface '" + ctx + "' is not open (open it first: extension open " + ctx + ")"};
|
|
3757
|
+
}
|
|
3758
|
+
return {code: "surface_reply_failed", message: "surface '" + ctx + "' answered but the reply did not arrive: " + lem};
|
|
3759
|
+
}
|
|
3760
|
+
|
|
3761
|
+
// The page a surface lives in, as the manifest names it, so the background
|
|
3762
|
+
// can ask the engine whether that page is open at all.
|
|
3763
|
+
function surfacePagePath(ctx) {
|
|
3764
|
+
var m = null;
|
|
3765
|
+
try { m = g.chrome.runtime.getManifest(); } catch (e) { return null; }
|
|
3766
|
+
if (!m) return null;
|
|
3767
|
+
var p = null;
|
|
3768
|
+
if (ctx === "popup") p = (m.action && m.action.default_popup) || (m.browser_action && m.browser_action.default_popup);
|
|
3769
|
+
else if (ctx === "options") p = (m.options_ui && m.options_ui.page) || m.options_page;
|
|
3770
|
+
else if (ctx === "sidebar") p = (m.side_panel && m.side_panel.default_path) || (m.sidebar_action && m.sidebar_action.default_panel);
|
|
3771
|
+
else if (ctx === "newtab" || ctx === "history" || ctx === "bookmarks") p = m.chrome_url_overrides && m.chrome_url_overrides[ctx];
|
|
3772
|
+
// Character classes on purpose: this source is a template literal, and
|
|
3773
|
+
// a backslash escape in a regex would be eaten before it ever ran.
|
|
3774
|
+
return typeof p === "string" && p ? p.replace(/^[.]?[/]/, "") : null;
|
|
3775
|
+
}
|
|
3776
|
+
|
|
3777
|
+
// Chrome says "message port closed" both when the surface died mid-op and
|
|
3778
|
+
// when a bystander extension page (the options page, a new tab override)
|
|
3779
|
+
// received the message and passed, since a relay returns nothing for a
|
|
3780
|
+
// context that is not its own. Only the engine's open-context list tells
|
|
3781
|
+
// the two apart, so it is asked before a reply gets the blame. Engines
|
|
3782
|
+
// without getContexts keep the plain classification.
|
|
3783
|
+
function classifySurfaceFailure(ctx, resp, cb) {
|
|
3784
|
+
var relayErr = surfaceReplyFailure(ctx, resp);
|
|
3785
|
+
if (!relayErr || relayErr.code !== "surface_reply_failed" || !/message port closed/i.test(relayErr.message)) { cb(relayErr); return; }
|
|
3786
|
+
var rt = g.chrome && g.chrome.runtime;
|
|
3787
|
+
var page = surfacePagePath(ctx);
|
|
3788
|
+
if (!page || !rt || typeof rt.getContexts !== "function") { cb(relayErr); return; }
|
|
3789
|
+
var wanted = "";
|
|
3790
|
+
try { wanted = String(rt.getURL(page)).split(/[?#]/)[0]; } catch (e) { cb(relayErr); return; }
|
|
3791
|
+
var notOpen = {code: "surface_not_open", message: "surface '" + ctx + "' is not open (open it first: extension open " + ctx + ")"};
|
|
3792
|
+
try {
|
|
3793
|
+
Promise.resolve(rt.getContexts({})).then(function (list) {
|
|
3794
|
+
var open = false;
|
|
3795
|
+
for (var i = 0; i < (list || []).length; i++) {
|
|
3796
|
+
var u = list[i] && list[i].documentUrl;
|
|
3797
|
+
if (u && String(u).split(/[?#]/)[0] === wanted) { open = true; break; }
|
|
3798
|
+
}
|
|
3799
|
+
cb(open ? relayErr : notOpen);
|
|
3800
|
+
}, function () { cb(relayErr); });
|
|
3801
|
+
} catch (e) { cb(relayErr); }
|
|
3802
|
+
}
|
|
3803
|
+
|
|
3726
3804
|
// Name the refusal the browser wrote as prose, so a consumer branches on
|
|
3727
3805
|
// error.code instead of matching the engine's sentence.
|
|
3728
3806
|
function openRefusalCode(message) {
|
|
@@ -3813,9 +3891,15 @@ const BRIDGE_PRODUCER_SOURCE = `;(function () {
|
|
|
3813
3891
|
var cmdId = cmd.cmdId;
|
|
3814
3892
|
// content/page eval & inspect need a numeric tab id: resolve --url (or
|
|
3815
3893
|
// nothing) to a tab and re-dispatch with target.tabId filled in. (#51)
|
|
3816
|
-
if ((op === "eval" || op === "inspect") && (ctx === "content" || ctx === "page") && target.tabId == null) {
|
|
3894
|
+
if ((op === "eval" || op === "inspect" || op === "reload") && (ctx === "content" || ctx === "page") && target.tabId == null) {
|
|
3817
3895
|
resolveTargetTab(target, function (tabId, err) {
|
|
3818
3896
|
if (tabId == null) {
|
|
3897
|
+
// A filter that matched nothing, or no active tab, is a missing
|
|
3898
|
+
// target the caller can fix, not a missing feature.
|
|
3899
|
+
if (err && /^no open tab matches|^no active tab/i.test(String(err))) {
|
|
3900
|
+
replyErr(cmdId, "TargetNotFound", err, "tab_not_found");
|
|
3901
|
+
return;
|
|
3902
|
+
}
|
|
3819
3903
|
replyErr(cmdId, "Unsupported", err || ("eval/inspect in context " + ctx + " needs a --tab id, a --url to match, or an active tab"));
|
|
3820
3904
|
return;
|
|
3821
3905
|
}
|
|
@@ -3874,13 +3958,15 @@ const BRIDGE_PRODUCER_SOURCE = `;(function () {
|
|
|
3874
3958
|
chrome.runtime.sendMessage(
|
|
3875
3959
|
{__extjsEvalRequest: true, target: target, args: {expression: String(args.expression)}},
|
|
3876
3960
|
function (resp) {
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3961
|
+
classifySurfaceFailure(ctx, resp, function (relayErr) {
|
|
3962
|
+
if (relayErr) {
|
|
3963
|
+
replyErr(cmdId, "Unsupported", relayErr.message, relayErr.code);
|
|
3964
|
+
} else if (resp.ok) {
|
|
3965
|
+
replyOk(cmdId, resp.value);
|
|
3966
|
+
} else {
|
|
3967
|
+
replyErr(cmdId, (resp.error && resp.error.name) || "EvalError", (resp.error && resp.error.message) || "eval failed");
|
|
3968
|
+
}
|
|
3969
|
+
});
|
|
3884
3970
|
}
|
|
3885
3971
|
);
|
|
3886
3972
|
} else if (ctx === "content" || ctx === "page") {
|
|
@@ -3926,7 +4012,7 @@ const BRIDGE_PRODUCER_SOURCE = `;(function () {
|
|
|
3926
4012
|
} }, 50);
|
|
3927
4013
|
} else if (target.tabId) {
|
|
3928
4014
|
nsCall("tabs", "reload", [target.tabId], function (err) {
|
|
3929
|
-
if (err)
|
|
4015
|
+
if (err) replyTabsFailure(cmdId, err, "ReloadError");
|
|
3930
4016
|
else replyOk(cmdId, {reloaded: target.tabId});
|
|
3931
4017
|
});
|
|
3932
4018
|
} else {
|
|
@@ -4058,6 +4144,35 @@ const BRIDGE_PRODUCER_SOURCE = `;(function () {
|
|
|
4058
4144
|
});
|
|
4059
4145
|
return;
|
|
4060
4146
|
}
|
|
4147
|
+
// Navigation is a static tabs.* call, never an eval: an MV3 background
|
|
4148
|
+
// (Safari's included) refuses eval by CSP, so a url must reach a tab
|
|
4149
|
+
// through code that shipped with the bridge. A tabId targets that
|
|
4150
|
+
// tab, newTab opens one, and otherwise the active tab is reused.
|
|
4151
|
+
if (op === "tabs.navigate") {
|
|
4152
|
+
var navUrl = typeof args.url === "string" ? args.url : "";
|
|
4153
|
+
if (!navUrl) { replyErr(cmdId, "BadRequest", "tabs.navigate needs a url"); return; }
|
|
4154
|
+
var navDone = function (err, tab, created) {
|
|
4155
|
+
if (err) { replyTabsFailure(cmdId, err, "TabsError", navUrl); return; }
|
|
4156
|
+
replyOk(cmdId, {tabId: tab && tab.id != null ? tab.id : null, url: navUrl, created: !!created});
|
|
4157
|
+
};
|
|
4158
|
+
if (target.tabId != null) {
|
|
4159
|
+
nsCall("tabs", "update", [target.tabId, {url: navUrl}], function (err, tab) { navDone(err, tab, false); });
|
|
4160
|
+
return;
|
|
4161
|
+
}
|
|
4162
|
+
if (args.newTab) {
|
|
4163
|
+
nsCall("tabs", "create", [{url: navUrl, active: args.active !== false}], function (err, tab) { navDone(err, tab, true); });
|
|
4164
|
+
return;
|
|
4165
|
+
}
|
|
4166
|
+
nsCall("tabs", "query", [{active: true, currentWindow: true}], function (qErr, tabs) {
|
|
4167
|
+
var active = !qErr && tabs && tabs[0];
|
|
4168
|
+
if (active && active.id != null) {
|
|
4169
|
+
nsCall("tabs", "update", [active.id, {url: navUrl}], function (err, tab) { navDone(err, tab, false); });
|
|
4170
|
+
} else {
|
|
4171
|
+
nsCall("tabs", "create", [{url: navUrl}], function (err, tab) { navDone(err, tab, true); });
|
|
4172
|
+
}
|
|
4173
|
+
});
|
|
4174
|
+
return;
|
|
4175
|
+
}
|
|
4061
4176
|
if (op === "inspect") {
|
|
4062
4177
|
// Extract a DOM snapshot from the target page via chrome.scripting
|
|
4063
4178
|
// (CDP-free). Closed shadow roots need CDP; here we read open ones.
|
|
@@ -4107,13 +4222,15 @@ const BRIDGE_PRODUCER_SOURCE = `;(function () {
|
|
|
4107
4222
|
chrome.runtime.sendMessage(
|
|
4108
4223
|
{__extjsInspectRequest: true, target: target, args: args},
|
|
4109
4224
|
function (resp) {
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4225
|
+
classifySurfaceFailure(ctx, resp, function (relayErr) {
|
|
4226
|
+
if (relayErr) {
|
|
4227
|
+
replyErr(cmdId, "Unsupported", relayErr.message, relayErr.code);
|
|
4228
|
+
} else if (resp.ok) {
|
|
4229
|
+
replyOk(cmdId, resp.value);
|
|
4230
|
+
} else {
|
|
4231
|
+
replyErr(cmdId, (resp.error && resp.error.name) || "InspectError", (resp.error && resp.error.message) || "inspect failed");
|
|
4232
|
+
}
|
|
4233
|
+
});
|
|
4117
4234
|
}
|
|
4118
4235
|
);
|
|
4119
4236
|
} else if (ctx === "background") {
|
|
@@ -5014,19 +5131,33 @@ const BRIDGE_RELAY_SOURCE = `;(function () {
|
|
|
5014
5131
|
chrome.runtime.onMessage.addListener(function (msg, _sender, sendResponse) {
|
|
5015
5132
|
if (!msg || !msg.__extjsEvalRequest) return;
|
|
5016
5133
|
if (!msg.target || msg.target.context !== CONTEXT) return; // not for me
|
|
5017
|
-
|
|
5018
|
-
var value = (0, eval)(String((msg.args && msg.args.expression) || ""));
|
|
5134
|
+
var respondValue = function (value) {
|
|
5019
5135
|
try { sendResponse({ok: true, value: value}); }
|
|
5020
5136
|
catch (eSend) { sendResponse({ok: true, value: String(value)}); }
|
|
5021
|
-
}
|
|
5137
|
+
};
|
|
5138
|
+
var respondError = function (e) {
|
|
5022
5139
|
var emsg = (e && e.message) || String(e);
|
|
5023
5140
|
if (/Content Security Policy|unsafe-eval|call to eval/i.test(emsg)) {
|
|
5024
5141
|
sendResponse({ok: false, error: {name: "Unsupported", message: "eval of a string is blocked in the " + CONTEXT + " page by the MV3 extension CSP. Use extension inspect " + CONTEXT + " to read its DOM, or --context background on an MV2/Firefox build. Original: " + emsg}});
|
|
5025
5142
|
} else {
|
|
5026
5143
|
sendResponse({ok: false, error: {name: (e && e.name) || "EvalError", message: emsg}});
|
|
5027
5144
|
}
|
|
5145
|
+
};
|
|
5146
|
+
try {
|
|
5147
|
+
var value = (0, eval)(String((msg.args && msg.args.expression) || ""));
|
|
5148
|
+
// A promise is not structured-cloneable: Chrome throws on the reply
|
|
5149
|
+
// and Firefox drops it on the way out, which the background read as
|
|
5150
|
+
// "surface is not open" while the page kept running the expression.
|
|
5151
|
+
// Settle it first, the way the background eval already does.
|
|
5152
|
+
if (value && typeof value.then === "function") {
|
|
5153
|
+
value.then(respondValue, respondError);
|
|
5154
|
+
} else {
|
|
5155
|
+
respondValue(value);
|
|
5156
|
+
}
|
|
5157
|
+
} catch (e) {
|
|
5158
|
+
respondError(e);
|
|
5028
5159
|
}
|
|
5029
|
-
return true; // responded
|
|
5160
|
+
return true; // responded, possibly later
|
|
5030
5161
|
});
|
|
5031
5162
|
}
|
|
5032
5163
|
} catch (e) {
|