Bug 1504756 - [marionette] Added "WebDriver:NewWindow" command to open a new top-level browsing context. r=ato

The patch adds the end-point for the recently defined `New Window`
command (https://github.com/w3c/webdriver/issues/1138). It allows
to open a new top-level browsing context as tab or as window.

Depends on D13662

Differential Revision: https://phabricator.services.mozilla.com/D13663

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Henrik Skupin 2019-01-10 10:14:24 +00:00
parent ea87c4dedf
commit c631c98202
3 changed files with 167 additions and 33 deletions

View File

@ -14,6 +14,7 @@ const {
const {
MessageManagerDestroyedPromise,
waitForEvent,
waitForObserverTopic,
} = ChromeUtils.import("chrome://marionette/content/sync.js", {});
this.EXPORTED_SYMBOLS = ["browser", "Context", "WindowState"];
@ -71,11 +72,11 @@ this.Context = Context;
*/
browser.getBrowserForTab = function(tab) {
// Fennec
if ("browser" in tab) {
if (tab && "browser" in tab) {
return tab.browser;
// Firefox
} else if ("linkedBrowser" in tab) {
} else if (tab && "linkedBrowser" in tab) {
return tab.linkedBrowser;
}
@ -297,6 +298,51 @@ browser.Context = class {
return Promise.all([destroyed, unloaded]);
}
/**
* Open a new browser window.
*
* @return {Promise}
* A promise resolving to the newly created chrome window.
*/
async openBrowserWindow(focus = false) {
switch (this.driver.appName) {
case "firefox":
// Open new browser window, and wait until it is fully loaded.
// Also wait for the window to be focused and activated to prevent a
// race condition when promptly focusing to the original window again.
let win = this.window.OpenBrowserWindow();
let activated = waitForEvent(win, "activate");
let focused = waitForEvent(win, "focus", {capture: true});
let startup = waitForObserverTopic("browser-delayed-startup-finished",
subject => subject == win);
// Bug 1509380 - Missing focus/activate event when Firefox is not
// the top-most application. As such wait for the next tick, and
// manually focus the newly opened window.
win.setTimeout(() => win.focus(), 0);
await Promise.all([activated, focused, startup]);
if (!focus) {
// The new window shouldn't get focused. As such set the
// focus back to the currently selected window.
activated = waitForEvent(this.window, "activate");
focused = waitForEvent(this.window, "focus", {capture: true});
this.window.focus();
await Promise.all([activated, focused]);
}
return win;
default:
throw new UnsupportedOperationError(
`openWindow() not supported in ${this.driver.appName}`);
}
}
/**
* Close the current tab.
*
@ -319,17 +365,19 @@ browser.Context = class {
let destroyed = new MessageManagerDestroyedPromise(this.messageManager);
let tabClosed;
if (this.tabBrowser.closeTab) {
switch (this.driver.appName) {
case "fennec":
// Fennec
tabClosed = waitForEvent(this.tabBrowser.deck, "TabClose");
this.tabBrowser.closeTab(this.tab);
break;
} else if (this.tabBrowser.removeTab) {
// Firefox
case "firefox":
tabClosed = waitForEvent(this.tab, "TabClose");
this.tabBrowser.removeTab(this.tab);
break;
} else {
default:
throw new UnsupportedOperationError(
`closeTab() not supported in ${this.driver.appName}`);
}
@ -338,13 +386,37 @@ browser.Context = class {
}
/**
* Opens a tab with given URI.
*
* @param {string} uri
* URI to open.
* Open a new tab in the currently selected chrome window.
*/
addTab(uri) {
return this.tabBrowser.addTab(uri, true);
async openTab(focus = false) {
let tab = null;
let tabOpened = waitForEvent(this.window, "TabOpen");
switch (this.driver.appName) {
case "fennec":
tab = this.tabBrowser.addTab(null, {selected: focus});
break;
case "firefox":
this.window.BrowserOpenTab();
tab = this.tabBrowser.selectedTab;
// The new tab is always selected by default. If focus is not wanted,
// the previously tab needs to be selected again.
if (!focus) {
this.tabBrowser.selectedTab = this.tab;
}
break;
default:
throw new UnsupportedOperationError(
`openTab() not supported in ${this.driver.appName}`);
}
await tabOpened;
return tab;
}
/**
@ -378,16 +450,18 @@ browser.Context = class {
this.tab = this.tabBrowser.tabs[index];
if (focus) {
if (this.tabBrowser.selectTab) {
// Fennec
switch (this.driver.appName) {
case "fennec":
this.tabBrowser.selectTab(this.tab);
break;
} else if ("selectedTab" in this.tabBrowser) {
// Firefox
case "firefox":
this.tabBrowser.selectedTab = this.tab;
break;
} else {
throw new UnsupportedOperationError("switchToTab() not supported");
default:
throw new UnsupportedOperationError(
`switchToTab() not supported in ${this.driver.appName}`);
}
}
}

View File

@ -108,13 +108,12 @@ const globalMessageManager = Services.mm;
*
* @class GeckoDriver
*
* @param {string} appId
* Unique identifier of the application.
* @param {MarionetteServer} server
* The instance of Marionette server.
*/
this.GeckoDriver = function(appId, server) {
this.appId = appId;
this.GeckoDriver = function(server) {
this.appId = Services.appinfo.ID;
this.appName = Services.appinfo.name.toLowerCase();
this._server = server;
this.sessionID = null;
@ -1294,6 +1293,7 @@ GeckoDriver.prototype.getIdForBrowser = function(browser) {
if (browser === null) {
return null;
}
let permKey = browser.permanentKey;
if (this._browserIds.has(permKey)) {
return this._browserIds.get(permKey);
@ -2709,6 +2709,66 @@ GeckoDriver.prototype.deleteCookie = async function(cmd) {
}
};
/**
* Open a new top-level browsing context.
*
* @param {string=} type
* Optional type of the new top-level browsing context. Can be one of
* `tab` or `window`. Defaults to `tab`.
* @param {boolean=} focus
* Optional flag if the new top-level browsing context should be opened
* in foreground (focused) or background (not focused). Defaults to false.
*
* @return {Object.<string, string>}
* Handle and type of the new browsing context.
*/
GeckoDriver.prototype.newWindow = async function(cmd) {
assert.open(this.getCurrentWindow(Context.Content));
await this._handleUserPrompts();
let focus = false;
if (typeof cmd.parameters.focus != "undefined") {
focus = assert.boolean(cmd.parameters.focus,
pprint`Expected "focus" to be a boolean, got ${cmd.parameters.focus}`);
}
let type;
if (typeof cmd.parameters.type != "undefined") {
type = assert.string(cmd.parameters.type,
pprint`Expected "type" to be a string, got ${cmd.parameters.type}`);
}
// If an invalid or no type has been specified default to a tab.
if (typeof type == "undefined" || !["tab", "window"].includes(type)) {
type = "tab";
}
let contentBrowser;
switch (type) {
case "window":
let win = await this.curBrowser.openBrowserWindow(focus);
contentBrowser = browser.getTabBrowser(win).selectedBrowser;
break;
default:
// To not fail if a new type gets added in the future, make opening
// a new tab the default action.
let tab = await this.curBrowser.openTab(focus);
contentBrowser = browser.getBrowserForTab(tab);
}
// Even with the framescript registered, the browser might not be known to
// the parent process yet. Wait until it is available.
// TODO: Fix by using `Browser:Init` or equivalent on bug 1311041
let windowId = await new PollPromise((resolve, reject) => {
let id = this.getIdForBrowser(contentBrowser);
this.windowHandles.includes(id) ? resolve(id) : reject();
});
return {handle: windowId.toString(), type};
};
/**
* Close the currently selected tab/window.
*
@ -3549,6 +3609,7 @@ GeckoDriver.prototype.commands = {
"WebDriver:MaximizeWindow": GeckoDriver.prototype.maximizeWindow,
"WebDriver:Navigate": GeckoDriver.prototype.get,
"WebDriver:NewSession": GeckoDriver.prototype.newSession,
"WebDriver:NewWindow": GeckoDriver.prototype.newWindow,
"WebDriver:PerformActions": GeckoDriver.prototype.performActions,
"WebDriver:Refresh": GeckoDriver.prototype.refresh,
"WebDriver:ReleaseActions": GeckoDriver.prototype.releaseActions,

View File

@ -11,7 +11,6 @@ const ServerSocket = CC(
"nsIServerSocket",
"initSpecialConnection");
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("chrome://marionette/content/assert.js");
@ -74,7 +73,7 @@ class TCPListener {
*/
driverFactory() {
MarionettePrefs.contentListener = false;
return new GeckoDriver(Services.appinfo.ID, this);
return new GeckoDriver(this);
}
set acceptConnections(value) {