extension-develop 4.1.34 → 4.1.35

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 (78) hide show
  1. package/dist/206~0.mjs +33 -0
  2. package/dist/363~0.mjs +1 -1
  3. package/dist/469~0.mjs +98 -16
  4. package/dist/519~0.mjs +12 -1
  5. package/dist/771~0.mjs +49 -8
  6. package/dist/832~0.mjs +127 -25
  7. package/dist/840~0.mjs +215 -54
  8. package/dist/846~0.mjs +16 -14
  9. package/dist/852~0.mjs +2 -0
  10. package/dist/88~0.mjs +10 -1
  11. package/dist/command-preview.d.ts +2 -0
  12. package/dist/contract/codes.json +9 -0
  13. package/dist/contracts-entry.d.ts +1 -1
  14. package/dist/contracts.mjs +109 -1
  15. package/dist/css-parse-guard-loader.mjs +7 -2
  16. package/dist/dev-server/control-bridge/log-rule.d.ts +38 -0
  17. package/dist/dev-server/control-bridge/logs-query.d.ts +2 -55
  18. package/dist/dev-server/control-bridge/producer-runtime.d.ts +1 -1
  19. package/dist/dev-server/html-refresh.d.ts +10 -0
  20. package/dist/dev-server/port-manager.d.ts +14 -0
  21. package/dist/dev-server/reserved-listen.d.ts +9 -0
  22. package/dist/dev-server/session-restart.d.ts +1 -1
  23. package/dist/dev-server~0.mjs +155 -30
  24. package/dist/extension-js-devtools/build-stamp.json +2 -2
  25. package/dist/extension-js-devtools/chrome/background/service_worker.js +2 -2
  26. package/dist/extension-js-devtools/chrome/pages/welcome.css +1 -1
  27. package/dist/extension-js-devtools/chromium/background/service_worker.js +2 -2
  28. package/dist/extension-js-devtools/chromium/pages/welcome.css +1 -1
  29. package/dist/extension-js-devtools/edge/background/service_worker.js +2 -2
  30. package/dist/extension-js-devtools/edge/pages/welcome.css +1 -1
  31. package/dist/extension-js-devtools/firefox/background/scripts.js +2 -2
  32. package/dist/extension-js-devtools/firefox/pages/welcome.css +1 -1
  33. package/dist/extension-js-theme/build-stamp.json +2 -2
  34. package/dist/feature-scripts-classic-concat-loader.js +45 -2
  35. package/dist/feature-scripts-classic-concat-loader.mjs +45 -2
  36. package/dist/feature-scripts-classic-traced-globals-loader.mjs +111 -0
  37. package/dist/feature-scripts-content-script-wrapper.js +10 -6
  38. package/dist/feature-scripts-content-script-wrapper.mjs +10 -6
  39. package/dist/lib/config-loader.d.ts +1 -0
  40. package/dist/lib/css-minimizer-options.d.ts +8 -0
  41. package/dist/lib/develop-context.d.ts +1 -1
  42. package/dist/lib/live-dev-session.d.ts +5 -0
  43. package/dist/lib/messages.d.ts +9 -1
  44. package/dist/lib/messaging.d.ts +2 -0
  45. package/dist/lib/package-manager.d.ts +7 -1
  46. package/dist/lib/paths.d.ts +1 -0
  47. package/dist/lib/project.d.ts +10 -2
  48. package/dist/lib/remote-archive.d.ts +11 -0
  49. package/dist/lib/rename-with-retry.d.ts +6 -0
  50. package/dist/lib/session-paths.d.ts +1 -0
  51. package/dist/lib/zip.d.ts +5 -1
  52. package/dist/plugin-browsers/index.d.ts +2 -0
  53. package/dist/plugin-css/css-in-content-script-loader.d.ts +1 -1
  54. package/dist/plugin-css/css-in-html-loader.d.ts +1 -1
  55. package/dist/plugin-css/css-lib/build-css-rules.d.ts +2 -1
  56. package/dist/plugin-css/css-lib/verbatim-css.d.ts +1 -0
  57. package/dist/plugin-js-frameworks/js-tools/typescript.d.ts +1 -0
  58. package/dist/plugin-playwright/index.d.ts +3 -0
  59. package/dist/plugin-special-folders/messages.d.ts +1 -0
  60. package/dist/plugin-special-folders/warn-upon-folder-changes.d.ts +1 -1
  61. package/dist/plugin-web-extension/feature-html/html-lib/options-dialog-floor.d.ts +7 -0
  62. package/dist/plugin-web-extension/feature-html/html-lib/patch-html.d.ts +3 -1
  63. package/dist/plugin-web-extension/feature-html/steps/update-html-file.d.ts +1 -0
  64. package/dist/plugin-web-extension/feature-manifest/steps/apply-dev-defaults-lib/dev-injected-hosts.d.ts +4 -1
  65. package/dist/plugin-web-extension/feature-scripts/messages.d.ts +1 -0
  66. package/dist/plugin-web-extension/feature-scripts/steps/add-content-script-wrapper/classic-concat-loader.d.ts +1 -0
  67. package/dist/plugin-web-extension/feature-scripts/steps/add-content-script-wrapper/content-script-wrapper.d.ts +1 -0
  68. package/dist/plugin-web-extension/feature-scripts/steps/add-content-script-wrapper/index.d.ts +1 -0
  69. package/dist/plugin-web-extension/feature-scripts/steps/classic-traced-globals-loader.d.ts +7 -0
  70. package/dist/plugin-web-extension/feature-scripts/steps/warn-unshipped-runtime-assets.d.ts +32 -0
  71. package/dist/plugin-web-extension/feature-web-resources/web-resources-lib/messages.d.ts +1 -0
  72. package/dist/plugin-web-extension/shared/discover-runtime-surfaces.d.ts +2 -0
  73. package/dist/plugin-web-extension/shared/script-graph.d.ts +4 -0
  74. package/dist/rspack-config~0.mjs +799 -196
  75. package/dist/types.d.ts +7 -1
  76. package/dist/zip~0.mjs +14 -7
  77. package/package.json +3 -3
  78. package/dist/plugin-css/css-lib/is-content-script.d.ts +0 -2
@@ -1,5 +1,5 @@
1
1
  export declare const CONTROL_PORT_ASSET_NAME = "extension-js-control.json";
2
- export declare const BRIDGE_PRODUCER_SOURCE = ";(function () {\n try {\n var g = (typeof globalThis === \"object\" && globalThis) ? globalThis : this;\n if (!g || g.__extjsBridgeProducerInstalled) return;\n\n var PORT = parseInt(\"__EXTJS_CONTROL_PORT__\", 10);\n var INSTANCE_ID = \"__EXTJS_INSTANCE_ID__\";\n var CONTEXT = \"__EXTJS_CONTEXT__\";\n // Connectable host of the dev-server control WS. Baked from the resolved\n // connectable host (loopback locally; the public host for remote/devcontainer).\n var HOST = \"__EXTJS_CONTROL_HOST__\";\n if (!HOST || HOST.indexOf(\"__EXTJS\") === 0) HOST = \"127.0.0.1\";\n if (!PORT || PORT < 1) return;\n\n var WS = g.WebSocket;\n if (typeof WS !== \"function\") return;\n\n var consoleRef = g.console || {};\n g.__extjsBridgeProducerInstalled = true;\n\n // Capture extension event listeners at install time: these wraps run\n // BEFORE user code, and no platform API exists to dispatch these events.\n function captureEvent(event, sink) {\n try {\n if (!event || typeof event.addListener !== \"function\") return;\n var origAdd = event.addListener.bind(event);\n event.addListener = function (cb) {\n if (typeof cb === \"function\" && sink.indexOf(cb) === -1) sink.push(cb);\n return origAdd(cb);\n };\n if (typeof event.removeListener === \"function\") {\n var origRemove = event.removeListener.bind(event);\n event.removeListener = function (cb) {\n var i = sink.indexOf(cb);\n if (i !== -1) sink.splice(i, 1);\n return origRemove(cb);\n };\n }\n } catch (e) { /* non-fatal: trigger falls back to its no-listener reply */ }\n }\n\n // Use the raw globals (not the hoisted chrome var below). This runs first.\n // On Gecko browser.* is a DISTINCT wrapper object: hook BOTH namespaces.\n // One sink per manifest key, so a replay fires only the entry it clicks.\n var ACTION_KEYS = [\"action\", \"browser_action\", \"page_action\"];\n var ACTION_NAMESPACES = {action: \"action\", browser_action: \"browserAction\", page_action: \"pageAction\"};\n var actionClickedListeners = {action: [], browser_action: [], page_action: []};\n var commandListeners = [];\n var capturedEvents = [];\n function captureEventOnce(event, sink) {\n // Polyfilled extensions expose ONE event object on both namespaces:\n // wrap it once, and the sink dedupes callbacks shared across wrappers.\n if (!event || capturedEvents.indexOf(event) !== -1) return;\n capturedEvents.push(event);\n captureEvent(event, sink);\n }\n var captureNamespaces = [g.chrome, g.browser];\n for (var cn = 0; cn < captureNamespaces.length; cn++) {\n var capNS = captureNamespaces[cn];\n if (!capNS) continue;\n for (var ak = 0; ak < ACTION_KEYS.length; ak++) {\n var akNS = capNS[ACTION_NAMESPACES[ACTION_KEYS[ak]]];\n captureEventOnce(akNS && akNS.onClicked, actionClickedListeners[ACTION_KEYS[ak]]);\n }\n captureEventOnce(capNS.commands && capNS.commands.onCommand, commandListeners);\n }\n\n var LEVELS = [\"log\", \"info\", \"warn\", \"error\", \"debug\", \"trace\"];\n // The dev server's HMR client logs its own chatter (\"[HMR] Waiting for\n // update signal from WDS...\") through the extension's console; it is not\n // the extension's output. Its warnings and errors still ship.\n function isDevServerChatter(level, args) {\n if (level === \"warn\" || level === \"error\") return false;\n var first = args && args[0];\n return typeof first === \"string\" && /^\\[(?:HMR|webpack-dev-server|rspack-dev-server)\\] /.test(first);\n }\n var socket = null;\n var open = false;\n var queue = [];\n var backoff = 250;\n var MAX_BACKOFF = 5000;\n var connectFailures = 0;\n var MAX_QUEUE = 1000;\n var MAX_RESULT_BYTES = 256 * 1024;\n // Where error frames are parked so a context that dies before its socket\n // opens does not take them along. Capped, and old entries are dropped.\n var PENDING_ERRORS_KEY = \"__extjsPendingErrors\";\n var MAX_PENDING_ERRORS = 20;\n var PENDING_ERROR_TTL = 300000;\n\n function nowId() {\n return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);\n }\n\n // The extension's own URL scheme names the engine outright, which beats a\n // user agent where Chrome and Safari both say \"Safari\".\n function engineName() {\n try {\n var scheme = (typeof location !== \"undefined\" && location.protocol) || \"\";\n if (scheme.indexOf(\"safari-web-extension\") === 0) return \"webkit\";\n if (scheme.indexOf(\"moz-extension\") === 0) return \"firefox\";\n if (scheme.indexOf(\"chrome-extension\") === 0) return \"chromium\";\n var ua = (typeof navigator !== \"undefined\" && navigator.userAgent) || \"\";\n if (/firefox/i.test(ua)) return \"firefox\";\n if (/safari/i.test(ua) && !/chrome|chromium|edg/i.test(ua)) return \"webkit\";\n return \"chromium\";\n } catch (e) { return \"chromium\"; }\n }\n\n function safeValue(v) {\n try {\n var s = JSON.stringify(v);\n if (s === undefined) return {value: undefined, truncated: false};\n if (s.length > MAX_RESULT_BYTES) {\n return {value: {__type: \"truncated\", preview: s.slice(0, 1024)}, truncated: true};\n }\n return {value: JSON.parse(s), truncated: false};\n } catch (e) {\n return {value: {__type: \"unserializable\", preview: String(v).slice(0, 512)}, truncated: false};\n }\n }\n\n function replyOk(cmdId, value) {\n var s = safeValue(value);\n var frame = {type: \"result\", cmdId: cmdId, ok: true, value: s.value};\n if (s.truncated) frame.truncated = true;\n try { socket && socket.send(JSON.stringify(frame)); } catch (e) {\n // Ignore\n }\n }\n\n function replyErr(cmdId, name, message, code) {\n var frame = {type: \"result\", cmdId: cmdId, ok: false, error: {name: name, message: String(message), engine: engineName()}};\n if (code) frame.error.code = code;\n try { socket && socket.send(JSON.stringify(frame)); } catch (e) {\n // Ignore\n }\n }\n\n // A tabs.* failure the caller can act on gets a name and a refusal code:\n // a tab id nobody has is TargetNotFound, and a url the engine refuses by\n // rule (Gecko's \"Illegal URL\", Chromium's javascript: ban) is BadRequest.\n // Anything else keeps the op's own error name.\n function replyTabsFailure(cmdId, err, fallbackName, url) {\n var msg = String((err && err.message) || err);\n if (/no tab with id|invalid tab id/i.test(msg)) {\n replyErr(cmdId, \"TargetNotFound\", msg, \"tab_not_found\");\n return;\n }\n if (url && /illegal url|javascript urls are not allowed|invalid url/i.test(msg)) {\n replyErr(cmdId, \"BadRequest\", engineName() + \" refuses to open \" + url + \" from the extension's tabs API (\" + msg + \")\", \"url_refused\");\n return;\n }\n replyErr(cmdId, fallbackName, msg);\n }\n\n // A surface relay that answered nothing is closed only when the engine\n // says no receiver exists. Any other lastError (a reply the engine could\n // not clone, a channel that closed) means the surface WAS open and ran the\n // expression, so the message quotes the engine instead of \"not open\".\n function surfaceReplyFailure(ctx, resp) {\n var le = null;\n try { le = g.chrome && g.chrome.runtime && g.chrome.runtime.lastError; } catch (e) {\n // Ignore\n }\n if (!le && resp) return null;\n var lem = le ? String((le && le.message) || le) : \"\";\n if (!lem || /receiving end does not exist|could not establish connection/i.test(lem)) {\n return {code: \"surface_not_open\", message: \"surface '\" + ctx + \"' is not open (open it first: extension open \" + ctx + \")\"};\n }\n return {code: \"surface_reply_failed\", message: \"surface '\" + ctx + \"' answered but the reply did not arrive: \" + lem};\n }\n\n // The page a surface lives in, as the manifest names it, so the background\n // can ask the engine whether that page is open at all.\n function surfacePagePath(ctx) {\n var m = null;\n try { m = g.chrome.runtime.getManifest(); } catch (e) { return null; }\n if (!m) return null;\n var p = null;\n if (ctx === \"popup\") p = (m.action && m.action.default_popup) || (m.browser_action && m.browser_action.default_popup) || (m.page_action && m.page_action.default_popup);\n else if (ctx === \"options\") p = (m.options_ui && m.options_ui.page) || m.options_page;\n else if (ctx === \"sidebar\") p = (m.side_panel && m.side_panel.default_path) || (m.sidebar_action && m.sidebar_action.default_panel);\n else if (ctx === \"newtab\" || ctx === \"history\" || ctx === \"bookmarks\") p = m.chrome_url_overrides && m.chrome_url_overrides[ctx];\n // Character classes on purpose: this source is a template literal, and\n // a backslash escape in a regex would be eaten before it ever ran.\n return typeof p === \"string\" && p ? p.replace(/^[.]?[/]/, \"\") : null;\n }\n\n // Chrome says \"message port closed\" both when the surface died mid-op and\n // when a bystander extension page (the options page, a new tab override)\n // received the message and passed, since a relay returns nothing for a\n // context that is not its own. Only the engine's open-context list tells\n // the two apart, so it is asked before a reply gets the blame. Engines\n // without getContexts keep the plain classification.\n function classifySurfaceFailure(ctx, resp, cb) {\n var relayErr = surfaceReplyFailure(ctx, resp);\n if (!relayErr || relayErr.code !== \"surface_reply_failed\" || !/message port closed/i.test(relayErr.message)) { cb(relayErr); return; }\n var rt = g.chrome && g.chrome.runtime;\n var page = surfacePagePath(ctx);\n if (!page || !rt || typeof rt.getContexts !== \"function\") { cb(relayErr); return; }\n var wanted = \"\";\n try { wanted = String(rt.getURL(page)).split(/[?#]/)[0]; } catch (e) { cb(relayErr); return; }\n var notOpen = {code: \"surface_not_open\", message: \"surface '\" + ctx + \"' is not open (open it first: extension open \" + ctx + \")\"};\n try {\n Promise.resolve(rt.getContexts({})).then(function (list) {\n var open = false;\n for (var i = 0; i < (list || []).length; i++) {\n var u = list[i] && list[i].documentUrl;\n if (u && String(u).split(/[?#]/)[0] === wanted) { open = true; break; }\n }\n cb(open ? relayErr : notOpen);\n }, function () { cb(relayErr); });\n } catch (e) { cb(relayErr); }\n }\n\n // Every engine's wording for \"this document's CSP forbids eval\". Gecko\n // says \"call to eval() blocked by CSP\", Chromium quotes the directive.\n function cspBlocksEval(message) {\n return /call to eval\\(\\)|blocked by (?:the )?CSP|Content Security Policy|unsafe-eval/i.test(String(message == null ? \"\" : message));\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 // Only a tab the engine could not find is a missing target. Anything else\n // happened in a tab that exists, a value it could not clone back above all.\n // Firefox refuses to script a moz-extension:// page or a page outside\n // host access with the tab right there, so that refusal is named too.\n function injectionErrorName(err) {\n var msg = String((err && err.message) || err || \"\");\n if (/no tab with id|invalid tab id/i.test(msg)) return \"TargetNotFound\";\n if (/clon/i.test(msg)) return \"DataCloneError\";\n if (injectionRefused(msg)) return \"InjectionRefused\";\n return \"ExecutorError\";\n }\n\n function injectionRefused(msg) {\n return /missing host permission|cannot access contents|cannot be scripted|cannot access a chrome|extensions gallery/i.test(String(msg));\n }\n\n function replyInjectionErr(cmdId, err) {\n var msg = (err && err.message) || err;\n replyErr(cmdId, injectionErrorName(err), msg, injectionRefused(msg) ? \"injection_refused\" : undefined);\n }\n\n function executeCommand(cmd) {\n var op = cmd.op, target = cmd.target || {}, args = cmd.args || {};\n var ctx = target.context || \"background\";\n var chrome = g.chrome;\n var cmdId = cmd.cmdId;\n // content/page eval & inspect need a numeric tab id: resolve --url (or\n // nothing) to a tab and re-dispatch with target.tabId filled in. (#51)\n if ((op === \"eval\" || op === \"inspect\" || op === \"reload\") && (ctx === \"content\" || ctx === \"page\") && target.tabId == null) {\n resolveTargetTab(target, function (tabId, err) {\n if (tabId == null) {\n // A filter that matched nothing, or no active tab, is a missing\n // target the caller can fix, not a missing feature.\n if (err && /^no open tab matches|^no active tab/i.test(String(err))) {\n replyErr(cmdId, \"TargetNotFound\", err, \"tab_not_found\");\n return;\n }\n replyErr(cmdId, \"Unsupported\", err || (\"eval/inspect in context \" + ctx + \" needs a --tab id, a --url to match, or an active tab\"));\n return;\n }\n var next = {};\n for (var k in cmd) { if (Object.prototype.hasOwnProperty.call(cmd, k)) next[k] = cmd[k]; }\n next.target = {};\n for (var tk in target) { if (Object.prototype.hasOwnProperty.call(target, tk)) next.target[tk] = target[tk]; }\n next.target.tabId = tabId;\n executeCommand(next);\n });\n return;\n }\n try {\n if (!chrome) { replyErr(cmdId, \"Unsupported\", \"chrome.* not available in this context\"); return; }\n if (op === \"eval\") {\n if (ctx === \"background\") {\n Promise.resolve().then(function () { return (0, eval)(args.expression); })\n .then(function (r) { replyOk(cmdId, r); }, function (e) {\n var msg = (e && e.message) || String(e);\n // MV3 forbids eval of strings in the SW/extension pages and\n // rejects 'unsafe-eval'. Gecko words the same refusal \"call to\n // eval() blocked by CSP\", which read as a guest throw.\n if (cspBlocksEval(msg)) {\n replyErr(cmdId, \"Unsupported\", \"eval is blocked in the extension background by the extension's content_security_policy, which allows no unsafe-eval. Use --context page --tab <id> (eval runs in the page's MAIN world), or run on a build whose CSP permits it. Engine: \" + engineName(), \"csp_blocks_eval\");\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 // A returned promise is settled in the tab, since Firefox cannot\n // clone one that sits inside the result object.\n nsCall(\"scripting\", \"executeScript\", [{\n target: {tabId: target.tabId},\n world: ctx === \"page\" ? \"MAIN\" : \"ISOLATED\",\n func: async function (src) {\n try { return {__extjsEval: 1, ok: true, value: await 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) { replyInjectionErr(cmdId, 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\" && (cspBlocksEval(String(frame.name) + \" \" + String(frame.message)) || String(frame.name) === \"EvalError\")) {\n replyErr(cmdId, \"Unsupported\", \"eval of a string is blocked in the ISOLATED (content) world by the extension's content_security_policy. Use --context page (runs in the page's MAIN world). Original: \" + frame.message, \"csp_blocks_eval\");\n return;\n }\n replyErr(cmdId, frame.name || \"EvalError\", frame.message);\n });\n } else if (ctx === \"popup\" || ctx === \"options\" || ctx === \"sidebar\" || ctx === \"devtools\" || ctx === \"newtab\" || ctx === \"history\" || ctx === \"bookmarks\") {\n // The SW can't eval in another extension page; ask the surface's\n // own in-bundle relay (only the open, matching-context page responds).\n chrome.runtime.sendMessage(\n {__extjsEvalRequest: true, target: target, args: {expression: String(args.expression)}},\n function (resp) {\n classifySurfaceFailure(ctx, resp, function (relayErr) {\n if (relayErr) {\n replyErr(cmdId, \"Unsupported\", relayErr.message, relayErr.code);\n } else if (resp.ok) {\n replyOk(cmdId, resp.value);\n } else {\n replyErr(cmdId, (resp.error && resp.error.name) || \"EvalError\", (resp.error && resp.error.message) || \"eval failed\", resp.error && resp.error.code);\n }\n });\n }\n );\n } else if (ctx === \"content\" || ctx === \"page\") {\n replyErr(cmdId, \"Unsupported\", target.tabId\n ? \"chrome.scripting is not available on this engine (MV2 has no scripting API); use --context background\"\n : \"eval in context \" + ctx + \" requires a tabId\");\n } else {\n replyErr(cmdId, \"BadRequest\", \"unknown eval context: \" + ctx);\n }\n return;\n }\n if (op === \"storage.get\" || op === \"storage.set\") {\n // chrome.* storage is callback-only on Gecko and older Chromium.\n // Prefer promisified browser.*, then fall back to the callback shape.\n var storageNS = (g.browser && g.browser.storage) ? g.browser.storage : chrome.storage;\n var storageArea = storageNS && storageNS[args.area || \"local\"];\n if (!storageArea) { replyErr(cmdId, \"StorageError\", \"storage.\" + (args.area || \"local\") + \" unavailable\"); return; }\n var isSet = op === \"storage.set\";\n var callArgs = isSet ? [args.items || {}] : [args.key != null ? args.key : null];\n // The dev runtime keeps its own bookkeeping under __extjs* keys in\n // the same area; a whole-area read shows only the extension's data.\n var withoutFrameworkKeys = function (r) {\n if (args.key != null || !r || typeof r !== \"object\") return r;\n var own = {};\n for (var k in r) {\n if (Object.prototype.hasOwnProperty.call(r, k) && k.indexOf(\"__extjs\") !== 0) own[k] = r[k];\n }\n return own;\n };\n var onStorageOk = function (r) { replyOk(cmdId, isSet ? {set: Object.keys(args.items || {})} : withoutFrameworkKeys(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 // The answer names the override tabs the next generation restores.\n restartExtension(50, function (restoring) {\n var value = {reloading: true, reloaded: [\"background\"]};\n if (restoring.length) {\n value.restoring = [];\n for (var r = 0; r < restoring.length; r++) value.restoring.push({tabId: restoring[r].tabId, surface: restoring[r].surface});\n }\n replyOk(cmdId, value);\n });\n } else if (target.tabId) {\n nsCall(\"tabs\", \"reload\", [target.tabId], function (err) {\n if (err) replyTabsFailure(cmdId, err, \"ReloadError\");\n else replyOk(cmdId, {reloaded: target.tabId});\n });\n } else {\n replyErr(cmdId, \"Unsupported\", \"reload needs background or a tabId\");\n }\n return;\n }\n if (op === \"open\") {\n var surface = args.surface || ctx;\n // The toolbar entry a click lands on: MV3 declares action, MV2\n // browser_action or page_action. Firefox MV3 still exposes\n // pageAction, so the manifest picks first, and its namespace has to\n // exist. Without a declared key, the first namespace present wins.\n var actionManifestKey = function () {\n var m = null;\n try { m = chrome.runtime.getManifest(); } catch (e) { m = null; }\n var hasNS = function (key) {\n var n = ACTION_NAMESPACES[key];\n return !!((g.browser && g.browser[n]) || (g.chrome && g.chrome[n]));\n };\n for (var k = 0; k < ACTION_KEYS.length; k++) {\n if (m && m[ACTION_KEYS[k]] && hasNS(ACTION_KEYS[k])) return ACTION_KEYS[k];\n }\n for (var n = 0; n < ACTION_KEYS.length; n++) {\n if (hasNS(ACTION_KEYS[n])) return ACTION_KEYS[n];\n }\n return null;\n };\n // A page action's popup is per tab, and both engines require the\n // tabId. Always resolves to a string.\n var getActionPopup = function (key, tab, cb) {\n var details = key === \"page_action\" ? {tabId: tab && tab.id} : {};\n nsCall(ACTION_NAMESPACES[key], \"getPopup\", [details], function (err, p) {\n cb(err ? \"\" : (p || \"\"));\n });\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 var popupKey = actionManifestKey();\n var popupNS = popupKey && ACTION_NAMESPACES[popupKey];\n if (popupNS && chrome[popupNS] && chrome[popupNS].openPopup) {\n nsCall(popupNS, \"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\", (popupNS || \"action\") + \".openPopup not available\", \"api_unavailable\"); }\n } else if (surface === \"options\") {\n // Chrome answers a manifest with no options page through lastError\n // and still calls back, so the callback must read it.\n try {\n chrome.runtime.openOptionsPage(function () {\n var optErr = chrome.runtime.lastError;\n if (optErr) {\n replyErr(cmdId, \"Unsupported\", \"openOptionsPage: \" + (optErr.message || optErr) + \" (the manifest declares no options_ui or options_page)\", \"surface_not_declared\");\n } else replyOk(cmdId, {opened: \"options\"});\n });\n }\n catch (e) { replyErr(cmdId, \"Unsupported\", \"openOptionsPage: \" + e); }\n } else if (surface === \"sidebar\") {\n // The two member reads below are exactly what the Safari\n // unsupported-API scan looks for, and this runtime is prepended to\n // the background asset at PROCESS_ASSETS_STAGE_REPORT + 101, after\n // that scan runs at SUMMARIZE + 1. Move the injection earlier and\n // every Safari dev session warns about this line.\n if (chrome.sidePanel && chrome.sidePanel.open && chrome.windows) {\n chrome.windows.getCurrent(function (w) {\n nsCall(\"sidePanel\", \"open\", [{windowId: w.id}], function (err) {\n if (err) {\n var sideMsg = (err && err.message) || err;\n replyErr(cmdId, \"Unsupported\", \"sidePanel.open: \" + sideMsg, openRefusalCode(sideMsg));\n } else replyOk(cmdId, {opened: \"sidebar\"});\n });\n });\n } else { replyErr(cmdId, \"Unsupported\", \"sidePanel not available (engine: \" + engineName() + \")\", \"api_unavailable\"); }\n } else if (surface === \"action\") {\n // With a popup the click opens it; without one, replay the\n // onClicked listeners captured at install time. The answer names\n // the manifest key the click went through.\n var actionKey = actionManifestKey();\n if (actionKey) {\n var actionNS = ACTION_NAMESPACES[actionKey];\n var clicked = actionClickedListeners[actionKey];\n resolveActiveTab(args, function (tab) {\n getActionPopup(actionKey, tab, function (popup) {\n if (popup) {\n nsCall(actionNS, \"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\", manifestKey: actionKey});\n });\n } else if (clicked.length) {\n var fired = 0;\n for (var i = 0; i < clicked.length; i++) {\n try { clicked[i](tab); fired++; } catch (e) {\n // Ignore\n }\n }\n var reply = {triggered: \"onClicked\", listeners: fired, gesture: false, manifestKey: actionKey};\n var warning = activeTabWarning();\n if (warning) reply.warning = warning;\n replyOk(cmdId, reply);\n } else {\n replyErr(cmdId, \"Unsupported\", actionKey + \" has no popup and no \" + actionNS + \".onClicked listener registered\");\n }\n });\n });\n } else { replyErr(cmdId, \"Unsupported\", \"the manifest declares no action, browser_action or page_action (engine: \" + engineName() + \")\", \"surface_not_declared\"); }\n } else if (surface === \"command\") {\n var commandName = (args && args.name) || undefined;\n if (!commandListeners.length) {\n replyErr(cmdId, \"Unsupported\", \"no chrome.commands.onCommand listener registered\");\n } else {\n resolveActiveTab(args, function (tab) {\n var fired = 0;\n for (var i = 0; i < commandListeners.length; i++) {\n try { commandListeners[i](commandName, tab); fired++; } catch (e) {\n // Ignore\n }\n }\n replyOk(cmdId, {triggered: \"command\", command: commandName || null, listeners: fired, gesture: false});\n });\n }\n } else { replyErr(cmdId, \"BadRequest\", \"unknown surface: \" + surface); }\n return;\n }\n if (op === \"tabs.query\") {\n nsCall(\"tabs\", \"query\", [args || {}], function (err, tabs) {\n if (err) { replyErr(cmdId, \"TabsError\", (err && err.message) || err); return; }\n replyOk(cmdId, (tabs || []).map(function (t) { return {id: t.id, url: t.url, title: t.title, active: t.active, windowId: t.windowId}; }));\n });\n return;\n }\n // Navigation is a static tabs.* call, never an eval: an MV3 background\n // (Safari's included) refuses eval by CSP, so a url must reach a tab\n // through code that shipped with the bridge. A tabId targets that\n // tab, newTab opens one, and otherwise the active tab is reused.\n if (op === \"tabs.navigate\") {\n var navUrl = typeof args.url === \"string\" ? args.url : \"\";\n if (!navUrl) { replyErr(cmdId, \"BadRequest\", \"tabs.navigate needs a url\"); return; }\n var navDone = function (err, tab, created) {\n if (err) { replyTabsFailure(cmdId, err, \"TabsError\", navUrl); return; }\n replyOk(cmdId, {tabId: tab && tab.id != null ? tab.id : null, url: navUrl, created: !!created});\n };\n if (target.tabId != null) {\n nsCall(\"tabs\", \"update\", [target.tabId, {url: navUrl}], function (err, tab) { navDone(err, tab, false); });\n return;\n }\n if (args.newTab) {\n nsCall(\"tabs\", \"create\", [{url: navUrl, active: args.active !== false}], function (err, tab) { navDone(err, tab, true); });\n return;\n }\n nsCall(\"tabs\", \"query\", [{active: true, currentWindow: true}], function (qErr, tabs) {\n var active = !qErr && tabs && tabs[0];\n if (active && active.id != null) {\n nsCall(\"tabs\", \"update\", [active.id, {url: navUrl}], function (err, tab) { navDone(err, tab, false); });\n } else {\n nsCall(\"tabs\", \"create\", [{url: navUrl}], function (err, tab) { navDone(err, tab, true); });\n }\n });\n return;\n }\n if (op === \"inspect\") {\n // Extract a DOM snapshot from the target page via chrome.scripting\n // (CDP-free). Closed shadow roots need CDP; here we read open ones.\n if ((ctx === \"content\" || ctx === \"page\") && target.tabId && chrome.scripting) {\n var maxBytes = (args && args.maxBytes) || 262144;\n var includeHtml = !args || !args.include || args.include.indexOf(\"html\") !== -1;\n nsCall(\"scripting\", \"executeScript\", [{\n target: {tabId: target.tabId},\n world: ctx === \"page\" ? \"MAIN\" : \"ISOLATED\",\n func: function (wantHtml, cap) {\n function countShadow(root) {\n var n = 0, els = root.querySelectorAll(\"*\");\n for (var i = 0; i < els.length; i++) { if (els[i].shadowRoot) { n++; n += countShadow(els[i].shadowRoot); } }\n return n;\n }\n var doc = document;\n var roots = doc.querySelectorAll('#extension-root,[data-extension-root]');\n var out = {\n url: location.href,\n title: doc.title,\n summary: {\n htmlLength: doc.documentElement.outerHTML.length,\n scriptCount: doc.querySelectorAll(\"script\").length,\n styleCount: doc.querySelectorAll(\"style,link[rel=stylesheet]\").length,\n extensionRootCount: roots.length,\n openShadowRoots: countShadow(doc),\n bodyChildCount: doc.body ? doc.body.children.length : 0\n }\n };\n if (wantHtml) {\n var html = doc.documentElement.outerHTML;\n if (cap > 0 && html.length > cap) { out.html = html.slice(0, cap); out.htmlTruncated = true; }\n else { out.html = html; }\n }\n return out;\n },\n args: [includeHtml, maxBytes]\n }], function (err, res) {\n if (err) { replyInjectionErr(cmdId, err); return; }\n var snap = res && res[0] ? res[0].result : undefined;\n if (snap == null) { replyErr(cmdId, \"TargetNotFound\", \"no injectable frame returned a snapshot for tab \" + target.tabId + \" (restricted page, or outside host_permissions)\"); return; }\n replyOk(cmdId, snap);\n });\n } else if (ctx === \"popup\" || ctx === \"options\" || ctx === \"sidebar\" || ctx === \"devtools\" || ctx === \"newtab\" || ctx === \"history\" || ctx === \"bookmarks\") {\n // The SW can't read a surface page's DOM; ask the surface's own\n // in-bundle relay (only the open, matching-context page responds).\n chrome.runtime.sendMessage(\n {__extjsInspectRequest: true, target: target, args: args},\n function (resp) {\n classifySurfaceFailure(ctx, resp, function (relayErr) {\n if (relayErr) {\n replyErr(cmdId, \"Unsupported\", relayErr.message, relayErr.code);\n } else if (resp.ok) {\n replyOk(cmdId, resp.value);\n } else {\n replyErr(cmdId, (resp.error && resp.error.name) || \"InspectError\", (resp.error && resp.error.message) || \"inspect failed\");\n }\n });\n }\n );\n } else if (ctx === \"background\") {\n replyErr(cmdId, \"Unsupported\", \"the service worker has no DOM; inspect a content/page (with --tab) or an open surface\");\n } else {\n replyErr(cmdId, \"Unsupported\", \"inspect of \" + ctx + \" requires a tabId (content/page) or an open surface\");\n }\n return;\n }\n replyErr(cmdId, \"BadRequest\", \"unknown op: \" + op);\n } catch (e) {\n replyErr(cmdId, \"ExecutorError\", e);\n }\n }\n\n function noopLastError() { try { void g.chrome.runtime.lastError; } catch (e) {\n // Ignore\n } }\n\n // Only http(s)/file/ftp tabs can run a content script; skip chrome://, the\n // extension's own pages, about:blank, etc.\n function isInjectableUrl(url) {\n return typeof url === \"string\" && /^(https?|file|ftp):/i.test(url);\n }\n\n // Re-inject one declared entry's fresh files into every matching open tab.\n // exclude_matches MUST be honored: query the excludes and subtract them.\n // opts.completeOnly skips a tab still loading: Chrome injects the static\n // script into it when it lands, and a second copy here would run twice.\n function reinjectContentScriptEntry(entry, opts) {\n var chrome = g.chrome;\n var completeOnly = !!(opts && opts.completeOnly);\n var matches = (entry && entry.matches) || [];\n if (!Array.isArray(matches) || !matches.length) return;\n var excludeMatches = (entry && entry.exclude_matches) || [];\n var jsFiles = ((entry && entry.js) || []).filter(function (f) { return typeof f === \"string\"; });\n var cssFiles = ((entry && entry.css) || []).filter(function (f) { return typeof f === \"string\"; });\n if (!jsFiles.length && !cssFiles.length) return;\n var world = entry.world === \"MAIN\" ? \"MAIN\" : \"ISOLATED\";\n var allFrames = !!entry.all_frames;\n function injectInto(excludedTabIds) {\n try {\n chrome.tabs.query({url: matches}, function (tabs) {\n var err = null;\n try { err = chrome.runtime.lastError; } catch (e) {\n // Ignore\n }\n if (err || !tabs) return;\n for (var i = 0; i < tabs.length; i++) {\n (function (tab) {\n if (!tab || tab.id == null || !isInjectableUrl(tab.url)) return;\n if (excludedTabIds[tab.id]) return;\n if (completeOnly && tab.status === \"loading\") return;\n var target = {tabId: tab.id, allFrames: allFrames};\n if (cssFiles.length && chrome.scripting.insertCSS) {\n try { chrome.scripting.insertCSS({target: target, files: cssFiles}, noopLastError); } catch (e) {\n // Ignore\n }\n }\n if (jsFiles.length && chrome.scripting.executeScript) {\n try {\n chrome.scripting.executeScript(\n {target: target, files: jsFiles, world: world, injectImmediately: true},\n noopLastError\n );\n } catch (e) {\n // Ignore\n }\n }\n })(tabs[i]);\n }\n });\n } catch (e) {\n // Ignore\n }\n }\n if (Array.isArray(excludeMatches) && excludeMatches.length) {\n try {\n chrome.tabs.query({url: excludeMatches}, function (excludedTabs) {\n try { void chrome.runtime.lastError; } catch (e) {\n // Ignore\n }\n var excluded = {};\n if (excludedTabs) {\n for (var i = 0; i < excludedTabs.length; i++) {\n if (excludedTabs[i] && excludedTabs[i].id != null) excluded[excludedTabs[i].id] = true;\n }\n }\n injectInto(excluded);\n });\n return;\n } catch (e) {\n // Ignore\n }\n }\n injectInto({});\n }\n\n // Re-inject all content scripts into their open tabs. Reads the manifest\n // FROM DISK: getManifest() is frozen and dev filenames are content-hashed.\n // opts.register false skips the dynamic registration: at boot the static\n // manifest already names the current files, and a copy injects twice.\n function reinjectContentScripts(onDone, opts) {\n var chrome = g.chrome;\n var register = !opts || opts.register !== false;\n try {\n if (typeof g.fetch !== \"function\" || !chrome.scripting) return false;\n g.fetch(chrome.runtime.getURL(\"manifest.json\"), {cache: \"no-store\"})\n .then(function (r) { return r.json(); })\n .then(function (manifest) {\n var entries = (manifest && manifest.content_scripts) || [];\n for (var i = 0; i < entries.length; i++) reinjectContentScriptEntry(entries[i], opts);\n if (register) reregisterForFutureNavigations(entries);\n if (onDone) { try { onDone(); } catch (e) {\n // Ignore\n } }\n })\n .catch(function () {\n // Ignore\n });\n return true;\n } catch (e) { return false; }\n }\n\n function mapRunAt(runAt) {\n if (runAt === \"document_start\") return \"document_start\";\n if (runAt === \"document_end\") return \"document_end\";\n return \"document_idle\";\n }\n\n // Re-register content scripts dynamically at the fresh files, so tabs\n // opened AFTER an edit also get the new build. Idempotent per entry id.\n function reregisterForFutureNavigations(entries) {\n var chrome = g.chrome;\n if (\n !chrome.scripting ||\n !chrome.scripting.registerContentScripts ||\n !chrome.scripting.getRegisteredContentScripts\n ) return;\n\n var scripts = [];\n for (var i = 0; i < entries.length; i++) {\n var e = entries[i] || {};\n if (!Array.isArray(e.matches) || !e.matches.length) continue;\n var js = (e.js || []).filter(function (f) { return typeof f === \"string\"; });\n var css = (e.css || []).filter(function (f) { return typeof f === \"string\"; });\n if (!js.length && !css.length) continue;\n // Every field is sent: updateContentScripts keeps any key left out, so\n // a dropped exclude_matches, css file or fallback would outlive the edit.\n scripts.push({\n id: \"extjs-dev-cs-\" + i,\n matches: e.matches,\n excludeMatches: (e.exclude_matches || []).filter(function (p) { return typeof p === \"string\"; }),\n js: js,\n css: css,\n runAt: mapRunAt(e.run_at),\n allFrames: !!e.all_frames,\n world: e.world === \"MAIN\" ? \"MAIN\" : \"ISOLATED\",\n matchOriginAsFallback: e.match_origin_as_fallback === true,\n // A browser restart loads the static manifest fresh; a copy that\n // outlived the session would inject every page twice.\n persistAcrossSessions: false\n });\n }\n if (!scripts.length) return;\n\n try {\n chrome.scripting.getRegisteredContentScripts(function (existing) {\n try { void chrome.runtime.lastError; } catch (e) {\n // Ignore\n }\n var have = {};\n if (existing) for (var k = 0; k < existing.length; k++) have[existing[k].id] = true;\n var toRegister = [], toUpdate = [];\n for (var j = 0; j < scripts.length; j++) {\n (have[scripts[j].id] ? toUpdate : toRegister).push(scripts[j]);\n }\n if (toRegister.length) {\n try { chrome.scripting.registerContentScripts(toRegister, noopLastError); } catch (e) {\n // Ignore\n }\n }\n if (toUpdate.length) {\n try { chrome.scripting.updateContentScripts(toUpdate, noopLastError); } catch (e) {\n // Ignore\n }\n }\n });\n } catch (e) {\n // Ignore\n }\n }\n\n // The dev content-script runtime (plugin-reload) owns registration, the\n // boot heal and the tab reloads when its registry exists; absent, the\n // manifest-based paths below stay in charge.\n function devContentScripts() {\n try { return g.__extjsDevContentScripts || null; } catch (e) { return null; }\n }\n\n // An extension reload unloads chrome_url_overrides, and Chromium leaves\n // the open override tabs on its stock page. Only a fresh navigation to the\n // browser url (a tab reload does not) picks the override up again.\n var OVERRIDE_TABS_KEY = \"__extjsDevOverrideTabs\";\n var OVERRIDE_KINDS = [\"newtab\", \"history\", \"bookmarks\"];\n\n function overriddenKinds() {\n var m = null;\n try { m = g.chrome.runtime.getManifest(); } catch (e) { return []; }\n var o = m && m.chrome_url_overrides;\n var out = [];\n if (!o) return out;\n for (var i = 0; i < OVERRIDE_KINDS.length; i++) {\n if (typeof o[OVERRIDE_KINDS[i]] === \"string\" && o[OVERRIDE_KINDS[i]]) out.push(OVERRIDE_KINDS[i]);\n }\n return out;\n }\n\n // chrome://newtab/, edge://history and the like. Gecko shows about: urls\n // there, which this never matches, so other engines record nothing.\n function overrideKindOf(url, kinds) {\n var match = /^[a-z-]+:[/][/]([a-z]+)[/]?(?:[?#].*)?$/.exec(String(url || \"\"));\n return match && kinds.indexOf(match[1]) !== -1 ? match[1] : null;\n }\n\n // Records the open override tabs for the next generation and answers with\n // them. The guard keeps a silent tabs.query from blocking the reload.\n function rememberOverrideTabs(cb) {\n var chrome = g.chrome;\n var answered = false;\n var guard = null;\n var answer = function (list) {\n if (answered) return;\n answered = true;\n if (guard) { try { clearTimeout(guard); } catch (e) {\n // Ignore\n } }\n try { cb(list); } catch (e) {\n // Ignore\n }\n };\n var kinds = overriddenKinds();\n if (!kinds.length || !chrome.tabs || !chrome.tabs.query || !chrome.storage || !chrome.storage.local) return answer([]);\n try { guard = setTimeout(function () { answer([]); }, 1000); } catch (e) {\n // Ignore\n }\n try {\n chrome.tabs.query({}, function (tabs) {\n noopLastError();\n var list = [];\n for (var i = 0; tabs && i < tabs.length; i++) {\n var t = tabs[i];\n var kind = t && t.id != null ? overrideKindOf(t.url, kinds) : null;\n if (kind) list.push({tabId: t.id, surface: kind, url: t.url});\n }\n if (!list.length) return answer(list);\n try {\n var rec = {};\n rec[OVERRIDE_TABS_KEY] = {at: Date.now(), tabs: list};\n chrome.storage.local.set(rec, function () { noopLastError(); answer(list); });\n } catch (e) { answer([]); }\n });\n } catch (e) { answer([]); }\n }\n\n // Shared by every path that restarts the extension, so none of them\n // strands the override tabs.\n function restartExtension(delayMs, onRecorded) {\n // Every restart path flags the next generation's heal, so open tabs get\n // their content scripts back whichever path asked for the reload.\n try {\n if (g.chrome && g.chrome.storage && g.chrome.storage.local) {\n g.chrome.storage.local.set({__extjsDevPendingReinject: Date.now()}, noopLastError);\n }\n } catch (e) {\n // Ignore\n }\n rememberOverrideTabs(function (list) {\n if (onRecorded) { try { onRecorded(list); } catch (e) {\n // Ignore\n } }\n try { setTimeout(function () { try { g.chrome.runtime.reload(); } catch (e) {\n // Ignore\n } }, delayMs); } catch (e) {\n // Ignore\n }\n });\n }\n g.__extjsDevRestartExtension = restartExtension;\n\n // The new generation sends each recorded tab back to its browser url, once,\n // unless the tab has navigated elsewhere since.\n function restoreOverrideTabsAtBoot() {\n var chrome = g.chrome;\n if (!chrome || !chrome.storage || !chrome.storage.local || !chrome.tabs || !chrome.tabs.update) return;\n chrome.storage.local.get(OVERRIDE_TABS_KEY, function (res) {\n noopLastError();\n var rec = res && res[OVERRIDE_TABS_KEY];\n if (!rec || typeof rec !== \"object\" || !Array.isArray(rec.tabs)) return;\n try { chrome.storage.local.remove(OVERRIDE_TABS_KEY, noopLastError); } catch (e) {\n // Ignore\n }\n if (typeof rec.at !== \"number\" || Date.now() - rec.at > 30000) return;\n var kinds = overriddenKinds();\n setTimeout(function () {\n for (var i = 0; i < rec.tabs.length; i++) {\n (function (t) {\n if (!t || t.tabId == null || kinds.indexOf(t.surface) === -1) return;\n var go = function () {\n try { chrome.tabs.update(t.tabId, {url: t.url}, noopLastError); } catch (e) {\n // Ignore\n }\n };\n if (!chrome.tabs.get) return go();\n try {\n chrome.tabs.get(t.tabId, function (tab) {\n noopLastError();\n if (tab && overrideKindOf(tab.url, kinds) === t.surface) go();\n });\n } catch (e) {\n // Ignore\n }\n })(rec.tabs[i]);\n }\n }, 250);\n });\n }\n\n // Dev-loop reload without the CDP controller: content-scripts reload the\n // matching tabs at the new bundle, or re-inject in place where no registry\n // exists; service-worker/full/manifest restart the extension.\n function performDevReload(type, onDone, frame) {\n var chrome = g.chrome;\n if (!chrome) return;\n\n var fullReload = function () {\n // restartExtension flags the next generation's heal before it reloads.\n // Deferred so in-flight frames and the tab console announcement flush\n // before the SW dies; the devtools companion confirms completion.\n restartExtension(150);\n };\n\n if (type === \"content-scripts\" && chrome.scripting && chrome.tabs && chrome.tabs.query) {\n var dev = devContentScripts();\n if (dev && typeof dev.reload === \"function\") {\n try {\n dev.reload(frame && frame.changedContentScriptEntries, function (handled) {\n if (handled) { if (onDone) { try { onDone(); } catch (e) {\n // Ignore\n } } return; }\n if (reinjectContentScripts(onDone)) return;\n fullReload();\n });\n return;\n } catch (e) {\n // Ignore\n }\n }\n if (reinjectContentScripts(onDone)) return;\n }\n\n fullReload();\n }\n\n // One server-built label travels the ReloadFrame; the announcement\n // fires HERE, next to the actual reload action, so it stays true.\n\n // console.log the reload line into every open injectable tab; the patched\n // ISOLATED-world console also lands it in the dev-server log stream.\n function announceReloadInTabs(text) {\n var chrome = g.chrome;\n if (!chrome || !chrome.tabs || !chrome.tabs.query) return;\n try {\n chrome.tabs.query({}, function (tabs) {\n noopLastError();\n if (!tabs) return;\n for (var i = 0; i < tabs.length; i++) {\n (function (tab) {\n if (!tab || tab.id == null || !isInjectableUrl(tab.url)) return;\n if (chrome.scripting && chrome.scripting.executeScript) {\n try {\n chrome.scripting.executeScript(\n {target: {tabId: tab.id}, func: function (t) { console.log(t); }, args: [text]},\n noopLastError\n );\n return;\n } catch (e) {\n // Ignore\n }\n }\n // MV2 fallback (Firefox without the scripting API).\n if (chrome.tabs.executeScript) {\n try {\n chrome.tabs.executeScript(tab.id, {code: \"console.log(\" + JSON.stringify(text) + \");\"}, noopLastError);\n } catch (e) {\n // Ignore\n }\n }\n })(tabs[i]);\n }\n });\n } catch (e) {\n // Ignore\n }\n }\n\n // The scripts-replay shim (plugin-reload) keeps the SW's own\n // executeScript registry; the replay is fire-and-forget by design.\n function replayProgrammaticScripts(changedScriptFiles) {\n if (!Array.isArray(changedScriptFiles) || changedScriptFiles.length === 0) return;\n var replay = g.__extjsScriptsReplay;\n if (typeof replay !== \"function\") return;\n try {\n var outcome = replay(changedScriptFiles);\n if (outcome && typeof outcome.then === \"function\") {\n outcome.then(null, function () {});\n }\n } catch (e) {\n // Ignore\n }\n }\n\n function handleDevReloadFrame(frame) {\n var kind = frame.reloadType || \"full\";\n var label = typeof frame.label === \"string\" ? frame.label : \"\";\n // The server always builds the label; the kind-derived fallback only\n // covers a malformed frame so the announcement stays truthful.\n var fallback = kind === \"content-scripts\" ? \"content_script\"\n : kind === \"service-worker\" ? \"service_worker\"\n : \"extension\";\n var announced = \"[Extension.js] Reloading \" + (label || fallback) + \"\u2026\";\n\n // Receipt for the broker: proves this SW's message pump processed the\n // frame (a socket write proves nothing). Sent FIRST, so a full reload\n // cannot tear the worker down between the receipt and the reload, and\n // the correlation id is echoed so it confirms only this broadcast.\n var ack = {type: \"reload-ack\", reloadType: kind, label: label};\n if (frame.reloadId) ack.reloadId = frame.reloadId;\n send(ack);\n\n // A scripts/ bundle edit: re-run the executeScript calls that named it\n // on their recorded tabs, before any reload decision below.\n replayProgrammaticScripts(frame.changedScriptFiles);\n\n if (kind === \"page\") {\n // Notify-only: rspack-dev-server's livereload refreshes the open\n // surface; reloading the extension here would race it.\n return;\n }\n\n announceReloadInTabs(announced);\n performDevReload(kind, function () {}, frame);\n }\n\n function sanitize(args) {\n var out = [];\n for (var i = 0; i < args.length; i++) {\n var p = args[i];\n try {\n if (typeof p === \"string\") out.push(p.length > 2048 ? p.slice(0, 2048) + \"...\" : p);\n else if (p instanceof Error) out.push(p.name + \": \" + p.message);\n else out.push(JSON.stringify(p));\n } catch (e) {\n out.push(String(p));\n }\n if (out.join(\" \").length > 8192) break;\n }\n return out;\n }\n\n function flush() {\n while (queue.length && open && socket) {\n var f = queue.shift();\n try { socket.send(JSON.stringify(f)); } catch (e) { break; }\n }\n }\n\n function send(frame) {\n if (open && socket) {\n try { socket.send(JSON.stringify(frame)); return; } catch (e) {\n // Ignore\n }\n }\n if (queue.length < MAX_QUEUE) queue.push(frame);\n parkPendingError(frame);\n }\n\n // The queue above lives in this context's memory only, so a background\n // that throws while it evaluates dies with the error still queued: the\n // socket has not finished its handshake yet. Park error frames in\n // extension storage too, where the NEXT context to connect can ship them.\n // Best effort by construction: the write is asynchronous, so a teardown\n // can cut it before the browser records anything.\n var parkedErrors = [];\n function storageLocalArea() {\n try {\n var ns = (g.chrome && g.chrome.storage) || (g.browser && g.browser.storage);\n var area = ns && ns.local;\n if (!area || typeof area.get !== \"function\" || typeof area.set !== \"function\") return null;\n return area;\n } catch (e) { return null; }\n }\n function parkPendingError(frame) {\n if (open) return;\n if (!frame || frame.type !== \"log\" || !frame.event || frame.event.level !== \"error\") return;\n var area = storageLocalArea();\n if (!area) return;\n // The whole list is rewritten on every park, so two errors in one turn\n // cannot lose each other to a read-modify-write, and the cap holds.\n parkedErrors.push(frame);\n while (parkedErrors.length > MAX_PENDING_ERRORS) parkedErrors.shift();\n var items = {};\n items[PENDING_ERRORS_KEY] = parkedErrors;\n try { area.set(items, noopLastError); } catch (e) {\n // Ignore\n }\n }\n function parkedHere(id) {\n for (var i = 0; i < parkedErrors.length; i++) {\n var ev = parkedErrors[i] && parkedErrors[i].event;\n if (ev && ev.id === id) return true;\n }\n return false;\n }\n // Ship what an earlier context parked and never got to send. Entries from\n // another build (a different instance id) or from an older run are dropped,\n // and the key is cleared on read so one failure is reported once.\n function flushPendingErrors() {\n var area = storageLocalArea();\n if (!area) return;\n var read = false;\n function onRead(res) {\n if (read) return;\n read = true;\n var list = res && res[PENDING_ERRORS_KEY];\n if (!list || !list.length) return;\n try {\n if (typeof area.remove === \"function\") area.remove(PENDING_ERRORS_KEY, noopLastError);\n } catch (e) {\n // Ignore\n }\n var now = Date.now();\n for (var i = 0; i < list.length; i++) {\n var frame = list[i];\n var ev = frame && frame.event;\n if (!ev || ev.runId !== INSTANCE_ID) continue;\n // This context parked it, so the queue flush above already has it.\n if (parkedHere(ev.id)) continue;\n if (typeof ev.timestamp !== \"number\" || now - ev.timestamp > PENDING_ERROR_TTL) continue;\n send(frame);\n }\n }\n try {\n var ret = area.get(PENDING_ERRORS_KEY, function (res) { noopLastError(); onRead(res); });\n if (ret && typeof ret.then === \"function\") ret.then(onRead, function () {});\n } catch (e) {\n // Ignore\n }\n }\n\n // The baked PORT is a snapshot of an ephemeral port. After a dev-server\n // restart, the port file shipped in the unpacked extension names the\n // live one, and unpacked resources are read from disk on every fetch.\n function refreshControlPort(done) {\n try {\n var runtime = (g.chrome && g.chrome.runtime) || (g.browser && g.browser.runtime);\n if (!runtime || typeof runtime.getURL !== \"function\" || typeof g.fetch !== \"function\") { done(); return; }\n g.fetch(runtime.getURL(\"extension-js-control.json\"), {cache: \"no-store\"})\n .then(function (res) { return res.json(); })\n .then(function (data) {\n var next = data && parseInt(data.port, 10);\n if (next && next > 0 && next < 65536 && next !== PORT) PORT = next;\n done();\n })\n .catch(function () { done(); });\n } catch (e) { done(); }\n }\n\n function schedule() {\n var delay = backoff;\n backoff = Math.min(backoff * 2, MAX_BACKOFF);\n connectFailures++;\n try {\n setTimeout(function () {\n // Two straight failures make the baked port suspect (a restarted\n // server dials out on a new port): re-resolve before re-dialing.\n if (connectFailures >= 2) refreshControlPort(connect);\n else connect();\n }, delay);\n } catch (e) {\n // Ignore\n }\n }\n\n function connect() {\n try {\n socket = new WS(\"ws://\" + HOST + \":\" + PORT + \"/extjs-control\");\n } catch (e) {\n schedule();\n return;\n }\n socket.onopen = function () {\n open = true;\n backoff = 250;\n connectFailures = 0;\n try {\n socket.send(JSON.stringify({type: \"hello\", v: 1, role: \"producer\", instanceId: INSTANCE_ID, acksReloads: true}));\n } catch (e) {\n // Ignore\n }\n flush();\n // Hello went first and this context's own queue is out; now replay any\n // error a previous context parked before it died.\n flushPendingErrors();\n };\n socket.onmessage = function (ev) {\n var frame;\n try { frame = JSON.parse(typeof ev.data === \"string\" ? ev.data : \"\"); } catch (e) { return; }\n if (frame && frame.type === \"command\") {\n try { executeCommand(frame); } catch (e) { replyErr(frame.cmdId, \"ExecutorError\", e); }\n } else if (frame && frame.type === \"reload\") {\n // Dev-loop reload broadcast (see broker.broadcastReload).\n // Fire-and-forget: no result frame is expected.\n try { handleDevReloadFrame(frame); } catch (e) {\n // Ignore\n }\n } else if (frame && frame.type === \"ping\") {\n // Server keepalive: merely RECEIVING this resets the MV3 SW's ~30s\n // idle timer (Chrome 116+), keeping it reachable. Nothing to do.\n }\n };\n socket.onclose = function () {\n open = false;\n socket = null;\n schedule();\n };\n socket.onerror = function () {\n try { socket && socket.close(); } catch (e) {\n // Ignore\n }\n };\n }\n\n // Uncaught errors don't route through the patched console; the handlers\n // below forward them, using these signatures to skip already-logged throws.\n var recentErrorSigs = [];\n function errorSig(message, stack) {\n return String(message == null ? \"\" : message) + \"::\" + String(stack == null ? \"\" : stack).slice(0, 200);\n }\n function noteErrorSig(sig) {\n recentErrorSigs.push({sig: sig, t: Date.now()});\n if (recentErrorSigs.length > 50) recentErrorSigs.shift();\n }\n function errorSigSeenRecently(sig) {\n var now = Date.now();\n for (var i = recentErrorSigs.length - 1; i >= 0; i--) {\n if (now - recentErrorSigs[i].t > 3000) { recentErrorSigs.splice(i, 1); continue; }\n if (recentErrorSigs[i].sig === sig) return true;\n }\n return false;\n }\n\n LEVELS.forEach(function (level) {\n var orig = typeof consoleRef[level] === \"function\"\n ? consoleRef[level].bind(consoleRef)\n : function () {};\n consoleRef[level] = function () {\n try {\n if (level === \"error\") {\n for (var ai = 0; ai < arguments.length; ai++) {\n if (arguments[ai] instanceof Error) noteErrorSig(errorSig(arguments[ai].message, arguments[ai].stack));\n }\n }\n if (!isDevServerChatter(level, arguments)) 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 // sendMessage path: how the devtools page relays, since its port would be\n // mistaken for the extension's own devtools connection. Also accepts any\n // surface still relaying the old way.\n try {\n var rt = g.chrome;\n if (rt && rt.runtime && rt.runtime.onMessage) {\n rt.runtime.onMessage.addListener(function (msg, sender) {\n if (!msg || !msg.__extjsBridgeLog) return;\n shipRelayedLog(msg.__extjsBridgeLog, sender);\n });\n }\n } catch (e) {\n // Ignore\n }\n\n // Dev registrations outlive runtime.reload(), while the new generation's\n // static manifest already names the current files: drop them first, or\n // every navigation from here on runs the content script twice.\n function unregisterDevContentScripts(onDone) {\n var chrome = g.chrome;\n var done = function () { if (onDone) { try { onDone(); } catch (e) {\n // Ignore\n } } };\n try {\n if (\n !chrome.scripting ||\n !chrome.scripting.getRegisteredContentScripts ||\n !chrome.scripting.unregisterContentScripts\n ) return done();\n chrome.scripting.getRegisteredContentScripts(function (existing) {\n try { void chrome.runtime.lastError; } catch (e) {\n // Ignore\n }\n var ids = [];\n if (existing) {\n for (var i = 0; i < existing.length; i++) {\n var id = existing[i] && existing[i].id;\n if (typeof id === \"string\" && id.indexOf(\"extjs-dev-cs-\") === 0) ids.push(id);\n }\n }\n if (!ids.length) return done();\n try {\n chrome.scripting.unregisterContentScripts({ids: ids}, function () { noopLastError(); done(); });\n } catch (e) { done(); }\n });\n } catch (e) { done(); }\n }\n\n // One heal per producer generation: onInstalled and the pre-reload storage\n // flag can both fire on the same boot, and each would inject once more.\n var bootHealDone = false;\n function healOpenTabsAtBoot() {\n if (bootHealDone) return;\n bootHealDone = true;\n setTimeout(function () {\n var dev = devContentScripts();\n if (dev && typeof dev.heal === \"function\") {\n try { dev.heal(); return; } catch (e) {\n // Ignore\n }\n }\n unregisterDevContentScripts(function () {\n try { reinjectContentScripts(undefined, {register: false, completeOnly: true}); } catch (e) {\n // Ignore\n }\n });\n }, 250);\n }\n\n // Chrome never injects manifest content scripts into already-open tabs, so\n // fire one reinject on onInstalled (which skips idle-stop SW wakes). Not\n // on the first install: the tabs the browser launched with load beside\n // the extension and Chrome injects them itself.\n try {\n var rtBoot = g.chrome;\n if (rtBoot && rtBoot.runtime && rtBoot.runtime.onInstalled) {\n rtBoot.runtime.onInstalled.addListener(function (details) {\n if (details && details.reason === \"install\") return;\n try { healOpenTabsAtBoot(); } catch (e) {\n // Ignore\n }\n });\n }\n } catch (e) {\n // Ignore\n }\n\n try { restoreOverrideTabsAtBoot(); } catch (e) {\n // Ignore\n }\n\n // runtime.reload() does NOT fire onInstalled (verified Chromium 146), so\n // consume the pre-reload storage flag here (fresh only) to heal open tabs.\n try {\n var stBoot = g.chrome;\n if (stBoot && stBoot.storage && stBoot.storage.local) {\n stBoot.storage.local.get(\"__extjsDevPendingReinject\", function (res) {\n noopLastError();\n var ts = res && res.__extjsDevPendingReinject;\n if (ts == null) return;\n try { stBoot.storage.local.remove(\"__extjsDevPendingReinject\", noopLastError); } catch (e) {\n // Ignore\n }\n if (typeof ts !== \"number\" || Date.now() - ts > 30000) return;\n healOpenTabsAtBoot();\n });\n }\n } catch (e) {\n // Ignore\n }\n\n connect();\n } catch (e) {\n // Ignore\n }\n})();\n";
2
+ export declare const BRIDGE_PRODUCER_SOURCE = ";(function () {\n try {\n var g = (typeof globalThis === \"object\" && globalThis) ? globalThis : this;\n if (!g || g.__extjsBridgeProducerInstalled) return;\n\n var PORT = parseInt(\"__EXTJS_CONTROL_PORT__\", 10);\n var INSTANCE_ID = \"__EXTJS_INSTANCE_ID__\";\n var CONTEXT = \"__EXTJS_CONTEXT__\";\n // Connectable host of the dev-server control WS. Baked from the resolved\n // connectable host (loopback locally; the public host for remote/devcontainer).\n var HOST = \"__EXTJS_CONTROL_HOST__\";\n if (!HOST || HOST.indexOf(\"__EXTJS\") === 0) HOST = \"127.0.0.1\";\n if (!PORT || PORT < 1) return;\n\n var WS = g.WebSocket;\n if (typeof WS !== \"function\") return;\n\n var consoleRef = g.console || {};\n g.__extjsBridgeProducerInstalled = true;\n\n // Capture extension event listeners at install time: these wraps run\n // BEFORE user code, and no platform API exists to dispatch these events.\n function captureEvent(event, sink) {\n try {\n if (!event || typeof event.addListener !== \"function\") return;\n var origAdd = event.addListener.bind(event);\n event.addListener = function (cb) {\n if (typeof cb === \"function\" && sink.indexOf(cb) === -1) sink.push(cb);\n return origAdd(cb);\n };\n if (typeof event.removeListener === \"function\") {\n var origRemove = event.removeListener.bind(event);\n event.removeListener = function (cb) {\n var i = sink.indexOf(cb);\n if (i !== -1) sink.splice(i, 1);\n return origRemove(cb);\n };\n }\n } catch (e) { /* non-fatal: trigger falls back to its no-listener reply */ }\n }\n\n // Use the raw globals (not the hoisted chrome var below). This runs first.\n // On Gecko browser.* is a DISTINCT wrapper object: hook BOTH namespaces.\n // One sink per manifest key, so a replay fires only the entry it clicks.\n var ACTION_KEYS = [\"action\", \"browser_action\", \"page_action\"];\n var ACTION_NAMESPACES = {action: \"action\", browser_action: \"browserAction\", page_action: \"pageAction\"};\n var actionClickedListeners = {action: [], browser_action: [], page_action: []};\n var commandListeners = [];\n var capturedEvents = [];\n function captureEventOnce(event, sink) {\n // Polyfilled extensions expose ONE event object on both namespaces:\n // wrap it once, and the sink dedupes callbacks shared across wrappers.\n if (!event || capturedEvents.indexOf(event) !== -1) return;\n capturedEvents.push(event);\n captureEvent(event, sink);\n }\n var captureNamespaces = [g.chrome, g.browser];\n for (var cn = 0; cn < captureNamespaces.length; cn++) {\n var capNS = captureNamespaces[cn];\n if (!capNS) continue;\n for (var ak = 0; ak < ACTION_KEYS.length; ak++) {\n var akNS = capNS[ACTION_NAMESPACES[ACTION_KEYS[ak]]];\n captureEventOnce(akNS && akNS.onClicked, actionClickedListeners[ACTION_KEYS[ak]]);\n }\n captureEventOnce(capNS.commands && capNS.commands.onCommand, commandListeners);\n }\n\n var LEVELS = [\"log\", \"info\", \"warn\", \"error\", \"debug\", \"trace\"];\n // The dev server's HMR client logs its own chatter (\"[HMR] Waiting for\n // update signal from WDS...\") through the extension's console; it is not\n // the extension's output. Its warnings and errors still ship.\n function isDevServerChatter(level, args) {\n if (level === \"warn\" || level === \"error\") return false;\n var first = args && args[0];\n return typeof first === \"string\" && /^\\[(?:HMR|webpack-dev-server|rspack-dev-server)\\] /.test(first);\n }\n var socket = null;\n var open = false;\n var queue = [];\n var backoff = 250;\n var MAX_BACKOFF = 5000;\n var connectFailures = 0;\n var MAX_QUEUE = 1000;\n var MAX_RESULT_BYTES = 256 * 1024;\n // Where error frames are parked so a context that dies before its socket\n // opens does not take them along. Capped, and old entries are dropped.\n var PENDING_ERRORS_KEY = \"__extjsPendingErrors\";\n var MAX_PENDING_ERRORS = 20;\n var PENDING_ERROR_TTL = 300000;\n\n function nowId() {\n return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);\n }\n\n // The extension's own URL scheme names the engine outright, which beats a\n // user agent where Chrome and Safari both say \"Safari\".\n function engineName() {\n try {\n var scheme = (typeof location !== \"undefined\" && location.protocol) || \"\";\n if (scheme.indexOf(\"safari-web-extension\") === 0) return \"webkit\";\n if (scheme.indexOf(\"moz-extension\") === 0) return \"firefox\";\n if (scheme.indexOf(\"chrome-extension\") === 0) return \"chromium\";\n var ua = (typeof navigator !== \"undefined\" && navigator.userAgent) || \"\";\n if (/firefox/i.test(ua)) return \"firefox\";\n if (/safari/i.test(ua) && !/chrome|chromium|edg/i.test(ua)) return \"webkit\";\n return \"chromium\";\n } catch (e) { return \"chromium\"; }\n }\n\n function safeValue(v) {\n try {\n var s = JSON.stringify(v);\n if (s === undefined) return {value: undefined, truncated: false};\n if (s.length > MAX_RESULT_BYTES) {\n return {value: {__type: \"truncated\", preview: s.slice(0, 1024)}, truncated: true};\n }\n return {value: JSON.parse(s), truncated: false};\n } catch (e) {\n return {value: {__type: \"unserializable\", preview: String(v).slice(0, 512)}, truncated: false};\n }\n }\n\n // A page cap counts characters, the transport counts the escaped JSON, and\n // markup grows under escaping. Shrink the html until the whole snapshot\n // fits, so a capped inspect still returns html instead of a placeholder.\n function fitSnapshot(snap) {\n try {\n if (!snap || typeof snap.html !== \"string\") return snap;\n if (JSON.stringify(snap).length <= MAX_RESULT_BYTES) return snap;\n var html = snap.html;\n var cut = html.length;\n for (var attempt = 0; attempt < 40 && cut > 0; attempt++) {\n snap.html = html.slice(0, cut);\n snap.htmlTruncated = true;\n var size = JSON.stringify(snap).length;\n if (size <= MAX_RESULT_BYTES) return snap;\n cut = Math.floor(cut * (MAX_RESULT_BYTES / size) * 0.98);\n }\n snap.html = \"\";\n snap.htmlTruncated = true;\n return snap;\n } catch (e) {\n return snap;\n }\n }\n\n function replyOk(cmdId, value) {\n var s = safeValue(value);\n var frame = {type: \"result\", cmdId: cmdId, ok: true, value: s.value};\n if (s.truncated) frame.truncated = true;\n try { socket && socket.send(JSON.stringify(frame)); } catch (e) {\n // Ignore\n }\n }\n\n function replyErr(cmdId, name, message, code) {\n var frame = {type: \"result\", cmdId: cmdId, ok: false, error: {name: name, message: String(message), engine: engineName()}};\n if (code) frame.error.code = code;\n try { socket && socket.send(JSON.stringify(frame)); } catch (e) {\n // Ignore\n }\n }\n\n // A tabs.* failure the caller can act on gets a name and a refusal code:\n // a tab id nobody has is TargetNotFound, and a url the engine refuses by\n // rule (Gecko's \"Illegal URL\", Chromium's javascript: ban) is BadRequest.\n // Anything else keeps the op's own error name.\n function replyTabsFailure(cmdId, err, fallbackName, url) {\n var msg = String((err && err.message) || err);\n if (/no tab with id|invalid tab id/i.test(msg)) {\n replyErr(cmdId, \"TargetNotFound\", msg, \"tab_not_found\");\n return;\n }\n if (url && /illegal url|javascript urls are not allowed|invalid url/i.test(msg)) {\n replyErr(cmdId, \"BadRequest\", engineName() + \" refuses to open \" + url + \" from the extension's tabs API (\" + msg + \")\", \"url_refused\");\n return;\n }\n replyErr(cmdId, fallbackName, msg);\n }\n\n // A surface relay that answered nothing is closed only when the engine\n // says no receiver exists. Any other lastError (a reply the engine could\n // not clone, a channel that closed) means the surface WAS open and ran the\n // expression, so the message quotes the engine instead of \"not open\".\n function surfaceReplyFailure(ctx, resp) {\n var le = null;\n try { le = g.chrome && g.chrome.runtime && g.chrome.runtime.lastError; } catch (e) {\n // Ignore\n }\n if (!le && resp) return null;\n var lem = le ? String((le && le.message) || le) : \"\";\n if (!lem || /receiving end does not exist|could not establish connection/i.test(lem)) {\n return {code: \"surface_not_open\", message: \"surface '\" + ctx + \"' is not open (open it first: extension open \" + ctx + \")\"};\n }\n return {code: \"surface_reply_failed\", message: \"surface '\" + ctx + \"' answered but the reply did not arrive: \" + lem};\n }\n\n // The page a surface lives in, as the manifest names it, so the background\n // can ask the engine whether that page is open at all.\n function surfacePagePath(ctx) {\n var m = null;\n try { m = g.chrome.runtime.getManifest(); } catch (e) { return null; }\n if (!m) return null;\n var p = null;\n if (ctx === \"popup\") p = (m.action && m.action.default_popup) || (m.browser_action && m.browser_action.default_popup) || (m.page_action && m.page_action.default_popup);\n else if (ctx === \"options\") p = (m.options_ui && m.options_ui.page) || m.options_page;\n else if (ctx === \"sidebar\") p = (m.side_panel && m.side_panel.default_path) || (m.sidebar_action && m.sidebar_action.default_panel);\n else if (ctx === \"newtab\" || ctx === \"history\" || ctx === \"bookmarks\") p = m.chrome_url_overrides && m.chrome_url_overrides[ctx];\n // Character classes on purpose: this source is a template literal, and\n // a backslash escape in a regex would be eaten before it ever ran.\n return typeof p === \"string\" && p ? p.replace(/^[.]?[/]/, \"\") : null;\n }\n\n // Chrome says \"message port closed\" both when the surface died mid-op and\n // when a bystander extension page (the options page, a new tab override)\n // received the message and passed, since a relay returns nothing for a\n // context that is not its own. Only the engine's open-context list tells\n // the two apart, so it is asked before a reply gets the blame. Engines\n // without getContexts keep the plain classification.\n function classifySurfaceFailure(ctx, resp, cb) {\n var relayErr = surfaceReplyFailure(ctx, resp);\n if (!relayErr || relayErr.code !== \"surface_reply_failed\" || !/message port closed/i.test(relayErr.message)) { cb(relayErr); return; }\n var rt = g.chrome && g.chrome.runtime;\n var page = surfacePagePath(ctx);\n if (!page || !rt || typeof rt.getContexts !== \"function\") { cb(relayErr); return; }\n var wanted = \"\";\n try { wanted = String(rt.getURL(page)).split(/[?#]/)[0]; } catch (e) { cb(relayErr); return; }\n var notOpen = {code: \"surface_not_open\", message: \"surface '\" + ctx + \"' is not open (open it first: extension open \" + ctx + \")\"};\n try {\n Promise.resolve(rt.getContexts({})).then(function (list) {\n var open = false;\n for (var i = 0; i < (list || []).length; i++) {\n var u = list[i] && list[i].documentUrl;\n if (u && String(u).split(/[?#]/)[0] === wanted) { open = true; break; }\n }\n cb(open ? relayErr : notOpen);\n }, function () { cb(relayErr); });\n } catch (e) { cb(relayErr); }\n }\n\n // Every engine's wording for \"this document's CSP forbids eval\". Gecko\n // says \"call to eval() blocked by CSP\", Chromium quotes the directive.\n function cspBlocksEval(message) {\n return /call to eval\\(\\)|blocked by (?:the )?CSP|Content Security Policy|unsafe-eval/i.test(String(message == null ? \"\" : message));\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 // Only a tab the engine could not find is a missing target. Anything else\n // happened in a tab that exists, a value it could not clone back above all.\n // Firefox refuses to script a moz-extension:// page or a page outside\n // host access with the tab right there, so that refusal is named too.\n function injectionErrorName(err) {\n var msg = String((err && err.message) || err || \"\");\n if (/no tab with id|invalid tab id/i.test(msg)) return \"TargetNotFound\";\n if (/clon/i.test(msg)) return \"DataCloneError\";\n if (injectionRefused(msg)) return \"InjectionRefused\";\n return \"ExecutorError\";\n }\n\n function injectionRefused(msg) {\n return /missing host permission|cannot access contents|cannot be scripted|cannot access a chrome|extensions gallery/i.test(String(msg));\n }\n\n function replyInjectionErr(cmdId, err) {\n var msg = (err && err.message) || err;\n replyErr(cmdId, injectionErrorName(err), msg, injectionRefused(msg) ? \"injection_refused\" : undefined);\n }\n\n function executeCommand(cmd) {\n var op = cmd.op, target = cmd.target || {}, args = cmd.args || {};\n var ctx = target.context || \"background\";\n var chrome = g.chrome;\n var cmdId = cmd.cmdId;\n // content/page eval & inspect need a numeric tab id: resolve --url (or\n // nothing) to a tab and re-dispatch with target.tabId filled in. (#51)\n if ((op === \"eval\" || op === \"inspect\" || op === \"reload\") && (ctx === \"content\" || ctx === \"page\") && target.tabId == null) {\n resolveTargetTab(target, function (tabId, err) {\n if (tabId == null) {\n // A filter that matched nothing, or no active tab, is a missing\n // target the caller can fix, not a missing feature.\n if (err && /^no open tab matches|^no active tab/i.test(String(err))) {\n replyErr(cmdId, \"TargetNotFound\", err, \"tab_not_found\");\n return;\n }\n replyErr(cmdId, \"Unsupported\", err || (\"eval/inspect in context \" + ctx + \" needs a --tab id, a --url to match, or an active tab\"));\n return;\n }\n var next = {};\n for (var k in cmd) { if (Object.prototype.hasOwnProperty.call(cmd, k)) next[k] = cmd[k]; }\n next.target = {};\n for (var tk in target) { if (Object.prototype.hasOwnProperty.call(target, tk)) next.target[tk] = target[tk]; }\n next.target.tabId = tabId;\n executeCommand(next);\n });\n return;\n }\n try {\n if (!chrome) { replyErr(cmdId, \"Unsupported\", \"chrome.* not available in this context\"); return; }\n if (op === \"eval\") {\n if (ctx === \"background\") {\n Promise.resolve().then(function () { return (0, eval)(args.expression); })\n .then(function (r) { replyOk(cmdId, r); }, function (e) {\n var msg = (e && e.message) || String(e);\n // MV3 forbids eval of strings in the SW/extension pages and\n // rejects 'unsafe-eval'. Gecko words the same refusal \"call to\n // eval() blocked by CSP\", which read as a guest throw.\n if (cspBlocksEval(msg)) {\n replyErr(cmdId, \"Unsupported\", \"eval is blocked in the extension background by the extension's content_security_policy, which allows no unsafe-eval. Use --context page --tab <id> (eval runs in the page's MAIN world), or run on a build whose CSP permits it. Engine: \" + engineName(), \"csp_blocks_eval\");\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 // A returned promise is settled in the tab, since Firefox cannot\n // clone one that sits inside the result object.\n nsCall(\"scripting\", \"executeScript\", [{\n target: {tabId: target.tabId},\n world: ctx === \"page\" ? \"MAIN\" : \"ISOLATED\",\n func: async function (src) {\n try { return {__extjsEval: 1, ok: true, value: await 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) { replyInjectionErr(cmdId, 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\" && (cspBlocksEval(String(frame.name) + \" \" + String(frame.message)) || String(frame.name) === \"EvalError\")) {\n replyErr(cmdId, \"Unsupported\", \"eval of a string is blocked in the ISOLATED (content) world by the extension's content_security_policy. Use --context page (runs in the page's MAIN world). Original: \" + frame.message, \"csp_blocks_eval\");\n return;\n }\n replyErr(cmdId, frame.name || \"EvalError\", frame.message);\n });\n } else if (ctx === \"popup\" || ctx === \"options\" || ctx === \"sidebar\" || ctx === \"devtools\" || ctx === \"newtab\" || ctx === \"history\" || ctx === \"bookmarks\") {\n // The SW can't eval in another extension page; ask the surface's\n // own in-bundle relay (only the open, matching-context page responds).\n chrome.runtime.sendMessage(\n {__extjsEvalRequest: true, target: target, args: {expression: String(args.expression)}},\n function (resp) {\n classifySurfaceFailure(ctx, resp, function (relayErr) {\n if (relayErr) {\n replyErr(cmdId, \"Unsupported\", relayErr.message, relayErr.code);\n } else if (resp.ok) {\n replyOk(cmdId, resp.value);\n } else {\n replyErr(cmdId, (resp.error && resp.error.name) || \"EvalError\", (resp.error && resp.error.message) || \"eval failed\", resp.error && resp.error.code);\n }\n });\n }\n );\n } else if (ctx === \"content\" || ctx === \"page\") {\n replyErr(cmdId, \"Unsupported\", target.tabId\n ? \"chrome.scripting is not available on this engine (MV2 has no scripting API); use --context background\"\n : \"eval in context \" + ctx + \" requires a tabId\");\n } else {\n replyErr(cmdId, \"BadRequest\", \"unknown eval context: \" + ctx);\n }\n return;\n }\n if (op === \"storage.get\" || op === \"storage.set\") {\n // chrome.* storage is callback-only on Gecko and older Chromium.\n // Prefer promisified browser.*, then fall back to the callback shape.\n var storageNS = (g.browser && g.browser.storage) ? g.browser.storage : chrome.storage;\n var storageArea = storageNS && storageNS[args.area || \"local\"];\n if (!storageArea) { replyErr(cmdId, \"StorageError\", \"storage.\" + (args.area || \"local\") + \" unavailable\"); return; }\n var isSet = op === \"storage.set\";\n var callArgs = isSet ? [args.items || {}] : [args.key != null ? args.key : null];\n // The dev runtime keeps its own bookkeeping under __extjs* keys in\n // the same area; a whole-area read shows only the extension's data.\n var withoutFrameworkKeys = function (r) {\n if (args.key != null || !r || typeof r !== \"object\") return r;\n var own = {};\n for (var k in r) {\n if (Object.prototype.hasOwnProperty.call(r, k) && k.indexOf(\"__extjs\") !== 0) own[k] = r[k];\n }\n return own;\n };\n var onStorageOk = function (r) { replyOk(cmdId, isSet ? {set: Object.keys(args.items || {})} : withoutFrameworkKeys(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 // The answer names the override tabs the next generation restores.\n restartExtension(50, function (restoring) {\n var value = {reloading: true, reloaded: [\"background\"]};\n if (restoring.length) {\n value.restoring = [];\n for (var r = 0; r < restoring.length; r++) value.restoring.push({tabId: restoring[r].tabId, surface: restoring[r].surface});\n }\n replyOk(cmdId, value);\n });\n } else if (target.tabId) {\n nsCall(\"tabs\", \"reload\", [target.tabId], function (err) {\n if (err) replyTabsFailure(cmdId, err, \"ReloadError\");\n else replyOk(cmdId, {reloaded: target.tabId});\n });\n } else {\n replyErr(cmdId, \"Unsupported\", \"reload needs background or a tabId\");\n }\n return;\n }\n if (op === \"open\") {\n var surface = args.surface || ctx;\n // The toolbar entry a click lands on: MV3 declares action, MV2\n // browser_action or page_action. Firefox MV3 still exposes\n // pageAction, so the manifest picks first, and its namespace has to\n // exist. Without a declared key, the first namespace present wins.\n var actionManifestKey = function () {\n var m = null;\n try { m = chrome.runtime.getManifest(); } catch (e) { m = null; }\n var hasNS = function (key) {\n var n = ACTION_NAMESPACES[key];\n return !!((g.browser && g.browser[n]) || (g.chrome && g.chrome[n]));\n };\n for (var k = 0; k < ACTION_KEYS.length; k++) {\n if (m && m[ACTION_KEYS[k]] && hasNS(ACTION_KEYS[k])) return ACTION_KEYS[k];\n }\n for (var n = 0; n < ACTION_KEYS.length; n++) {\n if (hasNS(ACTION_KEYS[n])) return ACTION_KEYS[n];\n }\n return null;\n };\n // A page action's popup is per tab, and both engines require the\n // tabId. Always resolves to a string.\n var getActionPopup = function (key, tab, cb) {\n var details = key === \"page_action\" ? {tabId: tab && tab.id} : {};\n nsCall(ACTION_NAMESPACES[key], \"getPopup\", [details], function (err, p) {\n cb(err ? \"\" : (p || \"\"));\n });\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 var popupKey = actionManifestKey();\n var popupNS = popupKey && ACTION_NAMESPACES[popupKey];\n if (popupNS && chrome[popupNS] && chrome[popupNS].openPopup) {\n nsCall(popupNS, \"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\", (popupNS || \"action\") + \".openPopup not available\", \"api_unavailable\"); }\n } else if (surface === \"options\") {\n // Chrome answers a manifest with no options page through lastError\n // and still calls back, so the callback must read it.\n try {\n chrome.runtime.openOptionsPage(function () {\n var optErr = chrome.runtime.lastError;\n if (optErr) {\n replyErr(cmdId, \"Unsupported\", \"openOptionsPage: \" + (optErr.message || optErr) + \" (the manifest declares no options_ui or options_page)\", \"surface_not_declared\");\n } else replyOk(cmdId, {opened: \"options\"});\n });\n }\n catch (e) { replyErr(cmdId, \"Unsupported\", \"openOptionsPage: \" + e); }\n } else if (surface === \"sidebar\") {\n // The two member reads below are exactly what the Safari\n // unsupported-API scan looks for, and this runtime is prepended to\n // the background asset at PROCESS_ASSETS_STAGE_REPORT + 101, after\n // that scan runs at SUMMARIZE + 1. Move the injection earlier and\n // every Safari dev session warns about this line.\n if (chrome.sidePanel && chrome.sidePanel.open && chrome.windows) {\n chrome.windows.getCurrent(function (w) {\n nsCall(\"sidePanel\", \"open\", [{windowId: w.id}], function (err) {\n if (err) {\n var sideMsg = (err && err.message) || err;\n replyErr(cmdId, \"Unsupported\", \"sidePanel.open: \" + sideMsg, openRefusalCode(sideMsg));\n } else replyOk(cmdId, {opened: \"sidebar\"});\n });\n });\n } else { replyErr(cmdId, \"Unsupported\", \"sidePanel not available (engine: \" + engineName() + \")\", \"api_unavailable\"); }\n } else if (surface === \"action\") {\n // With a popup the click opens it; without one, replay the\n // onClicked listeners captured at install time. The answer names\n // the manifest key the click went through.\n var actionKey = actionManifestKey();\n if (actionKey) {\n var actionNS = ACTION_NAMESPACES[actionKey];\n var clicked = actionClickedListeners[actionKey];\n resolveActiveTab(args, function (tab) {\n getActionPopup(actionKey, tab, function (popup) {\n if (popup) {\n nsCall(actionNS, \"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\", manifestKey: actionKey});\n });\n } else if (clicked.length) {\n var fired = 0;\n for (var i = 0; i < clicked.length; i++) {\n try { clicked[i](tab); fired++; } catch (e) {\n // Ignore\n }\n }\n var reply = {triggered: \"onClicked\", listeners: fired, gesture: false, manifestKey: actionKey};\n var warning = activeTabWarning();\n if (warning) reply.warning = warning;\n replyOk(cmdId, reply);\n } else {\n replyErr(cmdId, \"Unsupported\", actionKey + \" has no popup and no \" + actionNS + \".onClicked listener registered\");\n }\n });\n });\n } else { replyErr(cmdId, \"Unsupported\", \"the manifest declares no action, browser_action or page_action (engine: \" + engineName() + \")\", \"surface_not_declared\"); }\n } else if (surface === \"command\") {\n var commandName = (args && args.name) || undefined;\n if (!commandListeners.length) {\n replyErr(cmdId, \"Unsupported\", \"no chrome.commands.onCommand listener registered\");\n } else {\n resolveActiveTab(args, function (tab) {\n var fired = 0;\n for (var i = 0; i < commandListeners.length; i++) {\n try { commandListeners[i](commandName, tab); fired++; } catch (e) {\n // Ignore\n }\n }\n replyOk(cmdId, {triggered: \"command\", command: commandName || null, listeners: fired, gesture: false});\n });\n }\n } else { replyErr(cmdId, \"BadRequest\", \"unknown surface: \" + surface); }\n return;\n }\n if (op === \"tabs.query\") {\n nsCall(\"tabs\", \"query\", [args || {}], function (err, tabs) {\n if (err) { replyErr(cmdId, \"TabsError\", (err && err.message) || err); return; }\n replyOk(cmdId, (tabs || []).map(function (t) { return {id: t.id, url: t.url, title: t.title, active: t.active, windowId: t.windowId}; }));\n });\n return;\n }\n // Navigation is a static tabs.* call, never an eval: an MV3 background\n // (Safari's included) refuses eval by CSP, so a url must reach a tab\n // through code that shipped with the bridge. A tabId targets that\n // tab, newTab opens one, and otherwise the active tab is reused.\n if (op === \"tabs.navigate\") {\n var navUrl = typeof args.url === \"string\" ? args.url : \"\";\n if (!navUrl) { replyErr(cmdId, \"BadRequest\", \"tabs.navigate needs a url\"); return; }\n var navDone = function (err, tab, created) {\n if (err) { replyTabsFailure(cmdId, err, \"TabsError\", navUrl); return; }\n replyOk(cmdId, {tabId: tab && tab.id != null ? tab.id : null, url: navUrl, created: !!created});\n };\n if (target.tabId != null) {\n nsCall(\"tabs\", \"update\", [target.tabId, {url: navUrl}], function (err, tab) { navDone(err, tab, false); });\n return;\n }\n if (args.newTab) {\n nsCall(\"tabs\", \"create\", [{url: navUrl, active: args.active !== false}], function (err, tab) { navDone(err, tab, true); });\n return;\n }\n nsCall(\"tabs\", \"query\", [{active: true, currentWindow: true}], function (qErr, tabs) {\n var active = !qErr && tabs && tabs[0];\n if (active && active.id != null) {\n nsCall(\"tabs\", \"update\", [active.id, {url: navUrl}], function (err, tab) { navDone(err, tab, false); });\n } else {\n nsCall(\"tabs\", \"create\", [{url: navUrl}], function (err, tab) { navDone(err, tab, true); });\n }\n });\n return;\n }\n if (op === \"inspect\") {\n // Extract a DOM snapshot from the target page via chrome.scripting\n // (CDP-free). Closed shadow roots need CDP; here we read open ones.\n if ((ctx === \"content\" || ctx === \"page\") && target.tabId && chrome.scripting) {\n var maxBytes = (args && args.maxBytes) || 262144;\n var includeHtml = !args || !args.include || args.include.indexOf(\"html\") !== -1;\n nsCall(\"scripting\", \"executeScript\", [{\n target: {tabId: target.tabId},\n world: ctx === \"page\" ? \"MAIN\" : \"ISOLATED\",\n func: function (wantHtml, cap) {\n function countShadow(root) {\n var n = 0, els = root.querySelectorAll(\"*\");\n for (var i = 0; i < els.length; i++) { if (els[i].shadowRoot) { n++; n += countShadow(els[i].shadowRoot); } }\n return n;\n }\n var doc = document;\n var roots = doc.querySelectorAll('#extension-root,[data-extension-root]');\n var out = {\n url: location.href,\n title: doc.title,\n summary: {\n htmlLength: doc.documentElement.outerHTML.length,\n scriptCount: doc.querySelectorAll(\"script\").length,\n styleCount: doc.querySelectorAll(\"style,link[rel=stylesheet]\").length,\n extensionRootCount: roots.length,\n openShadowRoots: countShadow(doc),\n bodyChildCount: doc.body ? doc.body.children.length : 0\n }\n };\n if (wantHtml) {\n var html = doc.documentElement.outerHTML;\n if (cap > 0 && html.length > cap) { out.html = html.slice(0, cap); out.htmlTruncated = true; }\n else { out.html = html; }\n }\n return out;\n },\n args: [includeHtml, maxBytes]\n }], function (err, res) {\n if (err) { replyInjectionErr(cmdId, 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, fitSnapshot(snap));\n });\n } else if (ctx === \"popup\" || ctx === \"options\" || ctx === \"sidebar\" || ctx === \"devtools\" || ctx === \"newtab\" || ctx === \"history\" || ctx === \"bookmarks\") {\n // The SW can't read a surface page's DOM; ask the surface's own\n // in-bundle relay (only the open, matching-context page responds).\n chrome.runtime.sendMessage(\n {__extjsInspectRequest: true, target: target, args: args},\n function (resp) {\n classifySurfaceFailure(ctx, resp, function (relayErr) {\n if (relayErr) {\n replyErr(cmdId, \"Unsupported\", relayErr.message, relayErr.code);\n } else if (resp.ok) {\n replyOk(cmdId, fitSnapshot(resp.value));\n } else {\n replyErr(cmdId, (resp.error && resp.error.name) || \"InspectError\", (resp.error && resp.error.message) || \"inspect failed\");\n }\n });\n }\n );\n } else if (ctx === \"background\") {\n replyErr(cmdId, \"Unsupported\", \"the service worker has no DOM; inspect a content/page (with --tab) or an open surface\");\n } else {\n replyErr(cmdId, \"Unsupported\", \"inspect of \" + ctx + \" requires a tabId (content/page) or an open surface\");\n }\n return;\n }\n replyErr(cmdId, \"BadRequest\", \"unknown op: \" + op);\n } catch (e) {\n replyErr(cmdId, \"ExecutorError\", e);\n }\n }\n\n function noopLastError() { try { void g.chrome.runtime.lastError; } catch (e) {\n // Ignore\n } }\n\n // Only http(s)/file/ftp tabs can run a content script; skip chrome://, the\n // extension's own pages, about:blank, etc.\n function isInjectableUrl(url) {\n return typeof url === \"string\" && /^(https?|file|ftp):/i.test(url);\n }\n\n function stringList(value) {\n return Array.isArray(value) ? value.filter(function (v) { return typeof v === \"string\"; }) : [];\n }\n\n // The manifest glob dialect: * is any run of characters, ? exactly one,\n // and the pattern covers the whole url.\n function globMatches(glob, text) {\n var gi = 0, ti = 0, star = -1, mark = 0;\n while (ti < text.length) {\n if (gi < glob.length && (glob.charAt(gi) === \"?\" || glob.charAt(gi) === text.charAt(ti))) { gi++; ti++; }\n else if (gi < glob.length && glob.charAt(gi) === \"*\") { star = gi++; mark = ti; }\n else if (star !== -1) { gi = star + 1; ti = ++mark; }\n else return false;\n }\n while (gi < glob.length && glob.charAt(gi) === \"*\") gi++;\n return gi === glob.length;\n }\n\n // include_globs narrows matches and exclude_globs removes from them, the\n // same way the browser decides where the built extension runs.\n function passesGlobs(url, includeGlobs, excludeGlobs) {\n var u = String(url || \"\");\n var i;\n if (includeGlobs.length) {\n var included = false;\n for (i = 0; i < includeGlobs.length; i++) {\n if (globMatches(includeGlobs[i], u)) { included = true; break; }\n }\n if (!included) return false;\n }\n for (i = 0; i < excludeGlobs.length; i++) {\n if (globMatches(excludeGlobs[i], u)) return false;\n }\n return true;\n }\n\n // Re-inject one declared entry's fresh files into every matching open tab.\n // exclude_matches MUST be honored: query the excludes and subtract them.\n // opts.completeOnly skips a tab still loading: Chrome injects the static\n // script into it when it lands, and a second copy here would run twice.\n function reinjectContentScriptEntry(entry, opts) {\n var chrome = g.chrome;\n var completeOnly = !!(opts && opts.completeOnly);\n var matches = (entry && entry.matches) || [];\n if (!Array.isArray(matches) || !matches.length) return;\n var excludeMatches = (entry && entry.exclude_matches) || [];\n var includeGlobs = stringList(entry && entry.include_globs);\n var excludeGlobs = stringList(entry && entry.exclude_globs);\n var jsFiles = ((entry && entry.js) || []).filter(function (f) { return typeof f === \"string\"; });\n var cssFiles = ((entry && entry.css) || []).filter(function (f) { return typeof f === \"string\"; });\n if (!jsFiles.length && !cssFiles.length) return;\n var world = entry.world === \"MAIN\" ? \"MAIN\" : \"ISOLATED\";\n var allFrames = !!entry.all_frames;\n function injectInto(excludedTabIds) {\n try {\n chrome.tabs.query({url: matches}, function (tabs) {\n var err = null;\n try { err = chrome.runtime.lastError; } catch (e) {\n // Ignore\n }\n if (err || !tabs) return;\n for (var i = 0; i < tabs.length; i++) {\n (function (tab) {\n if (!tab || tab.id == null || !isInjectableUrl(tab.url)) return;\n if (excludedTabIds[tab.id]) return;\n if (!passesGlobs(tab.url, includeGlobs, excludeGlobs)) return;\n if (completeOnly && tab.status === \"loading\") return;\n var target = {tabId: tab.id, allFrames: allFrames};\n if (cssFiles.length && chrome.scripting.insertCSS) {\n try { chrome.scripting.insertCSS({target: target, files: cssFiles}, noopLastError); } catch (e) {\n // Ignore\n }\n }\n if (jsFiles.length && chrome.scripting.executeScript) {\n try {\n chrome.scripting.executeScript(\n {target: target, files: jsFiles, world: world, injectImmediately: true},\n noopLastError\n );\n } catch (e) {\n // Ignore\n }\n }\n })(tabs[i]);\n }\n });\n } catch (e) {\n // Ignore\n }\n }\n if (Array.isArray(excludeMatches) && excludeMatches.length) {\n try {\n chrome.tabs.query({url: excludeMatches}, function (excludedTabs) {\n try { void chrome.runtime.lastError; } catch (e) {\n // Ignore\n }\n var excluded = {};\n if (excludedTabs) {\n for (var i = 0; i < excludedTabs.length; i++) {\n if (excludedTabs[i] && excludedTabs[i].id != null) excluded[excludedTabs[i].id] = true;\n }\n }\n injectInto(excluded);\n });\n return;\n } catch (e) {\n // Ignore\n }\n }\n injectInto({});\n }\n\n // Re-inject all content scripts into their open tabs. Reads the manifest\n // FROM DISK: getManifest() is frozen and dev filenames are content-hashed.\n // opts.register false skips the dynamic registration: at boot the static\n // manifest already names the current files, and a copy injects twice.\n function reinjectContentScripts(onDone, opts) {\n var chrome = g.chrome;\n var register = !opts || opts.register !== false;\n try {\n if (typeof g.fetch !== \"function\" || !chrome.scripting) return false;\n g.fetch(chrome.runtime.getURL(\"manifest.json\"), {cache: \"no-store\"})\n .then(function (r) { return r.json(); })\n .then(function (manifest) {\n var entries = (manifest && manifest.content_scripts) || [];\n for (var i = 0; i < entries.length; i++) reinjectContentScriptEntry(entries[i], opts);\n if (register) reregisterForFutureNavigations(entries);\n if (onDone) { try { onDone(); } catch (e) {\n // Ignore\n } }\n })\n .catch(function () {\n // Ignore\n });\n return true;\n } catch (e) { return false; }\n }\n\n function mapRunAt(runAt) {\n if (runAt === \"document_start\") return \"document_start\";\n if (runAt === \"document_end\") return \"document_end\";\n return \"document_idle\";\n }\n\n // Re-register content scripts dynamically at the fresh files, so tabs\n // opened AFTER an edit also get the new build. Idempotent per entry id.\n function reregisterForFutureNavigations(entries) {\n var chrome = g.chrome;\n if (\n !chrome.scripting ||\n !chrome.scripting.registerContentScripts ||\n !chrome.scripting.getRegisteredContentScripts\n ) return;\n\n var scripts = [];\n for (var i = 0; i < entries.length; i++) {\n var e = entries[i] || {};\n if (!Array.isArray(e.matches) || !e.matches.length) continue;\n // registerContentScripts has no glob keys, so a copy of a globbed\n // entry would run on pages the built extension never reaches.\n if (stringList(e.include_globs).length || stringList(e.exclude_globs).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 // Every field is sent: updateContentScripts keeps any key left out, so\n // a dropped exclude_matches, css file or fallback would outlive the edit.\n scripts.push({\n id: \"extjs-dev-cs-\" + i,\n matches: e.matches,\n excludeMatches: (e.exclude_matches || []).filter(function (p) { return typeof p === \"string\"; }),\n js: js,\n css: css,\n runAt: mapRunAt(e.run_at),\n allFrames: !!e.all_frames,\n world: e.world === \"MAIN\" ? \"MAIN\" : \"ISOLATED\",\n matchOriginAsFallback: e.match_origin_as_fallback === true,\n // A browser restart loads the static manifest fresh; a copy that\n // outlived the session would inject every page twice.\n persistAcrossSessions: false\n });\n }\n if (!scripts.length) return;\n\n try {\n chrome.scripting.getRegisteredContentScripts(function (existing) {\n try { void chrome.runtime.lastError; } catch (e) {\n // Ignore\n }\n var have = {};\n if (existing) for (var k = 0; k < existing.length; k++) have[existing[k].id] = true;\n var toRegister = [], toUpdate = [];\n for (var j = 0; j < scripts.length; j++) {\n (have[scripts[j].id] ? toUpdate : toRegister).push(scripts[j]);\n }\n if (toRegister.length) {\n try { chrome.scripting.registerContentScripts(toRegister, noopLastError); } catch (e) {\n // Ignore\n }\n }\n if (toUpdate.length) {\n try { chrome.scripting.updateContentScripts(toUpdate, noopLastError); } catch (e) {\n // Ignore\n }\n }\n });\n } catch (e) {\n // Ignore\n }\n }\n\n // The dev content-script runtime (plugin-reload) owns registration, the\n // boot heal and the tab reloads when its registry exists; absent, the\n // manifest-based paths below stay in charge.\n function devContentScripts() {\n try { return g.__extjsDevContentScripts || null; } catch (e) { return null; }\n }\n\n // An extension reload unloads chrome_url_overrides, and Chromium leaves\n // the open override tabs on its stock page. Only a fresh navigation to the\n // browser url (a tab reload does not) picks the override up again.\n var OVERRIDE_TABS_KEY = \"__extjsDevOverrideTabs\";\n var OVERRIDE_KINDS = [\"newtab\", \"history\", \"bookmarks\"];\n\n function overriddenKinds() {\n var m = null;\n try { m = g.chrome.runtime.getManifest(); } catch (e) { return []; }\n var o = m && m.chrome_url_overrides;\n var out = [];\n if (!o) return out;\n for (var i = 0; i < OVERRIDE_KINDS.length; i++) {\n if (typeof o[OVERRIDE_KINDS[i]] === \"string\" && o[OVERRIDE_KINDS[i]]) out.push(OVERRIDE_KINDS[i]);\n }\n return out;\n }\n\n // chrome://newtab/, edge://history and the like. Gecko shows about: urls\n // there, which this never matches, so other engines record nothing.\n function overrideKindOf(url, kinds) {\n var match = /^[a-z-]+:[/][/]([a-z]+)[/]?(?:[?#].*)?$/.exec(String(url || \"\"));\n return match && kinds.indexOf(match[1]) !== -1 ? match[1] : null;\n }\n\n // Records the open override tabs for the next generation and answers with\n // them. The guard keeps a silent tabs.query from blocking the reload.\n function rememberOverrideTabs(cb) {\n var chrome = g.chrome;\n var answered = false;\n var guard = null;\n var answer = function (list) {\n if (answered) return;\n answered = true;\n if (guard) { try { clearTimeout(guard); } catch (e) {\n // Ignore\n } }\n try { cb(list); } catch (e) {\n // Ignore\n }\n };\n var kinds = overriddenKinds();\n if (!kinds.length || !chrome.tabs || !chrome.tabs.query || !chrome.storage || !chrome.storage.local) return answer([]);\n try { guard = setTimeout(function () { answer([]); }, 1000); } catch (e) {\n // Ignore\n }\n try {\n chrome.tabs.query({}, function (tabs) {\n noopLastError();\n var list = [];\n for (var i = 0; tabs && i < tabs.length; i++) {\n var t = tabs[i];\n var kind = t && t.id != null ? overrideKindOf(t.url, kinds) : null;\n if (kind) list.push({tabId: t.id, surface: kind, url: t.url});\n }\n if (!list.length) return answer(list);\n try {\n var rec = {};\n rec[OVERRIDE_TABS_KEY] = {at: Date.now(), tabs: list};\n chrome.storage.local.set(rec, function () { noopLastError(); answer(list); });\n } catch (e) { answer([]); }\n });\n } catch (e) { answer([]); }\n }\n\n // Shared by every path that restarts the extension, so none of them\n // strands the override tabs.\n function restartExtension(delayMs, onRecorded) {\n // Every restart path flags the next generation's heal, so open tabs get\n // their content scripts back whichever path asked for the reload.\n try {\n if (g.chrome && g.chrome.storage && g.chrome.storage.local) {\n g.chrome.storage.local.set({__extjsDevPendingReinject: Date.now()}, noopLastError);\n }\n } catch (e) {\n // Ignore\n }\n rememberOverrideTabs(function (list) {\n if (onRecorded) { try { onRecorded(list); } catch (e) {\n // Ignore\n } }\n try { setTimeout(function () { try { g.chrome.runtime.reload(); } catch (e) {\n // Ignore\n } }, delayMs); } catch (e) {\n // Ignore\n }\n });\n }\n g.__extjsDevRestartExtension = restartExtension;\n\n // The new generation sends each recorded tab back to its browser url, once,\n // unless the tab has navigated elsewhere since.\n function restoreOverrideTabsAtBoot() {\n var chrome = g.chrome;\n if (!chrome || !chrome.storage || !chrome.storage.local || !chrome.tabs || !chrome.tabs.update) return;\n chrome.storage.local.get(OVERRIDE_TABS_KEY, function (res) {\n noopLastError();\n var rec = res && res[OVERRIDE_TABS_KEY];\n if (!rec || typeof rec !== \"object\" || !Array.isArray(rec.tabs)) return;\n try { chrome.storage.local.remove(OVERRIDE_TABS_KEY, noopLastError); } catch (e) {\n // Ignore\n }\n if (typeof rec.at !== \"number\" || Date.now() - rec.at > 30000) return;\n var kinds = overriddenKinds();\n setTimeout(function () {\n for (var i = 0; i < rec.tabs.length; i++) {\n (function (t) {\n if (!t || t.tabId == null || kinds.indexOf(t.surface) === -1) return;\n var go = function () {\n try { chrome.tabs.update(t.tabId, {url: t.url}, noopLastError); } catch (e) {\n // Ignore\n }\n };\n if (!chrome.tabs.get) return go();\n try {\n chrome.tabs.get(t.tabId, function (tab) {\n noopLastError();\n if (tab && overrideKindOf(tab.url, kinds) === t.surface) go();\n });\n } catch (e) {\n // Ignore\n }\n })(rec.tabs[i]);\n }\n }, 250);\n });\n }\n\n // Dev-loop reload without the CDP controller: content-scripts reload the\n // matching tabs at the new bundle, or re-inject in place where no registry\n // exists; service-worker/full/manifest restart the extension.\n function performDevReload(type, onDone, frame) {\n var chrome = g.chrome;\n if (!chrome) return;\n\n var fullReload = function () {\n // restartExtension flags the next generation's heal before it reloads.\n // Deferred so in-flight frames and the tab console announcement flush\n // before the SW dies; the devtools companion confirms completion.\n restartExtension(150);\n };\n\n if (type === \"content-scripts\" && chrome.scripting && chrome.tabs && chrome.tabs.query) {\n var dev = devContentScripts();\n if (dev && typeof dev.reload === \"function\") {\n try {\n dev.reload(frame && frame.changedContentScriptEntries, function (handled) {\n if (handled) { if (onDone) { try { onDone(); } catch (e) {\n // Ignore\n } } return; }\n if (reinjectContentScripts(onDone)) return;\n fullReload();\n });\n return;\n } catch (e) {\n // Ignore\n }\n }\n if (reinjectContentScripts(onDone)) return;\n }\n\n fullReload();\n }\n\n // One server-built label travels the ReloadFrame; the announcement\n // fires HERE, next to the actual reload action, so it stays true.\n\n // console.log the reload line into every open injectable tab; the patched\n // ISOLATED-world console also lands it in the dev-server log stream.\n function announceReloadInTabs(text) {\n var chrome = g.chrome;\n if (!chrome || !chrome.tabs || !chrome.tabs.query) return;\n try {\n chrome.tabs.query({}, function (tabs) {\n noopLastError();\n if (!tabs) return;\n for (var i = 0; i < tabs.length; i++) {\n (function (tab) {\n if (!tab || tab.id == null || !isInjectableUrl(tab.url)) return;\n if (chrome.scripting && chrome.scripting.executeScript) {\n try {\n chrome.scripting.executeScript(\n {target: {tabId: tab.id}, func: function (t) { console.log(t); }, args: [text]},\n noopLastError\n );\n return;\n } catch (e) {\n // Ignore\n }\n }\n // MV2 fallback (Firefox without the scripting API).\n if (chrome.tabs.executeScript) {\n try {\n chrome.tabs.executeScript(tab.id, {code: \"console.log(\" + JSON.stringify(text) + \");\"}, noopLastError);\n } catch (e) {\n // Ignore\n }\n }\n })(tabs[i]);\n }\n });\n } catch (e) {\n // Ignore\n }\n }\n\n // The scripts-replay shim (plugin-reload) keeps the SW's own\n // executeScript registry; the replay is fire-and-forget by design.\n function replayProgrammaticScripts(changedScriptFiles) {\n if (!Array.isArray(changedScriptFiles) || changedScriptFiles.length === 0) return;\n var replay = g.__extjsScriptsReplay;\n if (typeof replay !== \"function\") return;\n try {\n var outcome = replay(changedScriptFiles);\n if (outcome && typeof outcome.then === \"function\") {\n outcome.then(null, function () {});\n }\n } catch (e) {\n // Ignore\n }\n }\n\n function handleDevReloadFrame(frame) {\n var kind = frame.reloadType || \"full\";\n var label = typeof frame.label === \"string\" ? frame.label : \"\";\n // The server always builds the label; the kind-derived fallback only\n // covers a malformed frame so the announcement stays truthful.\n var fallback = kind === \"content-scripts\" ? \"content_script\"\n : kind === \"service-worker\" ? \"service_worker\"\n : \"extension\";\n var announced = \"[Extension.js] Reloading \" + (label || fallback) + \"\u2026\";\n\n // Receipt for the broker: proves this SW's message pump processed the\n // frame (a socket write proves nothing). Sent FIRST, so a full reload\n // cannot tear the worker down between the receipt and the reload, and\n // the correlation id is echoed so it confirms only this broadcast.\n var ack = {type: \"reload-ack\", reloadType: kind, label: label};\n if (frame.reloadId) ack.reloadId = frame.reloadId;\n send(ack);\n\n // A scripts/ bundle edit: re-run the executeScript calls that named it\n // on their recorded tabs, before any reload decision below.\n replayProgrammaticScripts(frame.changedScriptFiles);\n\n if (kind === \"page\") {\n // Notify-only: rspack-dev-server's livereload refreshes the open\n // surface; reloading the extension here would race it.\n return;\n }\n\n announceReloadInTabs(announced);\n performDevReload(kind, function () {}, frame);\n }\n\n function sanitize(args) {\n var out = [];\n for (var i = 0; i < args.length; i++) {\n var p = args[i];\n try {\n if (typeof p === \"string\") out.push(p.length > 2048 ? p.slice(0, 2048) + \"...\" : p);\n else if (p instanceof Error) out.push(p.name + \": \" + p.message);\n else out.push(JSON.stringify(p));\n } catch (e) {\n out.push(String(p));\n }\n if (out.join(\" \").length > 8192) break;\n }\n return out;\n }\n\n function flush() {\n while (queue.length && open && socket) {\n var f = queue.shift();\n try { socket.send(JSON.stringify(f)); } catch (e) { break; }\n }\n }\n\n function send(frame) {\n if (open && socket) {\n try { socket.send(JSON.stringify(frame)); return; } catch (e) {\n // Ignore\n }\n }\n if (queue.length < MAX_QUEUE) queue.push(frame);\n parkPendingError(frame);\n }\n\n // The queue above lives in this context's memory only, so a background\n // that throws while it evaluates dies with the error still queued: the\n // socket has not finished its handshake yet. Park error frames in\n // extension storage too, where the NEXT context to connect can ship them.\n // Best effort by construction: the write is asynchronous, so a teardown\n // can cut it before the browser records anything.\n var parkedErrors = [];\n function storageLocalArea() {\n try {\n var ns = (g.chrome && g.chrome.storage) || (g.browser && g.browser.storage);\n var area = ns && ns.local;\n if (!area || typeof area.get !== \"function\" || typeof area.set !== \"function\") return null;\n return area;\n } catch (e) { return null; }\n }\n function parkPendingError(frame) {\n if (open) return;\n if (!frame || frame.type !== \"log\" || !frame.event || frame.event.level !== \"error\") return;\n var area = storageLocalArea();\n if (!area) return;\n // The whole list is rewritten on every park, so two errors in one turn\n // cannot lose each other to a read-modify-write, and the cap holds.\n parkedErrors.push(frame);\n while (parkedErrors.length > MAX_PENDING_ERRORS) parkedErrors.shift();\n var items = {};\n items[PENDING_ERRORS_KEY] = parkedErrors;\n try { area.set(items, noopLastError); } catch (e) {\n // Ignore\n }\n }\n function parkedHere(id) {\n for (var i = 0; i < parkedErrors.length; i++) {\n var ev = parkedErrors[i] && parkedErrors[i].event;\n if (ev && ev.id === id) return true;\n }\n return false;\n }\n // Ship what an earlier context parked and never got to send. Entries from\n // another build (a different instance id) or from an older run are dropped,\n // and the key is cleared on read so one failure is reported once.\n function flushPendingErrors() {\n var area = storageLocalArea();\n if (!area) return;\n var read = false;\n function onRead(res) {\n if (read) return;\n read = true;\n var list = res && res[PENDING_ERRORS_KEY];\n if (!list || !list.length) return;\n try {\n if (typeof area.remove === \"function\") area.remove(PENDING_ERRORS_KEY, noopLastError);\n } catch (e) {\n // Ignore\n }\n var now = Date.now();\n for (var i = 0; i < list.length; i++) {\n var frame = list[i];\n var ev = frame && frame.event;\n if (!ev || ev.runId !== INSTANCE_ID) continue;\n // This context parked it, so the queue flush above already has it.\n if (parkedHere(ev.id)) continue;\n if (typeof ev.timestamp !== \"number\" || now - ev.timestamp > PENDING_ERROR_TTL) continue;\n send(frame);\n }\n }\n try {\n var ret = area.get(PENDING_ERRORS_KEY, function (res) { noopLastError(); onRead(res); });\n if (ret && typeof ret.then === \"function\") ret.then(onRead, function () {});\n } catch (e) {\n // Ignore\n }\n }\n\n // The baked PORT is a snapshot of an ephemeral port. After a dev-server\n // restart, the port file shipped in the unpacked extension names the\n // live one, and unpacked resources are read from disk on every fetch.\n function refreshControlPort(done) {\n try {\n var runtime = (g.chrome && g.chrome.runtime) || (g.browser && g.browser.runtime);\n if (!runtime || typeof runtime.getURL !== \"function\" || typeof g.fetch !== \"function\") { done(); return; }\n g.fetch(runtime.getURL(\"extension-js-control.json\"), {cache: \"no-store\"})\n .then(function (res) { return res.json(); })\n .then(function (data) {\n var next = data && parseInt(data.port, 10);\n if (next && next > 0 && next < 65536 && next !== PORT) PORT = next;\n done();\n })\n .catch(function () { done(); });\n } catch (e) { done(); }\n }\n\n function schedule() {\n var delay = backoff;\n backoff = Math.min(backoff * 2, MAX_BACKOFF);\n connectFailures++;\n try {\n setTimeout(function () {\n // Two straight failures make the baked port suspect (a restarted\n // server dials out on a new port): re-resolve before re-dialing.\n if (connectFailures >= 2) refreshControlPort(connect);\n else connect();\n }, delay);\n } catch (e) {\n // Ignore\n }\n }\n\n function connect() {\n try {\n socket = new WS(\"ws://\" + HOST + \":\" + PORT + \"/extjs-control\");\n } catch (e) {\n schedule();\n return;\n }\n socket.onopen = function () {\n open = true;\n backoff = 250;\n connectFailures = 0;\n try {\n socket.send(JSON.stringify({type: \"hello\", v: 1, role: \"producer\", instanceId: INSTANCE_ID, acksReloads: true}));\n } catch (e) {\n // Ignore\n }\n flush();\n // Hello went first and this context's own queue is out; now replay any\n // error a previous context parked before it died.\n flushPendingErrors();\n };\n socket.onmessage = function (ev) {\n var frame;\n try { frame = JSON.parse(typeof ev.data === \"string\" ? ev.data : \"\"); } catch (e) { return; }\n if (frame && frame.type === \"command\") {\n try { executeCommand(frame); } catch (e) { replyErr(frame.cmdId, \"ExecutorError\", e); }\n } else if (frame && frame.type === \"reload\") {\n // Dev-loop reload broadcast (see broker.broadcastReload).\n // Fire-and-forget: no result frame is expected.\n try { handleDevReloadFrame(frame); } catch (e) {\n // Ignore\n }\n } else if (frame && frame.type === \"ping\") {\n // Server keepalive: merely RECEIVING this resets the MV3 SW's ~30s\n // idle timer (Chrome 116+), keeping it reachable. Nothing to do.\n }\n };\n socket.onclose = function () {\n open = false;\n socket = null;\n schedule();\n };\n socket.onerror = function () {\n try { socket && socket.close(); } catch (e) {\n // Ignore\n }\n };\n }\n\n // Uncaught errors don't route through the patched console; the handlers\n // below forward them, using these signatures to skip already-logged throws.\n var recentErrorSigs = [];\n function errorSig(message, stack) {\n return String(message == null ? \"\" : message) + \"::\" + String(stack == null ? \"\" : stack).slice(0, 200);\n }\n function noteErrorSig(sig) {\n recentErrorSigs.push({sig: sig, t: Date.now()});\n if (recentErrorSigs.length > 50) recentErrorSigs.shift();\n }\n function errorSigSeenRecently(sig) {\n var now = Date.now();\n for (var i = recentErrorSigs.length - 1; i >= 0; i--) {\n if (now - recentErrorSigs[i].t > 3000) { recentErrorSigs.splice(i, 1); continue; }\n if (recentErrorSigs[i].sig === sig) return true;\n }\n return false;\n }\n\n LEVELS.forEach(function (level) {\n var orig = typeof consoleRef[level] === \"function\"\n ? consoleRef[level].bind(consoleRef)\n : function () {};\n consoleRef[level] = function () {\n try {\n if (level === \"error\") {\n for (var ai = 0; ai < arguments.length; ai++) {\n if (arguments[ai] instanceof Error) noteErrorSig(errorSig(arguments[ai].message, arguments[ai].stack));\n }\n }\n if (!isDevServerChatter(level, arguments)) 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 // sendMessage path: how the devtools page relays, since its port would be\n // mistaken for the extension's own devtools connection. Also accepts any\n // surface still relaying the old way.\n try {\n var rt = g.chrome;\n if (rt && rt.runtime && rt.runtime.onMessage) {\n rt.runtime.onMessage.addListener(function (msg, sender) {\n if (!msg || !msg.__extjsBridgeLog) return;\n shipRelayedLog(msg.__extjsBridgeLog, sender);\n });\n }\n } catch (e) {\n // Ignore\n }\n\n // Dev registrations outlive runtime.reload(), while the new generation's\n // static manifest already names the current files: drop them first, or\n // every navigation from here on runs the content script twice.\n function unregisterDevContentScripts(onDone) {\n var chrome = g.chrome;\n var done = function () { if (onDone) { try { onDone(); } catch (e) {\n // Ignore\n } } };\n try {\n if (\n !chrome.scripting ||\n !chrome.scripting.getRegisteredContentScripts ||\n !chrome.scripting.unregisterContentScripts\n ) return done();\n chrome.scripting.getRegisteredContentScripts(function (existing) {\n try { void chrome.runtime.lastError; } catch (e) {\n // Ignore\n }\n var ids = [];\n if (existing) {\n for (var i = 0; i < existing.length; i++) {\n var id = existing[i] && existing[i].id;\n if (typeof id === \"string\" && id.indexOf(\"extjs-dev-cs-\") === 0) ids.push(id);\n }\n }\n if (!ids.length) return done();\n try {\n chrome.scripting.unregisterContentScripts({ids: ids}, function () { noopLastError(); done(); });\n } catch (e) { done(); }\n });\n } catch (e) { done(); }\n }\n\n // One heal per producer generation: onInstalled and the pre-reload storage\n // flag can both fire on the same boot, and each would inject once more.\n var bootHealDone = false;\n function healOpenTabsAtBoot() {\n if (bootHealDone) return;\n bootHealDone = true;\n setTimeout(function () {\n var dev = devContentScripts();\n if (dev && typeof dev.heal === \"function\") {\n try { dev.heal(); return; } catch (e) {\n // Ignore\n }\n }\n unregisterDevContentScripts(function () {\n try { reinjectContentScripts(undefined, {register: false, completeOnly: true}); } catch (e) {\n // Ignore\n }\n });\n }, 250);\n }\n\n // Chrome never injects manifest content scripts into already-open tabs, so\n // fire one reinject on onInstalled (which skips idle-stop SW wakes). Not\n // on the first install: the tabs the browser launched with load beside\n // the extension and Chrome injects them itself.\n try {\n var rtBoot = g.chrome;\n if (rtBoot && rtBoot.runtime && rtBoot.runtime.onInstalled) {\n rtBoot.runtime.onInstalled.addListener(function (details) {\n if (details && details.reason === \"install\") return;\n try { healOpenTabsAtBoot(); } catch (e) {\n // Ignore\n }\n });\n }\n } catch (e) {\n // Ignore\n }\n\n try { restoreOverrideTabsAtBoot(); } catch (e) {\n // Ignore\n }\n\n // runtime.reload() does NOT fire onInstalled (verified Chromium 146), so\n // consume the pre-reload storage flag here (fresh only) to heal open tabs.\n try {\n var stBoot = g.chrome;\n if (stBoot && stBoot.storage && stBoot.storage.local) {\n stBoot.storage.local.get(\"__extjsDevPendingReinject\", function (res) {\n noopLastError();\n var ts = res && res.__extjsDevPendingReinject;\n if (ts == null) return;\n try { stBoot.storage.local.remove(\"__extjsDevPendingReinject\", noopLastError); } catch (e) {\n // Ignore\n }\n if (typeof ts !== \"number\" || Date.now() - ts > 30000) return;\n healOpenTabsAtBoot();\n });\n }\n } catch (e) {\n // Ignore\n }\n\n connect();\n } catch (e) {\n // Ignore\n }\n})();\n";
3
3
  export declare const BRIDGE_RELAY_SOURCE = ";(function () {\n try {\n var g = (typeof globalThis === \"object\" && globalThis) ? globalThis : this;\n if (!g || g.__extjsBridgeRelayInstalled) return;\n\n var CONTEXT = \"__EXTJS_CONTEXT__\";\n var chrome = g.chrome;\n if (!chrome || !chrome.runtime) return;\n var canRelay = typeof chrome.runtime.connect === \"function\";\n\n g.__extjsBridgeRelayInstalled = true;\n var consoleRef = g.console || {};\n var LEVELS = [\"log\", \"info\", \"warn\", \"error\", \"debug\", \"trace\"];\n // Same filter as the producer: HMR chatter is not the page's output.\n function isDevServerChatter(level, args) {\n if (level === \"warn\" || level === \"error\") return false;\n var first = args && args[0];\n return typeof first === \"string\" && /^\\[(?:HMR|webpack-dev-server|rspack-dev-server)\\] /.test(first);\n }\n\n // Every engine's wording for \"this document's CSP forbids eval\". Gecko\n // says \"call to eval() blocked by CSP\", Chromium quotes the directive.\n function cspBlocksEval(message) {\n return /call to eval\\(\\)|blocked by (?:the )?CSP|Content Security Policy|unsafe-eval/i.test(String(message == null ? \"\" : message));\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) { 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 // The devtools page is the one surface whose port carries no sender.tab,\n // which is how a devtools extension recognizes its own devtools connection.\n // Ours would arrive first and be taken for it, so that surface sends\n // messages instead and the extension's port topology matches production.\n var relayOverPort = canRelay && CONTEXT !== \"devtools\";\n var canSend = typeof chrome.runtime.sendMessage === \"function\";\n\n function sendLog(payload) {\n if (!canSend) return;\n try {\n chrome.runtime.sendMessage({__extjsBridgeLog: payload}, function () {\n try { void chrome.runtime.lastError; } catch (e) {\n // Ignore\n }\n });\n } catch (e) {\n // Ignore\n }\n }\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 (!relayOverPort) 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 if (!relayOverPort) return sendLog(payload);\n\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 if (!isDevServerChatter(level, arguments)) postLog({level: level, context: CONTEXT, messageParts: sanitize([].slice.call(arguments)), url: here()});\n } catch (e) {\n // Ignore\n }\n return orig.apply(consoleRef, arguments);\n };\n });\n\n function shipUncaughtError(message, stack, url) {\n try {\n var sig = errorSig(message, stack);\n if (errorSigSeenRecently(sig)) return;\n noteErrorSig(sig);\n postLog({level: \"error\", context: CONTEXT, messageParts: sanitize([String(message) + (stack ? \"\\n\" + stack : \"\")]), url: url || here()});\n } catch (e) {\n // Ignore\n }\n }\n try {\n if (typeof g.addEventListener === \"function\") {\n g.addEventListener(\"error\", function (ev) {\n var err = ev && ev.error;\n var message = (err && err.message) || (ev && ev.message) || \"Uncaught error\";\n shipUncaughtError(message, err && err.stack, (ev && ev.filename) || undefined);\n });\n g.addEventListener(\"unhandledrejection\", function (ev) {\n var reason = ev && ev.reason;\n var message = (reason && reason.message) || (reason != null ? String(reason) : \"Unhandled rejection\");\n shipUncaughtError(\"Unhandled promise rejection: \" + message, reason && reason.stack, undefined);\n });\n }\n } catch (e) {\n // Ignore\n }\n\n // The SW executor can't reach another extension page's DOM or eval there,\n // so THIS surface answers inspect/eval requests for its own context.\n try {\n if (chrome.runtime && chrome.runtime.onMessage) {\n chrome.runtime.onMessage.addListener(function (msg, _sender, sendResponse) {\n if (!msg || !msg.__extjsEvalRequest) return;\n if (!msg.target || msg.target.context !== CONTEXT) return; // not for me\n var respondValue = function (value) {\n try { sendResponse({ok: true, value: value}); }\n catch (eSend) { sendResponse({ok: true, value: String(value)}); }\n };\n var respondError = function (e) {\n var emsg = (e && e.message) || String(e);\n if (cspBlocksEval(emsg)) {\n sendResponse({ok: false, error: {name: \"Unsupported\", code: \"csp_blocks_eval\", message: \"eval of a string is blocked in the \" + CONTEXT + \" page by the extension's content_security_policy. Use extension inspect --context \" + CONTEXT + \" to read its DOM instead. Original: \" + emsg}});\n } else {\n sendResponse({ok: false, error: {name: (e && e.name) || \"EvalError\", message: emsg}});\n }\n };\n try {\n var value = (0, eval)(String((msg.args && msg.args.expression) || \"\"));\n // A promise is not structured-cloneable: Chrome throws on the reply\n // and Firefox drops it on the way out, which the background read as\n // \"surface is not open\" while the page kept running the expression.\n // Settle it first, the way the background eval already does.\n if (value && typeof value.then === \"function\") {\n value.then(respondValue, respondError);\n } else {\n respondValue(value);\n }\n } catch (e) {\n respondError(e);\n }\n return true; // responded, possibly later\n });\n }\n } catch (e) {\n // Ignore\n }\n\n try {\n if (chrome.runtime && chrome.runtime.onMessage) {\n chrome.runtime.onMessage.addListener(function (msg, _sender, sendResponse) {\n if (!msg || !msg.__extjsInspectRequest) return;\n if (!msg.target || msg.target.context !== CONTEXT) return; // not for me\n try {\n var args = msg.args || {};\n var wantHtml = !args.include || args.include.indexOf(\"html\") !== -1;\n var cap = args.maxBytes || 262144;\n function countShadow(root) {\n var n = 0, els = root.querySelectorAll(\"*\");\n for (var i = 0; i < els.length; i++) { if (els[i].shadowRoot) { n++; n += countShadow(els[i].shadowRoot); } }\n return n;\n }\n var doc = g.document;\n var roots = doc.querySelectorAll('#extension-root,[data-extension-root]');\n var snap = {\n context: CONTEXT,\n url: here(),\n title: doc.title,\n summary: {\n htmlLength: doc.documentElement.outerHTML.length,\n scriptCount: doc.querySelectorAll(\"script\").length,\n styleCount: doc.querySelectorAll(\"style,link[rel=stylesheet]\").length,\n extensionRootCount: roots.length,\n openShadowRoots: countShadow(doc),\n bodyChildCount: doc.body ? doc.body.children.length : 0\n }\n };\n if (wantHtml) {\n var html = doc.documentElement.outerHTML;\n if (cap > 0 && html.length > cap) { snap.html = html.slice(0, cap); snap.htmlTruncated = true; }\n else { snap.html = html; }\n }\n sendResponse({ok: true, value: snap});\n } catch (e) {\n sendResponse({ok: false, error: {name: \"InspectError\", message: String(e)}});\n }\n return true; // responded\n });\n }\n } catch (e) {\n // Ignore\n }\n } catch (e) {\n // Ignore\n }\n})();\n";
4
4
  export interface BuildRelayOptions {
5
5
  context: string;
@@ -0,0 +1,10 @@
1
+ import type { Compiler } from '@rspack/core';
2
+ interface RefreshServer<Client> {
3
+ webSocketServer?: {
4
+ clients: Client[];
5
+ } | null;
6
+ sendMessage: (clients: Client[], type: string, data?: unknown) => void;
7
+ }
8
+ export declare function htmlSourcesToRefresh(modifiedFiles: Iterable<string> | undefined, fileDependencies: Iterable<string>, outputPath: string): string[];
9
+ export declare function attachHtmlRefresh<Client>(compiler: Compiler, server: RefreshServer<Client>): void;
10
+ export {};
@@ -1,3 +1,11 @@
1
+ import * as net from 'node:net';
2
+ export interface PortReservation {
3
+ port: number;
4
+ host: string;
5
+ server: net.Server;
6
+ release(): Promise<void>;
7
+ }
8
+ export declare function holdPortNow(port: number, host: string, bindHost?: string): PortReservation;
1
9
  export interface PortAllocation {
2
10
  port: number;
3
11
  instanceId: string;
@@ -11,9 +19,15 @@ export declare class PortManager {
11
19
  private readonly basePort;
12
20
  private currentInstance;
13
21
  private reservation;
22
+ private holdOnClose;
14
23
  constructor(basePort?: number);
15
24
  allocatePorts(requestedPort?: number, host?: string): Promise<PortAllocation>;
16
25
  getCurrentInstance(): LocalInstanceInfo | null;
26
+ takeReservation(): PortReservation | null;
27
+ holdPortAcrossRestart(): void;
28
+ shouldHoldPortOnClose(): boolean;
29
+ adoptHeldPort(reservation: PortReservation): void;
30
+ endRestartHold(): void;
17
31
  releaseReservedPort(): Promise<void>;
18
32
  terminateCurrentInstance(): Promise<void>;
19
33
  }
@@ -0,0 +1,9 @@
1
+ import * as http from 'node:http';
2
+ import { type PortReservation } from './port-manager.js';
3
+ export interface ReservedHttpServerOptions {
4
+ takeReservation: () => PortReservation | null;
5
+ onBindError: (error: NodeJS.ErrnoException) => void;
6
+ holdOnClose?: () => boolean;
7
+ onHeld?: (reservation: PortReservation) => void;
8
+ }
9
+ export declare function reservedHttpServer(options: ReservedHttpServerOptions): (serverOptions: http.ServerOptions | undefined, app: http.RequestListener) => http.Server;
@@ -1,4 +1,4 @@
1
- export type DevSessionRestartReason = 'scripts' | 'html' | 'icons' | 'json' | 'env';
1
+ export type DevSessionRestartReason = 'scripts' | 'html' | 'icons' | 'json' | 'env' | 'public';
2
2
  export interface DevSessionRestartRequest {
3
3
  reason: DevSessionRestartReason;
4
4
  pathAfter?: string;