diff --git a/additions/juggler/content/FrameTree.js b/additions/juggler/content/FrameTree.js index b6b4e66..65672be 100644 --- a/additions/juggler/content/FrameTree.js +++ b/additions/juggler/content/FrameTree.js @@ -517,6 +517,11 @@ class Frame { frameId: this.id(), name, }); + // Camoufox: Create a main world for the isolated context + if (ChromeUtils.camouGetBool('allowMainWorld', false)) { + const mainWorld = this._runtime.createMW(this.domWindow(), this.domWindow()); + world.mainEquivalent = mainWorld; + } this._worldNameToContext.set(name, world); return world; } diff --git a/additions/juggler/content/Runtime.js b/additions/juggler/content/Runtime.js index 89fdc7f..eaa02f7 100644 --- a/additions/juggler/content/Runtime.js +++ b/additions/juggler/content/Runtime.js @@ -99,6 +99,36 @@ class Runtime { const executionContext = this.findExecutionContext(executionContextId); if (!executionContext) throw new Error('Failed to find execution context with id = ' + executionContextId); + + // Hijack the utilityScript.evaluate function to evaluate in the main world + if ( + ChromeUtils.camouGetBool('allowMainWorld', false) && + functionDeclaration.includes('utilityScript.evaluate') && + args.length >= 4 && + args[3].value && + typeof args[3].value === 'string' && + args[3].value.startsWith('mw:')) { + ChromeUtils.camouDebug(`Evaluating in main world: ${args[3].value}`); + const mainWorldScript = args[3].value.substring(3); + + // Get the main world execution context + const mainContext = executionContext.mainEquivalent; + if (!mainContext) { + throw new Error(`Main world injection is not enabled.`); + } + // Extract arguments for the main world function + const functionArgs = args[5]?.value?.a || []; + try { + const exceptionDetails = {}; + const result = mainContext.executeInGlobal(mainWorldScript, functionArgs, exceptionDetails); + if (!result) + return {exceptionDetails}; + return {result}; + } catch (e) { + throw e; + } + } + const exceptionDetails = {}; let result = await executionContext.evaluateFunction(functionDeclaration, args, exceptionDetails); if (!result) @@ -106,7 +136,7 @@ class Runtime { if (returnByValue) result = executionContext.ensureSerializedToValue(result); return {result}; - } +} async getObjectProperties({executionContextId, objectId}) { const executionContext = this.findExecutionContext(executionContextId); @@ -282,6 +312,11 @@ class Runtime { return context; } + createMW(domWindow, contextGlobal) { + const context = new MainWorldContext(this, domWindow, contextGlobal); + return context; + } + findExecutionContext(executionContextId) { const executionContext = this._executionContexts.get(executionContextId); if (!executionContext) @@ -306,6 +341,68 @@ class Runtime { } } +class MainWorldContext { + constructor(runtime, domWindow, contextGlobal) { + this._runtime = runtime; + this._domWindow = domWindow; + this._contextGlobal = contextGlobal; + this._debuggee = runtime._debugger.addDebuggee(contextGlobal); + } + + _getResult(completionValue, exceptionDetails = {}) { + if (!completionValue) { + exceptionDetails.text = "Evaluation terminated"; + return {success: false, obj: null}; + } + + if (completionValue.throw) { + const result = this._debuggee.executeInGlobalWithBindings(` + (function(error) { + try { + if (error instanceof Error) { + return error.toString(); + } + return String(error); + } catch(e) { + return "Unknown error occurred"; + } + })(e) + `, { e: completionValue.throw }); + + exceptionDetails.text = result.return || "Unknown error"; + return {success: false, obj: null}; + } + + return {success: true, obj: completionValue.return}; + } + + executeInGlobal(script, args = [], exceptionDetails = {}) { + try { + const wrappedScript = ` + (() => { + const result = (${script}); + return typeof result === 'function' + ? result(${args.map(arg => JSON.stringify(arg)).join(', ')}) + : result; + })() + `; + + const result = this._debuggee.executeInGlobal(wrappedScript); + + let {success, obj} = this._getResult(result, exceptionDetails); + if (!success) { + return {exceptionDetails}; + } + + return {value: obj}; + } catch (e) { + exceptionDetails.text = e.message; + exceptionDetails.stack = e.stack; + return {exceptionDetails}; + } + } +} + class ExecutionContext { constructor(runtime, domWindow, contextGlobal, auxData) { this._runtime = runtime; diff --git a/settings/camoucfg.jvv b/settings/camoucfg.jvv index 5a20ff8..2d932e9 100644 --- a/settings/camoucfg.jvv +++ b/settings/camoucfg.jvv @@ -288,6 +288,7 @@ "humanize:minTime": "double[>=0]", "showcursor": "bool", + "allowMainWorld": "bool", "memorysaver": "bool", "addons": "array[str]", "debug": "bool" diff --git a/settings/properties.json b/settings/properties.json index b651725..938d723 100644 --- a/settings/properties.json +++ b/settings/properties.json @@ -91,6 +91,7 @@ { "property": "mediaDevices:webcams", "type": "uint" }, { "property": "mediaDevices:speakers", "type": "uint" }, { "property": "mediaDevices:enabled", "type": "bool" }, + { "property": "allowMainWorld", "type": "bool" }, { "property": "memorysaver", "type": "bool" }, { "property": "addons", "type": "array" }, { "property": "debug", "type": "bool" }