diff --git a/juggler/BrowserContextManager.js b/juggler/BrowserContextManager.js index 3365aa61871830..bd57d338c279f5 100644 --- a/juggler/BrowserContextManager.js +++ b/juggler/BrowserContextManager.js @@ -83,6 +83,7 @@ class BrowserContext { this._manager._userContextIdToBrowserContext.set(this.userContextId, this); this.options = options || {}; this.options.scriptsToEvaluateOnNewDocument = []; + this.options.bindings = []; this.pages = new Set(); } @@ -100,6 +101,11 @@ class BrowserContext { await Promise.all(Array.from(this.pages).map(page => page.addScriptToEvaluateOnNewDocument(script))); } + async addBinding(name, script) { + this.options.bindings.push({ name, script }); + await Promise.all(Array.from(this.pages).map(page => page.addBinding(name, script))); + } + async setGeolocationOverride(geolocation) { this.options.geolocation = geolocation; await Promise.all(Array.from(this.pages).map(page => page.setGeolocationOverride(geolocation))); diff --git a/juggler/TargetRegistry.js b/juggler/TargetRegistry.js index 75ea79a8fa493f..e624e3c21a20dd 100644 --- a/juggler/TargetRegistry.js +++ b/juggler/TargetRegistry.js @@ -234,6 +234,10 @@ 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 setGeolocationOverride(geolocation) { await this._channel.connect('').send('setGeolocationOverride', geolocation).catch(e => void e); } diff --git a/juggler/content/FrameTree.js b/juggler/content/FrameTree.js index 13c3cd817b369e..679b5851c42706 100644 --- a/juggler/content/FrameTree.js +++ b/juggler/content/FrameTree.js @@ -18,6 +18,7 @@ class FrameTree { this._browsingContextGroup.__jugglerFrameTrees = new Set(); this._browsingContextGroup.__jugglerFrameTrees.add(this); + this._bindings = new Map(); this._workers = new Map(); this._docShellToFrame = new Map(); this._frameIdToFrame = new Map(); @@ -48,6 +49,7 @@ class FrameTree { this._eventListeners = [ helper.addObserver(subject => this._onDocShellCreated(subject.QueryInterface(Ci.nsIDocShell)), 'webnavigation-create'), helper.addObserver(subject => this._onDocShellDestroyed(subject.QueryInterface(Ci.nsIDocShell)), 'webnavigation-destroy'), + helper.addObserver(window => this._onDOMWindowCreated(window), 'content-document-global-created'), helper.addProgressListener(webProgress, this, flags), ]; } @@ -111,6 +113,25 @@ class FrameTree { return this._scriptsToEvaluateOnNewDocument; } + addBinding(name, script) { + this._bindings.set(name, script); + for (const frame of this.frames()) + this._addBindingToFrame(frame, name, script); + } + + _addBindingToFrame(frame, name, script) { + Cu.exportFunction((...args) => { + this.emit(FrameTree.Events.BindingCalled, { + frame, + name, + payload: args[0] + }); + }, frame.domWindow(), { + defineAs: name, + }); + frame.domWindow().eval(script); + } + frameForDocShell(docShell) { return this._docShellToFrame.get(docShell) || null; } @@ -218,6 +239,8 @@ class FrameTree { const frame = new Frame(this, docShell, parentFrame); this._docShellToFrame.set(docShell, frame); this._frameIdToFrame.set(frame.id(), frame); + for (const [name, script] of this._bindings) + this._addBindingToFrame(frame, name, script); this.emit(FrameTree.Events.FrameAttached, frame); return frame; } @@ -228,6 +251,16 @@ class FrameTree { this._detachFrame(frame); } + _onDOMWindowCreated(window) { + const docShell = window.docShell; + const frame = this.frameForDocShell(docShell); + if (!frame) + return; + for (const [name, script] of this._bindings) + this._addBindingToFrame(frame, name, script); + this.emit(FrameTree.Events.GlobalObjectCreated, { frame, window }); + } + _detachFrame(frame) { // Detach all children first for (const subframe of frame._children) @@ -242,8 +275,10 @@ class FrameTree { } FrameTree.Events = { + BindingCalled: 'bindingcalled', FrameAttached: 'frameattached', FrameDetached: 'framedetached', + GlobalObjectCreated: 'globalobjectcreated', WorkerCreated: 'workercreated', WorkerDestroyed: 'workerdestroyed', NavigationStarted: 'navigationstarted', diff --git a/juggler/content/PageAgent.js b/juggler/content/PageAgent.js index 3dd06f27d071a1..6a001d9f51c819 100644 --- a/juggler/content/PageAgent.js +++ b/juggler/content/PageAgent.js @@ -63,8 +63,6 @@ class FrameData { name: '', }); - for (const bindingName of this._agent._bindingsToAdd.values()) - this.exposeFunction(bindingName); for (const script of this._agent._frameTree.scriptsToEvaluateOnNewDocument()) { // TODO: this should actually be handled in FrameTree, but first we have to move // execution contexts there. @@ -86,18 +84,6 @@ class FrameData { } } - exposeFunction(name) { - Cu.exportFunction((...args) => { - this._agent._browserPage.emit('pageBindingCalled', { - executionContextId: this.mainContext.id(), - name, - payload: args[0] - }); - }, this._frame.domWindow(), { - defineAs: name, - }); - } - createIsolatedWorld(name) { const principal = [this._frame.domWindow()]; // extended principal const sandbox = Cu.Sandbox(principal, { @@ -144,11 +130,10 @@ class PageAgent { this._frameData = new Map(); this._workerData = new Map(); this._scriptsToEvaluateOnNewDocument = new Map(); - this._bindingsToAdd = new Set(); this._eventListeners = [ browserChannel.register(sessionId + 'page', { - addBinding: this._addBinding.bind(this), + addBinding: ({ name, script }) => this._frameTree.addBinding(name, script), addScriptToEvaluateOnNewDocument: this._addScriptToEvaluateOnNewDocument.bind(this), adoptNode: this._adoptNode.bind(this), awaitViewportDimensions: this._awaitViewportDimensions.bind(this), @@ -270,13 +255,14 @@ class PageAgent { helper.addObserver(this._linkClicked.bind(this, false), 'juggler-link-click'), helper.addObserver(this._linkClicked.bind(this, true), 'juggler-link-click-sync'), helper.addObserver(this._filePickerShown.bind(this), 'juggler-file-picker-shown'), - helper.addObserver(this._onDOMWindowCreated.bind(this), 'content-document-global-created'), helper.addEventListener(this._messageManager, 'DOMContentLoaded', this._onDOMContentLoaded.bind(this)), helper.addEventListener(this._messageManager, 'pageshow', this._onLoad.bind(this)), helper.addObserver(this._onDocumentOpenLoad.bind(this), 'juggler-document-open-loaded'), helper.addEventListener(this._messageManager, 'error', this._onError.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, 'globalobjectcreated', this._onGlobalObjectCreated.bind(this)), helper.on(this._frameTree, 'navigationstarted', this._onNavigationStarted.bind(this)), helper.on(this._frameTree, 'navigationcommitted', this._onNavigationCommitted.bind(this)), helper.on(this._frameTree, 'navigationaborted', this._onNavigationAborted.bind(this)), @@ -434,11 +420,7 @@ class PageAgent { }); } - _onDOMWindowCreated(window) { - const docShell = window.docShell; - const frame = this._frameTree.frameForDocShell(docShell); - if (!frame) - return; + _onGlobalObjectCreated({ frame }) { this._frameData.get(frame).reset(); } @@ -457,6 +439,15 @@ class PageAgent { }); } + _onBindingCalled({frame, name, payload}) { + const frameData = this._frameData.get(frame); + this._browserPage.emit('pageBindingCalled', { + executionContextId: frameData.mainContext.id(), + name, + payload + }); + } + dispose() { for (const workerData of this._workerData.values()) workerData.dispose(); @@ -524,14 +515,6 @@ class PageAgent { return {navigationId: frame.pendingNavigationId(), navigationURL: frame.pendingNavigationURL()}; } - _addBinding({name}) { - if (this._bindingsToAdd.has(name)) - throw new Error(`Binding with name ${name} already exists`); - this._bindingsToAdd.add(name); - for (const frameData of this._frameData.values()) - frameData.exposeFunction(name); - } - async _adoptNode({frameId, objectId, executionContextId}) { const frame = this._frameTree.frame(frameId); if (!frame) diff --git a/juggler/content/main.js b/juggler/content/main.js index 56472e8515cb84..9bb5c2bff8eb3e 100644 --- a/juggler/content/main.js +++ b/juggler/content/main.js @@ -73,7 +73,7 @@ function initialize() { response = { sessionIds: [], browserContextOptions: {}, waitForInitialNavigation: false }; const { sessionIds, browserContextOptions, waitForInitialNavigation } = response; - const { userAgent, bypassCSP, javaScriptDisabled, viewport, scriptsToEvaluateOnNewDocument, locale, geolocation, onlineOverride } = browserContextOptions; + const { userAgent, bypassCSP, javaScriptDisabled, viewport, scriptsToEvaluateOnNewDocument, bindings, locale, geolocation, onlineOverride } = browserContextOptions; if (userAgent !== undefined) docShell.customUserAgent = userAgent; @@ -97,6 +97,8 @@ function initialize() { frameTree = new FrameTree(docShell, waitForInitialNavigation); for (const script of scriptsToEvaluateOnNewDocument || []) frameTree.addScriptToEvaluateOnNewDocument(script); + for (const { name, script } of bindings || []) + frameTree.addBinding(name, script); networkMonitor = new NetworkMonitor(docShell, frameTree); const channel = SimpleChannel.createForMessageManager('content::page', messageManager); @@ -117,6 +119,10 @@ function initialize() { frameTree.addScriptToEvaluateOnNewDocument(script); }, + addBinding(name, script) { + frameTree.addBinding(name, script); + }, + setGeolocationOverride(geolocation) { setGeolocationOverrideInDocShell(geolocation); }, diff --git a/juggler/protocol/BrowserHandler.js b/juggler/protocol/BrowserHandler.js index b3a7b47765d69f..e225fc81c62bbf 100644 --- a/juggler/protocol/BrowserHandler.js +++ b/juggler/protocol/BrowserHandler.js @@ -146,6 +146,10 @@ class BrowserHandler { await this._contextManager.browserContextForId(browserContextId).addScriptToEvaluateOnNewDocument(script); } + async addBinding({browserContextId, name, script}) { + await this._contextManager.browserContextForId(browserContextId).addBinding(name, script); + } + setCookies({browserContextId, cookies}) { this._contextManager.browserContextForId(browserContextId).setCookies(cookies); } diff --git a/juggler/protocol/Protocol.js b/juggler/protocol/Protocol.js index 390e68d71c0034..67df4d5592d66e 100644 --- a/juggler/protocol/Protocol.js +++ b/juggler/protocol/Protocol.js @@ -273,6 +273,13 @@ const Browser = { script: t.String, } }, + 'addBinding': { + params: { + browserContextId: t.Optional(t.String), + name: t.String, + script: t.String, + }, + }, 'grantPermissions': { params: { origin: t.String, @@ -559,6 +566,7 @@ const Page = { 'addBinding': { params: { name: t.String, + script: t.String, }, }, 'setViewportSize': {