Bug 937659 - Implement page load strategy.
By using the page load strategy each navigation request has to return
when the page load has reached the expected document ready state, or
immediately if a strategy of "none" is set.
This also removes the page load checks when switching frames because
this is not part of the webdriver spec.
MozReview-Commit-ID: 3KbsDvzEG6c
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py
@@ -25,16 +25,17 @@ class BaseNavigationTestCase(WindowManag
def setUp(self):
super(BaseNavigationTestCase, self).setUp()
self.test_page_frameset = self.marionette.absolute_url("frameset.html")
self.test_page_insecure = self.fixtures.where_is("test.html", on="https")
self.test_page_not_remote = "about:robots"
self.test_page_remote = self.marionette.absolute_url("test.html")
+ self.test_page_slow_resource = self.marionette.absolute_url("slow_resource.html")
if self.marionette.session_capabilities["platformName"] == "darwin":
self.mod_key = Keys.META
else:
self.mod_key = Keys.CONTROL
def open_with_link():
link = self.marionette.find_element(By.ID, "new-blank-tab")
@@ -89,16 +90,21 @@ class BaseNavigationTestCase(WindowManag
} else {
return null;
}
return tabBrowser.isRemoteBrowser;
""")
+ @property
+ def ready_state(self):
+ return self.marionette.execute_script("return window.document.readyState;",
+ sandbox=None)
+
class TestNavigate(BaseNavigationTestCase):
def test_set_location_through_execute_script(self):
self.marionette.execute_script(
"window.location.href = arguments[0];",
script_args=(self.test_page_remote,), sandbox=None)
@@ -143,19 +149,17 @@ class TestNavigate(BaseNavigationTestCas
def test_invalid_url(self):
with self.assertRaises(errors.MarionetteException):
self.marionette.navigate("foo")
with self.assertRaises(errors.MarionetteException):
self.marionette.navigate("thisprotocoldoesnotexist://")
def test_find_element_state_complete(self):
self.marionette.navigate(self.test_page_remote)
- state = self.marionette.execute_script(
- "return window.document.readyState", sandbox=None)
- self.assertEqual("complete", state)
+ self.assertEqual("complete", self.ready_state)
self.assertTrue(self.marionette.find_element(By.ID, "mozLink"))
def test_navigate_timeout_error_no_remoteness_change(self):
is_remote_before_timeout = self.is_remote_tab
self.marionette.timeout.page_load = 0.5
with self.assertRaises(errors.TimeoutException):
self.marionette.navigate(self.marionette.absolute_url("slow"))
self.assertEqual(self.is_remote_tab, is_remote_before_timeout)
@@ -601,8 +605,40 @@ class TestTLSNavigation(MarionetteTestCa
print "with unsafe session"
with self.unsafe_session() as session:
session.navigate(invalid_cert_url)
print "with safe session again"
with self.safe_session() as session:
with self.assertRaises(errors.InsecureCertificateException):
session.navigate(invalid_cert_url)
+
+
+class TestPageLoadStrategy(BaseNavigationTestCase):
+
+ def setUp(self):
+ super(TestPageLoadStrategy, self).setUp()
+
+ if self.marionette.session is not None:
+ self.marionette.delete_session()
+
+ def test_none(self):
+ self.marionette.start_session({"desiredCapabilities": {"pageLoadStrategy": "none"}})
+
+ # With a strategy of "none" there should be no wait for the page load, and the
+ # current load state is unknown. So only test that the command executes successfully.
+ self.marionette.navigate(self.test_page_slow_resource)
+
+ def test_eager(self):
+ self.marionette.start_session({"desiredCapabilities": {"pageLoadStrategy": "eager"}})
+
+ self.marionette.navigate(self.test_page_slow_resource)
+ self.assertEqual(self.test_page_slow_resource, self.marionette.get_url())
+ self.assertEqual("interactive", self.ready_state)
+ self.marionette.find_element(By.ID, "slow")
+
+ def test_normal(self):
+ self.marionette.start_session({"desiredCapabilities": {"pageLoadStrategy": "normal"}})
+
+ self.marionette.navigate(self.test_page_slow_resource)
+ self.assertEqual(self.test_page_slow_resource, self.marionette.get_url())
+ self.assertEqual("complete", self.ready_state)
+ self.marionette.find_element(By.ID, "slow")
new file mode 100644
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/slow_resource.html
@@ -0,0 +1,13 @@
+<!-- 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/. -->
+
+<!DOCTYPE html>
+<html>
+<head>
+<title>Slow loading resource</title>
+</head>
+<body>
+ <img src="/slow?delay=1" id="slow" />
+</body>
+</html>
\ No newline at end of file
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -67,18 +67,16 @@ var onunload;
// Flag to indicate whether an async script is currently running or not.
var asyncTestRunning = false;
var asyncTestCommandId;
var asyncTestTimeoutId;
var inactivityTimeoutId = null;
var originalOnError;
-//timer for doc changes
-var checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
// Send move events about this often
var EVENT_INTERVAL = 30; // milliseconds
// last touch for each fingerId
var multiLast = {};
var asyncChrome = proxy.toChromeAsync({
addMessageListener: addMessageListenerId.bind(this),
removeMessageListener: removeMessageListenerId.bind(this),
sendAsyncMessage: sendAsyncMessage.bind(this),
@@ -243,19 +241,21 @@ var loadListener = {
this.stop();
sendError(new InsecureCertificateError(), this.command_id);
} else if (/about:.*(error)\?/.exec(event.target.baseURI)) {
this.stop();
sendError(new UnknownError("Reached error page: " +
event.target.baseURI), this.command_id);
- // Special-case about:blocked pages which should be treated as non-error
- // pages but do not raise a pageshow event.
- } else if (/about:blocked\?/.exec(event.target.baseURI)) {
+ // Return early with a page load strategy of eager, and also special-case
+ // about:blocked pages which should be treated as non-error pages but do
+ // not raise a pageshow event.
+ } else if (capabilities.get("pageLoadStrategy") === session.PageLoadStrategy.Eager ||
+ /about:blocked\?/.exec(event.target.baseURI)) {
this.stop();
sendOk(this.command_id);
}
break;
case "pageshow":
if (event.target === curContainer.frame.document) {
this.stop();
@@ -346,16 +346,21 @@ var loadListener = {
* @param {boolean=} loadEventExpected
* Optional flag, which indicates that navigate has to wait for the page
* finished loading.
* @param {string=} url
* Optional URL, which is used to check if a page load is expected.
*/
navigate: function (trigger, command_id, timeout, loadEventExpected = true,
useUnloadTimer = false) {
+
+ // Only wait if the page load strategy is not `none`
+ loadEventExpected = loadEventExpected &&
+ capabilities.get("pageLoadStrategy") !== session.PageLoadStrategy.None;
+
if (loadEventExpected) {
let startTime = new Date().getTime();
this.start(command_id, timeout, startTime, true);
}
return Task.spawn(function* () {
yield trigger();
@@ -1041,17 +1046,18 @@ function setDispatch(batches, touches, b
maxTime = waitTime;
}
}
break;
}
}
if (maxTime != 0) {
- checkTimer.initWithCallback(function() {
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(function() {
setDispatch(batches, touches, batchIndex);
}, maxTime, Ci.nsITimer.TYPE_ONE_SHOT);
} else {
setDispatch(batches, touches, batchIndex);
}
}
/**
@@ -1525,31 +1531,20 @@ function switchToShadowRoot(id) {
}
/**
* Switch to frame given either the server-assigned element id,
* its index in window.frames, or the iframe's name or id.
*/
function switchToFrame(msg) {
let command_id = msg.json.command_id;
- function checkLoad() {
- let errorRegex = /about:.+(error)|(blocked)\?/;
- if (curContainer.frame.document.readyState == "complete") {
- sendOk(command_id);
- return;
- } else if (curContainer.frame.document.readyState == "interactive" &&
- errorRegex.exec(curContainer.frame.document.baseURI)) {
- sendError(new UnknownError("Error loading page"), command_id);
- return;
- }
- checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
- }
let foundFrame = null;
let frames = [];
let parWindow = null;
+
// Check of the curContainer.frame reference is dead
try {
frames = curContainer.frame.frames;
//Until Bug 761935 lands, we won't have multiple nested OOP iframes. We will only have one.
//parWindow will refer to the iframe above the nested OOP frame.
parWindow = curContainer.frame.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
} catch (e) {
@@ -1564,17 +1559,17 @@ function switchToFrame(msg) {
// returning to root frame
sendSyncMessage("Marionette:switchedToFrame", { frameValue: null });
curContainer.frame = content;
if(msg.json.focus == true) {
curContainer.frame.focus();
}
- checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
+ sendOk(command_id);
return;
}
let id = msg.json.element;
if (seenEls.has(id)) {
let wantedFrame;
try {
wantedFrame = seenEls.get(id, curContainer);
@@ -1614,21 +1609,22 @@ function switchToFrame(msg) {
curContainer.frame = foundFrame;
foundFrame = seenEls.add(curContainer.frame);
}
else {
// If foundFrame is null at this point then we have the top level browsing
// context so should treat it accordingly.
sendSyncMessage("Marionette:switchedToFrame", { frameValue: null});
curContainer.frame = content;
+
if(msg.json.focus == true) {
curContainer.frame.focus();
}
- checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
+ sendOk(command_id);
return;
}
} catch (e) {
// Since window.frames does not return OOP frames it will throw
// and we land up here. Let's not give up and check if there are
// iframes and switch to the indexed frame there
let iframes = curContainer.frame.document.getElementsByTagName("iframe");
if (msg.json.id >= 0 && msg.json.id < iframes.length) {
@@ -1645,32 +1641,32 @@ function switchToFrame(msg) {
}
// send a synchronous message to let the server update the currently active
// frame element (for getActiveFrame)
let frameValue = element.toJson(
curContainer.frame.wrappedJSObject, seenEls)[element.Key];
sendSyncMessage("Marionette:switchedToFrame", {frameValue: frameValue});
- let rv = null;
if (curContainer.frame.contentWindow === null) {
// The frame we want to switch to is a remote/OOP frame;
// notify our parent to handle the switch
curContainer.frame = content;
- rv = {win: parWindow, frame: foundFrame};
+ let rv = {win: parWindow, frame: foundFrame};
+ sendResponse(rv, command_id);
+
} else {
curContainer.frame = curContainer.frame.contentWindow;
+
if (msg.json.focus) {
curContainer.frame.focus();
}
- checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
- return;
+
+ sendOk(command_id);
}
-
- sendResponse(rv, command_id);
}
function addCookie(cookie) {
cookies.add(cookie.name, cookie.value, cookie);
}
/**
* Get all cookies for the current domain.