2015-03-20 00:12:58 +03:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
"use strict";
|
2017-06-28 21:04:14 +03:00
|
|
|
/* global XPCNativeWrapper */
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2019-01-17 21:18:31 +03:00
|
|
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
|
|
const { XPCOMUtils } = ChromeUtils.import(
|
|
|
|
"resource://gre/modules/XPCOMUtils.jsm"
|
|
|
|
);
|
2019-07-05 12:01:24 +03:00
|
|
|
|
2019-01-17 21:18:31 +03:00
|
|
|
const { accessibility } = ChromeUtils.import(
|
|
|
|
"chrome://marionette/content/accessibility.js"
|
2019-07-05 12:01:24 +03:00
|
|
|
);
|
2019-01-17 21:18:31 +03:00
|
|
|
const { Addon } = ChromeUtils.import("chrome://marionette/content/addon.js");
|
|
|
|
const { assert } = ChromeUtils.import("chrome://marionette/content/assert.js");
|
|
|
|
const { atom } = ChromeUtils.import("chrome://marionette/content/atom.js");
|
|
|
|
const { browser, Context, WindowState } = ChromeUtils.import(
|
|
|
|
"chrome://marionette/content/browser.js"
|
|
|
|
);
|
2018-06-10 15:37:19 +03:00
|
|
|
const { Capabilities, Timeouts, UnhandledPromptBehavior } = ChromeUtils.import(
|
2019-01-17 21:18:31 +03:00
|
|
|
"chrome://marionette/content/capabilities.js"
|
|
|
|
);
|
|
|
|
const { capture } = ChromeUtils.import(
|
|
|
|
"chrome://marionette/content/capture.js"
|
|
|
|
);
|
2018-04-23 12:01:40 +03:00
|
|
|
const {
|
|
|
|
CertificateOverrideManager,
|
|
|
|
InsecureSweepingOverride,
|
2019-01-17 21:18:31 +03:00
|
|
|
} = ChromeUtils.import("chrome://marionette/content/cert.js");
|
|
|
|
const { cookie } = ChromeUtils.import("chrome://marionette/content/cookie.js");
|
|
|
|
const { WebElementEventTarget } = ChromeUtils.import(
|
|
|
|
"chrome://marionette/content/dom.js"
|
|
|
|
);
|
|
|
|
const { ChromeWebElement, element, WebElement } = ChromeUtils.import(
|
|
|
|
"chrome://marionette/content/element.js"
|
|
|
|
);
|
2017-06-28 21:01:49 +03:00
|
|
|
const {
|
2019-02-16 03:03:11 +03:00
|
|
|
ElementNotInteractableError,
|
2017-06-28 21:01:49 +03:00
|
|
|
InsecureCertificateError,
|
|
|
|
InvalidArgumentError,
|
|
|
|
InvalidCookieDomainError,
|
|
|
|
InvalidSelectorError,
|
2018-02-26 20:35:30 +03:00
|
|
|
NoSuchAlertError,
|
2017-06-28 21:01:49 +03:00
|
|
|
NoSuchFrameError,
|
|
|
|
NoSuchWindowError,
|
|
|
|
SessionNotCreatedError,
|
2018-06-10 15:37:19 +03:00
|
|
|
UnexpectedAlertOpenError,
|
2017-06-28 21:01:49 +03:00
|
|
|
UnknownError,
|
|
|
|
UnsupportedOperationError,
|
|
|
|
WebDriverError,
|
2019-01-17 21:18:31 +03:00
|
|
|
} = ChromeUtils.import("chrome://marionette/content/error.js");
|
|
|
|
const { Sandboxes, evaluate } = ChromeUtils.import(
|
|
|
|
"chrome://marionette/content/evaluate.js"
|
|
|
|
);
|
|
|
|
const { pprint } = ChromeUtils.import("chrome://marionette/content/format.js");
|
|
|
|
const { interaction } = ChromeUtils.import(
|
|
|
|
"chrome://marionette/content/interaction.js"
|
|
|
|
);
|
|
|
|
const { l10n } = ChromeUtils.import("chrome://marionette/content/l10n.js");
|
|
|
|
const { legacyaction } = ChromeUtils.import(
|
|
|
|
"chrome://marionette/content/legacyaction.js"
|
|
|
|
);
|
|
|
|
const { Log } = ChromeUtils.import("chrome://marionette/content/log.js");
|
|
|
|
const { modal } = ChromeUtils.import("chrome://marionette/content/modal.js");
|
|
|
|
const { MarionettePrefs } = ChromeUtils.import(
|
|
|
|
"chrome://marionette/content/prefs.js",
|
|
|
|
null
|
|
|
|
);
|
|
|
|
const { proxy } = ChromeUtils.import("chrome://marionette/content/proxy.js");
|
|
|
|
const { reftest } = ChromeUtils.import(
|
|
|
|
"chrome://marionette/content/reftest.js"
|
|
|
|
);
|
2017-10-02 18:50:11 +03:00
|
|
|
const {
|
2018-11-08 16:11:20 +03:00
|
|
|
DebounceCallback,
|
2018-09-11 18:49:18 +03:00
|
|
|
IdlePromise,
|
2017-10-02 18:50:11 +03:00
|
|
|
PollPromise,
|
|
|
|
TimedPromise,
|
2019-01-10 13:10:47 +03:00
|
|
|
waitForEvent,
|
2019-01-10 13:14:22 +03:00
|
|
|
waitForObserverTopic,
|
2019-01-17 21:18:31 +03:00
|
|
|
} = ChromeUtils.import("chrome://marionette/content/sync.js");
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2018-06-06 16:40:05 +03:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "logger", Log.get);
|
2018-06-08 15:16:29 +03:00
|
|
|
XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
|
2017-06-13 16:46:59 +03:00
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
this.EXPORTED_SYMBOLS = ["GeckoDriver"];
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-10-12 14:21:17 +03:00
|
|
|
const APP_ID_FIREFOX = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
|
2018-11-23 00:38:19 +03:00
|
|
|
const APP_ID_THUNDERBIRD = "{3550f703-e582-4d05-9a08-453d09bdfdc6}";
|
2017-10-12 14:21:17 +03:00
|
|
|
|
2017-08-30 19:38:23 +03:00
|
|
|
const FRAME_SCRIPT = "chrome://marionette/content/listener.js";
|
|
|
|
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
2017-03-07 21:38:51 +03:00
|
|
|
|
2016-05-20 15:28:27 +03:00
|
|
|
const SUPPORTED_STRATEGIES = new Set([
|
|
|
|
element.Strategy.ClassName,
|
|
|
|
element.Strategy.Selector,
|
|
|
|
element.Strategy.ID,
|
|
|
|
element.Strategy.TagName,
|
|
|
|
element.Strategy.XPath,
|
|
|
|
element.Strategy.Anon,
|
|
|
|
element.Strategy.AnonAttribute,
|
|
|
|
]);
|
|
|
|
|
2019-01-21 21:39:30 +03:00
|
|
|
// Timeout used to abort fullscreen, maximize, and minimize
|
|
|
|
// commands if no window manager is present.
|
|
|
|
const TIMEOUT_NO_WINDOW_MANAGER = 5000;
|
|
|
|
|
2018-03-01 22:19:56 +03:00
|
|
|
const globalMessageManager = Services.mm;
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-07-26 15:11:53 +03:00
|
|
|
/**
|
|
|
|
* The Marionette WebDriver services provides a standard conforming
|
|
|
|
* implementation of the W3C WebDriver specification.
|
|
|
|
*
|
|
|
|
* @see {@link https://w3c.github.io/webdriver/webdriver-spec.html}
|
|
|
|
* @namespace driver
|
|
|
|
*/
|
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
/**
|
|
|
|
* Implements (parts of) the W3C WebDriver protocol. GeckoDriver lives
|
2015-04-28 16:25:37 +03:00
|
|
|
* in chrome space and mediates calls to the message listener of the current
|
|
|
|
* browsing context's content frame message listener via ListenerProxy.
|
2015-03-20 00:12:58 +03:00
|
|
|
*
|
2017-07-26 15:11:53 +03:00
|
|
|
* Throughout this prototype, functions with the argument <var>cmd</var>'s
|
2017-08-07 18:52:37 +03:00
|
|
|
* documentation refers to the contents of the <code>cmd.parameter</code>
|
2015-03-20 00:12:58 +03:00
|
|
|
* object.
|
|
|
|
*
|
2017-07-26 15:11:53 +03:00
|
|
|
* @class GeckoDriver
|
|
|
|
*
|
2016-10-17 14:19:19 +03:00
|
|
|
* @param {MarionetteServer} server
|
|
|
|
* The instance of Marionette server.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2019-01-10 13:14:24 +03:00
|
|
|
this.GeckoDriver = function(server) {
|
|
|
|
this.appId = Services.appinfo.ID;
|
|
|
|
this.appName = Services.appinfo.name.toLowerCase();
|
2016-10-17 14:19:19 +03:00
|
|
|
this._server = server;
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-08-04 22:32:19 +03:00
|
|
|
this.sessionID = null;
|
2016-05-23 12:21:15 +03:00
|
|
|
this.wins = new browser.Windows();
|
2015-03-20 00:12:58 +03:00
|
|
|
this.browsers = {};
|
|
|
|
// points to current browser
|
|
|
|
this.curBrowser = null;
|
2018-06-07 09:59:42 +03:00
|
|
|
// top-most chrome window
|
2015-03-20 00:12:58 +03:00
|
|
|
this.mainFrame = null;
|
|
|
|
// chrome iframe that currently has focus
|
|
|
|
this.curFrame = null;
|
2016-11-01 21:00:25 +03:00
|
|
|
this.currentFrameElement = null;
|
2015-03-20 00:12:58 +03:00
|
|
|
this.observing = null;
|
|
|
|
this._browserIds = new WeakMap();
|
|
|
|
|
2018-05-24 17:47:30 +03:00
|
|
|
// Use content context by default
|
2017-10-16 19:47:35 +03:00
|
|
|
this.context = Context.Content;
|
2016-11-01 21:00:25 +03:00
|
|
|
|
|
|
|
this.sandboxes = new Sandboxes(() => this.getCurrentWindow());
|
2016-11-29 19:00:52 +03:00
|
|
|
this.legacyactions = new legacyaction.Chain();
|
2016-11-01 21:00:25 +03:00
|
|
|
|
2018-06-26 19:15:28 +03:00
|
|
|
this.capabilities = new Capabilities();
|
2015-03-20 00:12:58 +03:00
|
|
|
|
|
|
|
this.mm = globalMessageManager;
|
2018-01-09 18:53:46 +03:00
|
|
|
this.listener = proxy.toListener(
|
|
|
|
this.sendAsync.bind(this),
|
|
|
|
() => this.curBrowser
|
|
|
|
);
|
2015-03-20 18:46:46 +03:00
|
|
|
|
2019-06-13 21:26:53 +03:00
|
|
|
// used for modal dialogs or tab modal alerts
|
2015-03-20 18:46:46 +03:00
|
|
|
this.dialog = null;
|
2019-06-13 21:26:53 +03:00
|
|
|
this.dialogObserver = null;
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
2016-12-19 22:28:17 +03:00
|
|
|
Object.defineProperty(GeckoDriver.prototype, "a11yChecks", {
|
2017-06-30 02:40:24 +03:00
|
|
|
get() {
|
2016-12-31 15:32:14 +03:00
|
|
|
return this.capabilities.get("moz:accessibilityChecks");
|
2017-06-30 02:40:24 +03:00
|
|
|
},
|
2016-12-19 22:28:17 +03:00
|
|
|
});
|
|
|
|
|
2018-05-24 17:47:30 +03:00
|
|
|
/**
|
|
|
|
* The current context decides if commands are executed in chrome- or
|
|
|
|
* content space.
|
|
|
|
*/
|
|
|
|
Object.defineProperty(GeckoDriver.prototype, "context", {
|
|
|
|
get() {
|
|
|
|
return this._context;
|
|
|
|
},
|
|
|
|
|
|
|
|
set(context) {
|
|
|
|
this._context = Context.fromString(context);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2017-06-13 16:46:59 +03:00
|
|
|
/**
|
|
|
|
* Returns the current URL of the ChromeWindow or content browser,
|
|
|
|
* depending on context.
|
|
|
|
*
|
|
|
|
* @return {URL}
|
|
|
|
* Read-only property containing the currently loaded URL.
|
|
|
|
*/
|
2017-05-31 17:49:56 +03:00
|
|
|
Object.defineProperty(GeckoDriver.prototype, "currentURL", {
|
2017-06-30 02:40:24 +03:00
|
|
|
get() {
|
2017-05-31 17:49:56 +03:00
|
|
|
switch (this.context) {
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Chrome:
|
2017-06-13 16:46:59 +03:00
|
|
|
let chromeWin = this.getCurrentWindow();
|
|
|
|
return new URL(chromeWin.location.href);
|
2017-05-31 17:49:56 +03:00
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Content:
|
2017-06-13 16:46:59 +03:00
|
|
|
return new URL(this.curBrowser.currentURI.spec);
|
2017-06-30 02:40:24 +03:00
|
|
|
|
|
|
|
default:
|
2017-10-06 15:07:13 +03:00
|
|
|
throw new TypeError(`Unknown context: ${this.context}`);
|
2017-05-31 17:49:56 +03:00
|
|
|
}
|
2017-06-30 02:40:24 +03:00
|
|
|
},
|
2017-05-31 17:49:56 +03:00
|
|
|
});
|
|
|
|
|
2017-07-02 14:31:18 +03:00
|
|
|
Object.defineProperty(GeckoDriver.prototype, "title", {
|
|
|
|
get() {
|
|
|
|
switch (this.context) {
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Chrome:
|
2017-07-02 14:31:18 +03:00
|
|
|
let chromeWin = this.getCurrentWindow();
|
|
|
|
return chromeWin.document.documentElement.getAttribute("title");
|
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Content:
|
2017-07-02 14:31:18 +03:00
|
|
|
return this.curBrowser.currentTitle;
|
|
|
|
|
|
|
|
default:
|
2017-10-06 15:07:13 +03:00
|
|
|
throw new TypeError(`Unknown context: ${this.context}`);
|
2017-07-02 14:31:18 +03:00
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2017-01-10 18:36:49 +03:00
|
|
|
Object.defineProperty(GeckoDriver.prototype, "proxy", {
|
2017-06-30 02:40:24 +03:00
|
|
|
get() {
|
2017-01-10 18:36:49 +03:00
|
|
|
return this.capabilities.get("proxy");
|
2017-06-30 02:40:24 +03:00
|
|
|
},
|
2017-01-10 18:36:49 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
Object.defineProperty(GeckoDriver.prototype, "secureTLS", {
|
2017-06-30 02:40:24 +03:00
|
|
|
get() {
|
2017-01-10 18:36:49 +03:00
|
|
|
return !this.capabilities.get("acceptInsecureCerts");
|
2017-06-30 02:40:24 +03:00
|
|
|
},
|
2017-01-10 18:36:49 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
Object.defineProperty(GeckoDriver.prototype, "timeouts", {
|
2017-06-30 02:40:24 +03:00
|
|
|
get() {
|
2017-01-10 18:36:49 +03:00
|
|
|
return this.capabilities.get("timeouts");
|
|
|
|
},
|
|
|
|
|
2017-06-30 02:40:24 +03:00
|
|
|
set(newTimeouts) {
|
2017-01-10 18:36:49 +03:00
|
|
|
this.capabilities.set("timeouts", newTimeouts);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2017-05-09 19:49:09 +03:00
|
|
|
Object.defineProperty(GeckoDriver.prototype, "windows", {
|
2017-06-30 02:40:24 +03:00
|
|
|
get() {
|
2018-08-19 04:13:14 +03:00
|
|
|
return Services.wm.getEnumerator(null);
|
2017-06-30 02:40:24 +03:00
|
|
|
},
|
2017-05-09 19:49:09 +03:00
|
|
|
});
|
|
|
|
|
2017-11-24 14:11:18 +03:00
|
|
|
Object.defineProperty(GeckoDriver.prototype, "windowType", {
|
|
|
|
get() {
|
|
|
|
return this.curBrowser.window.document.documentElement.getAttribute(
|
|
|
|
"windowtype"
|
|
|
|
);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2017-01-10 18:36:49 +03:00
|
|
|
Object.defineProperty(GeckoDriver.prototype, "windowHandles", {
|
2017-06-30 02:40:24 +03:00
|
|
|
get() {
|
2017-01-10 18:36:49 +03:00
|
|
|
let hs = [];
|
|
|
|
|
2017-05-09 19:49:09 +03:00
|
|
|
for (let win of this.windows) {
|
2017-01-26 18:42:35 +03:00
|
|
|
let tabBrowser = browser.getTabBrowser(win);
|
|
|
|
|
2017-06-13 19:08:44 +03:00
|
|
|
// Only return handles for browser windows
|
2017-06-09 20:28:10 +03:00
|
|
|
if (tabBrowser && tabBrowser.tabs) {
|
2018-08-09 01:22:39 +03:00
|
|
|
for (let tab of tabBrowser.tabs) {
|
2017-01-26 18:42:35 +03:00
|
|
|
let winId = this.getIdForBrowser(browser.getBrowserForTab(tab));
|
2017-01-10 18:36:49 +03:00
|
|
|
if (winId !== null) {
|
|
|
|
hs.push(winId);
|
|
|
|
}
|
2018-08-09 01:22:39 +03:00
|
|
|
}
|
2017-01-10 18:36:49 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return hs;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
Object.defineProperty(GeckoDriver.prototype, "chromeWindowHandles", {
|
2017-06-30 02:40:24 +03:00
|
|
|
get() {
|
2017-01-10 18:36:49 +03:00
|
|
|
let hs = [];
|
|
|
|
|
2017-05-09 19:49:09 +03:00
|
|
|
for (let win of this.windows) {
|
|
|
|
hs.push(getOuterWindowId(win));
|
2017-01-10 18:36:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return hs;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2018-04-23 06:55:06 +03:00
|
|
|
GeckoDriver.prototype.QueryInterface = ChromeUtils.generateQI([
|
2015-03-20 00:12:58 +03:00
|
|
|
Ci.nsIObserver,
|
2016-12-30 15:43:36 +03:00
|
|
|
Ci.nsISupportsWeakReference,
|
2015-03-20 00:12:58 +03:00
|
|
|
]);
|
|
|
|
|
2017-12-06 00:28:59 +03:00
|
|
|
GeckoDriver.prototype.init = function() {
|
2018-01-02 15:00:41 +03:00
|
|
|
this.mm.addMessageListener("Marionette:WebDriver:GetCapabilities", this);
|
|
|
|
this.mm.addMessageListener("Marionette:ListenersAttached", this);
|
|
|
|
this.mm.addMessageListener("Marionette:Register", this);
|
2017-12-06 00:28:59 +03:00
|
|
|
this.mm.addMessageListener("Marionette:switchedToFrame", this);
|
|
|
|
};
|
|
|
|
|
|
|
|
GeckoDriver.prototype.uninit = function() {
|
2018-01-02 15:00:41 +03:00
|
|
|
this.mm.removeMessageListener("Marionette:WebDriver:GetCapabilities", this);
|
|
|
|
this.mm.removeMessageListener("Marionette:ListenersAttached", this);
|
|
|
|
this.mm.removeMessageListener("Marionette:Register", this);
|
2017-12-06 00:28:59 +03:00
|
|
|
this.mm.removeMessageListener("Marionette:switchedToFrame", this);
|
|
|
|
};
|
|
|
|
|
2017-03-28 23:47:57 +03:00
|
|
|
/**
|
|
|
|
* Callback used to observe the creation of new modal or tab modal dialogs
|
|
|
|
* during the session's lifetime.
|
|
|
|
*/
|
2019-06-13 21:26:53 +03:00
|
|
|
GeckoDriver.prototype.handleModalDialog = function(action, dialog, win) {
|
|
|
|
// Only care about modals of the currently selected window.
|
|
|
|
if (win !== this.curBrowser.window) {
|
|
|
|
return;
|
|
|
|
}
|
2019-06-07 21:36:48 +03:00
|
|
|
|
2019-06-13 21:26:53 +03:00
|
|
|
if (action === modal.ACTION_OPENED) {
|
|
|
|
this.dialog = new modal.Dialog(() => this.curBrowser, dialog);
|
|
|
|
} else if (action === modal.ACTION_CLOSED) {
|
|
|
|
this.dialog = null;
|
2017-03-28 23:47:57 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
/**
|
|
|
|
* Helper method to send async messages to the content listener.
|
2015-03-24 00:32:03 +03:00
|
|
|
* Correct usage is to pass in the name of a function in listener.js,
|
2016-12-19 22:08:46 +03:00
|
|
|
* a serialisable object, and optionally the current command's ID
|
|
|
|
* when not using the modern dispatching technique.
|
2015-03-20 00:12:58 +03:00
|
|
|
*
|
|
|
|
* @param {string} name
|
2017-12-05 21:44:24 +03:00
|
|
|
* Suffix of the target message handler <tt>Marionette:SUFFIX</tt>.
|
|
|
|
* @param {Object=} data
|
|
|
|
* Data that must be serialisable using {@link evaluate.toJSON}.
|
2016-12-19 22:08:46 +03:00
|
|
|
* @param {number=} commandID
|
|
|
|
* Optional command ID to ensure synchronisity.
|
2017-12-05 21:44:24 +03:00
|
|
|
*
|
|
|
|
* @throws {JavaScriptError}
|
|
|
|
* If <var>data</var> could not be marshaled.
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* If there is no current target frame.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2017-06-30 02:40:24 +03:00
|
|
|
GeckoDriver.prototype.sendAsync = function(name, data, commandID) {
|
2017-10-05 19:57:17 +03:00
|
|
|
let payload = evaluate.toJSON(data, this.seenEls);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2018-05-03 13:00:26 +03:00
|
|
|
if (payload === null) {
|
|
|
|
payload = {};
|
|
|
|
}
|
|
|
|
|
2016-01-29 15:57:46 +03:00
|
|
|
// TODO(ato): When proxy.AsyncMessageChannel
|
|
|
|
// is used for all chrome <-> content communication
|
|
|
|
// this can be removed.
|
2016-12-19 22:08:46 +03:00
|
|
|
if (commandID) {
|
2017-08-19 16:22:17 +03:00
|
|
|
payload.commandID = commandID;
|
2015-03-23 23:43:18 +03:00
|
|
|
}
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2016-12-19 22:08:46 +03:00
|
|
|
this.curBrowser.executeWhenReady(() => {
|
|
|
|
if (this.curBrowser.curFrameId) {
|
2018-01-09 20:48:57 +03:00
|
|
|
let target = `Marionette:${name}`;
|
|
|
|
this.curBrowser.messageManager.sendAsyncMessage(target, payload);
|
2016-12-19 22:08:46 +03:00
|
|
|
} else {
|
|
|
|
throw new NoSuchWindowError(
|
|
|
|
"No such content frame; perhaps the listener was not registered?"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
/**
|
2017-03-21 00:43:32 +03:00
|
|
|
* Get the session's current top-level browsing context.
|
2015-03-20 00:12:58 +03:00
|
|
|
*
|
2017-07-26 15:11:53 +03:00
|
|
|
* It will return the outer {@link ChromeWindow} previously selected by
|
|
|
|
* window handle through {@link #switchToWindow}, or the first window that
|
|
|
|
* was registered.
|
2015-03-20 00:12:58 +03:00
|
|
|
*
|
2017-03-20 17:13:32 +03:00
|
|
|
* @param {Context=} forcedContext
|
2017-06-30 02:40:24 +03:00
|
|
|
* Optional name of the context to use for finding the window.
|
|
|
|
* It will be required if a command always needs a specific context,
|
|
|
|
* whether which context is currently set. Defaults to the current
|
|
|
|
* context.
|
2017-03-20 17:13:32 +03:00
|
|
|
*
|
2017-03-21 00:43:32 +03:00
|
|
|
* @return {ChromeWindow}
|
|
|
|
* The current top-level browsing context.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2017-06-30 02:40:24 +03:00
|
|
|
GeckoDriver.prototype.getCurrentWindow = function(forcedContext = undefined) {
|
2017-03-20 17:13:32 +03:00
|
|
|
let context =
|
|
|
|
typeof forcedContext == "undefined" ? this.context : forcedContext;
|
|
|
|
let win = null;
|
|
|
|
|
2017-03-21 00:43:32 +03:00
|
|
|
switch (context) {
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Chrome:
|
2017-03-21 00:43:32 +03:00
|
|
|
if (this.curFrame !== null) {
|
|
|
|
win = this.curFrame;
|
|
|
|
} else if (this.curBrowser !== null) {
|
2017-03-20 17:13:32 +03:00
|
|
|
win = this.curBrowser.window;
|
2017-02-03 19:49:38 +03:00
|
|
|
}
|
2017-03-21 00:43:32 +03:00
|
|
|
break;
|
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Content:
|
2017-03-21 00:43:32 +03:00
|
|
|
if (this.curFrame !== null) {
|
|
|
|
win = this.curFrame;
|
2017-06-13 19:08:44 +03:00
|
|
|
} else if (this.curBrowser !== null && this.curBrowser.contentBrowser) {
|
2017-05-09 21:05:49 +03:00
|
|
|
win = this.curBrowser.window;
|
2017-03-20 17:13:32 +03:00
|
|
|
}
|
2017-03-21 00:43:32 +03:00
|
|
|
break;
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
2017-03-20 17:13:32 +03:00
|
|
|
|
|
|
|
return win;
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
2017-06-30 02:40:24 +03:00
|
|
|
GeckoDriver.prototype.isReftestBrowser = function(element) {
|
2017-05-09 21:05:49 +03:00
|
|
|
return (
|
|
|
|
this._reftest &&
|
|
|
|
element &&
|
|
|
|
element.tagName === "xul:browser" &&
|
|
|
|
element.parentElement &&
|
|
|
|
element.parentElement.id === "reftest"
|
|
|
|
);
|
2017-06-30 02:40:24 +03:00
|
|
|
};
|
2017-05-09 21:05:49 +03:00
|
|
|
|
2017-06-30 02:40:24 +03:00
|
|
|
GeckoDriver.prototype.addFrameCloseListener = function(action) {
|
2015-03-20 00:12:58 +03:00
|
|
|
let win = this.getCurrentWindow();
|
|
|
|
this.mozBrowserClose = e => {
|
|
|
|
if (e.target.id == this.oopFrameId) {
|
|
|
|
win.removeEventListener("mozbrowserclose", this.mozBrowserClose, true);
|
2015-04-29 14:00:43 +03:00
|
|
|
throw new NoSuchWindowError("The window closed during action: " + action);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
win.addEventListener("mozbrowserclose", this.mozBrowserClose, true);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2016-05-10 15:29:21 +03:00
|
|
|
* Create a new browsing context for window and add to known browsers.
|
2015-03-20 00:12:58 +03:00
|
|
|
*
|
2017-08-21 20:52:18 +03:00
|
|
|
* @param {ChromeWindow} win
|
2016-05-10 15:29:21 +03:00
|
|
|
* Window for which we will create a browsing context.
|
2015-03-20 00:12:58 +03:00
|
|
|
*
|
|
|
|
* @return {string}
|
|
|
|
* Returns the unique server-assigned ID of the window.
|
|
|
|
*/
|
2017-08-21 20:52:18 +03:00
|
|
|
GeckoDriver.prototype.addBrowser = function(window) {
|
|
|
|
let bc = new browser.Context(window, this);
|
|
|
|
let winId = getOuterWindowId(window);
|
2017-01-27 11:09:32 +03:00
|
|
|
|
2016-05-10 15:29:21 +03:00
|
|
|
this.browsers[winId] = bc;
|
2015-03-20 00:12:58 +03:00
|
|
|
this.curBrowser = this.browsers[winId];
|
2016-05-23 12:21:15 +03:00
|
|
|
if (!this.wins.has(winId)) {
|
2015-03-20 00:12:58 +03:00
|
|
|
// add this to seenItems so we can guarantee
|
|
|
|
// the user will get winId as this window's id
|
2017-08-21 20:52:18 +03:00
|
|
|
this.wins.set(winId, window);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Registers a new browser, win, with Marionette.
|
|
|
|
*
|
|
|
|
* If we have not seen the browser content window before, the listener
|
|
|
|
* frame script will be loaded into it. If isNewSession is true, we will
|
|
|
|
* switch focus to the start frame when it registers.
|
|
|
|
*
|
2017-08-21 20:52:18 +03:00
|
|
|
* @param {ChromeWindow} win
|
2015-03-20 00:12:58 +03:00
|
|
|
* Window whose browser we need to access.
|
2017-07-26 15:11:53 +03:00
|
|
|
* @param {boolean=} [false] isNewSession
|
2015-03-20 00:12:58 +03:00
|
|
|
* True if this is the first time we're talking to this browser.
|
|
|
|
*/
|
2017-08-21 20:52:18 +03:00
|
|
|
GeckoDriver.prototype.startBrowser = function(window, isNewSession = false) {
|
|
|
|
this.mainFrame = window;
|
2015-03-20 00:12:58 +03:00
|
|
|
this.curFrame = null;
|
2017-08-21 20:52:18 +03:00
|
|
|
this.addBrowser(window);
|
|
|
|
this.whenBrowserStarted(window, isNewSession);
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Callback invoked after a new session has been started in a browser.
|
|
|
|
* Loads the Marionette frame script into the browser if needed.
|
|
|
|
*
|
2017-08-21 20:52:18 +03:00
|
|
|
* @param {ChromeWindow} window
|
2015-03-20 00:12:58 +03:00
|
|
|
* Window whose browser we need to access.
|
|
|
|
* @param {boolean} isNewSession
|
|
|
|
* True if this is the first time we're talking to this browser.
|
|
|
|
*/
|
2017-08-21 20:52:18 +03:00
|
|
|
GeckoDriver.prototype.whenBrowserStarted = function(window, isNewSession) {
|
|
|
|
let mm = window.messageManager;
|
2016-07-07 01:23:35 +03:00
|
|
|
if (mm) {
|
2015-03-20 00:12:58 +03:00
|
|
|
if (!isNewSession) {
|
|
|
|
// Loading the frame script corresponds to a situation we need to
|
|
|
|
// return to the server. If the messageManager is a message broadcaster
|
2017-06-30 02:40:24 +03:00
|
|
|
// with no children, we don't have a hope of coming back from this
|
|
|
|
// call, so send the ack here. Otherwise, make a note of how many
|
|
|
|
// child scripts will be loaded so we known when it's safe to return.
|
|
|
|
// Child managers may not have child scripts yet (e.g. socialapi),
|
|
|
|
// only count child managers that have children, but only count the top
|
|
|
|
// level children as they are the ones that we expect a response from.
|
2015-04-24 15:55:52 +03:00
|
|
|
if (mm.childCount !== 0) {
|
2016-08-24 01:24:06 +03:00
|
|
|
this.curBrowser.frameRegsPending = 0;
|
|
|
|
for (let i = 0; i < mm.childCount; i++) {
|
2016-12-30 15:43:36 +03:00
|
|
|
if (mm.getChildAt(i).childCount !== 0) {
|
2016-08-24 01:24:06 +03:00
|
|
|
this.curBrowser.frameRegsPending += 1;
|
2016-12-30 15:43:36 +03:00
|
|
|
}
|
2016-08-24 01:24:06 +03:00
|
|
|
}
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-23 09:26:09 +03:00
|
|
|
if (!MarionettePrefs.contentListener || !isNewSession) {
|
2016-07-26 15:35:01 +03:00
|
|
|
// load listener into the remote frame
|
|
|
|
// and any applicable new frames
|
|
|
|
// opened after this call
|
|
|
|
mm.loadFrameScript(FRAME_SCRIPT, true);
|
2018-04-23 09:26:09 +03:00
|
|
|
MarionettePrefs.contentListener = true;
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
2016-07-07 01:23:35 +03:00
|
|
|
} else {
|
2017-08-21 20:52:18 +03:00
|
|
|
logger.error("Unable to load content frame script");
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Recursively get all labeled text.
|
|
|
|
*
|
2017-08-21 20:56:19 +03:00
|
|
|
* @param {Element} el
|
2015-03-20 00:12:58 +03:00
|
|
|
* The parent element.
|
|
|
|
* @param {Array.<string>} lines
|
|
|
|
* Array that holds the text lines.
|
|
|
|
*/
|
2017-06-30 02:40:24 +03:00
|
|
|
GeckoDriver.prototype.getVisibleText = function(el, lines) {
|
2015-03-20 00:12:58 +03:00
|
|
|
try {
|
2016-02-03 21:56:02 +03:00
|
|
|
if (atom.isElementDisplayed(el, this.getCurrentWindow())) {
|
2015-03-20 00:12:58 +03:00
|
|
|
if (el.value) {
|
|
|
|
lines.push(el.value);
|
|
|
|
}
|
|
|
|
for (let child in el.childNodes) {
|
|
|
|
this.getVisibleText(el.childNodes[child], lines);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
if (el.nodeName == "#text") {
|
|
|
|
lines.push(el.textContent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles registration of new content listener browsers. Depending on
|
|
|
|
* their type they are either accepted or ignored.
|
|
|
|
*/
|
2017-06-30 02:40:24 +03:00
|
|
|
GeckoDriver.prototype.registerBrowser = function(id, be) {
|
2015-03-20 00:12:58 +03:00
|
|
|
let listenerWindow = Services.wm.getOuterWindowWithId(id);
|
|
|
|
|
2016-12-09 23:55:25 +03:00
|
|
|
// 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),
|
|
|
|
// as well as XUL frames. Ideally this should be cleaned up and we should
|
|
|
|
// keep track of browsers a different way.
|
2017-10-12 14:21:17 +03:00
|
|
|
if (
|
|
|
|
this.appId != APP_ID_FIREFOX ||
|
|
|
|
be.namespaceURI != XUL_NS ||
|
2016-12-09 23:55:25 +03:00
|
|
|
be.nodeName != "browser" ||
|
|
|
|
be.getTabBrowser()
|
|
|
|
) {
|
2015-03-20 00:12:58 +03:00
|
|
|
// curBrowser holds all the registered frames in knownFrames
|
2017-07-06 19:02:19 +03:00
|
|
|
this.curBrowser.register(id, be);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
|
2017-07-06 19:02:19 +03:00
|
|
|
this.wins.set(id, listenerWindow);
|
2018-01-02 15:00:41 +03:00
|
|
|
return id;
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
2017-06-30 02:40:24 +03:00
|
|
|
GeckoDriver.prototype.registerPromise = function() {
|
2018-01-02 15:00:41 +03:00
|
|
|
const li = "Marionette:Register";
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2016-12-19 22:08:46 +03:00
|
|
|
return new Promise(resolve => {
|
2018-01-09 22:50:30 +03:00
|
|
|
let cb = ({ json, target }) => {
|
|
|
|
let { outerWindowID } = json;
|
|
|
|
this.registerBrowser(outerWindowID, target);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2015-03-23 23:43:18 +03:00
|
|
|
if (this.curBrowser.frameRegsPending > 0) {
|
2015-03-20 00:12:58 +03:00
|
|
|
this.curBrowser.frameRegsPending--;
|
2015-03-23 23:43:18 +03:00
|
|
|
}
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2015-04-24 15:55:52 +03:00
|
|
|
if (this.curBrowser.frameRegsPending === 0) {
|
2015-03-20 00:12:58 +03:00
|
|
|
this.mm.removeMessageListener(li, cb);
|
|
|
|
resolve();
|
|
|
|
}
|
|
|
|
|
2018-01-09 22:50:30 +03:00
|
|
|
return { outerWindowID };
|
2015-06-24 10:10:10 +03:00
|
|
|
};
|
|
|
|
this.mm.addMessageListener(li, cb);
|
2015-03-20 00:12:58 +03:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2017-06-30 02:40:24 +03:00
|
|
|
GeckoDriver.prototype.listeningPromise = function() {
|
2018-01-02 15:00:41 +03:00
|
|
|
const li = "Marionette:ListenersAttached";
|
2017-07-06 19:02:19 +03:00
|
|
|
|
2016-12-19 22:08:46 +03:00
|
|
|
return new Promise(resolve => {
|
2017-07-06 19:02:19 +03:00
|
|
|
let cb = msg => {
|
2018-01-02 15:00:41 +03:00
|
|
|
if (msg.json.outerWindowID === this.curBrowser.curFrameId) {
|
2017-07-06 19:02:19 +03:00
|
|
|
this.mm.removeMessageListener(li, cb);
|
|
|
|
resolve();
|
|
|
|
}
|
2015-06-24 10:10:10 +03:00
|
|
|
};
|
|
|
|
this.mm.addMessageListener(li, cb);
|
2015-03-20 00:12:58 +03:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2017-08-04 22:04:12 +03:00
|
|
|
/**
|
|
|
|
* Create a new WebDriver session.
|
|
|
|
*
|
|
|
|
* It is expected that the caller performs the necessary checks on
|
|
|
|
* the requested capabilities to be WebDriver conforming. The WebDriver
|
|
|
|
* service offered by Marionette does not match or negotiate capabilities
|
|
|
|
* beyond type- and bounds checks.
|
|
|
|
*
|
|
|
|
* <h3>Capabilities</h3>
|
|
|
|
*
|
|
|
|
* <dl>
|
|
|
|
* <dt><code>pageLoadStrategy</code> (string)
|
|
|
|
* <dd>The page load strategy to use for the current session. Must be
|
|
|
|
* one of "<tt>none</tt>", "<tt>eager</tt>", and "<tt>normal</tt>".
|
|
|
|
*
|
|
|
|
* <dt><code>acceptInsecureCerts</code> (boolean)
|
|
|
|
* <dd>Indicates whether untrusted and self-signed TLS certificates
|
|
|
|
* are implicitly trusted on navigation for the duration of the session.
|
|
|
|
*
|
|
|
|
* <dt><code>timeouts</code> (Timeouts object)
|
|
|
|
* <dd>Describes the timeouts imposed on certian session operations.
|
|
|
|
*
|
|
|
|
* <dt><code>proxy</code> (Proxy object)
|
|
|
|
* <dd>Defines the proxy configuration.
|
|
|
|
*
|
|
|
|
* <dt><code>moz:accessibilityChecks</code> (boolean)
|
|
|
|
* <dd>Run a11y checks when clicking elements.
|
2017-09-01 18:11:35 +03:00
|
|
|
*
|
2018-01-23 19:31:06 +03:00
|
|
|
* <dt><code>moz:useNonSpecCompliantPointerOrigin</code> (boolean)
|
|
|
|
* <dd>Use the not WebDriver conforming calculation of the pointer origin
|
|
|
|
* when the origin is an element, and the element center point is used.
|
|
|
|
*
|
2017-09-01 18:11:35 +03:00
|
|
|
* <dt><code>moz:webdriverClick</code> (boolean)
|
|
|
|
* <dd>Use a WebDriver conforming <i>WebDriver::ElementClick</i>.
|
2017-08-04 22:04:12 +03:00
|
|
|
* </dl>
|
|
|
|
*
|
|
|
|
* <h4>Timeouts object</h4>
|
|
|
|
*
|
|
|
|
* <dl>
|
|
|
|
* <dt><code>script</code> (number)
|
|
|
|
* <dd>Determines when to interrupt a script that is being evaluates.
|
|
|
|
*
|
|
|
|
* <dt><code>pageLoad</code> (number)
|
|
|
|
* <dd>Provides the timeout limit used to interrupt navigation of the
|
|
|
|
* browsing context.
|
|
|
|
*
|
|
|
|
* <dt><code>implicit</code> (number)
|
|
|
|
* <dd>Gives the timeout of when to abort when locating an element.
|
|
|
|
* </dl>
|
|
|
|
*
|
|
|
|
* <h4>Proxy object</h4>
|
|
|
|
*
|
|
|
|
* <dl>
|
|
|
|
* <dt><code>proxyType</code> (string)
|
|
|
|
* <dd>Indicates the type of proxy configuration. Must be one
|
|
|
|
* of "<tt>pac</tt>", "<tt>direct</tt>", "<tt>autodetect</tt>",
|
|
|
|
* "<tt>system</tt>", or "<tt>manual</tt>".
|
|
|
|
*
|
|
|
|
* <dt><code>proxyAutoconfigUrl</code> (string)
|
|
|
|
* <dd>Defines the URL for a proxy auto-config file if
|
|
|
|
* <code>proxyType</code> is equal to "<tt>pac</tt>".
|
|
|
|
*
|
|
|
|
* <dt><code>ftpProxy</code> (string)
|
|
|
|
* <dd>Defines the proxy host for FTP traffic when the
|
|
|
|
* <code>proxyType</code> is "<tt>manual</tt>".
|
|
|
|
*
|
|
|
|
* <dt><code>httpProxy</code> (string)
|
|
|
|
* <dd>Defines the proxy host for HTTP traffic when the
|
|
|
|
* <code>proxyType</code> is "<tt>manual</tt>".
|
|
|
|
*
|
|
|
|
* <dt><code>noProxy</code> (string)
|
|
|
|
* <dd>Lists the adress for which the proxy should be bypassed when
|
|
|
|
* the <code>proxyType</code> is "<tt>manual</tt>". Must be a JSON
|
|
|
|
* List containing any number of any of domains, IPv4 addresses, or IPv6
|
|
|
|
* addresses.
|
|
|
|
*
|
|
|
|
* <dt><code>sslProxy</code> (string)
|
|
|
|
* <dd>Defines the proxy host for encrypted TLS traffic when the
|
|
|
|
* <code>proxyType</code> is "<tt>manual</tt>".
|
|
|
|
*
|
|
|
|
* <dt><code>socksProxy</code> (string)
|
|
|
|
* <dd>Defines the proxy host for a SOCKS proxy traffic when the
|
|
|
|
* <code>proxyType</code> is "<tt>manual</tt>".
|
|
|
|
*
|
|
|
|
* <dt><code>socksVersion</code> (string)
|
|
|
|
* <dd>Defines the SOCKS proxy version when the <code>proxyType</code> is
|
|
|
|
* "<tt>manual</tt>". It must be any integer between 0 and 255
|
|
|
|
* inclusive.
|
|
|
|
* </dl>
|
|
|
|
*
|
|
|
|
* <h3>Example</h3>
|
|
|
|
*
|
|
|
|
* Input:
|
|
|
|
*
|
|
|
|
* <pre><code>
|
|
|
|
* {"capabilities": {"acceptInsecureCerts": true}}
|
|
|
|
* </code></pre>
|
|
|
|
*
|
|
|
|
* @param {string=} sessionId
|
|
|
|
* Normally a unique ID is given to a new session, however this can
|
|
|
|
* be overriden by providing this field.
|
|
|
|
* @param {Object.<string, *>=} capabilities
|
|
|
|
* JSON Object containing any of the recognised capabilities listed
|
|
|
|
* above.
|
|
|
|
*
|
|
|
|
* @return {Object}
|
|
|
|
* Session ID and capabilities offered by the WebDriver service.
|
|
|
|
*
|
|
|
|
* @throws {SessionNotCreatedError}
|
|
|
|
* If, for whatever reason, a session could not be created.
|
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.newSession = async function(cmd) {
|
2017-08-04 22:32:19 +03:00
|
|
|
if (this.sessionID) {
|
2016-12-30 15:43:36 +03:00
|
|
|
throw new SessionNotCreatedError("Maximum number of active sessions");
|
2016-05-25 17:04:40 +03:00
|
|
|
}
|
2017-10-05 19:11:26 +03:00
|
|
|
this.sessionID = WebElement.generateUUID();
|
2017-08-18 15:49:00 +03:00
|
|
|
|
2016-12-31 15:27:13 +03:00
|
|
|
try {
|
2018-06-26 19:15:28 +03:00
|
|
|
this.capabilities = Capabilities.fromJSON(cmd.parameters);
|
2016-11-06 21:03:31 +03:00
|
|
|
|
2017-08-18 15:49:00 +03:00
|
|
|
if (!this.secureTLS) {
|
|
|
|
logger.warn("TLS certificate errors will be ignored for this session");
|
2018-04-23 12:01:40 +03:00
|
|
|
let acceptAllCerts = new InsecureSweepingOverride();
|
|
|
|
CertificateOverrideManager.install(acceptAllCerts);
|
2017-08-18 15:49:00 +03:00
|
|
|
}
|
2016-11-06 21:03:31 +03:00
|
|
|
|
2017-08-18 15:49:00 +03:00
|
|
|
if (this.proxy.init()) {
|
|
|
|
logger.info("Proxy settings initialised: " + JSON.stringify(this.proxy));
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
throw new SessionNotCreatedError(e);
|
2016-12-31 15:27:13 +03:00
|
|
|
}
|
|
|
|
|
2016-11-07 20:15:08 +03:00
|
|
|
// If we are testing accessibility with marionette, start a11y service in
|
|
|
|
// chrome first. This will ensure that we do not have any content-only
|
|
|
|
// services hanging around.
|
2016-12-19 22:28:17 +03:00
|
|
|
if (this.a11yChecks && accessibility.service) {
|
2016-11-07 20:15:08 +03:00
|
|
|
logger.info("Preemptively starting accessibility service in Chrome");
|
|
|
|
}
|
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
let registerBrowsers = this.registerPromise();
|
|
|
|
let browserListening = this.listeningPromise();
|
|
|
|
|
2017-03-01 14:48:55 +03:00
|
|
|
let waitForWindow = function() {
|
2019-02-04 21:15:52 +03:00
|
|
|
let windowTypes;
|
2018-11-23 00:38:19 +03:00
|
|
|
switch (this.appId) {
|
|
|
|
case APP_ID_THUNDERBIRD:
|
2019-02-04 21:15:52 +03:00
|
|
|
windowTypes = ["mail:3pane"];
|
2018-11-23 00:38:19 +03:00
|
|
|
break;
|
|
|
|
default:
|
2019-02-04 21:15:52 +03:00
|
|
|
// We assume that an app either has GeckoView windows, or
|
|
|
|
// Firefox/Fennec windows, but not both.
|
|
|
|
windowTypes = ["navigator:browser", "navigator:geckoview"];
|
2018-11-23 00:38:19 +03:00
|
|
|
break;
|
|
|
|
}
|
2019-02-04 21:15:52 +03:00
|
|
|
let win;
|
|
|
|
for (let windowType of windowTypes) {
|
|
|
|
win = Services.wm.getMostRecentWindow(windowType);
|
|
|
|
if (win) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2015-03-20 00:12:58 +03:00
|
|
|
if (!win) {
|
2017-03-01 14:48:55 +03:00
|
|
|
// if the window isn't even created, just poll wait for it
|
2015-03-20 00:12:58 +03:00
|
|
|
let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
|
|
|
checkTimer.initWithCallback(
|
|
|
|
waitForWindow.bind(this),
|
|
|
|
100,
|
|
|
|
Ci.nsITimer.TYPE_ONE_SHOT
|
|
|
|
);
|
|
|
|
} else if (win.document.readyState != "complete") {
|
|
|
|
// otherwise, wait for it to be fully loaded before proceeding
|
|
|
|
let listener = ev => {
|
|
|
|
// ensure that we proceed, on the top level document load event
|
|
|
|
// (not an iframe one...)
|
2015-03-23 23:43:18 +03:00
|
|
|
if (ev.target != win.document) {
|
2015-03-20 00:12:58 +03:00
|
|
|
return;
|
2015-03-23 23:43:18 +03:00
|
|
|
}
|
2015-03-20 00:12:58 +03:00
|
|
|
win.removeEventListener("load", listener);
|
|
|
|
waitForWindow.call(this);
|
|
|
|
};
|
|
|
|
win.addEventListener("load", listener, true);
|
|
|
|
} else {
|
2018-04-23 09:17:44 +03:00
|
|
|
if (MarionettePrefs.clickToStart) {
|
2017-03-21 00:43:32 +03:00
|
|
|
Services.prompt.alert(
|
|
|
|
win,
|
|
|
|
"",
|
|
|
|
"Click to start execution of marionette tests"
|
|
|
|
);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
this.startBrowser(win, true);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-04-23 09:26:09 +03:00
|
|
|
if (!MarionettePrefs.contentListener) {
|
2017-03-07 21:38:51 +03:00
|
|
|
waitForWindow.call(this);
|
2017-10-12 14:21:17 +03:00
|
|
|
} else if (this.appId != APP_ID_FIREFOX && this.curBrowser === null) {
|
2017-03-07 21:38:51 +03:00
|
|
|
// if there is a content listener, then we just wake it up
|
2017-07-04 11:27:54 +03:00
|
|
|
let win = this.getCurrentWindow();
|
|
|
|
this.addBrowser(win);
|
|
|
|
this.whenBrowserStarted(win, false);
|
2015-03-20 00:12:58 +03:00
|
|
|
} else {
|
2017-03-07 21:38:51 +03:00
|
|
|
throw new WebDriverError("Session already running");
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
|
2017-08-07 18:52:37 +03:00
|
|
|
await registerBrowsers;
|
|
|
|
await browserListening;
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2018-06-07 09:59:42 +03:00
|
|
|
if (this.mainFrame) {
|
|
|
|
this.mainFrame.focus();
|
|
|
|
}
|
|
|
|
|
2017-01-26 18:42:35 +03:00
|
|
|
if (this.curBrowser.tab) {
|
2017-03-27 12:02:36 +03:00
|
|
|
this.curBrowser.contentBrowser.focus();
|
2017-01-26 18:42:35 +03:00
|
|
|
}
|
2017-01-04 22:38:34 +03:00
|
|
|
|
2019-06-13 21:26:53 +03:00
|
|
|
// Setup observer for modal dialogs
|
|
|
|
this.dialogObserver = new modal.DialogObserver(this);
|
|
|
|
this.dialogObserver.add(this.handleModalDialog.bind(this));
|
|
|
|
|
|
|
|
// Check if there is already an open dialog for the selected browser window.
|
2017-03-28 23:47:57 +03:00
|
|
|
this.dialog = modal.findModalDialogs(this.curBrowser);
|
|
|
|
|
2016-12-19 22:08:46 +03:00
|
|
|
return {
|
2017-08-04 22:32:19 +03:00
|
|
|
sessionId: this.sessionID,
|
2016-12-31 15:32:14 +03:00
|
|
|
capabilities: this.capabilities,
|
2016-12-19 22:08:46 +03:00
|
|
|
};
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Send the current session's capabilities to the client.
|
|
|
|
*
|
|
|
|
* Capabilities informs the client of which WebDriver features are
|
|
|
|
* supported by Firefox and Marionette. They are immutable for the
|
|
|
|
* length of the session.
|
|
|
|
*
|
|
|
|
* The return value is an immutable map of string keys
|
|
|
|
* ("capabilities") to values, which may be of types boolean,
|
|
|
|
* numerical or string.
|
|
|
|
*/
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.getSessionCapabilities = function() {
|
|
|
|
return { capabilities: this.capabilities };
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2017-10-16 19:47:35 +03:00
|
|
|
* Sets the context of the subsequent commands.
|
|
|
|
*
|
|
|
|
* All subsequent requests to commands that in some way involve
|
|
|
|
* interaction with a browsing context will target the chosen browsing
|
|
|
|
* context.
|
2015-03-20 00:12:58 +03:00
|
|
|
*
|
|
|
|
* @param {string} value
|
|
|
|
* Name of the context to be switched to. Must be one of "chrome" or
|
|
|
|
* "content".
|
2017-10-16 19:47:35 +03:00
|
|
|
*
|
|
|
|
* @throws {InvalidArgumentError}
|
|
|
|
* If <var>value</var> is not a string.
|
|
|
|
* @throws {WebDriverError}
|
|
|
|
* If <var>value</var> is not a valid browsing context.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.setContext = function(cmd) {
|
2017-10-16 19:47:35 +03:00
|
|
|
let value = assert.string(cmd.parameters.value);
|
2018-05-24 17:47:30 +03:00
|
|
|
|
|
|
|
this.context = value;
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
/**
|
|
|
|
* Gets the context type that is Marionette's current target for
|
|
|
|
* browsing context scoped commands.
|
|
|
|
*
|
|
|
|
* You may choose a context through the {@link #setContext} command.
|
|
|
|
*
|
|
|
|
* The default browsing context is {@link Context.Content}.
|
|
|
|
*
|
|
|
|
* @return {Context}
|
|
|
|
* Current context.
|
|
|
|
*/
|
|
|
|
GeckoDriver.prototype.getContext = function() {
|
|
|
|
return this.context;
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
* Executes a JavaScript function in the context of the current browsing
|
|
|
|
* context, if in content space, or in chrome space otherwise, and returns
|
|
|
|
* the return value of the function.
|
2015-03-20 00:12:58 +03:00
|
|
|
*
|
2017-07-26 15:11:53 +03:00
|
|
|
* It is important to note that if the <var>sandboxName</var> parameter
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
* is left undefined, the script will be evaluated in a mutable sandbox,
|
|
|
|
* causing any change it makes on the global state of the document to have
|
|
|
|
* lasting side-effects.
|
|
|
|
*
|
|
|
|
* @param {string} script
|
|
|
|
* Script to evaluate as a function body.
|
|
|
|
* @param {Array.<(string|boolean|number|object|WebElement)>} args
|
2017-07-26 15:11:53 +03:00
|
|
|
* Arguments exposed to the script in <code>arguments</code>.
|
|
|
|
* The array items must be serialisable to the WebDriver protocol.
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
* @param {string=} sandbox
|
|
|
|
* Name of the sandbox to evaluate the script in. The sandbox is
|
|
|
|
* cached for later re-use on the same Window object if
|
2017-07-26 15:11:53 +03:00
|
|
|
* <var>newSandbox</var> is false. If he parameter is undefined,
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
* the script is evaluated in a mutable sandbox. If the parameter
|
|
|
|
* is "system", it will be evaluted in a sandbox with elevated system
|
|
|
|
* privileges, equivalent to chrome space.
|
|
|
|
* @param {boolean=} newSandbox
|
|
|
|
* Forces the script to be evaluated in a fresh sandbox. Note that if
|
|
|
|
* it is undefined, the script will normally be evaluted in a fresh
|
|
|
|
* sandbox.
|
|
|
|
* @param {string=} filename
|
|
|
|
* Filename of the client's program where this script is evaluated.
|
|
|
|
* @param {number=} line
|
|
|
|
* Line in the client's program where this script is evaluated.
|
|
|
|
*
|
|
|
|
* @return {(string|boolean|number|object|WebElement)}
|
|
|
|
* Return value from the script, or null which signifies either the
|
|
|
|
* JavaScript notion of null or undefined.
|
|
|
|
*
|
2017-07-26 15:11:53 +03:00
|
|
|
* @throws {ScriptTimeoutError}
|
2019-01-03 14:20:16 +03:00
|
|
|
* If the script was interrupted due to reaching the session's
|
|
|
|
* script timeout.
|
2017-07-26 15:11:53 +03:00
|
|
|
* @throws {JavaScriptError}
|
|
|
|
* If an {@link Error} was thrown whilst evaluating the script.
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
*/
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.executeScript = async function(cmd) {
|
2018-03-02 16:25:59 +03:00
|
|
|
let { script, args } = cmd.parameters;
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
let opts = {
|
2018-03-02 16:25:59 +03:00
|
|
|
script: cmd.parameters.script,
|
|
|
|
args: cmd.parameters.args,
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
sandboxName: cmd.parameters.sandbox,
|
2018-03-02 16:25:59 +03:00
|
|
|
newSandbox: cmd.parameters.newSandbox,
|
2017-07-26 15:11:53 +03:00
|
|
|
file: cmd.parameters.filename,
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
line: cmd.parameters.line,
|
|
|
|
};
|
2018-05-15 16:17:41 +03:00
|
|
|
|
|
|
|
return { value: await this.execute_(script, args, opts) };
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
--HG--
extra : rebase_source : 38cc7b1e374fd42afb213133fd1a5e11bf8bdd95
2016-02-26 17:36:39 +03:00
|
|
|
};
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2016-05-07 01:12:14 +03:00
|
|
|
/**
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
* Executes a JavaScript function in the context of the current browsing
|
|
|
|
* context, if in content space, or in chrome space otherwise, and returns
|
|
|
|
* the object passed to the callback.
|
|
|
|
*
|
2017-07-26 15:11:53 +03:00
|
|
|
* The callback is always the last argument to the <var>arguments</var>
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
* list passed to the function scope of the script. It can be retrieved
|
|
|
|
* as such:
|
|
|
|
*
|
2017-07-26 15:11:53 +03:00
|
|
|
* <pre><code>
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
* let callback = arguments[arguments.length - 1];
|
|
|
|
* callback("foo");
|
|
|
|
* // "foo" is returned
|
2017-07-26 15:11:53 +03:00
|
|
|
* </code></pre>
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
*
|
2017-07-26 15:11:53 +03:00
|
|
|
* It is important to note that if the <var>sandboxName</var> parameter
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
* is left undefined, the script will be evaluated in a mutable sandbox,
|
|
|
|
* causing any change it makes on the global state of the document to have
|
|
|
|
* lasting side-effects.
|
2016-05-07 01:12:14 +03:00
|
|
|
*
|
|
|
|
* @param {string} script
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
* Script to evaluate as a function body.
|
|
|
|
* @param {Array.<(string|boolean|number|object|WebElement)>} args
|
2017-07-26 15:11:53 +03:00
|
|
|
* Arguments exposed to the script in <code>arguments</code>.
|
|
|
|
* The array items must be serialisable to the WebDriver protocol.
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
* @param {string=} sandbox
|
|
|
|
* Name of the sandbox to evaluate the script in. The sandbox is
|
|
|
|
* cached for later re-use on the same Window object if
|
2017-07-26 15:11:53 +03:00
|
|
|
* <var>newSandbox</var> is false. If the parameter is undefined,
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
* the script is evaluated in a mutable sandbox. If the parameter
|
|
|
|
* is "system", it will be evaluted in a sandbox with elevated system
|
|
|
|
* privileges, equivalent to chrome space.
|
|
|
|
* @param {boolean=} newSandbox
|
|
|
|
* Forces the script to be evaluated in a fresh sandbox. Note that if
|
|
|
|
* it is undefined, the script will normally be evaluted in a fresh
|
|
|
|
* sandbox.
|
|
|
|
* @param {string=} filename
|
|
|
|
* Filename of the client's program where this script is evaluated.
|
|
|
|
* @param {number=} line
|
|
|
|
* Line in the client's program where this script is evaluated.
|
|
|
|
*
|
|
|
|
* @return {(string|boolean|number|object|WebElement)}
|
|
|
|
* Return value from the script, or null which signifies either the
|
|
|
|
* JavaScript notion of null or undefined.
|
|
|
|
*
|
2017-07-26 15:11:53 +03:00
|
|
|
* @throws {ScriptTimeoutError}
|
2019-01-03 14:20:16 +03:00
|
|
|
* If the script was interrupted due to reaching the session's
|
|
|
|
* script timeout.
|
2017-07-26 15:11:53 +03:00
|
|
|
* @throws {JavaScriptError}
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
* If an Error was thrown whilst evaluating the script.
|
|
|
|
*/
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.executeAsyncScript = async function(cmd) {
|
2018-03-02 16:25:59 +03:00
|
|
|
let { script, args } = cmd.parameters;
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
let opts = {
|
2018-03-02 16:25:59 +03:00
|
|
|
script: cmd.parameters.script,
|
|
|
|
args: cmd.parameters.args,
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
sandboxName: cmd.parameters.sandbox,
|
2018-03-02 16:25:59 +03:00
|
|
|
newSandbox: cmd.parameters.newSandbox,
|
2017-07-26 15:11:53 +03:00
|
|
|
file: cmd.parameters.filename,
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
line: cmd.parameters.line,
|
|
|
|
async: true,
|
|
|
|
};
|
2018-05-15 16:17:41 +03:00
|
|
|
|
|
|
|
return { value: await this.execute_(script, args, opts) };
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
};
|
2016-05-07 01:12:14 +03:00
|
|
|
|
2017-08-07 18:53:02 +03:00
|
|
|
GeckoDriver.prototype.execute_ = async function(
|
2018-03-02 16:25:59 +03:00
|
|
|
script,
|
|
|
|
args = [],
|
|
|
|
{
|
|
|
|
sandboxName = null,
|
|
|
|
newSandbox = false,
|
|
|
|
file = "",
|
|
|
|
line = 0,
|
|
|
|
async = false,
|
|
|
|
} = {}
|
|
|
|
) {
|
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2018-03-02 16:25:59 +03:00
|
|
|
|
|
|
|
assert.string(script, pprint`Expected "script" to be a string: ${script}`);
|
|
|
|
assert.array(args, pprint`Expected script args to be an array: ${args}`);
|
|
|
|
if (sandboxName !== null) {
|
|
|
|
assert.string(
|
|
|
|
sandboxName,
|
|
|
|
pprint`Expected sandbox name to be a string: ${sandboxName}`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
assert.boolean(
|
|
|
|
newSandbox,
|
|
|
|
pprint`Expected newSandbox to be boolean: ${newSandbox}`
|
|
|
|
);
|
|
|
|
assert.string(file, pprint`Expected file to be a string: ${file}`);
|
|
|
|
assert.number(line, pprint`Expected line to be a number: ${line}`);
|
|
|
|
|
|
|
|
let opts = {
|
2019-01-03 14:20:16 +03:00
|
|
|
timeout: this.timeouts.script,
|
2018-03-02 16:25:59 +03:00
|
|
|
sandboxName,
|
|
|
|
newSandbox,
|
|
|
|
file,
|
|
|
|
line,
|
|
|
|
async,
|
|
|
|
};
|
|
|
|
|
2017-08-07 18:53:02 +03:00
|
|
|
let res, els;
|
|
|
|
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
switch (this.context) {
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Content:
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
// evaluate in content with lasting side-effects
|
2018-03-02 16:25:59 +03:00
|
|
|
if (!sandboxName) {
|
|
|
|
res = await this.listener.execute(script, args, opts);
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
|
|
|
|
// evaluate in content with sandbox
|
2017-08-07 18:53:02 +03:00
|
|
|
} else {
|
2018-03-02 16:25:59 +03:00
|
|
|
res = await this.listener.executeInSandbox(script, args, opts);
|
2017-08-07 18:53:02 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
--HG--
extra : rebase_source : 38cc7b1e374fd42afb213133fd1a5e11bf8bdd95
2016-02-26 17:36:39 +03:00
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Chrome:
|
2018-03-02 16:25:59 +03:00
|
|
|
let sb = this.sandboxes.get(sandboxName, newSandbox);
|
2017-05-08 19:05:20 +03:00
|
|
|
let wargs = evaluate.fromJSON(args, this.curBrowser.seenEls, sb.window);
|
2017-08-07 18:53:02 +03:00
|
|
|
res = await evaluate.sandbox(sb, script, wargs, opts);
|
|
|
|
els = this.curBrowser.seenEls;
|
|
|
|
break;
|
2017-06-30 02:40:24 +03:00
|
|
|
|
|
|
|
default:
|
|
|
|
throw new TypeError(`Unknown context: ${this.context}`);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
2017-08-07 18:53:02 +03:00
|
|
|
|
|
|
|
return evaluate.toJSON(res, els);
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2015-05-04 13:25:03 +03:00
|
|
|
* Navigate to given URL.
|
2015-03-20 00:12:58 +03:00
|
|
|
*
|
2015-05-04 13:25:03 +03:00
|
|
|
* Navigates the current browsing context to the given URL and waits for
|
|
|
|
* the document to load or the session's page timeout duration to elapse
|
|
|
|
* before returning.
|
2015-03-20 00:12:58 +03:00
|
|
|
*
|
2015-05-04 13:25:03 +03:00
|
|
|
* The command will return with a failure if there is an error loading
|
|
|
|
* the document or the URL is blocked. This can occur if it fails to
|
|
|
|
* reach host, the URL is malformed, or if there is a certificate issue
|
|
|
|
* to name some examples.
|
2015-03-20 00:12:58 +03:00
|
|
|
*
|
2015-05-04 13:25:03 +03:00
|
|
|
* The document is considered successfully loaded when the
|
|
|
|
* DOMContentLoaded event on the frame element associated with the
|
|
|
|
* current window triggers and document.readyState is "complete".
|
2015-03-20 00:12:58 +03:00
|
|
|
*
|
2015-05-04 13:25:03 +03:00
|
|
|
* In chrome context it will change the current window's location to
|
|
|
|
* the supplied URL and wait until document.readyState equals "complete"
|
|
|
|
* or the page timeout duration has elapsed.
|
2015-03-20 00:12:58 +03:00
|
|
|
*
|
|
|
|
* @param {string} url
|
|
|
|
* URL to navigate to.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
|
|
|
* @throws {UnsupportedOperationError}
|
|
|
|
* Not available in current context.
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.get = async function(cmd) {
|
2016-12-29 16:04:16 +03:00
|
|
|
assert.content(this.context);
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2016-12-29 16:04:16 +03:00
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
let url = cmd.parameters.url;
|
|
|
|
|
2017-06-30 02:40:24 +03:00
|
|
|
let get = this.listener.get({ url, pageTimeout: this.timeouts.pageLoad });
|
2016-12-29 16:04:16 +03:00
|
|
|
|
2018-11-06 15:08:55 +03:00
|
|
|
// If a process change of the frame script interrupts our page load, this
|
|
|
|
// will never return. We need to re-issue this request to correctly poll for
|
2017-07-06 19:02:19 +03:00
|
|
|
// readyState and send errors.
|
2016-12-29 16:04:16 +03:00
|
|
|
this.curBrowser.pendingCommands.push(() => {
|
2017-03-09 13:21:30 +03:00
|
|
|
let parameters = {
|
|
|
|
// TODO(ato): Bug 1242595
|
2017-08-19 16:22:17 +03:00
|
|
|
commandID: this.listener.activeMessageId,
|
2017-03-09 13:21:30 +03:00
|
|
|
pageTimeout: this.timeouts.pageLoad,
|
|
|
|
startTime: new Date().getTime(),
|
|
|
|
};
|
2018-01-09 20:48:57 +03:00
|
|
|
this.curBrowser.messageManager.sendAsyncMessage(
|
|
|
|
"Marionette:waitForPageLoaded",
|
|
|
|
parameters
|
|
|
|
);
|
2016-12-29 16:04:16 +03:00
|
|
|
});
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-08-07 18:52:37 +03:00
|
|
|
await get;
|
2017-03-27 12:02:36 +03:00
|
|
|
|
|
|
|
this.curBrowser.contentBrowser.focus();
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a string representing the current URL.
|
|
|
|
*
|
|
|
|
* On Desktop this returns a string representation of the URL of the
|
|
|
|
* current top level browsing context. This is equivalent to
|
|
|
|
* document.location.href.
|
|
|
|
*
|
|
|
|
* When in the context of the chrome, this returns the canonical URL
|
|
|
|
* of the current resource.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2018-08-22 23:24:39 +03:00
|
|
|
GeckoDriver.prototype.getCurrentUrl = async function() {
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2017-06-13 16:46:59 +03:00
|
|
|
return this.currentURL.toString();
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
2017-04-20 20:00:46 +03:00
|
|
|
/**
|
|
|
|
* Gets the current title of the window.
|
|
|
|
*
|
|
|
|
* @return {string}
|
|
|
|
* Document title of the top-level browsing context.
|
|
|
|
*
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
|
|
|
*/
|
2018-08-22 23:24:39 +03:00
|
|
|
GeckoDriver.prototype.getTitle = async function() {
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2017-07-02 14:31:18 +03:00
|
|
|
return this.title;
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/** Gets the current type of the window. */
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.getWindowType = function() {
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2018-05-15 16:17:41 +03:00
|
|
|
return this.windowType;
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
2017-04-20 20:00:46 +03:00
|
|
|
/**
|
|
|
|
* Gets the page source of the content document.
|
|
|
|
*
|
|
|
|
* @return {string}
|
|
|
|
* String serialisation of the DOM of the current browsing context's
|
|
|
|
* active document.
|
|
|
|
*
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
|
|
|
*/
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.getPageSource = async function() {
|
2018-01-12 17:25:30 +03:00
|
|
|
const win = assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
switch (this.context) {
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Chrome:
|
2015-03-20 00:12:58 +03:00
|
|
|
let s = new win.XMLSerializer();
|
2018-05-15 16:17:41 +03:00
|
|
|
return s.serializeToString(win.document);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Content:
|
2018-05-15 16:17:41 +03:00
|
|
|
return this.listener.getPageSource();
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new TypeError(`Unknown context: ${this.context}`);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-03-06 16:14:21 +03:00
|
|
|
/**
|
|
|
|
* Cause the browser to traverse one step backward in the joint history
|
|
|
|
* of the current browsing context.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
|
|
|
* @throws {UnsupportedOperationError}
|
|
|
|
* Not available in current context.
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2017-03-06 16:14:21 +03:00
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.goBack = async function() {
|
2016-12-29 16:04:16 +03:00
|
|
|
assert.content(this.context);
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.curBrowser);
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2016-12-29 16:04:16 +03:00
|
|
|
|
2017-05-31 17:49:56 +03:00
|
|
|
// If there is no history, just return
|
2017-03-27 12:02:36 +03:00
|
|
|
if (!this.curBrowser.contentBrowser.webNavigation.canGoBack) {
|
2017-03-06 16:14:21 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-06-13 16:46:59 +03:00
|
|
|
let lastURL = this.currentURL;
|
2017-03-06 16:14:21 +03:00
|
|
|
let goBack = this.listener.goBack({ pageTimeout: this.timeouts.pageLoad });
|
|
|
|
|
2018-11-06 15:08:55 +03:00
|
|
|
// If a process change of the frame script interrupts our page load, this
|
|
|
|
// will never return. We need to re-issue this request to correctly poll for
|
2017-07-06 19:02:19 +03:00
|
|
|
// readyState and send errors.
|
2017-03-06 16:14:21 +03:00
|
|
|
this.curBrowser.pendingCommands.push(() => {
|
|
|
|
let parameters = {
|
|
|
|
// TODO(ato): Bug 1242595
|
2017-08-19 16:22:17 +03:00
|
|
|
commandID: this.listener.activeMessageId,
|
2017-06-13 16:46:59 +03:00
|
|
|
lastSeenURL: lastURL.toString(),
|
2017-03-06 16:14:21 +03:00
|
|
|
pageTimeout: this.timeouts.pageLoad,
|
|
|
|
startTime: new Date().getTime(),
|
|
|
|
};
|
2018-01-09 20:48:57 +03:00
|
|
|
this.curBrowser.messageManager.sendAsyncMessage(
|
|
|
|
"Marionette:waitForPageLoaded",
|
|
|
|
parameters
|
|
|
|
);
|
2017-03-06 16:14:21 +03:00
|
|
|
});
|
|
|
|
|
2017-08-07 18:52:37 +03:00
|
|
|
await goBack;
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
2017-03-06 16:14:21 +03:00
|
|
|
/**
|
|
|
|
* Cause the browser to traverse one step forward in the joint history
|
|
|
|
* of the current browsing context.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
|
|
|
* @throws {UnsupportedOperationError}
|
|
|
|
* Not available in current context.
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2017-03-06 16:14:21 +03:00
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.goForward = async function() {
|
2016-12-29 16:04:16 +03:00
|
|
|
assert.content(this.context);
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.curBrowser);
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2016-12-29 16:04:16 +03:00
|
|
|
|
2017-05-31 17:49:56 +03:00
|
|
|
// If there is no history, just return
|
2017-03-27 12:02:36 +03:00
|
|
|
if (!this.curBrowser.contentBrowser.webNavigation.canGoForward) {
|
2017-03-06 16:14:21 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-06-13 16:46:59 +03:00
|
|
|
let lastURL = this.currentURL;
|
2017-06-30 02:40:24 +03:00
|
|
|
let goForward = this.listener.goForward({
|
|
|
|
pageTimeout: this.timeouts.pageLoad,
|
|
|
|
});
|
2017-03-06 16:14:21 +03:00
|
|
|
|
2018-11-06 15:08:55 +03:00
|
|
|
// If a process change of the frame script interrupts our page load, this
|
|
|
|
// will never return. We need to re-issue this request to correctly poll for
|
2017-07-06 19:02:19 +03:00
|
|
|
// readyState and send errors.
|
2017-03-06 16:14:21 +03:00
|
|
|
this.curBrowser.pendingCommands.push(() => {
|
|
|
|
let parameters = {
|
|
|
|
// TODO(ato): Bug 1242595
|
2017-08-19 16:22:17 +03:00
|
|
|
commandID: this.listener.activeMessageId,
|
2017-06-13 16:46:59 +03:00
|
|
|
lastSeenURL: lastURL.toString(),
|
2017-03-06 16:14:21 +03:00
|
|
|
pageTimeout: this.timeouts.pageLoad,
|
|
|
|
startTime: new Date().getTime(),
|
|
|
|
};
|
2018-01-09 20:48:57 +03:00
|
|
|
this.curBrowser.messageManager.sendAsyncMessage(
|
|
|
|
"Marionette:waitForPageLoaded",
|
|
|
|
parameters
|
|
|
|
);
|
2017-03-06 16:14:21 +03:00
|
|
|
});
|
|
|
|
|
2017-08-07 18:52:37 +03:00
|
|
|
await goForward;
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
2017-03-27 17:16:36 +03:00
|
|
|
/**
|
2017-04-20 20:00:46 +03:00
|
|
|
* Causes the browser to reload the page in current top-level browsing
|
|
|
|
* context.
|
|
|
|
*
|
|
|
|
* @throws {UnsupportedOperationError}
|
|
|
|
* Not available in current context.
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2017-03-27 17:16:36 +03:00
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.refresh = async function() {
|
2016-12-29 16:04:16 +03:00
|
|
|
assert.content(this.context);
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2016-12-29 16:04:16 +03:00
|
|
|
|
2017-07-29 12:03:00 +03:00
|
|
|
let refresh = this.listener.refresh({ pageTimeout: this.timeouts.pageLoad });
|
2017-07-06 19:02:19 +03:00
|
|
|
|
2018-11-06 15:08:55 +03:00
|
|
|
// If a process change of the frame script interrupts our page load, this
|
|
|
|
// will never return. We need to re-issue this request to correctly poll for
|
2017-07-06 19:02:19 +03:00
|
|
|
// readyState and send errors.
|
|
|
|
this.curBrowser.pendingCommands.push(() => {
|
|
|
|
let parameters = {
|
|
|
|
// TODO(ato): Bug 1242595
|
2017-08-19 16:22:17 +03:00
|
|
|
commandID: this.listener.activeMessageId,
|
2017-07-06 19:02:19 +03:00
|
|
|
pageTimeout: this.timeouts.pageLoad,
|
|
|
|
startTime: new Date().getTime(),
|
|
|
|
};
|
2018-01-09 20:48:57 +03:00
|
|
|
this.curBrowser.messageManager.sendAsyncMessage(
|
|
|
|
"Marionette:waitForPageLoaded",
|
|
|
|
parameters
|
|
|
|
);
|
2017-07-06 19:02:19 +03:00
|
|
|
});
|
|
|
|
|
2017-08-07 18:52:37 +03:00
|
|
|
await refresh;
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Forces an update for the given browser's id.
|
|
|
|
*/
|
2017-06-30 02:40:24 +03:00
|
|
|
GeckoDriver.prototype.updateIdForBrowser = function(browser, newId) {
|
2015-03-20 00:12:58 +03:00
|
|
|
this._browserIds.set(browser.permanentKey, newId);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieves a listener id for the given xul browser element. In case
|
|
|
|
* the browser is not known, an attempt is made to retrieve the id from
|
|
|
|
* a CPOW, and null is returned if this fails.
|
|
|
|
*/
|
2017-06-30 02:40:24 +03:00
|
|
|
GeckoDriver.prototype.getIdForBrowser = function(browser) {
|
2015-03-20 00:12:58 +03:00
|
|
|
if (browser === null) {
|
|
|
|
return null;
|
|
|
|
}
|
2019-01-10 13:14:24 +03:00
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
let permKey = browser.permanentKey;
|
|
|
|
if (this._browserIds.has(permKey)) {
|
|
|
|
return this._browserIds.get(permKey);
|
|
|
|
}
|
|
|
|
|
|
|
|
let winId = browser.outerWindowID;
|
|
|
|
if (winId) {
|
|
|
|
this._browserIds.set(permKey, winId);
|
|
|
|
return winId;
|
|
|
|
}
|
|
|
|
return null;
|
2018-08-30 02:12:55 +03:00
|
|
|
};
|
2015-03-20 00:12:58 +03:00
|
|
|
|
|
|
|
/**
|
2017-01-10 18:36:49 +03:00
|
|
|
* Get the current window's handle. On desktop this typically corresponds
|
|
|
|
* to the currently selected tab.
|
|
|
|
*
|
|
|
|
* Return an opaque server-assigned identifier to this window that
|
|
|
|
* uniquely identifies it within this Marionette instance. This can
|
|
|
|
* be used to switch to this window at a later point.
|
|
|
|
*
|
|
|
|
* @return {string}
|
|
|
|
* Unique window handle.
|
2017-06-13 19:08:44 +03:00
|
|
|
*
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
2017-01-10 18:36:49 +03:00
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.getWindowHandle = function() {
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.curBrowser);
|
2017-01-10 18:36:49 +03:00
|
|
|
|
2017-06-13 19:08:44 +03:00
|
|
|
return this.curBrowser.curFrameId.toString();
|
2017-01-10 18:36:49 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a list of top-level browsing contexts. On desktop this typically
|
2017-06-30 02:40:24 +03:00
|
|
|
* corresponds to the set of open tabs for browser windows, or the window
|
|
|
|
* itself for non-browser chrome windows.
|
2015-03-20 00:12:58 +03:00
|
|
|
*
|
|
|
|
* Each window handle is assigned by the server and is guaranteed unique,
|
|
|
|
* however the return array does not have a specified ordering.
|
|
|
|
*
|
|
|
|
* @return {Array.<string>}
|
|
|
|
* Unique window handles.
|
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.getWindowHandles = function() {
|
2017-05-05 23:21:51 +03:00
|
|
|
return this.windowHandles.map(String);
|
2017-07-29 12:03:00 +03:00
|
|
|
};
|
2015-03-20 00:12:58 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the current window's handle. This corresponds to a window that
|
|
|
|
* may itself contain tabs.
|
|
|
|
*
|
|
|
|
* Return an opaque server-assigned identifier to this window that
|
|
|
|
* uniquely identifies it within this Marionette instance. This can
|
|
|
|
* be used to switch to this window at a later point.
|
|
|
|
*
|
|
|
|
* @return {string}
|
|
|
|
* Unique window handle.
|
2017-06-13 19:08:44 +03:00
|
|
|
*
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
2018-05-15 16:17:41 +03:00
|
|
|
* @throws {UnknownError}
|
|
|
|
* Internal browsing context reference not found
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.getChromeWindowHandle = function() {
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow(Context.Chrome));
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
for (let i in this.browsers) {
|
|
|
|
if (this.curBrowser == this.browsers[i]) {
|
2018-05-15 16:17:41 +03:00
|
|
|
return i;
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
}
|
2018-05-15 16:17:41 +03:00
|
|
|
|
|
|
|
throw new UnknownError("Invalid browsing context");
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns identifiers for each open chrome window for tests interested in
|
|
|
|
* managing a set of chrome windows and tabs separately.
|
|
|
|
*
|
|
|
|
* @return {Array.<string>}
|
|
|
|
* Unique window handles.
|
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.getChromeWindowHandles = function() {
|
2017-05-05 23:21:51 +03:00
|
|
|
return this.chromeWindowHandles.map(String);
|
2017-07-29 12:03:00 +03:00
|
|
|
};
|
2015-03-20 00:12:58 +03:00
|
|
|
|
|
|
|
/**
|
2017-03-24 16:54:37 +03:00
|
|
|
* Get the current position and size of the browser window currently in focus.
|
|
|
|
*
|
|
|
|
* Will return the current browser window size in pixels. Refers to
|
|
|
|
* window outerWidth and outerHeight values, which include scroll bars,
|
|
|
|
* title bars, etc.
|
2015-03-20 00:12:58 +03:00
|
|
|
*
|
|
|
|
* @return {Object.<string, number>}
|
2017-03-24 16:54:37 +03:00
|
|
|
* Object with |x| and |y| coordinates, and |width| and |height|
|
|
|
|
* of browser window.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2018-08-22 23:24:39 +03:00
|
|
|
GeckoDriver.prototype.getWindowRect = async function() {
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2018-05-15 16:17:41 +03:00
|
|
|
|
Bug 1364319 - Make setWindowRect deterministic; r=automatedtester,maja_zf
The Marionette setWindowRect command is meant to provide a blocking API
for resizing and moving the window. This currently has an intermittent
rate of ~0.254 with the WPT conformance test.
The main issue in providing a blocking API is that the DOM resize
event fires at a high rate and needs to be throttled. We are able to
throttle this successfully with requestAnimationFrame, and before that,
a hard-coded 60 fps setTimeout delay. To the naked eye, this appears to
synchronise window resizing before returning the resposne to the client.
However, occasionally the response contains the wrong window size.
window.outerWidth and window.outerHeight do not appear to be
deterministic as DOM properties are not synchronously populated.
Calls to ChromeWindow.outerWidth and ChromeWindow.outerHeight sometimes
also returns inconsistent values. Tests, document in the bug, have
shown that somtimes, the returned window size from the setWindowRect
command is correct, and that the size from the subsequent getWindowRect
command is not.
By using a combination of Services.tm.mainThread.idleDispatch and a
blocking promise on not returning a response until the window size has
changed, we are able to reduce the intermittent rate significantly (by
over an order of magnitude).
As one would expect, delaying computation allows DOM property values to
populate, and as such does not address the underlying problem or make it
inconceivable that the race described above could re-appear, but it does
seem to be more reliable than the current approach.
MozReview-Commit-ID: Lf2yeeXH082
--HG--
extra : rebase_source : e27912fdcb6edbf825bf3168f3542ff2b4551b8b
2017-06-21 19:13:51 +03:00
|
|
|
return this.curBrowser.rect;
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2017-04-20 20:00:46 +03:00
|
|
|
* Set the window position and size of the browser on the operating
|
|
|
|
* system window manager.
|
2015-03-20 00:12:58 +03:00
|
|
|
*
|
2018-10-11 19:10:28 +03:00
|
|
|
* The supplied `width` and `height` values refer to the window `outerWidth`
|
|
|
|
* and `outerHeight` values, which include browser chrome and OS-level
|
2017-03-24 17:35:38 +03:00
|
|
|
* window borders.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
2015-03-20 00:12:58 +03:00
|
|
|
* @param {number} x
|
|
|
|
* X coordinate of the top/left of the window that it will be
|
|
|
|
* moved to.
|
|
|
|
* @param {number} y
|
|
|
|
* Y coordinate of the top/left of the window that it will be
|
|
|
|
* moved to.
|
2017-04-20 20:00:46 +03:00
|
|
|
* @param {number} width
|
|
|
|
* Width to resize the window to.
|
|
|
|
* @param {number} height
|
|
|
|
* Height to resize the window to.
|
2016-10-06 15:11:31 +03:00
|
|
|
*
|
|
|
|
* @return {Object.<string, number>}
|
2018-10-11 19:10:28 +03:00
|
|
|
* Object with `x` and `y` coordinates and `width` and `height`
|
2017-04-20 20:00:46 +03:00
|
|
|
* dimensions.
|
2017-03-24 17:35:38 +03:00
|
|
|
*
|
2017-04-20 20:00:46 +03:00
|
|
|
* @throws {UnsupportedOperationError}
|
|
|
|
* Not applicable to application.
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.setWindowRect = async function(cmd) {
|
2017-07-29 12:03:00 +03:00
|
|
|
assert.firefox();
|
2018-01-12 17:25:30 +03:00
|
|
|
const win = assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-04-19 23:19:36 +03:00
|
|
|
let { x, y, width, height } = cmd.parameters;
|
Bug 1364319 - Make setWindowRect deterministic; r=automatedtester,maja_zf
The Marionette setWindowRect command is meant to provide a blocking API
for resizing and moving the window. This currently has an intermittent
rate of ~0.254 with the WPT conformance test.
The main issue in providing a blocking API is that the DOM resize
event fires at a high rate and needs to be throttled. We are able to
throttle this successfully with requestAnimationFrame, and before that,
a hard-coded 60 fps setTimeout delay. To the naked eye, this appears to
synchronise window resizing before returning the resposne to the client.
However, occasionally the response contains the wrong window size.
window.outerWidth and window.outerHeight do not appear to be
deterministic as DOM properties are not synchronously populated.
Calls to ChromeWindow.outerWidth and ChromeWindow.outerHeight sometimes
also returns inconsistent values. Tests, document in the bug, have
shown that somtimes, the returned window size from the setWindowRect
command is correct, and that the size from the subsequent getWindowRect
command is not.
By using a combination of Services.tm.mainThread.idleDispatch and a
blocking promise on not returning a response until the window size has
changed, we are able to reduce the intermittent rate significantly (by
over an order of magnitude).
As one would expect, delaying computation allows DOM property values to
populate, and as such does not address the underlying problem or make it
inconceivable that the race described above could re-appear, but it does
seem to be more reliable than the current approach.
MozReview-Commit-ID: Lf2yeeXH082
--HG--
extra : rebase_source : e27912fdcb6edbf825bf3168f3542ff2b4551b8b
2017-06-21 19:13:51 +03:00
|
|
|
|
2017-09-09 14:20:40 +03:00
|
|
|
switch (WindowState.from(win.windowState)) {
|
|
|
|
case WindowState.Fullscreen:
|
|
|
|
await exitFullscreen(win);
|
2017-08-18 18:01:24 +03:00
|
|
|
break;
|
|
|
|
|
2018-11-08 16:11:31 +03:00
|
|
|
case WindowState.Maximized:
|
2017-09-09 14:20:40 +03:00
|
|
|
case WindowState.Minimized:
|
2018-11-08 16:11:16 +03:00
|
|
|
await restoreWindow(win);
|
2017-08-18 18:01:24 +03:00
|
|
|
break;
|
Bug 1364319 - Make setWindowRect deterministic; r=automatedtester,maja_zf
The Marionette setWindowRect command is meant to provide a blocking API
for resizing and moving the window. This currently has an intermittent
rate of ~0.254 with the WPT conformance test.
The main issue in providing a blocking API is that the DOM resize
event fires at a high rate and needs to be throttled. We are able to
throttle this successfully with requestAnimationFrame, and before that,
a hard-coded 60 fps setTimeout delay. To the naked eye, this appears to
synchronise window resizing before returning the resposne to the client.
However, occasionally the response contains the wrong window size.
window.outerWidth and window.outerHeight do not appear to be
deterministic as DOM properties are not synchronously populated.
Calls to ChromeWindow.outerWidth and ChromeWindow.outerHeight sometimes
also returns inconsistent values. Tests, document in the bug, have
shown that somtimes, the returned window size from the setWindowRect
command is correct, and that the size from the subsequent getWindowRect
command is not.
By using a combination of Services.tm.mainThread.idleDispatch and a
blocking promise on not returning a response until the window size has
changed, we are able to reduce the intermittent rate significantly (by
over an order of magnitude).
As one would expect, delaying computation allows DOM property values to
populate, and as such does not address the underlying problem or make it
inconceivable that the race described above could re-appear, but it does
seem to be more reliable than the current approach.
MozReview-Commit-ID: Lf2yeeXH082
--HG--
extra : rebase_source : e27912fdcb6edbf825bf3168f3542ff2b4551b8b
2017-06-21 19:13:51 +03:00
|
|
|
}
|
|
|
|
|
2019-01-14 18:24:54 +03:00
|
|
|
if (width != null && height != null) {
|
2017-03-24 17:35:38 +03:00
|
|
|
assert.positiveInteger(height);
|
|
|
|
assert.positiveInteger(width);
|
2017-04-19 23:19:36 +03:00
|
|
|
|
2019-01-14 18:24:54 +03:00
|
|
|
if (win.outerWidth != width || win.outerHeight != height) {
|
2018-11-08 16:11:26 +03:00
|
|
|
win.resizeTo(width, height);
|
2018-11-08 16:11:28 +03:00
|
|
|
await new IdlePromise(win);
|
2019-01-14 18:24:54 +03:00
|
|
|
}
|
2017-03-24 17:35:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (x != null && y != null) {
|
|
|
|
assert.integer(x);
|
|
|
|
assert.integer(y);
|
2017-04-19 23:19:36 +03:00
|
|
|
|
2018-11-08 16:11:24 +03:00
|
|
|
if (win.screenX != x || win.screenY != y) {
|
|
|
|
win.moveTo(x, y);
|
2018-11-08 16:11:28 +03:00
|
|
|
await new IdlePromise(win);
|
2018-11-08 16:11:24 +03:00
|
|
|
}
|
2017-03-24 17:35:38 +03:00
|
|
|
}
|
2016-11-22 01:43:35 +03:00
|
|
|
|
Bug 1364319 - Make setWindowRect deterministic; r=automatedtester,maja_zf
The Marionette setWindowRect command is meant to provide a blocking API
for resizing and moving the window. This currently has an intermittent
rate of ~0.254 with the WPT conformance test.
The main issue in providing a blocking API is that the DOM resize
event fires at a high rate and needs to be throttled. We are able to
throttle this successfully with requestAnimationFrame, and before that,
a hard-coded 60 fps setTimeout delay. To the naked eye, this appears to
synchronise window resizing before returning the resposne to the client.
However, occasionally the response contains the wrong window size.
window.outerWidth and window.outerHeight do not appear to be
deterministic as DOM properties are not synchronously populated.
Calls to ChromeWindow.outerWidth and ChromeWindow.outerHeight sometimes
also returns inconsistent values. Tests, document in the bug, have
shown that somtimes, the returned window size from the setWindowRect
command is correct, and that the size from the subsequent getWindowRect
command is not.
By using a combination of Services.tm.mainThread.idleDispatch and a
blocking promise on not returning a response until the window size has
changed, we are able to reduce the intermittent rate significantly (by
over an order of magnitude).
As one would expect, delaying computation allows DOM property values to
populate, and as such does not address the underlying problem or make it
inconceivable that the race described above could re-appear, but it does
seem to be more reliable than the current approach.
MozReview-Commit-ID: Lf2yeeXH082
--HG--
extra : rebase_source : e27912fdcb6edbf825bf3168f3542ff2b4551b8b
2017-06-21 19:13:51 +03:00
|
|
|
return this.curBrowser.rect;
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2017-06-30 02:40:24 +03:00
|
|
|
* Switch current top-level browsing context by name or server-assigned
|
|
|
|
* ID. Searches for windows by name, then ID. Content windows take
|
|
|
|
* precedence.
|
2015-03-20 00:12:58 +03:00
|
|
|
*
|
|
|
|
* @param {string} name
|
|
|
|
* Target name or ID of the window to switch to.
|
2017-01-30 17:35:16 +03:00
|
|
|
* @param {boolean=} focus
|
|
|
|
* A boolean value which determines whether to focus
|
|
|
|
* the window. Defaults to true.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.switchToWindow = async function(cmd) {
|
2017-06-30 02:40:24 +03:00
|
|
|
let focus = true;
|
|
|
|
if (typeof cmd.parameters.focus != "undefined") {
|
|
|
|
focus = cmd.parameters.focus;
|
|
|
|
}
|
2017-05-09 11:25:26 +03:00
|
|
|
|
2017-06-30 02:40:24 +03:00
|
|
|
// Window IDs are internally handled as numbers, but here it could
|
|
|
|
// also be the name of the window.
|
2017-05-09 11:25:26 +03:00
|
|
|
let switchTo = parseInt(cmd.parameters.name);
|
|
|
|
if (isNaN(switchTo)) {
|
|
|
|
switchTo = cmd.parameters.name;
|
|
|
|
}
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-06-30 02:40:24 +03:00
|
|
|
let byNameOrId = function(win, windowId) {
|
2017-05-09 20:59:09 +03:00
|
|
|
return switchTo === win.name || switchTo === windowId;
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
2017-05-09 20:59:09 +03:00
|
|
|
let found = this.findWindow(this.windows, byNameOrId);
|
|
|
|
|
|
|
|
if (found) {
|
2017-08-07 18:52:37 +03:00
|
|
|
await this.setWindowHandle(found, focus);
|
2017-05-09 20:59:09 +03:00
|
|
|
} else {
|
|
|
|
throw new NoSuchWindowError(`Unable to locate window: ${switchTo}`);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find a specific window according to some filter function.
|
|
|
|
*
|
|
|
|
* @param {Iterable.<Window>} winIterable
|
|
|
|
* Iterable that emits Windows.
|
|
|
|
* @param {function(Window, number): boolean} filter
|
|
|
|
* A callback function taking two arguments; the window and
|
|
|
|
* the outerId of the window, and returning a boolean indicating
|
|
|
|
* whether the window is the target.
|
|
|
|
*
|
|
|
|
* @return {Object}
|
|
|
|
* A window handle object containing the window and some
|
|
|
|
* associated metadata.
|
|
|
|
*/
|
2017-06-30 02:40:24 +03:00
|
|
|
GeckoDriver.prototype.findWindow = function(winIterable, filter) {
|
2017-05-09 20:59:09 +03:00
|
|
|
for (let win of winIterable) {
|
2015-03-20 00:12:58 +03:00
|
|
|
let outerId = getOuterWindowId(win);
|
2017-02-05 17:55:10 +03:00
|
|
|
let tabBrowser = browser.getTabBrowser(win);
|
2017-06-30 02:40:24 +03:00
|
|
|
|
|
|
|
// In case the wanted window is a chrome window, we are done.
|
2017-05-09 20:59:09 +03:00
|
|
|
if (filter(win, outerId)) {
|
2017-06-30 02:40:24 +03:00
|
|
|
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.
|
2017-06-09 20:28:10 +03:00
|
|
|
} else if (tabBrowser && tabBrowser.tabs) {
|
2017-02-05 17:55:10 +03:00
|
|
|
for (let i = 0; i < tabBrowser.tabs.length; ++i) {
|
|
|
|
let contentBrowser = browser.getBrowserForTab(tabBrowser.tabs[i]);
|
2017-01-26 18:42:35 +03:00
|
|
|
let contentWindowId = this.getIdForBrowser(contentBrowser);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-05-09 20:59:09 +03:00
|
|
|
if (filter(win, contentWindowId)) {
|
|
|
|
return {
|
2017-06-30 02:40:24 +03:00
|
|
|
win,
|
|
|
|
outerId,
|
2017-02-05 17:55:10 +03:00
|
|
|
hasTabBrowser: true,
|
2015-03-20 00:12:58 +03:00
|
|
|
tabIndex: i,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-06-30 02:40:24 +03:00
|
|
|
|
2017-05-09 20:59:09 +03:00
|
|
|
return null;
|
|
|
|
};
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-05-09 20:59:09 +03:00
|
|
|
/**
|
|
|
|
* Switch the marionette window to a given window. If the browser in
|
2019-06-04 23:42:28 +03:00
|
|
|
* the window is unregistered, register that browser and wait for
|
2017-05-09 20:59:09 +03:00
|
|
|
* the registration is complete. If |focus| is true then set the focus
|
|
|
|
* on the window.
|
|
|
|
*
|
|
|
|
* @param {Object} winProperties
|
|
|
|
* Object containing window properties such as returned from
|
|
|
|
* GeckoDriver#findWindow
|
|
|
|
* @param {boolean=} focus
|
|
|
|
* A boolean value which determines whether to focus the window.
|
|
|
|
* Defaults to true.
|
|
|
|
*/
|
2017-08-07 18:52:37 +03:00
|
|
|
GeckoDriver.prototype.setWindowHandle = async function(
|
2017-06-30 02:40:24 +03:00
|
|
|
winProperties,
|
|
|
|
focus = true
|
|
|
|
) {
|
2017-05-09 20:59:09 +03:00
|
|
|
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;
|
|
|
|
|
|
|
|
if (winProperties.hasTabBrowser) {
|
|
|
|
registerBrowsers = this.registerPromise();
|
|
|
|
browserListening = this.listeningPromise();
|
|
|
|
}
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-05-09 20:59:09 +03:00
|
|
|
this.startBrowser(winProperties.win, false /* isNewSession */);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-05-09 20:59:09 +03:00
|
|
|
if (registerBrowsers && browserListening) {
|
2017-08-07 18:52:37 +03:00
|
|
|
await registerBrowsers;
|
|
|
|
await browserListening;
|
2017-05-09 20:59:09 +03:00
|
|
|
}
|
|
|
|
} else {
|
2018-06-07 09:51:27 +03:00
|
|
|
// Otherwise switch to the known chrome window
|
2017-05-09 20:59:09 +03:00
|
|
|
this.curBrowser = this.browsers[winProperties.outerId];
|
2018-06-07 09:51:27 +03:00
|
|
|
this.mainFrame = this.curBrowser.window;
|
|
|
|
this.curFrame = null;
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2018-06-07 09:51:27 +03:00
|
|
|
// .. and activate the tab if it's a content browser.
|
2017-05-09 20:59:09 +03:00
|
|
|
if ("tabIndex" in winProperties) {
|
2019-06-04 23:42:28 +03:00
|
|
|
await this.curBrowser.switchToTab(
|
2017-06-30 02:40:24 +03:00
|
|
|
winProperties.tabIndex,
|
|
|
|
winProperties.win,
|
|
|
|
focus
|
|
|
|
);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
}
|
2019-06-04 23:42:28 +03:00
|
|
|
|
|
|
|
if (focus) {
|
|
|
|
await this.curBrowser.focusWindow();
|
|
|
|
}
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.getActiveFrame = function() {
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2018-05-15 16:17:41 +03:00
|
|
|
let frame = null;
|
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
switch (this.context) {
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Chrome:
|
2015-03-20 00:12:58 +03:00
|
|
|
// no frame means top-level
|
2015-03-23 23:43:18 +03:00
|
|
|
if (this.curFrame) {
|
2018-05-15 16:17:41 +03:00
|
|
|
frame = this.curBrowser.seenEls.add(this.curFrame.frameElement);
|
2015-03-23 23:43:18 +03:00
|
|
|
}
|
2015-03-20 00:12:58 +03:00
|
|
|
break;
|
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Content:
|
2018-05-15 16:17:41 +03:00
|
|
|
frame = this.currentFrameElement;
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
2018-05-15 16:17:41 +03:00
|
|
|
|
|
|
|
return frame;
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
2017-04-20 20:00:46 +03:00
|
|
|
/**
|
|
|
|
* Set the current browsing context for future commands to the parent
|
|
|
|
* of the current browsing context.
|
|
|
|
*
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.switchToParentFrame = async function() {
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2017-08-07 18:52:37 +03:00
|
|
|
await this.listener.switchToParentFrame();
|
2015-10-19 23:39:48 +03:00
|
|
|
};
|
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
/**
|
|
|
|
* Switch to a given frame within the current window.
|
|
|
|
*
|
|
|
|
* @param {Object} element
|
|
|
|
* A web element reference to the element to switch to.
|
|
|
|
* @param {(string|number)} id
|
|
|
|
* If element is not defined, then this holds either the id, name,
|
|
|
|
* or index of the frame to switch to.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.switchToFrame = async function(cmd) {
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2017-10-21 21:30:27 +03:00
|
|
|
let { id, focus } = cmd.parameters;
|
|
|
|
|
|
|
|
// TODO(ato): element can be either string (deprecated) or a web
|
|
|
|
// element JSON Object. Can be removed with Firefox 60.
|
|
|
|
let byFrame;
|
|
|
|
if (typeof cmd.parameters.element == "string") {
|
|
|
|
byFrame = WebElement.fromUUID(cmd.parameters.element, Context.Chrome);
|
|
|
|
} else if (cmd.parameters.element) {
|
|
|
|
byFrame = WebElement.fromJSON(cmd.parameters.element);
|
|
|
|
}
|
2015-11-09 20:48:31 +03:00
|
|
|
|
2016-11-06 21:01:23 +03:00
|
|
|
const otherErrorsExpr = /about:.+(error)|(blocked)\?/;
|
|
|
|
const checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
let curWindow = this.getCurrentWindow();
|
|
|
|
|
|
|
|
let checkLoad = function() {
|
2016-11-06 21:01:23 +03:00
|
|
|
let win = this.getCurrentWindow();
|
|
|
|
if (win.document.readyState == "complete") {
|
2015-03-20 00:12:58 +03:00
|
|
|
return;
|
2016-11-06 21:01:23 +03:00
|
|
|
} else if (win.document.readyState == "interactive") {
|
2017-07-07 16:36:08 +03:00
|
|
|
let documentURI = win.document.documentURI;
|
|
|
|
if (documentURI.startsWith("about:certerror")) {
|
2016-11-06 21:01:23 +03:00
|
|
|
throw new InsecureCertificateError();
|
2017-07-07 16:36:08 +03:00
|
|
|
} else if (otherErrorsExpr.exec(documentURI)) {
|
|
|
|
throw new UnknownError("Reached error page: " + documentURI);
|
2016-11-06 21:01:23 +03:00
|
|
|
}
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
|
2017-06-30 02:40:24 +03:00
|
|
|
checkTimer.initWithCallback(
|
|
|
|
checkLoad.bind(this),
|
|
|
|
100,
|
|
|
|
Ci.nsITimer.TYPE_ONE_SHOT
|
|
|
|
);
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
if (this.context == Context.Chrome) {
|
2015-03-20 00:12:58 +03:00
|
|
|
let foundFrame = null;
|
2015-04-24 15:55:52 +03:00
|
|
|
|
2017-06-30 02:40:24 +03:00
|
|
|
// just focus
|
2017-10-21 21:30:27 +03:00
|
|
|
if (typeof id == "undefined" && !byFrame) {
|
2015-03-20 00:12:58 +03:00
|
|
|
this.curFrame = null;
|
2015-11-09 20:48:31 +03:00
|
|
|
if (focus) {
|
2015-03-20 00:12:58 +03:00
|
|
|
this.mainFrame.focus();
|
|
|
|
}
|
2017-06-30 02:40:24 +03:00
|
|
|
checkTimer.initWithCallback(
|
|
|
|
checkLoad.bind(this),
|
|
|
|
100,
|
|
|
|
Ci.nsITimer.TYPE_ONE_SHOT
|
|
|
|
);
|
2015-03-20 00:12:58 +03:00
|
|
|
return;
|
|
|
|
}
|
2015-04-24 15:55:52 +03:00
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
// by element (HTMLIFrameElement)
|
2017-10-21 21:30:27 +03:00
|
|
|
if (byFrame) {
|
|
|
|
let wantedFrame = this.curBrowser.seenEls.get(byFrame);
|
2017-10-05 19:57:17 +03:00
|
|
|
|
2016-05-20 17:07:21 +03:00
|
|
|
// Deal with an embedded xul:browser case
|
2017-06-30 02:40:24 +03:00
|
|
|
if (
|
|
|
|
wantedFrame.tagName == "xul:browser" ||
|
|
|
|
wantedFrame.tagName == "browser"
|
|
|
|
) {
|
2016-05-20 17:07:21 +03:00
|
|
|
curWindow = wantedFrame.contentWindow;
|
|
|
|
this.curFrame = curWindow;
|
|
|
|
if (focus) {
|
|
|
|
this.curFrame.focus();
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
2017-06-30 02:40:24 +03:00
|
|
|
checkTimer.initWithCallback(
|
|
|
|
checkLoad.bind(this),
|
|
|
|
100,
|
|
|
|
Ci.nsITimer.TYPE_ONE_SHOT
|
|
|
|
);
|
2016-05-20 17:07:21 +03:00
|
|
|
return;
|
|
|
|
}
|
2016-01-09 00:55:55 +03:00
|
|
|
|
2016-05-20 17:07:21 +03:00
|
|
|
// Check if the frame is XBL anonymous
|
|
|
|
let parent = curWindow.document.getBindingParent(wantedFrame);
|
2017-06-30 02:40:24 +03:00
|
|
|
// Shadow nodes also show up in getAnonymousNodes, we should
|
|
|
|
// ignore them.
|
|
|
|
if (
|
|
|
|
parent &&
|
|
|
|
!(parent.shadowRoot && parent.shadowRoot.contains(wantedFrame))
|
|
|
|
) {
|
|
|
|
const doc = curWindow.document;
|
|
|
|
let anonNodes = [...(doc.getAnonymousNodes(parent) || [])];
|
2016-05-20 17:07:21 +03:00
|
|
|
if (anonNodes.length > 0) {
|
|
|
|
let el = wantedFrame;
|
|
|
|
while (el) {
|
|
|
|
if (anonNodes.indexOf(el) > -1) {
|
|
|
|
curWindow = wantedFrame.contentWindow;
|
|
|
|
this.curFrame = curWindow;
|
|
|
|
if (focus) {
|
|
|
|
this.curFrame.focus();
|
2016-01-09 00:55:55 +03:00
|
|
|
}
|
2017-06-30 02:40:24 +03:00
|
|
|
checkTimer.initWithCallback(
|
|
|
|
checkLoad.bind(this),
|
|
|
|
100,
|
|
|
|
Ci.nsITimer.TYPE_ONE_SHOT
|
|
|
|
);
|
2016-05-20 17:07:21 +03:00
|
|
|
return;
|
2016-01-09 00:55:55 +03:00
|
|
|
}
|
2016-05-20 17:07:21 +03:00
|
|
|
el = el.parentNode;
|
2016-01-09 00:55:55 +03:00
|
|
|
}
|
|
|
|
}
|
2016-05-20 17:07:21 +03:00
|
|
|
}
|
2016-01-09 00:55:55 +03:00
|
|
|
|
2016-05-20 17:07:21 +03:00
|
|
|
// else, assume iframe
|
|
|
|
let frames = curWindow.document.getElementsByTagName("iframe");
|
|
|
|
let numFrames = frames.length;
|
|
|
|
for (let i = 0; i < numFrames; i++) {
|
2017-06-30 02:40:24 +03:00
|
|
|
let wrappedEl = new XPCNativeWrapper(frames[i]);
|
|
|
|
let wrappedWanted = new XPCNativeWrapper(wantedFrame);
|
|
|
|
if (wrappedEl == wrappedWanted) {
|
2016-05-20 17:07:21 +03:00
|
|
|
curWindow = frames[i].contentWindow;
|
|
|
|
this.curFrame = curWindow;
|
|
|
|
if (focus) {
|
|
|
|
this.curFrame.focus();
|
2015-11-09 20:48:31 +03:00
|
|
|
}
|
2017-06-30 02:40:24 +03:00
|
|
|
checkTimer.initWithCallback(
|
|
|
|
checkLoad.bind(this),
|
|
|
|
100,
|
|
|
|
Ci.nsITimer.TYPE_ONE_SHOT
|
|
|
|
);
|
2016-05-20 17:07:21 +03:00
|
|
|
return;
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-11-09 20:48:31 +03:00
|
|
|
|
|
|
|
switch (typeof id) {
|
2017-10-05 19:57:17 +03:00
|
|
|
case "string":
|
2015-11-09 20:48:31 +03:00
|
|
|
let foundById = null;
|
|
|
|
let frames = curWindow.document.getElementsByTagName("iframe");
|
|
|
|
let numFrames = frames.length;
|
|
|
|
for (let i = 0; i < numFrames; i++) {
|
2017-06-30 02:40:24 +03:00
|
|
|
// give precedence to name
|
2015-11-09 20:48:31 +03:00
|
|
|
let frame = frames[i];
|
|
|
|
if (frame.getAttribute("name") == id) {
|
|
|
|
foundFrame = i;
|
|
|
|
curWindow = frame.contentWindow;
|
|
|
|
break;
|
|
|
|
} else if (foundById === null && frame.id == id) {
|
|
|
|
foundById = i;
|
|
|
|
}
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
2015-11-09 20:48:31 +03:00
|
|
|
if (foundFrame === null && foundById !== null) {
|
|
|
|
foundFrame = foundById;
|
|
|
|
curWindow = frames[foundById].contentWindow;
|
|
|
|
}
|
|
|
|
break;
|
2017-10-05 19:57:17 +03:00
|
|
|
|
2015-11-09 20:48:31 +03:00
|
|
|
case "number":
|
|
|
|
if (typeof curWindow.frames[id] != "undefined") {
|
|
|
|
foundFrame = id;
|
2017-06-30 02:40:24 +03:00
|
|
|
let frameEl = curWindow.frames[foundFrame].frameElement;
|
|
|
|
curWindow = frameEl.contentWindow;
|
2015-11-09 20:48:31 +03:00
|
|
|
}
|
|
|
|
break;
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
2015-11-09 20:48:31 +03:00
|
|
|
|
2015-04-24 15:55:52 +03:00
|
|
|
if (foundFrame !== null) {
|
2015-03-20 00:12:58 +03:00
|
|
|
this.curFrame = curWindow;
|
2015-11-09 20:48:31 +03:00
|
|
|
if (focus) {
|
2015-03-20 00:12:58 +03:00
|
|
|
this.curFrame.focus();
|
|
|
|
}
|
2017-06-30 02:40:24 +03:00
|
|
|
checkTimer.initWithCallback(
|
|
|
|
checkLoad.bind(this),
|
|
|
|
100,
|
|
|
|
Ci.nsITimer.TYPE_ONE_SHOT
|
|
|
|
);
|
2015-03-20 00:12:58 +03:00
|
|
|
} else {
|
2015-11-09 20:48:31 +03:00
|
|
|
throw new NoSuchFrameError(`Unable to locate frame: ${id}`);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
2017-10-16 19:47:35 +03:00
|
|
|
} else if (this.context == Context.Content) {
|
2017-08-19 16:22:17 +03:00
|
|
|
cmd.commandID = cmd.id;
|
2017-12-06 00:08:48 +03:00
|
|
|
await this.listener.switchToFrame(cmd.parameters);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.getTimeouts = function() {
|
2016-12-31 15:27:13 +03:00
|
|
|
return this.timeouts;
|
2016-11-10 21:29:55 +03:00
|
|
|
};
|
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
/**
|
|
|
|
* Set timeout for page loading, searching, and scripts.
|
|
|
|
*
|
2016-09-14 20:10:22 +03:00
|
|
|
* @param {Object.<string, number>}
|
|
|
|
* Dictionary of timeout types and their new value, where all timeout
|
|
|
|
* types are optional.
|
|
|
|
*
|
|
|
|
* @throws {InvalidArgumentError}
|
|
|
|
* If timeout type key is unknown, or the value provided with it is
|
|
|
|
* not an integer.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.setTimeouts = function(cmd) {
|
2016-12-31 15:27:13 +03:00
|
|
|
// merge with existing timeouts
|
2017-06-22 12:42:55 +03:00
|
|
|
let merged = Object.assign(this.timeouts.toJSON(), cmd.parameters);
|
2018-06-26 19:15:28 +03:00
|
|
|
this.timeouts = Timeouts.fromJSON(merged);
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/** Single tap. */
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.singleTap = async function(cmd) {
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
let { id, x, y } = cmd.parameters;
|
2017-10-05 19:57:17 +03:00
|
|
|
let webEl = WebElement.fromUUID(id, this.context);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
|
|
|
switch (this.context) {
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Chrome:
|
2016-12-29 16:04:16 +03:00
|
|
|
throw new UnsupportedOperationError(
|
|
|
|
"Command 'singleTap' is not yet available in chrome context"
|
|
|
|
);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Content:
|
2017-10-05 19:57:17 +03:00
|
|
|
await this.listener.singleTap(webEl, x, y);
|
2015-03-20 00:12:58 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-12-14 02:29:48 +03:00
|
|
|
/**
|
|
|
|
* Perform a series of grouped actions at the specified points in time.
|
|
|
|
*
|
|
|
|
* @param {Array.<?>} actions
|
|
|
|
* Array of objects that each represent an action sequence.
|
|
|
|
*
|
|
|
|
* @throws {UnsupportedOperationError}
|
2017-04-20 20:00:46 +03:00
|
|
|
* Not yet available in current context.
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2016-12-14 02:29:48 +03:00
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.performActions = async function(cmd) {
|
2017-04-20 20:00:46 +03:00
|
|
|
assert.content(
|
|
|
|
this.context,
|
|
|
|
"Command 'performActions' is not yet available in chrome context"
|
|
|
|
);
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2017-04-20 20:00:46 +03:00
|
|
|
let actions = cmd.parameters.actions;
|
2017-08-07 18:52:37 +03:00
|
|
|
await this.listener.performActions({ actions });
|
2016-12-14 02:29:48 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Release all the keys and pointer buttons that are currently depressed.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
|
|
|
* @throws {UnsupportedOperationError}
|
|
|
|
* Not available in current context.
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2016-12-14 02:29:48 +03:00
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.releaseActions = async function() {
|
2017-04-20 20:00:46 +03:00
|
|
|
assert.content(this.context);
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2017-08-07 18:52:37 +03:00
|
|
|
await this.listener.releaseActions();
|
2016-12-14 02:29:48 +03:00
|
|
|
};
|
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
/**
|
|
|
|
* An action chain.
|
|
|
|
*
|
|
|
|
* @param {Object} value
|
|
|
|
* A nested array where the inner array represents each event,
|
|
|
|
* and the outer array represents a collection of events.
|
|
|
|
*
|
|
|
|
* @return {number}
|
|
|
|
* Last touch ID.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
|
|
|
* @throws {UnsupportedOperationError}
|
|
|
|
* Not applicable to application.
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.actionChain = async function(cmd) {
|
2018-01-12 17:25:30 +03:00
|
|
|
const win = assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2015-03-24 18:35:58 +03:00
|
|
|
let { chain, nextId } = cmd.parameters;
|
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
switch (this.context) {
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Chrome:
|
2016-12-29 16:04:16 +03:00
|
|
|
// be conservative until this has a use case and is established
|
|
|
|
// to work as expected in Fennec
|
2017-03-20 17:13:32 +03:00
|
|
|
assert.firefox();
|
2015-03-24 18:35:58 +03:00
|
|
|
|
2018-05-15 16:17:41 +03:00
|
|
|
return this.legacyactions.dispatchActions(
|
2016-05-20 18:49:19 +03:00
|
|
|
chain,
|
|
|
|
nextId,
|
|
|
|
{ frame: win },
|
|
|
|
this.curBrowser.seenEls
|
|
|
|
);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Content:
|
2018-05-15 16:17:41 +03:00
|
|
|
return this.listener.actionChain(chain, nextId);
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new TypeError(`Unknown context: ${this.context}`);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A multi-action chain.
|
|
|
|
*
|
|
|
|
* @param {Object} value
|
|
|
|
* A nested array where the inner array represents eache vent,
|
|
|
|
* the middle array represents a collection of events for each
|
|
|
|
* finger, and the outer array represents all fingers.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
|
|
|
* @throws {UnsupportedOperationError}
|
|
|
|
* Not available in current context.
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.multiAction = async function(cmd) {
|
2017-04-20 20:00:46 +03:00
|
|
|
assert.content(this.context);
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2017-10-26 13:47:01 +03:00
|
|
|
let { value, max_length } = cmd.parameters; // eslint-disable-line camelcase
|
2017-08-07 18:52:37 +03:00
|
|
|
await this.listener.multiAction(value, max_length);
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find an element using the indicated search strategy.
|
|
|
|
*
|
|
|
|
* @param {string} using
|
|
|
|
* Indicates which search method to use.
|
|
|
|
* @param {string} value
|
|
|
|
* Value the client is looking for.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.findElement = async function(cmd) {
|
2018-01-12 17:25:30 +03:00
|
|
|
const win = assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
let { using, value } = cmd.parameters;
|
|
|
|
let startNode;
|
|
|
|
if (typeof cmd.parameters.element != "undefined") {
|
|
|
|
startNode = WebElement.fromUUID(cmd.parameters.element, this.context);
|
|
|
|
}
|
|
|
|
|
2016-02-23 18:19:21 +03:00
|
|
|
let opts = {
|
2017-10-05 19:57:17 +03:00
|
|
|
startNode,
|
2016-12-31 15:27:13 +03:00
|
|
|
timeout: this.timeouts.implicit,
|
2016-02-23 18:19:21 +03:00
|
|
|
all: false,
|
|
|
|
};
|
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
switch (this.context) {
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Chrome:
|
2017-10-05 19:57:17 +03:00
|
|
|
if (!SUPPORTED_STRATEGIES.has(using)) {
|
|
|
|
throw new InvalidSelectorError(`Strategy not supported: ${using}`);
|
2016-05-20 15:28:27 +03:00
|
|
|
}
|
|
|
|
|
2017-03-20 17:13:32 +03:00
|
|
|
let container = { frame: win };
|
2016-05-20 15:28:27 +03:00
|
|
|
if (opts.startNode) {
|
2017-08-30 16:22:39 +03:00
|
|
|
opts.startNode = this.curBrowser.seenEls.get(opts.startNode);
|
2016-05-20 15:28:27 +03:00
|
|
|
}
|
2017-10-05 19:57:17 +03:00
|
|
|
let el = await element.find(container, using, value, opts);
|
2018-05-15 16:17:41 +03:00
|
|
|
return this.curBrowser.seenEls.add(el);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Content:
|
2018-05-15 16:17:41 +03:00
|
|
|
return this.listener.findElementContent(using, value, opts);
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new TypeError(`Unknown context: ${this.context}`);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find elements using the indicated search strategy.
|
|
|
|
*
|
|
|
|
* @param {string} using
|
|
|
|
* Indicates which search method to use.
|
|
|
|
* @param {string} value
|
|
|
|
* Value the client is looking for.
|
|
|
|
*/
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.findElements = async function(cmd) {
|
2018-01-12 17:25:30 +03:00
|
|
|
const win = assert.open(this.getCurrentWindow());
|
2018-10-31 01:08:24 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-10-05 19:57:17 +03:00
|
|
|
|
|
|
|
let { using, value } = cmd.parameters;
|
|
|
|
let startNode;
|
|
|
|
if (typeof cmd.parameters.element != "undefined") {
|
|
|
|
startNode = WebElement.fromUUID(cmd.parameters.element, this.context);
|
|
|
|
}
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2016-02-23 18:19:21 +03:00
|
|
|
let opts = {
|
2017-10-05 19:57:17 +03:00
|
|
|
startNode,
|
2016-12-31 15:27:13 +03:00
|
|
|
timeout: this.timeouts.implicit,
|
2016-02-23 18:19:21 +03:00
|
|
|
all: true,
|
|
|
|
};
|
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
switch (this.context) {
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Chrome:
|
2017-10-05 19:57:17 +03:00
|
|
|
if (!SUPPORTED_STRATEGIES.has(using)) {
|
|
|
|
throw new InvalidSelectorError(`Strategy not supported: ${using}`);
|
2016-05-20 15:28:27 +03:00
|
|
|
}
|
|
|
|
|
2017-03-20 17:13:32 +03:00
|
|
|
let container = { frame: win };
|
2017-10-05 19:57:17 +03:00
|
|
|
if (startNode) {
|
2017-08-30 16:22:39 +03:00
|
|
|
opts.startNode = this.curBrowser.seenEls.get(opts.startNode);
|
2016-05-20 15:28:27 +03:00
|
|
|
}
|
2017-10-05 19:57:17 +03:00
|
|
|
let els = await element.find(container, using, value, opts);
|
2018-05-15 16:17:41 +03:00
|
|
|
return this.curBrowser.seenEls.addAll(els);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Content:
|
2018-05-15 16:17:41 +03:00
|
|
|
return this.listener.findElementsContent(using, value, opts);
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new TypeError(`Unknown context: ${this.context}`);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-04-20 20:00:46 +03:00
|
|
|
/**
|
2017-11-24 19:23:02 +03:00
|
|
|
* Return the active element in the document.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
|
|
|
* @return {WebElement}
|
2017-11-24 19:23:02 +03:00
|
|
|
* Active element of the current browsing context's document
|
|
|
|
* element, if the document element is non-null.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
|
|
|
* @throws {UnsupportedOperationError}
|
|
|
|
* Not available in current context.
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2017-11-24 19:23:02 +03:00
|
|
|
* @throws {NoSuchElementError}
|
|
|
|
* If the document does not have an active element, i.e. if
|
|
|
|
* its document element has been deleted.
|
2017-04-20 20:00:46 +03:00
|
|
|
*/
|
2017-11-24 19:20:21 +03:00
|
|
|
GeckoDriver.prototype.getActiveElement = async function() {
|
2017-04-20 20:00:46 +03:00
|
|
|
assert.content(this.context);
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2017-11-24 19:20:21 +03:00
|
|
|
return this.listener.getActiveElement();
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Send click event to element.
|
|
|
|
*
|
|
|
|
* @param {string} id
|
|
|
|
* Reference ID to the element that will be clicked.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
2017-10-05 19:57:17 +03:00
|
|
|
* @throws {InvalidArgumentError}
|
|
|
|
* If element <var>id</var> is not a string.
|
|
|
|
* @throws {NoSuchElementError}
|
|
|
|
* If element represented by reference <var>id</var> is unknown.
|
2017-04-20 20:00:46 +03:00
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.clickElement = async function(cmd) {
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
let id = assert.string(cmd.parameters.id);
|
|
|
|
let webEl = WebElement.fromUUID(id, this.context);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
|
|
|
switch (this.context) {
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Chrome:
|
2017-10-05 19:57:17 +03:00
|
|
|
let el = this.curBrowser.seenEls.get(webEl);
|
2017-08-04 17:45:02 +03:00
|
|
|
await interaction.clickElement(el, this.a11yChecks);
|
2015-03-20 00:12:58 +03:00
|
|
|
break;
|
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Content:
|
2017-06-30 02:40:24 +03:00
|
|
|
let click = this.listener.clickElement({
|
2017-10-05 19:57:17 +03:00
|
|
|
webElRef: webEl.toJSON(),
|
|
|
|
pageTimeout: this.timeouts.pageLoad,
|
|
|
|
});
|
2017-04-20 13:12:27 +03:00
|
|
|
|
2018-11-06 15:08:55 +03:00
|
|
|
// If a process change 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.
|
2017-04-20 13:12:27 +03:00
|
|
|
this.curBrowser.pendingCommands.push(() => {
|
|
|
|
let parameters = {
|
|
|
|
// TODO(ato): Bug 1242595
|
2017-08-19 16:22:17 +03:00
|
|
|
commandID: this.listener.activeMessageId,
|
2017-04-20 13:12:27 +03:00
|
|
|
pageTimeout: this.timeouts.pageLoad,
|
|
|
|
startTime: new Date().getTime(),
|
|
|
|
};
|
2018-01-09 20:48:57 +03:00
|
|
|
this.curBrowser.messageManager.sendAsyncMessage(
|
|
|
|
"Marionette:waitForPageLoaded",
|
|
|
|
parameters
|
|
|
|
);
|
2017-04-20 13:12:27 +03:00
|
|
|
});
|
|
|
|
|
2017-08-04 17:45:02 +03:00
|
|
|
await click;
|
2015-03-20 00:12:58 +03:00
|
|
|
break;
|
2018-05-15 16:17:41 +03:00
|
|
|
|
|
|
|
default:
|
|
|
|
throw new TypeError(`Unknown context: ${this.context}`);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a given attribute of an element.
|
|
|
|
*
|
|
|
|
* @param {string} id
|
2016-05-13 16:42:05 +03:00
|
|
|
* Web element reference ID to the element that will be inspected.
|
2015-03-20 00:12:58 +03:00
|
|
|
* @param {string} name
|
2016-05-13 16:42:05 +03:00
|
|
|
* Name of the attribute which value to retrieve.
|
|
|
|
*
|
|
|
|
* @return {string}
|
|
|
|
* Value of the attribute.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
2017-10-05 19:57:17 +03:00
|
|
|
* @throws {InvalidArgumentError}
|
|
|
|
* If <var>id</var> or <var>name</var> are not strings.
|
|
|
|
* @throws {NoSuchElementError}
|
|
|
|
* If element represented by reference <var>id</var> is unknown.
|
2017-04-20 20:00:46 +03:00
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.getElementAttribute = async function(cmd) {
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
let id = assert.string(cmd.parameters.id);
|
|
|
|
let name = assert.string(cmd.parameters.name);
|
|
|
|
let webEl = WebElement.fromUUID(id, this.context);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
|
|
|
switch (this.context) {
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Chrome:
|
2017-10-05 19:57:17 +03:00
|
|
|
let el = this.curBrowser.seenEls.get(webEl);
|
2018-05-15 16:17:41 +03:00
|
|
|
return el.getAttribute(name);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Content:
|
2018-05-15 16:17:41 +03:00
|
|
|
return this.listener.getElementAttribute(webEl, name);
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new TypeError(`Unknown context: ${this.context}`);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-05-13 16:42:05 +03:00
|
|
|
/**
|
|
|
|
* Returns the value of a property associated with given element.
|
|
|
|
*
|
|
|
|
* @param {string} id
|
|
|
|
* Web element reference ID to the element that will be inspected.
|
|
|
|
* @param {string} name
|
|
|
|
* Name of the property which value to retrieve.
|
|
|
|
*
|
|
|
|
* @return {string}
|
|
|
|
* Value of the property.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
2017-10-05 19:57:17 +03:00
|
|
|
* @throws {InvalidArgumentError}
|
|
|
|
* If <var>id</var> or <var>name</var> are not strings.
|
|
|
|
* @throws {NoSuchElementError}
|
|
|
|
* If element represented by reference <var>id</var> is unknown.
|
2017-04-20 20:00:46 +03:00
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2016-05-13 16:42:05 +03:00
|
|
|
*/
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.getElementProperty = async function(cmd) {
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
let id = assert.string(cmd.parameters.id);
|
|
|
|
let name = assert.string(cmd.parameters.name);
|
|
|
|
let webEl = WebElement.fromUUID(id, this.context);
|
2016-05-13 16:42:05 +03:00
|
|
|
|
|
|
|
switch (this.context) {
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Chrome:
|
2017-10-05 19:57:17 +03:00
|
|
|
let el = this.curBrowser.seenEls.get(webEl);
|
2019-05-08 16:42:30 +03:00
|
|
|
return evaluate.toJSON(el[name], this.curBrowser.seenEls);
|
2016-05-13 16:42:05 +03:00
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Content:
|
2018-05-15 16:17:41 +03:00
|
|
|
return this.listener.getElementProperty(webEl, name);
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new TypeError(`Unknown context: ${this.context}`);
|
2016-05-13 16:42:05 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
/**
|
|
|
|
* Get the text of an element, if any. Includes the text of all child
|
|
|
|
* elements.
|
|
|
|
*
|
|
|
|
* @param {string} id
|
|
|
|
* Reference ID to the element that will be inspected.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
|
|
|
* @return {string}
|
|
|
|
* Element's text "as rendered".
|
|
|
|
*
|
2017-10-05 19:57:17 +03:00
|
|
|
* @throws {InvalidArgumentError}
|
|
|
|
* If <var>id</var> is not a string.
|
|
|
|
* @throws {NoSuchElementError}
|
|
|
|
* If element represented by reference <var>id</var> is unknown.
|
2017-04-20 20:00:46 +03:00
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.getElementText = async function(cmd) {
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
let id = assert.string(cmd.parameters.id);
|
|
|
|
let webEl = WebElement.fromUUID(id, this.context);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
|
|
|
switch (this.context) {
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Chrome:
|
2015-03-20 00:12:58 +03:00
|
|
|
// for chrome, we look at text nodes, and any node with a "label" field
|
2017-10-05 19:57:17 +03:00
|
|
|
let el = this.curBrowser.seenEls.get(webEl);
|
2015-03-20 00:12:58 +03:00
|
|
|
let lines = [];
|
|
|
|
this.getVisibleText(el, lines);
|
2018-05-15 16:17:41 +03:00
|
|
|
return lines.join("\n");
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Content:
|
2018-05-15 16:17:41 +03:00
|
|
|
return this.listener.getElementText(webEl);
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new TypeError(`Unknown context: ${this.context}`);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the tag name of the element.
|
|
|
|
*
|
|
|
|
* @param {string} id
|
|
|
|
* Reference ID to the element that will be inspected.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
|
|
|
* @return {string}
|
|
|
|
* Local tag name of element.
|
|
|
|
*
|
2017-10-05 19:57:17 +03:00
|
|
|
* @throws {InvalidArgumentError}
|
|
|
|
* If <var>id</var> is not a string.
|
|
|
|
* @throws {NoSuchElementError}
|
|
|
|
* If element represented by reference <var>id</var> is unknown.
|
2017-04-20 20:00:46 +03:00
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.getElementTagName = async function(cmd) {
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
let id = assert.string(cmd.parameters.id);
|
|
|
|
let webEl = WebElement.fromUUID(id, this.context);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
|
|
|
switch (this.context) {
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Chrome:
|
2017-10-05 19:57:17 +03:00
|
|
|
let el = this.curBrowser.seenEls.get(webEl);
|
2018-05-15 16:17:41 +03:00
|
|
|
return el.tagName.toLowerCase();
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Content:
|
2018-05-15 16:17:41 +03:00
|
|
|
return this.listener.getElementTagName(webEl);
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new TypeError(`Unknown context: ${this.context}`);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if element is displayed.
|
|
|
|
*
|
|
|
|
* @param {string} id
|
|
|
|
* Reference ID to the element that will be inspected.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
* True if displayed, false otherwise.
|
|
|
|
*
|
2017-10-05 19:57:17 +03:00
|
|
|
* @throws {InvalidArgumentError}
|
|
|
|
* If <var>id</var> is not a string.
|
|
|
|
* @throws {NoSuchElementError}
|
|
|
|
* If element represented by reference <var>id</var> is unknown.
|
2017-04-20 20:00:46 +03:00
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.isElementDisplayed = async function(cmd) {
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
let id = assert.string(cmd.parameters.id);
|
|
|
|
let webEl = WebElement.fromUUID(id, this.context);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
|
|
|
switch (this.context) {
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Chrome:
|
2017-10-05 19:57:17 +03:00
|
|
|
let el = this.curBrowser.seenEls.get(webEl);
|
2018-05-15 16:17:41 +03:00
|
|
|
return interaction.isElementDisplayed(el, this.a11yChecks);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Content:
|
2018-05-15 16:17:41 +03:00
|
|
|
return this.listener.isElementDisplayed(webEl);
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new TypeError(`Unknown context: ${this.context}`);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the property of the computed style of an element.
|
|
|
|
*
|
|
|
|
* @param {string} id
|
|
|
|
* Reference ID to the element that will be checked.
|
|
|
|
* @param {string} propertyName
|
|
|
|
* CSS rule that is being requested.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
|
|
|
* @return {string}
|
|
|
|
* Value of |propertyName|.
|
|
|
|
*
|
2017-10-05 19:57:17 +03:00
|
|
|
* @throws {InvalidArgumentError}
|
|
|
|
* If <var>id</var> or <var>propertyName</var> are not strings.
|
|
|
|
* @throws {NoSuchElementError}
|
|
|
|
* If element represented by reference <var>id</var> is unknown.
|
2017-04-20 20:00:46 +03:00
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.getElementValueOfCssProperty = async function(cmd) {
|
2018-01-12 17:25:30 +03:00
|
|
|
const win = assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
let id = assert.string(cmd.parameters.id);
|
|
|
|
let prop = assert.string(cmd.parameters.propertyName);
|
|
|
|
let webEl = WebElement.fromUUID(id, this.context);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
|
|
|
switch (this.context) {
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Chrome:
|
2017-10-05 19:57:17 +03:00
|
|
|
let el = this.curBrowser.seenEls.get(webEl);
|
2017-01-27 12:51:02 +03:00
|
|
|
let sty = win.document.defaultView.getComputedStyle(el);
|
2018-05-15 16:17:41 +03:00
|
|
|
return sty.getPropertyValue(prop);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Content:
|
2018-05-15 16:17:41 +03:00
|
|
|
return this.listener.getElementValueOfCssProperty(webEl, prop);
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new TypeError(`Unknown context: ${this.context}`);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if element is enabled.
|
|
|
|
*
|
|
|
|
* @param {string} id
|
|
|
|
* Reference ID to the element that will be checked.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
* True if enabled, false if disabled.
|
|
|
|
*
|
2017-10-05 19:57:17 +03:00
|
|
|
* @throws {InvalidArgumentError}
|
|
|
|
* If <var>id</var> is not a string.
|
|
|
|
* @throws {NoSuchElementError}
|
|
|
|
* If element represented by reference <var>id</var> is unknown.
|
2017-04-20 20:00:46 +03:00
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.isElementEnabled = async function(cmd) {
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
let id = assert.string(cmd.parameters.id);
|
|
|
|
let webEl = WebElement.fromUUID(id, this.context);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
|
|
|
switch (this.context) {
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Chrome:
|
2015-03-20 00:12:58 +03:00
|
|
|
// Selenium atom doesn't quite work here
|
2017-10-05 19:57:17 +03:00
|
|
|
let el = this.curBrowser.seenEls.get(webEl);
|
2018-05-15 16:17:41 +03:00
|
|
|
return interaction.isElementEnabled(el, this.a11yChecks);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Content:
|
2018-05-15 16:17:41 +03:00
|
|
|
return this.listener.isElementEnabled(webEl);
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new TypeError(`Unknown context: ${this.context}`);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
2017-04-20 20:00:46 +03:00
|
|
|
};
|
2015-03-20 00:12:58 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if element is selected.
|
|
|
|
*
|
|
|
|
* @param {string} id
|
|
|
|
* Reference ID to the element that will be checked.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
* True if selected, false if unselected.
|
|
|
|
*
|
2017-10-05 19:57:17 +03:00
|
|
|
* @throws {InvalidArgumentError}
|
|
|
|
* If <var>id</var> is not a string.
|
|
|
|
* @throws {NoSuchElementError}
|
|
|
|
* If element represented by reference <var>id</var> is unknown.
|
2017-04-20 20:00:46 +03:00
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.isElementSelected = async function(cmd) {
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
let id = assert.string(cmd.parameters.id);
|
|
|
|
let webEl = WebElement.fromUUID(id, this.context);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
|
|
|
switch (this.context) {
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Chrome:
|
2015-03-20 00:12:58 +03:00
|
|
|
// Selenium atom doesn't quite work here
|
2017-10-05 19:57:17 +03:00
|
|
|
let el = this.curBrowser.seenEls.get(webEl);
|
2018-05-15 16:17:41 +03:00
|
|
|
return interaction.isElementSelected(el, this.a11yChecks);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Content:
|
2018-05-15 16:17:41 +03:00
|
|
|
return this.listener.isElementSelected(webEl);
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new TypeError(`Unknown context: ${this.context}`);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-04-20 20:00:46 +03:00
|
|
|
/**
|
2017-10-05 19:57:17 +03:00
|
|
|
* @throws {InvalidArgumentError}
|
|
|
|
* If <var>id</var> is not a string.
|
|
|
|
* @throws {NoSuchElementError}
|
|
|
|
* If element represented by reference <var>id</var> is unknown.
|
2017-04-20 20:00:46 +03:00
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
|
|
|
*/
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.getElementRect = async function(cmd) {
|
2018-01-12 17:25:30 +03:00
|
|
|
const win = assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
let id = assert.string(cmd.parameters.id);
|
|
|
|
let webEl = WebElement.fromUUID(id, this.context);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
|
|
|
switch (this.context) {
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Chrome:
|
2017-10-05 19:57:17 +03:00
|
|
|
let el = this.curBrowser.seenEls.get(webEl);
|
2015-03-20 00:12:58 +03:00
|
|
|
let rect = el.getBoundingClientRect();
|
2018-05-15 16:17:41 +03:00
|
|
|
return {
|
2015-03-20 00:12:58 +03:00
|
|
|
x: rect.x + win.pageXOffset,
|
|
|
|
y: rect.y + win.pageYOffset,
|
|
|
|
width: rect.width,
|
2017-06-30 02:40:24 +03:00
|
|
|
height: rect.height,
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Content:
|
2018-05-15 16:17:41 +03:00
|
|
|
return this.listener.getElementRect(webEl);
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new TypeError(`Unknown context: ${this.context}`);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Send key presses to element after focusing on it.
|
|
|
|
*
|
|
|
|
* @param {string} id
|
|
|
|
* Reference ID to the element that will be checked.
|
2017-10-05 19:57:17 +03:00
|
|
|
* @param {string} text
|
2015-03-20 00:12:58 +03:00
|
|
|
* Value to send to the element.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
2017-10-05 19:57:17 +03:00
|
|
|
* @throws {InvalidArgumentError}
|
2018-11-03 19:31:00 +03:00
|
|
|
* If `id` or `text` are not strings.
|
2017-10-05 19:57:17 +03:00
|
|
|
* @throws {NoSuchElementError}
|
2018-11-03 19:31:00 +03:00
|
|
|
* If element represented by reference `id` is unknown.
|
2017-04-20 20:00:46 +03:00
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.sendKeysToElement = async function(cmd) {
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-04-20 20:00:46 +03:00
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
let id = assert.string(cmd.parameters.id);
|
|
|
|
let text = assert.string(cmd.parameters.text);
|
|
|
|
let webEl = WebElement.fromUUID(id, this.context);
|
2015-04-02 17:16:00 +03:00
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
switch (this.context) {
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Chrome:
|
2017-10-05 19:57:17 +03:00
|
|
|
let el = this.curBrowser.seenEls.get(webEl);
|
2018-11-03 19:31:00 +03:00
|
|
|
await interaction.sendKeysToElement(el, text, {
|
|
|
|
accessibilityChecks: this.a11yChecks,
|
|
|
|
});
|
2015-03-20 00:12:58 +03:00
|
|
|
break;
|
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Content:
|
2017-10-05 19:57:17 +03:00
|
|
|
await this.listener.sendKeysToElement(webEl, text);
|
2015-03-20 00:12:58 +03:00
|
|
|
break;
|
2018-05-15 16:17:41 +03:00
|
|
|
|
|
|
|
default:
|
|
|
|
throw new TypeError(`Unknown context: ${this.context}`);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clear the text of an element.
|
|
|
|
*
|
|
|
|
* @param {string} id
|
|
|
|
* Reference ID to the element that will be cleared.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
2017-10-05 19:57:17 +03:00
|
|
|
* @throws {InvalidArgumentError}
|
|
|
|
* If <var>id</var> is not a string.
|
|
|
|
* @throws {NoSuchElementError}
|
|
|
|
* If element represented by reference <var>id</var> is unknown.
|
2017-04-20 20:00:46 +03:00
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.clearElement = async function(cmd) {
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
let id = assert.string(cmd.parameters.id);
|
|
|
|
let webEl = WebElement.fromUUID(id, this.context);
|
2015-03-20 00:12:58 +03:00
|
|
|
|
|
|
|
switch (this.context) {
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Chrome:
|
2015-03-20 00:12:58 +03:00
|
|
|
// the selenium atom doesn't work here
|
2017-10-05 19:57:17 +03:00
|
|
|
let el = this.curBrowser.seenEls.get(webEl);
|
2015-03-20 00:12:58 +03:00
|
|
|
if (el.nodeName == "textbox") {
|
|
|
|
el.value = "";
|
|
|
|
} else if (el.nodeName == "checkbox") {
|
|
|
|
el.checked = false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Content:
|
2017-10-05 19:57:17 +03:00
|
|
|
await this.listener.clearElement(webEl);
|
2015-03-20 00:12:58 +03:00
|
|
|
break;
|
2018-05-15 16:17:41 +03:00
|
|
|
|
|
|
|
default:
|
|
|
|
throw new TypeError(`Unknown context: ${this.context}`);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-08-28 23:43:54 +03:00
|
|
|
/**
|
|
|
|
* Switch to shadow root of the given host element.
|
|
|
|
*
|
2018-04-28 02:12:35 +03:00
|
|
|
* @param {string=} id
|
2017-10-05 19:57:17 +03:00
|
|
|
* Reference ID to the element.
|
|
|
|
*
|
|
|
|
* @throws {InvalidArgumentError}
|
|
|
|
* If <var>id</var> is not a string.
|
|
|
|
* @throws {NoSuchElementError}
|
|
|
|
* If element represented by reference <var>id</var> is unknown.
|
2015-08-28 23:43:54 +03:00
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.switchToShadowRoot = async function(cmd) {
|
2017-04-20 20:00:46 +03:00
|
|
|
assert.content(this.context);
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2016-12-29 16:04:16 +03:00
|
|
|
|
2018-04-28 02:12:35 +03:00
|
|
|
let id = cmd.parameters.id;
|
|
|
|
let webEl = null;
|
|
|
|
if (id != null) {
|
|
|
|
assert.string(id);
|
|
|
|
webEl = WebElement.fromUUID(id, this.context);
|
|
|
|
}
|
2017-10-05 19:57:17 +03:00
|
|
|
await this.listener.switchToShadowRoot(webEl);
|
2015-08-28 23:43:54 +03:00
|
|
|
};
|
|
|
|
|
2017-04-20 20:00:46 +03:00
|
|
|
/**
|
Bug 1371733 - Move cookie service to chrome space; r=whimboo
The cookie service relies on the current document's
domain which is accessible from chrome space through
this.curBrowser.contentBrowser.contentURI.host.
As it is implemented currently, Marionette's cookie service jumps
between chrome- and content space more than necessary. This incurs
significant serialisation and IPC overhead, considering that the domain,
hostname, and current path information is readily available in chrome
space.
This patch removes all cookie-related functionality from
testing/marionette/listener.js, and implements a pure chrome-only
version of the service. It does, however, not try to fix conformance
issues with the WebDriver specification, of which there are many.
Some of the algorithms, especially to do with iteration over cookies,
implemented in cookie.iter, is also highly suboptimal. I have not
fundamentally changed any algorithms, and so my recommendation is to
address this later when addressing potential conformance bugs.
MozReview-Commit-ID: Fgs8ocbDJxb
--HG--
extra : rebase_source : 16470d5341459e40b1ceed12728451d517bbc490
2017-06-12 20:05:22 +03:00
|
|
|
* Add a single cookie to the cookie store associated with the active
|
|
|
|
* document's address.
|
|
|
|
*
|
|
|
|
* @param {Map.<string, (string|number|boolean)> cookie
|
|
|
|
* Cookie object.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
|
|
|
* @throws {UnsupportedOperationError}
|
|
|
|
* Not available in current context.
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
Bug 1371733 - Move cookie service to chrome space; r=whimboo
The cookie service relies on the current document's
domain which is accessible from chrome space through
this.curBrowser.contentBrowser.contentURI.host.
As it is implemented currently, Marionette's cookie service jumps
between chrome- and content space more than necessary. This incurs
significant serialisation and IPC overhead, considering that the domain,
hostname, and current path information is readily available in chrome
space.
This patch removes all cookie-related functionality from
testing/marionette/listener.js, and implements a pure chrome-only
version of the service. It does, however, not try to fix conformance
issues with the WebDriver specification, of which there are many.
Some of the algorithms, especially to do with iteration over cookies,
implemented in cookie.iter, is also highly suboptimal. I have not
fundamentally changed any algorithms, and so my recommendation is to
address this later when addressing potential conformance bugs.
MozReview-Commit-ID: Fgs8ocbDJxb
--HG--
extra : rebase_source : 16470d5341459e40b1ceed12728451d517bbc490
2017-06-12 20:05:22 +03:00
|
|
|
* @throws {InvalidCookieDomainError}
|
2017-07-26 15:11:53 +03:00
|
|
|
* If <var>cookie</var> is for a different domain than the active
|
|
|
|
* document's host.
|
2017-04-20 20:00:46 +03:00
|
|
|
*/
|
2018-08-22 23:24:39 +03:00
|
|
|
GeckoDriver.prototype.addCookie = async function(cmd) {
|
2017-04-20 20:00:46 +03:00
|
|
|
assert.content(this.context);
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2016-12-29 16:04:16 +03:00
|
|
|
|
Bug 1371733 - Move cookie service to chrome space; r=whimboo
The cookie service relies on the current document's
domain which is accessible from chrome space through
this.curBrowser.contentBrowser.contentURI.host.
As it is implemented currently, Marionette's cookie service jumps
between chrome- and content space more than necessary. This incurs
significant serialisation and IPC overhead, considering that the domain,
hostname, and current path information is readily available in chrome
space.
This patch removes all cookie-related functionality from
testing/marionette/listener.js, and implements a pure chrome-only
version of the service. It does, however, not try to fix conformance
issues with the WebDriver specification, of which there are many.
Some of the algorithms, especially to do with iteration over cookies,
implemented in cookie.iter, is also highly suboptimal. I have not
fundamentally changed any algorithms, and so my recommendation is to
address this later when addressing potential conformance bugs.
MozReview-Commit-ID: Fgs8ocbDJxb
--HG--
extra : rebase_source : 16470d5341459e40b1ceed12728451d517bbc490
2017-06-12 20:05:22 +03:00
|
|
|
let { protocol, hostname } = this.currentURL;
|
|
|
|
|
|
|
|
const networkSchemes = ["ftp:", "http:", "https:"];
|
|
|
|
if (!networkSchemes.includes(protocol)) {
|
|
|
|
throw new InvalidCookieDomainError("Document is cookie-averse");
|
|
|
|
}
|
2016-12-29 16:04:16 +03:00
|
|
|
|
Bug 1371733 - Move cookie service to chrome space; r=whimboo
The cookie service relies on the current document's
domain which is accessible from chrome space through
this.curBrowser.contentBrowser.contentURI.host.
As it is implemented currently, Marionette's cookie service jumps
between chrome- and content space more than necessary. This incurs
significant serialisation and IPC overhead, considering that the domain,
hostname, and current path information is readily available in chrome
space.
This patch removes all cookie-related functionality from
testing/marionette/listener.js, and implements a pure chrome-only
version of the service. It does, however, not try to fix conformance
issues with the WebDriver specification, of which there are many.
Some of the algorithms, especially to do with iteration over cookies,
implemented in cookie.iter, is also highly suboptimal. I have not
fundamentally changed any algorithms, and so my recommendation is to
address this later when addressing potential conformance bugs.
MozReview-Commit-ID: Fgs8ocbDJxb
--HG--
extra : rebase_source : 16470d5341459e40b1ceed12728451d517bbc490
2017-06-12 20:05:22 +03:00
|
|
|
let newCookie = cookie.fromJSON(cmd.parameters.cookie);
|
|
|
|
|
|
|
|
cookie.add(newCookie, { restrictToHost: hostname });
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get all the cookies for the current domain.
|
|
|
|
*
|
2017-07-26 15:11:53 +03:00
|
|
|
* This is the equivalent of calling <code>document.cookie</code> and
|
|
|
|
* parsing the result.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
|
|
|
* @throws {UnsupportedOperationError}
|
|
|
|
* Not available in current context.
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2018-08-22 23:24:39 +03:00
|
|
|
GeckoDriver.prototype.getCookies = async function() {
|
2017-04-20 20:00:46 +03:00
|
|
|
assert.content(this.context);
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2016-12-29 16:04:16 +03:00
|
|
|
|
Bug 1371733 - Move cookie service to chrome space; r=whimboo
The cookie service relies on the current document's
domain which is accessible from chrome space through
this.curBrowser.contentBrowser.contentURI.host.
As it is implemented currently, Marionette's cookie service jumps
between chrome- and content space more than necessary. This incurs
significant serialisation and IPC overhead, considering that the domain,
hostname, and current path information is readily available in chrome
space.
This patch removes all cookie-related functionality from
testing/marionette/listener.js, and implements a pure chrome-only
version of the service. It does, however, not try to fix conformance
issues with the WebDriver specification, of which there are many.
Some of the algorithms, especially to do with iteration over cookies,
implemented in cookie.iter, is also highly suboptimal. I have not
fundamentally changed any algorithms, and so my recommendation is to
address this later when addressing potential conformance bugs.
MozReview-Commit-ID: Fgs8ocbDJxb
--HG--
extra : rebase_source : 16470d5341459e40b1ceed12728451d517bbc490
2017-06-12 20:05:22 +03:00
|
|
|
let { hostname, pathname } = this.currentURL;
|
2018-05-15 16:17:41 +03:00
|
|
|
return [...cookie.iter(hostname, pathname)];
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
2017-04-20 20:00:46 +03:00
|
|
|
/**
|
|
|
|
* Delete all cookies that are visible to a document.
|
|
|
|
*
|
|
|
|
* @throws {UnsupportedOperationError}
|
|
|
|
* Not available in current context.
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
|
|
|
*/
|
2018-08-22 23:24:39 +03:00
|
|
|
GeckoDriver.prototype.deleteAllCookies = async function() {
|
2017-04-20 20:00:46 +03:00
|
|
|
assert.content(this.context);
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2016-12-29 16:04:16 +03:00
|
|
|
|
Bug 1371733 - Move cookie service to chrome space; r=whimboo
The cookie service relies on the current document's
domain which is accessible from chrome space through
this.curBrowser.contentBrowser.contentURI.host.
As it is implemented currently, Marionette's cookie service jumps
between chrome- and content space more than necessary. This incurs
significant serialisation and IPC overhead, considering that the domain,
hostname, and current path information is readily available in chrome
space.
This patch removes all cookie-related functionality from
testing/marionette/listener.js, and implements a pure chrome-only
version of the service. It does, however, not try to fix conformance
issues with the WebDriver specification, of which there are many.
Some of the algorithms, especially to do with iteration over cookies,
implemented in cookie.iter, is also highly suboptimal. I have not
fundamentally changed any algorithms, and so my recommendation is to
address this later when addressing potential conformance bugs.
MozReview-Commit-ID: Fgs8ocbDJxb
--HG--
extra : rebase_source : 16470d5341459e40b1ceed12728451d517bbc490
2017-06-12 20:05:22 +03:00
|
|
|
let { hostname, pathname } = this.currentURL;
|
|
|
|
for (let toDelete of cookie.iter(hostname, pathname)) {
|
|
|
|
cookie.remove(toDelete);
|
|
|
|
}
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
2017-04-20 20:00:46 +03:00
|
|
|
/**
|
|
|
|
* Delete a cookie by name.
|
|
|
|
*
|
|
|
|
* @throws {UnsupportedOperationError}
|
|
|
|
* Not available in current context.
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
|
|
|
*/
|
2018-08-22 23:24:39 +03:00
|
|
|
GeckoDriver.prototype.deleteCookie = async function(cmd) {
|
2017-04-20 20:00:46 +03:00
|
|
|
assert.content(this.context);
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2016-12-29 16:04:16 +03:00
|
|
|
|
Bug 1371733 - Move cookie service to chrome space; r=whimboo
The cookie service relies on the current document's
domain which is accessible from chrome space through
this.curBrowser.contentBrowser.contentURI.host.
As it is implemented currently, Marionette's cookie service jumps
between chrome- and content space more than necessary. This incurs
significant serialisation and IPC overhead, considering that the domain,
hostname, and current path information is readily available in chrome
space.
This patch removes all cookie-related functionality from
testing/marionette/listener.js, and implements a pure chrome-only
version of the service. It does, however, not try to fix conformance
issues with the WebDriver specification, of which there are many.
Some of the algorithms, especially to do with iteration over cookies,
implemented in cookie.iter, is also highly suboptimal. I have not
fundamentally changed any algorithms, and so my recommendation is to
address this later when addressing potential conformance bugs.
MozReview-Commit-ID: Fgs8ocbDJxb
--HG--
extra : rebase_source : 16470d5341459e40b1ceed12728451d517bbc490
2017-06-12 20:05:22 +03:00
|
|
|
let { hostname, pathname } = this.currentURL;
|
2017-10-03 18:25:57 +03:00
|
|
|
let name = assert.string(cmd.parameters.name);
|
|
|
|
for (let c of cookie.iter(hostname, pathname)) {
|
|
|
|
if (c.name === name) {
|
|
|
|
cookie.remove(c);
|
Bug 1371733 - Move cookie service to chrome space; r=whimboo
The cookie service relies on the current document's
domain which is accessible from chrome space through
this.curBrowser.contentBrowser.contentURI.host.
As it is implemented currently, Marionette's cookie service jumps
between chrome- and content space more than necessary. This incurs
significant serialisation and IPC overhead, considering that the domain,
hostname, and current path information is readily available in chrome
space.
This patch removes all cookie-related functionality from
testing/marionette/listener.js, and implements a pure chrome-only
version of the service. It does, however, not try to fix conformance
issues with the WebDriver specification, of which there are many.
Some of the algorithms, especially to do with iteration over cookies,
implemented in cookie.iter, is also highly suboptimal. I have not
fundamentally changed any algorithms, and so my recommendation is to
address this later when addressing potential conformance bugs.
MozReview-Commit-ID: Fgs8ocbDJxb
--HG--
extra : rebase_source : 16470d5341459e40b1ceed12728451d517bbc490
2017-06-12 20:05:22 +03:00
|
|
|
}
|
|
|
|
}
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
2019-01-10 13:14:24 +03:00
|
|
|
/**
|
|
|
|
* 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 };
|
|
|
|
};
|
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
/**
|
2017-01-10 18:36:49 +03:00
|
|
|
* Close the currently selected tab/window.
|
|
|
|
*
|
2017-04-20 20:00:46 +03:00
|
|
|
* With multiple open tabs present the currently selected tab will
|
|
|
|
* be closed. Otherwise the window itself will be closed. If it is the
|
|
|
|
* last window currently open, the window will not be closed to prevent
|
|
|
|
* a shutdown of the application. Instead the returned list of window
|
|
|
|
* handles is empty.
|
2015-03-20 00:12:58 +03:00
|
|
|
*
|
2017-01-10 18:36:49 +03:00
|
|
|
* @return {Array.<string>}
|
|
|
|
* Unique window handles of remaining windows.
|
2017-04-20 20:00:46 +03:00
|
|
|
*
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.close = async function() {
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow(Context.Content));
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
let nwins = 0;
|
2017-03-01 14:49:21 +03:00
|
|
|
|
2017-05-09 19:49:09 +03:00
|
|
|
for (let win of this.windows) {
|
|
|
|
// For browser windows count the tabs. Otherwise take the window itself.
|
2017-01-26 18:42:35 +03:00
|
|
|
let tabbrowser = browser.getTabBrowser(win);
|
2017-06-09 20:28:10 +03:00
|
|
|
if (tabbrowser && tabbrowser.tabs) {
|
2017-01-26 18:42:35 +03:00
|
|
|
nwins += tabbrowser.tabs.length;
|
2017-06-09 20:28:10 +03:00
|
|
|
} else {
|
|
|
|
nwins += 1;
|
2015-03-23 23:43:18 +03:00
|
|
|
}
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
|
2017-06-30 02:40:24 +03:00
|
|
|
// If there is only one window left, do not close it. Instead return
|
|
|
|
// a faked empty array of window handles. This will instruct geckodriver
|
|
|
|
// to terminate the application.
|
2017-06-13 19:08:44 +03:00
|
|
|
if (nwins === 1) {
|
2017-01-10 18:36:49 +03:00
|
|
|
return [];
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
|
2017-08-07 18:52:37 +03:00
|
|
|
await this.curBrowser.closeTab();
|
|
|
|
return this.windowHandles.map(String);
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2017-01-10 18:36:49 +03:00
|
|
|
* Close the currently selected chrome window.
|
|
|
|
*
|
|
|
|
* If it is the last window currently open, the chrome window will not be
|
|
|
|
* closed to prevent a shutdown of the application. Instead the returned
|
|
|
|
* list of chrome window handles is empty.
|
2015-03-20 00:12:58 +03:00
|
|
|
*
|
2017-01-10 18:36:49 +03:00
|
|
|
* @return {Array.<string>}
|
|
|
|
* Unique chrome window handles of remaining chrome windows.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.closeChromeWindow = async function() {
|
2017-01-10 18:36:49 +03:00
|
|
|
assert.firefox();
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow(Context.Chrome));
|
2015-03-20 00:12:58 +03:00
|
|
|
|
|
|
|
let nwins = 0;
|
2017-03-01 14:49:21 +03:00
|
|
|
|
2017-06-30 02:40:24 +03:00
|
|
|
// eslint-disable-next-line
|
2017-05-09 19:49:09 +03:00
|
|
|
for (let _ of this.windows) {
|
2015-03-20 00:12:58 +03:00
|
|
|
nwins++;
|
|
|
|
}
|
|
|
|
|
2017-06-30 02:40:24 +03:00
|
|
|
// If there is only one window left, do not close it. Instead return
|
|
|
|
// a faked empty array of window handles. This will instruct geckodriver
|
|
|
|
// to terminate the application.
|
2015-03-20 00:12:58 +03:00
|
|
|
if (nwins == 1) {
|
2017-01-10 18:36:49 +03:00
|
|
|
return [];
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
|
2017-01-10 18:36:49 +03:00
|
|
|
// reset frame to the top-most frame
|
|
|
|
this.curFrame = null;
|
2016-12-13 23:36:56 +03:00
|
|
|
|
2017-08-07 18:52:37 +03:00
|
|
|
await this.curBrowser.closeWindow();
|
|
|
|
return this.chromeWindowHandles.map(String);
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
2017-02-02 19:11:08 +03:00
|
|
|
/** Delete Marionette session. */
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.deleteSession = function() {
|
2015-04-24 15:55:52 +03:00
|
|
|
if (this.curBrowser !== null) {
|
2017-02-02 19:10:18 +03:00
|
|
|
// frame scripts can be safely reused
|
2018-04-23 09:26:09 +03:00
|
|
|
MarionettePrefs.contentListener = false;
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2018-01-12 16:30:23 +03:00
|
|
|
globalMessageManager.broadcastAsyncMessage("Marionette:Session:Delete");
|
2018-01-12 16:24:54 +03:00
|
|
|
globalMessageManager.broadcastAsyncMessage("Marionette:Deregister");
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-05-09 19:49:09 +03:00
|
|
|
for (let win of this.windows) {
|
2017-02-02 19:10:18 +03:00
|
|
|
if (win.messageManager) {
|
2016-07-07 01:23:35 +03:00
|
|
|
win.messageManager.removeDelayedFrameScript(FRAME_SCRIPT);
|
|
|
|
} else {
|
|
|
|
logger.error(
|
|
|
|
`Could not remove listener from page ${win.location.href}`
|
|
|
|
);
|
|
|
|
}
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-07 09:59:42 +03:00
|
|
|
// reset frame to the top-most frame, and clear reference to chrome window
|
2015-03-20 00:12:58 +03:00
|
|
|
this.curFrame = null;
|
2018-06-07 09:59:42 +03:00
|
|
|
this.mainFrame = null;
|
2015-03-20 00:12:58 +03:00
|
|
|
|
|
|
|
if (this.observing !== null) {
|
|
|
|
for (let topic in this.observing) {
|
|
|
|
Services.obs.removeObserver(this.observing[topic], topic);
|
|
|
|
}
|
|
|
|
this.observing = null;
|
|
|
|
}
|
2016-11-06 21:03:31 +03:00
|
|
|
|
2019-06-13 21:26:53 +03:00
|
|
|
if (this.dialogObserver) {
|
|
|
|
this.dialogObserver.cleanup();
|
|
|
|
this.dialogObserver = null;
|
|
|
|
}
|
2017-03-28 23:47:57 +03:00
|
|
|
|
Bug 1123506 - Evaluate scripts in content with lasting side-effects; r=automatedtester
In order to achieve WebDriver parity, Marionette needs the ability to
evaluate scripts in content space with lasting side-effects. This means
that state modifications should affect behaviour and state of the browsing
context, and such transgress the boundaries of the sandbox.
This patch brings a new script evaluation module that is shared between
code in chrome- and content space. This brings the number of unique
script evaluation implementations in Marionette down from six to one.
evaluate.sandbox provides the main entry-point for execution. It is
compatible with existing Marionette uses of Execute Script and Execute
Async Script commands in Mozilla clients, but also provides a new stateful
sandbox for evaluation that should have lasting side-effects.
It is not expected that Mozilla clients, such as testing/marionette/client
and the Node.js client in Gaia, should have to change as a consequence
of this change.
A substantial change to the script's runtime environment is that many
globals that previously existed are now only exposed whenever needed.
This means for example that Simple Test harness functionality (waitFor,
ok, isnot, is, &c.) is only available when using a sandbox augmented
with a Simple Test harness adapter.
Conversely, this patch does not expose marionetteScriptFinished as a
callback to asynchronous scripts for sandboxes which sandboxName parameter
is undefined, because this is what determines if the script should be
evaluated under WebDriver conformance constraints. In all other cases
where sandboxName _is_ defined, the traditional marionetteScriptFinished
et al. runtime environment is preserved.
MozReview-Commit-ID: 8FZ6rNVImuC
2016-02-26 17:36:39 +03:00
|
|
|
this.sandboxes.clear();
|
2018-04-23 12:01:40 +03:00
|
|
|
CertificateOverrideManager.uninstall();
|
2016-12-31 15:28:19 +03:00
|
|
|
|
2017-08-04 22:32:19 +03:00
|
|
|
this.sessionID = null;
|
2018-06-26 19:15:28 +03:00
|
|
|
this.capabilities = new Capabilities();
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Takes a screenshot of a web element, current frame, or viewport.
|
|
|
|
*
|
|
|
|
* The screen capture is returned as a lossless PNG image encoded as
|
|
|
|
* a base 64 string.
|
|
|
|
*
|
2017-06-30 02:40:24 +03:00
|
|
|
* If called in the content context, the |id| argument is not null and
|
|
|
|
* refers to a present and visible web element's ID, the capture area will
|
|
|
|
* be limited to the bounding box of that element. Otherwise, the capture
|
|
|
|
* area will be the bounding box of the current frame.
|
2015-03-20 00:12:58 +03:00
|
|
|
*
|
2017-06-30 02:40:24 +03:00
|
|
|
* If called in the chrome context, the screenshot will always represent
|
|
|
|
* the entire viewport.
|
2015-03-20 00:12:58 +03:00
|
|
|
*
|
2016-12-20 17:30:48 +03:00
|
|
|
* @param {string=} id
|
|
|
|
* Optional web element reference to take a screenshot of.
|
|
|
|
* If undefined, a screenshot will be taken of the document element.
|
2019-08-08 00:00:46 +03:00
|
|
|
* @param {boolean=} full
|
|
|
|
* True to take a screenshot of the entire document element. Is only
|
2017-07-26 15:11:53 +03:00
|
|
|
* considered if <var>id</var> is not defined. Defaults to true.
|
2016-12-20 17:30:48 +03:00
|
|
|
* @param {boolean=} hash
|
2019-08-08 00:00:46 +03:00
|
|
|
* True if the user requests a hash of the image data. Defaults to false.
|
2016-12-20 17:30:48 +03:00
|
|
|
* @param {boolean=} scroll
|
2019-08-08 00:00:46 +03:00
|
|
|
* Scroll to element if |id| is provided. Defaults to true.
|
2015-03-20 00:12:58 +03:00
|
|
|
*
|
|
|
|
* @return {string}
|
2017-07-26 15:11:53 +03:00
|
|
|
* If <var>hash</var> is false, PNG image encoded as Base64 encoded
|
|
|
|
* string. If <var>hash</var> is true, hex digest of the SHA-256
|
|
|
|
* hash of the Base64 encoded string.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2019-08-27 16:08:24 +03:00
|
|
|
GeckoDriver.prototype.takeScreenshot = async function(cmd) {
|
2018-01-12 17:25:30 +03:00
|
|
|
let win = assert.open(this.getCurrentWindow());
|
2017-03-20 17:13:32 +03:00
|
|
|
|
2019-08-21 15:42:25 +03:00
|
|
|
let { id, full, hash, scroll } = cmd.parameters;
|
2016-12-20 17:30:48 +03:00
|
|
|
let format = hash ? capture.Format.Hash : capture.Format.Base64;
|
2015-10-09 14:02:42 +03:00
|
|
|
|
2019-08-08 00:00:46 +03:00
|
|
|
full = typeof full == "undefined" ? true : full;
|
|
|
|
scroll = typeof scroll == "undefined" ? true : scroll;
|
|
|
|
|
|
|
|
// Only consider full screenshot if no element has been specified
|
|
|
|
full = id ? false : full;
|
|
|
|
|
2019-08-27 16:08:24 +03:00
|
|
|
let rect;
|
|
|
|
let dY = 0;
|
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
switch (this.context) {
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Chrome:
|
2019-08-27 16:08:24 +03:00
|
|
|
if (id) {
|
|
|
|
let webEl = WebElement.fromUUID(id, this.context);
|
|
|
|
let el = this.curBrowser.seenEls.get(webEl, win);
|
|
|
|
rect = el.getBoundingClientRect();
|
|
|
|
} else if (full) {
|
|
|
|
rect = win.document.documentElement.getBoundingClientRect();
|
2019-08-08 00:00:46 +03:00
|
|
|
} else {
|
2019-08-27 16:08:24 +03:00
|
|
|
// viewport
|
|
|
|
rect = new win.DOMRect(
|
|
|
|
win.pageXOffset,
|
|
|
|
win.pageYOffset,
|
|
|
|
win.innerWidth,
|
|
|
|
win.innerHeight
|
|
|
|
);
|
2016-12-06 01:06:36 +03:00
|
|
|
}
|
2016-12-20 17:30:48 +03:00
|
|
|
break;
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
case Context.Content:
|
2019-08-27 16:08:24 +03:00
|
|
|
rect = await this.listener.getScreenshotRect({ id, full, scroll });
|
|
|
|
// Temporarily needed until drawSnapshot() is used
|
|
|
|
dY = win.gNavToolbox.clientHeight;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
let canvas = capture.canvas(win, rect.x, rect.y, rect.width, rect.height, {
|
|
|
|
dY,
|
|
|
|
});
|
|
|
|
|
|
|
|
switch (format) {
|
|
|
|
case capture.Format.Hash:
|
|
|
|
return capture.toHash(canvas);
|
|
|
|
|
|
|
|
case capture.Format.Base64:
|
|
|
|
return capture.toBase64(canvas);
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
2017-06-30 02:40:24 +03:00
|
|
|
|
|
|
|
throw new TypeError(`Unknown context: ${this.context}`);
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the current browser orientation.
|
|
|
|
*
|
|
|
|
* Will return one of the valid primary orientation values
|
|
|
|
* portrait-primary, landscape-primary, portrait-secondary, or
|
|
|
|
* landscape-secondary.
|
|
|
|
*/
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.getScreenOrientation = function() {
|
2016-12-29 16:04:16 +03:00
|
|
|
assert.fennec();
|
2018-01-12 17:25:30 +03:00
|
|
|
let win = assert.open(this.getCurrentWindow());
|
2016-12-29 16:04:16 +03:00
|
|
|
|
2018-05-15 16:17:41 +03:00
|
|
|
return win.screen.mozOrientation;
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the current browser orientation.
|
|
|
|
*
|
|
|
|
* The supplied orientation should be given as one of the valid
|
|
|
|
* orientation values. If the orientation is unknown, an error will
|
|
|
|
* be raised.
|
|
|
|
*
|
|
|
|
* Valid orientations are "portrait" and "landscape", which fall
|
|
|
|
* back to "portrait-primary" and "landscape-primary" respectively,
|
|
|
|
* and "portrait-secondary" as well as "landscape-secondary".
|
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.setScreenOrientation = function(cmd) {
|
2016-11-01 01:12:30 +03:00
|
|
|
assert.fennec();
|
2018-01-12 17:25:30 +03:00
|
|
|
let win = assert.open(this.getCurrentWindow());
|
2016-03-17 17:54:48 +03:00
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
const ors = [
|
|
|
|
"portrait",
|
|
|
|
"landscape",
|
|
|
|
"portrait-primary",
|
|
|
|
"landscape-primary",
|
2016-11-01 01:12:30 +03:00
|
|
|
"portrait-secondary",
|
|
|
|
"landscape-secondary",
|
2015-03-20 00:12:58 +03:00
|
|
|
];
|
|
|
|
|
|
|
|
let or = String(cmd.parameters.orientation);
|
2016-11-01 01:12:30 +03:00
|
|
|
assert.string(or);
|
2015-03-20 00:12:58 +03:00
|
|
|
let mozOr = or.toLowerCase();
|
2016-12-29 16:04:16 +03:00
|
|
|
if (!ors.includes(mozOr)) {
|
2016-05-10 16:31:04 +03:00
|
|
|
throw new InvalidArgumentError(`Unknown screen orientation: ${or}`);
|
2015-03-23 23:43:18 +03:00
|
|
|
}
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2015-03-23 23:43:18 +03:00
|
|
|
if (!win.screen.mozLockOrientation(mozOr)) {
|
2015-03-20 00:12:58 +03:00
|
|
|
throw new WebDriverError(`Unable to set screen orientation: ${or}`);
|
2015-03-23 23:43:18 +03:00
|
|
|
}
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
2017-07-21 12:00:41 +03:00
|
|
|
/**
|
|
|
|
* Synchronously minimizes the user agent window as if the user pressed
|
2017-08-18 20:30:50 +03:00
|
|
|
* the minimize button.
|
|
|
|
*
|
|
|
|
* No action is taken if the window is already minimized.
|
2017-07-21 12:00:41 +03:00
|
|
|
*
|
|
|
|
* Not supported on Fennec.
|
|
|
|
*
|
|
|
|
* @return {Object.<string, number>}
|
|
|
|
* Window rect and window state.
|
|
|
|
*
|
|
|
|
* @throws {UnsupportedOperationError}
|
|
|
|
* Not available for current application.
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.minimizeWindow = async function() {
|
2017-07-21 12:00:41 +03:00
|
|
|
assert.firefox();
|
2018-01-12 17:25:30 +03:00
|
|
|
const win = assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-07-21 12:00:41 +03:00
|
|
|
|
2019-01-25 16:16:24 +03:00
|
|
|
switch (WindowState.from(win.windowState)) {
|
|
|
|
case WindowState.Fullscreen:
|
2018-11-08 16:11:18 +03:00
|
|
|
await exitFullscreen(win);
|
2019-01-25 16:16:24 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case WindowState.Maximized:
|
|
|
|
await restoreWindow(win);
|
|
|
|
break;
|
|
|
|
}
|
2018-11-08 16:11:18 +03:00
|
|
|
|
2019-01-25 16:16:24 +03:00
|
|
|
if (WindowState.from(win.windowState) != WindowState.Minimized) {
|
2019-01-10 17:26:52 +03:00
|
|
|
let cb;
|
|
|
|
let observer = new WebElementEventTarget(this.curBrowser.messageManager);
|
2019-01-21 21:39:30 +03:00
|
|
|
// Use a timed promise to abort if no window manager is present
|
2018-11-08 16:11:18 +03:00
|
|
|
await new TimedPromise(
|
|
|
|
resolve => {
|
2019-01-10 17:26:52 +03:00
|
|
|
cb = new DebounceCallback(resolve);
|
|
|
|
observer.addEventListener("visibilitychange", cb);
|
2017-07-21 12:00:41 +03:00
|
|
|
win.minimize();
|
2019-01-21 21:39:30 +03:00
|
|
|
},
|
|
|
|
{ throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
|
|
|
|
);
|
2019-01-10 17:26:52 +03:00
|
|
|
observer.removeEventListener("visibilitychange", cb);
|
2018-11-08 16:11:28 +03:00
|
|
|
await new IdlePromise(win);
|
2017-08-18 20:30:50 +03:00
|
|
|
}
|
2017-07-21 12:00:41 +03:00
|
|
|
|
2017-08-01 20:17:29 +03:00
|
|
|
return this.curBrowser.rect;
|
2017-07-21 12:00:41 +03:00
|
|
|
};
|
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
/**
|
2017-04-20 19:04:58 +03:00
|
|
|
* Synchronously maximizes the user agent window as if the user pressed
|
2017-08-18 20:31:42 +03:00
|
|
|
* the maximize button.
|
|
|
|
*
|
|
|
|
* No action is taken if the window is already maximized.
|
2017-04-20 19:04:58 +03:00
|
|
|
*
|
|
|
|
* Not supported on Fennec.
|
|
|
|
*
|
2017-08-01 20:30:52 +03:00
|
|
|
* @return {Object.<string, number>}
|
2017-04-20 19:04:58 +03:00
|
|
|
* Window rect.
|
2015-03-20 00:12:58 +03:00
|
|
|
*
|
2017-04-20 20:00:46 +03:00
|
|
|
* @throws {UnsupportedOperationError}
|
|
|
|
* Not available for current application.
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.maximizeWindow = async function() {
|
2017-04-20 20:00:46 +03:00
|
|
|
assert.firefox();
|
2018-01-12 17:25:30 +03:00
|
|
|
const win = assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-09-09 14:20:40 +03:00
|
|
|
switch (WindowState.from(win.windowState)) {
|
|
|
|
case WindowState.Fullscreen:
|
|
|
|
await exitFullscreen(win);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WindowState.Minimized:
|
2018-11-08 16:11:16 +03:00
|
|
|
await restoreWindow(win);
|
2017-09-09 14:20:40 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-11-08 16:11:22 +03:00
|
|
|
if (WindowState.from(win.windowState) != WindowState.Maximized) {
|
2018-11-08 16:11:20 +03:00
|
|
|
let cb;
|
2019-01-21 21:39:30 +03:00
|
|
|
// Use a timed promise to abort if no window manager is present
|
2017-08-18 20:31:42 +03:00
|
|
|
await new TimedPromise(
|
|
|
|
resolve => {
|
2018-11-08 16:11:20 +03:00
|
|
|
cb = new DebounceCallback(resolve);
|
|
|
|
win.addEventListener("sizemodechange", cb);
|
2017-08-21 19:19:10 +03:00
|
|
|
win.maximize();
|
2019-01-21 21:39:30 +03:00
|
|
|
},
|
|
|
|
{ throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
|
|
|
|
);
|
2018-11-08 16:11:20 +03:00
|
|
|
win.removeEventListener("sizemodechange", cb);
|
2018-11-08 16:11:28 +03:00
|
|
|
await new IdlePromise(win);
|
2017-08-18 20:31:42 +03:00
|
|
|
}
|
2017-04-20 19:04:58 +03:00
|
|
|
|
2017-08-01 20:17:29 +03:00
|
|
|
return this.curBrowser.rect;
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
2017-05-08 18:41:35 +03:00
|
|
|
/**
|
|
|
|
* Synchronously sets the user agent window to full screen as if the user
|
2017-08-18 20:31:42 +03:00
|
|
|
* had done "View > Enter Full Screen".
|
|
|
|
*
|
|
|
|
* No action is taken if the window is already in full screen mode.
|
2017-05-08 18:41:35 +03:00
|
|
|
*
|
|
|
|
* Not supported on Fennec.
|
|
|
|
*
|
|
|
|
* @return {Map.<string, number>}
|
|
|
|
* Window rect.
|
|
|
|
*
|
|
|
|
* @throws {UnsupportedOperationError}
|
|
|
|
* Not available for current application.
|
|
|
|
* @throws {NoSuchWindowError}
|
|
|
|
* Top-level browsing context has been discarded.
|
|
|
|
* @throws {UnexpectedAlertOpenError}
|
|
|
|
* A modal dialog is open, blocking this operation.
|
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.fullscreenWindow = async function() {
|
2017-05-08 18:41:35 +03:00
|
|
|
assert.firefox();
|
2018-01-12 17:25:30 +03:00
|
|
|
const win = assert.open(this.getCurrentWindow());
|
2018-08-22 23:24:39 +03:00
|
|
|
await this._handleUserPrompts();
|
2017-05-08 18:41:35 +03:00
|
|
|
|
2019-01-25 16:16:24 +03:00
|
|
|
switch (WindowState.from(win.windowState)) {
|
|
|
|
case WindowState.Maximized:
|
|
|
|
case WindowState.Minimized:
|
|
|
|
await restoreWindow(win);
|
|
|
|
break;
|
2017-09-09 14:20:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (WindowState.from(win.windowState) != WindowState.Fullscreen) {
|
2018-11-08 16:11:20 +03:00
|
|
|
let cb;
|
2019-01-21 21:39:30 +03:00
|
|
|
// Use a timed promise to abort if no window manager is present
|
2018-11-08 16:11:20 +03:00
|
|
|
await new TimedPromise(
|
|
|
|
resolve => {
|
|
|
|
cb = new DebounceCallback(resolve);
|
|
|
|
win.addEventListener("sizemodechange", cb);
|
2017-08-18 20:32:11 +03:00
|
|
|
win.fullScreen = true;
|
2019-01-21 21:39:30 +03:00
|
|
|
},
|
|
|
|
{ throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
|
|
|
|
);
|
2018-11-08 16:11:20 +03:00
|
|
|
win.removeEventListener("sizemodechange", cb);
|
2017-08-18 20:32:11 +03:00
|
|
|
}
|
2018-11-08 16:11:28 +03:00
|
|
|
await new IdlePromise(win);
|
2017-05-08 18:41:35 +03:00
|
|
|
|
2017-08-01 20:17:29 +03:00
|
|
|
return this.curBrowser.rect;
|
2017-05-08 18:41:35 +03:00
|
|
|
};
|
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
/**
|
|
|
|
* Dismisses a currently displayed tab modal, or returns no such alert if
|
|
|
|
* no modal is displayed.
|
|
|
|
*/
|
2018-08-22 23:24:39 +03:00
|
|
|
GeckoDriver.prototype.dismissDialog = async function() {
|
|
|
|
let win = assert.open(this.getCurrentWindow());
|
2016-05-23 13:16:04 +03:00
|
|
|
this._checkIfAlertIsPresent();
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2019-01-10 13:10:47 +03:00
|
|
|
let dialogClosed = waitForEvent(win, "DOMModalDialogClosed");
|
2019-01-09 21:22:19 +03:00
|
|
|
|
2019-01-10 13:10:47 +03:00
|
|
|
let { button0, button1 } = this.dialog.ui;
|
|
|
|
(button1 ? button1 : button0).click();
|
|
|
|
|
|
|
|
await dialogClosed;
|
2019-06-13 21:26:53 +03:00
|
|
|
await new IdlePromise(win);
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Accepts a currently displayed tab modal, or returns no such alert if
|
|
|
|
* no modal is displayed.
|
|
|
|
*/
|
2018-08-22 23:24:39 +03:00
|
|
|
GeckoDriver.prototype.acceptDialog = async function() {
|
|
|
|
let win = assert.open(this.getCurrentWindow());
|
2016-05-23 13:16:04 +03:00
|
|
|
this._checkIfAlertIsPresent();
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2019-01-10 13:10:47 +03:00
|
|
|
let dialogClosed = waitForEvent(win, "DOMModalDialogClosed");
|
2019-01-09 21:22:19 +03:00
|
|
|
|
2019-01-10 13:10:47 +03:00
|
|
|
let { button0 } = this.dialog.ui;
|
|
|
|
button0.click();
|
|
|
|
|
|
|
|
await dialogClosed;
|
2019-06-13 21:26:53 +03:00
|
|
|
await new IdlePromise(win);
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2017-06-30 02:40:24 +03:00
|
|
|
* Returns the message shown in a currently displayed modal, or returns
|
|
|
|
* a no such alert error if no modal is currently displayed.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.getTextFromDialog = function() {
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2016-05-23 13:16:04 +03:00
|
|
|
this._checkIfAlertIsPresent();
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2018-05-15 16:17:41 +03:00
|
|
|
return this.dialog.ui.infoBody.textContent;
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2017-04-07 01:37:04 +03:00
|
|
|
* Set the user prompt's value field.
|
|
|
|
*
|
2015-03-20 00:12:58 +03:00
|
|
|
* Sends keys to the input field of a currently displayed modal, or
|
|
|
|
* returns a no such alert error if no modal is currently displayed. If
|
|
|
|
* a tab modal is currently displayed but has no means for text input,
|
|
|
|
* an element not visible error is returned.
|
2017-04-07 01:37:04 +03:00
|
|
|
*
|
|
|
|
* @param {string} text
|
|
|
|
* Input to the user prompt's value field.
|
|
|
|
*
|
|
|
|
* @throws {ElementNotInteractableError}
|
|
|
|
* If the current user prompt is an alert or confirm.
|
|
|
|
* @throws {NoSuchAlertError}
|
|
|
|
* If there is no current user prompt.
|
|
|
|
* @throws {UnsupportedOperationError}
|
|
|
|
* If the current user prompt is something other than an alert,
|
|
|
|
* confirm, or a prompt.
|
2015-03-20 00:12:58 +03:00
|
|
|
*/
|
2017-11-09 22:39:51 +03:00
|
|
|
GeckoDriver.prototype.sendKeysToDialog = async function(cmd) {
|
2018-01-12 17:25:30 +03:00
|
|
|
assert.open(this.getCurrentWindow());
|
2016-05-23 13:16:04 +03:00
|
|
|
this._checkIfAlertIsPresent();
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2019-02-16 03:01:51 +03:00
|
|
|
let text = assert.string(cmd.parameters.text);
|
2019-02-16 03:03:11 +03:00
|
|
|
let promptType = this.dialog.args.promptType;
|
|
|
|
|
|
|
|
switch (promptType) {
|
|
|
|
case "alert":
|
|
|
|
case "confirm":
|
|
|
|
throw new ElementNotInteractableError(
|
|
|
|
`User prompt of type ${promptType} is not interactable`
|
|
|
|
);
|
|
|
|
case "prompt":
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new UnsupportedOperationError(
|
|
|
|
`User prompt of type ${promptType} is not supported`
|
|
|
|
);
|
|
|
|
}
|
2019-02-16 03:01:51 +03:00
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
// see toolkit/components/prompts/content/commonDialog.js
|
2017-11-09 22:39:51 +03:00
|
|
|
let { loginTextbox } = this.dialog.ui;
|
2019-02-16 03:03:21 +03:00
|
|
|
loginTextbox.value = text;
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
|
|
|
|
2017-06-30 02:40:24 +03:00
|
|
|
GeckoDriver.prototype._checkIfAlertIsPresent = function() {
|
2016-05-23 13:16:04 +03:00
|
|
|
if (!this.dialog || !this.dialog.ui) {
|
2018-06-10 15:37:19 +03:00
|
|
|
throw new NoSuchAlertError();
|
2016-05-23 13:16:04 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-08-22 23:24:39 +03:00
|
|
|
GeckoDriver.prototype._handleUserPrompts = async function() {
|
2018-06-10 15:37:19 +03:00
|
|
|
if (!this.dialog || !this.dialog.ui) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-10-31 03:37:15 +03:00
|
|
|
let { textContent } = this.dialog.ui.infoBody;
|
|
|
|
|
2018-06-10 15:37:19 +03:00
|
|
|
let behavior = this.capabilities.get("unhandledPromptBehavior");
|
|
|
|
switch (behavior) {
|
|
|
|
case UnhandledPromptBehavior.Accept:
|
2018-08-22 23:24:39 +03:00
|
|
|
await this.acceptDialog();
|
2018-06-10 15:37:19 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case UnhandledPromptBehavior.AcceptAndNotify:
|
2018-08-22 23:24:39 +03:00
|
|
|
await this.acceptDialog();
|
2018-10-31 03:37:15 +03:00
|
|
|
throw new UnexpectedAlertOpenError(
|
|
|
|
`Accepted user prompt dialog: ${textContent}`
|
|
|
|
);
|
2018-06-10 15:37:19 +03:00
|
|
|
|
|
|
|
case UnhandledPromptBehavior.Dismiss:
|
2018-08-22 23:24:39 +03:00
|
|
|
await this.dismissDialog();
|
2018-06-10 15:37:19 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case UnhandledPromptBehavior.DismissAndNotify:
|
2018-08-22 23:24:39 +03:00
|
|
|
await this.dismissDialog();
|
2018-10-31 03:37:15 +03:00
|
|
|
throw new UnexpectedAlertOpenError(
|
|
|
|
`Dismissed user prompt dialog: ${textContent}`
|
|
|
|
);
|
2018-06-10 15:37:19 +03:00
|
|
|
|
|
|
|
case UnhandledPromptBehavior.Ignore:
|
2018-10-31 03:37:15 +03:00
|
|
|
throw new UnexpectedAlertOpenError(
|
|
|
|
"Encountered unhandled user prompt dialog"
|
|
|
|
);
|
2018-06-10 15:37:19 +03:00
|
|
|
|
|
|
|
default:
|
|
|
|
throw new TypeError(`Unknown unhandledPromptBehavior "${behavior}"`);
|
2017-11-10 20:25:50 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-10-17 14:19:19 +03:00
|
|
|
/**
|
|
|
|
* Enables or disables accepting new socket connections.
|
|
|
|
*
|
2017-06-30 02:40:24 +03:00
|
|
|
* By calling this method with `false` the server will not accept any
|
|
|
|
* further connections, but existing connections will not be forcible
|
|
|
|
* closed. Use `true` to re-enable accepting connections.
|
2016-10-17 14:19:19 +03:00
|
|
|
*
|
2017-06-30 02:40:24 +03:00
|
|
|
* Please note that when closing the connection via the client you can
|
|
|
|
* end-up in a non-recoverable state if it hasn't been enabled before.
|
2016-10-17 14:19:19 +03:00
|
|
|
*
|
2017-06-30 02:40:24 +03:00
|
|
|
* This method is used for custom in application shutdowns via
|
|
|
|
* marionette.quit() or marionette.restart(), like File -> Quit.
|
2016-10-17 14:19:19 +03:00
|
|
|
*
|
|
|
|
* @param {boolean} state
|
|
|
|
* True if the server should accept new socket connections.
|
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.acceptConnections = function(cmd) {
|
2016-11-01 01:12:30 +03:00
|
|
|
assert.boolean(cmd.parameters.value);
|
2016-10-17 14:19:19 +03:00
|
|
|
this._server.acceptConnections = cmd.parameters.value;
|
2017-07-29 12:03:00 +03:00
|
|
|
};
|
2016-10-17 14:19:19 +03:00
|
|
|
|
2015-09-26 19:12:01 +03:00
|
|
|
/**
|
2017-02-09 21:35:00 +03:00
|
|
|
* Quits the application with the provided flags.
|
|
|
|
*
|
|
|
|
* Marionette will stop accepting new connections before ending the
|
|
|
|
* current session, and finally attempting to quit the application.
|
|
|
|
*
|
2017-07-26 15:11:53 +03:00
|
|
|
* Optional {@link nsIAppStartup} flags may be provided as
|
2017-02-09 21:35:00 +03:00
|
|
|
* an array of masks, and these will be combined by ORing
|
|
|
|
* them with a bitmask. The available masks are defined in
|
|
|
|
* https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIAppStartup.
|
|
|
|
*
|
|
|
|
* Crucially, only one of the *Quit flags can be specified. The |eRestart|
|
|
|
|
* flag may be bit-wise combined with one of the *Quit flags to cause
|
|
|
|
* the application to restart after it quits.
|
|
|
|
*
|
|
|
|
* @param {Array.<string>=} flags
|
|
|
|
* Constant name of masks to pass to |Services.startup.quit|.
|
|
|
|
* If empty or undefined, |nsIAppStartup.eAttemptQuit| is used.
|
|
|
|
*
|
2017-03-27 14:49:22 +03:00
|
|
|
* @return {string}
|
|
|
|
* Explaining the reason why the application quit. This can be
|
|
|
|
* in response to a normal shutdown or restart, yielding "shutdown"
|
|
|
|
* or "restart", respectively.
|
|
|
|
*
|
2017-02-09 21:35:00 +03:00
|
|
|
* @throws {InvalidArgumentError}
|
2017-08-07 18:52:37 +03:00
|
|
|
* If <var>flags</var> contains unknown or incompatible flags,
|
|
|
|
* for example multiple Quit flags.
|
2015-09-26 19:12:01 +03:00
|
|
|
*/
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.quit = async function(cmd) {
|
2017-02-09 21:35:00 +03:00
|
|
|
const quits = ["eConsiderQuit", "eAttemptQuit", "eForceQuit"];
|
2015-09-26 19:12:01 +03:00
|
|
|
|
2017-02-09 21:35:00 +03:00
|
|
|
let flags = [];
|
|
|
|
if (typeof cmd.parameters.flags != "undefined") {
|
|
|
|
flags = assert.array(cmd.parameters.flags);
|
2015-09-26 19:12:01 +03:00
|
|
|
}
|
|
|
|
|
2017-02-09 21:35:00 +03:00
|
|
|
// bug 1298921
|
2017-07-29 12:03:00 +03:00
|
|
|
assert.firefox();
|
2015-09-26 19:12:01 +03:00
|
|
|
|
2017-02-09 21:35:00 +03:00
|
|
|
let quitSeen;
|
|
|
|
let mode = 0;
|
|
|
|
if (flags.length > 0) {
|
|
|
|
for (let k of flags) {
|
|
|
|
assert.in(k, Ci.nsIAppStartup);
|
|
|
|
|
|
|
|
if (quits.includes(k)) {
|
|
|
|
if (quitSeen) {
|
|
|
|
throw new InvalidArgumentError(
|
|
|
|
`${k} cannot be combined with ${quitSeen}`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
quitSeen = k;
|
|
|
|
}
|
|
|
|
|
|
|
|
mode |= Ci.nsIAppStartup[k];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
mode = Ci.nsIAppStartup.eAttemptQuit;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._server.acceptConnections = false;
|
2017-02-02 19:11:08 +03:00
|
|
|
this.deleteSession();
|
2017-02-09 21:35:00 +03:00
|
|
|
|
|
|
|
// delay response until the application is about to quit
|
2019-01-10 13:14:22 +03:00
|
|
|
let quitApplication = waitForObserverTopic("quit-application");
|
2017-02-09 21:35:00 +03:00
|
|
|
Services.startup.quit(mode);
|
2017-03-27 14:49:22 +03:00
|
|
|
|
2019-01-10 13:14:22 +03:00
|
|
|
return { cause: (await quitApplication).data };
|
2015-09-26 19:12:01 +03:00
|
|
|
};
|
|
|
|
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.installAddon = function(cmd) {
|
2018-11-23 00:38:19 +03:00
|
|
|
assert.desktop();
|
2016-08-26 15:38:03 +03:00
|
|
|
|
|
|
|
let path = cmd.parameters.path;
|
|
|
|
let temp = cmd.parameters.temporary || false;
|
|
|
|
if (
|
|
|
|
typeof path == "undefined" ||
|
|
|
|
typeof path != "string" ||
|
|
|
|
typeof temp != "boolean"
|
|
|
|
) {
|
2017-10-06 15:07:13 +03:00
|
|
|
throw new InvalidArgumentError();
|
2016-08-26 15:38:03 +03:00
|
|
|
}
|
|
|
|
|
2018-04-23 10:12:30 +03:00
|
|
|
return Addon.install(path, temp);
|
2016-08-26 15:38:03 +03:00
|
|
|
};
|
|
|
|
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.uninstallAddon = function(cmd) {
|
2017-07-29 12:03:00 +03:00
|
|
|
assert.firefox();
|
2016-08-26 15:38:03 +03:00
|
|
|
|
|
|
|
let id = cmd.parameters.id;
|
|
|
|
if (typeof id == "undefined" || typeof id != "string") {
|
|
|
|
throw new InvalidArgumentError();
|
|
|
|
}
|
|
|
|
|
2018-04-23 10:12:30 +03:00
|
|
|
return Addon.uninstall(id);
|
2016-08-26 15:38:03 +03:00
|
|
|
};
|
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
/** Receives all messages from content messageManager. */
|
2017-06-30 02:40:24 +03:00
|
|
|
/* eslint-disable consistent-return */
|
|
|
|
GeckoDriver.prototype.receiveMessage = function(message) {
|
2015-03-20 00:12:58 +03:00
|
|
|
switch (message.name) {
|
|
|
|
case "Marionette:switchedToFrame":
|
|
|
|
if (message.json.restorePrevious) {
|
|
|
|
this.currentFrameElement = this.previousFrameElement;
|
|
|
|
} else {
|
|
|
|
// we don't arbitrarily save previousFrameElement, since
|
|
|
|
// we allow frame switching after modals appear, which would
|
|
|
|
// override this value and we'd lose our reference
|
2015-03-23 23:43:18 +03:00
|
|
|
if (message.json.storePrevious) {
|
2017-10-05 19:57:17 +03:00
|
|
|
this.previousFrameElement = new ChromeWebElement(
|
|
|
|
this.currentFrameElement
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (message.json.frameValue) {
|
|
|
|
this.currentFrameElement = new ChromeWebElement(
|
|
|
|
message.json.frameValue
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
this.currentFrameElement = null;
|
2015-03-23 23:43:18 +03:00
|
|
|
}
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2018-01-02 15:00:41 +03:00
|
|
|
case "Marionette:Register":
|
2018-01-09 22:50:30 +03:00
|
|
|
let { outerWindowID } = message.json;
|
|
|
|
this.registerBrowser(outerWindowID, message.target);
|
2018-01-02 15:00:41 +03:00
|
|
|
return { outerWindowID };
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2018-01-02 15:00:41 +03:00
|
|
|
case "Marionette:ListenersAttached":
|
|
|
|
if (message.json.outerWindowID === this.curBrowser.curFrameId) {
|
2015-03-20 00:12:58 +03:00
|
|
|
this.curBrowser.flushPendingCommands();
|
|
|
|
}
|
|
|
|
break;
|
2017-11-12 04:43:24 +03:00
|
|
|
|
2018-01-02 15:00:41 +03:00
|
|
|
case "Marionette:WebDriver:GetCapabilities":
|
|
|
|
return this.capabilities.toJSON();
|
2015-03-20 00:12:58 +03:00
|
|
|
}
|
|
|
|
};
|
2017-06-30 02:40:24 +03:00
|
|
|
/* eslint-enable consistent-return */
|
2015-03-20 00:12:58 +03:00
|
|
|
|
2017-06-30 02:40:24 +03:00
|
|
|
GeckoDriver.prototype.responseCompleted = function() {
|
2015-03-20 00:12:58 +03:00
|
|
|
if (this.curBrowser !== null) {
|
|
|
|
this.curBrowser.pendingCommands = [];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-11-12 00:49:58 +03:00
|
|
|
/**
|
|
|
|
* Retrieve the localized string for the specified entity id.
|
|
|
|
*
|
|
|
|
* Example:
|
2017-11-15 02:37:04 +03:00
|
|
|
* localizeEntity(["chrome://branding/locale/brand.dtd"], "brandShortName")
|
2016-11-12 00:49:58 +03:00
|
|
|
*
|
|
|
|
* @param {Array.<string>} urls
|
|
|
|
* Array of .dtd URLs.
|
|
|
|
* @param {string} id
|
|
|
|
* The ID of the entity to retrieve the localized string for.
|
|
|
|
*
|
|
|
|
* @return {string}
|
|
|
|
* The localized string for the requested entity.
|
|
|
|
*/
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.localizeEntity = function(cmd) {
|
2016-11-12 00:49:58 +03:00
|
|
|
let { urls, id } = cmd.parameters;
|
|
|
|
|
|
|
|
if (!Array.isArray(urls)) {
|
|
|
|
throw new InvalidArgumentError("Value of `urls` should be of type 'Array'");
|
|
|
|
}
|
|
|
|
if (typeof id != "string") {
|
|
|
|
throw new InvalidArgumentError("Value of `id` should be of type 'string'");
|
|
|
|
}
|
|
|
|
|
2018-05-15 16:17:41 +03:00
|
|
|
return l10n.localizeEntity(urls, id);
|
2017-07-29 12:03:00 +03:00
|
|
|
};
|
2016-11-12 00:49:58 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve the localized string for the specified property id.
|
|
|
|
*
|
|
|
|
* Example:
|
2017-06-30 02:40:24 +03:00
|
|
|
*
|
|
|
|
* localizeProperty(
|
|
|
|
* ["chrome://global/locale/findbar.properties"], "FastFind");
|
2016-11-12 00:49:58 +03:00
|
|
|
*
|
|
|
|
* @param {Array.<string>} urls
|
|
|
|
* Array of .properties URLs.
|
|
|
|
* @param {string} id
|
|
|
|
* The ID of the property to retrieve the localized string for.
|
|
|
|
*
|
|
|
|
* @return {string}
|
|
|
|
* The localized string for the requested property.
|
|
|
|
*/
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.localizeProperty = function(cmd) {
|
2016-11-12 00:49:58 +03:00
|
|
|
let { urls, id } = cmd.parameters;
|
|
|
|
|
|
|
|
if (!Array.isArray(urls)) {
|
|
|
|
throw new InvalidArgumentError("Value of `urls` should be of type 'Array'");
|
|
|
|
}
|
|
|
|
if (typeof id != "string") {
|
|
|
|
throw new InvalidArgumentError("Value of `id` should be of type 'string'");
|
|
|
|
}
|
|
|
|
|
2018-05-15 16:17:41 +03:00
|
|
|
return l10n.localizeProperty(urls, id);
|
2017-07-29 12:03:00 +03:00
|
|
|
};
|
2016-11-12 00:49:58 +03:00
|
|
|
|
Bug 1363428 - Add reftest-specific endpoints to Marionette, r=ato
This adds commands to start a reftest session, run a test, and end the
session. It as assumed that after you start a reftest session you will
just run reftests until you end the session. When starting a session
the user provides a string indicating when screenshots should be
taken, and an object mapping urls to a count of the number of times
that url is expected to be used in the session, to help with
caching. Running the tests takes a url to a test, an expected status,
a timeout, and a nested list of possible references, in which each
entry at a specific level is combined by OR and nested references are
combined by AND.
The implementation is heavilly inspired by the existing reftest
harness, starting a minimal window with no tabs, and loading the urls
directly in there. In order to get a screenshot in the e10s case we
have to pass the DRAW_VIEW and USE_WIDGET_LAYERS flags when taking the
screenshot.
For performance we heavily cache canvases; for references that will be
repeated we cache the full canvas with image, and we also cache a
single canvas to use for all other screenshots to avoid the overhead
of repeatedly creating a new canvas element.
MozReview-Commit-ID: JOFvtmH7tg
2017-05-10 12:51:10 +03:00
|
|
|
/**
|
|
|
|
* Initialize the reftest mode
|
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.setupReftest = async function(cmd) {
|
Bug 1363428 - Add reftest-specific endpoints to Marionette, r=ato
This adds commands to start a reftest session, run a test, and end the
session. It as assumed that after you start a reftest session you will
just run reftests until you end the session. When starting a session
the user provides a string indicating when screenshots should be
taken, and an object mapping urls to a count of the number of times
that url is expected to be used in the session, to help with
caching. Running the tests takes a url to a test, an expected status,
a timeout, and a nested list of possible references, in which each
entry at a specific level is combined by OR and nested references are
combined by AND.
The implementation is heavilly inspired by the existing reftest
harness, starting a minimal window with no tabs, and loading the urls
directly in there. In order to get a screenshot in the e10s case we
have to pass the DRAW_VIEW and USE_WIDGET_LAYERS flags when taking the
screenshot.
For performance we heavily cache canvases; for references that will be
repeated we cache the full canvas with image, and we also cache a
single canvas to use for all other screenshots to avoid the overhead
of repeatedly creating a new canvas element.
MozReview-Commit-ID: JOFvtmH7tg
2017-05-10 12:51:10 +03:00
|
|
|
if (this._reftest) {
|
2017-08-07 18:52:37 +03:00
|
|
|
throw new UnsupportedOperationError(
|
|
|
|
"Called reftest:setup with a reftest session already active"
|
|
|
|
);
|
Bug 1363428 - Add reftest-specific endpoints to Marionette, r=ato
This adds commands to start a reftest session, run a test, and end the
session. It as assumed that after you start a reftest session you will
just run reftests until you end the session. When starting a session
the user provides a string indicating when screenshots should be
taken, and an object mapping urls to a count of the number of times
that url is expected to be used in the session, to help with
caching. Running the tests takes a url to a test, an expected status,
a timeout, and a nested list of possible references, in which each
entry at a specific level is combined by OR and nested references are
combined by AND.
The implementation is heavilly inspired by the existing reftest
harness, starting a minimal window with no tabs, and loading the urls
directly in there. In order to get a screenshot in the e10s case we
have to pass the DRAW_VIEW and USE_WIDGET_LAYERS flags when taking the
screenshot.
For performance we heavily cache canvases; for references that will be
repeated we cache the full canvas with image, and we also cache a
single canvas to use for all other screenshots to avoid the overhead
of repeatedly creating a new canvas element.
MozReview-Commit-ID: JOFvtmH7tg
2017-05-10 12:51:10 +03:00
|
|
|
}
|
|
|
|
|
2017-10-16 19:47:35 +03:00
|
|
|
if (this.context !== Context.Chrome) {
|
2017-08-07 18:52:37 +03:00
|
|
|
throw new UnsupportedOperationError(
|
|
|
|
"Must set chrome context before running reftests"
|
|
|
|
);
|
Bug 1363428 - Add reftest-specific endpoints to Marionette, r=ato
This adds commands to start a reftest session, run a test, and end the
session. It as assumed that after you start a reftest session you will
just run reftests until you end the session. When starting a session
the user provides a string indicating when screenshots should be
taken, and an object mapping urls to a count of the number of times
that url is expected to be used in the session, to help with
caching. Running the tests takes a url to a test, an expected status,
a timeout, and a nested list of possible references, in which each
entry at a specific level is combined by OR and nested references are
combined by AND.
The implementation is heavilly inspired by the existing reftest
harness, starting a minimal window with no tabs, and loading the urls
directly in there. In order to get a screenshot in the e10s case we
have to pass the DRAW_VIEW and USE_WIDGET_LAYERS flags when taking the
screenshot.
For performance we heavily cache canvases; for references that will be
repeated we cache the full canvas with image, and we also cache a
single canvas to use for all other screenshots to avoid the overhead
of repeatedly creating a new canvas element.
MozReview-Commit-ID: JOFvtmH7tg
2017-05-10 12:51:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
let { urlCount = {}, screenshot = "unexpected" } = cmd.parameters;
|
|
|
|
if (!["always", "fail", "unexpected"].includes(screenshot)) {
|
2017-08-07 18:52:37 +03:00
|
|
|
throw new InvalidArgumentError(
|
|
|
|
"Value of `screenshot` should be 'always', 'fail' or 'unexpected'"
|
|
|
|
);
|
Bug 1363428 - Add reftest-specific endpoints to Marionette, r=ato
This adds commands to start a reftest session, run a test, and end the
session. It as assumed that after you start a reftest session you will
just run reftests until you end the session. When starting a session
the user provides a string indicating when screenshots should be
taken, and an object mapping urls to a count of the number of times
that url is expected to be used in the session, to help with
caching. Running the tests takes a url to a test, an expected status,
a timeout, and a nested list of possible references, in which each
entry at a specific level is combined by OR and nested references are
combined by AND.
The implementation is heavilly inspired by the existing reftest
harness, starting a minimal window with no tabs, and loading the urls
directly in there. In order to get a screenshot in the e10s case we
have to pass the DRAW_VIEW and USE_WIDGET_LAYERS flags when taking the
screenshot.
For performance we heavily cache canvases; for references that will be
repeated we cache the full canvas with image, and we also cache a
single canvas to use for all other screenshots to avoid the overhead
of repeatedly creating a new canvas element.
MozReview-Commit-ID: JOFvtmH7tg
2017-05-10 12:51:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
this._reftest = new reftest.Runner(this);
|
2019-01-11 21:11:30 +03:00
|
|
|
this._reftest.setup(urlCount, screenshot);
|
Bug 1363428 - Add reftest-specific endpoints to Marionette, r=ato
This adds commands to start a reftest session, run a test, and end the
session. It as assumed that after you start a reftest session you will
just run reftests until you end the session. When starting a session
the user provides a string indicating when screenshots should be
taken, and an object mapping urls to a count of the number of times
that url is expected to be used in the session, to help with
caching. Running the tests takes a url to a test, an expected status,
a timeout, and a nested list of possible references, in which each
entry at a specific level is combined by OR and nested references are
combined by AND.
The implementation is heavilly inspired by the existing reftest
harness, starting a minimal window with no tabs, and loading the urls
directly in there. In order to get a screenshot in the e10s case we
have to pass the DRAW_VIEW and USE_WIDGET_LAYERS flags when taking the
screenshot.
For performance we heavily cache canvases; for references that will be
repeated we cache the full canvas with image, and we also cache a
single canvas to use for all other screenshots to avoid the overhead
of repeatedly creating a new canvas element.
MozReview-Commit-ID: JOFvtmH7tg
2017-05-10 12:51:10 +03:00
|
|
|
};
|
|
|
|
|
2017-08-07 18:52:37 +03:00
|
|
|
/** Run a reftest. */
|
2018-05-15 16:17:41 +03:00
|
|
|
GeckoDriver.prototype.runReftest = async function(cmd) {
|
2019-01-11 21:11:30 +03:00
|
|
|
let { test, references, expected, timeout, width, height } = cmd.parameters;
|
Bug 1363428 - Add reftest-specific endpoints to Marionette, r=ato
This adds commands to start a reftest session, run a test, and end the
session. It as assumed that after you start a reftest session you will
just run reftests until you end the session. When starting a session
the user provides a string indicating when screenshots should be
taken, and an object mapping urls to a count of the number of times
that url is expected to be used in the session, to help with
caching. Running the tests takes a url to a test, an expected status,
a timeout, and a nested list of possible references, in which each
entry at a specific level is combined by OR and nested references are
combined by AND.
The implementation is heavilly inspired by the existing reftest
harness, starting a minimal window with no tabs, and loading the urls
directly in there. In order to get a screenshot in the e10s case we
have to pass the DRAW_VIEW and USE_WIDGET_LAYERS flags when taking the
screenshot.
For performance we heavily cache canvases; for references that will be
repeated we cache the full canvas with image, and we also cache a
single canvas to use for all other screenshots to avoid the overhead
of repeatedly creating a new canvas element.
MozReview-Commit-ID: JOFvtmH7tg
2017-05-10 12:51:10 +03:00
|
|
|
|
|
|
|
if (!this._reftest) {
|
2017-08-07 18:52:37 +03:00
|
|
|
throw new UnsupportedOperationError(
|
|
|
|
"Called reftest:run before reftest:start"
|
|
|
|
);
|
Bug 1363428 - Add reftest-specific endpoints to Marionette, r=ato
This adds commands to start a reftest session, run a test, and end the
session. It as assumed that after you start a reftest session you will
just run reftests until you end the session. When starting a session
the user provides a string indicating when screenshots should be
taken, and an object mapping urls to a count of the number of times
that url is expected to be used in the session, to help with
caching. Running the tests takes a url to a test, an expected status,
a timeout, and a nested list of possible references, in which each
entry at a specific level is combined by OR and nested references are
combined by AND.
The implementation is heavilly inspired by the existing reftest
harness, starting a minimal window with no tabs, and loading the urls
directly in there. In order to get a screenshot in the e10s case we
have to pass the DRAW_VIEW and USE_WIDGET_LAYERS flags when taking the
screenshot.
For performance we heavily cache canvases; for references that will be
repeated we cache the full canvas with image, and we also cache a
single canvas to use for all other screenshots to avoid the overhead
of repeatedly creating a new canvas element.
MozReview-Commit-ID: JOFvtmH7tg
2017-05-10 12:51:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
assert.string(test);
|
|
|
|
assert.string(expected);
|
|
|
|
assert.array(references);
|
|
|
|
|
2018-05-15 16:17:41 +03:00
|
|
|
return {
|
|
|
|
value: await this._reftest.run(
|
2019-01-11 21:11:30 +03:00
|
|
|
test,
|
|
|
|
references,
|
|
|
|
expected,
|
|
|
|
timeout,
|
|
|
|
width,
|
|
|
|
height
|
2019-07-05 12:01:24 +03:00
|
|
|
),
|
2019-01-11 21:11:30 +03:00
|
|
|
};
|
Bug 1363428 - Add reftest-specific endpoints to Marionette, r=ato
This adds commands to start a reftest session, run a test, and end the
session. It as assumed that after you start a reftest session you will
just run reftests until you end the session. When starting a session
the user provides a string indicating when screenshots should be
taken, and an object mapping urls to a count of the number of times
that url is expected to be used in the session, to help with
caching. Running the tests takes a url to a test, an expected status,
a timeout, and a nested list of possible references, in which each
entry at a specific level is combined by OR and nested references are
combined by AND.
The implementation is heavilly inspired by the existing reftest
harness, starting a minimal window with no tabs, and loading the urls
directly in there. In order to get a screenshot in the e10s case we
have to pass the DRAW_VIEW and USE_WIDGET_LAYERS flags when taking the
screenshot.
For performance we heavily cache canvases; for references that will be
repeated we cache the full canvas with image, and we also cache a
single canvas to use for all other screenshots to avoid the overhead
of repeatedly creating a new canvas element.
MozReview-Commit-ID: JOFvtmH7tg
2017-05-10 12:51:10 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2017-08-07 18:52:37 +03:00
|
|
|
* End a reftest run.
|
Bug 1363428 - Add reftest-specific endpoints to Marionette, r=ato
This adds commands to start a reftest session, run a test, and end the
session. It as assumed that after you start a reftest session you will
just run reftests until you end the session. When starting a session
the user provides a string indicating when screenshots should be
taken, and an object mapping urls to a count of the number of times
that url is expected to be used in the session, to help with
caching. Running the tests takes a url to a test, an expected status,
a timeout, and a nested list of possible references, in which each
entry at a specific level is combined by OR and nested references are
combined by AND.
The implementation is heavilly inspired by the existing reftest
harness, starting a minimal window with no tabs, and loading the urls
directly in there. In order to get a screenshot in the e10s case we
have to pass the DRAW_VIEW and USE_WIDGET_LAYERS flags when taking the
screenshot.
For performance we heavily cache canvases; for references that will be
repeated we cache the full canvas with image, and we also cache a
single canvas to use for all other screenshots to avoid the overhead
of repeatedly creating a new canvas element.
MozReview-Commit-ID: JOFvtmH7tg
2017-05-10 12:51:10 +03:00
|
|
|
*
|
|
|
|
* Closes the reftest window (without changing the current window handle),
|
|
|
|
* and removes cached canvases.
|
|
|
|
*/
|
2017-10-03 16:35:47 +03:00
|
|
|
GeckoDriver.prototype.teardownReftest = function() {
|
Bug 1363428 - Add reftest-specific endpoints to Marionette, r=ato
This adds commands to start a reftest session, run a test, and end the
session. It as assumed that after you start a reftest session you will
just run reftests until you end the session. When starting a session
the user provides a string indicating when screenshots should be
taken, and an object mapping urls to a count of the number of times
that url is expected to be used in the session, to help with
caching. Running the tests takes a url to a test, an expected status,
a timeout, and a nested list of possible references, in which each
entry at a specific level is combined by OR and nested references are
combined by AND.
The implementation is heavilly inspired by the existing reftest
harness, starting a minimal window with no tabs, and loading the urls
directly in there. In order to get a screenshot in the e10s case we
have to pass the DRAW_VIEW and USE_WIDGET_LAYERS flags when taking the
screenshot.
For performance we heavily cache canvases; for references that will be
repeated we cache the full canvas with image, and we also cache a
single canvas to use for all other screenshots to avoid the overhead
of repeatedly creating a new canvas element.
MozReview-Commit-ID: JOFvtmH7tg
2017-05-10 12:51:10 +03:00
|
|
|
if (!this._reftest) {
|
2017-08-07 18:52:37 +03:00
|
|
|
throw new UnsupportedOperationError(
|
|
|
|
"Called reftest:teardown before reftest:start"
|
|
|
|
);
|
Bug 1363428 - Add reftest-specific endpoints to Marionette, r=ato
This adds commands to start a reftest session, run a test, and end the
session. It as assumed that after you start a reftest session you will
just run reftests until you end the session. When starting a session
the user provides a string indicating when screenshots should be
taken, and an object mapping urls to a count of the number of times
that url is expected to be used in the session, to help with
caching. Running the tests takes a url to a test, an expected status,
a timeout, and a nested list of possible references, in which each
entry at a specific level is combined by OR and nested references are
combined by AND.
The implementation is heavilly inspired by the existing reftest
harness, starting a minimal window with no tabs, and loading the urls
directly in there. In order to get a screenshot in the e10s case we
have to pass the DRAW_VIEW and USE_WIDGET_LAYERS flags when taking the
screenshot.
For performance we heavily cache canvases; for references that will be
repeated we cache the full canvas with image, and we also cache a
single canvas to use for all other screenshots to avoid the overhead
of repeatedly creating a new canvas element.
MozReview-Commit-ID: JOFvtmH7tg
2017-05-10 12:51:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
this._reftest.abort();
|
|
|
|
this._reftest = null;
|
|
|
|
};
|
|
|
|
|
2015-03-20 00:12:58 +03:00
|
|
|
GeckoDriver.prototype.commands = {
|
2017-06-22 14:11:21 +03:00
|
|
|
// Marionette service
|
|
|
|
"Marionette:AcceptConnections": GeckoDriver.prototype.acceptConnections,
|
2018-04-05 22:23:57 +03:00
|
|
|
"Marionette:GetContext": GeckoDriver.prototype.getContext,
|
|
|
|
"Marionette:GetScreenOrientation": GeckoDriver.prototype.getScreenOrientation,
|
|
|
|
"Marionette:GetWindowType": GeckoDriver.prototype.getWindowType,
|
2017-06-22 14:11:21 +03:00
|
|
|
"Marionette:Quit": GeckoDriver.prototype.quit,
|
2018-04-05 22:23:57 +03:00
|
|
|
"Marionette:SetContext": GeckoDriver.prototype.setContext,
|
|
|
|
"Marionette:SetScreenOrientation": GeckoDriver.prototype.setScreenOrientation,
|
2018-11-05 21:40:39 +03:00
|
|
|
"Marionette:ActionChain": GeckoDriver.prototype.actionChain, // bug 1354578, legacy actions
|
|
|
|
"Marionette:MultiAction": GeckoDriver.prototype.multiAction, // bug 1354578, legacy actions
|
2018-04-05 22:23:57 +03:00
|
|
|
"Marionette:SingleTap": GeckoDriver.prototype.singleTap,
|
2017-06-22 14:11:21 +03:00
|
|
|
|
2017-06-22 14:55:00 +03:00
|
|
|
// Addon service
|
|
|
|
"Addon:Install": GeckoDriver.prototype.installAddon,
|
|
|
|
"Addon:Uninstall": GeckoDriver.prototype.uninstallAddon,
|
|
|
|
|
2017-06-22 14:59:33 +03:00
|
|
|
// L10n service
|
|
|
|
"L10n:LocalizeEntity": GeckoDriver.prototype.localizeEntity,
|
|
|
|
"L10n:LocalizeProperty": GeckoDriver.prototype.localizeProperty,
|
|
|
|
|
Bug 1363428 - Add reftest-specific endpoints to Marionette, r=ato
This adds commands to start a reftest session, run a test, and end the
session. It as assumed that after you start a reftest session you will
just run reftests until you end the session. When starting a session
the user provides a string indicating when screenshots should be
taken, and an object mapping urls to a count of the number of times
that url is expected to be used in the session, to help with
caching. Running the tests takes a url to a test, an expected status,
a timeout, and a nested list of possible references, in which each
entry at a specific level is combined by OR and nested references are
combined by AND.
The implementation is heavilly inspired by the existing reftest
harness, starting a minimal window with no tabs, and loading the urls
directly in there. In order to get a screenshot in the e10s case we
have to pass the DRAW_VIEW and USE_WIDGET_LAYERS flags when taking the
screenshot.
For performance we heavily cache canvases; for references that will be
repeated we cache the full canvas with image, and we also cache a
single canvas to use for all other screenshots to avoid the overhead
of repeatedly creating a new canvas element.
MozReview-Commit-ID: JOFvtmH7tg
2017-05-10 12:51:10 +03:00
|
|
|
// Reftest service
|
|
|
|
"reftest:setup": GeckoDriver.prototype.setupReftest,
|
|
|
|
"reftest:run": GeckoDriver.prototype.runReftest,
|
|
|
|
"reftest:teardown": GeckoDriver.prototype.teardownReftest,
|
|
|
|
|
2017-06-22 15:07:02 +03:00
|
|
|
// WebDriver service
|
2018-04-05 16:50:28 +03:00
|
|
|
"WebDriver:AcceptAlert": GeckoDriver.prototype.acceptDialog,
|
2018-11-05 21:40:39 +03:00
|
|
|
"WebDriver:AcceptDialog": GeckoDriver.prototype.acceptDialog, // deprecated, but used in geckodriver (see also bug 1495063)
|
2017-06-22 15:07:02 +03:00
|
|
|
"WebDriver:AddCookie": GeckoDriver.prototype.addCookie,
|
|
|
|
"WebDriver:Back": GeckoDriver.prototype.goBack,
|
|
|
|
"WebDriver:CloseChromeWindow": GeckoDriver.prototype.closeChromeWindow,
|
|
|
|
"WebDriver:CloseWindow": GeckoDriver.prototype.close,
|
|
|
|
"WebDriver:DeleteAllCookies": GeckoDriver.prototype.deleteAllCookies,
|
|
|
|
"WebDriver:DeleteCookie": GeckoDriver.prototype.deleteCookie,
|
|
|
|
"WebDriver:DeleteSession": GeckoDriver.prototype.deleteSession,
|
|
|
|
"WebDriver:DismissAlert": GeckoDriver.prototype.dismissDialog,
|
|
|
|
"WebDriver:ElementClear": GeckoDriver.prototype.clearElement,
|
|
|
|
"WebDriver:ElementClick": GeckoDriver.prototype.clickElement,
|
|
|
|
"WebDriver:ElementSendKeys": GeckoDriver.prototype.sendKeysToElement,
|
|
|
|
"WebDriver:ExecuteAsyncScript": GeckoDriver.prototype.executeAsyncScript,
|
|
|
|
"WebDriver:ExecuteScript": GeckoDriver.prototype.executeScript,
|
|
|
|
"WebDriver:FindElement": GeckoDriver.prototype.findElement,
|
|
|
|
"WebDriver:FindElements": GeckoDriver.prototype.findElements,
|
|
|
|
"WebDriver:Forward": GeckoDriver.prototype.goForward,
|
2017-08-18 20:29:47 +03:00
|
|
|
"WebDriver:FullscreenWindow": GeckoDriver.prototype.fullscreenWindow,
|
2017-06-22 15:07:02 +03:00
|
|
|
"WebDriver:GetActiveElement": GeckoDriver.prototype.getActiveElement,
|
|
|
|
"WebDriver:GetActiveFrame": GeckoDriver.prototype.getActiveFrame,
|
|
|
|
"WebDriver:GetAlertText": GeckoDriver.prototype.getTextFromDialog,
|
|
|
|
"WebDriver:GetCapabilities": GeckoDriver.prototype.getSessionCapabilities,
|
|
|
|
"WebDriver:GetChromeWindowHandle":
|
|
|
|
GeckoDriver.prototype.getChromeWindowHandle,
|
|
|
|
"WebDriver:GetChromeWindowHandles":
|
|
|
|
GeckoDriver.prototype.getChromeWindowHandles,
|
|
|
|
"WebDriver:GetCookies": GeckoDriver.prototype.getCookies,
|
|
|
|
"WebDriver:GetCurrentChromeWindowHandle":
|
|
|
|
GeckoDriver.prototype.getChromeWindowHandle,
|
|
|
|
"WebDriver:GetCurrentURL": GeckoDriver.prototype.getCurrentUrl,
|
|
|
|
"WebDriver:GetElementAttribute": GeckoDriver.prototype.getElementAttribute,
|
|
|
|
"WebDriver:GetElementCSSValue":
|
|
|
|
GeckoDriver.prototype.getElementValueOfCssProperty,
|
|
|
|
"WebDriver:GetElementProperty": GeckoDriver.prototype.getElementProperty,
|
|
|
|
"WebDriver:GetElementRect": GeckoDriver.prototype.getElementRect,
|
|
|
|
"WebDriver:GetElementTagName": GeckoDriver.prototype.getElementTagName,
|
|
|
|
"WebDriver:GetElementText": GeckoDriver.prototype.getElementText,
|
|
|
|
"WebDriver:GetPageSource": GeckoDriver.prototype.getPageSource,
|
|
|
|
"WebDriver:GetTimeouts": GeckoDriver.prototype.getTimeouts,
|
|
|
|
"WebDriver:GetTitle": GeckoDriver.prototype.getTitle,
|
|
|
|
"WebDriver:GetWindowHandle": GeckoDriver.prototype.getWindowHandle,
|
|
|
|
"WebDriver:GetWindowHandles": GeckoDriver.prototype.getWindowHandles,
|
|
|
|
"WebDriver:GetWindowRect": GeckoDriver.prototype.getWindowRect,
|
|
|
|
"WebDriver:IsElementDisplayed": GeckoDriver.prototype.isElementDisplayed,
|
|
|
|
"WebDriver:IsElementEnabled": GeckoDriver.prototype.isElementEnabled,
|
|
|
|
"WebDriver:IsElementSelected": GeckoDriver.prototype.isElementSelected,
|
2017-07-21 12:00:41 +03:00
|
|
|
"WebDriver:MinimizeWindow": GeckoDriver.prototype.minimizeWindow,
|
2017-06-22 15:07:02 +03:00
|
|
|
"WebDriver:MaximizeWindow": GeckoDriver.prototype.maximizeWindow,
|
|
|
|
"WebDriver:Navigate": GeckoDriver.prototype.get,
|
|
|
|
"WebDriver:NewSession": GeckoDriver.prototype.newSession,
|
2019-01-10 13:14:24 +03:00
|
|
|
"WebDriver:NewWindow": GeckoDriver.prototype.newWindow,
|
2017-06-22 15:07:02 +03:00
|
|
|
"WebDriver:PerformActions": GeckoDriver.prototype.performActions,
|
|
|
|
"WebDriver:Refresh": GeckoDriver.prototype.refresh,
|
|
|
|
"WebDriver:ReleaseActions": GeckoDriver.prototype.releaseActions,
|
|
|
|
"WebDriver:SendAlertText": GeckoDriver.prototype.sendKeysToDialog,
|
|
|
|
"WebDriver:SetTimeouts": GeckoDriver.prototype.setTimeouts,
|
|
|
|
"WebDriver:SetWindowRect": GeckoDriver.prototype.setWindowRect,
|
|
|
|
"WebDriver:SwitchToFrame": GeckoDriver.prototype.switchToFrame,
|
|
|
|
"WebDriver:SwitchToParentFrame": GeckoDriver.prototype.switchToParentFrame,
|
|
|
|
"WebDriver:SwitchToShadowRoot": GeckoDriver.prototype.switchToShadowRoot,
|
|
|
|
"WebDriver:SwitchToWindow": GeckoDriver.prototype.switchToWindow,
|
|
|
|
"WebDriver:TakeScreenshot": GeckoDriver.prototype.takeScreenshot,
|
2015-03-20 00:12:58 +03:00
|
|
|
};
|
2016-12-19 22:08:46 +03:00
|
|
|
|
2017-01-27 11:09:32 +03:00
|
|
|
function getOuterWindowId(win) {
|
2018-07-25 02:47:43 +03:00
|
|
|
return win.windowUtils.outerWindowID;
|
2017-01-27 11:09:32 +03:00
|
|
|
}
|
2017-09-04 18:55:34 +03:00
|
|
|
|
2019-01-25 16:16:24 +03:00
|
|
|
async function exitFullscreen(win) {
|
2018-11-08 16:11:20 +03:00
|
|
|
let cb;
|
2019-01-21 21:39:30 +03:00
|
|
|
// Use a timed promise to abort if no window manager is present
|
2018-11-08 16:11:20 +03:00
|
|
|
await new TimedPromise(
|
|
|
|
resolve => {
|
|
|
|
cb = new DebounceCallback(resolve);
|
2019-01-25 16:16:24 +03:00
|
|
|
win.addEventListener("sizemodechange", cb);
|
|
|
|
win.fullScreen = false;
|
2019-01-21 21:39:30 +03:00
|
|
|
},
|
|
|
|
{ throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
|
|
|
|
);
|
2019-01-25 16:16:24 +03:00
|
|
|
win.removeEventListener("sizemodechange", cb);
|
2017-09-09 14:20:40 +03:00
|
|
|
}
|
|
|
|
|
2019-01-25 16:16:24 +03:00
|
|
|
async function restoreWindow(win) {
|
|
|
|
win.restore();
|
2019-01-21 21:39:30 +03:00
|
|
|
// Use a poll promise to abort if no window manager is present
|
|
|
|
await new PollPromise(
|
|
|
|
(resolve, reject) => {
|
2019-01-25 16:16:24 +03:00
|
|
|
if (WindowState.from(win.windowState) == WindowState.Normal) {
|
2018-11-08 16:11:16 +03:00
|
|
|
resolve();
|
|
|
|
} else {
|
|
|
|
reject();
|
|
|
|
}
|
2019-01-21 21:39:30 +03:00
|
|
|
},
|
|
|
|
{ timeout: TIMEOUT_NO_WINDOW_MANAGER }
|
|
|
|
);
|
2017-09-09 14:20:40 +03:00
|
|
|
}
|