From f8039bed100942859ff11c0839fbc87c11fabf1c Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Tue, 11 May 2021 16:27:39 +0000 Subject: [PATCH] browser(firefox): bindings in isolated worlds (#6493) --- browser_patches/firefox/BUILD_NUMBER | 4 +- .../firefox/juggler/TargetRegistry.js | 10 ++-- .../firefox/juggler/content/FrameTree.js | 50 ++++++++++--------- .../firefox/juggler/content/PageAgent.js | 8 +-- .../firefox/juggler/content/Runtime.js | 26 ++++++++++ .../firefox/juggler/content/main.js | 8 +-- .../juggler/protocol/BrowserHandler.js | 4 +- .../firefox/juggler/protocol/Protocol.js | 7 ++- 8 files changed, 76 insertions(+), 41 deletions(-) diff --git a/browser_patches/firefox/BUILD_NUMBER b/browser_patches/firefox/BUILD_NUMBER index 06d739129d..db841f64ee 100644 --- a/browser_patches/firefox/BUILD_NUMBER +++ b/browser_patches/firefox/BUILD_NUMBER @@ -1,2 +1,2 @@ -1258 -Changed: pavel.feldman@gmail.com Mon 10 May 2021 09:56:40 PM PDT +1259 +Changed: yurys@chromium.org Tue 11 May 2021 09:09:59 AM PDT diff --git a/browser_patches/firefox/juggler/TargetRegistry.js b/browser_patches/firefox/juggler/TargetRegistry.js index e308a6c13c..b5c88a0cb6 100644 --- a/browser_patches/firefox/juggler/TargetRegistry.js +++ b/browser_patches/firefox/juggler/TargetRegistry.js @@ -479,8 +479,8 @@ class PageTarget { await this._channel.connect('').send('addScriptToEvaluateOnNewDocument', script).catch(e => void e); } - async addBinding(name, script) { - await this._channel.connect('').send('addBinding', { name, script }).catch(e => void e); + async addBinding(worldName, name, script) { + await this._channel.connect('').send('addBinding', { worldName, name, script }).catch(e => void e); } async applyContextSetting(name, value) { @@ -717,9 +717,9 @@ class BrowserContext { await Promise.all(Array.from(this.pages).map(page => page.addScriptToEvaluateOnNewDocument(script))); } - async addBinding(name, script) { - this.bindings.push({ name, script }); - await Promise.all(Array.from(this.pages).map(page => page.addBinding(name, script))); + async addBinding(worldName, name, script) { + this.bindings.push({ worldName, name, script }); + await Promise.all(Array.from(this.pages).map(page => page.addBinding(worldName, name, script))); } async applySetting(name, value) { diff --git a/browser_patches/firefox/juggler/content/FrameTree.js b/browser_patches/firefox/juggler/content/FrameTree.js index 8a55330a5d..ee06c9b3e4 100644 --- a/browser_patches/firefox/juggler/content/FrameTree.js +++ b/browser_patches/firefox/juggler/content/FrameTree.js @@ -148,10 +148,10 @@ class FrameTree { return true; } - addBinding(name, script) { - this._bindings.set(name, script); + addBinding(worldName, name, script) { + this._bindings.set(worldName + ':' + name, {worldName, name, script}); for (const frame of this.frames()) - frame._addBinding(name, script); + frame._addBinding(worldName, name, script); } frameForDocShell(docShell) { @@ -297,7 +297,6 @@ class FrameTree { } FrameTree.Events = { - BindingCalled: 'bindingcalled', FrameAttached: 'frameattached', FrameDetached: 'framedetached', WorkerCreated: 'workercreated', @@ -461,18 +460,18 @@ class Frame { this._executionContext = null; } - _addBinding(name, script) { - Cu.exportFunction((...args) => { - this._frameTree.emit(FrameTree.Events.BindingCalled, { - frame: this, - name, - payload: args[0] - }); - }, this.domWindow(), { - defineAs: name, - }); - if (this._executionContext) - this._evaluateScriptSafely(this._executionContext, script); + _getOrCreateIsolatedContext(worldName) { + for (let context of this._isolatedWorlds.values()) { + if (context.auxData().name === worldName) + return context; + } + return this.createIsolatedWorld(worldName); + } + + _addBinding(worldName, name, script) { + const executionContext = worldName ? this._getOrCreateIsolatedContext(worldName) : this._executionContext; + if (executionContext) + executionContext.addBinding(name, script); } _evaluateScriptSafely(executionContext, script) { @@ -494,20 +493,25 @@ class Frame { if (this._executionContext) this._runtime.destroyExecutionContext(this._executionContext); + for (const world of this._isolatedWorlds.values()) + this._runtime.destroyExecutionContext(world); + this._isolatedWorlds.clear(); + this._executionContext = this._runtime.createExecutionContext(this.domWindow(), this.domWindow(), { frameId: this._frameId, name: '', }); - for (const [name, script] of this._frameTree._bindings) - this._addBinding(name, script); + 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 world of this._isolatedWorlds.values()) - this._runtime.destroyExecutionContext(world); - this._isolatedWorlds.clear(); for (const {script, worldName} of this._frameTree._isolatedWorlds.values()) { - const context = worldName ? this.createIsolatedWorld(worldName) : this.executionContext(); + const context = worldName ? this._getOrCreateIsolatedContext(worldName) : this.executionContext(); this._evaluateScriptSafely(context, script); } } diff --git a/browser_patches/firefox/juggler/content/PageAgent.js b/browser_patches/firefox/juggler/content/PageAgent.js index 25205bcaa6..f03a54362a 100644 --- a/browser_patches/firefox/juggler/content/PageAgent.js +++ b/browser_patches/firefox/juggler/content/PageAgent.js @@ -104,7 +104,6 @@ class PageAgent { helper.addObserver(this._onDocumentOpenLoad.bind(this), 'juggler-document-open-loaded'), helper.addEventListener(this._messageManager, 'error', this._onError.bind(this)), helper.on(this._frameTree, 'load', this._onLoad.bind(this)), - helper.on(this._frameTree, 'bindingcalled', this._onBindingCalled.bind(this)), helper.on(this._frameTree, 'frameattached', this._onFrameAttached.bind(this)), helper.on(this._frameTree, 'framedetached', this._onFrameDetached.bind(this)), helper.on(this._frameTree, 'navigationstarted', this._onNavigationStarted.bind(this)), @@ -133,8 +132,9 @@ class PageAgent { this._runtime.events.onConsoleMessage(msg => this._browserPage.emit('runtimeConsole', msg)), this._runtime.events.onExecutionContextCreated(this._onExecutionContextCreated.bind(this)), this._runtime.events.onExecutionContextDestroyed(this._onExecutionContextDestroyed.bind(this)), + this._runtime.events.onBindingCalled(this._onBindingCalled.bind(this)), browserChannel.register('page', { - addBinding: ({ name, script }) => this._frameTree.addBinding(name, script), + addBinding: ({ worldName, name, script }) => this._frameTree.addBinding(worldName, name, script), addScriptToEvaluateOnNewDocument: ({script, worldName}) => this._frameTree.addScriptToEvaluateOnNewDocument(script, worldName), adoptNode: this._adoptNode.bind(this), crash: this._crash.bind(this), @@ -355,9 +355,9 @@ class PageAgent { }); } - _onBindingCalled({frame, name, payload}) { + _onBindingCalled({executionContextId, name, payload}) { this._browserPage.emit('pageBindingCalled', { - executionContextId: frame.executionContext().id(), + executionContextId, name, payload }); diff --git a/browser_patches/firefox/juggler/content/Runtime.js b/browser_patches/firefox/juggler/content/Runtime.js index 110975ebe1..6816251843 100644 --- a/browser_patches/firefox/juggler/content/Runtime.js +++ b/browser_patches/firefox/juggler/content/Runtime.js @@ -74,6 +74,7 @@ class Runtime { onErrorFromWorker: createEvent(), onExecutionContextCreated: createEvent(), onExecutionContextDestroyed: createEvent(), + onBindingCalled: createEvent(), }; } @@ -166,6 +167,10 @@ class Runtime { _registerConsoleObserver(Services) { const consoleObserver = ({wrappedJSObject}, topic, data) => { const executionContext = Array.from(this._executionContexts.values()).find(context => { + // There is no easy way to determine isolated world context and we normally don't write + // objects to console from utility worlds so we always return main world context here. + if (context._isIsolatedWorldContext()) + return false; const domWindow = context._domWindow; return domWindow && domWindow.windowGlobalChild.innerWindowId === wrappedJSObject.innerID; }); @@ -311,6 +316,10 @@ class ExecutionContext { return this._auxData; } + _isIsolatedWorldContext() { + return !!this._auxData.name; + } + async evaluateScript(script, exceptionDetails = {}) { const userInputHelper = this._domWindow ? this._domWindow.windowUtils.setHandlingUserInput(true) : null; if (this._domWindow && this._domWindow.document) @@ -365,6 +374,23 @@ class ExecutionContext { return this._createRemoteObject(obj); } + addBinding(name, script) { + Cu.exportFunction((...args) => { + emitEvent(this._runtime.events.onBindingCalled, { + executionContextId: this._id, + name, + payload: args[0], + }); + }, this._contextGlobal, { + defineAs: name, + }); + try { + this._global.executeInGlobal(script); + } catch (e) { + dump(`ERROR: ${e.message}\n${e.stack}\n`); + } + } + unsafeObject(objectId) { if (!this._remoteObjects.has(objectId)) return; diff --git a/browser_patches/firefox/juggler/content/main.js b/browser_patches/firefox/juggler/content/main.js index be1ca25746..1ccf3eacc0 100644 --- a/browser_patches/firefox/juggler/content/main.js +++ b/browser_patches/firefox/juggler/content/main.js @@ -91,8 +91,8 @@ function initialize() { } for (const script of scriptsToEvaluateOnNewDocument) frameTree.addScriptToEvaluateOnNewDocument(script); - for (const { name, script } of bindings) - frameTree.addBinding(name, script); + for (const { worldName, name, script } of bindings) + frameTree.addBinding(worldName, name, script); pageAgent = new PageAgent(messageManager, channel, frameTree); @@ -101,8 +101,8 @@ function initialize() { frameTree.addScriptToEvaluateOnNewDocument(script); }, - addBinding({name, script}) { - frameTree.addBinding(name, script); + addBinding({worldName, name, script}) { + frameTree.addBinding(worldName, name, script); }, applyContextSetting({name, value}) { diff --git a/browser_patches/firefox/juggler/protocol/BrowserHandler.js b/browser_patches/firefox/juggler/protocol/BrowserHandler.js index 48c43ab7f3..e915dc6deb 100644 --- a/browser_patches/firefox/juggler/protocol/BrowserHandler.js +++ b/browser_patches/firefox/juggler/protocol/BrowserHandler.js @@ -237,8 +237,8 @@ class BrowserHandler { await this._targetRegistry.browserContextForId(browserContextId).addScriptToEvaluateOnNewDocument(script); } - async ['Browser.addBinding']({browserContextId, name, script}) { - await this._targetRegistry.browserContextForId(browserContextId).addBinding(name, script); + async ['Browser.addBinding']({browserContextId, worldName, name, script}) { + await this._targetRegistry.browserContextForId(browserContextId).addBinding(worldName, name, script); } ['Browser.setCookies']({browserContextId, cookies}) { diff --git a/browser_patches/firefox/juggler/protocol/Protocol.js b/browser_patches/firefox/juggler/protocol/Protocol.js index 99b2ca5a34..405b40c38b 100644 --- a/browser_patches/firefox/juggler/protocol/Protocol.js +++ b/browser_patches/firefox/juggler/protocol/Protocol.js @@ -373,6 +373,7 @@ const Browser = { 'addBinding': { params: { browserContextId: t.Optional(t.String), + worldName: t.Optional(t.String), name: t.String, script: t.String, }, @@ -523,7 +524,10 @@ const Runtime = { events: { 'executionContextCreated': { executionContextId: t.String, - auxData: t.Any, + auxData: { + frameId: t.Optional(t.String), + name: t.Optional(t.String), + }, }, 'executionContextDestroyed': { executionContextId: t.String, @@ -718,6 +722,7 @@ const Page = { }, 'addBinding': { params: { + worldName: t.Optional(t.String), name: t.String, script: t.String, },