diff --git a/testing/marionette/assert.js b/testing/marionette/assert.js index 0cfba6f4cce2..c7e13e2c0c21 100644 --- a/testing/marionette/assert.js +++ b/testing/marionette/assert.js @@ -287,25 +287,6 @@ assert.callable = function(obj, msg = "") { return assert.that(o => typeof o == "function", msg)(obj); }; -/** - * Asserts that obj is an unsigned short number. - * - * @param {?} obj - * Value to test. - * @param {string=} msg - * Custom error message. - * - * @return {number} - * obj is returned unaltered. - * - * @throws {InvalidArgumentError} - * If obj is not an unsigned short. - */ -assert.unsignedShort = function(obj, msg = "") { - msg = msg || pprint`Expected ${obj} to be >= 0 and < 65536`; - return assert.that(n => n >= 0 && n < 65536, msg)(obj); -}; - /** * Asserts that obj is an integer. * diff --git a/testing/marionette/browser.js b/testing/marionette/browser.js index 068bf702049b..8d30646baf7e 100644 --- a/testing/marionette/browser.js +++ b/testing/marionette/browser.js @@ -567,17 +567,17 @@ browser.Context = class { }; /** - * The window storage is used to save browsing context ids mapped to weak + * The window storage is used to save outer window IDs mapped to weak * references of Window objects. * * Usage: * * let wins = new browser.Windows(); - * wins.set(browser.browsingContext.id, window); + * wins.set(browser.outerWindowID, window); * * ... * - * let win = wins.get(browser.browsingContext.id); + * let win = wins.get(browser.outerWindowID); * */ browser.Windows = class extends Map { @@ -585,7 +585,7 @@ browser.Windows = class extends Map { * Save a weak reference to the Window object. * * @param {string} id - * Browsing context id. + * Outer window ID. * @param {Window} win * Window object to save. * @@ -602,7 +602,7 @@ browser.Windows = class extends Map { * Get the window object stored by provided |id|. * * @param {string} id - * Browsing context id. + * Outer window ID. * * @return {Window} * Saved window object. diff --git a/testing/marionette/driver.js b/testing/marionette/driver.js index c3789818aefb..f381bb51b7e0 100644 --- a/testing/marionette/driver.js +++ b/testing/marionette/driver.js @@ -288,7 +288,7 @@ Object.defineProperty(GeckoDriver.prototype, "chromeWindowHandles", { let hs = []; for (let win of this.windows) { - hs.push(getWindowId(win)); + hs.push(getOuterWindowId(win)); } return hs; @@ -474,16 +474,16 @@ GeckoDriver.prototype.addFrameCloseListener = function(action) { * @return {string} * Returns the unique server-assigned ID of the window. */ -GeckoDriver.prototype.addBrowser = function(win) { - let context = new browser.Context(win, this); - let winId = getWindowId(win); +GeckoDriver.prototype.addBrowser = function(window) { + let bc = new browser.Context(window, this); + let winId = getOuterWindowId(window); - this.browsers[winId] = context; + this.browsers[winId] = bc; this.curBrowser = this.browsers[winId]; if (!this.wins.has(winId)) { // add this to seenItems so we can guarantee // the user will get winId as this window's id - this.wins.set(winId, win); + this.wins.set(winId, window); } }; @@ -579,6 +579,8 @@ GeckoDriver.prototype.getVisibleText = function(el, lines) { * their type they are either accepted or ignored. */ GeckoDriver.prototype.registerBrowser = function(id, be) { + let listenerWindow = Services.wm.getOuterWindowWithId(id); + // 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), @@ -594,9 +596,7 @@ GeckoDriver.prototype.registerBrowser = function(id, be) { this.curBrowser.register(id, be); } - const context = BrowsingContext.get(id); - this.wins.set(id, context.currentWindowGlobal); - + this.wins.set(id, listenerWindow); return id; }; @@ -605,8 +605,8 @@ GeckoDriver.prototype.registerPromise = function() { return new Promise(resolve => { let cb = ({ json, target }) => { - let { frameId } = json; - this.registerBrowser(frameId, target); + let { outerWindowID } = json; + this.registerBrowser(outerWindowID, target); if (this.curBrowser.frameRegsPending > 0) { this.curBrowser.frameRegsPending--; @@ -617,7 +617,7 @@ GeckoDriver.prototype.registerPromise = function() { resolve(); } - return { frameId }; + return { outerWindowID }; }; this.mm.addMessageListener(li, cb); }); @@ -628,7 +628,7 @@ GeckoDriver.prototype.listeningPromise = function() { return new Promise(resolve => { let cb = msg => { - if (msg.json.frameId === this.curBrowser.curFrameId) { + if (msg.json.outerWindowID === this.curBrowser.curFrameId) { this.mm.removeMessageListener(li, cb); resolve(); } @@ -1390,7 +1390,7 @@ GeckoDriver.prototype.getIdForBrowser = function(browser) { return this._browserIds.get(permKey); } - let winId = browser.browsingContext.id; + let winId = browser.outerWindowID; if (winId) { this._browserIds.set(permKey, winId); return winId; @@ -1618,13 +1618,13 @@ GeckoDriver.prototype.switchToWindow = async function(cmd) { * associated metadata. */ GeckoDriver.prototype.findWindow = function(winIterable, filter) { - for (const win of winIterable) { - const bc = win.docShell.browsingContext; - const tabBrowser = browser.getTabBrowser(win); + for (let win of winIterable) { + let outerId = getOuterWindowId(win); + let tabBrowser = browser.getTabBrowser(win); // In case the wanted window is a chrome window, we are done. - if (filter(win, bc.id)) { - return { win, id: bc.id, hasTabBrowser: !!tabBrowser }; + if (filter(win, outerId)) { + return { win, outerId, hasTabBrowser: !!tabBrowser }; // Otherwise check if the chrome window has a tab browser, and that it // contains a tab with the wanted window handle. @@ -1636,7 +1636,7 @@ GeckoDriver.prototype.findWindow = function(winIterable, filter) { if (filter(win, contentWindowId)) { return { win, - id: bc.id, + outerId, hasTabBrowser: true, tabIndex: i, }; @@ -1665,7 +1665,7 @@ GeckoDriver.prototype.setWindowHandle = async function( winProperties, focus = true ) { - if (!(winProperties.id in this.browsers)) { + if (!(winProperties.outerId in this.browsers)) { // Initialise Marionette if the current chrome window has not been seen // before. Also register the initial tab, if one exists. let registerBrowsers, browserListening; @@ -1683,7 +1683,7 @@ GeckoDriver.prototype.setWindowHandle = async function( } } else { // Otherwise switch to the known chrome window - this.curBrowser = this.browsers[winProperties.id]; + this.curBrowser = this.browsers[winProperties.outerId]; this.mainFrame = this.curBrowser.window; this.curFrame = null; @@ -1758,10 +1758,6 @@ GeckoDriver.prototype.switchToFrame = async function(cmd) { let { id, focus } = cmd.parameters; - if (typeof id == "number") { - assert.unsignedShort(id, `Expected id to be unsigned short, got ${id}`); - } - // TODO(ato): element can be either string (deprecated) or a web // element JSON Object. Can be removed with Firefox 60. let byFrame; @@ -3572,12 +3568,12 @@ GeckoDriver.prototype.receiveMessage = function(message) { break; case "Marionette:Register": - let { frameId } = message.json; - this.registerBrowser(frameId, message.target); - return { frameId }; + let { outerWindowID } = message.json; + this.registerBrowser(outerWindowID, message.target); + return { outerWindowID }; case "Marionette:ListenersAttached": - if (message.json.frameId === this.curBrowser.curFrameId) { + if (message.json.outerWindowID === this.curBrowser.curFrameId) { this.curBrowser.flushPendingCommands(); } break; @@ -3899,8 +3895,8 @@ GeckoDriver.prototype.commands = { "WebDriver:TakeScreenshot": GeckoDriver.prototype.takeScreenshot, }; -function getWindowId(win) { - return win.docShell.browsingContext.id; +function getOuterWindowId(win) { + return win.windowUtils.outerWindowID; } async function exitFullscreen(win) { diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/window_manager.py b/testing/marionette/harness/marionette_harness/runner/mixins/window_manager.py index 59f2af38f9e3..243c0a032328 100644 --- a/testing/marionette/harness/marionette_harness/runner/mixins/window_manager.py +++ b/testing/marionette/harness/marionette_harness/runner/mixins/window_manager.py @@ -92,7 +92,7 @@ class WindowManagerMixin(object): return self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); - const win = BrowsingContext.get(Number(arguments[0])).window; + let win = Services.wm.getOuterWindowWithId(Number(arguments[0])); return win.document.readyState == "complete"; """, script_args=[handle]) @@ -174,7 +174,7 @@ class WindowManagerMixin(object): await focused; } - resolve(win.docShell.browsingContext.id); + resolve(win.windowUtils.outerWindowID); })(); """, script_args=(url, focus)) diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame.py b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame.py index 923e572bc84e..ec59b70838f0 100644 --- a/testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame.py +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame.py @@ -6,7 +6,6 @@ from __future__ import absolute_import from marionette_driver.by import By from marionette_driver.errors import ( - InvalidArgumentException, JavascriptException, NoSuchFrameException, ) @@ -123,13 +122,11 @@ class TestSwitchFrame(MarionetteTestCase): def test_switch_to_frame_with_out_of_bounds_index(self): self.marionette.navigate(self.marionette.absolute_url("test_iframe.html")) count = self.marionette.execute_script("return window.frames.length;") - for index in [count, 65535]: - self.assertRaises(NoSuchFrameException, self.marionette.switch_to_frame, index) + self.assertRaises(NoSuchFrameException, self.marionette.switch_to_frame, count) - def test_switch_to_frame_with_invalid_index(self): + def test_switch_to_frame_with_negative_index(self): self.marionette.navigate(self.marionette.absolute_url("test_iframe.html")) - for index in [-1, 65536]: - self.assertRaises(InvalidArgumentException, self.marionette.switch_to_frame, index) + self.assertRaises(NoSuchFrameException, self.marionette.switch_to_frame, -1) def test_switch_to_parent_frame(self): frame_html = self.marionette.absolute_url("frameset.html") diff --git a/testing/marionette/listener.js b/testing/marionette/listener.js index 8c04a6ba4365..18ebe963415b 100644 --- a/testing/marionette/listener.js +++ b/testing/marionette/listener.js @@ -58,26 +58,13 @@ const { navigate } = ChromeUtils.import( ); const { proxy } = ChromeUtils.import("chrome://marionette/content/proxy.js"); -XPCOMUtils.defineLazyGetter(this, "logger", () => Log.getWithPrefix(contentId)); +XPCOMUtils.defineLazyGetter(this, "logger", () => + Log.getWithPrefix(outerWindowID) +); XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]); -const contentId = content.docShell.browsingContext.id; - -const curContainer = { - _frame: null, - shadowRoot: null, - - get frame() { - return this._frame; - }, - - set frame(frame) { - this._frame = frame; - - this.id = this._frame.docShell.browsingContext.id; - this.shadowRoot = null; - }, -}; +let { outerWindowID } = winUtil; +let curContainer = { frame: content, shadowRoot: null }; // Listen for click event to indicate one click has happened, so actions // code can send dblclick event @@ -386,16 +373,17 @@ const loadListener = { }, observe(subject, topic) { - logger.trace(`Received observer notification ${topic}`); + const win = curContainer.frame; + const winID = subject.QueryInterface(Ci.nsISupportsPRUint64).data; + const curWinID = win.windowUtils.outerWindowID; - const winId = subject.QueryInterface(Ci.nsISupportsPRUint64).data; - const bc = BrowsingContext.get(curContainer.id); + logger.trace(`Received observer notification ${topic}`); switch (topic) { // In the case when the currently selected frame is closed, // there will be no further load events. Stop listening immediately. case "outer-window-destroyed": - if (bc.window.windowUtils.outerWindowID == winId) { + if (curWinID === winID) { this.stop(); sendOk(this.commandID); } @@ -493,27 +481,25 @@ const loadListener = { function registerSelf() { logger.trace("Frame script loaded"); - curContainer.frame = content; - sandboxes.clear(); + curContainer = { + frame: content, + shadowRoot: null, + }; legacyactions.mouseEventsOnly = false; action.inputStateMap = new Map(); action.inputsToCancel = []; - let reply = sendSyncMessage("Marionette:Register", { - frameId: contentId, - }); + let reply = sendSyncMessage("Marionette:Register", { outerWindowID }); if (reply.length == 0) { logger.error("No reply from Marionette:Register"); return; } - if (reply[0].frameId === contentId) { + if (reply[0].outerWindowID === outerWindowID) { logger.trace("Frame script registered"); startListeners(); - sendAsyncMessage("Marionette:ListenersAttached", { - frameId: contentId, - }); + sendAsyncMessage("Marionette:ListenersAttached", { outerWindowID }); } } @@ -680,11 +666,9 @@ function deregister() { function deleteSession() { seenEls.clear(); - // reset container frame to the top-most frame - curContainer.frame = content; + curContainer = { frame: content, shadowRoot: null }; curContainer.frame.focus(); - legacyactions.touchIds = {}; if (action.inputStateMap !== undefined) { action.inputStateMap.clear(); @@ -765,7 +749,8 @@ function emitTouchEvent(type, touch) { ); const win = curContainer.frame; - if (win.docShell.asyncPanZoomEnabled && legacyactions.scrolling) { + let docShell = win.docShell; + if (docShell.asyncPanZoomEnabled && legacyactions.scrolling) { let ev = { index: 0, type, @@ -1530,9 +1515,7 @@ function switchToParentFrame(msg) { */ function switchToFrame(msg) { let commandID = msg.json.commandID; - - let foundFrame; - let frameWebEl; + let foundFrame = null; // check if curContainer.frame reference is dead let frames = []; @@ -1552,7 +1535,6 @@ function switchToFrame(msg) { sendSyncMessage("Marionette:switchedToFrame", { frameValue: null }); curContainer.frame = content; - if (msg.json.focus) { curContainer.frame.focus(); } @@ -1588,12 +1570,13 @@ function switchToFrame(msg) { let wrappedItem = new XPCNativeWrapper(frameEl); let wrappedWanted = new XPCNativeWrapper(wantedFrame); if (wrappedItem == wrappedWanted) { - foundFrame = frameEl; + curContainer.frame = frameEl; + foundFrame = i; } } } - if (!foundFrame) { + if (foundFrame === null) { // Either the frame has been removed or we have a OOP frame // so lets just get all the iframes and do a quick loop before // throwing in the towel @@ -1604,47 +1587,48 @@ function switchToFrame(msg) { let wrappedEl = new XPCNativeWrapper(frameEl); let wrappedWanted = new XPCNativeWrapper(wantedFrame); if (wrappedEl == wrappedWanted) { - foundFrame = iframes[i]; + curContainer.frame = iframes[i]; + foundFrame = i; } } } } - if (!foundFrame) { + if (foundFrame === null) { if (typeof msg.json.id === "number") { try { - if (msg.json.id >= 0 && msg.json.id < frames.length) { - foundFrame = frames[msg.json.id].frameElement; - if (foundFrame !== null) { - frameWebEl = seenEls.add(foundFrame.wrappedJSObject); - } else { - // If foundFrame is null at this point then we have the top - // level browsing context so should treat it accordingly. - sendSyncMessage("Marionette:switchedToFrame", { frameValue: null }); - curContainer.frame = content; + foundFrame = frames[msg.json.id].frameElement; + if (foundFrame !== null) { + curContainer.frame = foundFrame; + foundFrame = seenEls.add(curContainer.frame); + } else { + // If foundFrame is null at this point then we have the top + // level browsing context so should treat it accordingly. + sendSyncMessage("Marionette:switchedToFrame", { frameValue: null }); + curContainer.frame = content; - if (msg.json.focus) { - curContainer.frame.focus(); - } - - sendOk(commandID); - return; + if (msg.json.focus) { + curContainer.frame.focus(); } + + sendOk(commandID); + return; } } catch (e) { // Since window.frames does not return OOP frames it will throw // and we land up here. Let's not give up and check if there are // iframes and switch to the indexed frame there - let doc = foundFrame.document; + let doc = curContainer.frame.document; let iframes = doc.getElementsByTagName("iframe"); if (msg.json.id >= 0 && msg.json.id < iframes.length) { - foundFrame = iframes[msg.json.id]; + curContainer.frame = iframes[msg.json.id]; + foundFrame = msg.json.id; } } } } - if (!foundFrame) { + if (foundFrame === null) { let failedFrame = msg.json.id || msg.json.element; let err = new NoSuchFrameError(`Unable to locate frame: ${failedFrame}`); sendError(err, commandID); @@ -1653,14 +1637,12 @@ function switchToFrame(msg) { // send a synchronous message to let the server update the currently active // frame element (for getActiveFrame) - if (!frameWebEl) { - frameWebEl = seenEls.add(foundFrame.wrappedJSObject); - } + let frameWebEl = seenEls.add(curContainer.frame.wrappedJSObject); sendSyncMessage("Marionette:switchedToFrame", { frameValue: frameWebEl.uuid, }); - curContainer.frame = foundFrame.contentWindow; + curContainer.frame = curContainer.frame.contentWindow; if (msg.json.focus) { curContainer.frame.focus(); }