// ==UserScript== // @name JSON Formatter // @version 0.7.3 // @description Makes JSON easy to read. Open source. // @namespace json-formatter // @author Converter Script // @match *://*/* // @grant GM_setValue // @grant GM_getValue // @grant GM_listValues // @grant GM_deleteValue // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @grant GM_openInTab // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABVJJREFUeNq8V1sspVcU3jg4rq3LEB13SlWbikv0oR1pzGjiElRCeas2Hj1LE6YmM8JLIyNpwoOXJo2gSMpDU4mkWhmDqMvQIhwd17jNYTg42P2+P7/JcdzOOWO6ki85l70ue6+11/q2RlgnzoAv8DbgCngCkcAsoAcMwAawCexbYlBjwRp7wAcIysnJyd3Y2MjW6/VhgGZ3d1e7vr4uXF1dj729vY8cHBw2gb9mZ2d/wvpnwKIa0PFlxu2ucMz/3IODgz+KjY39bnFxMWVubk6DzyIuLk4EBQUJOBUGg0E4OTmJzc1N8fz5czE5OSmmpqZONBrNEoJtPjg4aFGDeQlISwPgrgOys7O/xQ6L9/f3tQkJCSItLU2Eh4cLPz8/4ebmJrRareL88PBQCWRvb0/AqZienhY9PT2ir6/vcHl5+Z+VlZUfYK9LPZETS448KjMz82lSUpLMz8+Xzc3Ncn5+XsKRtES4jutbW1tlQUGBxGmtwGYdEK3av1QcWFR5eXlTyKusqKiQ4+PjEruTtgj1JiYmZGVlpcRpGWG7Foi4Koh3MjIyniQmJsqHDx9K5F2enJzI1xHqLywsyAcPHsjQ0NAF+Pie6b3IubaoqKgWi2R5eblEQb22c/Mg7t+/L1EzvK7F9Hcm776+vinY+UF6erocHh6Wx8fH8iaF9kZGRiQK2WBnZ/cEPj81TYUPiq47LCxMtre3S1wd+SaExdnZ2Slxfdfh8xGbGiOwQ8GFr62t3cHuRXJysnK13oQ4OjqK+Ph4UVhY6IpTSMFPoQzAGYX3JRqJY2pqqkAqzinW1NSIsrIyq5xxPfXMhfbv3bvn4unpGYSvHyptdnt7+3N0PBEREaFEeZGhra0tqwLg+osCp/3IyEgREhLCrx9zFry1s7NzG01H3Lp165yh7u5uUVJSIqqrq60KoL6+Xnh5eSn65uLj48NUOI6OjiopcENxuAUEBHCoiP9D6CcqKsqDU5UBOKFjafz9/ZXebiqYaspRcie2CPWoTztnGg78BAYG0pkHA3DkMHF3dxeYYK8WtbS0KDVBI0yBLUI96tMO7ZnWAf2RDjCAI3t7+xNOMjSLV4vu3r2r5HFoaEg0NDTYFAD1qE87tHcqR0dHPBmSFyMDMCAiA0amwNg9c3zcAcew+Q3AHb728+lNoP7pSZwKR7dOpyNReckADsBitjB4BBiORTtDU7v281XCADBlORP0DOCF0WicIJNBN7z0Ttsil+mRxvX395OY6PjdBdfi65iYGCMJhDnpwP3ntiSO0qq+z/XUo76pIP8SBalHuv7G/9+cBpWAQTRXWloqQaHOGRscHFRYkTXC9dQzF9SaREH+AZ9E0qvmhAH0CHzP2NXVZTMDsoQhdXR0rGP30/BZBfiZ8sBP0IwmsrKyJFrkjZERU1IyNjYmMXMG4Iu4Y07N2IeLcSUPyQXJYG6SES0tLUmM4d9VZvyV6k8hoadC0riGxRrk6V3cDFdOLQ8Pj3P32xrh1URdiaqqqtHGxkb+9Cvwo/p6upCS86lVi5thIIfD9ZRoUDbtnHrUV3e+rVLzKLONX0jNyd8fo3vpcnNzd9va2pSUWPMuIKltamraiI6O/lPN+WPgfXPnV72MAoEsdMl8jOowFKcvmQzTQlbDl5Gzs7MyWJAupY2zw62uroqZmRl9XV3ds97eXmekdAd2WoFfgAXzl9G1b0MgFvgC+MzFxSUIVXwAMqEFozGied0muUBf16GVO2DwrA0MDDihA/qrxUY28jMwAexY8zY0TwmJYjDwAUAy+Z76m6cKPjxfqGCH+011+i8771Wv4/8EGADDWPCX+M7kKwAAAABJRU5ErkJggg== // @run-at document-end // ==/UserScript== console.log("Script start:",performance.now());const e=!0,t=e=>e,o="passthrough";let s,c={createHTML:t,createScript:t,createScriptURL:t},i=!1;const r=()=>{try{void 0!==window.isSecureContext&&window.isSecureContext&&window.trustedTypes&&window.trustedTypes.createPolicy&&(i=!0,trustedTypes.defaultPolicy?(l("TT Default Policy exists"),c=window.trustedTypes.createPolicy("default",c),s=trustedTypes.defaultPolicy,l(`Created custom passthrough policy, in case the default policy is too restrictive: Use Policy '${o}' in var 'TTP':`,c)):s=c=window.trustedTypes.createPolicy("default",c),l("Trusted-Type Policies: TTP:",c,"TTP_default:",s))}catch(e){l(e)}},l=(...e)=>{console.log(...e)};r(); (function() { // #region Logging const SCRIPT_NAME = "JSON Formatter"; const _log = (...args) => {}; const _warn = (...args) => console.warn(`[${typeof SCRIPT_NAME === 'string' ? SCRIPT_NAME : '[USERSCRIPT_CONVERTED]'}]`, ...args); const _error = (...args) => { let e = args[0]; console.error(`[${typeof SCRIPT_NAME === 'string' ? SCRIPT_NAME : '[USERSCRIPT_CONVERTED]'}]`, ...args); } // #endregion // #region Unified Polyfill // #region Messaging implementation function createEventBus( scopeId, type = "page", // "page" or "iframe" { allowedOrigin = "*", children = [], parentWindow = null } = {} ) { if (!scopeId) throw new Error("createEventBus requires a scopeId"); const handlers = {}; function handleIncoming(ev) { if (allowedOrigin !== "*" && ev.origin !== allowedOrigin) return; const msg = ev.data; if (!msg || msg.__eventBus !== true || msg.scopeId !== scopeId) return; const { event, payload } = msg; // PAGE: if it's an INIT from an iframe, adopt it if (type === "page" && event === "__INIT__") { const win = ev.source; if (win && !children.includes(win)) { children.push(win); } return; } (handlers[event] || []).forEach((fn) => fn(payload, { origin: ev.origin, source: ev.source }) ); } window.addEventListener("message", handleIncoming); function emitTo(win, event, payload) { const envelope = { __eventBus: true, scopeId, event, payload, }; win.postMessage(envelope, allowedOrigin); } // IFRAME: announce to page on startup if (type === "iframe") { setTimeout(() => { const pw = parentWindow || window.parent; if (pw && pw.postMessage) { emitTo(pw, "__INIT__", null); } }, 0); } return { on(event, fn) { handlers[event] = handlers[event] || []; handlers[event].push(fn); }, off(event, fn) { if (!handlers[event]) return; handlers[event] = handlers[event].filter((h) => h !== fn); }, /** * Emits an event. * @param {string} event - The event name. * @param {any} payload - The event payload. * @param {object} [options] - Emission options. * @param {Window} [options.to] - A specific window to target. If provided, message is ONLY sent to the target. */ emit(event, payload, { to } = {}) { // If a specific target window is provided, send only to it and DO NOT dispatch locally. // This prevents a port from receiving its own messages. if (to) { if (to && typeof to.postMessage === "function") { emitTo(to, event, payload); } return; // Exit after targeted send. } // For broadcast messages (no 'to' target), dispatch locally first. (handlers[event] || []).forEach((fn) => fn(payload, { origin: location.origin, source: window }) ); // Then propagate the broadcast to other windows. if (type === "page") { children.forEach((win) => emitTo(win, event, payload)); } else { const pw = parentWindow || window.parent; if (pw && pw.postMessage) { emitTo(pw, event, payload); } } }, }; } function createRuntime(type = "background", bus) { let nextId = 1; const pending = {}; const msgListeners = []; let nextPortId = 1; const ports = {}; const onConnectListeners = []; function parseArgs(args) { let target, message, options, callback; const arr = [...args]; if (arr.length === 0) { throw new Error("sendMessage requires at least one argument"); } if (arr.length === 1) { return { message: arr[0] }; } // last object could be options if ( arr.length && typeof arr[arr.length - 1] === "object" && !Array.isArray(arr[arr.length - 1]) ) { options = arr.pop(); } // last function is callback if (arr.length && typeof arr[arr.length - 1] === "function") { callback = arr.pop(); } if ( arr.length === 2 && (typeof arr[0] === "string" || typeof arr[0] === "number") ) { [target, message] = arr; } else { [message] = arr; } return { target, message, options, callback }; } if (type === "background") { bus.on("__REQUEST__", ({ id, message }, { source }) => { let responded = false, isAsync = false; function sendResponse(resp) { if (responded) return; responded = true; // Target the response directly back to the window that sent the request. bus.emit("__RESPONSE__", { id, response: resp }, { to: source }); } const results = msgListeners .map((fn) => { try { // msg, sender, sendResponse const ret = fn(message, { id, tab: { id: source } }, sendResponse); if (ret === true || (ret && typeof ret.then === "function")) { isAsync = true; return ret; } return ret; } catch (e) { _error(e); } }) .filter((r) => r !== undefined); const promises = results.filter((r) => r && typeof r.then === "function"); if (!isAsync && promises.length === 0) { const out = results.length === 1 ? results[0] : results; sendResponse(out); } else if (promises.length) { Promise.all(promises).then((vals) => { if (!responded) { const out = vals.length === 1 ? vals[0] : vals; sendResponse(out); } }); } }); } if (type !== "background") { bus.on("__RESPONSE__", ({ id, response }) => { const entry = pending[id]; if (!entry) return; entry.resolve(response); if (entry.callback) entry.callback(response); delete pending[id]; }); } function sendMessage(...args) { // Background should be able to send message to itself // if (type === "background") { // throw new Error("Background cannot sendMessage to itself"); // } const { target, message, callback } = parseArgs(args); const id = nextId++; const promise = new Promise((resolve) => { pending[id] = { resolve, callback }; bus.emit("__REQUEST__", { id, message }); }); return promise; } bus.on("__PORT_CONNECT__", ({ portId, name }, { source }) => { if (type !== "background") return; const backgroundPort = makePort("background", portId, name, source); ports[portId] = backgroundPort; onConnectListeners.forEach((fn) => fn(backgroundPort)); // send back a CONNECT_ACK so the client can // start listening on its end: bus.emit("__PORT_CONNECT_ACK__", { portId, name }, { to: source }); }); // Clients handle the ACK and finalize their Port object by learning the remote window. bus.on("__PORT_CONNECT_ACK__", ({ portId, name }, { source }) => { if (type === "background") return; // ignore const p = ports[portId]; if (!p) return; // Call the port's internal finalize method to complete the handshake if (p._finalize) { p._finalize(source); } }); // Any port message travels via "__PORT_MESSAGE__" bus.on("__PORT_MESSAGE__", (envelope, { source }) => { const { portId } = envelope; const p = ports[portId]; if (!p) return; p._receive(envelope, source); }); // Any port disconnect: bus.on("__PORT_DISCONNECT__", ({ portId }) => { const p = ports[portId]; if (!p) return; p._disconnect(); delete ports[portId]; }); // Refactored makePort to correctly manage internal state and the connection handshake. function makePort(side, portId, name, remoteWindow) { let onMessageHandlers = []; let onDisconnectHandlers = []; let buffer = []; // Unique instance ID for this port instance const instanceId = Math.random().toString(36).slice(2) + Date.now(); // These state variables are part of the closure and are updated by _finalize let _ready = side === "background"; function _drainBuffer() { buffer.forEach((m) => _post(m)); buffer = []; } function _post(msg) { // Always use the 'to' parameter for port messages, making them directional. // Include senderInstanceId bus.emit( "__PORT_MESSAGE__", { portId, msg, senderInstanceId: instanceId }, { to: remoteWindow } ); } function postMessage(msg) { if (!_ready) { buffer.push(msg); } else { _post(msg); } } function _receive(envelope, source) { // envelope: { msg, senderInstanceId } if (envelope.senderInstanceId === instanceId) return; // Don't dispatch to self onMessageHandlers.forEach((fn) => fn(envelope.msg, { id: portId, tab: { id: source } }) ); } function disconnect() { // Also use the 'to' parameter for disconnect messages bus.emit("__PORT_DISCONNECT__", { portId }, { to: remoteWindow }); _disconnect(); delete ports[portId]; } function _disconnect() { onDisconnectHandlers.forEach((fn) => fn()); onMessageHandlers = []; onDisconnectHandlers = []; } // This function is called on the client port when the ACK is received from background. // It updates the port's state, completing the connection. function _finalize(win) { remoteWindow = win; // <-- This is the crucial part: learn the destination _ready = true; _drainBuffer(); } return { name, sender: { id: portId, }, onMessage: { addListener(fn) { onMessageHandlers.push(fn); }, removeListener(fn) { onMessageHandlers = onMessageHandlers.filter((x) => x !== fn); }, }, onDisconnect: { addListener(fn) { onDisconnectHandlers.push(fn); }, removeListener(fn) { onDisconnectHandlers = onDisconnectHandlers.filter((x) => x !== fn); }, }, postMessage, disconnect, // Internal methods used by the runtime _receive, _disconnect, _finalize, // Expose the finalizer for the ACK handler }; } function connect(connectInfo = {}) { if (type === "background") { throw new Error("Background must use onConnect, not connect()"); } const name = connectInfo.name || ""; const portId = nextPortId++; // create the client side port // remoteWindow is initially null; it will be set by _finalize upon ACK. const clientPort = makePort("client", portId, name, null); ports[portId] = clientPort; // fire the connect event across the bus bus.emit("__PORT_CONNECT__", { portId, name }); return clientPort; } function onConnect(fn) { if (type !== "background") { throw new Error("connect event only fires in background"); } onConnectListeners.push(fn); } return { // rpc: sendMessage, onMessage: { addListener(fn) { msgListeners.push(fn); }, removeListener(fn) { const i = msgListeners.indexOf(fn); if (i >= 0) msgListeners.splice(i, 1); }, }, // port API: connect, onConnect: { addListener(fn) { onConnect(fn); }, removeListener(fn) { const i = onConnectListeners.indexOf(fn); if (i >= 0) onConnectListeners.splice(i, 1); }, }, }; } // #region Abstraction layer Handle postmesage for (function () { const pendingRequests = new Map(); // requestId -> { resolve, reject, timeout } let nextRequestId = 1; window.addEventListener("message", async (event) => { const { type, requestId, method, args } = event.data; if (type === "abstraction-request") { try { let result; switch (method) { case "_storageSet": result = await _storageSet(args[0]); break; case "_storageGet": result = await _storageGet(args[0]); break; case "_storageRemove": result = await _storageRemove(args[0]); break; case "_storageClear": result = await _storageClear(); break; case "_cookieList": result = await _cookieList(args[0]); break; case "_cookieSet": result = await _cookieSet(args[0]); break; case "_cookieDelete": result = await _cookieDelete(args[0]); break; case "_fetch": result = await _fetch(args[0], args[1]); break; case "_registerMenuCommand": result = _registerMenuCommand(args[0], args[1]); break; case "_openTab": result = _openTab(args[0], args[1]); break; case "_initStorage": result = await _initStorage(); break; default: throw new Error(`Unknown abstraction method: ${method}`); } event.source.postMessage({ type: "abstraction-response", requestId, success: true, result, }); } catch (error) { event.source.postMessage({ type: "abstraction-response", requestId, success: false, error: { message: error.message, stack: error.stack, }, }); } } }); _log("[PostMessage Handler] Abstraction layer message handler initialized"); })(); // #endregion // #region Abstraction Layer Userscript Target async function _storageSet(items) { try { for (const key in items) { if (items.hasOwnProperty(key)) { await GM_setValue(key, items[key]); } } return Promise.resolve(); } catch (e) { _error("GM_setValue error:", e); return Promise.reject(e); } } async function _storageGet(keys) { if (!keys) { keys = null; } if ( Array.isArray(keys) && (keys.length === 0 || [null, undefined].includes(keys[0])) ) { keys = null; } try { const results = {}; let keyList = []; let defaults = {}; let requestedKeys = []; if (keys === null) { keyList = await GM_listValues(); requestedKeys = [...keyList]; } else if (typeof keys === "string") { keyList = [keys]; requestedKeys = [keys]; } else if (Array.isArray(keys)) { keyList = keys; requestedKeys = [...keys]; } else if (typeof keys === "object" && keys !== null) { keyList = Object.keys(keys); requestedKeys = [...keyList]; defaults = keys; } else { _error("_storageGet error: Invalid keys format", keys); return Promise.reject(new Error("Invalid keys format for get")); } for (const key of keyList) { const defaultValue = defaults.hasOwnProperty(key) ? defaults[key] : undefined; const storedValue = await GM_getValue(key, defaultValue); results[key] = storedValue; } const finalResult = {}; for (const key of requestedKeys) { if (results.hasOwnProperty(key)) { finalResult[key] = results[key]; } else if (defaults.hasOwnProperty(key)) { finalResult[key] = defaults[key]; } } return Promise.resolve(finalResult); } catch (e) { _error("GM_getValue/GM_listValues error:", e); return Promise.reject(e); } } async function _storageRemove(keysToRemove) { try { let keyList = []; if (typeof keysToRemove === "string") { keyList = [keysToRemove]; } else if (Array.isArray(keysToRemove)) { keyList = keysToRemove; } else { _error("_storageRemove error: Invalid keys format", keysToRemove); return Promise.reject(new Error("Invalid keys format for remove")); } for (const key of keyList) { await GM_deleteValue(key); } return Promise.resolve(); } catch (e) { _error("GM_deleteValue error:", e); return Promise.reject(e); } } async function _storageClear() { try { const keys = await GM_listValues(); await Promise.all(keys.map((key) => GM_deleteValue(key))); return Promise.resolve(); } catch (e) { _error("GM_listValues/GM_deleteValue error during clear:", e); return Promise.reject(e); } } async function _cookieList(details) { return new Promise((resolve, reject) => { if (typeof GM_cookie === "undefined" || !GM_cookie.list) { return reject(new Error("GM_cookie.list is not available.")); } GM_cookie.list(details, (cookies, error) => { if (error) { return reject(new Error(error)); } resolve(cookies); }); }); } async function _cookieSet(details) { return new Promise((resolve, reject) => { if (typeof GM_cookie === "undefined" || !GM_cookie.set) { return reject(new Error("GM_cookie.set is not available.")); } GM_cookie.set(details, (error) => { if (error) { return reject(new Error(error)); } resolve(); }); }); } async function _cookieDelete(details) { return new Promise((resolve, reject) => { if (typeof GM_cookie === "undefined" || !GM_cookie.delete) { return reject(new Error("GM_cookie.delete is not available.")); } GM_cookie.delete(details, (error) => { if (error) { return reject(new Error(error)); } resolve(); }); }); } async function _fetch(url, options = {}) { return new Promise((resolve, reject) => { try { GM_xmlhttpRequest({ method: options.method || "GET", url: url, headers: options.headers || {}, data: options.body, responseType: options.responseType, timeout: options.timeout || 0, binary: options.responseType === "blob" || options.responseType === "arraybuffer", onload: function (response) { const responseHeaders = {}; if (response.responseHeaders) { response.responseHeaders .trim() .split("\\r\\n") .forEach((header) => { const parts = header.match(/^([^:]+):\s*(.*)$/); if (parts && parts.length === 3) { responseHeaders[parts[1].toLowerCase()] = parts[2]; } }); } const mockResponse = { ok: response.status >= 200 && response.status < 300, status: response.status, statusText: response.statusText || (response.status >= 200 && response.status < 300 ? "OK" : ""), url: response.finalUrl || url, headers: new Headers(responseHeaders), text: () => Promise.resolve(response.responseText), json: () => { try { return Promise.resolve(JSON.parse(response.responseText)); } catch (e) { return Promise.reject(new SyntaxError("Could not parse JSON")); } }, blob: () => { if (response.response instanceof Blob) { return Promise.resolve(response.response); } return Promise.reject( new Error("Requires responseType:'blob' in GM_xmlhttpRequest") ); }, arrayBuffer: () => { if (response.response instanceof ArrayBuffer) { return Promise.resolve(response.response); } return Promise.reject( new Error( "Requires responseType:'arraybuffer' in GM_xmlhttpRequest" ) ); }, clone: function () { const cloned = { ...this }; cloned.text = () => Promise.resolve(response.responseText); cloned.json = () => this.json(); cloned.blob = () => this.blob(); cloned.arrayBuffer = () => this.arrayBuffer(); return cloned; }, }; if (mockResponse.ok) { resolve(mockResponse); } else { const error = new Error(`HTTP error! status: ${response.status}`); error.response = mockResponse; reject(error); } }, onerror: function (response) { reject( new Error( `GM_xmlhttpRequest network error: ${ response.statusText || "Unknown Error" }` ) ); }, onabort: function () { reject(new Error("GM_xmlhttpRequest aborted")); }, ontimeout: function () { reject(new Error("GM_xmlhttpRequest timed out")); }, }); } catch (e) { _error("_fetch (GM_xmlhttpRequest) error:", e); reject(e); } }); } function _registerMenuCommand(name, func) { if (typeof GM_registerMenuCommand === "function") { try { GM_registerMenuCommand(name, func); } catch (e) { _error("GM_registerMenuCommand failed:", e); } } else { _warn("GM_registerMenuCommand not available."); } } function _openTab(url, active) { if (typeof GM_openInTab === "function") { try { GM_openInTab(url, { loadInBackground: !active }); } catch (e) { _error("GM_openInTab failed:", e); } } else { _warn("GM_openInTab not available, using window.open as fallback."); try { window.open(url); } catch (e) { _error("window.open fallback failed:", e); } } } async function _initStorage() { return Promise.resolve(); } const EXTENSION_ASSETS_MAP = { "options/options.html": "\n\n \n \n Options - JSON Formatter\n \n \n\n \n
\n Theme\n\n
\n \n
\n
\n \n
\n
\n \n
\n
\n\n \n \n\n" }; // #endregion // #endregion // #region Polyfill Implementation function buildPolyfill({ isBackground = false, isOtherPage = false } = {}) { // Generate a unique context ID for this polyfill instance const contextType = isBackground ? "background" : isOtherPage ? "options" : "content"; const contextId = `${contextType}_${Math.random() .toString(36) .substring(2, 15)}`; const IS_IFRAME = "false" === "true"; const BUS = (function () { if (globalThis.__BUS) { return globalThis.__BUS; } globalThis.__BUS = createEventBus( "json-formatter", IS_IFRAME ? "iframe" : "page", ); return globalThis.__BUS; })(); const RUNTIME = createRuntime(isBackground ? "background" : "tab", BUS); const createNoopListeners = () => ({ addListener: (callback) => { _log("addListener", callback); }, removeListener: (callback) => { _log("removeListener", callback); }, }); // TODO: Stub const storageChangeListeners = new Set(); function broadcastStorageChange(changes, areaName) { storageChangeListeners.forEach((listener) => { listener(changes, areaName); }); } let REQ_PERMS = []; // #region Chrome polyfill let chrome = { extension: { isAllowedIncognitoAccess: () => Promise.resolve(true), sendMessage: (...args) => _messagingHandler.sendMessage(...args), }, permissions: { // TODO: Remove origin permission means exclude from origin in startup (when checking for content scripts) request: (permissions, callback) => { _log("permissions.request", permissions, callback); if (Array.isArray(permissions)) { REQ_PERMS = [...REQ_PERMS, ...permissions]; } if (typeof callback === "function") { callback(permissions); } return Promise.resolve(permissions); }, contains: (permissions, callback) => { if (typeof callback === "function") { callback(true); } return Promise.resolve(true); }, getAll: () => { return Promise.resolve({ permissions: EXTENSION_PERMISSIONS, origins: ORIGIN_PERMISSIONS, }); }, onAdded: createNoopListeners(), onRemoved: createNoopListeners(), }, i18n: { getUILanguage: () => { return USED_LOCALE || "en"; }, getMessage: (key, substitutions = []) => { if (typeof substitutions === "string") { substitutions = [substitutions]; } if (typeof LOCALE_KEYS !== "undefined" && LOCALE_KEYS[key]) { return LOCALE_KEYS[key].message?.replace( /\$(\d+)/g, (match, p1) => substitutions[p1 - 1] || match, ); } return key; }, }, alarms: { onAlarm: createNoopListeners(), create: () => { _log("alarms.create", arguments); }, get: () => { _log("alarms.get", arguments); }, }, runtime: { ...RUNTIME, onInstalled: createNoopListeners(), onStartup: createNoopListeners(), // TODO: Postmessage to parent to open options page or call openOptionsPage openOptionsPage: () => { // const url = chrome.runtime.getURL(OPTIONS_PAGE_PATH); // console.log("openOptionsPage", _openTab, url, EXTENSION_ASSETS_MAP); // _openTab(url); if (typeof openOptionsPage === "function") { openOptionsPage(); } else if (window.parent) { window.parent.postMessage({ type: "openOptionsPage" }, "*"); } else { _warn("openOptionsPage not available."); } }, getManifest: () => { // The manifest object will be injected into the scope where buildPolyfill is called if (typeof INJECTED_MANIFEST !== "undefined") { return JSON.parse(JSON.stringify(INJECTED_MANIFEST)); // Return deep copy } _warn("INJECTED_MANIFEST not found for chrome.runtime.getManifest"); return { name: "Unknown", version: "0.0", manifest_version: 2 }; }, getURL: (path) => { if (!path) return ""; if (path.startsWith("/")) { path = path.substring(1); } if (typeof _createAssetUrl === "function") { return _createAssetUrl(path); } _warn( `chrome.runtime.getURL fallback for '${path}'. Assets may not be available.`, ); // Attempt a relative path resolution (highly context-dependent and likely wrong) try { if (window.location.protocol.startsWith("http")) { return new URL(path, window.location.href).toString(); } } catch (e) { /* ignore error, fallback */ } return path; }, id: "polyfilled-extension-" + Math.random().toString(36).substring(2, 15), lastError: null, setUninstallURL: () => {}, setUpdateURL: () => {}, getPlatformInfo: async () => { const platform = { os: "unknown", arch: "unknown", nacl_arch: "unknown", }; if (typeof navigator !== "undefined") { const userAgent = navigator.userAgent.toLowerCase(); if (userAgent.includes("mac")) platform.os = "mac"; else if (userAgent.includes("win")) platform.os = "win"; else if (userAgent.includes("linux")) platform.os = "linux"; else if (userAgent.includes("android")) platform.os = "android"; else if (userAgent.includes("ios")) platform.os = "ios"; if (userAgent.includes("x86_64") || userAgent.includes("amd64")) { platform.arch = "x86-64"; } else if (userAgent.includes("i386") || userAgent.includes("i686")) { platform.arch = "x86-32"; } else if (userAgent.includes("arm")) { platform.arch = "arm"; } } return platform; }, getBrowserInfo: async () => { const info = { name: "unknown", version: "unknown", buildID: "unknown", }; if (typeof navigator !== "undefined") { const userAgent = navigator.userAgent; if (userAgent.includes("Chrome")) { info.name = "Chrome"; const match = userAgent.match(/Chrome\/([0-9.]+)/); if (match) info.version = match[1]; } else if (userAgent.includes("Firefox")) { info.name = "Firefox"; const match = userAgent.match(/Firefox\/([0-9.]+)/); if (match) info.version = match[1]; } else if (userAgent.includes("Safari")) { info.name = "Safari"; const match = userAgent.match(/Version\/([0-9.]+)/); if (match) info.version = match[1]; } } return info; }, }, storage: { local: { get: function (keys, callback) { if (typeof _storageGet !== "function") throw new Error("_storageGet not defined"); const promise = _storageGet(keys); if (typeof callback === "function") { promise .then((result) => { try { callback(result); } catch (e) { _error("Error in storage.get callback:", e); } }) .catch((error) => { _error("Storage.get error:", error); callback({}); }); return; } return promise; }, set: function (items, callback) { if (typeof _storageSet !== "function") throw new Error("_storageSet not defined"); const promise = _storageSet(items).then((result) => { broadcastStorageChange(items, "local"); return result; }); if (typeof callback === "function") { promise .then((result) => { try { callback(result); } catch (e) { _error("Error in storage.set callback:", e); } }) .catch((error) => { _error("Storage.set error:", error); callback(); }); return; } return promise; }, remove: function (keys, callback) { if (typeof _storageRemove !== "function") throw new Error("_storageRemove not defined"); const promise = _storageRemove(keys).then((result) => { const changes = {}; const keyList = Array.isArray(keys) ? keys : [keys]; keyList.forEach((key) => { changes[key] = { oldValue: undefined, newValue: undefined }; }); broadcastStorageChange(changes, "local"); return result; }); if (typeof callback === "function") { promise .then((result) => { try { callback(result); } catch (e) { _error("Error in storage.remove callback:", e); } }) .catch((error) => { _error("Storage.remove error:", error); callback(); }); return; } return promise; }, clear: function (callback) { if (typeof _storageClear !== "function") throw new Error("_storageClear not defined"); const promise = _storageClear().then((result) => { broadcastStorageChange({}, "local"); return result; }); if (typeof callback === "function") { promise .then((result) => { try { callback(result); } catch (e) { _error("Error in storage.clear callback:", e); } }) .catch((error) => { _error("Storage.clear error:", error); callback(); }); return; } return promise; }, onChanged: { addListener: (callback) => { storageChangeListeners.add(callback); }, removeListener: (callback) => { storageChangeListeners.delete(callback); }, }, }, sync: { get: function (keys, callback) { _warn("chrome.storage.sync polyfill maps to local"); return chrome.storage.local.get(keys, callback); }, set: function (items, callback) { _warn("chrome.storage.sync polyfill maps to local"); const promise = chrome.storage.local.set(items).then((result) => { broadcastStorageChange(items, "sync"); return result; }); if (typeof callback === "function") { promise .then((result) => { try { callback(result); } catch (e) { _error("Error in storage.sync.set callback:", e); } }) .catch((error) => { _error("Storage.sync.set error:", error); callback(); }); return; } return promise; }, remove: function (keys, callback) { _warn("chrome.storage.sync polyfill maps to local"); const promise = chrome.storage.local.remove(keys).then((result) => { const changes = {}; const keyList = Array.isArray(keys) ? keys : [keys]; keyList.forEach((key) => { changes[key] = { oldValue: undefined, newValue: undefined }; }); broadcastStorageChange(changes, "sync"); return result; }); if (typeof callback === "function") { promise .then((result) => { try { callback(result); } catch (e) { _error("Error in storage.sync.remove callback:", e); } }) .catch((error) => { _error("Storage.sync.remove error:", error); callback(); }); return; } return promise; }, clear: function (callback) { _warn("chrome.storage.sync polyfill maps to local"); const promise = chrome.storage.local.clear().then((result) => { broadcastStorageChange({}, "sync"); return result; }); if (typeof callback === "function") { promise .then((result) => { try { callback(result); } catch (e) { _error("Error in storage.sync.clear callback:", e); } }) .catch((error) => { _error("Storage.sync.clear error:", error); callback(); }); return; } return promise; }, onChanged: { addListener: (callback) => { storageChangeListeners.add(callback); }, removeListener: (callback) => { storageChangeListeners.delete(callback); }, }, }, onChanged: { addListener: (callback) => { storageChangeListeners.add(callback); }, removeListener: (callback) => { storageChangeListeners.delete(callback); }, }, managed: { get: function (keys, callback) { _warn("chrome.storage.managed polyfill is read-only empty."); const promise = Promise.resolve({}); if (typeof callback === "function") { promise.then((result) => { try { callback(result); } catch (e) { _error("Error in storage.managed.get callback:", e); } }); return; } return promise; }, }, }, cookies: (function () { const cookieChangeListeners = new Set(); function broadcastCookieChange(changeInfo) { cookieChangeListeners.forEach((listener) => { try { listener(changeInfo); } catch (e) { _error("Error in cookies.onChanged listener:", e); } }); } function handlePromiseCallback(promise, callback) { if (typeof callback === "function") { promise .then((result) => callback(result)) .catch((error) => { // chrome.runtime.lastError = { message: error.message }; // TODO: Implement lastError _error(error); callback(); // Call with undefined on error }); return; } return promise; } return { get: function (details, callback) { if (typeof _cookieList !== "function") { return handlePromiseCallback( Promise.reject(new Error("_cookieList not defined")), callback, ); } const promise = _cookieList({ url: details.url, name: details.name, storeId: details.storeId, partitionKey: details.partitionKey, }).then((cookies) => { if (!cookies || cookies.length === 0) { return null; } // Sort by path length (longest first), then creation time (earliest first, if available) cookies.sort((a, b) => { const pathLenDiff = (b.path || "").length - (a.path || "").length; if (pathLenDiff !== 0) return pathLenDiff; return (a.creationTime || 0) - (b.creationTime || 0); }); return cookies[0]; }); return handlePromiseCallback(promise, callback); }, getAll: function (details, callback) { if (typeof _cookieList !== "function") { return handlePromiseCallback( Promise.reject(new Error("_cookieList not defined")), callback, ); } if (details.partitionKey) { _warn( "cookies.getAll: partitionKey is not fully supported in this environment.", ); } const promise = _cookieList(details); return handlePromiseCallback(promise, callback); }, set: function (details, callback) { const promise = (async () => { if ( typeof _cookieSet !== "function" || typeof _cookieList !== "function" ) { throw new Error("_cookieSet or _cookieList not defined"); } if (details.partitionKey) { _warn( "cookies.set: partitionKey is not fully supported in this environment.", ); } const getDetails = { url: details.url, name: details.name, storeId: details.storeId, }; const oldCookies = await _cookieList(getDetails); const oldCookie = oldCookies && oldCookies[0]; if (oldCookie) { broadcastCookieChange({ cause: "overwrite", cookie: oldCookie, removed: true, }); } await _cookieSet(details); const newCookies = await _cookieList(getDetails); const newCookie = newCookies && newCookies[0]; if (newCookie) { broadcastCookieChange({ cause: "explicit", cookie: newCookie, removed: false, }); } return newCookie || null; })(); return handlePromiseCallback(promise, callback); }, remove: function (details, callback) { const promise = (async () => { if ( typeof _cookieDelete !== "function" || typeof _cookieList !== "function" ) { throw new Error("_cookieDelete or _cookieList not defined"); } const oldCookies = await _cookieList(details); const oldCookie = oldCookies && oldCookies[0]; if (!oldCookie) return null; // Nothing to remove await _cookieDelete(details); broadcastCookieChange({ cause: "explicit", cookie: oldCookie, removed: true, }); return { url: details.url, name: details.name, storeId: details.storeId || "0", partitionKey: details.partitionKey, }; })(); return handlePromiseCallback(promise, callback); }, getAllCookieStores: function (callback) { const promise = Promise.resolve([ { id: "0", tabIds: [1] }, // Mock store for the current context ]); return handlePromiseCallback(promise, callback); }, getPartitionKey: function (details, callback) { _warn( "chrome.cookies.getPartitionKey is not supported in this environment.", ); const promise = Promise.resolve({ partitionKey: {} }); // Return empty partition key return handlePromiseCallback(promise, callback); }, onChanged: { addListener: (callback) => { if (typeof callback === "function") { cookieChangeListeners.add(callback); } }, removeListener: (callback) => { cookieChangeListeners.delete(callback); }, }, }; })(), tabs: { query: async (queryInfo) => { _warn("chrome.tabs.query polyfill only returns current tab info."); const dummyId = Math.floor(Math.random() * 1000) + 1; return [ { id: dummyId, url: CURRENT_LOCATION, active: true, windowId: 1, status: "complete", }, ]; }, create: async ({ url, active = true }) => { _log(`[Polyfill tabs.create] URL: ${url}`); if (typeof _openTab !== "function") throw new Error("_openTab not defined"); _openTab(url, active); const dummyId = Math.floor(Math.random() * 1000) + 1001; return Promise.resolve({ id: dummyId, url: url, active, windowId: 1, }); }, sendMessage: async (tabId, message) => { _warn( `chrome.tabs.sendMessage polyfill (to tab ${tabId}) redirects to runtime.sendMessage (current context).`, ); return chrome.runtime.sendMessage(message); }, onActivated: createNoopListeners(), onUpdated: createNoopListeners(), onRemoved: createNoopListeners(), onReplaced: createNoopListeners(), onCreated: createNoopListeners(), onMoved: createNoopListeners(), onDetached: createNoopListeners(), onAttached: createNoopListeners(), }, windows: { onFocusChanged: createNoopListeners(), onCreated: createNoopListeners(), onRemoved: createNoopListeners(), onFocused: createNoopListeners(), onFocus: createNoopListeners(), onBlur: createNoopListeners(), onFocused: createNoopListeners(), }, notifications: { create: async (notificationId, options) => { try { let id = notificationId; let notificationOptions = options; if (typeof notificationId === "object" && notificationId !== null) { notificationOptions = notificationId; id = "notification_" + Math.random().toString(36).substring(2, 15); } else if (typeof notificationId === "string" && options) { id = notificationId; notificationOptions = options; } else { throw new Error("Invalid parameters for notifications.create"); } if (!notificationOptions || typeof notificationOptions !== "object") { throw new Error("Notification options must be an object"); } const { title, message, iconUrl, type = "basic", } = notificationOptions; if (!title || !message) { throw new Error("Notification must have title and message"); } if ("Notification" in window) { if (Notification.permission === "granted") { const notification = new Notification(title, { body: message, icon: iconUrl, tag: id, }); _log(`[Notifications] Created notification: ${id}`); return id; } else if (Notification.permission === "default") { const permission = await Notification.requestPermission(); if (permission === "granted") { const notification = new Notification(title, { body: message, icon: iconUrl, tag: id, }); _log( `[Notifications] Created notification after permission: ${id}`, ); return id; } else { _warn("[Notifications] Permission denied for notifications"); return id; } } else { _warn("[Notifications] Notifications are blocked"); return id; } } else { _warn( "[Notifications] Native notifications not supported, using console fallback", ); _log(`[Notification] ${title}: ${message}`); return id; } } catch (error) { _error("[Notifications] Error creating notification:", error.message); throw error; } }, clear: async (notificationId) => { _log(`[Notifications] Clear notification: ${notificationId}`); // For native notifications, there's no direct way to clear by ID // This is a limitation of the Web Notifications API return true; }, getAll: async () => { _warn("[Notifications] getAll not fully supported in polyfill"); return {}; }, getPermissionLevel: async () => { if ("Notification" in window) { const permission = Notification.permission; return { level: permission === "granted" ? "granted" : "denied" }; } return { level: "denied" }; }, }, contextMenus: { create: (createProperties, callback) => { try { if (!createProperties || typeof createProperties !== "object") { throw new Error("Context menu create properties must be an object"); } const { id, title, contexts = ["page"], onclick } = createProperties; const menuId = id || `menu_${Math.random().toString(36).substring(2, 15)}`; if (!title || typeof title !== "string") { throw new Error("Context menu must have a title"); } // Store menu items for potential use if (!window._polyfillContextMenus) { window._polyfillContextMenus = new Map(); } window._polyfillContextMenus.set(menuId, { id: menuId, title, contexts, onclick, enabled: createProperties.enabled !== false, }); _log( `[ContextMenus] Created context menu item: ${title} (${menuId})`, ); // Try to register a menu command as fallback if (typeof _registerMenuCommand === "function") { try { _registerMenuCommand( title, onclick || (() => { _log(`Context menu clicked: ${title}`); }), ); } catch (e) { _warn( "[ContextMenus] Failed to register as menu command:", e.message, ); } } if (callback && typeof callback === "function") { setTimeout(() => callback(), 0); } return menuId; } catch (error) { _error("[ContextMenus] Error creating context menu:", error.message); if (callback && typeof callback === "function") { setTimeout(() => callback(), 0); } throw error; } }, update: (id, updateProperties, callback) => { try { if ( !window._polyfillContextMenus || !window._polyfillContextMenus.has(id) ) { throw new Error(`Context menu item not found: ${id}`); } const menuItem = window._polyfillContextMenus.get(id); Object.assign(menuItem, updateProperties); _log(`[ContextMenus] Updated context menu item: ${id}`); if (callback && typeof callback === "function") { setTimeout(() => callback(), 0); } } catch (error) { _error("[ContextMenus] Error updating context menu:", error.message); if (callback && typeof callback === "function") { setTimeout(() => callback(), 0); } } }, remove: (menuItemId, callback) => { try { if ( window._polyfillContextMenus && window._polyfillContextMenus.has(menuItemId) ) { window._polyfillContextMenus.delete(menuItemId); _log(`[ContextMenus] Removed context menu item: ${menuItemId}`); } else { _warn( `[ContextMenus] Context menu item not found for removal: ${menuItemId}`, ); } if (callback && typeof callback === "function") { setTimeout(() => callback(), 0); } } catch (error) { _error("[ContextMenus] Error removing context menu:", error.message); if (callback && typeof callback === "function") { setTimeout(() => callback(), 0); } } }, removeAll: (callback) => { try { if (window._polyfillContextMenus) { const count = window._polyfillContextMenus.size; window._polyfillContextMenus.clear(); _log(`[ContextMenus] Removed all ${count} context menu items`); } if (callback && typeof callback === "function") { setTimeout(() => callback(), 0); } } catch (error) { _error( "[ContextMenus] Error removing all context menus:", error.message, ); if (callback && typeof callback === "function") { setTimeout(() => callback(), 0); } } }, onClicked: { addListener: (callback) => { if (!window._polyfillContextMenuListeners) { window._polyfillContextMenuListeners = new Set(); } window._polyfillContextMenuListeners.add(callback); _log("[ContextMenus] Added click listener"); }, removeListener: (callback) => { if (window._polyfillContextMenuListeners) { window._polyfillContextMenuListeners.delete(callback); _log("[ContextMenus] Removed click listener"); } }, }, }, }; const tc = (fn) => { try { fn(); } catch (e) {} }; const loggingProxyHandler = (_key) => ({ get(target, key, receiver) { tc(() => _log(`[${contextType}] [CHROME - ${_key}] Getting ${key}`)); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { tc(() => _log(`[${contextType}] [CHROME - ${_key}] Setting ${key} to ${value}`), ); return Reflect.set(target, key, value, receiver); }, has(target, key) { tc(() => _log(`[${contextType}] [CHROME - ${_key}] Checking if ${key} exists`), ); return Reflect.has(target, key); }, }); chrome = Object.fromEntries( Object.entries(chrome).map(([key, value]) => [ key, new Proxy(value, loggingProxyHandler(key)), ]), ); // Alias browser to chrome for common Firefox pattern const browser = new Proxy(chrome, loggingProxyHandler); const oldGlobalThis = globalThis; const oldWindow = window; const oldSelf = self; const oldGlobal = globalThis; const __globalsStorage = {}; const TO_MODIFY = [oldGlobalThis, oldWindow, oldSelf, oldGlobal]; const set = (k, v) => { __globalsStorage[k] = v; TO_MODIFY.forEach((target) => { target[k] = v; }); }; const proxyHandler = { get(target, key, receiver) { const fns = [ () => __globalsStorage[key], () => Reflect.get(target, key, target), () => target[key], ]; const out = fns .map((f) => { try { let out = f(); return out; } catch (e) { return undefined; } }) .find((f) => f !== undefined); if (typeof out === "function") { return out.bind(target); } return out; }, set(target, key, value, receiver) { try { tc(() => _log(`[${contextType}] Setting ${key} to ${value}`)); set(key, value); return Reflect.set(target, key, value, receiver); } catch (e) { _error("Error setting", key, value, e); try { target[key] = value; return true; } catch (e) { _error("Error setting", key, value, e); } return false; } }, has(target, key) { try { return key in __globalsStorage || key in target; } catch (e) { _error("Error has", key, e); try { return key in __globalsStorage || key in target; } catch (e) { _error("Error has", key, e); } return false; } }, getOwnPropertyDescriptor(target, key) { try { if (key in __globalsStorage) { return { configurable: true, enumerable: true, writable: true, value: __globalsStorage[key], }; } // fall back to the real globalThis const desc = Reflect.getOwnPropertyDescriptor(target, key); // ensure it's configurable so the with‑scope binding logic can override it if (desc && !desc.configurable) { desc.configurable = true; } return desc; } catch (e) { _error("Error getOwnPropertyDescriptor", key, e); return { configurable: true, enumerable: true, writable: true, value: undefined, }; } }, defineProperty(target, key, descriptor) { try { // Normalize descriptor to avoid mixed accessor & data attributes const hasAccessor = "get" in descriptor || "set" in descriptor; if (hasAccessor) { // Build a clean descriptor without value/writable when accessors present const normalized = { configurable: "configurable" in descriptor ? descriptor.configurable : true, enumerable: "enumerable" in descriptor ? descriptor.enumerable : false, }; if ("get" in descriptor) normalized.get = descriptor.get; if ("set" in descriptor) normalized.set = descriptor.set; // Store accessor references for inspection but avoid breaking invariants set(key, { get: descriptor.get, set: descriptor.set, }); return Reflect.defineProperty(target, key, normalized); } // Data descriptor path set(key, descriptor.value); return Reflect.defineProperty(target, key, descriptor); } catch (e) { _error("Error defineProperty", key, descriptor, e); return false; } }, }; // Create proxies once proxyHandler is defined const proxyWindow = new Proxy(oldWindow, proxyHandler); const proxyGlobalThis = new Proxy(oldGlobalThis, proxyHandler); const proxyGlobal = new Proxy(oldGlobal, proxyHandler); const proxySelf = new Proxy(oldSelf, proxyHandler); // Seed storage with core globals so lookups succeed inside `with` blocks Object.assign(__globalsStorage, { chrome, browser, window: proxyWindow, globalThis: proxyGlobalThis, global: proxyGlobal, self: proxySelf, document: oldWindow.document, }); const __globals = { chrome, browser, window: proxyWindow, globalThis: proxyGlobalThis, global: proxyGlobal, self: proxySelf, __globals: __globalsStorage, }; __globals.contextId = contextId; __globals.contextType = contextType; __globals.module = undefined; __globals.amd = undefined; __globals.define = undefined; __globals.importScripts = (...args) => { _log("importScripts", args); }; return __globals; } if (typeof window !== 'undefined') { window.buildPolyfill = buildPolyfill; } // #endregion // #endregion // #endregion // #region Background Script Environment // #endregion // #region Orchestration Logic // Other globals currently defined at this spot: SCRIPT_NAME, _log, _warn, _error const INJECTED_MANIFEST = {"manifest_version":3,"name":"JSON Formatter","version":"0.7.3","description":"Makes JSON easy to read. Open source.","permissions":["storage"],"optional_permissions":[],"content_scripts":[{"matches":[""],"js":["content.js"],"run_at":"document_end","css":[]}],"options_ui":{"page":"options/options.html","open_in_tab":false},"browser_action":{},"page_action":{},"action":{},"icons":{"32":"icons/32.png","128":"icons/128.png"},"web_accessible_resources":[],"background":{},"_id":"json-formatter"}; const CONTENT_SCRIPT_CONFIGS_FOR_MATCHING = [ { "matches": [ "" ] } ]; const OPTIONS_PAGE_PATH = "options/options.html"; const POPUP_PAGE_PATH = null; const EXTENSION_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABVJJREFUeNq8V1sspVcU3jg4rq3LEB13SlWbikv0oR1pzGjiElRCeas2Hj1LE6YmM8JLIyNpwoOXJo2gSMpDU4mkWhmDqMvQIhwd17jNYTg42P2+P7/JcdzOOWO6ki85l70ue6+11/q2RlgnzoAv8DbgCngCkcAsoAcMwAawCexbYlBjwRp7wAcIysnJyd3Y2MjW6/VhgGZ3d1e7vr4uXF1dj729vY8cHBw2gb9mZ2d/wvpnwKIa0PFlxu2ucMz/3IODgz+KjY39bnFxMWVubk6DzyIuLk4EBQUJOBUGg0E4OTmJzc1N8fz5czE5OSmmpqZONBrNEoJtPjg4aFGDeQlISwPgrgOys7O/xQ6L9/f3tQkJCSItLU2Eh4cLPz8/4ebmJrRareL88PBQCWRvb0/AqZienhY9PT2ir6/vcHl5+Z+VlZUfYK9LPZETS448KjMz82lSUpLMz8+Xzc3Ncn5+XsKRtES4jutbW1tlQUGBxGmtwGYdEK3av1QcWFR5eXlTyKusqKiQ4+PjEruTtgj1JiYmZGVlpcRpGWG7Foi4Koh3MjIyniQmJsqHDx9K5F2enJzI1xHqLywsyAcPHsjQ0NAF+Pie6b3IubaoqKgWi2R5eblEQb22c/Mg7t+/L1EzvK7F9Hcm776+vinY+UF6erocHh6Wx8fH8iaF9kZGRiQK2WBnZ/cEPj81TYUPiq47LCxMtre3S1wd+SaExdnZ2Slxfdfh8xGbGiOwQ8GFr62t3cHuRXJysnK13oQ4OjqK+Ph4UVhY6IpTSMFPoQzAGYX3JRqJY2pqqkAqzinW1NSIsrIyq5xxPfXMhfbv3bvn4unpGYSvHyptdnt7+3N0PBEREaFEeZGhra0tqwLg+osCp/3IyEgREhLCrx9zFry1s7NzG01H3Lp165yh7u5uUVJSIqqrq60KoL6+Xnh5eSn65uLj48NUOI6OjiopcENxuAUEBHCoiP9D6CcqKsqDU5UBOKFjafz9/ZXebiqYaspRcie2CPWoTztnGg78BAYG0pkHA3DkMHF3dxeYYK8WtbS0KDVBI0yBLUI96tMO7ZnWAf2RDjCAI3t7+xNOMjSLV4vu3r2r5HFoaEg0NDTYFAD1qE87tHcqR0dHPBmSFyMDMCAiA0amwNg9c3zcAcew+Q3AHb728+lNoP7pSZwKR7dOpyNReckADsBitjB4BBiORTtDU7v281XCADBlORP0DOCF0WicIJNBN7z0Ttsil+mRxvX395OY6PjdBdfi65iYGCMJhDnpwP3ntiSO0qq+z/XUo76pIP8SBalHuv7G/9+cBpWAQTRXWloqQaHOGRscHFRYkTXC9dQzF9SaREH+AZ9E0qvmhAH0CHzP2NXVZTMDsoQhdXR0rGP30/BZBfiZ8sBP0IwmsrKyJFrkjZERU1IyNjYmMXMG4Iu4Y07N2IeLcSUPyQXJYG6SES0tLUmM4d9VZvyV6k8hoadC0riGxRrk6V3cDFdOLQ8Pj3P32xrh1URdiaqqqtHGxkb+9Cvwo/p6upCS86lVi5thIIfD9ZRoUDbtnHrUV3e+rVLzKLONX0jNyd8fo3vpcnNzd9va2pSUWPMuIKltamraiI6O/lPN+WPgfXPnV72MAoEsdMl8jOowFKcvmQzTQlbDl5Gzs7MyWJAupY2zw62uroqZmRl9XV3ds97eXmekdAd2WoFfgAXzl9G1b0MgFvgC+MzFxSUIVXwAMqEFozGied0muUBf16GVO2DwrA0MDDihA/qrxUY28jMwAexY8zY0TwmJYjDwAUAy+Z76m6cKPjxfqGCH+011+i8771Wv4/8EGADDWPCX+M7kKwAAAABJRU5ErkJggg=="; const extensionCssData = {}; const LOCALE_KEYS = {}; const USED_LOCALE = "en"; const CURRENT_LOCATION = window.location.href; const convertMatchPatternToRegExp = function convertMatchPatternToRegExp(pattern) { if (pattern === "") return new RegExp(".*"); try { const singleEscapedPattern = convertMatchPatternToRegExpString(pattern).replace(/\\\\/g, "\\"); return new RegExp(singleEscapedPattern); } catch (error) { debug("Error converting match pattern to RegExp: %s, Error: %s", pattern, error.message); return new RegExp("$."); } }; const convertMatchPatternToRegExpString = function convertMatchPatternToRegExpString(pattern) { function escapeRegex(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, "\\\\$&"); } if (typeof pattern !== "string" || !pattern) return "$."; const schemeMatch = pattern.match(/^(\*|https?|file|ftp):\/\//); if (!schemeMatch) return "$."; const scheme = schemeMatch[1]; pattern = pattern.substring(schemeMatch[0].length); const schemeRegex = scheme === "*" ? "https?|file|ftp" : scheme, hostMatch = pattern.match(/^([^\/]+)/); if (!hostMatch) return "$."; const host = hostMatch[1]; pattern = pattern.substring(host.length); let hostRegex; if (host === "*") hostRegex = "[^/]+"; else if (host.startsWith("*.")) hostRegex = "(?:[^\\/]+\\.)?" + escapeRegex(host.substring(2)); else hostRegex = escapeRegex(host); let pathRegex = pattern; if (!pathRegex.startsWith("/")) pathRegex = "/" + pathRegex; pathRegex = pathRegex.split("*").map(escapeRegex).join(".*"); if (pathRegex === "/.*") pathRegex = "(?:/.*)?"; else pathRegex = pathRegex + "(?:[?#]|$)"; return `^${schemeRegex}:\\/\\/${hostRegex}${pathRegex}`; }; const ALL_PERMISSIONS = [ ...(INJECTED_MANIFEST.permissions || []), ...(INJECTED_MANIFEST.optional_permissions || []), ...(INJECTED_MANIFEST.host_permissions || []), ...(INJECTED_MANIFEST.content_scripts ?.map((cs) => cs.matches || []) ?.flat() || []), ]; const isOrigin = (perm) => { if ( perm.startsWith("*://") || perm.startsWith("http://") || perm.startsWith("https://") ) { return true; } return false; }; const ORIGIN_PERMISSIONS = ALL_PERMISSIONS.filter(isOrigin); const EXTENSION_PERMISSIONS = ALL_PERMISSIONS.filter((perm) => !isOrigin(perm)); function _testBlobCSP() { try { const code = `console.log("Blob CSP test");`; const blob = new Blob([code], { type: "application/javascript" }); const blobUrl = URL.createObjectURL(blob); const script = document.createElement("script"); script.src = blobUrl; let blocked = false; script.onerror = () => { blocked = true; }; document.head.appendChild(script); return new Promise((resolve) => { setTimeout(() => { resolve(!blocked); document.head.removeChild(script); URL.revokeObjectURL(blobUrl); }, 100); }); } catch (e) { return Promise.resolve(false); } } let CAN_USE_BLOB_CSP = false; const waitForDOMEnd = () => { if (document.readyState === "loading") { return new Promise((resolve) => document.addEventListener("DOMContentLoaded", resolve, { once: true }) ); } return Promise.resolve(); }; waitForDOMEnd().then(() => { _testBlobCSP().then((result) => { CAN_USE_BLOB_CSP = result; }); }); function _base64ToBlob(base64, mimeType = "application/octet-stream") { const binary = atob(base64); const len = binary.length; const bytes = new Uint8Array(len); for (let i = 0; i < len; i++) bytes[i] = binary.charCodeAt(i); return new Blob([bytes], { type: mimeType }); } function _getMimeTypeFromPath(p) { const ext = (p.split(".").pop() || "").toLowerCase(); const map = { html: "text/html", htm: "text/html", js: "text/javascript", css: "text/css", json: "application/json", png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif", svg: "image/svg+xml", webp: "image/webp", ico: "image/x-icon", woff: "font/woff", woff2: "font/woff2", ttf: "font/ttf", otf: "font/otf", eot: "application/vnd.ms-fontobject", }; return map[ext] || "application/octet-stream"; } function _isTextAsset(ext) { return ["html", "htm", "js", "css", "json", "svg", "txt", "xml"].includes( ext ); } function _createAssetUrl(path = "") { if (path.startsWith("/")) path = path.slice(1); const assetData = EXTENSION_ASSETS_MAP[path]; if (typeof assetData === "undefined") { _warn("[runtime.getURL] Asset not found for", path); return path; } const mime = _getMimeTypeFromPath(path); const ext = (path.split(".").pop() || "").toLowerCase(); if (CAN_USE_BLOB_CSP) { let blob; if (_isTextAsset(ext)) { blob = new Blob([assetData], { type: mime }); } else { blob = _base64ToBlob(assetData, mime); } return URL.createObjectURL(blob); } else { if (_isTextAsset(ext)) { return `data:${mime};base64,${btoa(assetData)}`; } else { return `data:${mime};base64,${assetData}`; } } } function _matchGlobPattern(pattern, path) { if (!pattern || !path) return false; pattern = pattern.replace(/\\/g, "/"); path = path.replace(/\\/g, "/"); if (pattern === path) return true; let regexPattern = pattern .replace(/[.+?^${}()|[\]\\]/g, "\\$&") // Escape regex chars .replace(/\*\*/g, "__DOUBLESTAR__") // Temporarily replace ** .replace(/\*/g, "[^/]*") // * matches any chars except / .replace(/__DOUBLESTAR__/g, ".*"); // ** matches any chars including / regexPattern = "^" + regexPattern + "$"; try { const regex = new RegExp(regexPattern); return regex.test(path); } catch (e) { _error(`Invalid glob pattern: ${pattern}`, e); return false; } } function _isWebAccessibleResource(resourcePath, webAccessibleResources) { if ( !Array.isArray(webAccessibleResources) || webAccessibleResources.length === 0 ) { return false; } // Normalize the resource path const normalizedPath = resourcePath.replace(/\\/g, "/").replace(/^\/+/, ""); for (const webAccessibleResource of webAccessibleResources) { let patterns = []; // Handle both manifest v2 and v3 formats if (typeof webAccessibleResource === "string") { // Manifest v2 format: array of strings patterns = [webAccessibleResource]; } else if ( webAccessibleResource && Array.isArray(webAccessibleResource.resources) ) { // Manifest v3 format: objects with resources array patterns = webAccessibleResource.resources; } // Check if the path matches any pattern for (const pattern of patterns) { if (_matchGlobPattern(pattern, normalizedPath)) { return true; } } } return false; } window._matchGlobPattern = _matchGlobPattern; window._isWebAccessibleResource = _isWebAccessibleResource; // This function contains all the CSS injection and JS execution, // ordered by run_at timing internally using await. // #region Script Execution Logic async function executeAllScripts(globalThis, extensionCssData) { const {chrome, browser, global, window, self} = globalThis; const scriptName = "JSON Formatter"; _log(`Starting execution phases...`); // #region Document Start if (typeof document !== 'undefined') { _log(`Executing document-start phase...`); const scriptPaths = []; _log(` Executing JS (start): ${scriptPaths}`); try { // Keep variables from being redeclared for global scope, but also make them apply to global scope. (Theoretically) with (globalThis){; ;} } catch(e) { _error(` Error executing scripts ${scriptPaths}`, e); } } else { _log(`Skipping document-start phase (no document).`); } // #endregion // #region Wait for Document End DOMContentLoaded --- if (typeof document !== 'undefined' && document.readyState === 'loading') { _log(`Waiting for DOMContentLoaded...`); await new Promise(resolve => document.addEventListener('DOMContentLoaded', resolve, { once: true })); _log(`DOMContentLoaded fired.`); } else if (typeof document !== 'undefined') { _log(`DOMContentLoaded already passed or not applicable.`); } // #endregion // #region Document End if (typeof document !== 'undefined') { _log(`Executing document-end phase...`); const scriptPaths = ["content.js"]; _log(` Executing JS (end): ${scriptPaths}`); try { // Keep variables from being redeclared for global scope, but also make them apply to global scope. (Theoretically) with (globalThis){; // START: content.js (()=>{window.__jsonFormatterStartTime=performance.now();var M="Runtime assertion failed";function f(e,l){if(e)return;let i=typeof l=="function"?l():l,n=i?"".concat(M,": ").concat(i):M;throw new Error(n)}var O=e=>typeof e=="string"?1:typeof e=="number"?2:e===!1||e===!0?5:e===null?6:Array.isArray(e)?4:3;var J=document.createElement("span"),_=()=>J.cloneNode(!1),m=e=>{let l=_();return l.className=e,l},h=(e,l)=>{let i=_();return i.className=l,i.innerText=e,i},s={t_entry:m("entry"),t_exp:m("e"),t_key:m("k"),t_string:m("s"),t_number:m("n"),t_null:h("null","nl"),t_true:h("true","bl"),t_false:h("false","bl"),t_oBrace:h("{","b"),t_cBrace:h("}","b"),t_oBracket:h("[","b"),t_cBracket:h("]","b"),t_sizeComment:m("sizeComment"),t_ellipsis:m("ell"),t_blockInner:m("blockInner"),t_colonAndSpace:document.createTextNode(":\xA0"),t_commaText:document.createTextNode(","),t_dblqText:document.createTextNode('"')};var k=(e,l)=>{let i=O(e),n=s.t_entry.cloneNode(!1),d=0;i===3?d=Object.keys(e).length:i===4&&(d=e.length);let b=!1;if(i===3||i===4){for(let t in e)if(e.hasOwnProperty(t)){b=!0;break}b&&n.appendChild(s.t_exp.cloneNode(!1))}if(l!==!1){n.classList.add("objProp");let t=s.t_key.cloneNode(!1);t.textContent=JSON.stringify(l).slice(1,-1),n.appendChild(s.t_dblqText.cloneNode(!1)),n.appendChild(t),n.appendChild(s.t_dblqText.cloneNode(!1)),n.appendChild(s.t_colonAndSpace.cloneNode(!1))}else n.classList.add("arrElem");let p,c;switch(i){case 1:{f(typeof e=="string");let t=_(),o=JSON.stringify(e);if(o=o.substring(1,o.length-1),e.substring(0,8)==="https://"||e.substring(0,7)==="http://"||e[0]==="/"){let a=document.createElement("a");a.href=e,a.innerText=o,t.appendChild(a)}else t.innerText=o;let r=s.t_string.cloneNode(!1);r.appendChild(s.t_dblqText.cloneNode(!1)),r.appendChild(t),r.appendChild(s.t_dblqText.cloneNode(!1)),n.appendChild(r);break}case 2:{let t=s.t_number.cloneNode(!1);t.innerText=String(e),n.appendChild(t);break}case 3:{if(f(typeof e=="object"),n.appendChild(s.t_oBrace.cloneNode(!0)),b){n.appendChild(s.t_ellipsis.cloneNode(!1)),p=s.t_blockInner.cloneNode(!1);let t;for(let o in e)if(e.hasOwnProperty(o)){c=k(e[o],o);let r=s.t_commaText.cloneNode();c.appendChild(r),p.appendChild(c),t=r}f(typeof c<"u"&&typeof t<"u"),c.removeChild(t),n.appendChild(p)}n.appendChild(s.t_cBrace.cloneNode(!0)),n.dataset.size=` // ${d} ${d===1?"item":"items"}`;break}case 4:{if(f(Array.isArray(e)),n.appendChild(s.t_oBracket.cloneNode(!0)),b){n.appendChild(s.t_ellipsis.cloneNode(!1)),p=s.t_blockInner.cloneNode(!1);for(let t=0,o=e.length,r=o-1;t .blockInner { display: none; } .collapsed > .ell:after { content: '\u2026'; font-weight: bold; } .collapsed > .ell { margin: 0 4px; color: #888; } .collapsed .entry { display: inline; } .collapsed:after { content: attr(data-size); color: #aaa; } .e { width: 20px; height: 18px; display: block; position: absolute; left: 0px; top: 1px; color: black; z-index: 5; background-repeat: no-repeat; background-position: center center; display: flex; align-items: center; justify-content: center; opacity: 0.15; } .e::after { content: ''; display: block; width: 0; height: 0; border-style: solid; border-width: 4px 0 4px 6.9px; border-color: transparent transparent transparent currentColor; transform: rotate(90deg) translateY(1px); } .collapsed > .e::after { transform: none; } .e:hover { opacity: 0.35; } .e:active { opacity: 0.5; } .collapsed .entry .e { display: none; } .blockInner { display: block; padding-left: 24px; border-left: 1px dotted #bbb; margin-left: 2px; } #jsonFormatterParsed { color: #444; } .entry { font-size: 13px; font-family: monospace; } .b { font-weight: bold; } .s { color: #0b7500; word-wrap: break-word; } a:link, a:visited { text-decoration: none; color: inherit; } a:hover, a:active { text-decoration: underline; color: #050; } .bl, .nl, .n { font-weight: bold; color: #1a01cc; } .k { color: #000; } [hidden] { display: none !important; } span { white-space: pre-wrap; } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } #spinner { animation: spin 2s linear infinite; } `,F=`body { background-color: #1a1a1a; color: #eee; -webkit-font-smoothing: antialiased; } a:hover, a:active { color: hsl(114, 90%, 55%); } #optionBar { -webkit-font-smoothing: subpixel-antialiased; } #jsonFormatterParsed { color: #b6b6b6; } .blockInner { border-color: #4d4d4d; } .k { color: #fff; } .s { color: hsl(114, 100%, 35%); } .bl, .nl, .n { color: hsl(200, 100%, 70%); } .e { color: #fff; opacity: 0.25; } .e:hover { opacity: 0.45; } .e:active { opacity: 0.6; } .collapsed:after { color: #707070; } :is(#buttonPlain, #buttonFormatted) { text-shadow: none; border: 0; background: hsl(200, 35%, 60%); box-shadow: none; color: #000; } :is(#buttonPlain, #buttonFormatted):not(.selected):hover { box-shadow: none; background: hsl(200, 50%, 70%); color: #000; } :is(#buttonPlain, #buttonFormatted).selected { box-shadow: inset 0px 1px 5px rgba(0, 0, 0, 0.7); background: hsl(200, 40%, 60%); color: #000; } `,R=!1,Y=new Promise(e=>{chrome.storage.local.get("themeOverride",l=>{switch(l.themeOverride){case"force_light":e(L);break;case"force_dark":e(L+` `+F);break;case"system":default:e(L+` @media (prefers-color-scheme: dark) { `+F+` }`)}})}),j=(async()=>{let e=(()=>{let t=document.body.children,o=t.length;for(let r=0;rpre found",rawLength:null};let l=e.textContent;if(!l)return{formatted:!1,note:"No content in body>pre",rawLength:0};let i=l.length;if(i>3e6)return{formatted:!1,note:"Too long",rawLength:i};if(!/^\s*[\{\[]/.test(l))return{formatted:!1,note:"Does not start with { or ]",rawLength:i};e.remove();let n=document.createElement("div");n.id="jsonFormatterParsed",document.body.appendChild(n);let d=document.createElement("div");d.hidden=!0,d.id="jsonFormatterRaw",d.append(e),document.body.appendChild(d);{let t;try{t=JSON.parse(l)}catch{return n.remove(),d.remove(),document.body.prepend(e),{formatted:!1,note:"Does not parse as JSON",rawLength:i}}if(typeof t!="object"&&!Array.isArray(t))return{formatted:!1,note:"Technically JSON but not an object or array",rawLength:i};let o=t;{let a=document.createElement("style");a.id="jfStyleEl",a.insertAdjacentHTML("beforeend",await Y),document.head.appendChild(a);let E=document.createElement("div");E.id="optionBar";let g=document.createElement("button"),S=document.createElement("span"),u=document.createElement("button"),B=document.createElement("span");g.appendChild(S),u.appendChild(B),g.id="buttonPlain",S.innerText="Raw",u.id="buttonFormatted",B.innerText="Parsed",u.classList.add("selected");let T=!1;g.addEventListener("mousedown",()=>{T||(T=!0,d.hidden=!1,n.hidden=!0,u.classList.remove("selected"),g.classList.add("selected"))},!1),u.addEventListener("mousedown",function(){T&&(T=!1,d.hidden=!0,n.hidden=!1,u.classList.add("selected"),g.classList.remove("selected"))},!1),E.appendChild(g),E.appendChild(u),document.body.prepend(E),document.addEventListener("mousedown",c)}let r=k(o,!1);await Promise.resolve(),n.append(r)}for(let t of document.getElementsByClassName("json-formatter-container"))t.style.display="none";return{formatted:!0,note:"done",rawLength:i};function b(t){let o,r,a;for(r=t.length-1;r>=0;r--)if(o=t[r],o.classList.add("collapsed"),!o.id){for(a=o.firstElementChild;a&&!a.classList.contains("blockInner");)a=a.nextElementSibling;if(!a)continue}}function p(t){for(let o=t.length-1;o>=0;o--)t[o].classList.remove("collapsed")}function c(t){let o=t.target;if(o instanceof HTMLElement&&o.className==="e"){t.preventDefault();let r=o.parentNode;if(f(r instanceof HTMLElement),r.classList.contains("collapsed"))if(t.metaKey||t.ctrlKey){let a=r.parentNode;f(a instanceof HTMLElement),p(a.children)}else p([r]);else if(t.metaKey||t.ctrlKey){let a=r.parentNode;f(a instanceof HTMLElement),b(a.children)}else b([r])}}})();R&&j.then(e=>{let l=window.__jsonFormatterStartTime,n=performance.now()-l;console.log("JSON Formatter",e),console.log("Duration:",Math.round(n*10)/10,"ms")});})(); // END: content.js ;} } catch(e) { _error(` Error executing scripts ${scriptPaths}`, e); } } else { _log(`Skipping document-end phase (no document).`); } // #endregion // #region Wait for Document Idle _log(`Waiting for document idle state...`); if (typeof window !== 'undefined' && typeof window.requestIdleCallback === 'function') { await new Promise(resolve => window.requestIdleCallback(resolve, { timeout: 2000 })); // 2-second timeout fallback _log(`requestIdleCallback fired or timed out.`); } else { // Fallback: wait a short period after DOMContentLoaded/current execution if requestIdleCallback is unavailable await new Promise(resolve => setTimeout(resolve, 50)); _log(`Idle fallback timer completed.`); } // #endregion // #region Document Idle if (typeof document !== 'undefined') { _log(`Executing document-idle phase...`); const scriptPaths = []; _log(` Executing JS (idle): ${scriptPaths}`); try { // Keep variables from being redeclared for global scope, but also make them apply to global scope. (Theoretically) with (globalThis){; ;} } catch(e) { _error(` Error executing scripts ${scriptPaths}`, e); } } else { _log(`Skipping document-idle phase (no document).`); } _log(`All execution phases complete, re-firing load events.`); document.dispatchEvent(new Event("DOMContentLoaded", { bubbles: true, cancelable: true })); } // #endregion // #region Event Listener No changes needed here --- window.addEventListener("message", (event) => { if (event.data.type === "openOptionsPage") { openOptionsPage(); } if (event.data.type === "openPopupPage") { openPopupPage(); } if (event.data.type === "closeOptionsPage") { closeOptionsModal(); } if (event.data.type === "closePopupPage") { closePopupModal(); } }); // #endregion // #region Refactored Modal Closing Functions Promise-based --- function closeOptionsModal() { return new Promise((resolve) => { const DURATION = 100; const backdrop = document.getElementById("extension-options-backdrop"); const modal = document.getElementById("extension-options-modal"); if (!backdrop || !modal) { return resolve(); } modal.style.animation = `modalCloseAnimation ${DURATION / 1000}s ease-out forwards`; backdrop.style.animation = `backdropFadeOut ${DURATION / 1000}s ease-out forwards`; setTimeout(() => { if (confirm("Close options and reload the page?")) { window.location.reload(); // Note: This will stop further execution } else { backdrop.remove(); } resolve(); }, DURATION); }); } function closePopupModal() { return new Promise((resolve) => { const DURATION = 100; const backdrop = document.getElementById("extension-popup-backdrop"); const modal = document.getElementById("extension-popup-modal"); if (!backdrop || !modal) { return resolve(); } modal.style.animation = `modalCloseAnimation ${DURATION / 1000}s ease-out forwards`; backdrop.style.animation = `backdropFadeOut ${DURATION / 1000}s ease-out forwards`; setTimeout(() => { backdrop.remove(); resolve(); }, DURATION); }); } // #endregion // #region Simplified Public API Functions --- async function openPopupPage() { if (!POPUP_PAGE_PATH || typeof EXTENSION_ASSETS_MAP === "undefined") { _warn("No popup page available."); return; } await openModal({ type: "popup", pagePath: POPUP_PAGE_PATH, defaultTitle: "Extension Popup", closeFn: closePopupModal, }); } async function openOptionsPage() { if (!OPTIONS_PAGE_PATH || typeof EXTENSION_ASSETS_MAP === "undefined") { _warn("No options page available."); return; } await openModal({ type: "options", pagePath: OPTIONS_PAGE_PATH, defaultTitle: "Extension Options", closeFn: closeOptionsModal, }); } // #endregion // #region Generic Modal Logic Style Injection --- let stylesInjected = false; function injectGlobalStyles() { if (stylesInjected) return; stylesInjected = true; const styles = ` .extension-backdrop { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0, 0, 0, 0.13); backdrop-filter: blur(3px); z-index: 2147483646; display: flex; align-items: center; justify-content: center; animation: backdropFadeIn 0.3s ease-out forwards; } .extension-modal { z-index: 2147483647; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; --background: #ffffff; --rad: 10px; --border: #666; --border-thickness: 2px; display: flex; flex-direction: column; overflow: hidden; animation: modalOpenAnimation 0.3s ease-out forwards; } /* Size specific styles */ .extension-modal.popup-size { width: 400px; height: 600px; max-width: calc(100vw - 40px); max-height: calc(100vh - 40px); } .extension-modal.options-size { width: calc(100vw - 80px); height: calc(100vh - 80px); max-width: 1200px; max-height: 800px; } /* Common modal components */ .extension-modal .modal-header { display: flex; justify-content: space-between; align-items: flex-end; padding: 0 16px; position: relative; flex-shrink: 0; } .extension-modal .tab { padding: 12px 16px; color: #606266; display: flex; align-items: center; gap: 8px; font-size: 14px; cursor: pointer; border-radius: var(--rad) var(--rad) 0 0; transition: background-color 0.2s ease; user-select: none; } .extension-modal .tab.active, .extension-modal .tab.close-button { background-color: var(--background); border: var(--border-thickness) solid var(--border); border-bottom-color: var(--background); margin-bottom: -1px; z-index: 1; color: #303133; font-weight: 500; } .extension-modal .tab.close-button { padding: 8px; } .extension-modal .tab.close-button:hover { background-color: #f5f7fa; } .extension-modal .tab svg { stroke: currentColor; } .extension-modal .tab.active img { width: 16px; height: 16px; } .extension-modal .tab.close-button svg { width: 20px; height: 20px; } .extension-modal .modal-content { flex-grow: 1; position: relative; border-radius: var(--rad); overflow: hidden; bottom: calc(var(--border-thickness) - 1px); border: var(--border-thickness) solid var(--border); } .extension-modal .modal-content iframe { width: 100%; height: 100%; border: 0; background: white; } /* Animations */ @keyframes backdropFadeIn { from { opacity: 0; backdrop-filter: blur(0px); } to { opacity: 1; backdrop-filter: blur(3px); } } @keyframes backdropFadeOut { from { opacity: 1; backdrop-filter: blur(3px); } to { opacity: 0; backdrop-filter: blur(0px); } } @keyframes modalOpenAnimation { from { transform: scaleY(0.8); opacity: 0; } to { transform: scaleY(1); opacity: 1; } } @keyframes modalCloseAnimation { from { transform: scaleY(1); opacity: 1; } to { transform: scaleY(0.8); opacity: 0; } } `; const styleSheet = document.createElement("style"); styleSheet.id = "extension-global-styles"; styleSheet.innerText = styles; document.head.appendChild(styleSheet); } async function openModal(config) { injectGlobalStyles(); const { type, pagePath, defaultTitle, closeFn } = config; const html = EXTENSION_ASSETS_MAP[pagePath]; if (!html) { _warn(`${defaultTitle} HTML not found in asset map`); return; } const backdropId = `extension-${type}-backdrop`; const modalId = `extension-${type}-modal`; const sizeClass = `${type}-size`; // #endregion // #region Smoothly close the other modal if it s open --- const otherType = type === "popup" ? "options" : "popup"; const otherBackdrop = document.getElementById( `extension-${otherType}-backdrop` ); if (otherBackdrop) { // Await the correct close function await (otherType === "popup" ? closePopupModal() : closeOptionsModal()); } let backdrop = document.getElementById(backdropId); let modal, iframe; if (!backdrop) { backdrop = document.createElement("div"); backdrop.id = backdropId; backdrop.className = "extension-backdrop"; modal = document.createElement("div"); modal.id = modalId; modal.className = `extension-modal ${sizeClass}`; const extensionName = INJECTED_MANIFEST.name || defaultTitle; const iconSrc = EXTENSION_ICON || "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIHN0cm9rZT0ibm9uZSIgZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Ik00IDdoM2ExIDEgMCAwIDAgMSAtMXYtMWEyIDIgMCAwIDEgNCAwdjFhMSAxIDAgMCAwIDEgMWgzYTEgMSAwIDAgMSAxIDF2M2ExIDEgMCAwIDAgMSAxaDFhMiAyIDAgMCAxIDAgNGgtMWExIDEgMCAwIDAgLTEgMXYzYTEgMSAwIDAgMSAtMSAxaC0zYTEgMSAwIDAgMSAtMSAtMXYtMWEyIDIgMCAwIDAgLTQgMHYxYTEgMSAwIDAgMSAtMSAxaC0zYTEgMSAwIDAgMSAtMSAtMXYtM2ExIDEgMCAwIDEgMSAtMWgxYTIgMiAwIDAgMCAwIC00aC0xYTEgMSAwIDAgMSAtMSAtMXYtM2ExIDEgMCAwIDEgMSAtMSIgLz48L3N2Zz4="; modal.innerHTML = ` `; backdrop.appendChild(modal); backdrop.addEventListener("click", (e) => { if (e.target === backdrop) closeFn(); }); modal.querySelector(".close-button").addEventListener("click", closeFn); document.body.appendChild(backdrop); iframe = modal.querySelector("iframe"); } else { // If it already exists, just make sure it's visible backdrop.style.display = "flex"; modal = backdrop.querySelector(".extension-modal"); iframe = modal.querySelector("iframe"); } // Load content into iframe try { const polyfillString = generateCompletePolyfillForIframe(); const doc = new DOMParser().parseFromString(html, "text/html"); const script = doc.createElement("script"); script.textContent = polyfillString; doc.head.insertAdjacentElement("afterbegin", script); iframe.srcdoc = doc.documentElement.outerHTML; } catch (e) { _error("Error generating complete polyfill for iframe", e); iframe.srcdoc = html; } } function generateCompletePolyfillForIframe() { const polyfillString = "\n// -- Messaging implementation\n\nfunction createEventBus(\n scopeId,\n type = \"page\", // \"page\" or \"iframe\"\n { allowedOrigin = \"*\", children = [], parentWindow = null } = {}\n) {\n if (!scopeId) throw new Error(\"createEventBus requires a scopeId\");\n\n const handlers = {};\n\n function handleIncoming(ev) {\n if (allowedOrigin !== \"*\" && ev.origin !== allowedOrigin) return;\n\n const msg = ev.data;\n if (!msg || msg.__eventBus !== true || msg.scopeId !== scopeId) return;\n\n const { event, payload } = msg;\n\n // PAGE: if it's an INIT from an iframe, adopt it\n if (type === \"page\" && event === \"__INIT__\") {\n const win = ev.source;\n if (win && !children.includes(win)) {\n children.push(win);\n }\n return;\n }\n\n (handlers[event] || []).forEach((fn) =>\n fn(payload, { origin: ev.origin, source: ev.source })\n );\n }\n\n window.addEventListener(\"message\", handleIncoming);\n\n function emitTo(win, event, payload) {\n const envelope = {\n __eventBus: true,\n scopeId,\n event,\n payload,\n };\n win.postMessage(envelope, allowedOrigin);\n }\n\n // IFRAME: announce to page on startup\n if (type === \"iframe\") {\n setTimeout(() => {\n const pw = parentWindow || window.parent;\n if (pw && pw.postMessage) {\n emitTo(pw, \"__INIT__\", null);\n }\n }, 0);\n }\n\n return {\n on(event, fn) {\n handlers[event] = handlers[event] || [];\n handlers[event].push(fn);\n },\n off(event, fn) {\n if (!handlers[event]) return;\n handlers[event] = handlers[event].filter((h) => h !== fn);\n },\n /**\n * Emits an event.\n * @param {string} event - The event name.\n * @param {any} payload - The event payload.\n * @param {object} [options] - Emission options.\n * @param {Window} [options.to] - A specific window to target. If provided, message is ONLY sent to the target.\n */\n emit(event, payload, { to } = {}) {\n // If a specific target window is provided, send only to it and DO NOT dispatch locally.\n // This prevents a port from receiving its own messages.\n if (to) {\n if (to && typeof to.postMessage === \"function\") {\n emitTo(to, event, payload);\n }\n return; // Exit after targeted send.\n }\n\n // For broadcast messages (no 'to' target), dispatch locally first.\n (handlers[event] || []).forEach((fn) =>\n fn(payload, { origin: location.origin, source: window })\n );\n\n // Then propagate the broadcast to other windows.\n if (type === \"page\") {\n children.forEach((win) => emitTo(win, event, payload));\n } else {\n const pw = parentWindow || window.parent;\n if (pw && pw.postMessage) {\n emitTo(pw, event, payload);\n }\n }\n },\n };\n}\n\nfunction createRuntime(type = \"background\", bus) {\n let nextId = 1;\n const pending = {};\n const msgListeners = [];\n\n let nextPortId = 1;\n const ports = {};\n const onConnectListeners = [];\n\n function parseArgs(args) {\n let target, message, options, callback;\n const arr = [...args];\n if (arr.length === 0) {\n throw new Error(\"sendMessage requires at least one argument\");\n }\n if (arr.length === 1) {\n return { message: arr[0] };\n }\n // last object could be options\n if (\n arr.length &&\n typeof arr[arr.length - 1] === \"object\" &&\n !Array.isArray(arr[arr.length - 1])\n ) {\n options = arr.pop();\n }\n // last function is callback\n if (arr.length && typeof arr[arr.length - 1] === \"function\") {\n callback = arr.pop();\n }\n if (\n arr.length === 2 &&\n (typeof arr[0] === \"string\" || typeof arr[0] === \"number\")\n ) {\n [target, message] = arr;\n } else {\n [message] = arr;\n }\n return { target, message, options, callback };\n }\n\n if (type === \"background\") {\n bus.on(\"__REQUEST__\", ({ id, message }, { source }) => {\n let responded = false,\n isAsync = false;\n function sendResponse(resp) {\n if (responded) return;\n responded = true;\n // Target the response directly back to the window that sent the request.\n bus.emit(\"__RESPONSE__\", { id, response: resp }, { to: source });\n }\n const results = msgListeners\n .map((fn) => {\n try {\n // msg, sender, sendResponse\n const ret = fn(message, { id, tab: { id: source } }, sendResponse);\n if (ret === true || (ret && typeof ret.then === \"function\")) {\n isAsync = true;\n return ret;\n }\n return ret;\n } catch (e) {\n _error(e);\n }\n })\n .filter((r) => r !== undefined);\n\n const promises = results.filter((r) => r && typeof r.then === \"function\");\n if (!isAsync && promises.length === 0) {\n const out = results.length === 1 ? results[0] : results;\n sendResponse(out);\n } else if (promises.length) {\n Promise.all(promises).then((vals) => {\n if (!responded) {\n const out = vals.length === 1 ? vals[0] : vals;\n sendResponse(out);\n }\n });\n }\n });\n }\n\n if (type !== \"background\") {\n bus.on(\"__RESPONSE__\", ({ id, response }) => {\n const entry = pending[id];\n if (!entry) return;\n entry.resolve(response);\n if (entry.callback) entry.callback(response);\n delete pending[id];\n });\n }\n\n function sendMessage(...args) {\n // Background should be able to send message to itself\n // if (type === \"background\") {\n // throw new Error(\"Background cannot sendMessage to itself\");\n // }\n const { target, message, callback } = parseArgs(args);\n const id = nextId++;\n const promise = new Promise((resolve) => {\n pending[id] = { resolve, callback };\n bus.emit(\"__REQUEST__\", { id, message });\n });\n return promise;\n }\n\n bus.on(\"__PORT_CONNECT__\", ({ portId, name }, { source }) => {\n if (type !== \"background\") return;\n const backgroundPort = makePort(\"background\", portId, name, source);\n ports[portId] = backgroundPort;\n\n onConnectListeners.forEach((fn) => fn(backgroundPort));\n\n // send back a CONNECT_ACK so the client can\n // start listening on its end:\n bus.emit(\"__PORT_CONNECT_ACK__\", { portId, name }, { to: source });\n });\n\n // Clients handle the ACK and finalize their Port object by learning the remote window.\n bus.on(\"__PORT_CONNECT_ACK__\", ({ portId, name }, { source }) => {\n if (type === \"background\") return; // ignore\n const p = ports[portId];\n if (!p) return;\n // Call the port's internal finalize method to complete the handshake\n if (p._finalize) {\n p._finalize(source);\n }\n });\n\n // Any port message travels via \"__PORT_MESSAGE__\"\n bus.on(\"__PORT_MESSAGE__\", (envelope, { source }) => {\n const { portId } = envelope;\n const p = ports[portId];\n if (!p) return;\n p._receive(envelope, source);\n });\n\n // Any port disconnect:\n bus.on(\"__PORT_DISCONNECT__\", ({ portId }) => {\n const p = ports[portId];\n if (!p) return;\n p._disconnect();\n delete ports[portId];\n });\n\n // Refactored makePort to correctly manage internal state and the connection handshake.\n function makePort(side, portId, name, remoteWindow) {\n let onMessageHandlers = [];\n let onDisconnectHandlers = [];\n let buffer = [];\n // Unique instance ID for this port instance\n const instanceId = Math.random().toString(36).slice(2) + Date.now();\n // These state variables are part of the closure and are updated by _finalize\n let _ready = side === \"background\";\n\n function _drainBuffer() {\n buffer.forEach((m) => _post(m));\n buffer = [];\n }\n\n function _post(msg) {\n // Always use the 'to' parameter for port messages, making them directional.\n // Include senderInstanceId\n bus.emit(\n \"__PORT_MESSAGE__\",\n { portId, msg, senderInstanceId: instanceId },\n { to: remoteWindow }\n );\n }\n\n function postMessage(msg) {\n if (!_ready) {\n buffer.push(msg);\n } else {\n _post(msg);\n }\n }\n\n function _receive(envelope, source) {\n // envelope: { msg, senderInstanceId }\n if (envelope.senderInstanceId === instanceId) return; // Don't dispatch to self\n onMessageHandlers.forEach((fn) =>\n fn(envelope.msg, { id: portId, tab: { id: source } })\n );\n }\n\n function disconnect() {\n // Also use the 'to' parameter for disconnect messages\n bus.emit(\"__PORT_DISCONNECT__\", { portId }, { to: remoteWindow });\n _disconnect();\n delete ports[portId];\n }\n\n function _disconnect() {\n onDisconnectHandlers.forEach((fn) => fn());\n onMessageHandlers = [];\n onDisconnectHandlers = [];\n }\n\n // This function is called on the client port when the ACK is received from background.\n // It updates the port's state, completing the connection.\n function _finalize(win) {\n remoteWindow = win; // <-- This is the crucial part: learn the destination\n _ready = true;\n _drainBuffer();\n }\n\n return {\n name,\n sender: {\n id: portId,\n },\n onMessage: {\n addListener(fn) {\n onMessageHandlers.push(fn);\n },\n removeListener(fn) {\n onMessageHandlers = onMessageHandlers.filter((x) => x !== fn);\n },\n },\n onDisconnect: {\n addListener(fn) {\n onDisconnectHandlers.push(fn);\n },\n removeListener(fn) {\n onDisconnectHandlers = onDisconnectHandlers.filter((x) => x !== fn);\n },\n },\n postMessage,\n disconnect,\n // Internal methods used by the runtime\n _receive,\n _disconnect,\n _finalize, // Expose the finalizer for the ACK handler\n };\n }\n\n function connect(connectInfo = {}) {\n if (type === \"background\") {\n throw new Error(\"Background must use onConnect, not connect()\");\n }\n const name = connectInfo.name || \"\";\n const portId = nextPortId++;\n // create the client side port\n // remoteWindow is initially null; it will be set by _finalize upon ACK.\n const clientPort = makePort(\"client\", portId, name, null);\n ports[portId] = clientPort;\n\n // fire the connect event across the bus\n bus.emit(\"__PORT_CONNECT__\", { portId, name });\n return clientPort;\n }\n\n function onConnect(fn) {\n if (type !== \"background\") {\n throw new Error(\"connect event only fires in background\");\n }\n onConnectListeners.push(fn);\n }\n\n return {\n // rpc:\n sendMessage,\n onMessage: {\n addListener(fn) {\n msgListeners.push(fn);\n },\n removeListener(fn) {\n const i = msgListeners.indexOf(fn);\n if (i >= 0) msgListeners.splice(i, 1);\n },\n },\n\n // port API:\n connect,\n onConnect: {\n addListener(fn) {\n onConnect(fn);\n },\n removeListener(fn) {\n const i = onConnectListeners.indexOf(fn);\n if (i >= 0) onConnectListeners.splice(i, 1);\n },\n },\n };\n}\n\n\n// --- Abstraction Layer: PostMessage Target\n\nlet nextRequestId = 1;\nconst pendingRequests = new Map(); // requestId -> { resolve, reject, timeout }\n\nfunction sendAbstractionRequest(method, args = []) {\n return new Promise((resolve, reject) => {\n const requestId = nextRequestId++;\n\n const timeout = setTimeout(() => {\n pendingRequests.delete(requestId);\n reject(new Error(`PostMessage request timeout for method: ${method}`));\n }, 10000);\n\n pendingRequests.set(requestId, { resolve, reject, timeout });\n\n window.parent.postMessage({\n type: \"abstraction-request\",\n requestId,\n method,\n args,\n });\n });\n}\n\nwindow.addEventListener(\"message\", (event) => {\n const { type, requestId, success, result, error } = event.data;\n\n if (type === \"abstraction-response\") {\n const pending = pendingRequests.get(requestId);\n if (pending) {\n clearTimeout(pending.timeout);\n pendingRequests.delete(requestId);\n\n if (success) {\n pending.resolve(result);\n } else {\n const err = new Error(error.message);\n err.stack = error.stack;\n pending.reject(err);\n }\n }\n }\n});\n\nasync function _storageSet(items) {\n return sendAbstractionRequest(\"_storageSet\", [items]);\n}\n\nasync function _storageGet(keys) {\n return sendAbstractionRequest(\"_storageGet\", [keys]);\n}\n\nasync function _storageRemove(keysToRemove) {\n return sendAbstractionRequest(\"_storageRemove\", [keysToRemove]);\n}\n\nasync function _storageClear() {\n return sendAbstractionRequest(\"_storageClear\");\n}\n\nasync function _cookieList(details) {\n return sendAbstractionRequest(\"_cookieList\", [details]);\n}\n\nasync function _cookieSet(details) {\n return sendAbstractionRequest(\"_cookieSet\", [details]);\n}\n\nasync function _cookieDelete(details) {\n return sendAbstractionRequest(\"_cookieDelete\", [details]);\n}\n\nasync function _fetch(url, options) {\n return sendAbstractionRequest(\"_fetch\", [url, options]);\n}\n\nfunction _registerMenuCommand(name, func) {\n _warn(\"_registerMenuCommand called from iframe context:\", name);\n return sendAbstractionRequest(\"_registerMenuCommand\", [\n name,\n func.toString(),\n ]);\n}\n\nfunction _openTab(url, active) {\n return sendAbstractionRequest(\"_openTab\", [url, active]);\n}\n\nasync function _initStorage() {\n return sendAbstractionRequest(\"_initStorage\");\n}\n\n\nconst EXTENSION_ASSETS_MAP = {{EXTENSION_ASSETS_MAP}};\n\n// -- Polyfill Implementation\nfunction buildPolyfill({ isBackground = false, isOtherPage = false } = {}) {\n // Generate a unique context ID for this polyfill instance\n const contextType = isBackground\n ? \"background\"\n : isOtherPage\n ? \"options\"\n : \"content\";\n const contextId = `${contextType}_${Math.random()\n .toString(36)\n .substring(2, 15)}`;\n\n const IS_IFRAME = \"true\" === \"true\";\n const BUS = (function () {\n if (globalThis.__BUS) {\n return globalThis.__BUS;\n }\n globalThis.__BUS = createEventBus(\n \"json-formatter\",\n IS_IFRAME ? \"iframe\" : \"page\",\n );\n return globalThis.__BUS;\n })();\n const RUNTIME = createRuntime(isBackground ? \"background\" : \"tab\", BUS);\n const createNoopListeners = () => ({\n addListener: (callback) => {\n _log(\"addListener\", callback);\n },\n removeListener: (callback) => {\n _log(\"removeListener\", callback);\n },\n });\n // TODO: Stub\n const storageChangeListeners = new Set();\n function broadcastStorageChange(changes, areaName) {\n storageChangeListeners.forEach((listener) => {\n listener(changes, areaName);\n });\n }\n\n let REQ_PERMS = [];\n\n // --- Chrome polyfill\n let chrome = {\n extension: {\n isAllowedIncognitoAccess: () => Promise.resolve(true),\n sendMessage: (...args) => _messagingHandler.sendMessage(...args),\n },\n permissions: {\n // TODO: Remove origin permission means exclude from origin in startup (when checking for content scripts)\n request: (permissions, callback) => {\n _log(\"permissions.request\", permissions, callback);\n if (Array.isArray(permissions)) {\n REQ_PERMS = [...REQ_PERMS, ...permissions];\n }\n if (typeof callback === \"function\") {\n callback(permissions);\n }\n return Promise.resolve(permissions);\n },\n contains: (permissions, callback) => {\n if (typeof callback === \"function\") {\n callback(true);\n }\n return Promise.resolve(true);\n },\n getAll: () => {\n return Promise.resolve({\n permissions: EXTENSION_PERMISSIONS,\n origins: ORIGIN_PERMISSIONS,\n });\n },\n onAdded: createNoopListeners(),\n onRemoved: createNoopListeners(),\n },\n i18n: {\n getUILanguage: () => {\n return USED_LOCALE || \"en\";\n },\n getMessage: (key, substitutions = []) => {\n if (typeof substitutions === \"string\") {\n substitutions = [substitutions];\n }\n if (typeof LOCALE_KEYS !== \"undefined\" && LOCALE_KEYS[key]) {\n return LOCALE_KEYS[key].message?.replace(\n /\\$(\\d+)/g,\n (match, p1) => substitutions[p1 - 1] || match,\n );\n }\n return key;\n },\n },\n alarms: {\n onAlarm: createNoopListeners(),\n create: () => {\n _log(\"alarms.create\", arguments);\n },\n get: () => {\n _log(\"alarms.get\", arguments);\n },\n },\n runtime: {\n ...RUNTIME,\n onInstalled: createNoopListeners(),\n onStartup: createNoopListeners(),\n // TODO: Postmessage to parent to open options page or call openOptionsPage\n openOptionsPage: () => {\n // const url = chrome.runtime.getURL(OPTIONS_PAGE_PATH);\n // console.log(\"openOptionsPage\", _openTab, url, EXTENSION_ASSETS_MAP);\n // _openTab(url);\n if (typeof openOptionsPage === \"function\") {\n openOptionsPage();\n } else if (window.parent) {\n window.parent.postMessage({ type: \"openOptionsPage\" }, \"*\");\n } else {\n _warn(\"openOptionsPage not available.\");\n }\n },\n getManifest: () => {\n // The manifest object will be injected into the scope where buildPolyfill is called\n if (typeof INJECTED_MANIFEST !== \"undefined\") {\n return JSON.parse(JSON.stringify(INJECTED_MANIFEST)); // Return deep copy\n }\n _warn(\"INJECTED_MANIFEST not found for chrome.runtime.getManifest\");\n return { name: \"Unknown\", version: \"0.0\", manifest_version: 2 };\n },\n getURL: (path) => {\n if (!path) return \"\";\n if (path.startsWith(\"/\")) {\n path = path.substring(1);\n }\n\n if (typeof _createAssetUrl === \"function\") {\n return _createAssetUrl(path);\n }\n\n _warn(\n `chrome.runtime.getURL fallback for '${path}'. Assets may not be available.`,\n );\n // Attempt a relative path resolution (highly context-dependent and likely wrong)\n try {\n if (window.location.protocol.startsWith(\"http\")) {\n return new URL(path, window.location.href).toString();\n }\n } catch (e) {\n /* ignore error, fallback */\n }\n return path;\n },\n id: \"polyfilled-extension-\" + Math.random().toString(36).substring(2, 15),\n lastError: null,\n setUninstallURL: () => {},\n setUpdateURL: () => {},\n getPlatformInfo: async () => {\n const platform = {\n os: \"unknown\",\n arch: \"unknown\",\n nacl_arch: \"unknown\",\n };\n\n if (typeof navigator !== \"undefined\") {\n const userAgent = navigator.userAgent.toLowerCase();\n if (userAgent.includes(\"mac\")) platform.os = \"mac\";\n else if (userAgent.includes(\"win\")) platform.os = \"win\";\n else if (userAgent.includes(\"linux\")) platform.os = \"linux\";\n else if (userAgent.includes(\"android\")) platform.os = \"android\";\n else if (userAgent.includes(\"ios\")) platform.os = \"ios\";\n\n if (userAgent.includes(\"x86_64\") || userAgent.includes(\"amd64\")) {\n platform.arch = \"x86-64\";\n } else if (userAgent.includes(\"i386\") || userAgent.includes(\"i686\")) {\n platform.arch = \"x86-32\";\n } else if (userAgent.includes(\"arm\")) {\n platform.arch = \"arm\";\n }\n }\n\n return platform;\n },\n getBrowserInfo: async () => {\n const info = {\n name: \"unknown\",\n version: \"unknown\",\n buildID: \"unknown\",\n };\n\n if (typeof navigator !== \"undefined\") {\n const userAgent = navigator.userAgent;\n if (userAgent.includes(\"Chrome\")) {\n info.name = \"Chrome\";\n const match = userAgent.match(/Chrome\\/([0-9.]+)/);\n if (match) info.version = match[1];\n } else if (userAgent.includes(\"Firefox\")) {\n info.name = \"Firefox\";\n const match = userAgent.match(/Firefox\\/([0-9.]+)/);\n if (match) info.version = match[1];\n } else if (userAgent.includes(\"Safari\")) {\n info.name = \"Safari\";\n const match = userAgent.match(/Version\\/([0-9.]+)/);\n if (match) info.version = match[1];\n }\n }\n\n return info;\n },\n },\n storage: {\n local: {\n get: function (keys, callback) {\n if (typeof _storageGet !== \"function\")\n throw new Error(\"_storageGet not defined\");\n\n const promise = _storageGet(keys);\n\n if (typeof callback === \"function\") {\n promise\n .then((result) => {\n try {\n callback(result);\n } catch (e) {\n _error(\"Error in storage.get callback:\", e);\n }\n })\n .catch((error) => {\n _error(\"Storage.get error:\", error);\n callback({});\n });\n return;\n }\n\n return promise;\n },\n set: function (items, callback) {\n if (typeof _storageSet !== \"function\")\n throw new Error(\"_storageSet not defined\");\n\n const promise = _storageSet(items).then((result) => {\n broadcastStorageChange(items, \"local\");\n return result;\n });\n\n if (typeof callback === \"function\") {\n promise\n .then((result) => {\n try {\n callback(result);\n } catch (e) {\n _error(\"Error in storage.set callback:\", e);\n }\n })\n .catch((error) => {\n _error(\"Storage.set error:\", error);\n callback();\n });\n return;\n }\n\n return promise;\n },\n remove: function (keys, callback) {\n if (typeof _storageRemove !== \"function\")\n throw new Error(\"_storageRemove not defined\");\n\n const promise = _storageRemove(keys).then((result) => {\n const changes = {};\n const keyList = Array.isArray(keys) ? keys : [keys];\n keyList.forEach((key) => {\n changes[key] = { oldValue: undefined, newValue: undefined };\n });\n broadcastStorageChange(changes, \"local\");\n return result;\n });\n\n if (typeof callback === \"function\") {\n promise\n .then((result) => {\n try {\n callback(result);\n } catch (e) {\n _error(\"Error in storage.remove callback:\", e);\n }\n })\n .catch((error) => {\n _error(\"Storage.remove error:\", error);\n callback();\n });\n return;\n }\n\n return promise;\n },\n clear: function (callback) {\n if (typeof _storageClear !== \"function\")\n throw new Error(\"_storageClear not defined\");\n\n const promise = _storageClear().then((result) => {\n broadcastStorageChange({}, \"local\");\n return result;\n });\n\n if (typeof callback === \"function\") {\n promise\n .then((result) => {\n try {\n callback(result);\n } catch (e) {\n _error(\"Error in storage.clear callback:\", e);\n }\n })\n .catch((error) => {\n _error(\"Storage.clear error:\", error);\n callback();\n });\n return;\n }\n\n return promise;\n },\n onChanged: {\n addListener: (callback) => {\n storageChangeListeners.add(callback);\n },\n removeListener: (callback) => {\n storageChangeListeners.delete(callback);\n },\n },\n },\n sync: {\n get: function (keys, callback) {\n _warn(\"chrome.storage.sync polyfill maps to local\");\n return chrome.storage.local.get(keys, callback);\n },\n set: function (items, callback) {\n _warn(\"chrome.storage.sync polyfill maps to local\");\n\n const promise = chrome.storage.local.set(items).then((result) => {\n broadcastStorageChange(items, \"sync\");\n return result;\n });\n\n if (typeof callback === \"function\") {\n promise\n .then((result) => {\n try {\n callback(result);\n } catch (e) {\n _error(\"Error in storage.sync.set callback:\", e);\n }\n })\n .catch((error) => {\n _error(\"Storage.sync.set error:\", error);\n callback();\n });\n return;\n }\n\n return promise;\n },\n remove: function (keys, callback) {\n _warn(\"chrome.storage.sync polyfill maps to local\");\n\n const promise = chrome.storage.local.remove(keys).then((result) => {\n const changes = {};\n const keyList = Array.isArray(keys) ? keys : [keys];\n keyList.forEach((key) => {\n changes[key] = { oldValue: undefined, newValue: undefined };\n });\n broadcastStorageChange(changes, \"sync\");\n return result;\n });\n\n if (typeof callback === \"function\") {\n promise\n .then((result) => {\n try {\n callback(result);\n } catch (e) {\n _error(\"Error in storage.sync.remove callback:\", e);\n }\n })\n .catch((error) => {\n _error(\"Storage.sync.remove error:\", error);\n callback();\n });\n return;\n }\n\n return promise;\n },\n clear: function (callback) {\n _warn(\"chrome.storage.sync polyfill maps to local\");\n\n const promise = chrome.storage.local.clear().then((result) => {\n broadcastStorageChange({}, \"sync\");\n return result;\n });\n\n if (typeof callback === \"function\") {\n promise\n .then((result) => {\n try {\n callback(result);\n } catch (e) {\n _error(\"Error in storage.sync.clear callback:\", e);\n }\n })\n .catch((error) => {\n _error(\"Storage.sync.clear error:\", error);\n callback();\n });\n return;\n }\n\n return promise;\n },\n onChanged: {\n addListener: (callback) => {\n storageChangeListeners.add(callback);\n },\n removeListener: (callback) => {\n storageChangeListeners.delete(callback);\n },\n },\n },\n onChanged: {\n addListener: (callback) => {\n storageChangeListeners.add(callback);\n },\n removeListener: (callback) => {\n storageChangeListeners.delete(callback);\n },\n },\n managed: {\n get: function (keys, callback) {\n _warn(\"chrome.storage.managed polyfill is read-only empty.\");\n\n const promise = Promise.resolve({});\n\n if (typeof callback === \"function\") {\n promise.then((result) => {\n try {\n callback(result);\n } catch (e) {\n _error(\"Error in storage.managed.get callback:\", e);\n }\n });\n return;\n }\n\n return promise;\n },\n },\n },\n cookies: (function () {\n const cookieChangeListeners = new Set();\n function broadcastCookieChange(changeInfo) {\n cookieChangeListeners.forEach((listener) => {\n try {\n listener(changeInfo);\n } catch (e) {\n _error(\"Error in cookies.onChanged listener:\", e);\n }\n });\n }\n\n function handlePromiseCallback(promise, callback) {\n if (typeof callback === \"function\") {\n promise\n .then((result) => callback(result))\n .catch((error) => {\n // chrome.runtime.lastError = { message: error.message }; // TODO: Implement lastError\n _error(error);\n callback(); // Call with undefined on error\n });\n return;\n }\n return promise;\n }\n\n return {\n get: function (details, callback) {\n if (typeof _cookieList !== \"function\") {\n return handlePromiseCallback(\n Promise.reject(new Error(\"_cookieList not defined\")),\n callback,\n );\n }\n const promise = _cookieList({\n url: details.url,\n name: details.name,\n storeId: details.storeId,\n partitionKey: details.partitionKey,\n }).then((cookies) => {\n if (!cookies || cookies.length === 0) {\n return null;\n }\n // Sort by path length (longest first), then creation time (earliest first, if available)\n cookies.sort((a, b) => {\n const pathLenDiff = (b.path || \"\").length - (a.path || \"\").length;\n if (pathLenDiff !== 0) return pathLenDiff;\n return (a.creationTime || 0) - (b.creationTime || 0);\n });\n return cookies[0];\n });\n return handlePromiseCallback(promise, callback);\n },\n\n getAll: function (details, callback) {\n if (typeof _cookieList !== \"function\") {\n return handlePromiseCallback(\n Promise.reject(new Error(\"_cookieList not defined\")),\n callback,\n );\n }\n if (details.partitionKey) {\n _warn(\n \"cookies.getAll: partitionKey is not fully supported in this environment.\",\n );\n }\n const promise = _cookieList(details);\n return handlePromiseCallback(promise, callback);\n },\n\n set: function (details, callback) {\n const promise = (async () => {\n if (\n typeof _cookieSet !== \"function\" ||\n typeof _cookieList !== \"function\"\n ) {\n throw new Error(\"_cookieSet or _cookieList not defined\");\n }\n if (details.partitionKey) {\n _warn(\n \"cookies.set: partitionKey is not fully supported in this environment.\",\n );\n }\n\n const getDetails = {\n url: details.url,\n name: details.name,\n storeId: details.storeId,\n };\n const oldCookies = await _cookieList(getDetails);\n const oldCookie = oldCookies && oldCookies[0];\n\n if (oldCookie) {\n broadcastCookieChange({\n cause: \"overwrite\",\n cookie: oldCookie,\n removed: true,\n });\n }\n\n await _cookieSet(details);\n const newCookies = await _cookieList(getDetails);\n const newCookie = newCookies && newCookies[0];\n\n if (newCookie) {\n broadcastCookieChange({\n cause: \"explicit\",\n cookie: newCookie,\n removed: false,\n });\n }\n return newCookie || null;\n })();\n return handlePromiseCallback(promise, callback);\n },\n\n remove: function (details, callback) {\n const promise = (async () => {\n if (\n typeof _cookieDelete !== \"function\" ||\n typeof _cookieList !== \"function\"\n ) {\n throw new Error(\"_cookieDelete or _cookieList not defined\");\n }\n const oldCookies = await _cookieList(details);\n const oldCookie = oldCookies && oldCookies[0];\n\n if (!oldCookie) return null; // Nothing to remove\n\n await _cookieDelete(details);\n\n broadcastCookieChange({\n cause: \"explicit\",\n cookie: oldCookie,\n removed: true,\n });\n\n return {\n url: details.url,\n name: details.name,\n storeId: details.storeId || \"0\",\n partitionKey: details.partitionKey,\n };\n })();\n return handlePromiseCallback(promise, callback);\n },\n\n getAllCookieStores: function (callback) {\n const promise = Promise.resolve([\n { id: \"0\", tabIds: [1] }, // Mock store for the current context\n ]);\n return handlePromiseCallback(promise, callback);\n },\n\n getPartitionKey: function (details, callback) {\n _warn(\n \"chrome.cookies.getPartitionKey is not supported in this environment.\",\n );\n const promise = Promise.resolve({ partitionKey: {} }); // Return empty partition key\n return handlePromiseCallback(promise, callback);\n },\n\n onChanged: {\n addListener: (callback) => {\n if (typeof callback === \"function\") {\n cookieChangeListeners.add(callback);\n }\n },\n removeListener: (callback) => {\n cookieChangeListeners.delete(callback);\n },\n },\n };\n })(),\n tabs: {\n query: async (queryInfo) => {\n _warn(\"chrome.tabs.query polyfill only returns current tab info.\");\n const dummyId = Math.floor(Math.random() * 1000) + 1;\n return [\n {\n id: dummyId,\n url: CURRENT_LOCATION,\n active: true,\n windowId: 1,\n status: \"complete\",\n },\n ];\n },\n create: async ({ url, active = true }) => {\n _log(`[Polyfill tabs.create] URL: ${url}`);\n if (typeof _openTab !== \"function\")\n throw new Error(\"_openTab not defined\");\n _openTab(url, active);\n const dummyId = Math.floor(Math.random() * 1000) + 1001;\n return Promise.resolve({\n id: dummyId,\n url: url,\n active,\n windowId: 1,\n });\n },\n sendMessage: async (tabId, message) => {\n _warn(\n `chrome.tabs.sendMessage polyfill (to tab ${tabId}) redirects to runtime.sendMessage (current context).`,\n );\n return chrome.runtime.sendMessage(message);\n },\n onActivated: createNoopListeners(),\n onUpdated: createNoopListeners(),\n onRemoved: createNoopListeners(),\n onReplaced: createNoopListeners(),\n onCreated: createNoopListeners(),\n onMoved: createNoopListeners(),\n onDetached: createNoopListeners(),\n onAttached: createNoopListeners(),\n },\n windows: {\n onFocusChanged: createNoopListeners(),\n onCreated: createNoopListeners(),\n onRemoved: createNoopListeners(),\n onFocused: createNoopListeners(),\n onFocus: createNoopListeners(),\n onBlur: createNoopListeners(),\n onFocused: createNoopListeners(),\n },\n notifications: {\n create: async (notificationId, options) => {\n try {\n let id = notificationId;\n let notificationOptions = options;\n\n if (typeof notificationId === \"object\" && notificationId !== null) {\n notificationOptions = notificationId;\n id = \"notification_\" + Math.random().toString(36).substring(2, 15);\n } else if (typeof notificationId === \"string\" && options) {\n id = notificationId;\n notificationOptions = options;\n } else {\n throw new Error(\"Invalid parameters for notifications.create\");\n }\n\n if (!notificationOptions || typeof notificationOptions !== \"object\") {\n throw new Error(\"Notification options must be an object\");\n }\n\n const {\n title,\n message,\n iconUrl,\n type = \"basic\",\n } = notificationOptions;\n\n if (!title || !message) {\n throw new Error(\"Notification must have title and message\");\n }\n\n if (\"Notification\" in window) {\n if (Notification.permission === \"granted\") {\n const notification = new Notification(title, {\n body: message,\n icon: iconUrl,\n tag: id,\n });\n\n _log(`[Notifications] Created notification: ${id}`);\n return id;\n } else if (Notification.permission === \"default\") {\n const permission = await Notification.requestPermission();\n if (permission === \"granted\") {\n const notification = new Notification(title, {\n body: message,\n icon: iconUrl,\n tag: id,\n });\n _log(\n `[Notifications] Created notification after permission: ${id}`,\n );\n return id;\n } else {\n _warn(\"[Notifications] Permission denied for notifications\");\n return id;\n }\n } else {\n _warn(\"[Notifications] Notifications are blocked\");\n return id;\n }\n } else {\n _warn(\n \"[Notifications] Native notifications not supported, using console fallback\",\n );\n _log(`[Notification] ${title}: ${message}`);\n return id;\n }\n } catch (error) {\n _error(\"[Notifications] Error creating notification:\", error.message);\n throw error;\n }\n },\n clear: async (notificationId) => {\n _log(`[Notifications] Clear notification: ${notificationId}`);\n // For native notifications, there's no direct way to clear by ID\n // This is a limitation of the Web Notifications API\n return true;\n },\n getAll: async () => {\n _warn(\"[Notifications] getAll not fully supported in polyfill\");\n return {};\n },\n getPermissionLevel: async () => {\n if (\"Notification\" in window) {\n const permission = Notification.permission;\n return { level: permission === \"granted\" ? \"granted\" : \"denied\" };\n }\n return { level: \"denied\" };\n },\n },\n contextMenus: {\n create: (createProperties, callback) => {\n try {\n if (!createProperties || typeof createProperties !== \"object\") {\n throw new Error(\"Context menu create properties must be an object\");\n }\n\n const { id, title, contexts = [\"page\"], onclick } = createProperties;\n const menuId =\n id || `menu_${Math.random().toString(36).substring(2, 15)}`;\n\n if (!title || typeof title !== \"string\") {\n throw new Error(\"Context menu must have a title\");\n }\n\n // Store menu items for potential use\n if (!window._polyfillContextMenus) {\n window._polyfillContextMenus = new Map();\n }\n\n window._polyfillContextMenus.set(menuId, {\n id: menuId,\n title,\n contexts,\n onclick,\n enabled: createProperties.enabled !== false,\n });\n\n _log(\n `[ContextMenus] Created context menu item: ${title} (${menuId})`,\n );\n\n // Try to register a menu command as fallback\n if (typeof _registerMenuCommand === \"function\") {\n try {\n _registerMenuCommand(\n title,\n onclick ||\n (() => {\n _log(`Context menu clicked: ${title}`);\n }),\n );\n } catch (e) {\n _warn(\n \"[ContextMenus] Failed to register as menu command:\",\n e.message,\n );\n }\n }\n\n if (callback && typeof callback === \"function\") {\n setTimeout(() => callback(), 0);\n }\n\n return menuId;\n } catch (error) {\n _error(\"[ContextMenus] Error creating context menu:\", error.message);\n if (callback && typeof callback === \"function\") {\n setTimeout(() => callback(), 0);\n }\n throw error;\n }\n },\n update: (id, updateProperties, callback) => {\n try {\n if (\n !window._polyfillContextMenus ||\n !window._polyfillContextMenus.has(id)\n ) {\n throw new Error(`Context menu item not found: ${id}`);\n }\n\n const menuItem = window._polyfillContextMenus.get(id);\n Object.assign(menuItem, updateProperties);\n\n _log(`[ContextMenus] Updated context menu item: ${id}`);\n\n if (callback && typeof callback === \"function\") {\n setTimeout(() => callback(), 0);\n }\n } catch (error) {\n _error(\"[ContextMenus] Error updating context menu:\", error.message);\n if (callback && typeof callback === \"function\") {\n setTimeout(() => callback(), 0);\n }\n }\n },\n remove: (menuItemId, callback) => {\n try {\n if (\n window._polyfillContextMenus &&\n window._polyfillContextMenus.has(menuItemId)\n ) {\n window._polyfillContextMenus.delete(menuItemId);\n _log(`[ContextMenus] Removed context menu item: ${menuItemId}`);\n } else {\n _warn(\n `[ContextMenus] Context menu item not found for removal: ${menuItemId}`,\n );\n }\n\n if (callback && typeof callback === \"function\") {\n setTimeout(() => callback(), 0);\n }\n } catch (error) {\n _error(\"[ContextMenus] Error removing context menu:\", error.message);\n if (callback && typeof callback === \"function\") {\n setTimeout(() => callback(), 0);\n }\n }\n },\n removeAll: (callback) => {\n try {\n if (window._polyfillContextMenus) {\n const count = window._polyfillContextMenus.size;\n window._polyfillContextMenus.clear();\n _log(`[ContextMenus] Removed all ${count} context menu items`);\n }\n\n if (callback && typeof callback === \"function\") {\n setTimeout(() => callback(), 0);\n }\n } catch (error) {\n _error(\n \"[ContextMenus] Error removing all context menus:\",\n error.message,\n );\n if (callback && typeof callback === \"function\") {\n setTimeout(() => callback(), 0);\n }\n }\n },\n onClicked: {\n addListener: (callback) => {\n if (!window._polyfillContextMenuListeners) {\n window._polyfillContextMenuListeners = new Set();\n }\n window._polyfillContextMenuListeners.add(callback);\n _log(\"[ContextMenus] Added click listener\");\n },\n removeListener: (callback) => {\n if (window._polyfillContextMenuListeners) {\n window._polyfillContextMenuListeners.delete(callback);\n _log(\"[ContextMenus] Removed click listener\");\n }\n },\n },\n },\n };\n\n const tc = (fn) => {\n try {\n fn();\n } catch (e) {}\n };\n const loggingProxyHandler = (_key) => ({\n get(target, key, receiver) {\n tc(() => _log(`[${contextType}] [CHROME - ${_key}] Getting ${key}`));\n return Reflect.get(target, key, receiver);\n },\n set(target, key, value, receiver) {\n tc(() =>\n _log(`[${contextType}] [CHROME - ${_key}] Setting ${key} to ${value}`),\n );\n return Reflect.set(target, key, value, receiver);\n },\n has(target, key) {\n tc(() =>\n _log(`[${contextType}] [CHROME - ${_key}] Checking if ${key} exists`),\n );\n return Reflect.has(target, key);\n },\n });\n chrome = Object.fromEntries(\n Object.entries(chrome).map(([key, value]) => [\n key,\n new Proxy(value, loggingProxyHandler(key)),\n ]),\n );\n\n // Alias browser to chrome for common Firefox pattern\n const browser = new Proxy(chrome, loggingProxyHandler);\n\n const oldGlobalThis = globalThis;\n const oldWindow = window;\n const oldSelf = self;\n const oldGlobal = globalThis;\n const __globalsStorage = {};\n\n const TO_MODIFY = [oldGlobalThis, oldWindow, oldSelf, oldGlobal];\n const set = (k, v) => {\n __globalsStorage[k] = v;\n TO_MODIFY.forEach((target) => {\n target[k] = v;\n });\n };\n const proxyHandler = {\n get(target, key, receiver) {\n const fns = [\n () => __globalsStorage[key],\n () => Reflect.get(target, key, target),\n () => target[key],\n ];\n const out = fns\n .map((f) => {\n try {\n let out = f();\n return out;\n } catch (e) {\n return undefined;\n }\n })\n .find((f) => f !== undefined);\n if (typeof out === \"function\") {\n return out.bind(target);\n }\n return out;\n },\n set(target, key, value, receiver) {\n try {\n tc(() => _log(`[${contextType}] Setting ${key} to ${value}`));\n set(key, value);\n return Reflect.set(target, key, value, receiver);\n } catch (e) {\n _error(\"Error setting\", key, value, e);\n try {\n target[key] = value;\n return true;\n } catch (e) {\n _error(\"Error setting\", key, value, e);\n }\n return false;\n }\n },\n has(target, key) {\n try {\n return key in __globalsStorage || key in target;\n } catch (e) {\n _error(\"Error has\", key, e);\n try {\n return key in __globalsStorage || key in target;\n } catch (e) {\n _error(\"Error has\", key, e);\n }\n return false;\n }\n },\n getOwnPropertyDescriptor(target, key) {\n try {\n if (key in __globalsStorage) {\n return {\n configurable: true,\n enumerable: true,\n writable: true,\n value: __globalsStorage[key],\n };\n }\n // fall back to the real globalThis\n const desc = Reflect.getOwnPropertyDescriptor(target, key);\n // ensure it's configurable so the with‑scope binding logic can override it\n if (desc && !desc.configurable) {\n desc.configurable = true;\n }\n return desc;\n } catch (e) {\n _error(\"Error getOwnPropertyDescriptor\", key, e);\n return {\n configurable: true,\n enumerable: true,\n writable: true,\n value: undefined,\n };\n }\n },\n\n defineProperty(target, key, descriptor) {\n try {\n // Normalize descriptor to avoid mixed accessor & data attributes\n const hasAccessor = \"get\" in descriptor || \"set\" in descriptor;\n\n if (hasAccessor) {\n // Build a clean descriptor without value/writable when accessors present\n const normalized = {\n configurable:\n \"configurable\" in descriptor ? descriptor.configurable : true,\n enumerable:\n \"enumerable\" in descriptor ? descriptor.enumerable : false,\n };\n if (\"get\" in descriptor) normalized.get = descriptor.get;\n if (\"set\" in descriptor) normalized.set = descriptor.set;\n\n // Store accessor references for inspection but avoid breaking invariants\n set(key, {\n get: descriptor.get,\n set: descriptor.set,\n });\n\n return Reflect.defineProperty(target, key, normalized);\n }\n\n // Data descriptor path\n set(key, descriptor.value);\n return Reflect.defineProperty(target, key, descriptor);\n } catch (e) {\n _error(\"Error defineProperty\", key, descriptor, e);\n return false;\n }\n },\n };\n\n // Create proxies once proxyHandler is defined\n const proxyWindow = new Proxy(oldWindow, proxyHandler);\n const proxyGlobalThis = new Proxy(oldGlobalThis, proxyHandler);\n const proxyGlobal = new Proxy(oldGlobal, proxyHandler);\n const proxySelf = new Proxy(oldSelf, proxyHandler);\n\n // Seed storage with core globals so lookups succeed inside `with` blocks\n Object.assign(__globalsStorage, {\n chrome,\n browser,\n window: proxyWindow,\n globalThis: proxyGlobalThis,\n global: proxyGlobal,\n self: proxySelf,\n document: oldWindow.document,\n });\n\n const __globals = {\n chrome,\n browser,\n window: proxyWindow,\n globalThis: proxyGlobalThis,\n global: proxyGlobal,\n self: proxySelf,\n __globals: __globalsStorage,\n };\n\n __globals.contextId = contextId;\n __globals.contextType = contextType;\n __globals.module = undefined;\n __globals.amd = undefined;\n __globals.define = undefined;\n __globals.importScripts = (...args) => {\n _log(\"importScripts\", args);\n };\n\n return __globals;\n}\n\n\nif (typeof window !== 'undefined') {\n window.buildPolyfill = buildPolyfill;\n}\n" let newMap = JSON.parse(JSON.stringify(EXTENSION_ASSETS_MAP)); delete newMap[OPTIONS_PAGE_PATH]; const PASS_ON = Object.fromEntries( Object.entries({ LOCALE_KEYS, INJECTED_MANIFEST, USED_LOCALE, EXTENSION_ICON, CURRENT_LOCATION, OPTIONS_PAGE_PATH, CAN_USE_BLOB_CSP, ALL_PERMISSIONS, ORIGIN_PERMISSIONS, EXTENSION_PERMISSIONS, SCRIPT_NAME, _base64ToBlob, _getMimeTypeFromPath, _isTextAsset, _createAssetUrl, _matchGlobPattern, _isWebAccessibleResource, _log, _warn, _error, }).map((i) => { let out = [...i]; if (typeof i[1] === "function") { out[1] = i[1].toString(); } else { out[1] = JSON.stringify(i[1]); } return out; }) ); _log(PASS_ON); return ` ${Object.entries(PASS_ON) .map( (i) => `const ${i[0]} = ${i[1]};\nwindow[${JSON.stringify(i[0])}] = ${i[0]}` ) .join("\n")} _log("Initialized polyfill", {${Object.keys(PASS_ON).join(", ")}}) ${polyfillString.replaceAll("{{EXTENSION_ASSETS_MAP}}", `JSON.parse(unescape(atob("${btoa(encodeURIComponent(JSON.stringify(EXTENSION_ASSETS_MAP)))}")))`)} // Initialize the polyfill context for options page const polyfillCtx = buildPolyfill({ isOtherPage: true }); const APPLY_TO = [window, self, globalThis]; for (const obj of APPLY_TO) { obj.chrome = polyfillCtx.chrome; obj.browser = polyfillCtx.browser; obj.INJECTED_MANIFEST = ${JSON.stringify(INJECTED_MANIFEST)}; } `; } async function main() { _log(`Initializing...`, performance.now()); if (typeof _initStorage === "function") { try { _initStorage() .then(() => { _log(`Storage initialized.`); }) .catch((e) => { _error("Error during storage initialization:", e); }); } catch (e) { _error("Error during storage initialization:", e); } } _log(`Starting content scripts...`); const currentUrl = window.location.href; let shouldRunAnyScript = false; _log(`Checking URL: ${currentUrl}`); if ( CONTENT_SCRIPT_CONFIGS_FOR_MATCHING && CONTENT_SCRIPT_CONFIGS_FOR_MATCHING.length > 0 ) { for (const config of CONTENT_SCRIPT_CONFIGS_FOR_MATCHING) { if ( config.matches && config.matches.some((pattern) => { try { const regex = convertMatchPatternToRegExp(pattern); if (regex.test(currentUrl)) { return true; } return false; } catch (e) { _error(`Error testing match pattern "${pattern}":`, e); return false; } }) ) { shouldRunAnyScript = true; _log(`URL match found via config:`, config); break; } } } else { _log(`No content script configurations found in manifest data.`); } if (shouldRunAnyScript) { let polyfillContext; try { polyfillContext = buildPolyfill({ isBackground: false }); } catch (e) { _error(`Failed to build polyfill:`, e); return; } _log(`Polyfill built. Executing combined script logic...`); // async function executeAllScripts({chrome, browser, global, window, globalThis, self, __globals}, extensionCssData) { await executeAllScripts.call( polyfillContext.globalThis, polyfillContext, extensionCssData ); } else { _log( `No matching content script patterns for this URL. No scripts will be executed.` ); } if (OPTIONS_PAGE_PATH) { if (typeof _registerMenuCommand === "function") { try { _registerMenuCommand("Open Options", openOptionsPage); _log(`Options menu command registered.`); } catch (e) { _error("Failed to register menu command", e); } } } if (POPUP_PAGE_PATH) { if (typeof _registerMenuCommand === "function") { try { _registerMenuCommand("Open Popup", openPopupPage); _log(`Popup menu command registered.`); } catch (e) { _error("Failed to register popup menu command", e); } } } _log(`Initialization sequence complete.`); } main()//.catch((e) => _error(`Error during script initialization:`, e)); try { const fnKey = "OPEN_OPTIONS_PAGE_" + String(SCRIPT_NAME).replace(/\s+/g, "_"); window[fnKey] = openOptionsPage; } catch (e) {} try { const fnKey = "OPEN_POPUP_PAGE_" + String(SCRIPT_NAME).replace(/\s+/g, "_"); window[fnKey] = openPopupPage; } catch (e) {} })(); // #endregion // #endregion // #endregion