From d540b4478bb0879866dca78d2ed91fd5445a8b27 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Wed, 12 May 2021 17:46:19 +0000 Subject: [PATCH] browser(firefox-stable): simplify isolated world structures (#6542) --- browser_patches/firefox-stable/BUILD_NUMBER | 4 +- .../juggler/content/FrameTree.js | 119 ++++++++---------- .../juggler/content/PageAgent.js | 5 +- .../firefox-stable/juggler/content/Runtime.js | 52 ++++---- .../firefox-stable/juggler/content/main.js | 4 +- .../juggler/protocol/PageHandler.js | 4 - .../juggler/protocol/Protocol.js | 8 -- 7 files changed, 86 insertions(+), 110 deletions(-) diff --git a/browser_patches/firefox-stable/BUILD_NUMBER b/browser_patches/firefox-stable/BUILD_NUMBER index c4e4cb3274..3f78fcb7c2 100644 --- a/browser_patches/firefox-stable/BUILD_NUMBER +++ b/browser_patches/firefox-stable/BUILD_NUMBER @@ -1,2 +1,2 @@ -1249 -Changed: yurys@chromium.org Tue 11 May 2021 09:29:40 AM PDT +1250 +Changed: yurys@chromium.org Wed 12 May 2021 08:53:32 AM PDT diff --git a/browser_patches/firefox-stable/juggler/content/FrameTree.js b/browser_patches/firefox-stable/juggler/content/FrameTree.js index ee06c9b3e4..0b2cce1998 100644 --- a/browser_patches/firefox-stable/juggler/content/FrameTree.js +++ b/browser_patches/firefox-stable/juggler/content/FrameTree.js @@ -22,14 +22,12 @@ class FrameTree { if (!this._browsingContextGroup.__jugglerFrameTrees) this._browsingContextGroup.__jugglerFrameTrees = new Set(); this._browsingContextGroup.__jugglerFrameTrees.add(this); - this._scriptsToEvaluateOnNewDocument = new Map(); this._isolatedWorlds = new Map(); this._webSocketEventService = Cc[ "@mozilla.org/websocketevent/service;1" ].getService(Ci.nsIWebSocketEventService); - this._bindings = new Map(); this._runtime = new Runtime(false /* isWorker */); this._workers = new Map(); this._docShellToFrame = new Map(); @@ -74,22 +72,25 @@ class FrameTree { } addScriptToEvaluateOnNewDocument(script, worldName) { - const scriptId = helper.generateId(); - if (worldName) { - this._isolatedWorlds.set(scriptId, {script, worldName}); + worldName = worldName || ''; + const existing = this._isolatedWorlds.has(worldName); + const world = this._ensureWorld(worldName); + world._scriptsToEvaluateOnNewDocument.push(script); + // FIXME: 'should inherit http credentials from browser context' fails without this + if (worldName && !existing) { for (const frame of this.frames()) - frame.createIsolatedWorld(worldName); - } else { - this._scriptsToEvaluateOnNewDocument.set(scriptId, script); + frame._createIsolatedContext(worldName); } - return {scriptId}; } - removeScriptToEvaluateOnNewDocument(scriptId) { - if (this._isolatedWorlds.has(scriptId)) - this._isolatedWorlds.delete(scriptId); - else - this._scriptsToEvaluateOnNewDocument.delete(scriptId); + _ensureWorld(worldName) { + worldName = worldName || ''; + let world = this._isolatedWorlds.get(worldName); + if (!world) { + world = new IsolatedWorld(worldName); + this._isolatedWorlds.set(worldName, world); + } + return world; } _frameForWorker(workerDebugger) { @@ -149,7 +150,9 @@ class FrameTree { } addBinding(worldName, name, script) { - this._bindings.set(worldName + ':' + name, {worldName, name, script}); + worldName = worldName || ''; + const world = this._ensureWorld(worldName); + world._bindings.set(name, script); for (const frame of this.frames()) frame._addBinding(worldName, name, script); } @@ -314,6 +317,14 @@ FrameTree.Events = { Load: 'load', }; +class IsolatedWorld { + constructor(name) { + this._name = name; + this._scriptsToEvaluateOnNewDocument = []; + this._bindings = new Map(); + } +} + class Frame { constructor(frameTree, runtime, docShell, parentFrame) { this._frameTree = frameTree; @@ -335,9 +346,8 @@ class Frame { this._pendingNavigationURL = null; this._textInputProcessor = null; - this._executionContext = null; - this._isolatedWorlds = new Map(); + this._worldNameToContext = new Map(); this._initialNavigationDone = false; this._webSocketListenerInnerWindowId = 0; @@ -425,7 +435,7 @@ class Frame { }; } - createIsolatedWorld(name) { + _createIsolatedContext(name) { const principal = [this.domWindow()]; // extended principal const sandbox = Cu.Sandbox(principal, { sandboxPrototype: this.domWindow(), @@ -437,13 +447,12 @@ class Frame { frameId: this.id(), name, }); - this._isolatedWorlds.set(world.id(), world); + this._worldNameToContext.set(name, world); return world; } unsafeObject(objectId) { - const contexts = [this.executionContext(), ...this._isolatedWorlds.values()]; - for (const context of contexts) { + for (const context of this._worldNameToContext.values()) { const result = context.unsafeObject(objectId); if (result) return result.object; @@ -452,38 +461,19 @@ class Frame { } dispose() { - for (const world of this._isolatedWorlds.values()) - this._runtime.destroyExecutionContext(world); - this._isolatedWorlds.clear(); - if (this._executionContext) - this._runtime.destroyExecutionContext(this._executionContext); - this._executionContext = null; - } - - _getOrCreateIsolatedContext(worldName) { - for (let context of this._isolatedWorlds.values()) { - if (context.auxData().name === worldName) - return context; - } - return this.createIsolatedWorld(worldName); + for (const context of this._worldNameToContext.values()) + this._runtime.destroyExecutionContext(context); + this._worldNameToContext.clear(); } _addBinding(worldName, name, script) { - const executionContext = worldName ? this._getOrCreateIsolatedContext(worldName) : this._executionContext; + let executionContext = this._worldNameToContext.get(worldName); + if (worldName && !executionContext) + executionContext = this._createIsolatedContext(worldName); if (executionContext) executionContext.addBinding(name, script); } - _evaluateScriptSafely(executionContext, script) { - try { - let result = executionContext.evaluateScript(script); - if (result && result.objectId) - executionContext.disposeObject(result.objectId); - } catch (e) { - dump(`ERROR: ${e.message}\n${e.stack}\n`); - } - } - _onGlobalObjectCleared() { const webSocketService = this._frameTree._webSocketEventService; if (this._webSocketListenerInnerWindowId) @@ -491,33 +481,28 @@ class Frame { this._webSocketListenerInnerWindowId = this.domWindow().windowGlobalChild.innerWindowId; webSocketService.addListener(this._webSocketListenerInnerWindowId, this._webSocketListener); - if (this._executionContext) - this._runtime.destroyExecutionContext(this._executionContext); - for (const world of this._isolatedWorlds.values()) - this._runtime.destroyExecutionContext(world); - this._isolatedWorlds.clear(); + for (const context of this._worldNameToContext.values()) + this._runtime.destroyExecutionContext(context); + this._worldNameToContext.clear(); - this._executionContext = this._runtime.createExecutionContext(this.domWindow(), this.domWindow(), { + this._worldNameToContext.set('', this._runtime.createExecutionContext(this.domWindow(), this.domWindow(), { frameId: this._frameId, name: '', - }); - for (const {script, worldName} of this._frameTree._isolatedWorlds.values()) - this.createIsolatedWorld(worldName); - - // Add bindings before evaluating scripts. - for (const [id, {worldName, name, script}] of this._frameTree._bindings) - this._addBinding(worldName, name, script); - - for (const script of this._frameTree._scriptsToEvaluateOnNewDocument.values()) - this._evaluateScriptSafely(this._executionContext, script); - for (const {script, worldName} of this._frameTree._isolatedWorlds.values()) { - const context = worldName ? this._getOrCreateIsolatedContext(worldName) : this.executionContext(); - this._evaluateScriptSafely(context, script); + })); + for (const [name, world] of this._frameTree._isolatedWorlds) { + if (name) + this._createIsolatedContext(name); + const executionContext = this._worldNameToContext.get(name); + // Add bindings before evaluating scripts. + for (const [name, script] of world._bindings) + executionContext.addBinding(name, script); + for (const script of world._scriptsToEvaluateOnNewDocument) + executionContext.evaluateScriptSafely(script); } } - executionContext() { - return this._executionContext; + mainExecutionContext() { + return this._worldNameToContext.get(''); } textInputProcessor() { diff --git a/browser_patches/firefox-stable/juggler/content/PageAgent.js b/browser_patches/firefox-stable/juggler/content/PageAgent.js index f03a54362a..a58f8309a3 100644 --- a/browser_patches/firefox-stable/juggler/content/PageAgent.js +++ b/browser_patches/firefox-stable/juggler/content/PageAgent.js @@ -150,7 +150,6 @@ class PageAgent { insertText: this._insertText.bind(this), navigate: this._navigate.bind(this), reload: this._reload.bind(this), - removeScriptToEvaluateOnNewDocument: ({scriptId}) => this._frameTree.removeScriptToEvaluateOnNewDocument(scriptId), screenshot: this._screenshot.bind(this), scrollIntoViewIfNeeded: this._scrollIntoViewIfNeeded.bind(this), setCacheDisabled: this._setCacheDisabled.bind(this), @@ -251,8 +250,8 @@ class PageAgent { return; const frame = this._findFrameForNode(inputElement); this._browserPage.emit('pageFileChooserOpened', { - executionContextId: frame.executionContext().id(), - element: frame.executionContext().rawValueToRemoteObject(inputElement) + executionContextId: frame.mainExecutionContext().id(), + element: frame.mainExecutionContext().rawValueToRemoteObject(inputElement) }); } diff --git a/browser_patches/firefox-stable/juggler/content/Runtime.js b/browser_patches/firefox-stable/juggler/content/Runtime.js index 6816251843..373fd5d497 100644 --- a/browser_patches/firefox-stable/juggler/content/Runtime.js +++ b/browser_patches/firefox-stable/juggler/content/Runtime.js @@ -217,9 +217,9 @@ class Runtime { if (obj.promiseState === 'fulfilled') return {success: true, obj: obj.promiseValue}; if (obj.promiseState === 'rejected') { - const global = executionContext._global; - exceptionDetails.text = global.executeInGlobalWithBindings('e.message', {e: obj.promiseReason}).return; - exceptionDetails.stack = global.executeInGlobalWithBindings('e.stack', {e: obj.promiseReason}).return; + const debuggee = executionContext._debuggee; + exceptionDetails.text = debuggee.executeInGlobalWithBindings('e.message', {e: obj.promiseReason}).return; + exceptionDetails.stack = debuggee.executeInGlobalWithBindings('e.stack', {e: obj.promiseReason}).return; return {success: false, obj: null}; } let resolve, reject; @@ -245,15 +245,15 @@ class Runtime { pendingPromise.resolve({success: true, obj: obj.promiseValue}); return; }; - const global = pendingPromise.executionContext._global; - pendingPromise.exceptionDetails.text = global.executeInGlobalWithBindings('e.message', {e: obj.promiseReason}).return; - pendingPromise.exceptionDetails.stack = global.executeInGlobalWithBindings('e.stack', {e: obj.promiseReason}).return; + const debuggee = pendingPromise.executionContext._debuggee; + pendingPromise.exceptionDetails.text = debuggee.executeInGlobalWithBindings('e.message', {e: obj.promiseReason}).return; + pendingPromise.exceptionDetails.stack = debuggee.executeInGlobalWithBindings('e.stack', {e: obj.promiseReason}).return; pendingPromise.resolve({success: false, obj: null}); } createExecutionContext(domWindow, contextGlobal, auxData) { // Note: domWindow is null for workers. - const context = new ExecutionContext(this, domWindow, contextGlobal, this._debugger.addDebuggee(contextGlobal), auxData); + const context = new ExecutionContext(this, domWindow, contextGlobal, auxData); this._executionContexts.set(context._id, context); if (domWindow) this._windowToExecutionContext.set(domWindow, context); @@ -286,15 +286,15 @@ class Runtime { } class ExecutionContext { - constructor(runtime, domWindow, contextGlobal, global, auxData) { + constructor(runtime, domWindow, contextGlobal, auxData) { this._runtime = runtime; this._domWindow = domWindow; this._contextGlobal = contextGlobal; - this._global = global; + this._debuggee = runtime._debugger.addDebuggee(contextGlobal); this._remoteObjects = new Map(); this._id = generateId(); this._auxData = auxData; - this._jsonStringifyObject = this._global.executeInGlobal(`((stringify, dateProto, object) => { + this._jsonStringifyObject = this._debuggee.executeInGlobal(`((stringify, dateProto, object) => { const oldToJson = dateProto.toJSON; dateProto.toJSON = undefined; let hasSymbol = false; @@ -325,7 +325,7 @@ class ExecutionContext { if (this._domWindow && this._domWindow.document) this._domWindow.document.notifyUserGestureActivation(); - let {success, obj} = this._getResult(this._global.executeInGlobal(script), exceptionDetails); + let {success, obj} = this._getResult(this._debuggee.executeInGlobal(script), exceptionDetails); userInputHelper && userInputHelper.destruct(); if (!success) return null; @@ -338,8 +338,16 @@ class ExecutionContext { return this._createRemoteObject(obj); } + evaluateScriptSafely(script) { + try { + this._debuggee.executeInGlobal(script); + } catch (e) { + dump(`ERROR: ${e.message}\n${e.stack}\n`); + } + } + async evaluateFunction(functionText, args, exceptionDetails = {}) { - const funEvaluation = this._getResult(this._global.executeInGlobal('(' + functionText + ')'), exceptionDetails); + const funEvaluation = this._getResult(this._debuggee.executeInGlobal('(' + functionText + ')'), exceptionDetails); if (!funEvaluation.success) return null; if (!funEvaluation.obj.callable) @@ -384,11 +392,7 @@ class ExecutionContext { }, this._contextGlobal, { defineAs: name, }); - try { - this._global.executeInGlobal(script); - } catch (e) { - dump(`ERROR: ${e.message}\n${e.stack}\n`); - } + this.evaluateScriptSafely(script); } unsafeObject(objectId) { @@ -398,14 +402,14 @@ class ExecutionContext { } rawValueToRemoteObject(rawValue) { - const debuggerObj = this._global.makeDebuggeeValue(rawValue); + const debuggerObj = this._debuggee.makeDebuggeeValue(rawValue); return this._createRemoteObject(debuggerObj); } _instanceOf(debuggerObj, rawObj, className) { if (this._domWindow) return rawObj instanceof this._domWindow[className]; - return this._global.executeInGlobalWithBindings('o instanceof this[className]', {o: debuggerObj, className: this._global.makeDebuggeeValue(className)}).return; + return this._debuggee.executeInGlobalWithBindings('o instanceof this[className]', {o: debuggerObj, className: this._debuggee.makeDebuggeeValue(className)}).return; } _createRemoteObject(debuggerObj) { @@ -489,13 +493,13 @@ class ExecutionContext { }; } const baseObject = Array.isArray(obj) ? '([])' : '({})'; - const debuggerObj = this._global.executeInGlobal(baseObject).return; + const debuggerObj = this._debuggee.executeInGlobal(baseObject).return; debuggerObj.defineProperties(properties); return debuggerObj; } _serialize(obj) { - const result = this._global.executeInGlobalWithBindings('stringify(e)', {e: obj, stringify: this._jsonStringifyObject}); + const result = this._debuggee.executeInGlobalWithBindings('stringify(e)', {e: obj, stringify: this._jsonStringifyObject}); if (result.throw) throw new Error('Object is not serializable'); return result.return === undefined ? undefined : JSON.parse(result.return); @@ -530,9 +534,9 @@ class ExecutionContext { return {success: false, obj: null}; } if (completionValue.throw) { - if (this._global.executeInGlobalWithBindings('e instanceof Error', {e: completionValue.throw}).return) { - exceptionDetails.text = this._global.executeInGlobalWithBindings('e.message', {e: completionValue.throw}).return; - exceptionDetails.stack = this._global.executeInGlobalWithBindings('e.stack', {e: completionValue.throw}).return; + if (this._debuggee.executeInGlobalWithBindings('e instanceof Error', {e: completionValue.throw}).return) { + exceptionDetails.text = this._debuggee.executeInGlobalWithBindings('e.message', {e: completionValue.throw}).return; + exceptionDetails.stack = this._debuggee.executeInGlobalWithBindings('e.stack', {e: completionValue.throw}).return; } else { exceptionDetails.value = this._serialize(completionValue.throw); } diff --git a/browser_patches/firefox-stable/juggler/content/main.js b/browser_patches/firefox-stable/juggler/content/main.js index 1ccf3eacc0..ec9da55e35 100644 --- a/browser_patches/firefox-stable/juggler/content/main.js +++ b/browser_patches/firefox-stable/juggler/content/main.js @@ -89,10 +89,10 @@ function initialize() { if (value !== undefined) applySetting[name](value); } - for (const script of scriptsToEvaluateOnNewDocument) - frameTree.addScriptToEvaluateOnNewDocument(script); for (const { worldName, name, script } of bindings) frameTree.addBinding(worldName, name, script); + for (const script of scriptsToEvaluateOnNewDocument) + frameTree.addScriptToEvaluateOnNewDocument(script); pageAgent = new PageAgent(messageManager, channel, frameTree); diff --git a/browser_patches/firefox-stable/juggler/protocol/PageHandler.js b/browser_patches/firefox-stable/juggler/protocol/PageHandler.js index 964742b00d..7e44c8f742 100644 --- a/browser_patches/firefox-stable/juggler/protocol/PageHandler.js +++ b/browser_patches/firefox-stable/juggler/protocol/PageHandler.js @@ -336,10 +336,6 @@ class PageHandler { return await this._contentPage.send('addScriptToEvaluateOnNewDocument', options); } - async ['Page.removeScriptToEvaluateOnNewDocument'](options) { - return await this._contentPage.send('removeScriptToEvaluateOnNewDocument', options); - } - async ['Page.dispatchKeyEvent'](options) { return await this._contentPage.send('dispatchKeyEvent', options); } diff --git a/browser_patches/firefox-stable/juggler/protocol/Protocol.js b/browser_patches/firefox-stable/juggler/protocol/Protocol.js index 405b40c38b..1dcd2f916b 100644 --- a/browser_patches/firefox-stable/juggler/protocol/Protocol.js +++ b/browser_patches/firefox-stable/juggler/protocol/Protocol.js @@ -768,16 +768,8 @@ const Page = { params: { script: t.String, worldName: t.Optional(t.String), - }, - returns: { - scriptId: t.String, } }, - 'removeScriptToEvaluateOnNewDocument': { - params: { - scriptId: t.String, - }, - }, 'navigate': { params: { frameId: t.String,