extension-develop 4.0.19 → 4.0.20

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.
Files changed (31) hide show
  1. package/dist/0~dev-server.mjs +8 -3
  2. package/dist/0~rspack-config.mjs +3759 -205
  3. package/dist/101.mjs +65 -2
  4. package/dist/349.mjs +2 -0
  5. package/dist/494.mjs +7 -1
  6. package/dist/839.mjs +164 -3619
  7. package/dist/887.mjs +5 -1
  8. package/dist/bridge-entry.d.ts +5 -4
  9. package/dist/bridge.mjs +25 -5
  10. package/dist/contract/codes.json +19 -2
  11. package/dist/contract/envelope.schema.json +10 -1
  12. package/dist/contract/golden.eval.target-not-found.json +14 -0
  13. package/dist/contract/golden.open.headed-window-required.json +14 -0
  14. package/dist/contracts-entry.d.ts +2 -0
  15. package/dist/contracts.mjs +12 -0
  16. package/dist/dev-server/control-bridge/consumer-client.d.ts +15 -1
  17. package/dist/dev-server/control-bridge/contracts.d.ts +6 -0
  18. package/dist/dev-server/control-bridge/producer-runtime.d.ts +1 -1
  19. package/dist/ensure-hmr-for-scripts.js +1 -1
  20. package/dist/ensure-hmr-for-scripts.mjs +1 -1
  21. package/dist/feature-scripts-content-script-wrapper.js +2 -2
  22. package/dist/feature-scripts-content-script-wrapper.mjs +2 -2
  23. package/dist/lib/extension-id.d.ts +9 -0
  24. package/dist/lib/messaging.d.ts +9 -0
  25. package/dist/lib/session-paths.d.ts +2 -0
  26. package/dist/plugin-playwright/index.d.ts +7 -0
  27. package/dist/plugin-web-extension/feature-icons/messages.d.ts +3 -0
  28. package/dist/plugin-web-extension/feature-manifest/manifest-lib/theme-values.d.ts +7 -0
  29. package/dist/plugin-web-extension/feature-manifest/messages.d.ts +1 -0
  30. package/dist/plugin-web-extension/feature-manifest/steps/validate-theme-values.d.ts +9 -0
  31. package/package.json +10 -1
package/dist/887.mjs CHANGED
@@ -3,6 +3,10 @@ import { controlTokenPath, legacyControlTokenPath } from "./494.mjs";
3
3
  import * as __rspack_external_node_fs_5ea92f0c from "node:fs";
4
4
  import * as __rspack_external_node_path_c5b9b54f from "node:path";
5
5
  import * as __rspack_external_node_crypto_9ba42079 from "node:crypto";
6
+ const REFUSAL_NEEDS_HEADED_WINDOW = 'needs_headed_window';
7
+ const REFUSAL_NEEDS_USER_GESTURE = 'needs_user_gesture';
8
+ const REFUSAL_SURFACE_NOT_OPEN = 'surface_not_open';
9
+ const REFUSAL_API_UNAVAILABLE = 'api_unavailable';
6
10
  const CONTROL_WS_PATH = '/extjs-control';
7
11
  function readPersistedControlPort(filePath) {
8
12
  try {
@@ -69,4 +73,4 @@ function clearControlToken(projectPath, browser) {
69
73
  });
70
74
  } catch {}
71
75
  }
72
- export { CONTROL_WS_PATH, clearControlToken, readControlToken, readPersistedControlPort, writeControlToken, writePersistedControlPort };
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 };
@@ -1,8 +1,9 @@
1
- export { BridgeConsumer, type ConsumerOptions, type ReadyContractInfo, readReadyContract } from './dev-server/control-bridge/consumer-client';
2
- export type { BridgeTarget, CommandOp, GapFrame, LogContext, LogEvent, LogLevel, ReadyFrame, ResultFrame } from './dev-server/control-bridge/contracts';
3
- export { CLOSE_BAD_HELLO, CLOSE_BAD_INSTANCE, CLOSE_CONTROL_UNAVAILABLE, CLOSE_SLOW_CONSUMER, CONTROL_ENVELOPE_VERSION, CONTROL_WS_PATH, LOG_EVENT_VERSION } from './dev-server/control-bridge/contracts';
1
+ export { BridgeConsumer, type ConsumerCloseInfo, type ConsumerOptions, type ReadyContractDocument, type ReadyContractInfo, readReadyContract, readReadyContractDocument } from './dev-server/control-bridge/consumer-client';
2
+ export type { BridgeRefusalCode, BridgeTarget, CommandOp, GapFrame, LogContext, LogEvent, LogLevel, ReadyFrame, ResultFrame } from './dev-server/control-bridge/contracts';
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';
4
4
  export { controlPortFilePath, readPersistedControlPort } from './dev-server/control-bridge/control-port-store';
5
5
  export { BridgeController, type CommandInput, type ControllerOptions } from './dev-server/control-bridge/controller-client';
6
6
  export { LOG_LEVEL_ORDER, type LogQuery, logLevelRank, matchesLogQuery, readLogEvents } from './dev-server/control-bridge/logs-query';
7
7
  export { controlTokenPath, readControlToken } from './dev-server/control-bridge/session-token';
8
- export { actionsPath, browserArtifactsDir, buildSummaryPath, eventsPath, logsPath, readyContractPath, sessionArtifactsRootDir, sessionStateDir } from './lib/session-paths';
8
+ export { actionsPath, browserArtifactsDir, browserDistDir, browserProfileRootDir, buildSummaryPath, eventsPath, logsPath, readyContractPath, sessionArtifactsRootDir, sessionStateDir } from './lib/session-paths';
9
+ export type { PlaywrightAutomationCommand, ReadyMetadata, ReadyStatus } from './plugin-playwright';
package/dist/bridge.mjs CHANGED
@@ -3,6 +3,16 @@ import { WebSocket } from "ws";
3
3
  import { readyContractPath, logsPath } from "./494.mjs";
4
4
  import { CONTROL_WS_PATH } from "./887.mjs";
5
5
  import * as __rspack_external_node_fs_5ea92f0c from "node:fs";
6
+ function readReadyContractDocument(projectPath, browser = 'chrome') {
7
+ const readyPath = readyContractPath(projectPath, browser);
8
+ try {
9
+ const parsed = JSON.parse(__rspack_external_node_fs_5ea92f0c.readFileSync(readyPath, 'utf-8'));
10
+ if (!parsed || 'object' != typeof parsed || Array.isArray(parsed)) return null;
11
+ return parsed;
12
+ } catch {
13
+ return null;
14
+ }
15
+ }
6
16
  function readReadyContract(projectPath, browser = 'chrome') {
7
17
  const readyPath = readyContractPath(projectPath, browser);
8
18
  try {
@@ -12,6 +22,8 @@ function readReadyContract(projectPath, browser = 'chrome') {
12
22
  controlPort: c.controlPort,
13
23
  instanceId: String(c.instanceId),
14
24
  runId: String(c.runId || ''),
25
+ schemaVersion: 'number' == typeof c.schemaVersion ? c.schemaVersion : void 0,
26
+ schema: 'number' == typeof c.schema ? c.schema : void 0,
15
27
  logsPath: c.logsPath,
16
28
  status: c.status,
17
29
  pid: 'number' == typeof c.pid ? c.pid : void 0,
@@ -34,9 +46,13 @@ class BridgeConsumer {
34
46
  maxBackoff = 5000;
35
47
  closed = false;
36
48
  timer = null;
49
+ lastCloseInfo = null;
37
50
  constructor(options){
38
51
  this.opts = options;
39
52
  }
53
+ get lastClose() {
54
+ return this.lastCloseInfo;
55
+ }
40
56
  start() {
41
57
  this.closed = false;
42
58
  this.connect();
@@ -86,9 +102,13 @@ class BridgeConsumer {
86
102
  else if ('log' === frame.type) this.opts.onLog?.(frame.event);
87
103
  else if ('gap' === frame.type) this.opts.onGap?.(frame);
88
104
  });
89
- socket.on('close', ()=>{
105
+ socket.on('close', (code, reason)=>{
90
106
  this.socket = null;
91
- this.opts.onClose?.();
107
+ this.lastCloseInfo = {
108
+ code,
109
+ reason: reason?.toString() ?? ''
110
+ };
111
+ this.opts.onClose?.(this.lastCloseInfo);
92
112
  if (this.opts.reconnect) this.scheduleReconnect();
93
113
  });
94
114
  socket.on('error', ()=>{
@@ -312,6 +332,6 @@ var bridge_entry_CLOSE_CONTROL_UNAVAILABLE = 4003;
312
332
  var bridge_entry_CLOSE_SLOW_CONSUMER = 4008;
313
333
  var bridge_entry_CONTROL_ENVELOPE_VERSION = 1;
314
334
  var bridge_entry_LOG_EVENT_VERSION = 1;
315
- export { CONTROL_WS_PATH, readControlToken, readPersistedControlPort } from "./887.mjs";
316
- export { actionsPath, browserArtifactsDir, buildSummaryPath, controlPortFilePath, controlTokenPath, eventsPath, logsPath, readyContractPath, sessionArtifactsRootDir, sessionStateDir } from "./494.mjs";
317
- 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, logLevelRank, matchesLogQuery, readLogEvents, readReadyContract };
335
+ export { CONTROL_WS_PATH, REFUSAL_API_UNAVAILABLE, REFUSAL_NEEDS_HEADED_WINDOW, REFUSAL_NEEDS_USER_GESTURE, REFUSAL_SURFACE_NOT_OPEN, readControlToken, readPersistedControlPort } from "./887.mjs";
336
+ export { actionsPath, browserArtifactsDir, browserDistDir, browserProfileRootDir, buildSummaryPath, controlPortFilePath, controlTokenPath, eventsPath, logsPath, readyContractPath, sessionArtifactsRootDir, sessionStateDir } from "./494.mjs";
337
+ 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, logLevelRank, matchesLogQuery, readLogEvents, readReadyContract, readReadyContractDocument };
@@ -423,6 +423,14 @@
423
423
  "area": "control",
424
424
  "summary": "No tab, frame, or context matched the requested target."
425
425
  },
426
+ "E_HEADED_WINDOW_REQUIRED": {
427
+ "area": "control",
428
+ "summary": "The surface needs a headed browser window this session does not have."
429
+ },
430
+ "E_USER_GESTURE_REQUIRED": {
431
+ "area": "control",
432
+ "summary": "The surface needs a real user gesture the caller cannot synthesize."
433
+ },
426
434
  "E_EVAL": {
427
435
  "area": "control",
428
436
  "summary": "The evaluated expression threw inside the page."
@@ -520,9 +528,18 @@
520
528
  "names": {
521
529
  "Timeout": "E_TIMEOUT",
522
530
  "Unavailable": "E_CONTROL_UNAVAILABLE",
523
- "Forbidden": ["E_TOKEN_MISSING", "E_EVAL_REFUSED"],
531
+ "Forbidden": "E_EVAL_REFUSED",
532
+ "EvalDisabled": "E_EVAL_REFUSED",
533
+ "EvalTokenMissing": "E_TOKEN_MISSING",
534
+ "EvalTokenMismatch": "E_EVAL_REFUSED",
535
+ "TargetNotFound": "E_TARGET_NOT_FOUND",
524
536
  "BadRequest": "E_ARGS",
525
- "Unsupported": ["E_TARGET_NOT_FOUND", "E_NOT_IMPLEMENTED"],
537
+ "Unsupported": [
538
+ "E_TARGET_NOT_FOUND",
539
+ "E_NOT_IMPLEMENTED",
540
+ "E_HEADED_WINDOW_REQUIRED",
541
+ "E_USER_GESTURE_REQUIRED"
542
+ ],
526
543
  "EvalError": "E_EVAL",
527
544
  "InspectError": "E_INSPECT",
528
545
  "StorageError": "E_STORAGE",
@@ -29,7 +29,16 @@
29
29
  "message": { "type": "string" },
30
30
  "name": { "type": "string" },
31
31
  "engine": { "type": "string" },
32
- "hint": { "type": "string" }
32
+ "hint": { "type": "string" },
33
+ "refs": {
34
+ "type": "object",
35
+ "properties": {
36
+ "flag": { "type": "string" },
37
+ "command": { "type": "string" },
38
+ "path": { "type": "string" },
39
+ "version": { "type": "string" }
40
+ }
41
+ }
33
42
  }
34
43
  }
35
44
  ]
@@ -0,0 +1,14 @@
1
+ {
2
+ "schema": 1,
3
+ "ok": false,
4
+ "command": "eval",
5
+ "status": "not-found",
6
+ "value": null,
7
+ "error": {
8
+ "code": "E_TARGET_NOT_FOUND",
9
+ "message": "the expression never executed in tab 12: no injectable frame returned a result (restricted page, or outside host_permissions)",
10
+ "name": "TargetNotFound",
11
+ "engine": "chromium"
12
+ },
13
+ "warnings": []
14
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "schema": 1,
3
+ "ok": false,
4
+ "command": "open",
5
+ "status": "failed",
6
+ "value": null,
7
+ "error": {
8
+ "code": "E_HEADED_WINDOW_REQUIRED",
9
+ "message": "openPopup: Could not find an active browser window.",
10
+ "name": "Unsupported",
11
+ "engine": "chromium"
12
+ },
13
+ "warnings": []
14
+ }
@@ -0,0 +1,2 @@
1
+ export * from './dev-server/control-bridge/contracts';
2
+ export type { LogLevelFilter, LogQuery } from './dev-server/control-bridge/logs-query';
@@ -0,0 +1,12 @@
1
+ const LOG_EVENT_VERSION = 1;
2
+ const CONTROL_ENVELOPE_VERSION = 1;
3
+ const REFUSAL_NEEDS_HEADED_WINDOW = 'needs_headed_window';
4
+ const REFUSAL_NEEDS_USER_GESTURE = 'needs_user_gesture';
5
+ const REFUSAL_SURFACE_NOT_OPEN = 'surface_not_open';
6
+ const REFUSAL_API_UNAVAILABLE = 'api_unavailable';
7
+ const CONTROL_WS_PATH = '/extjs-control';
8
+ const CLOSE_BAD_INSTANCE = 4001;
9
+ const CLOSE_BAD_HELLO = 4002;
10
+ const CLOSE_CONTROL_UNAVAILABLE = 4003;
11
+ 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 };
@@ -1,8 +1,13 @@
1
+ import type { ReadyMetadata } from '../../plugin-playwright';
1
2
  import { type GapFrame, type LogEvent, type ReadyFrame } from './contracts';
2
3
  export interface ReadyContractInfo {
3
4
  controlPort: number;
4
5
  instanceId: string;
5
6
  runId: string;
7
+ /** Ready-contract version, `ReadyMetadata['schemaVersion']` on current engines. */
8
+ schemaVersion?: number;
9
+ /** Result-envelope capability advertisement, `1` on current engines. */
10
+ schema?: number;
6
11
  logsPath?: string;
7
12
  status?: string;
8
13
  /** Dev-server pid; absent in pre-4.1 contracts. */
@@ -21,7 +26,13 @@ export interface ReadyContractInfo {
21
26
  /** Last contract write time (ISO). */
22
27
  ts?: string;
23
28
  }
29
+ export type ReadyContractDocument = Partial<ReadyMetadata> & Record<string, unknown>;
30
+ export declare function readReadyContractDocument(projectPath: string, browser?: string): ReadyContractDocument | null;
24
31
  export declare function readReadyContract(projectPath: string, browser?: string): ReadyContractInfo | null;
32
+ export interface ConsumerCloseInfo {
33
+ code: number;
34
+ reason: string;
35
+ }
25
36
  export interface ConsumerOptions {
26
37
  controlPort: number;
27
38
  instanceId: string;
@@ -31,7 +42,7 @@ export interface ConsumerOptions {
31
42
  onReady?: (frame: ReadyFrame) => void;
32
43
  onLog?: (event: LogEvent) => void;
33
44
  onGap?: (frame: GapFrame) => void;
34
- onClose?: () => void;
45
+ onClose?: (info: ConsumerCloseInfo) => void;
35
46
  }
36
47
  export declare class BridgeConsumer {
37
48
  private readonly opts;
@@ -40,7 +51,10 @@ export declare class BridgeConsumer {
40
51
  private readonly maxBackoff;
41
52
  private closed;
42
53
  private timer;
54
+ private lastCloseInfo;
43
55
  constructor(options: ConsumerOptions);
56
+ /** Why the last socket closed; null until a close has happened. */
57
+ get lastClose(): ConsumerCloseInfo | null;
44
58
  start(): void;
45
59
  close(): void;
46
60
  private url;
@@ -95,6 +95,11 @@ export interface CommandFrame {
95
95
  args?: Record<string, unknown>;
96
96
  timeoutMs?: number;
97
97
  }
98
+ export declare const REFUSAL_NEEDS_HEADED_WINDOW = "needs_headed_window";
99
+ export declare const REFUSAL_NEEDS_USER_GESTURE = "needs_user_gesture";
100
+ export declare const REFUSAL_SURFACE_NOT_OPEN = "surface_not_open";
101
+ export declare const REFUSAL_API_UNAVAILABLE = "api_unavailable";
102
+ export type BridgeRefusalCode = typeof REFUSAL_NEEDS_HEADED_WINDOW | typeof REFUSAL_NEEDS_USER_GESTURE | typeof REFUSAL_SURFACE_NOT_OPEN | typeof REFUSAL_API_UNAVAILABLE;
98
103
  export interface ResultFrame {
99
104
  type: 'result';
100
105
  cmdId: string;
@@ -107,6 +112,7 @@ export interface ResultFrame {
107
112
  message: string;
108
113
  stack?: string;
109
114
  engine?: string;
115
+ code?: BridgeRefusalCode;
110
116
  };
111
117
  }
112
118
  /**
@@ -1,4 +1,4 @@
1
- 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 g.chrome (not the hoisted chrome var below). This runs first.\n var actionClickedListeners = [];\n var commandListeners = [];\n if (g.chrome) {\n captureEvent(g.chrome.action && g.chrome.action.onClicked, actionClickedListeners);\n captureEvent(g.chrome.commands && g.chrome.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 MAX_QUEUE = 1000;\n var MAX_RESULT_BYTES = 256 * 1024;\n\n function nowId() {\n return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);\n }\n\n function engineName() {\n try {\n return (typeof navigator !== \"undefined\" && /firefox/i.test(navigator.userAgent)) ? \"firefox\" : \"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) {\n var frame = {type: \"result\", cmdId: cmdId, ok: false, error: {name: name, message: String(message), engine: engineName()}};\n try { socket && socket.send(JSON.stringify(frame)); } catch (e) {\n // Ignore\n }\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 MV3 service worker by CSP. Use --context page --tab <id> (eval runs in the page's MAIN world), or run on an MV2/Firefox build. 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, \"EvalError\", (err && err.message) || err); return; }\n var frame = res && res[0] ? res[0].result : undefined;\n if (!frame || frame.__extjsEval !== 1) {\n replyErr(cmdId, \"EvalError\", \"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 + \")\");\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) replyErr(cmdId, \"Unsupported\", \"openPopup: \" + ((err && err.message) || err));\n else replyOk(cmdId, {opened: \"popup\"});\n });\n } else { replyErr(cmdId, \"Unsupported\", \"action.openPopup not available\"); }\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 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) replyErr(cmdId, \"Unsupported\", \"sidePanel.open: \" + ((err && err.message) || err));\n else replyOk(cmdId, {opened: \"sidebar\"});\n });\n });\n } else { replyErr(cmdId, \"Unsupported\", \"sidePanel not available (engine: \" + engineName() + \")\"); }\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) replyErr(cmdId, \"Unsupported\", \"openPopup: \" + ((err && err.message) || err));\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() + \")\"); }\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, \"InspectError\", (err && err.message) || err); return; }\n var snap = res && res[0] ? res[0].result : undefined;\n if (snap == null) { replyErr(cmdId, \"InspectError\", \"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 + \")\");\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 function reinjectContentScriptEntry(entry) {\n var chrome = g.chrome;\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 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 function reinjectContentScripts(onDone) {\n var chrome = g.chrome;\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]);\n 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 };\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 // Dev-loop reload without the CDP controller: content-scripts re-inject in\n // place into open tabs; service-worker/full/manifest restart the extension.\n function performDevReload(type, onDone) {\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 if (reinjectContentScripts(onDone)) return;\n }\n\n fullReload();\n }\n\n // One server-built label travels the ReloadFrame; both announcement\n // surfaces fire HERE, next to the actual reload action, so they stay true.\n\n // Stable IDs of the bundled extension-js-devtools companion: Chromium pins\n // via the manifest \"key\"; Firefox via browser_specific_settings.gecko.id.\n var DEVTOOLS_COMPANION_ID_CHROMIUM = \"kgdaecdpfkikjncaalnmmnjjfpofkcbl\";\n var DEVTOOLS_COMPANION_ID_FIREFOX = \"devtools@extension.js\";\n\n function notifyDevtoolsCompanion(phase, label, kind) {\n try {\n var chrome = g.chrome;\n if (!chrome || !chrome.runtime || typeof chrome.runtime.sendMessage !== \"function\") return;\n var id = engineName() === \"firefox\" ? DEVTOOLS_COMPANION_ID_FIREFOX : DEVTOOLS_COMPANION_ID_CHROMIUM;\n chrome.runtime.sendMessage(\n id,\n {type: \"extjs-dev-reload-state\", phase: phase, label: label || \"\", kind: kind || \"\", instanceId: INSTANCE_ID},\n function () { noopLastError(); }\n );\n } catch (e) {\n // Ignore\n }\n }\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 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 notifyDevtoolsCompanion(\"reloading\", label, kind);\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 () {\n // Only the content-scripts path confirms from here; full/SW reloads\n // are confirmed by the devtools companion's chrome.management listeners.\n notifyDevtoolsCompanion(\"reloaded\", label, kind);\n });\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 }\n\n function schedule() {\n var delay = backoff;\n backoff = Math.min(backoff * 2, MAX_BACKOFF);\n try { setTimeout(connect, delay); } 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 try {\n socket.send(JSON.stringify({type: \"hello\", v: 1, role: \"producer\", instanceId: INSTANCE_ID}));\n } catch (e) {\n // Ignore\n }\n flush();\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 // Chrome never injects manifest content scripts into already-open tabs, so\n // fire one reinject on onInstalled (which skips idle-stop SW wakes).\n try {\n var rtBoot = g.chrome;\n if (rtBoot && rtBoot.runtime && rtBoot.runtime.onInstalled) {\n rtBoot.runtime.onInstalled.addListener(function () {\n try {\n setTimeout(function () {\n try { reinjectContentScripts(); } catch (e) {\n // Ignore\n }\n }, 250);\n } 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 setTimeout(function () {\n try { reinjectContentScripts(); } catch (e) {\n // Ignore\n }\n }, 250);\n });\n }\n } catch (e) {\n // Ignore\n }\n\n connect();\n } catch (e) {\n // Ignore\n }\n})();\n";
1
+ 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 g.chrome (not the hoisted chrome var below). This runs first.\n var actionClickedListeners = [];\n var commandListeners = [];\n if (g.chrome) {\n captureEvent(g.chrome.action && g.chrome.action.onClicked, actionClickedListeners);\n captureEvent(g.chrome.commands && g.chrome.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 MAX_QUEUE = 1000;\n var MAX_RESULT_BYTES = 256 * 1024;\n\n function nowId() {\n return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);\n }\n\n function engineName() {\n try {\n return (typeof navigator !== \"undefined\" && /firefox/i.test(navigator.userAgent)) ? \"firefox\" : \"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 MV3 service worker by CSP. Use --context page --tab <id> (eval runs in the page's MAIN world), or run on an MV2/Firefox build. 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 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 function reinjectContentScriptEntry(entry) {\n var chrome = g.chrome;\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 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 function reinjectContentScripts(onDone) {\n var chrome = g.chrome;\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]);\n 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 };\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 // Dev-loop reload without the CDP controller: content-scripts re-inject in\n // place into open tabs; service-worker/full/manifest restart the extension.\n function performDevReload(type, onDone) {\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 if (reinjectContentScripts(onDone)) return;\n }\n\n fullReload();\n }\n\n // One server-built label travels the ReloadFrame; both announcement\n // surfaces fire HERE, next to the actual reload action, so they stay true.\n\n // Stable IDs of the bundled extension-js-devtools companion: Chromium pins\n // via the manifest \"key\"; Firefox via browser_specific_settings.gecko.id.\n var DEVTOOLS_COMPANION_ID_CHROMIUM = \"kgdaecdpfkikjncaalnmmnjjfpofkcbl\";\n var DEVTOOLS_COMPANION_ID_FIREFOX = \"devtools@extension.js\";\n\n function notifyDevtoolsCompanion(phase, label, kind) {\n try {\n var chrome = g.chrome;\n if (!chrome || !chrome.runtime || typeof chrome.runtime.sendMessage !== \"function\") return;\n var id = engineName() === \"firefox\" ? DEVTOOLS_COMPANION_ID_FIREFOX : DEVTOOLS_COMPANION_ID_CHROMIUM;\n chrome.runtime.sendMessage(\n id,\n {type: \"extjs-dev-reload-state\", phase: phase, label: label || \"\", kind: kind || \"\", instanceId: INSTANCE_ID},\n function () { noopLastError(); }\n );\n } catch (e) {\n // Ignore\n }\n }\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 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 notifyDevtoolsCompanion(\"reloading\", label, kind);\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 () {\n // Only the content-scripts path confirms from here; full/SW reloads\n // are confirmed by the devtools companion's chrome.management listeners.\n notifyDevtoolsCompanion(\"reloaded\", label, kind);\n });\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 }\n\n function schedule() {\n var delay = backoff;\n backoff = Math.min(backoff * 2, MAX_BACKOFF);\n try { setTimeout(connect, delay); } 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 try {\n socket.send(JSON.stringify({type: \"hello\", v: 1, role: \"producer\", instanceId: INSTANCE_ID}));\n } catch (e) {\n // Ignore\n }\n flush();\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 // Chrome never injects manifest content scripts into already-open tabs, so\n // fire one reinject on onInstalled (which skips idle-stop SW wakes).\n try {\n var rtBoot = g.chrome;\n if (rtBoot && rtBoot.runtime && rtBoot.runtime.onInstalled) {\n rtBoot.runtime.onInstalled.addListener(function () {\n try {\n setTimeout(function () {\n try { reinjectContentScripts(); } catch (e) {\n // Ignore\n }\n }, 250);\n } 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 setTimeout(function () {\n try { reinjectContentScripts(); } catch (e) {\n // Ignore\n }\n }, 250);\n });\n }\n } catch (e) {\n // Ignore\n }\n\n connect();\n } catch (e) {\n // Ignore\n }\n})();\n";
2
2
  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 try {\n var value = (0, eval)(String((msg.args && msg.args.expression) || \"\"));\n try { sendResponse({ok: true, value: value}); }\n catch (eSend) { sendResponse({ok: true, value: String(value)}); }\n } catch (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 return true; // responded\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";
3
3
  export interface BuildRelayOptions {
4
4
  context: string;
@@ -3,9 +3,9 @@ import node_fs from "node:fs";
3
3
  import node_path from "node:path";
4
4
  import { init, parse } from "es-module-lexer";
5
5
  import { validate } from "schema-utils";
6
- import { toResourceKey } from "./93.mjs";
7
6
  import { EXTENSIONJS_CONTENT_SCRIPT_LAYER } from "./291.mjs";
8
7
  import { stripBom } from "./23.mjs";
8
+ import { toResourceKey } from "./93.mjs";
9
9
  const schema = {
10
10
  type: 'object',
11
11
  properties: {
@@ -3,9 +3,9 @@ import node_fs from "node:fs";
3
3
  import node_path from "node:path";
4
4
  import { init, parse } from "es-module-lexer";
5
5
  import { validate } from "schema-utils";
6
- import { toResourceKey } from "./93.mjs";
7
6
  import { EXTENSIONJS_CONTENT_SCRIPT_LAYER } from "./291.mjs";
8
7
  import { stripBom } from "./23.mjs";
8
+ import { toResourceKey } from "./93.mjs";
9
9
  const schema = {
10
10
  type: 'object',
11
11
  properties: {
@@ -3,10 +3,10 @@ import node_fs from "node:fs";
3
3
  import node_path from "node:path";
4
4
  import { parse } from "es-module-lexer/js";
5
5
  import { validate } from "schema-utils";
6
- import { findNearestProjectManifestSync } from "./80.mjs";
7
- import { canonicalizeDir, canonicalizeResourcePath } from "./93.mjs";
8
6
  import { reservedScriptsFolder } from "./266.mjs";
9
7
  import { stripBom } from "./23.mjs";
8
+ import { canonicalizeDir, canonicalizeResourcePath } from "./93.mjs";
9
+ import { findNearestProjectManifestSync } from "./80.mjs";
10
10
  import { getCanonicalContentScriptEntryName, CANONICAL_CONTENT_SCRIPT_ENTRY_PREFIX } from "./291.mjs";
11
11
  const schema = {
12
12
  type: 'object',
@@ -3,10 +3,10 @@ import node_fs from "node:fs";
3
3
  import node_path from "node:path";
4
4
  import { parse } from "es-module-lexer/js";
5
5
  import { validate } from "schema-utils";
6
- import { findNearestProjectManifestSync } from "./80.mjs";
7
- import { canonicalizeDir, canonicalizeResourcePath } from "./93.mjs";
8
6
  import { reservedScriptsFolder } from "./266.mjs";
9
7
  import { stripBom } from "./23.mjs";
8
+ import { canonicalizeDir, canonicalizeResourcePath } from "./93.mjs";
9
+ import { findNearestProjectManifestSync } from "./80.mjs";
10
10
  import { getCanonicalContentScriptEntryName, CANONICAL_CONTENT_SCRIPT_ENTRY_PREFIX } from "./291.mjs";
11
11
  const schema = {
12
12
  type: 'object',
@@ -0,0 +1,9 @@
1
+ export interface ManagedExtensionRecord {
2
+ path: string;
3
+ id?: string;
4
+ }
5
+ export declare function chromiumExtensionIdFromKey(manifestKey: string): string;
6
+ export declare function chromiumExtensionIdFromPath(extensionDir: string): string;
7
+ export declare function chromiumExtensionId(extensionDir: string): string;
8
+ export declare function geckoExtensionId(extensionDir: string): string | undefined;
9
+ export declare function managedExtensionRecords(browser: string, extensionDirs: string[]): ManagedExtensionRecord[];