diff --git a/testing/marionette/browser.js b/testing/marionette/browser.js index dba4bd285ec6..1221e1040de8 100644 --- a/testing/marionette/browser.js +++ b/testing/marionette/browser.js @@ -78,8 +78,8 @@ browser.getTabBrowser = function(win) { browser.Context = class { /** - * @param {} win - * Frame that is expected to contain the view of the web document. + * @param {ChromeWindow} win + * ChromeWindow that contains the top-level browsing context. * @param {GeckoDriver} driver * Reference to driver instance. */ @@ -106,7 +106,14 @@ browser.Context = class { // browser window, this.tab will still point to tab A, despite tab B // being the currently selected tab. this.tab = null; + + // Commands which trigger a page load can cause the frame script to be + // reloaded. To not loose the currently active command, or any other + // already pushed following command, store them as long as they haven't + // been fully processed. The commands get flushed after a new browser + // has been registered. this.pendingCommands = []; + this._needsFlushPendingCommands = false; // We should have one frame.Manager per browser.Context so that we // can handle modals in each . @@ -117,8 +124,6 @@ browser.Context = class { this.frameManager.addMessageManagerListeners(driver.mm); this.getIdForBrowser = driver.getIdForBrowser.bind(driver); this.updateIdForBrowser = driver.updateIdForBrowser.bind(driver); - this._browserWasRemote = null; - this._hasRemotenessChange = false; } /** @@ -286,8 +291,7 @@ browser.Context = class { } /** - * Set the current tab and update remoteness tracking if a tabbrowser - * is available. + * Set the current tab. * * @param {number=} index * Tab index to switch to. If the parameter is undefined, @@ -330,11 +334,6 @@ browser.Context = class { } } } - - if (this.driver.appName == "Firefox") { - this._browserWasRemote = this.contentBrowser.isRemoteBrowser; - this._hasRemotenessChange = false; - } } /** @@ -344,65 +343,38 @@ browser.Context = class { * * @param {string} uid * Frame uid for use by Marionette. - * @param the XUL that was the target of the originating message. + * @param {xul:browser} target + * The that was the target of the originating message. */ register(uid, target) { - let remotenessChange = this.hasRemotenessChange(); - if (this.curFrameId === null || remotenessChange) { - if (this.tabBrowser) { - // If we're setting up a new session on Firefox, we only process the - // registration for this frame if it belongs to the current tab. - if (!this.tab) { - this.switchToTab(); - } + if (this.tabBrowser) { + // If we're setting up a new session on Firefox, we only process the + // registration for this frame if it belongs to the current tab. + if (!this.tab) { + this.switchToTab(); + } - if (target === this.contentBrowser) { - this.updateIdForBrowser(this.contentBrowser, uid); - } + if (target === this.contentBrowser) { + this.updateIdForBrowser(this.contentBrowser, uid); + this._needsFlushPendingCommands = true; } } // used to delete sessions this.knownFrames.push(uid); - return remotenessChange; } /** - * When navigating between pages results in changing a browser's - * process, we need to take measures not to lose contact with a listener - * script. This function does the necessary bookkeeping. - */ - hasRemotenessChange() { - // None of these checks are relevant if we don't have a tab yet, - // and may not apply on Fennec. - if (this.driver.appName != "Firefox" || - this.tab === null || - this.contentBrowser === null) { - return false; - } - - if (this._hasRemotenessChange) { - return true; - } - - let currentIsRemote = this.contentBrowser.isRemoteBrowser; - this._hasRemotenessChange = this._browserWasRemote !== currentIsRemote; - this._browserWasRemote = currentIsRemote; - return this._hasRemotenessChange; - } - - /** - * Flushes any pending commands queued when a remoteness change is being - * processed and mark this remotenessUpdate as complete. + * Flushes any queued pending commands after a reload of the frame script. */ flushPendingCommands() { - if (!this._hasRemotenessChange) { + if (!this._needsFlushPendingCommands) { return; } - this._hasRemotenessChange = false; this.pendingCommands.forEach(cb => cb()); this.pendingCommands = []; + this._needsFlushPendingCommands = false; } /** @@ -412,11 +384,11 @@ browser.Context = class { * No commands interacting with content are safe to process until * the new listener script is loaded and registers itself. * This occurs when a command whose effect is asynchronous (such - * as goBack) results in a remoteness change and new commands + * as goBack) results in a reload of the frame script and new commands * are subsequently posted to the server. */ executeWhenReady(cb) { - if (this.hasRemotenessChange()) { + if (this._needsFlushPendingCommands) { this.pendingCommands.push(cb); } else { cb(); diff --git a/testing/marionette/driver.js b/testing/marionette/driver.js index c66c070d0920..5b5d527a3608 100644 --- a/testing/marionette/driver.js +++ b/testing/marionette/driver.js @@ -571,8 +571,6 @@ GeckoDriver.prototype.registerBrowser = function(id, be) { this.curBrowser.frameManager.currentRemoteFrame.targetFrameId = id; } - let reg = {}; - // We want to ignore frames that are XUL browsers that aren't in the "main" // tabbrowser, but accept things on Fennec (which doesn't have a // xul:tabbrowser), and accept HTML iframes (because tests depend on it), @@ -581,11 +579,10 @@ GeckoDriver.prototype.registerBrowser = function(id, be) { if (this.appName != "Firefox" || be.namespaceURI != XUL_NS || be.nodeName != "browser" || be.getTabBrowser()) { // curBrowser holds all the registered frames in knownFrames - reg.id = id; - reg.remotenessChange = this.curBrowser.register(id, be); + this.curBrowser.register(id, be); } - this.wins.set(reg.id, listenerWindow); + this.wins.set(id, listenerWindow); if (nullPrevious && (this.curBrowser.curFrameId !== null)) { this.sendAsync( "newSession", @@ -596,7 +593,7 @@ GeckoDriver.prototype.registerBrowser = function(id, be) { } } - return [reg, this.capabilities.toJSON()]; + return [id, this.capabilities.toJSON()]; }; GeckoDriver.prototype.registerPromise = function() { @@ -626,10 +623,13 @@ GeckoDriver.prototype.registerPromise = function() { GeckoDriver.prototype.listeningPromise = function() { const li = "Marionette:listenersAttached"; + return new Promise(resolve => { - let cb = () => { - this.mm.removeMessageListener(li, cb); - resolve(); + let cb = msg => { + if (msg.json.listenerId === this.curBrowser.curFrameId) { + this.mm.removeMessageListener(li, cb); + resolve(); + } }; this.mm.addMessageListener(li, cb); }); @@ -978,9 +978,9 @@ GeckoDriver.prototype.get = function* (cmd, resp) { let get = this.listener.get({url, pageTimeout: this.timeouts.pageLoad}); - // If a remoteness update interrupts our page load, this will never return - // We need to re-issue this request to correctly poll for readyState and - // send errors. + // If a reload of the frame script interrupts our page load, this will + // never return. We need to re-issue this request to correctly poll for + // readyState and send errors. this.curBrowser.pendingCommands.push(() => { let parameters = { // TODO(ato): Bug 1242595 @@ -1097,9 +1097,9 @@ GeckoDriver.prototype.goBack = function* (cmd, resp) { let lastURL = this.currentURL; let goBack = this.listener.goBack({pageTimeout: this.timeouts.pageLoad}); - // If a remoteness update interrupts our page load, this will never return - // We need to re-issue this request to correctly poll for readyState and - // send errors. + // If a reload of the frame script interrupts our page load, this will + // never return. We need to re-issue this request to correctly poll for + // readyState and send errors. this.curBrowser.pendingCommands.push(() => { let parameters = { // TODO(ato): Bug 1242595 @@ -1141,9 +1141,9 @@ GeckoDriver.prototype.goForward = function* (cmd, resp) { let goForward = this.listener.goForward( {pageTimeout: this.timeouts.pageLoad}); - // If a remoteness update interrupts our page load, this will never return - // We need to re-issue this request to correctly poll for readyState and - // send errors. + // If a reload of the frame script interrupts our page load, this will + // never return. We need to re-issue this request to correctly poll for + // readyState and send errors. this.curBrowser.pendingCommands.push(() => { let parameters = { // TODO(ato): Bug 1242595 @@ -1176,7 +1176,25 @@ GeckoDriver.prototype.refresh = function* (cmd, resp) { assert.window(this.getCurrentWindow()); assert.noUserPrompt(this.dialog); - yield this.listener.refresh({pageTimeout: this.timeouts.pageLoad}); + let refresh = this.listener.refresh( + {pageTimeout: this.timeouts.pageLoad}) + + // If a reload of the frame script interrupts our page load, this will + // never return. We need to re-issue this request to correctly poll for + // readyState and send errors. + this.curBrowser.pendingCommands.push(() => { + let parameters = { + // TODO(ato): Bug 1242595 + command_id: this.listener.activeMessageId, + pageTimeout: this.timeouts.pageLoad, + startTime: new Date().getTime(), + }; + this.mm.broadcastAsyncMessage( + "Marionette:waitForPageLoaded" + this.curBrowser.curFrameId, + parameters); + }); + + yield refresh; }; /** @@ -2069,9 +2087,9 @@ GeckoDriver.prototype.clickElement = function* (cmd, resp) { let click = this.listener.clickElement( {id, pageTimeout: this.timeouts.pageLoad}); - // If a remoteness update interrupts our page load, this will - // never return We need to re-issue this request to correctly poll - // for readyState and send errors. + // If a reload of the frame script interrupts our page load, this will + // never return. We need to re-issue this request to correctly poll for + // readyState and send errors. this.curBrowser.pendingCommands.push(() => { let parameters = { // TODO(ato): Bug 1242595 @@ -3178,9 +3196,9 @@ GeckoDriver.prototype.receiveMessage = function(message) { case "Marionette:listenersAttached": if (message.json.listenerId === this.curBrowser.curFrameId) { - // If remoteness gets updated we need to call newSession. In the case - // of desktop this just sets up a small amount of state that doesn't - // change over the course of a session. + // If the frame script gets reloaded we need to call newSession. + // In the case of desktop this just sets up a small amount of state + // that doesn't change over the course of a session. this.sendAsync("newSession", this.capabilities.toJSON()); this.curBrowser.flushPendingCommands(); } diff --git a/testing/marionette/listener.js b/testing/marionette/listener.js index 2068c6f28c59..29c723372e42 100644 --- a/testing/marionette/listener.js +++ b/testing/marionette/listener.js @@ -54,7 +54,6 @@ var winUtil = content.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils); var listenerId = null; // unique ID of this listener var curContainer = {frame: content, shadowRoot: null}; -var isRemoteBrowser = () => curContainer.frame.contentWindow !== null; var previousContainer = null; var seenEls = new element.Store(); @@ -118,10 +117,10 @@ var sandboxes = new Sandboxes(() => curContainer.frame); var sandboxName = "default"; /** - * The load listener singleton helps to keep track of active page - * load activities, and can be used by any command which might cause a - * navigation to happen. In the specific case of remoteness changes it - * allows to continue observing the current page load. + * The load listener singleton helps to keep track of active page load + * activities, and can be used by any command which might cause a navigation + * to happen. In the specific case of a reload of the frame script it allows + * to continue observing the current page load. */ var loadListener = { command_id: null, @@ -157,7 +156,7 @@ var loadListener = { .createInstance(Ci.nsITimer); this.timerPageUnload = null; - // In case of a remoteness change, only wait the remaining time + // In case the frame script has been reloaded, wait the remaining time timeout = startTime + timeout - new Date().getTime(); if (timeout <= 0) { @@ -212,8 +211,8 @@ var loadListener = { } } - // In the case when the observer was added before a remoteness change, - // it will no longer be available. Exceptions can be silently ignored. + // In case the observer was added before the frame script has been + // reloaded, it will no longer be available. Exceptions can be ignored. try { Services.obs.removeObserver(this, "outer-window-destroyed"); } catch (e) {} @@ -344,8 +343,8 @@ var loadListener = { }, /** - * Continue to listen for page load events after a remoteness change - * happened. + * Continue to listen for page load events after the frame script has been + * reloaded. * * @param {number} command_id * ID of the currently handled message between the driver and @@ -356,7 +355,7 @@ var loadListener = { * @param {number} startTime * Unix timestap when the navitation request got triggered. */ - waitForLoadAfterRemotenessChange(command_id, timeout, startTime) { + waitForLoadAfterFramescriptReload(command_id, timeout, startTime) { this.start(command_id, timeout, startTime, false); }, @@ -430,18 +429,13 @@ function registerSelf() { // register will have the ID and a boolean describing if this is the // main process or not let register = sendSyncMessage("Marionette:register", msg); - if (register[0]) { - let {id, remotenessChange} = register[0][0]; + listenerId = register[0][0]; capabilities = session.Capabilities.fromJSON(register[0][1]); - listenerId = id; - if (typeof id != "undefined") { + if (typeof listenerId != "undefined") { startListeners(); - let rv = {}; - if (remotenessChange) { - rv.listenerId = id; - } - sendAsyncMessage("Marionette:listenersAttached", rv); + sendAsyncMessage("Marionette:listenersAttached", + {"listenerId": listenerId}); } } } @@ -1105,8 +1099,8 @@ function cancelRequest() { } /** - * This implements the latter part of a get request (for the case we - * need to resume one when a remoteness update happens in the middle of a + * This implements the latter part of a get request (for the case we need + * to resume one when the frame script has been reloaded in the middle of a * navigate request). This is most of of the work of a navigate request, * but doesn't assume DOMContentLoaded is yet to fire. * @@ -1122,7 +1116,7 @@ function cancelRequest() { function waitForPageLoaded(msg) { let {command_id, pageTimeout, startTime} = msg.json; - loadListener.waitForLoadAfterRemotenessChange( + loadListener.waitForLoadAfterFramescriptReload( command_id, pageTimeout, startTime); }