2012-03-22 19:19:57 +04: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/. */
|
|
|
|
|
2017-06-28 21:04:14 +03:00
|
|
|
/* eslint-env mozilla/frame-script */
|
|
|
|
/* global XPCNativeWrapper */
|
2018-11-07 01:35:05 +03:00
|
|
|
/* eslint-disable no-restricted-globals */
|
2017-06-28 21:04: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
|
|
|
"use strict";
|
|
|
|
|
2018-07-25 02:47:43 +03:00
|
|
|
const winUtil = content.windowUtils;
|
2012-03-22 19:19:57 +04: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"
|
|
|
|
);
|
|
|
|
const { action } = ChromeUtils.import("chrome://marionette/content/action.js");
|
|
|
|
const { atom } = ChromeUtils.import("chrome://marionette/content/atom.js");
|
|
|
|
const { Capabilities, PageLoadStrategy } = ChromeUtils.import(
|
|
|
|
"chrome://marionette/content/capabilities.js"
|
|
|
|
);
|
|
|
|
const { element, WebElement } = ChromeUtils.import(
|
|
|
|
"chrome://marionette/content/element.js"
|
|
|
|
);
|
2017-06-28 21:01:49 +03:00
|
|
|
const {
|
|
|
|
ElementNotInteractableError,
|
|
|
|
InsecureCertificateError,
|
|
|
|
InvalidArgumentError,
|
|
|
|
InvalidSelectorError,
|
|
|
|
NoSuchElementError,
|
|
|
|
NoSuchFrameError,
|
|
|
|
TimeoutError,
|
|
|
|
UnknownError,
|
2019-01-17 21:18:31 +03:00
|
|
|
} = ChromeUtils.import("chrome://marionette/content/error.js");
|
|
|
|
const { Sandboxes, evaluate, sandbox } = ChromeUtils.import(
|
|
|
|
"chrome://marionette/content/evaluate.js"
|
|
|
|
);
|
|
|
|
const { event } = ChromeUtils.import("chrome://marionette/content/event.js");
|
|
|
|
const { ContentEventObserverService } = ChromeUtils.import(
|
|
|
|
"chrome://marionette/content/dom.js"
|
|
|
|
);
|
|
|
|
const { pprint, truncate } = ChromeUtils.import(
|
|
|
|
"chrome://marionette/content/format.js"
|
|
|
|
);
|
|
|
|
const { interaction } = ChromeUtils.import(
|
|
|
|
"chrome://marionette/content/interaction.js"
|
|
|
|
);
|
|
|
|
const { legacyaction } = ChromeUtils.import(
|
|
|
|
"chrome://marionette/content/legacyaction.js"
|
2019-07-05 12:01:24 +03:00
|
|
|
);
|
2019-01-17 21:18:31 +03:00
|
|
|
const { Log } = ChromeUtils.import("chrome://marionette/content/log.js");
|
|
|
|
const { navigate } = ChromeUtils.import(
|
|
|
|
"chrome://marionette/content/navigate.js"
|
|
|
|
);
|
|
|
|
const { proxy } = ChromeUtils.import("chrome://marionette/content/proxy.js");
|
2019-07-05 12:01:24 +03:00
|
|
|
|
2020-06-05 09:09:27 +03:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "logger", () => Log.getWithPrefix(contentId));
|
2018-06-08 15:16:29 +03:00
|
|
|
XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
|
2016-05-27 23:26:16 +03:00
|
|
|
|
2020-06-05 09:10:25 +03:00
|
|
|
const contentId = content.docShell.browsingContext.id;
|
2020-06-05 09:09:27 +03:00
|
|
|
|
|
|
|
const curContainer = {
|
|
|
|
_frame: null,
|
|
|
|
shadowRoot: null,
|
|
|
|
|
|
|
|
get frame() {
|
|
|
|
return this._frame;
|
|
|
|
},
|
|
|
|
|
|
|
|
set frame(frame) {
|
|
|
|
this._frame = frame;
|
|
|
|
|
2020-07-15 11:21:46 +03:00
|
|
|
this.id = this._frame.browsingContext.id;
|
2020-06-05 09:09:27 +03:00
|
|
|
this.shadowRoot = null;
|
|
|
|
},
|
|
|
|
};
|
2017-11-24 23:53:57 +03:00
|
|
|
|
|
|
|
// Listen for click event to indicate one click has happened, so actions
|
2018-01-18 23:41:23 +03:00
|
|
|
// code can send dblclick event
|
2017-11-24 23:53:57 +03:00
|
|
|
addEventListener("click", event.DoubleClickTracker.setClick);
|
|
|
|
addEventListener("dblclick", event.DoubleClickTracker.resetClick);
|
2018-01-24 23:03:53 +03:00
|
|
|
addEventListener("unload", event.DoubleClickTracker.resetClick, true);
|
2017-11-24 23:53:57 +03:00
|
|
|
|
2017-08-30 19:38:23 +03:00
|
|
|
const seenEls = new element.Store();
|
2016-05-20 15:28:27 +03:00
|
|
|
|
2018-01-02 15:00:41 +03:00
|
|
|
Object.defineProperty(this, "capabilities", {
|
|
|
|
get() {
|
|
|
|
let payload = sendSyncMessage("Marionette:WebDriver:GetCapabilities");
|
2018-06-26 19:15:28 +03:00
|
|
|
return Capabilities.fromJSON(payload[0]);
|
2018-01-02 15:00:41 +03:00
|
|
|
},
|
|
|
|
configurable: true,
|
|
|
|
});
|
2016-01-15 00:12:13 +03:00
|
|
|
|
2017-12-05 22:22:53 +03:00
|
|
|
let legacyactions = new legacyaction.Chain();
|
2012-03-22 19:19:57 +04:00
|
|
|
|
2013-03-19 00:42:46 +04:00
|
|
|
// last touch for each fingerId
|
2017-08-30 19:38:23 +03:00
|
|
|
let multiLast = {};
|
|
|
|
|
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
|
|
|
// sandbox storage and name of the current sandbox
|
2017-08-30 19:38:23 +03:00
|
|
|
const sandboxes = new Sandboxes(() => curContainer.frame);
|
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-09-15 19:07:41 +03:00
|
|
|
const eventObservers = new ContentEventObserverService(
|
|
|
|
content,
|
|
|
|
sendAsyncMessage.bind(this)
|
|
|
|
);
|
|
|
|
|
2017-03-28 22:41:38 +03:00
|
|
|
/**
|
2017-07-06 19:02:19 +03:00
|
|
|
* The load listener singleton helps to keep track of active page load
|
|
|
|
* activities, and can be used by any command which might cause a navigation
|
2018-11-06 15:08:55 +03:00
|
|
|
* to happen. In the specific case of a process change of the frame script it
|
|
|
|
* allows to continue observing the current page load.
|
2017-03-28 22:41:38 +03:00
|
|
|
*/
|
2017-08-30 19:38:23 +03:00
|
|
|
const loadListener = {
|
2017-08-19 16:22:17 +03:00
|
|
|
commandID: null,
|
2017-05-04 12:53:30 +03:00
|
|
|
seenBeforeUnload: false,
|
|
|
|
seenUnload: false,
|
2017-03-28 22:41:38 +03:00
|
|
|
timeout: null,
|
2017-04-20 13:12:27 +03:00
|
|
|
timerPageLoad: null,
|
|
|
|
timerPageUnload: null,
|
2017-03-28 22:41:38 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Start listening for page unload/load events.
|
|
|
|
*
|
2017-08-19 16:22:17 +03:00
|
|
|
* @param {number} commandID
|
2017-06-30 02:40:24 +03:00
|
|
|
* ID of the currently handled message between the driver and
|
|
|
|
* listener.
|
2017-03-28 22:41:38 +03:00
|
|
|
* @param {number} timeout
|
2017-06-30 02:40:24 +03:00
|
|
|
* Timeout in seconds the method has to wait for the page being
|
|
|
|
* finished loading.
|
2017-03-28 22:41:38 +03:00
|
|
|
* @param {number} startTime
|
|
|
|
* Unix timestap when the navitation request got triggered.
|
|
|
|
* @param {boolean=} waitForUnloaded
|
2017-06-30 02:40:24 +03:00
|
|
|
* If true wait for page unload events, otherwise only for page
|
|
|
|
* load events.
|
2017-03-28 22:41:38 +03:00
|
|
|
*/
|
2017-08-19 16:22:17 +03:00
|
|
|
start(commandID, timeout, startTime, waitForUnloaded = true) {
|
|
|
|
this.commandID = commandID;
|
2017-03-28 22:41:38 +03:00
|
|
|
this.timeout = timeout;
|
|
|
|
|
2017-05-04 12:53:30 +03:00
|
|
|
this.seenBeforeUnload = false;
|
2017-04-20 13:12:27 +03:00
|
|
|
this.seenUnload = false;
|
|
|
|
|
2017-06-30 02:40:24 +03:00
|
|
|
this.timerPageLoad = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
2017-04-28 11:27:12 +03:00
|
|
|
this.timerPageUnload = null;
|
2017-04-20 13:12:27 +03:00
|
|
|
|
2018-11-06 15:08:55 +03:00
|
|
|
// In case the frame script has been moved to a differnt process,
|
|
|
|
// wait the remaining time
|
2017-03-28 22:41:38 +03:00
|
|
|
timeout = startTime + timeout - new Date().getTime();
|
|
|
|
|
|
|
|
if (timeout <= 0) {
|
2017-04-20 13:12:27 +03:00
|
|
|
this.notify(this.timerPageLoad);
|
2017-03-28 22:41:38 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (waitForUnloaded) {
|
2017-10-25 15:15:30 +03:00
|
|
|
addEventListener("beforeunload", this, true);
|
|
|
|
addEventListener("hashchange", this, true);
|
|
|
|
addEventListener("pagehide", this, true);
|
|
|
|
addEventListener("popstate", this, true);
|
|
|
|
addEventListener("unload", this, true);
|
2017-04-20 13:12:27 +03:00
|
|
|
|
|
|
|
Services.obs.addObserver(this, "outer-window-destroyed");
|
2017-03-28 22:41:38 +03:00
|
|
|
} else {
|
2018-11-06 15:08:55 +03:00
|
|
|
// The frame script has been moved to a differnt content process.
|
2017-07-18 16:00:37 +03:00
|
|
|
// Due to the time it takes to re-register the browser in Marionette,
|
|
|
|
// it can happen that page load events are missed before the listeners
|
|
|
|
// are getting attached again. By checking the document readyState the
|
|
|
|
// command can return immediately if the page load is already done.
|
|
|
|
let readyState = content.document.readyState;
|
|
|
|
let documentURI = content.document.documentURI;
|
2018-11-29 22:31:38 +03:00
|
|
|
logger.trace(truncate`Check readyState ${readyState} for ${documentURI}`);
|
2017-07-18 16:00:37 +03:00
|
|
|
// If the page load has already finished, don't setup listeners and
|
|
|
|
// timers but return immediatelly.
|
|
|
|
if (this.handleReadyState(readyState, documentURI)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-10-25 15:15:30 +03:00
|
|
|
addEventListener("DOMContentLoaded", loadListener, true);
|
|
|
|
addEventListener("pageshow", loadListener, true);
|
2017-03-28 22:41:38 +03:00
|
|
|
}
|
2017-04-20 13:12:27 +03:00
|
|
|
|
2017-06-30 02:40:24 +03:00
|
|
|
this.timerPageLoad.initWithCallback(
|
|
|
|
this,
|
|
|
|
timeout,
|
|
|
|
Ci.nsITimer.TYPE_ONE_SHOT
|
|
|
|
);
|
2017-03-28 22:41:38 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stop listening for page unload/load events.
|
|
|
|
*/
|
2017-06-30 02:40:24 +03:00
|
|
|
stop() {
|
2017-04-20 13:12:27 +03:00
|
|
|
if (this.timerPageLoad) {
|
|
|
|
this.timerPageLoad.cancel();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.timerPageUnload) {
|
|
|
|
this.timerPageUnload.cancel();
|
2017-03-28 22:41:38 +03:00
|
|
|
}
|
|
|
|
|
2017-10-25 15:15:30 +03:00
|
|
|
removeEventListener("beforeunload", this, true);
|
|
|
|
removeEventListener("hashchange", this, true);
|
|
|
|
removeEventListener("pagehide", this, true);
|
|
|
|
removeEventListener("popstate", this, true);
|
|
|
|
removeEventListener("DOMContentLoaded", this, true);
|
|
|
|
removeEventListener("pageshow", this, true);
|
|
|
|
removeEventListener("unload", this, true);
|
2017-04-20 13:12:27 +03:00
|
|
|
|
2018-11-06 15:08:55 +03:00
|
|
|
// In case the observer was added before the frame script has been moved
|
|
|
|
// to a different process, it will no longer be available. Exceptions can
|
|
|
|
// be ignored.
|
2017-04-20 13:12:27 +03:00
|
|
|
try {
|
|
|
|
Services.obs.removeObserver(this, "outer-window-destroyed");
|
|
|
|
} catch (e) {}
|
2017-03-28 22:41:38 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Callback for registered DOM events.
|
|
|
|
*/
|
2017-06-30 02:40:24 +03:00
|
|
|
handleEvent(event) {
|
2017-07-07 17:38:41 +03:00
|
|
|
// Only care about events from the currently selected browsing context,
|
|
|
|
// whereby some of those do not bubble up to the window.
|
|
|
|
if (
|
|
|
|
event.target != curContainer.frame &&
|
|
|
|
event.target != curContainer.frame.document
|
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-07-07 16:36:08 +03:00
|
|
|
let location = event.target.documentURI || event.target.location.href;
|
2018-11-29 22:31:38 +03:00
|
|
|
logger.trace(truncate`Received DOM event ${event.type} for ${location}`);
|
2017-04-20 13:12:27 +03:00
|
|
|
|
2017-03-28 22:41:38 +03:00
|
|
|
switch (event.type) {
|
2017-04-28 11:27:12 +03:00
|
|
|
case "beforeunload":
|
2017-05-04 12:53:30 +03:00
|
|
|
this.seenBeforeUnload = true;
|
2017-04-28 11:27:12 +03:00
|
|
|
break;
|
|
|
|
|
2017-04-20 13:12:27 +03:00
|
|
|
case "unload":
|
|
|
|
this.seenUnload = true;
|
|
|
|
break;
|
|
|
|
|
2017-03-28 22:41:38 +03:00
|
|
|
case "pagehide":
|
2017-07-07 17:38:41 +03:00
|
|
|
this.seenUnload = true;
|
2017-04-20 13:12:27 +03:00
|
|
|
|
2017-10-25 15:15:30 +03:00
|
|
|
removeEventListener("hashchange", this, true);
|
|
|
|
removeEventListener("pagehide", this, true);
|
|
|
|
removeEventListener("popstate", this, true);
|
2017-07-07 17:38:41 +03:00
|
|
|
|
|
|
|
// Now wait until the target page has been loaded
|
2017-10-25 15:15:30 +03:00
|
|
|
addEventListener("DOMContentLoaded", this, true);
|
|
|
|
addEventListener("pageshow", this, true);
|
2017-03-28 22:41:38 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case "hashchange":
|
2017-08-08 20:48:35 +03:00
|
|
|
case "popstate":
|
2017-07-07 17:38:41 +03:00
|
|
|
this.stop();
|
2017-08-19 16:22:17 +03:00
|
|
|
sendOk(this.commandID);
|
2017-03-28 22:41:38 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case "DOMContentLoaded":
|
2017-07-18 16:00:37 +03:00
|
|
|
case "pageshow":
|
|
|
|
this.handleReadyState(
|
|
|
|
event.target.readyState,
|
|
|
|
event.target.documentURI
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks the value of readyState for the current page
|
|
|
|
* load activity, and resolves the command if the load
|
|
|
|
* has been finished. It also takes care of the selected
|
|
|
|
* page load strategy.
|
|
|
|
*
|
|
|
|
* @param {string} readyState
|
|
|
|
* Current ready state of the document.
|
|
|
|
* @param {string} documentURI
|
|
|
|
* Current document URI of the document.
|
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
* True if the page load has been finished.
|
|
|
|
*/
|
|
|
|
handleReadyState(readyState, documentURI) {
|
|
|
|
let finished = false;
|
|
|
|
|
|
|
|
switch (readyState) {
|
|
|
|
case "interactive":
|
|
|
|
if (documentURI.startsWith("about:certerror")) {
|
2017-03-28 22:41:38 +03:00
|
|
|
this.stop();
|
2017-08-19 16:22:17 +03:00
|
|
|
sendError(new InsecureCertificateError(), this.commandID);
|
2017-07-18 16:00:37 +03:00
|
|
|
finished = true;
|
|
|
|
} else if (/about:.*(error)\?/.exec(documentURI)) {
|
2017-03-28 22:41:38 +03:00
|
|
|
this.stop();
|
2017-07-18 16:00:37 +03:00
|
|
|
sendError(
|
|
|
|
new UnknownError(`Reached error page: ${documentURI}`),
|
2017-08-19 16:22:17 +03:00
|
|
|
this.commandID
|
|
|
|
);
|
2017-07-18 16:00:37 +03:00
|
|
|
finished = true;
|
2017-03-28 22:41:38 +03:00
|
|
|
|
2017-06-30 02:40:24 +03:00
|
|
|
// Return early with a page load strategy of eager, and also
|
|
|
|
// special-case about:blocked pages which should be treated as
|
2017-07-18 16:00:37 +03:00
|
|
|
// non-error pages but do not raise a pageshow event. about:blank
|
|
|
|
// is also treaded specifically here, because it gets temporary
|
|
|
|
// loaded for new content processes, and we only want to rely on
|
|
|
|
// complete loads for it.
|
2018-06-26 19:15:28 +03:00
|
|
|
} else if (
|
|
|
|
(capabilities.get("pageLoadStrategy") === PageLoadStrategy.Eager &&
|
2017-07-18 16:00:37 +03:00
|
|
|
documentURI != "about:blank") ||
|
|
|
|
/about:blocked\?/.exec(documentURI)
|
|
|
|
) {
|
2017-03-28 22:41:38 +03:00
|
|
|
this.stop();
|
2017-08-19 16:22:17 +03:00
|
|
|
sendOk(this.commandID);
|
2017-07-18 16:00:37 +03:00
|
|
|
finished = true;
|
2017-03-28 22:41:38 +03:00
|
|
|
}
|
2017-07-07 17:38:41 +03:00
|
|
|
|
2017-03-28 22:41:38 +03:00
|
|
|
break;
|
|
|
|
|
2017-07-18 16:00:37 +03:00
|
|
|
case "complete":
|
2017-07-07 17:38:41 +03:00
|
|
|
this.stop();
|
2017-08-19 16:22:17 +03:00
|
|
|
sendOk(this.commandID);
|
2017-07-18 16:00:37 +03:00
|
|
|
finished = true;
|
2017-07-07 17:38:41 +03:00
|
|
|
|
2017-03-28 22:41:38 +03:00
|
|
|
break;
|
|
|
|
}
|
2017-07-18 16:00:37 +03:00
|
|
|
|
|
|
|
return finished;
|
2017-03-28 22:41:38 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Callback for navigation timeout timer.
|
|
|
|
*/
|
2017-06-30 02:40:24 +03:00
|
|
|
notify(timer) {
|
2017-04-20 13:12:27 +03:00
|
|
|
switch (timer) {
|
|
|
|
case this.timerPageUnload:
|
2017-06-30 02:40:24 +03:00
|
|
|
// In the case when a document has a beforeunload handler
|
|
|
|
// registered, the currently active command will return immediately
|
|
|
|
// due to the modal dialog observer in proxy.js.
|
|
|
|
//
|
|
|
|
// Otherwise the timeout waiting for the document to start
|
|
|
|
// navigating is increased by 5000 ms to ensure a possible load
|
|
|
|
// event is not missed. In the common case such an event should
|
|
|
|
// occur pretty soon after beforeunload, and we optimise for this.
|
2017-05-04 12:53:30 +03:00
|
|
|
if (this.seenBeforeUnload) {
|
|
|
|
this.seenBeforeUnload = null;
|
2017-06-30 02:40:24 +03:00
|
|
|
this.timerPageUnload.initWithCallback(
|
2017-07-29 12:03:00 +03:00
|
|
|
this,
|
|
|
|
5000,
|
|
|
|
Ci.nsITimer.TYPE_ONE_SHOT
|
|
|
|
);
|
2017-05-04 12:53:30 +03:00
|
|
|
|
2017-06-30 02:40:24 +03:00
|
|
|
// If no page unload has been detected, ensure to properly stop
|
|
|
|
// the load listener, and return from the currently active command.
|
2017-05-04 12:53:30 +03:00
|
|
|
} else if (!this.seenUnload) {
|
2017-04-20 13:12:27 +03:00
|
|
|
logger.debug(
|
|
|
|
"Canceled page load listener because no navigation " +
|
|
|
|
"has been detected"
|
|
|
|
);
|
|
|
|
this.stop();
|
2017-08-19 16:22:17 +03:00
|
|
|
sendOk(this.commandID);
|
2017-04-20 13:12:27 +03:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2017-06-30 02:40:24 +03:00
|
|
|
case this.timerPageLoad:
|
|
|
|
this.stop();
|
|
|
|
sendError(
|
|
|
|
new TimeoutError(`Timeout loading page after ${this.timeout}ms`),
|
2017-08-19 16:22:17 +03:00
|
|
|
this.commandID
|
|
|
|
);
|
2017-06-30 02:40:24 +03:00
|
|
|
break;
|
2017-04-20 13:12:27 +03:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-10-03 16:35:47 +03:00
|
|
|
observe(subject, topic) {
|
2020-06-03 18:34:46 +03:00
|
|
|
logger.trace(`Received observer notification ${topic}`);
|
2020-06-02 23:16:52 +03:00
|
|
|
|
2020-06-05 09:10:25 +03:00
|
|
|
const winId = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
|
|
|
|
const bc = BrowsingContext.get(curContainer.id);
|
|
|
|
|
2017-04-20 13:12:27 +03:00
|
|
|
switch (topic) {
|
2017-05-19 12:16:38 +03:00
|
|
|
// In the case when the currently selected frame is closed,
|
2017-04-20 13:12:27 +03:00
|
|
|
// there will be no further load events. Stop listening immediately.
|
|
|
|
case "outer-window-destroyed":
|
2020-06-05 09:10:25 +03:00
|
|
|
if (bc.window.windowUtils.outerWindowID == winId) {
|
2017-04-20 13:12:27 +03:00
|
|
|
this.stop();
|
2017-08-19 16:22:17 +03:00
|
|
|
sendOk(this.commandID);
|
2017-04-20 13:12:27 +03:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2017-03-28 22:41:38 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2017-07-06 19:02:19 +03:00
|
|
|
* Continue to listen for page load events after the frame script has been
|
2018-11-06 15:08:55 +03:00
|
|
|
* moved to a different content process.
|
2017-03-28 22:41:38 +03:00
|
|
|
*
|
2017-08-19 16:22:17 +03:00
|
|
|
* @param {number} commandID
|
2017-06-30 02:40:24 +03:00
|
|
|
* ID of the currently handled message between the driver and
|
|
|
|
* listener.
|
2017-03-28 22:41:38 +03:00
|
|
|
* @param {number} timeout
|
2017-06-30 02:40:24 +03:00
|
|
|
* Timeout in milliseconds the method has to wait for the page
|
|
|
|
* being finished loading.
|
2017-03-28 22:41:38 +03:00
|
|
|
* @param {number} startTime
|
|
|
|
* Unix timestap when the navitation request got triggered.
|
|
|
|
*/
|
2017-08-19 16:22:17 +03:00
|
|
|
waitForLoadAfterFramescriptReload(commandID, timeout, startTime) {
|
|
|
|
this.start(commandID, timeout, startTime, false);
|
2017-03-28 22:41:38 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Use a trigger callback to initiate a page load, and attach listeners if
|
|
|
|
* a page load is expected.
|
|
|
|
*
|
|
|
|
* @param {function} trigger
|
|
|
|
* Callback that triggers the page load.
|
2017-08-19 16:22:17 +03:00
|
|
|
* @param {number} commandID
|
2017-03-28 22:41:38 +03:00
|
|
|
* ID of the currently handled message between the driver and listener.
|
|
|
|
* @param {number} pageTimeout
|
2017-06-30 02:40:24 +03:00
|
|
|
* Timeout in milliseconds the method has to wait for the page
|
|
|
|
* finished loading.
|
2017-04-20 13:12:27 +03:00
|
|
|
* @param {boolean=} loadEventExpected
|
|
|
|
* Optional flag, which indicates that navigate has to wait for the page
|
|
|
|
* finished loading.
|
2017-03-28 22:41:38 +03:00
|
|
|
* @param {string=} url
|
|
|
|
* Optional URL, which is used to check if a page load is expected.
|
|
|
|
*/
|
2020-07-20 01:53:58 +03:00
|
|
|
async navigate(
|
2017-08-19 16:22:17 +03:00
|
|
|
trigger,
|
|
|
|
commandID,
|
|
|
|
timeout,
|
|
|
|
loadEventExpected = true,
|
2017-04-20 13:12:27 +03:00
|
|
|
useUnloadTimer = false
|
|
|
|
) {
|
2017-04-19 14:22:13 +03:00
|
|
|
// Only wait if the page load strategy is not `none`
|
|
|
|
loadEventExpected =
|
|
|
|
loadEventExpected &&
|
2018-06-26 19:15:28 +03:00
|
|
|
capabilities.get("pageLoadStrategy") !== PageLoadStrategy.None;
|
2017-04-19 14:22:13 +03:00
|
|
|
|
2017-03-28 22:41:38 +03:00
|
|
|
if (loadEventExpected) {
|
|
|
|
let startTime = new Date().getTime();
|
2017-08-19 16:22:17 +03:00
|
|
|
this.start(commandID, timeout, startTime, true);
|
2017-03-28 22:41:38 +03:00
|
|
|
}
|
|
|
|
|
2020-07-20 01:53:58 +03:00
|
|
|
await trigger();
|
2020-07-18 17:07:26 +03:00
|
|
|
|
2020-07-20 01:53:58 +03:00
|
|
|
try {
|
|
|
|
if (!loadEventExpected) {
|
|
|
|
sendOk(commandID);
|
|
|
|
return;
|
|
|
|
}
|
2020-07-19 23:20:41 +03:00
|
|
|
|
2020-07-20 01:53:58 +03:00
|
|
|
// If requested setup a timer to detect a possible page load
|
|
|
|
if (useUnloadTimer) {
|
|
|
|
this.timerPageUnload = Cc["@mozilla.org/timer;1"].createInstance(
|
|
|
|
Ci.nsITimer
|
|
|
|
);
|
|
|
|
this.timerPageUnload.initWithCallback(
|
|
|
|
this,
|
|
|
|
200,
|
|
|
|
Ci.nsITimer.TYPE_ONE_SHOT
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
if (loadEventExpected) {
|
|
|
|
this.stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
sendError(e, commandID);
|
|
|
|
}
|
2017-03-28 22:41:38 +03:00
|
|
|
},
|
2017-07-29 12:03:00 +03:00
|
|
|
};
|
2017-03-28 22:41:38 +03:00
|
|
|
|
2012-03-22 19:19:57 +04:00
|
|
|
/**
|
2017-06-30 02:40:24 +03:00
|
|
|
* Called when listener is first started up. The listener sends its
|
|
|
|
* unique window ID and its current URI to the actor. If the actor returns
|
|
|
|
* an ID, we start the listeners. Otherwise, nothing happens.
|
2012-03-22 19:19:57 +04:00
|
|
|
*/
|
|
|
|
function registerSelf() {
|
2018-11-29 22:31:38 +03:00
|
|
|
logger.trace("Frame script loaded");
|
2018-01-09 22:50:30 +03:00
|
|
|
|
2020-06-05 09:09:27 +03:00
|
|
|
curContainer.frame = content;
|
|
|
|
|
2018-01-09 22:50:30 +03:00
|
|
|
sandboxes.clear();
|
|
|
|
legacyactions.mouseEventsOnly = false;
|
|
|
|
action.inputStateMap = new Map();
|
|
|
|
action.inputsToCancel = [];
|
2017-05-19 12:16:38 +03:00
|
|
|
|
2020-06-05 09:09:27 +03:00
|
|
|
let reply = sendSyncMessage("Marionette:Register", {
|
|
|
|
frameId: contentId,
|
|
|
|
});
|
2018-01-12 16:28:28 +03:00
|
|
|
if (reply.length == 0) {
|
|
|
|
logger.error("No reply from Marionette:Register");
|
2019-04-01 15:28:02 +03:00
|
|
|
return;
|
2018-01-12 16:28:28 +03:00
|
|
|
}
|
|
|
|
|
2020-06-05 09:09:27 +03:00
|
|
|
if (reply[0].frameId === contentId) {
|
2018-11-29 22:31:38 +03:00
|
|
|
logger.trace("Frame script registered");
|
2018-01-02 15:00:41 +03:00
|
|
|
startListeners();
|
2020-06-05 09:09:27 +03:00
|
|
|
sendAsyncMessage("Marionette:ListenersAttached", {
|
|
|
|
frameId: contentId,
|
|
|
|
});
|
2012-03-22 19:19:57 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
// Eventually we will not have a closure for every single command,
|
|
|
|
// but use a generic dispatch for all listener commands.
|
2015-08-21 17:00:29 +03:00
|
|
|
//
|
2017-10-05 19:57:17 +03:00
|
|
|
// Worth nothing that this shares many characteristics with
|
|
|
|
// server.TCPConnection#execute. Perhaps this could be generalised
|
|
|
|
// at the point.
|
2015-04-15 14:18:00 +03:00
|
|
|
function dispatch(fn) {
|
2016-02-29 21:52:30 +03:00
|
|
|
if (typeof fn != "function") {
|
|
|
|
throw new TypeError("Provided dispatch handler is not a function");
|
|
|
|
}
|
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
return msg => {
|
|
|
|
const id = msg.json.commandID;
|
2015-08-21 17:00:29 +03:00
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
let req = new Promise(resolve => {
|
|
|
|
const args = evaluate.fromJSON(msg.json, seenEls, curContainer.frame);
|
2015-04-15 14:18:00 +03:00
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
let rv;
|
|
|
|
if (typeof args == "undefined" || args instanceof Array) {
|
|
|
|
rv = fn.apply(null, args);
|
2015-04-15 14:18:00 +03:00
|
|
|
} else {
|
2017-10-05 19:57:17 +03:00
|
|
|
rv = fn(args);
|
2015-04-15 14:18:00 +03:00
|
|
|
}
|
2017-10-05 19:57:17 +03:00
|
|
|
resolve(rv);
|
|
|
|
});
|
2015-08-21 17:00:29 +03:00
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
req
|
2020-03-14 02:38:52 +03:00
|
|
|
.then(
|
|
|
|
rv => sendResponse(rv, id),
|
|
|
|
err => sendError(err, id)
|
|
|
|
)
|
2017-11-24 21:19:18 +03:00
|
|
|
.catch(err => sendError(err, id));
|
2015-04-15 14:18:00 +03:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-08-30 19:38:23 +03:00
|
|
|
let getActiveElementFn = dispatch(getActiveElement);
|
2020-01-08 19:32:22 +03:00
|
|
|
let getBrowsingContextIdFn = dispatch(getBrowsingContextId);
|
2017-08-30 19:38:23 +03:00
|
|
|
let getElementAttributeFn = dispatch(getElementAttribute);
|
|
|
|
let getElementPropertyFn = dispatch(getElementProperty);
|
|
|
|
let getElementTextFn = dispatch(getElementText);
|
|
|
|
let getElementTagNameFn = dispatch(getElementTagName);
|
|
|
|
let getElementRectFn = dispatch(getElementRect);
|
2020-01-08 19:32:22 +03:00
|
|
|
let getPageSourceFn = dispatch(getPageSource);
|
2019-08-27 16:08:24 +03:00
|
|
|
let getScreenshotRectFn = dispatch(getScreenshotRect);
|
2017-08-30 19:38:23 +03:00
|
|
|
let isElementEnabledFn = dispatch(isElementEnabled);
|
|
|
|
let findElementContentFn = dispatch(findElementContent);
|
|
|
|
let findElementsContentFn = dispatch(findElementsContent);
|
|
|
|
let isElementSelectedFn = dispatch(isElementSelected);
|
|
|
|
let clearElementFn = dispatch(clearElement);
|
|
|
|
let isElementDisplayedFn = dispatch(isElementDisplayed);
|
|
|
|
let getElementValueOfCssPropertyFn = dispatch(getElementValueOfCssProperty);
|
|
|
|
let switchToShadowRootFn = dispatch(switchToShadowRoot);
|
|
|
|
let singleTapFn = dispatch(singleTap);
|
|
|
|
let performActionsFn = dispatch(performActions);
|
|
|
|
let releaseActionsFn = dispatch(releaseActions);
|
|
|
|
let actionChainFn = dispatch(actionChain);
|
|
|
|
let multiActionFn = dispatch(multiAction);
|
|
|
|
let executeFn = dispatch(execute);
|
|
|
|
let executeInSandboxFn = dispatch(executeInSandbox);
|
|
|
|
let sendKeysToElementFn = dispatch(sendKeysToElement);
|
|
|
|
let reftestWaitFn = dispatch(reftestWait);
|
2015-04-15 14:18:00 +03:00
|
|
|
|
2012-03-22 19:19:57 +04:00
|
|
|
function startListeners() {
|
2018-01-09 20:48:57 +03:00
|
|
|
addMessageListener("Marionette:actionChain", actionChainFn);
|
|
|
|
addMessageListener("Marionette:cancelRequest", cancelRequest);
|
2018-01-09 22:48:14 +03:00
|
|
|
addMessageListener("Marionette:clearElement", clearElementFn);
|
|
|
|
addMessageListener("Marionette:clickElement", clickElement);
|
2018-01-09 22:50:30 +03:00
|
|
|
addMessageListener("Marionette:Deregister", deregister);
|
2018-01-09 22:48:14 +03:00
|
|
|
addMessageListener("Marionette:DOM:AddEventListener", domAddEventListener);
|
|
|
|
addMessageListener(
|
|
|
|
"Marionette:DOM:RemoveEventListener",
|
|
|
|
domRemoveEventListener
|
|
|
|
);
|
|
|
|
addMessageListener("Marionette:execute", executeFn);
|
|
|
|
addMessageListener("Marionette:executeInSandbox", executeInSandboxFn);
|
2018-01-09 20:48:57 +03:00
|
|
|
addMessageListener("Marionette:findElementContent", findElementContentFn);
|
2018-01-09 22:48:14 +03:00
|
|
|
addMessageListener("Marionette:findElementsContent", findElementsContentFn);
|
2018-01-09 20:48:57 +03:00
|
|
|
addMessageListener("Marionette:getActiveElement", getActiveElementFn);
|
2020-01-08 19:32:22 +03:00
|
|
|
addMessageListener("Marionette:getBrowsingContextId", getBrowsingContextIdFn);
|
2018-01-09 22:48:14 +03:00
|
|
|
addMessageListener("Marionette:getElementAttribute", getElementAttributeFn);
|
2018-01-09 20:48:57 +03:00
|
|
|
addMessageListener("Marionette:getElementProperty", getElementPropertyFn);
|
2018-01-09 22:48:14 +03:00
|
|
|
addMessageListener("Marionette:getElementRect", getElementRectFn);
|
2018-01-09 20:48:57 +03:00
|
|
|
addMessageListener("Marionette:getElementTagName", getElementTagNameFn);
|
2018-01-09 22:48:14 +03:00
|
|
|
addMessageListener("Marionette:getElementText", getElementTextFn);
|
|
|
|
addMessageListener(
|
|
|
|
"Marionette:getElementValueOfCssProperty",
|
|
|
|
getElementValueOfCssPropertyFn
|
|
|
|
);
|
|
|
|
addMessageListener("Marionette:getPageSource", getPageSourceFn);
|
2019-08-27 16:08:24 +03:00
|
|
|
addMessageListener("Marionette:getScreenshotRect", getScreenshotRectFn);
|
2018-01-09 22:48:14 +03:00
|
|
|
addMessageListener("Marionette:goBack", goBack);
|
|
|
|
addMessageListener("Marionette:goForward", goForward);
|
2018-01-09 20:48:57 +03:00
|
|
|
addMessageListener("Marionette:isElementDisplayed", isElementDisplayedFn);
|
|
|
|
addMessageListener("Marionette:isElementEnabled", isElementEnabledFn);
|
|
|
|
addMessageListener("Marionette:isElementSelected", isElementSelectedFn);
|
2018-01-09 22:48:14 +03:00
|
|
|
addMessageListener("Marionette:multiAction", multiActionFn);
|
2020-07-20 01:54:07 +03:00
|
|
|
addMessageListener("Marionette:navigateTo", navigateTo);
|
2018-01-09 22:48:14 +03:00
|
|
|
addMessageListener("Marionette:performActions", performActionsFn);
|
|
|
|
addMessageListener("Marionette:refresh", refresh);
|
|
|
|
addMessageListener("Marionette:reftestWait", reftestWaitFn);
|
|
|
|
addMessageListener("Marionette:releaseActions", releaseActionsFn);
|
2018-01-09 20:48:57 +03:00
|
|
|
addMessageListener("Marionette:sendKeysToElement", sendKeysToElementFn);
|
2018-01-12 16:30:23 +03:00
|
|
|
addMessageListener("Marionette:Session:Delete", deleteSession);
|
2018-01-09 22:48:14 +03:00
|
|
|
addMessageListener("Marionette:singleTap", singleTapFn);
|
2018-01-09 20:48:57 +03:00
|
|
|
addMessageListener("Marionette:switchToFrame", switchToFrame);
|
|
|
|
addMessageListener("Marionette:switchToParentFrame", switchToParentFrame);
|
|
|
|
addMessageListener("Marionette:switchToShadowRoot", switchToShadowRootFn);
|
2018-01-09 22:48:14 +03:00
|
|
|
addMessageListener("Marionette:waitForPageLoaded", waitForPageLoaded);
|
2012-03-22 19:19:57 +04:00
|
|
|
}
|
|
|
|
|
2018-01-09 22:50:30 +03:00
|
|
|
function deregister() {
|
2018-01-09 22:48:14 +03:00
|
|
|
removeMessageListener("Marionette:actionChain", actionChainFn);
|
|
|
|
removeMessageListener("Marionette:cancelRequest", cancelRequest);
|
|
|
|
removeMessageListener("Marionette:clearElement", clearElementFn);
|
|
|
|
removeMessageListener("Marionette:clickElement", clickElement);
|
2018-01-09 22:50:30 +03:00
|
|
|
removeMessageListener("Marionette:Deregister", deregister);
|
2018-01-09 20:48:57 +03:00
|
|
|
removeMessageListener("Marionette:execute", executeFn);
|
|
|
|
removeMessageListener("Marionette:executeInSandbox", executeInSandboxFn);
|
2018-01-09 22:48:14 +03:00
|
|
|
removeMessageListener("Marionette:findElementContent", findElementContentFn);
|
|
|
|
removeMessageListener(
|
|
|
|
"Marionette:findElementsContent",
|
|
|
|
findElementsContentFn
|
|
|
|
);
|
|
|
|
removeMessageListener("Marionette:getActiveElement", getActiveElementFn);
|
2020-01-08 19:32:22 +03:00
|
|
|
removeMessageListener(
|
|
|
|
"Marionette:getBrowsingContextId",
|
|
|
|
getBrowsingContextIdFn
|
|
|
|
);
|
2018-01-09 22:48:14 +03:00
|
|
|
removeMessageListener(
|
|
|
|
"Marionette:getElementAttribute",
|
|
|
|
getElementAttributeFn
|
|
|
|
);
|
|
|
|
removeMessageListener("Marionette:getElementProperty", getElementPropertyFn);
|
|
|
|
removeMessageListener("Marionette:getElementRect", getElementRectFn);
|
|
|
|
removeMessageListener("Marionette:getElementTagName", getElementTagNameFn);
|
|
|
|
removeMessageListener("Marionette:getElementText", getElementTextFn);
|
|
|
|
removeMessageListener(
|
|
|
|
"Marionette:getElementValueOfCssProperty",
|
|
|
|
getElementValueOfCssPropertyFn
|
|
|
|
);
|
2018-01-09 20:48:57 +03:00
|
|
|
removeMessageListener("Marionette:getPageSource", getPageSourceFn);
|
2019-08-27 16:08:24 +03:00
|
|
|
removeMessageListener("Marionette:getScreenshotRect", getScreenshotRectFn);
|
2018-01-09 20:48:57 +03:00
|
|
|
removeMessageListener("Marionette:goBack", goBack);
|
|
|
|
removeMessageListener("Marionette:goForward", goForward);
|
2018-01-09 22:48:14 +03:00
|
|
|
removeMessageListener("Marionette:isElementDisplayed", isElementDisplayedFn);
|
2018-01-09 20:48:57 +03:00
|
|
|
removeMessageListener("Marionette:isElementEnabled", isElementEnabledFn);
|
2018-01-09 22:48:14 +03:00
|
|
|
removeMessageListener("Marionette:isElementSelected", isElementSelectedFn);
|
|
|
|
removeMessageListener("Marionette:multiAction", multiActionFn);
|
2020-07-20 01:54:07 +03:00
|
|
|
removeMessageListener("Marionette:navigateTo", navigateTo);
|
2018-01-09 22:48:14 +03:00
|
|
|
removeMessageListener("Marionette:performActions", performActionsFn);
|
|
|
|
removeMessageListener("Marionette:refresh", refresh);
|
|
|
|
removeMessageListener("Marionette:releaseActions", releaseActionsFn);
|
|
|
|
removeMessageListener("Marionette:sendKeysToElement", sendKeysToElementFn);
|
2018-01-12 16:30:23 +03:00
|
|
|
removeMessageListener("Marionette:Session:Delete", deleteSession);
|
2018-01-09 22:48:14 +03:00
|
|
|
removeMessageListener("Marionette:singleTap", singleTapFn);
|
2018-01-09 20:48:57 +03:00
|
|
|
removeMessageListener("Marionette:switchToFrame", switchToFrame);
|
2018-01-09 22:48:14 +03:00
|
|
|
removeMessageListener("Marionette:switchToParentFrame", switchToParentFrame);
|
|
|
|
removeMessageListener("Marionette:switchToShadowRoot", switchToShadowRootFn);
|
|
|
|
removeMessageListener("Marionette:waitForPageLoaded", waitForPageLoaded);
|
2018-01-12 16:30:23 +03:00
|
|
|
}
|
2017-05-03 17:46:58 +03:00
|
|
|
|
2018-01-12 16:30:23 +03:00
|
|
|
function deleteSession() {
|
2016-05-20 18:54:42 +03:00
|
|
|
seenEls.clear();
|
2020-06-05 09:09:27 +03:00
|
|
|
|
2015-08-28 23:43:54 +03:00
|
|
|
// reset container frame to the top-most frame
|
2020-06-05 09:09:27 +03:00
|
|
|
curContainer.frame = content;
|
2015-08-28 23:43:54 +03:00
|
|
|
curContainer.frame.focus();
|
2020-06-05 09:09:27 +03:00
|
|
|
|
2016-11-29 19:00:52 +03:00
|
|
|
legacyactions.touchIds = {};
|
2016-12-14 02:29:48 +03:00
|
|
|
if (action.inputStateMap !== undefined) {
|
|
|
|
action.inputStateMap.clear();
|
|
|
|
}
|
|
|
|
if (action.inputsToCancel !== undefined) {
|
|
|
|
action.inputsToCancel.length = 0;
|
|
|
|
}
|
2012-03-22 19:19:57 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-01-29 15:57:46 +03:00
|
|
|
* Send asynchronous reply to chrome.
|
|
|
|
*
|
|
|
|
* @param {UUID} uuid
|
|
|
|
* Unique identifier of the request.
|
|
|
|
* @param {AsyncContentSender.ResponseType} type
|
|
|
|
* Type of response.
|
2017-07-26 15:11:53 +03:00
|
|
|
* @param {*} [Object] data
|
2016-01-29 15:57:46 +03:00
|
|
|
* JSON serialisable object to accompany the message. Defaults to
|
|
|
|
* an empty dictionary.
|
2012-03-22 19:19:57 +04:00
|
|
|
*/
|
2018-08-11 00:03:18 +03:00
|
|
|
let sendToServer = (uuid, data = undefined) => {
|
2018-01-09 18:53:46 +03:00
|
|
|
let channel = new proxy.AsyncMessageChannel(sendAsyncMessage.bind(this));
|
2016-01-29 15:57:46 +03:00
|
|
|
channel.reply(uuid, data);
|
2018-08-11 00:03:18 +03:00
|
|
|
};
|
2012-03-22 19:19:57 +04:00
|
|
|
|
|
|
|
/**
|
2016-01-29 15:57:46 +03:00
|
|
|
* Send asynchronous reply with value to chrome.
|
|
|
|
*
|
2017-07-26 15:11:53 +03:00
|
|
|
* @param {Object} obj
|
2016-01-29 15:57:46 +03:00
|
|
|
* JSON serialisable object of arbitrary type and complexity.
|
|
|
|
* @param {UUID} uuid
|
|
|
|
* Unique identifier of the request.
|
2012-03-22 19:19:57 +04:00
|
|
|
*/
|
2017-03-28 22:41:38 +03:00
|
|
|
function sendResponse(obj, uuid) {
|
2017-10-05 19:57:17 +03:00
|
|
|
let payload = evaluate.toJSON(obj, seenEls);
|
|
|
|
sendToServer(uuid, payload);
|
2012-03-22 19:19:57 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-01-29 15:57:46 +03:00
|
|
|
* Send asynchronous reply to chrome.
|
|
|
|
*
|
|
|
|
* @param {UUID} uuid
|
|
|
|
* Unique identifier of the request.
|
2012-03-22 19:19:57 +04:00
|
|
|
*/
|
2016-01-29 15:57:46 +03:00
|
|
|
function sendOk(uuid) {
|
|
|
|
sendToServer(uuid);
|
2012-03-22 19:19:57 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-01-29 15:57:46 +03:00
|
|
|
* Send asynchronous error reply to chrome.
|
|
|
|
*
|
|
|
|
* @param {Error} err
|
|
|
|
* Error to notify chrome of.
|
|
|
|
* @param {UUID} uuid
|
|
|
|
* Unique identifier of the request.
|
2012-03-22 19:19:57 +04:00
|
|
|
*/
|
2016-01-29 15:57:46 +03:00
|
|
|
function sendError(err, uuid) {
|
|
|
|
sendToServer(uuid, err);
|
2012-03-22 19:19:57 +04:00
|
|
|
}
|
|
|
|
|
2018-03-02 16:25:59 +03:00
|
|
|
async function 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
|
|
|
let sb = sandbox.createMutable(curContainer.frame);
|
2017-10-05 19:57:17 +03:00
|
|
|
return evaluate.sandbox(sb, script, args, opts);
|
2012-03-22 19:19:57 +04:00
|
|
|
}
|
|
|
|
|
2018-03-02 16:25:59 +03:00
|
|
|
async function executeInSandbox(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
|
|
|
let sb = sandboxes.get(opts.sandboxName, opts.newSandbox);
|
2017-10-05 19:57:17 +03:00
|
|
|
return evaluate.sandbox(sb, 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-02-29 21:52:30 +03:00
|
|
|
|
2014-06-10 19:34:27 +04:00
|
|
|
function emitTouchEvent(type, touch) {
|
2017-12-05 22:22:53 +03:00
|
|
|
logger.info(
|
|
|
|
`Emitting Touch event of type ${type} ` +
|
|
|
|
`to element with id: ${touch.target.id} ` +
|
|
|
|
`and tag name: ${touch.target.tagName} ` +
|
|
|
|
`at coordinates (${touch.clientX}), ` +
|
|
|
|
`${touch.clientY}) relative to the viewport`
|
|
|
|
);
|
|
|
|
|
|
|
|
const win = curContainer.frame;
|
2020-06-05 09:09:27 +03:00
|
|
|
if (win.docShell.asyncPanZoomEnabled && legacyactions.scrolling) {
|
2017-12-05 22:40:29 +03:00
|
|
|
let ev = {
|
|
|
|
index: 0,
|
|
|
|
type,
|
|
|
|
id: touch.identifier,
|
|
|
|
clientX: touch.clientX,
|
|
|
|
clientY: touch.clientY,
|
|
|
|
screenX: touch.screenX,
|
|
|
|
screenY: touch.screenY,
|
|
|
|
radiusX: touch.radiusX,
|
|
|
|
radiusY: touch.radiusY,
|
|
|
|
rotation: touch.rotationAngle,
|
|
|
|
force: touch.force,
|
|
|
|
};
|
|
|
|
sendSyncMessage("Marionette:emitTouchEvent", ev);
|
|
|
|
return;
|
2013-09-19 21:35:19 +04:00
|
|
|
}
|
2017-12-05 22:22:53 +03:00
|
|
|
|
|
|
|
// we get here if we're not in asyncPacZoomEnabled land, or if we're
|
|
|
|
// the main process
|
2018-07-25 02:47:43 +03:00
|
|
|
win.windowUtils.sendTouchEvent(
|
2017-12-05 22:22:53 +03:00
|
|
|
type,
|
|
|
|
[touch.identifier],
|
|
|
|
[touch.clientX],
|
|
|
|
[touch.clientY],
|
|
|
|
[touch.radiusX],
|
|
|
|
[touch.radiusY],
|
|
|
|
[touch.rotationAngle],
|
|
|
|
[touch.force],
|
|
|
|
0
|
|
|
|
);
|
2013-01-22 23:27:44 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function that perform a single tap
|
|
|
|
*/
|
2017-10-05 19:57:17 +03:00
|
|
|
async function singleTap(el, corx, cory) {
|
2015-09-10 18:45:33 +03:00
|
|
|
// after this block, the element will be scrolled into view
|
2016-03-03 17:20:39 +03:00
|
|
|
let visible = element.isVisible(el, corx, cory);
|
2015-09-10 18:45:33 +03:00
|
|
|
if (!visible) {
|
2017-06-30 02:40:24 +03:00
|
|
|
throw new ElementNotInteractableError(
|
|
|
|
"Element is not currently visible and may not be manipulated"
|
|
|
|
);
|
2015-09-10 18:45:33 +03:00
|
|
|
}
|
2016-03-03 16:58:13 +03:00
|
|
|
|
2016-12-31 15:30:49 +03:00
|
|
|
let a11y = accessibility.get(capabilities.get("moz:accessibilityChecks"));
|
2017-08-07 20:59:45 +03:00
|
|
|
let acc = await a11y.getAccessible(el, true);
|
|
|
|
a11y.assertVisible(acc, el, visible);
|
|
|
|
a11y.assertActionable(acc, el);
|
|
|
|
if (!curContainer.frame.document.createTouch) {
|
|
|
|
legacyactions.mouseEventsOnly = true;
|
|
|
|
}
|
|
|
|
let c = element.coordinates(el, corx, cory);
|
|
|
|
if (!legacyactions.mouseEventsOnly) {
|
|
|
|
let touchId = legacyactions.nextTouchId++;
|
|
|
|
let touch = createATouch(el, c.x, c.y, touchId);
|
|
|
|
emitTouchEvent("touchstart", touch);
|
|
|
|
emitTouchEvent("touchend", touch);
|
|
|
|
}
|
|
|
|
legacyactions.mouseTap(el.ownerDocument, c.x, c.y);
|
2013-01-22 23:27:44 +04:00
|
|
|
}
|
|
|
|
|
2013-02-06 23:58:14 +04:00
|
|
|
/**
|
|
|
|
* Function to create a touch based on the element
|
2013-05-27 21:12:13 +04:00
|
|
|
* corx and cory are relative to the viewport, id is the touchId
|
2013-02-06 23:58:14 +04:00
|
|
|
*/
|
2013-05-27 21:12:13 +04:00
|
|
|
function createATouch(el, corx, cory, touchId) {
|
2013-03-05 05:09:58 +04:00
|
|
|
let doc = el.ownerDocument;
|
|
|
|
let win = doc.defaultView;
|
2014-09-22 19:41:51 +04:00
|
|
|
let [
|
|
|
|
clientX,
|
|
|
|
clientY,
|
|
|
|
pageX,
|
|
|
|
pageY,
|
|
|
|
screenX,
|
|
|
|
screenY,
|
2017-06-30 02:40:24 +03:00
|
|
|
] = legacyactions.getCoordinateInfo(el, corx, cory);
|
|
|
|
let atouch = doc.createTouch(
|
|
|
|
win,
|
|
|
|
el,
|
|
|
|
touchId,
|
|
|
|
pageX,
|
|
|
|
pageY,
|
|
|
|
screenX,
|
|
|
|
screenY,
|
|
|
|
clientX,
|
|
|
|
clientY
|
|
|
|
);
|
2013-02-06 23:58:14 +04:00
|
|
|
return atouch;
|
|
|
|
}
|
|
|
|
|
2016-12-14 02:29:48 +03:00
|
|
|
/**
|
|
|
|
* Perform a series of grouped actions at the specified points in time.
|
|
|
|
*
|
|
|
|
* @param {obj} msg
|
|
|
|
* Object with an |actions| attribute that is an Array of objects
|
|
|
|
* each of which represents an action sequence.
|
|
|
|
*/
|
2017-08-07 20:59:45 +03:00
|
|
|
async function performActions(msg) {
|
2017-08-21 20:00:31 +03:00
|
|
|
let chain = action.Chain.fromJSON(msg.actions);
|
2018-01-23 19:31:06 +03:00
|
|
|
await action.dispatch(
|
|
|
|
chain,
|
|
|
|
curContainer.frame,
|
|
|
|
!capabilities.get("moz:useNonSpecCompliantPointerOrigin")
|
|
|
|
);
|
2016-12-14 02:29:48 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-08-07 20:59:45 +03:00
|
|
|
* The release actions command is used to release all the keys and pointer
|
2017-06-30 02:40:24 +03:00
|
|
|
* buttons that are currently depressed. This causes events to be fired
|
|
|
|
* as if the state was released by an explicit series of actions. It also
|
|
|
|
* clears all the internal state of the virtual devices.
|
2016-12-14 02:29:48 +03:00
|
|
|
*/
|
2017-08-07 20:59:45 +03:00
|
|
|
async function releaseActions() {
|
|
|
|
await action.dispatchTickActions(
|
2017-10-09 21:55:27 +03:00
|
|
|
action.inputsToCancel.reverse(),
|
|
|
|
0,
|
|
|
|
curContainer.frame
|
|
|
|
);
|
2016-12-14 02:29:48 +03:00
|
|
|
action.inputsToCancel.length = 0;
|
|
|
|
action.inputStateMap.clear();
|
2018-08-30 12:35:04 +03:00
|
|
|
|
|
|
|
event.DoubleClickTracker.resetClick();
|
2016-12-14 02:29:48 +03:00
|
|
|
}
|
|
|
|
|
2013-03-05 05:09:58 +04:00
|
|
|
/**
|
2015-09-02 15:36:03 +03:00
|
|
|
* Start action chain on one finger.
|
2013-03-05 05:09:58 +04:00
|
|
|
*/
|
2015-09-02 15:36:03 +03:00
|
|
|
function actionChain(chain, touchId) {
|
2015-03-20 04:41:19 +03:00
|
|
|
let touchProvider = {};
|
|
|
|
touchProvider.createATouch = createATouch;
|
|
|
|
touchProvider.emitTouchEvent = emitTouchEvent;
|
|
|
|
|
2016-11-29 19:00:52 +03:00
|
|
|
return legacyactions.dispatchActions(
|
2016-02-22 16:18:13 +03:00
|
|
|
chain,
|
|
|
|
touchId,
|
|
|
|
curContainer,
|
2016-05-20 18:49:19 +03:00
|
|
|
seenEls,
|
2016-02-22 16:18:13 +03:00
|
|
|
touchProvider
|
|
|
|
);
|
2013-03-05 05:09:58 +04:00
|
|
|
}
|
|
|
|
|
2013-03-19 00:42:46 +04:00
|
|
|
function emitMultiEvents(type, touch, touches) {
|
|
|
|
let target = touch.target;
|
|
|
|
let doc = target.ownerDocument;
|
|
|
|
let win = doc.defaultView;
|
|
|
|
// touches that are in the same document
|
2017-06-30 02:40:24 +03:00
|
|
|
let documentTouches = doc.createTouchList(
|
|
|
|
touches.filter(function(t) {
|
|
|
|
return t.target.ownerDocument === doc && type != "touchcancel";
|
2013-03-19 00:42:46 +04:00
|
|
|
})
|
|
|
|
);
|
|
|
|
// touches on the same target
|
2017-06-30 02:40:24 +03:00
|
|
|
let targetTouches = doc.createTouchList(
|
|
|
|
touches.filter(function(t) {
|
|
|
|
return (
|
|
|
|
t.target === target && (type != "touchcancel" || type != "touchend")
|
|
|
|
);
|
2013-03-19 00:42:46 +04:00
|
|
|
})
|
|
|
|
);
|
|
|
|
// Create changed touches
|
|
|
|
let changedTouches = doc.createTouchList(touch);
|
|
|
|
// Create the event object
|
2017-06-30 02:40:24 +03:00
|
|
|
let event = doc.createEvent("TouchEvent");
|
2013-03-19 00:42:46 +04:00
|
|
|
event.initTouchEvent(
|
|
|
|
type,
|
2017-07-29 12:03:00 +03:00
|
|
|
true,
|
|
|
|
true,
|
|
|
|
win,
|
|
|
|
0,
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
documentTouches,
|
|
|
|
targetTouches,
|
|
|
|
changedTouches
|
|
|
|
);
|
2013-03-19 00:42:46 +04:00
|
|
|
target.dispatchEvent(event);
|
|
|
|
}
|
|
|
|
|
2017-06-30 02:40:24 +03:00
|
|
|
function setDispatch(batches, touches, batchIndex = 0) {
|
2013-03-19 00:42:46 +04:00
|
|
|
// check if all the sets have been fired
|
|
|
|
if (batchIndex >= batches.length) {
|
|
|
|
multiLast = {};
|
|
|
|
return;
|
|
|
|
}
|
2015-09-02 16:04:58 +03:00
|
|
|
|
2013-03-19 00:42:46 +04:00
|
|
|
// a set of actions need to be done
|
|
|
|
let batch = batches[batchIndex];
|
|
|
|
// each action for some finger
|
|
|
|
let pack;
|
|
|
|
// the touch id for the finger (pack)
|
|
|
|
let touchId;
|
|
|
|
// command for the finger
|
|
|
|
let command;
|
|
|
|
// touch that will be created for the finger
|
|
|
|
let el;
|
|
|
|
let touch;
|
|
|
|
let lastTouch;
|
|
|
|
let touchIndex;
|
|
|
|
let waitTime = 0;
|
|
|
|
let maxTime = 0;
|
2013-05-27 21:12:13 +04:00
|
|
|
let c;
|
2015-09-02 16:04:58 +03:00
|
|
|
|
2013-03-19 00:42:46 +04:00
|
|
|
// loop through the batch
|
2015-09-02 16:04:58 +03:00
|
|
|
batchIndex++;
|
2013-03-19 00:42:46 +04:00
|
|
|
for (let i = 0; i < batch.length; i++) {
|
|
|
|
pack = batch[i];
|
|
|
|
touchId = pack[0];
|
|
|
|
command = pack[1];
|
2015-09-02 16:04:58 +03:00
|
|
|
|
2013-03-19 00:42:46 +04:00
|
|
|
switch (command) {
|
2015-09-02 16:04:58 +03:00
|
|
|
case "press":
|
2017-10-02 21:09:26 +03:00
|
|
|
el = seenEls.get(pack[2], curContainer.frame);
|
2016-03-03 17:28:40 +03:00
|
|
|
c = element.coordinates(el, pack[3], pack[4]);
|
2013-05-27 21:12:13 +04:00
|
|
|
touch = createATouch(el, c.x, c.y, touchId);
|
2013-03-19 00:42:46 +04:00
|
|
|
multiLast[touchId] = touch;
|
|
|
|
touches.push(touch);
|
2015-09-02 16:04:58 +03:00
|
|
|
emitMultiEvents("touchstart", touch, touches);
|
2013-03-19 00:42:46 +04:00
|
|
|
break;
|
2015-09-02 16:04:58 +03:00
|
|
|
|
|
|
|
case "release":
|
2013-03-19 00:42:46 +04:00
|
|
|
touch = multiLast[touchId];
|
2017-06-30 02:40:24 +03:00
|
|
|
// the index of the previous touch for the finger may change in
|
|
|
|
// the touches array
|
2013-03-19 00:42:46 +04:00
|
|
|
touchIndex = touches.indexOf(touch);
|
|
|
|
touches.splice(touchIndex, 1);
|
2015-09-02 16:04:58 +03:00
|
|
|
emitMultiEvents("touchend", touch, touches);
|
2013-03-19 00:42:46 +04:00
|
|
|
break;
|
2015-09-02 16:04:58 +03:00
|
|
|
|
|
|
|
case "move":
|
2017-10-02 21:09:26 +03:00
|
|
|
el = seenEls.get(pack[2], curContainer.frame);
|
2016-03-03 17:28:40 +03:00
|
|
|
c = element.coordinates(el);
|
2013-05-27 21:12:13 +04:00
|
|
|
touch = createATouch(multiLast[touchId].target, c.x, c.y, touchId);
|
2013-03-19 00:42:46 +04:00
|
|
|
touchIndex = touches.indexOf(lastTouch);
|
|
|
|
touches[touchIndex] = touch;
|
|
|
|
multiLast[touchId] = touch;
|
2015-09-02 16:04:58 +03:00
|
|
|
emitMultiEvents("touchmove", touch, touches);
|
2013-03-19 00:42:46 +04:00
|
|
|
break;
|
2015-09-02 16:04:58 +03:00
|
|
|
|
|
|
|
case "moveByOffset":
|
2013-03-19 00:42:46 +04:00
|
|
|
el = multiLast[touchId].target;
|
|
|
|
lastTouch = multiLast[touchId];
|
|
|
|
touchIndex = touches.indexOf(lastTouch);
|
|
|
|
let doc = el.ownerDocument;
|
|
|
|
let win = doc.defaultView;
|
2017-06-30 02:40:24 +03:00
|
|
|
// since x and y are relative to the last touch, therefore,
|
|
|
|
// it's relative to the position of the last touch
|
|
|
|
let clientX = lastTouch.clientX + pack[2];
|
|
|
|
let clientY = lastTouch.clientY + pack[3];
|
|
|
|
let pageX = clientX + win.pageXOffset;
|
|
|
|
let pageY = clientY + win.pageYOffset;
|
|
|
|
let screenX = clientX + win.mozInnerScreenX;
|
|
|
|
let screenY = clientY + win.mozInnerScreenY;
|
|
|
|
touch = doc.createTouch(
|
|
|
|
win,
|
|
|
|
el,
|
|
|
|
touchId,
|
|
|
|
pageX,
|
|
|
|
pageY,
|
|
|
|
screenX,
|
|
|
|
screenY,
|
|
|
|
clientX,
|
|
|
|
clientY
|
|
|
|
);
|
2013-03-19 00:42:46 +04:00
|
|
|
touches[touchIndex] = touch;
|
|
|
|
multiLast[touchId] = touch;
|
2015-09-02 16:04:58 +03:00
|
|
|
emitMultiEvents("touchmove", touch, touches);
|
2013-03-19 00:42:46 +04:00
|
|
|
break;
|
2015-09-02 16:04:58 +03:00
|
|
|
|
|
|
|
case "wait":
|
|
|
|
if (typeof pack[2] != "undefined") {
|
|
|
|
waitTime = pack[2] * 1000;
|
2013-03-19 00:42:46 +04:00
|
|
|
if (waitTime > maxTime) {
|
|
|
|
maxTime = waitTime;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2015-09-02 16:04:58 +03:00
|
|
|
}
|
2013-03-19 00:42:46 +04:00
|
|
|
}
|
2015-09-02 16:04:58 +03:00
|
|
|
|
|
|
|
if (maxTime != 0) {
|
2017-04-19 14:22:13 +03:00
|
|
|
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
|
|
|
timer.initWithCallback(
|
|
|
|
function() {
|
2015-09-02 16:04:58 +03:00
|
|
|
setDispatch(batches, touches, batchIndex);
|
|
|
|
},
|
|
|
|
maxTime,
|
|
|
|
Ci.nsITimer.TYPE_ONE_SHOT
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
setDispatch(batches, touches, batchIndex);
|
2013-03-19 00:42:46 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-09-02 16:04:58 +03:00
|
|
|
* Start multi-action.
|
|
|
|
*
|
|
|
|
* @param {Number} maxLen
|
|
|
|
* Longest action chain for one finger.
|
2013-03-19 00:42:46 +04:00
|
|
|
*/
|
2015-09-02 16:04:58 +03:00
|
|
|
function multiAction(args, maxLen) {
|
|
|
|
// unwrap the original nested array
|
2017-10-05 19:57:17 +03:00
|
|
|
let commandArray = evaluate.fromJSON(args, seenEls, curContainer.frame);
|
2015-09-02 16:04:58 +03:00
|
|
|
let concurrentEvent = [];
|
|
|
|
let temp;
|
|
|
|
for (let i = 0; i < maxLen; i++) {
|
|
|
|
let row = [];
|
|
|
|
for (let j = 0; j < commandArray.length; j++) {
|
|
|
|
if (typeof commandArray[j][i] != "undefined") {
|
2017-06-30 02:40:24 +03:00
|
|
|
// add finger id to the front of each action,
|
|
|
|
// i.e. [finger_id, action, element]
|
2015-09-02 16:04:58 +03:00
|
|
|
temp = commandArray[j][i];
|
|
|
|
temp.unshift(j);
|
|
|
|
row.push(temp);
|
2013-03-19 00:42:46 +04:00
|
|
|
}
|
|
|
|
}
|
2015-09-02 16:04:58 +03:00
|
|
|
concurrentEvent.push(row);
|
2013-03-19 00:42:46 +04:00
|
|
|
}
|
2015-09-02 16:04:58 +03:00
|
|
|
|
2017-06-30 02:40:24 +03:00
|
|
|
// Now concurrent event is made of sets where each set contain a list
|
|
|
|
// of actions that need to be fired.
|
|
|
|
//
|
|
|
|
// But note that each action belongs to a different finger
|
|
|
|
// pendingTouches keeps track of current touches that's on the screen.
|
2015-09-02 16:04:58 +03:00
|
|
|
let pendingTouches = [];
|
|
|
|
setDispatch(concurrentEvent, pendingTouches);
|
2013-03-19 00:42:46 +04:00
|
|
|
}
|
|
|
|
|
2017-03-28 22:41:38 +03:00
|
|
|
/**
|
|
|
|
* Cancel the polling and remove the event listener associated with a
|
|
|
|
* current navigation request in case we're interupted by an onbeforeunload
|
|
|
|
* handler and navigation doesn't complete.
|
|
|
|
*/
|
|
|
|
function cancelRequest() {
|
|
|
|
loadListener.stop();
|
|
|
|
}
|
|
|
|
|
2017-03-09 13:21:30 +03:00
|
|
|
/**
|
2017-07-06 19:02:19 +03:00
|
|
|
* This implements the latter part of a get request (for the case we need
|
2018-11-06 15:08:55 +03:00
|
|
|
* to resume one when the frame script has been moved to a different content
|
|
|
|
* process in the middle of a navigate request). This is most of of the work
|
|
|
|
* of a navigate request, but doesn't assume DOMContentLoaded is yet to fire.
|
2017-03-09 13:21:30 +03:00
|
|
|
*
|
2017-08-19 16:22:17 +03:00
|
|
|
* @param {number} commandID
|
2017-06-30 02:40:24 +03:00
|
|
|
* ID of the currently handled message between the driver and
|
|
|
|
* listener.
|
2017-03-09 13:21:30 +03:00
|
|
|
* @param {number} pageTimeout
|
2017-06-30 02:40:24 +03:00
|
|
|
* Timeout in seconds the method has to wait for the page being
|
|
|
|
* finished loading.
|
2017-03-09 13:21:30 +03:00
|
|
|
* @param {number} startTime
|
2018-11-06 15:08:55 +03:00
|
|
|
* Unix timestap when the navitation request got triggered.
|
2012-03-22 19:19:57 +04:00
|
|
|
*/
|
2017-03-28 22:41:38 +03:00
|
|
|
function waitForPageLoaded(msg) {
|
2017-08-19 16:22:17 +03:00
|
|
|
let { commandID, pageTimeout, startTime } = msg.json;
|
2017-07-06 19:02:19 +03:00
|
|
|
loadListener.waitForLoadAfterFramescriptReload(
|
2017-08-19 16:22:17 +03:00
|
|
|
commandID,
|
|
|
|
pageTimeout,
|
|
|
|
startTime
|
|
|
|
);
|
2015-03-11 02:19:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Navigate to the given URL. The operation will be performed on the
|
2015-05-04 13:25:03 +03:00
|
|
|
* current browsing context, which means it handles the case where we
|
2017-06-30 02:40:24 +03:00
|
|
|
* navigate within an iframe. All other navigation is handled by the driver
|
|
|
|
* (in chrome space).
|
2015-03-11 02:19:40 +03:00
|
|
|
*/
|
2020-07-20 01:53:58 +03:00
|
|
|
async function navigateTo(msg) {
|
2020-07-20 11:26:51 +03:00
|
|
|
let { commandID, pageTimeout, url, loadEventExpected } = msg.json;
|
2017-03-09 13:21:30 +03:00
|
|
|
|
2017-04-13 11:26:02 +03:00
|
|
|
try {
|
2020-07-20 01:53:58 +03:00
|
|
|
await loadListener.navigate(
|
2017-04-13 11:26:02 +03:00
|
|
|
() => {
|
|
|
|
curContainer.frame.location = url;
|
2017-08-19 16:22:17 +03:00
|
|
|
},
|
|
|
|
commandID,
|
|
|
|
pageTimeout,
|
|
|
|
loadEventExpected
|
|
|
|
);
|
2017-04-13 11:26:02 +03:00
|
|
|
} catch (e) {
|
2017-08-19 16:22:17 +03:00
|
|
|
sendError(e, commandID);
|
2017-04-13 11:26:02 +03:00
|
|
|
}
|
2017-03-06 16:14:21 +03:00
|
|
|
}
|
|
|
|
|
2012-03-22 19:19:57 +04:00
|
|
|
/**
|
2015-08-21 13:19:53 +03:00
|
|
|
* Cause the browser to traverse one step backward in the joint history
|
2017-03-06 16:14:21 +03:00
|
|
|
* of the current browsing context.
|
|
|
|
*
|
2017-08-19 16:22:17 +03:00
|
|
|
* @param {number} commandID
|
2017-06-30 02:40:24 +03:00
|
|
|
* ID of the currently handled message between the driver and
|
|
|
|
* listener.
|
2017-03-06 16:14:21 +03:00
|
|
|
* @param {number} pageTimeout
|
2017-06-30 02:40:24 +03:00
|
|
|
* Timeout in milliseconds the method has to wait for the page being
|
|
|
|
* finished loading.
|
2012-03-22 19:19:57 +04:00
|
|
|
*/
|
2020-07-20 01:53:58 +03:00
|
|
|
async function goBack(msg) {
|
2017-08-19 16:22:17 +03:00
|
|
|
let { commandID, pageTimeout } = msg.json;
|
2017-03-06 16:14:21 +03:00
|
|
|
|
2017-04-13 11:26:02 +03:00
|
|
|
try {
|
2020-07-20 01:53:58 +03:00
|
|
|
await loadListener.navigate(
|
2017-04-13 11:26:02 +03:00
|
|
|
() => {
|
|
|
|
curContainer.frame.history.back();
|
2017-08-19 16:22:17 +03:00
|
|
|
},
|
|
|
|
commandID,
|
|
|
|
pageTimeout
|
|
|
|
);
|
2017-04-13 11:26:02 +03:00
|
|
|
} catch (e) {
|
2017-08-19 16:22:17 +03:00
|
|
|
sendError(e, commandID);
|
2017-04-13 11:26:02 +03:00
|
|
|
}
|
2012-03-22 19:19:57 +04: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-08-19 16:22:17 +03:00
|
|
|
* @param {number} commandID
|
2017-06-30 02:40:24 +03:00
|
|
|
* ID of the currently handled message between the driver and
|
|
|
|
* listener.
|
2017-03-06 16:14:21 +03:00
|
|
|
* @param {number} pageTimeout
|
2017-06-30 02:40:24 +03:00
|
|
|
* Timeout in milliseconds the method has to wait for the page being
|
|
|
|
* finished loading.
|
2012-03-22 19:19:57 +04:00
|
|
|
*/
|
2020-07-20 01:53:58 +03:00
|
|
|
async function goForward(msg) {
|
2017-08-19 16:22:17 +03:00
|
|
|
let { commandID, pageTimeout } = msg.json;
|
2017-03-06 16:14:21 +03:00
|
|
|
|
2017-04-13 11:26:02 +03:00
|
|
|
try {
|
2020-07-20 01:53:58 +03:00
|
|
|
await loadListener.navigate(
|
2017-04-13 11:26:02 +03:00
|
|
|
() => {
|
|
|
|
curContainer.frame.history.forward();
|
2017-08-19 16:22:17 +03:00
|
|
|
},
|
|
|
|
commandID,
|
|
|
|
pageTimeout
|
|
|
|
);
|
2017-04-13 11:26:02 +03:00
|
|
|
} catch (e) {
|
2017-08-19 16:22:17 +03:00
|
|
|
sendError(e, commandID);
|
2017-04-13 11:26:02 +03:00
|
|
|
}
|
2017-03-28 22:41:38 +03:00
|
|
|
}
|
|
|
|
|
2017-03-27 17:16:36 +03:00
|
|
|
/**
|
2017-06-30 02:40:24 +03:00
|
|
|
* Causes the browser to reload the page in in current top-level browsing
|
|
|
|
* context.
|
2017-03-27 17:16:36 +03:00
|
|
|
*
|
2017-08-19 16:22:17 +03:00
|
|
|
* @param {number} commandID
|
2017-06-30 02:40:24 +03:00
|
|
|
* ID of the currently handled message between the driver and
|
|
|
|
* listener.
|
2017-03-27 17:16:36 +03:00
|
|
|
* @param {number} pageTimeout
|
2017-06-30 02:40:24 +03:00
|
|
|
* Timeout in milliseconds the method has to wait for the page being
|
|
|
|
* finished loading.
|
2017-03-27 17:16:36 +03:00
|
|
|
*/
|
2020-07-20 01:53:58 +03:00
|
|
|
async function refresh(msg) {
|
2017-08-19 16:22:17 +03:00
|
|
|
let { commandID, pageTimeout } = msg.json;
|
2017-03-27 17:16:36 +03:00
|
|
|
|
2017-04-13 11:26:02 +03:00
|
|
|
try {
|
|
|
|
// We need to move to the top frame before navigating
|
|
|
|
sendSyncMessage("Marionette:switchedToFrame", { frameValue: null });
|
|
|
|
curContainer.frame = content;
|
2017-03-27 17:16:36 +03:00
|
|
|
|
2020-07-20 01:53:58 +03:00
|
|
|
await loadListener.navigate(
|
2017-04-13 11:26:02 +03:00
|
|
|
() => {
|
|
|
|
curContainer.frame.location.reload(true);
|
2017-08-19 16:22:17 +03:00
|
|
|
},
|
|
|
|
commandID,
|
|
|
|
pageTimeout
|
|
|
|
);
|
2017-04-13 11:26:02 +03:00
|
|
|
} catch (e) {
|
2017-08-19 16:22:17 +03:00
|
|
|
sendError(e, commandID);
|
2017-04-13 11:26:02 +03:00
|
|
|
}
|
2017-03-27 17:16:36 +03:00
|
|
|
}
|
|
|
|
|
2017-03-28 22:41:38 +03:00
|
|
|
/**
|
|
|
|
* Get source of the current browsing context's DOM.
|
|
|
|
*/
|
|
|
|
function getPageSource() {
|
|
|
|
return curContainer.frame.document.documentElement.outerHTML;
|
2012-03-22 19:19:57 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-08-21 17:05:13 +03:00
|
|
|
* Find an element in the current browsing context's document using the
|
|
|
|
* given search strategy.
|
2012-03-22 19:19:57 +04:00
|
|
|
*/
|
2017-08-07 20:59:45 +03:00
|
|
|
async function findElementContent(strategy, selector, opts = {}) {
|
2016-02-23 18:19:21 +03:00
|
|
|
opts.all = false;
|
2017-08-07 20:59:45 +03:00
|
|
|
let el = await element.find(curContainer, strategy, selector, opts);
|
2017-10-05 19:57:17 +03:00
|
|
|
return seenEls.add(el);
|
2012-03-22 19:19:57 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-08-21 17:03:03 +03:00
|
|
|
* Find elements in the current browsing context's document using the
|
|
|
|
* given search strategy.
|
2012-03-22 19:19:57 +04:00
|
|
|
*/
|
2017-08-07 20:59:45 +03:00
|
|
|
async function findElementsContent(strategy, selector, opts = {}) {
|
2016-02-23 18:19:21 +03:00
|
|
|
opts.all = true;
|
2017-08-07 20:59:45 +03:00
|
|
|
let els = await element.find(curContainer, strategy, selector, opts);
|
2017-10-05 19:57:17 +03:00
|
|
|
let webEls = seenEls.addAll(els);
|
2016-05-20 15:28:27 +03:00
|
|
|
return webEls;
|
2012-03-22 19:19:57 +04:00
|
|
|
}
|
|
|
|
|
2017-11-24 19:23:02 +03:00
|
|
|
/**
|
|
|
|
* Return the active element in the document.
|
|
|
|
*
|
|
|
|
* @return {WebElement}
|
|
|
|
* Active element of the current browsing context's document
|
|
|
|
* element, if the document element is non-null.
|
|
|
|
*
|
|
|
|
* @throws {NoSuchElementError}
|
|
|
|
* If the document does not have an active element, i.e. if
|
|
|
|
* its document element has been deleted.
|
|
|
|
*/
|
2015-04-15 14:18:00 +03:00
|
|
|
function getActiveElement() {
|
2015-08-28 23:43:54 +03:00
|
|
|
let el = curContainer.frame.document.activeElement;
|
2017-11-24 19:23:02 +03:00
|
|
|
if (!el) {
|
|
|
|
throw new NoSuchElementError();
|
|
|
|
}
|
2017-05-08 19:05:20 +03:00
|
|
|
return evaluate.toJSON(el, seenEls);
|
2013-01-14 18:57:54 +04:00
|
|
|
}
|
|
|
|
|
2020-01-08 19:32:22 +03:00
|
|
|
/**
|
|
|
|
* Return the current browsing context id.
|
|
|
|
*
|
|
|
|
* @param {boolean=} topContext
|
|
|
|
* If set to true use the window's top-level browsing context,
|
|
|
|
* otherwise the one from the currently selected frame. Defaults to false.
|
|
|
|
*
|
|
|
|
* @return {number}
|
|
|
|
* Id of the browsing context.
|
|
|
|
*/
|
|
|
|
function getBrowsingContextId(topContext = false) {
|
|
|
|
if (topContext) {
|
|
|
|
return content.docShell.browsingContext.id;
|
|
|
|
}
|
|
|
|
|
|
|
|
return curContainer.frame.docShell.browsingContext.id;
|
|
|
|
}
|
|
|
|
|
2012-03-22 19:19:57 +04:00
|
|
|
/**
|
2015-04-15 14:18:00 +03:00
|
|
|
* Send click event to element.
|
|
|
|
*
|
2017-08-19 16:22:17 +03:00
|
|
|
* @param {number} commandID
|
2017-06-30 02:40:24 +03:00
|
|
|
* ID of the currently handled message between the driver and
|
|
|
|
* listener.
|
2019-08-27 16:01:01 +03:00
|
|
|
* @param {WebElement} webElRef
|
2015-04-15 14:18:00 +03:00
|
|
|
* Reference to the web element to click.
|
2017-04-20 13:12:27 +03:00
|
|
|
* @param {number} pageTimeout
|
2017-06-30 02:40:24 +03:00
|
|
|
* Timeout in milliseconds the method has to wait for the page being
|
|
|
|
* finished loading.
|
2015-04-15 14:18:00 +03:00
|
|
|
*/
|
2020-07-20 01:53:58 +03:00
|
|
|
async function clickElement(msg) {
|
2017-10-05 19:57:17 +03:00
|
|
|
let { commandID, webElRef, pageTimeout } = msg.json;
|
2017-04-20 13:12:27 +03:00
|
|
|
|
|
|
|
try {
|
2017-11-01 21:06:21 +03:00
|
|
|
let webEl = WebElement.fromJSON(webElRef);
|
|
|
|
let el = seenEls.get(webEl, curContainer.frame);
|
2017-04-20 13:12:27 +03:00
|
|
|
|
2017-11-01 21:06:21 +03:00
|
|
|
let loadEventExpected = true;
|
2017-10-05 19:57:17 +03:00
|
|
|
let target = getElementAttribute(el, "target");
|
2017-04-20 13:12:27 +03:00
|
|
|
|
|
|
|
if (target === "_blank") {
|
|
|
|
loadEventExpected = false;
|
|
|
|
}
|
|
|
|
|
2020-07-20 01:53:58 +03:00
|
|
|
await loadListener.navigate(
|
2017-04-20 13:12:27 +03:00
|
|
|
() => {
|
|
|
|
return interaction.clickElement(
|
2017-10-05 19:57:17 +03:00
|
|
|
el,
|
2017-07-29 12:03:00 +03:00
|
|
|
capabilities.get("moz:accessibilityChecks"),
|
2017-09-01 18:11:35 +03:00
|
|
|
capabilities.get("moz:webdriverClick")
|
2017-04-20 13:12:27 +03:00
|
|
|
);
|
2017-08-19 16:22:17 +03:00
|
|
|
},
|
|
|
|
commandID,
|
|
|
|
pageTimeout,
|
|
|
|
loadEventExpected,
|
|
|
|
true
|
|
|
|
);
|
2017-04-20 13:12:27 +03:00
|
|
|
} catch (e) {
|
2017-08-19 16:22:17 +03:00
|
|
|
sendError(e, commandID);
|
2017-04-20 13:12:27 +03:00
|
|
|
}
|
2012-04-11 04:28:08 +04:00
|
|
|
}
|
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
function getElementAttribute(el, name) {
|
2016-05-16 23:24:57 +03:00
|
|
|
if (element.isBooleanAttribute(el, name)) {
|
|
|
|
if (el.hasAttribute(name)) {
|
|
|
|
return "true";
|
|
|
|
}
|
2017-06-30 02:40:24 +03:00
|
|
|
return null;
|
2016-05-16 23:24:57 +03:00
|
|
|
}
|
2017-06-30 02:40:24 +03:00
|
|
|
return el.getAttribute(name);
|
2012-04-11 04:28:08 +04:00
|
|
|
}
|
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
function getElementProperty(el, name) {
|
2016-06-24 11:25:03 +03:00
|
|
|
return typeof el[name] != "undefined" ? el[name] : null;
|
2016-05-13 16:42:05 +03:00
|
|
|
}
|
|
|
|
|
2012-04-11 04:28:08 +04:00
|
|
|
/**
|
2017-10-05 19:57:17 +03:00
|
|
|
* Get the text of this element. This includes text from child
|
|
|
|
* elements.
|
2012-04-11 04:28:08 +04:00
|
|
|
*/
|
2017-10-05 19:57:17 +03:00
|
|
|
function getElementText(el) {
|
2016-02-03 21:56:02 +03:00
|
|
|
return atom.getElementText(el, curContainer.frame);
|
2012-04-11 04:28:08 +04:00
|
|
|
}
|
|
|
|
|
2012-08-09 00:21:50 +04:00
|
|
|
/**
|
|
|
|
* Get the tag name of an element.
|
2015-04-15 14:18:00 +03:00
|
|
|
*
|
|
|
|
* @param {WebElement} id
|
|
|
|
* Reference to web element.
|
|
|
|
*
|
|
|
|
* @return {string}
|
|
|
|
* Tag name of element.
|
2012-08-09 00:21:50 +04:00
|
|
|
*/
|
2017-10-05 19:57:17 +03:00
|
|
|
function getElementTagName(el) {
|
2015-04-15 14:18:00 +03:00
|
|
|
return el.tagName.toLowerCase();
|
2012-08-09 00:21:50 +04:00
|
|
|
}
|
|
|
|
|
2012-04-11 04:28:08 +04:00
|
|
|
/**
|
2015-08-21 17:49:47 +03:00
|
|
|
* Determine the element displayedness of the given web element.
|
|
|
|
*
|
|
|
|
* Also performs additional accessibility checks if enabled by session
|
|
|
|
* capability.
|
2012-04-11 04:28:08 +04:00
|
|
|
*/
|
2017-10-05 19:57:17 +03:00
|
|
|
function isElementDisplayed(el) {
|
2016-03-03 16:58:13 +03:00
|
|
|
return interaction.isElementDisplayed(
|
2016-12-31 15:30:49 +03:00
|
|
|
el,
|
|
|
|
capabilities.get("moz:accessibilityChecks")
|
|
|
|
);
|
2012-04-11 04:28:08 +04:00
|
|
|
}
|
|
|
|
|
2013-05-08 01:38:21 +04:00
|
|
|
/**
|
2015-08-21 18:10:02 +03:00
|
|
|
* Retrieves the computed value of the given CSS property of the given
|
|
|
|
* web element.
|
2013-05-08 01:38:21 +04:00
|
|
|
*/
|
2017-10-05 19:57:17 +03:00
|
|
|
function getElementValueOfCssProperty(el, prop) {
|
2015-08-28 23:43:54 +03:00
|
|
|
let st = curContainer.frame.document.defaultView.getComputedStyle(el);
|
2015-08-21 18:10:02 +03:00
|
|
|
return st.getPropertyValue(prop);
|
2013-05-08 01:38:21 +04:00
|
|
|
}
|
|
|
|
|
2012-10-20 00:59:15 +04:00
|
|
|
/**
|
2015-09-08 19:22:06 +03:00
|
|
|
* Get the position and dimensions of the element.
|
2015-04-15 14:18:00 +03:00
|
|
|
*
|
|
|
|
* @return {Object.<string, number>}
|
|
|
|
* The x, y, width, and height properties of the element.
|
|
|
|
*/
|
2017-10-05 19:57:17 +03:00
|
|
|
function getElementRect(el) {
|
2015-04-15 14:18:00 +03:00
|
|
|
let clientRect = el.getBoundingClientRect();
|
|
|
|
return {
|
2015-08-28 23:43:54 +03:00
|
|
|
x: clientRect.x + curContainer.frame.pageXOffset,
|
2017-06-30 02:40:24 +03:00
|
|
|
y: clientRect.y + curContainer.frame.pageYOffset,
|
2015-04-15 14:18:00 +03:00
|
|
|
width: clientRect.width,
|
2017-06-30 02:40:24 +03:00
|
|
|
height: clientRect.height,
|
2015-04-15 14:18:00 +03:00
|
|
|
};
|
2014-07-16 23:58:37 +04:00
|
|
|
}
|
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
function isElementEnabled(el) {
|
2016-03-03 16:58:13 +03:00
|
|
|
return interaction.isElementEnabled(
|
2016-12-31 15:30:49 +03:00
|
|
|
el,
|
|
|
|
capabilities.get("moz:accessibilityChecks")
|
|
|
|
);
|
2012-04-11 04:28:08 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-08-21 17:09:37 +03:00
|
|
|
* Determines if the referenced element is selected or not.
|
|
|
|
*
|
|
|
|
* This operation only makes sense on input elements of the Checkbox-
|
|
|
|
* and Radio Button states, or option elements.
|
2012-04-11 04:28:08 +04:00
|
|
|
*/
|
2017-10-05 19:57:17 +03:00
|
|
|
function isElementSelected(el) {
|
2016-03-03 16:58:13 +03:00
|
|
|
return interaction.isElementSelected(
|
2016-12-31 15:30:49 +03:00
|
|
|
el,
|
|
|
|
capabilities.get("moz:accessibilityChecks")
|
|
|
|
);
|
2012-04-11 04:28:08 +04:00
|
|
|
}
|
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
async function sendKeysToElement(el, val) {
|
2018-11-03 19:31:00 +03:00
|
|
|
let opts = {
|
|
|
|
strictFileInteractability: capabilities.get("strictFileInteractability"),
|
|
|
|
accessibilityChecks: capabilities.get("moz:accessibilityChecks"),
|
|
|
|
webdriverClick: capabilities.get("moz:webdriverClick"),
|
|
|
|
};
|
|
|
|
await interaction.sendKeysToElement(el, val, opts);
|
2012-04-11 04:28:08 +04:00
|
|
|
}
|
|
|
|
|
2017-08-07 20:59:45 +03:00
|
|
|
/** Clear the text of an element. */
|
2017-10-05 19:57:17 +03:00
|
|
|
function clearElement(el) {
|
2017-12-31 17:53:42 +03:00
|
|
|
interaction.clearElement(el);
|
2012-03-22 19:19:57 +04:00
|
|
|
}
|
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
/** Switch the current context to the specified host's Shadow DOM. */
|
|
|
|
function switchToShadowRoot(el) {
|
2018-05-09 11:19:28 +03:00
|
|
|
if (!element.isElement(el)) {
|
2017-06-30 02:40:24 +03:00
|
|
|
// If no host element is passed, attempt to find a parent shadow
|
|
|
|
// root or, if none found, unset the current shadow root
|
2015-08-28 23:43:54 +03:00
|
|
|
if (curContainer.shadowRoot) {
|
2015-11-24 23:26:50 +03:00
|
|
|
let parent;
|
|
|
|
try {
|
|
|
|
parent = curContainer.shadowRoot.host;
|
|
|
|
} catch (e) {
|
|
|
|
// There is a chance that host element is dead and we are trying to
|
|
|
|
// access a dead object.
|
|
|
|
curContainer.shadowRoot = null;
|
|
|
|
return;
|
|
|
|
}
|
2015-08-28 23:43:54 +03:00
|
|
|
while (parent && !(parent instanceof curContainer.frame.ShadowRoot)) {
|
|
|
|
parent = parent.parentNode;
|
|
|
|
}
|
|
|
|
curContainer.shadowRoot = parent;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
let foundShadowRoot = el.shadowRoot;
|
2015-08-28 23:43:54 +03:00
|
|
|
if (!foundShadowRoot) {
|
2017-10-05 19:57:17 +03:00
|
|
|
throw new NoSuchElementError(pprint`Unable to locate shadow root: ${el}`);
|
2015-08-28 23:43:54 +03:00
|
|
|
}
|
|
|
|
curContainer.shadowRoot = foundShadowRoot;
|
|
|
|
}
|
|
|
|
|
2015-10-19 23:39:48 +03:00
|
|
|
/**
|
2017-06-30 02:40:24 +03:00
|
|
|
* Switch to the parent frame of the current frame. If the frame is the
|
|
|
|
* top most is the current frame then no action will happen.
|
2015-10-19 23:39:48 +03:00
|
|
|
*/
|
2017-06-30 02:40:24 +03:00
|
|
|
function switchToParentFrame(msg) {
|
|
|
|
curContainer.frame = curContainer.frame.parent;
|
|
|
|
let parentElement = seenEls.add(curContainer.frame);
|
2015-10-19 23:39:48 +03:00
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
sendSyncMessage("Marionette:switchedToFrame", {
|
|
|
|
frameValue: parentElement.uuid,
|
|
|
|
});
|
2015-10-19 23:39:48 +03:00
|
|
|
|
2017-08-19 16:22:17 +03:00
|
|
|
sendOk(msg.json.commandID);
|
2017-06-30 02:40:24 +03:00
|
|
|
}
|
2015-10-19 23:39:48 +03:00
|
|
|
|
2012-03-22 19:19:57 +04:00
|
|
|
/**
|
2020-07-15 11:19:35 +03:00
|
|
|
* Switch to the specified frame.
|
|
|
|
*
|
|
|
|
* @param {boolean=} focus
|
|
|
|
* Focus the frame if set to true. Defaults to false.
|
|
|
|
* @param {(string|Object)=} element
|
|
|
|
* A web element reference of the frame or its element id.
|
|
|
|
* @param {number=} id
|
|
|
|
* The index of the frame to switch to.
|
|
|
|
* If both element and id are not defined, switch to top-level frame.
|
2012-03-22 19:19:57 +04:00
|
|
|
*/
|
2020-07-15 11:21:46 +03:00
|
|
|
function switchToFrame({ json }) {
|
|
|
|
let { commandID, element, focus, id } = json;
|
2020-06-05 09:09:14 +03:00
|
|
|
|
|
|
|
let foundFrame;
|
|
|
|
let frameWebEl;
|
2020-07-15 11:21:46 +03:00
|
|
|
let wantedFrame = null;
|
2017-04-19 14:22:13 +03:00
|
|
|
|
2017-12-06 00:08:48 +03:00
|
|
|
// check if curContainer.frame reference is dead
|
|
|
|
let frames = [];
|
2013-01-16 19:00:00 +04:00
|
|
|
try {
|
2015-08-28 23:43:54 +03:00
|
|
|
frames = curContainer.frame.frames;
|
2013-01-16 19:00:00 +04:00
|
|
|
} catch (e) {
|
2017-12-06 00:08:48 +03:00
|
|
|
// dead comparment, redirect to top frame
|
2020-07-15 11:21:46 +03:00
|
|
|
id = null;
|
|
|
|
element = null;
|
2013-01-16 19:00:00 +04:00
|
|
|
}
|
2014-01-19 00:08:36 +04:00
|
|
|
|
2020-07-15 11:21:46 +03:00
|
|
|
// switch to top-level frame
|
|
|
|
if (id == null && !element) {
|
2017-06-30 02:40:24 +03:00
|
|
|
sendSyncMessage("Marionette:switchedToFrame", { frameValue: null });
|
2015-08-28 23:43:54 +03:00
|
|
|
curContainer.frame = content;
|
2020-06-05 09:10:25 +03:00
|
|
|
|
2020-07-15 11:21:46 +03:00
|
|
|
if (focus) {
|
2015-08-28 23:43:54 +03:00
|
|
|
curContainer.frame.focus();
|
2012-12-17 13:45:28 +04:00
|
|
|
}
|
2015-04-23 23:39:38 +03:00
|
|
|
|
2017-08-19 16:22:17 +03:00
|
|
|
sendOk(commandID);
|
2012-03-22 19:19:57 +04:00
|
|
|
return;
|
|
|
|
}
|
2014-01-19 00:08:36 +04:00
|
|
|
|
2017-10-05 19:57:17 +03:00
|
|
|
let webEl;
|
2020-07-15 11:21:46 +03:00
|
|
|
if (typeof element != "undefined") {
|
|
|
|
webEl = WebElement.fromUUID(element, "content");
|
2017-10-05 19:57:17 +03:00
|
|
|
}
|
2019-01-09 19:21:58 +03:00
|
|
|
|
|
|
|
if (webEl) {
|
|
|
|
if (!seenEls.has(webEl)) {
|
|
|
|
let err = new NoSuchElementError(`Unable to locate element: ${webEl}`);
|
|
|
|
sendError(err, commandID);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-05-20 17:07:21 +03:00
|
|
|
try {
|
2017-10-05 19:57:17 +03:00
|
|
|
wantedFrame = seenEls.get(webEl, curContainer.frame);
|
2016-05-20 17:07:21 +03:00
|
|
|
} catch (e) {
|
2017-08-19 16:22:17 +03:00
|
|
|
sendError(e, commandID);
|
2017-10-05 19:57:17 +03:00
|
|
|
return;
|
2016-05-20 17:07:21 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (frames.length > 0) {
|
2020-07-15 11:21:46 +03:00
|
|
|
// use XPCNativeWrapper to compare elements; see bug 834266
|
|
|
|
let wrappedWanted = new XPCNativeWrapper(wantedFrame);
|
|
|
|
foundFrame = Array.prototype.find.call(frames, frame => {
|
|
|
|
return new XPCNativeWrapper(frame.frameElement) === wrappedWanted;
|
|
|
|
});
|
2016-05-20 17:07:21 +03:00
|
|
|
}
|
|
|
|
|
2020-06-05 09:09:14 +03:00
|
|
|
if (!foundFrame) {
|
2016-05-20 17:07:21 +03:00
|
|
|
// Either the frame has been removed or we have a OOP frame
|
|
|
|
// so lets just get all the iframes and do a quick loop before
|
|
|
|
// throwing in the towel
|
2020-07-15 11:21:46 +03:00
|
|
|
let iframes = curContainer.frame.document.getElementsByTagName("iframe");
|
|
|
|
let wrappedWanted = new XPCNativeWrapper(wantedFrame);
|
|
|
|
foundFrame = Array.prototype.find.call(iframes, frame => {
|
|
|
|
return new XPCNativeWrapper(frame) === wrappedWanted;
|
|
|
|
});
|
2014-01-19 00:08:36 +04:00
|
|
|
}
|
|
|
|
}
|
2016-05-20 17:07:21 +03:00
|
|
|
|
2020-06-05 09:09:14 +03:00
|
|
|
if (!foundFrame) {
|
2020-07-15 11:21:46 +03:00
|
|
|
if (typeof id === "number") {
|
2014-05-01 01:59:25 +04:00
|
|
|
try {
|
2020-07-15 11:21:46 +03:00
|
|
|
let frameEl;
|
|
|
|
if (id >= 0 && id < frames.length) {
|
|
|
|
frameEl = frames[id].frameElement;
|
|
|
|
if (frameEl !== null) {
|
|
|
|
foundFrame = frameEl.contentWindow;
|
|
|
|
frameWebEl = seenEls.add(frameEl.wrappedJSObject);
|
2020-06-05 09:09:14 +03:00
|
|
|
} else {
|
|
|
|
// If foundFrame is null at this point then we have the top
|
|
|
|
// level browsing context so should treat it accordingly.
|
|
|
|
sendSyncMessage("Marionette:switchedToFrame", { frameValue: null });
|
|
|
|
curContainer.frame = content;
|
|
|
|
|
2020-07-15 11:21:46 +03:00
|
|
|
if (focus) {
|
2020-06-05 09:09:14 +03:00
|
|
|
curContainer.frame.focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
sendOk(commandID);
|
|
|
|
return;
|
2014-05-09 18:01:34 +04:00
|
|
|
}
|
|
|
|
}
|
2014-05-01 01:59:25 +04:00
|
|
|
} catch (e) {
|
|
|
|
// Since window.frames does not return OOP frames it will throw
|
|
|
|
// and we land up here. Let's not give up and check if there are
|
|
|
|
// iframes and switch to the indexed frame there
|
2020-07-15 11:21:46 +03:00
|
|
|
let iframes = foundFrame.document.getElementsByTagName("iframe");
|
|
|
|
if (id >= 0 && id < iframes.length) {
|
|
|
|
foundFrame = iframes[id];
|
2014-10-28 12:41:00 +03:00
|
|
|
}
|
2014-05-01 01:59:25 +04:00
|
|
|
}
|
2014-01-19 00:08:36 +04:00
|
|
|
}
|
|
|
|
}
|
2015-04-15 14:18:00 +03:00
|
|
|
|
2020-06-05 09:09:14 +03:00
|
|
|
if (!foundFrame) {
|
2020-07-15 11:21:46 +03:00
|
|
|
let failedFrame = id || element;
|
2017-06-30 02:40:24 +03:00
|
|
|
let err = new NoSuchFrameError(`Unable to locate frame: ${failedFrame}`);
|
2017-08-19 16:22:17 +03:00
|
|
|
sendError(err, commandID);
|
2017-06-30 02:40:24 +03:00
|
|
|
return;
|
2012-03-22 19:19:57 +04:00
|
|
|
}
|
2012-05-09 23:05:39 +04:00
|
|
|
|
2013-07-29 21:46:01 +04:00
|
|
|
// send a synchronous message to let the server update the currently active
|
|
|
|
// frame element (for getActiveFrame)
|
2020-06-05 09:09:14 +03:00
|
|
|
if (!frameWebEl) {
|
|
|
|
frameWebEl = seenEls.add(foundFrame.wrappedJSObject);
|
|
|
|
}
|
2017-10-05 19:57:17 +03:00
|
|
|
sendSyncMessage("Marionette:switchedToFrame", {
|
|
|
|
frameValue: frameWebEl.uuid,
|
|
|
|
});
|
2013-07-29 21:46:01 +04:00
|
|
|
|
2020-07-15 11:21:46 +03:00
|
|
|
curContainer.frame = foundFrame;
|
|
|
|
if (focus) {
|
2017-12-06 00:08:48 +03:00
|
|
|
curContainer.frame.focus();
|
2017-04-19 14:22:13 +03:00
|
|
|
}
|
2017-12-06 00:08:48 +03:00
|
|
|
|
|
|
|
sendOk(commandID);
|
2012-03-22 19:19:57 +04:00
|
|
|
}
|
2014-11-11 21:15:02 +03:00
|
|
|
|
2012-12-01 03:25:26 +04:00
|
|
|
/**
|
2019-08-27 16:08:24 +03:00
|
|
|
* Returns the rect of the element to screenshot.
|
|
|
|
*
|
|
|
|
* Because the screen capture takes place in the parent process the dimensions
|
|
|
|
* for the screenshot have to be determined in the appropriate child process.
|
|
|
|
*
|
|
|
|
* Also it takes care of scrolling an element into view if requested.
|
|
|
|
*
|
|
|
|
* @param {Object.<string, ?>} opts
|
|
|
|
* Options.
|
2014-01-24 17:45:58 +04:00
|
|
|
*
|
2016-12-20 17:30:48 +03:00
|
|
|
* Accepted values for |opts|:
|
|
|
|
*
|
2019-08-27 16:01:01 +03:00
|
|
|
* @param {WebElement} webEl
|
|
|
|
* Optional element to take a screenshot of.
|
2019-08-08 00:00:46 +03:00
|
|
|
* @param {boolean=} full
|
|
|
|
* True to take a screenshot of the entire document element. Is only
|
|
|
|
* considered if <var>id</var> is not defined. Defaults to true.
|
|
|
|
* @param {boolean=} scroll
|
|
|
|
* When <var>id</var> is given, scroll it into view before taking the
|
|
|
|
* screenshot. Defaults to true.
|
2016-12-20 17:30:48 +03:00
|
|
|
* @param {capture.Format} format
|
|
|
|
* Format to return the screenshot in.
|
|
|
|
* @param {Object.<string, ?>} opts
|
|
|
|
* Options.
|
2015-10-13 18:52:26 +03:00
|
|
|
*
|
2019-08-27 16:08:24 +03:00
|
|
|
* @return {DOMRect}
|
|
|
|
* The area to take a snapshot from
|
2012-12-01 03:25:26 +04:00
|
|
|
*/
|
2019-08-27 16:01:01 +03:00
|
|
|
function getScreenshotRect({ el, full = true, scroll = true } = {}) {
|
2020-01-07 18:24:29 +03:00
|
|
|
let win = el ? curContainer.frame : content;
|
2016-04-18 04:37:14 +03:00
|
|
|
|
2019-08-27 16:08:24 +03:00
|
|
|
let rect;
|
2015-10-13 18:52:26 +03:00
|
|
|
|
2019-08-27 16:01:01 +03:00
|
|
|
if (el) {
|
2019-08-27 16:08:24 +03:00
|
|
|
if (scroll) {
|
|
|
|
element.scrollIntoView(el);
|
2012-12-01 03:25:26 +04:00
|
|
|
}
|
2019-08-27 16:01:01 +03:00
|
|
|
rect = getElementRect(el);
|
2019-08-27 16:08:24 +03:00
|
|
|
} else if (full) {
|
2020-01-03 12:07:00 +03:00
|
|
|
const docEl = win.document.documentElement;
|
|
|
|
rect = new DOMRect(0, 0, docEl.scrollWidth, docEl.scrollHeight);
|
2019-08-08 00:00:46 +03:00
|
|
|
} else {
|
2019-08-27 16:08:24 +03:00
|
|
|
// viewport
|
|
|
|
rect = new DOMRect(
|
|
|
|
win.pageXOffset,
|
|
|
|
win.pageYOffset,
|
|
|
|
win.innerWidth,
|
|
|
|
win.innerHeight
|
|
|
|
);
|
2012-12-01 03:25:26 +04:00
|
|
|
}
|
|
|
|
|
2019-08-27 16:08:24 +03:00
|
|
|
return rect;
|
2012-12-01 03:25:26 +04: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
|
|
|
function flushRendering() {
|
|
|
|
let content = curContainer.frame;
|
|
|
|
let anyPendingPaintsGeneratedInDescendants = false;
|
|
|
|
|
2018-07-25 02:47:43 +03:00
|
|
|
let windowUtils = content.windowUtils;
|
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
|
|
|
|
|
|
|
function flushWindow(win) {
|
2018-07-25 02:47:43 +03:00
|
|
|
let utils = win.windowUtils;
|
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 afterPaintWasPending = utils.isMozAfterPaintPending;
|
|
|
|
|
|
|
|
let root = win.document.documentElement;
|
|
|
|
if (root) {
|
|
|
|
try {
|
2019-06-13 05:19:44 +03:00
|
|
|
// Flush pending restyles and reflows for this window (layout)
|
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
|
|
|
root.getBoundingClientRect();
|
|
|
|
} catch (e) {
|
2018-06-06 16:45:46 +03:00
|
|
|
logger.error("flushWindow failed", e);
|
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 (!afterPaintWasPending && utils.isMozAfterPaintPending) {
|
|
|
|
anyPendingPaintsGeneratedInDescendants = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let i = 0; i < win.frames.length; ++i) {
|
|
|
|
flushWindow(win.frames[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
flushWindow(content);
|
|
|
|
|
|
|
|
if (
|
|
|
|
anyPendingPaintsGeneratedInDescendants &&
|
|
|
|
!windowUtils.isMozAfterPaintPending
|
|
|
|
) {
|
2018-06-06 16:45:46 +03:00
|
|
|
logger.error(
|
|
|
|
"Descendant frame generated a MozAfterPaint event, " +
|
|
|
|
"but the root document doesn't have one!"
|
|
|
|
);
|
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 20:59:45 +03:00
|
|
|
async function reftestWait(url, remote) {
|
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 win = curContainer.frame;
|
|
|
|
let document = curContainer.frame.document;
|
2019-11-25 17:54:51 +03:00
|
|
|
let reftestWait;
|
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 (document.location.href !== url || document.readyState != "complete") {
|
2019-11-25 17:54:51 +03:00
|
|
|
reftestWait = await documentLoad(win, url);
|
|
|
|
win = curContainer.frame;
|
|
|
|
document = curContainer.frame.document;
|
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
|
|
|
} else {
|
|
|
|
reftestWait = document.documentElement.classList.contains("reftest-wait");
|
2017-06-30 02:40:24 +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
|
|
|
|
2019-11-25 17:54:51 +03:00
|
|
|
logger.debug("Waiting for event loop to spin");
|
|
|
|
await new Promise(resolve => win.setTimeout(resolve, 0));
|
|
|
|
|
|
|
|
await paintComplete(win, remote);
|
|
|
|
|
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 root = document.documentElement;
|
|
|
|
if (reftestWait) {
|
2019-11-25 17:54:51 +03:00
|
|
|
let event = new Event("TestRendered", { bubbles: true });
|
|
|
|
root.dispatchEvent(event);
|
|
|
|
logger.info("Emitted TestRendered event");
|
|
|
|
await reftestWaitRemoved(win, root);
|
|
|
|
await paintComplete(win, remote);
|
2018-09-11 20:58:35 +03:00
|
|
|
}
|
2019-12-04 17:58:56 +03:00
|
|
|
if (
|
|
|
|
win.innerWidth < document.documentElement.scrollWidth ||
|
|
|
|
win.innerHeight < document.documentElement.scrollHeight
|
|
|
|
) {
|
|
|
|
logger.warn(
|
2020-03-09 00:45:16 +03:00
|
|
|
`${url} overflows viewport (width: ${document.documentElement.scrollWidth}, height: ${document.documentElement.scrollHeight})`
|
2019-12-04 17:58:56 +03:00
|
|
|
);
|
|
|
|
}
|
2019-11-25 17:54:51 +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
|
|
|
|
2019-11-25 17:54:51 +03:00
|
|
|
function documentLoad(win, url) {
|
|
|
|
logger.debug(truncate`Waiting for page load of ${url}`);
|
|
|
|
return new Promise(resolve => {
|
|
|
|
let maybeResolve = event => {
|
|
|
|
if (
|
|
|
|
event.target === curContainer.frame.document &&
|
|
|
|
event.target.location.href === url
|
|
|
|
) {
|
|
|
|
let reftestWait = win.document.documentElement.classList.contains(
|
|
|
|
"reftest-wait"
|
|
|
|
);
|
|
|
|
removeEventListener("load", maybeResolve, { once: true });
|
|
|
|
resolve(reftestWait);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
addEventListener("load", maybeResolve, true);
|
|
|
|
});
|
|
|
|
}
|
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
|
|
|
|
2019-11-25 17:54:51 +03:00
|
|
|
function paintComplete(win, remote) {
|
|
|
|
logger.debug("Waiting for rendering");
|
|
|
|
let windowUtils = content.windowUtils;
|
|
|
|
return new Promise(resolve => {
|
2018-09-11 20:58:35 +03:00
|
|
|
let maybeResolve = () => {
|
2019-06-13 05:19:44 +03:00
|
|
|
flushRendering();
|
|
|
|
if (remote) {
|
|
|
|
// Flush display (paint)
|
|
|
|
windowUtils.updateLayerTree();
|
|
|
|
}
|
|
|
|
if (windowUtils.isMozAfterPaintPending) {
|
|
|
|
logger.debug(`reftestWait: ${windowUtils.isMozAfterPaintPending}`);
|
2018-09-11 20:58:35 +03:00
|
|
|
win.addEventListener("MozAfterPaint", maybeResolve, { once: true });
|
|
|
|
} else {
|
2019-06-13 05:19:44 +03:00
|
|
|
// resolve at the start of the next frame in case of leftover paints
|
|
|
|
win.requestAnimationFrame(() => {
|
|
|
|
win.requestAnimationFrame(resolve);
|
|
|
|
});
|
2018-09-11 20:58:35 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
maybeResolve();
|
|
|
|
});
|
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
|
|
|
}
|
|
|
|
|
2019-11-25 17:54:51 +03:00
|
|
|
function reftestWaitRemoved(win, root) {
|
|
|
|
logger.debug("Waiting for reftest-wait removal");
|
|
|
|
return new Promise(resolve => {
|
|
|
|
let observer = new win.MutationObserver(() => {
|
|
|
|
if (!root.classList.contains("reftest-wait")) {
|
|
|
|
observer.disconnect();
|
|
|
|
logger.debug("reftest-wait removed");
|
|
|
|
win.setTimeout(resolve, 0);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (root.classList.contains("reftest-wait")) {
|
|
|
|
observer.observe(root, { attributes: true });
|
|
|
|
} else {
|
|
|
|
win.setTimeout(resolve, 0);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-09-15 19:07:41 +03:00
|
|
|
function domAddEventListener(msg) {
|
|
|
|
eventObservers.add(msg.json.type);
|
|
|
|
}
|
|
|
|
|
|
|
|
function domRemoveEventListener(msg) {
|
|
|
|
eventObservers.remove(msg.json.type);
|
|
|
|
}
|
|
|
|
|
2014-01-24 17:45:58 +04:00
|
|
|
// Call register self when we get loaded
|
2012-03-22 19:19:57 +04:00
|
|
|
registerSelf();
|