--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -1024,28 +1024,96 @@ GeckoDriver.prototype.getPageSource = fu
break;
case Context.CONTENT:
resp.body.value = yield this.listener.getPageSource();
break;
}
};
-/** Go back in history. */
-GeckoDriver.prototype.goBack = function*(cmd, resp) {
+/**
+ * Cause the browser to traverse one step backward in the joint history
+ * of the current browsing context.
+ */
+GeckoDriver.prototype.goBack = function* (cmd, resp) {
assert.content(this.context);
- yield this.listener.goBack();
+ if (!this.curBrowser.tab) {
+ // Navigation does not work for non-browser windows
+ return;
+ }
+
+ let contentBrowser = browser.getBrowserForTab(this.curBrowser.tab)
+ if (!contentBrowser.webNavigation.canGoBack) {
+ return;
+ }
+
+ let currentURL = yield this.listener.getCurrentUrl();
+ let goBack = this.listener.goBack({pageTimeout: this.timeouts.pageLoad});
+
+ // If a remoteness update interrupts our page load, this will never return
+ // We need to re-issue this request to correctly poll for readyState and
+ // send errors.
+ this.curBrowser.pendingCommands.push(() => {
+ let parameters = {
+ // TODO(ato): Bug 1242595
+ command_id: this.listener.activeMessageId,
+ lastSeenURL: currentURL,
+ pageTimeout: this.timeouts.pageLoad,
+ startTime: new Date().getTime(),
+ };
+ this.mm.broadcastAsyncMessage(
+ // TODO: combine with
+ // "Marionette:pollForReadyState" + this.curBrowser.curFrameId,
+ "Marionette:pollForReadyState" + this.curBrowser.curFrameId,
+ parameters);
+ });
+
+ yield goBack;
};
-/** Go forward in history. */
-GeckoDriver.prototype.goForward = function*(cmd, resp) {
+/**
+ * Cause the browser to traverse one step forward in the joint history
+ * of the current browsing context.
+ */
+GeckoDriver.prototype.goForward = function* (cmd, resp) {
assert.content(this.context);
- yield this.listener.goForward();
+ if (!this.curBrowser.tab) {
+ // Navigation does not work for non-browser windows
+ return;
+ }
+
+ let contentBrowser = browser.getBrowserForTab(this.curBrowser.tab)
+ if (!contentBrowser.webNavigation.canGoForward) {
+ return;
+ }
+
+ let currentURL = yield this.listener.getCurrentUrl();
+ let goForward = this.listener.goForward({pageTimeout: this.timeouts.pageLoad});
+
+ // If a remoteness update interrupts our page load, this will never return
+ // We need to re-issue this request to correctly poll for readyState and
+ // send errors.
+ this.curBrowser.pendingCommands.push(() => {
+ let parameters = {
+ // TODO(ato): Bug 1242595
+ command_id: this.listener.activeMessageId,
+ lastSeenURL: currentURL,
+ pageTimeout: this.timeouts.pageLoad,
+ startTime: new Date().getTime(),
+ };
+ this.mm.broadcastAsyncMessage(
+ // TODO: combine with
+ // "Marionette:pollForReadyState" + this.curBrowser.curFrameId,
+ "Marionette:pollForReadyState" + this.curBrowser.curFrameId,
+ parameters);
+ });
+
+ yield goForward;
};
/** Refresh the page. */
GeckoDriver.prototype.refresh = function*(cmd, resp) {
assert.content(this.context);
yield this.listener.refresh();
};
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_about_pages.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_about_pages.py
@@ -39,23 +39,20 @@ class TestAboutPages(WindowManagerMixin,
new_tab = self.open_tab(trigger=self.open_tab_with_link)
self.marionette.switch_to_window(new_tab)
self.marionette.navigate("about:blank")
self.marionette.navigate(self.remote_uri)
self.marionette.navigate("about:support")
self.marionette.go_back()
- Wait(self.marionette).until(lambda mn: mn.get_url() == self.remote_uri,
- message="'{}' hasn't been loaded".format(self.remote_uri))
+ self.assertEqual(self.marionette.get_url(), self.remote_uri)
- # Bug 1332998 - Timeout loading the page
- # self.marionette.go_forward()
- # Wait(self.marionette).until(lambda mn: mn.get_url() == self.remote_uri,
- # message="'about:support' hasn't been loaded")
+ self.marionette.go_forward()
+ self.assertEqual(self.marionette.get_url(), "about:support")
self.marionette.close()
self.marionette.switch_to_window(self.start_tab)
@skip_if_mobile("Bug 1333209 - Process killed because of connection loss")
def test_navigate_non_remote_about_pages(self):
# Bug 1311041 - Prevent changing of window handle by forcing the test
# to be run in a new tab.
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py
@@ -1,41 +1,253 @@
# 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/.
import contextlib
import time
import urllib
-from marionette_driver import errors, By, Wait
+from marionette_driver import By, errors, expected, Wait
from marionette_harness import (
MarionetteTestCase,
+ run_if_e10s,
run_if_manage_instance,
skip,
skip_if_mobile,
WindowManagerMixin,
)
def inline(doc):
return "data:text/html;charset=utf-8,%s" % urllib.quote(doc)
+class TestBackForwardNavigation(WindowManagerMixin, MarionetteTestCase):
+
+ def setUp(self):
+ super(TestBackForwardNavigation, self).setUp()
+
+ self.test_page = self.marionette.absolute_url('test.html')
+
+ def open_with_link():
+ link = self.marionette.find_element(By.ID, "new-blank-tab")
+ link.click()
+
+ # Always use a blank new tab for an empty history
+ self.marionette.navigate(self.marionette.absolute_url("windowHandles.html"))
+ self.new_tab = self.open_tab(open_with_link)
+ self.marionette.switch_to_window(self.new_tab)
+ self.assertEqual(self.history_length, 1)
+
+ def tearDown(self):
+ self.marionette.switch_to_parent_frame()
+ self.close_all_tabs()
+
+ super(TestBackForwardNavigation, self).tearDown()
+
+ @property
+ def history_length(self):
+ return self.marionette.execute_script("return window.history.length;")
+
+ def run_test(self, test_pages):
+ # Helper method to run simple back and forward testcases.
+ for index, page in enumerate(test_pages):
+ if "error" in page:
+ with self.assertRaises(page["error"]):
+ self.marionette.navigate(page["url"])
+ else:
+ self.marionette.navigate(page["url"])
+ self.assertEqual(page["url"], self.marionette.get_url())
+ self.assertEqual(self.history_length, index + 1)
+
+ for page in test_pages[-2::-1]:
+ if "error" in page:
+ with self.assertRaises(page["error"]):
+ self.marionette.go_back()
+ else:
+ self.marionette.go_back()
+ self.assertEqual(page["url"], self.marionette.get_url())
+
+ for page in test_pages[1::]:
+ if "error" in page:
+ with self.assertRaises(page["error"]):
+ self.marionette.go_forward()
+ else:
+ self.marionette.go_forward()
+ self.assertEqual(page["url"], self.marionette.get_url())
+
+ def test_no_history_items(self):
+ # Both methods should not raise a failure if no navigation is possible
+ self.marionette.go_back()
+ self.marionette.go_forward()
+
+ def test_data_urls(self):
+ test_pages = [
+ {"url": inline("<p>foobar</p>")},
+ {"url": self.test_page},
+ {"url": inline("<p>foobar</p>")},
+ ]
+ self.run_test(test_pages)
+
+ def test_same_document_hash_change(self):
+ test_pages = [
+ {"url": "{}#23".format(self.test_page)},
+ {"url": self.test_page},
+ {"url": "{}#42".format(self.test_page)},
+ ]
+ self.run_test(test_pages)
+
+ @skip("Causes crashes for JS GC (bug 1344863) and a11y (bug 1344868)")
+ def test_frameset(self):
+ test_pages = [
+ {"url": self.marionette.absolute_url("frameset.html")},
+ {"url": self.test_page},
+ {"url": self.marionette.absolute_url("frameset.html")},
+ ]
+ self.run_test(test_pages)
+
+ def test_frameset_after_navigating_in_frame(self):
+ test_element_locator = (By.ID, "email")
+
+ self.marionette.navigate(self.test_page)
+ self.assertEqual(self.marionette.get_url(), self.test_page)
+ self.assertEqual(self.history_length, 1)
+ page = self.marionette.absolute_url("frameset.html")
+ self.marionette.navigate(page)
+ self.assertEqual(self.marionette.get_url(), page)
+ self.assertEqual(self.history_length, 2)
+ frame = self.marionette.find_element(By.ID, "fifth")
+ self.marionette.switch_to_frame(frame)
+ link = self.marionette.find_element(By.ID, "linkId")
+ link.click()
+
+ # We cannot use get_url() to wait until the target page has been loaded,
+ # because it will return the URL of the top browsing context and doesn't
+ # wait for the page load to be complete.
+ Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
+ expected.element_present(*test_element_locator),
+ message="Target element 'email' has not been found")
+ self.assertEqual(self.history_length, 3)
+
+ # Go back to the frame the click navigated away from
+ self.marionette.go_back()
+ self.assertEqual(self.marionette.get_url(), page)
+ with self.assertRaises(errors.NoSuchElementException):
+ self.marionette.find_element(*test_element_locator)
+
+ # Go back to the non-frameset page
+ self.marionette.switch_to_parent_frame()
+ self.marionette.go_back()
+ self.assertEqual(self.marionette.get_url(), self.test_page)
+
+ # Go forward to the frameset page
+ self.marionette.go_forward()
+ self.assertEqual(self.marionette.get_url(), page)
+
+ # Go forward to the frame the click navigated to
+ # TODO: See above for automatic browser context switches. Hard to do here
+ frame = self.marionette.find_element(By.ID, "fifth")
+ self.marionette.switch_to_frame(frame)
+ self.marionette.go_forward()
+ self.marionette.find_element(*test_element_locator)
+ self.assertEqual(self.marionette.get_url(), page)
+
+ def test_image_document_to_html(self):
+ test_pages = [
+ {"url": self.marionette.absolute_url('black.png')},
+ {"url": self.test_page},
+ {"url": self.marionette.absolute_url('white.png')},
+ ]
+ self.run_test(test_pages)
+
+ def test_image_document_to_image_document(self):
+ test_pages = [
+ {"url": self.marionette.absolute_url('black.png')},
+ {"url": self.marionette.absolute_url('white.png')},
+ ]
+ self.run_test(test_pages)
+
+ @run_if_e10s("Requires e10s mode enabled")
+ def test_remoteness_change(self):
+ # TODO: Verify that a remoteness change happened
+ # like: self.assertNotEqual(self.marionette.current_window_handle, self.new_tab)
+
+ # about:robots is always a non-remote page for now
+ test_pages = [
+ {"url": "about:robots"},
+ {"url": self.test_page},
+ {"url": "about:robots"},
+ ]
+ self.run_test(test_pages)
+
+ def test_navigate_to_requested_about_page_after_error_page(self):
+ test_pages = [
+ {"url": "about:neterror"},
+ {"url": self.marionette.absolute_url("test.html")},
+ {"url": "about:blocked"},
+ ]
+ self.run_test(test_pages)
+
+ def test_timeout_error(self):
+ urls = [
+ self.marionette.absolute_url('slow'),
+ self.test_page,
+ self.marionette.absolute_url('slow'),
+ ]
+
+ # First, load all pages completely to get them added to the cache
+ for index, url in enumerate(urls):
+ self.marionette.navigate(url)
+ self.assertEqual(url, self.marionette.get_url())
+ self.assertEqual(self.history_length, index + 1)
+
+ self.marionette.go_back()
+ self.assertEqual(urls[1], self.marionette.get_url())
+
+ # Force triggering a timeout error
+ self.marionette.timeout.page_load = 0.1
+ with self.assertRaises(errors.TimeoutException):
+ self.marionette.go_back()
+ self.assertEqual(urls[0], self.marionette.get_url())
+ self.marionette.timeout.page_load = 300000
+
+ self.marionette.go_forward()
+ self.assertEqual(urls[1], self.marionette.get_url())
+
+ # Force triggering a timeout error
+ self.marionette.timeout.page_load = 0.1
+ with self.assertRaises(errors.TimeoutException):
+ self.marionette.go_forward()
+ self.assertEqual(urls[2], self.marionette.get_url())
+ self.marionette.timeout.page_load = 300000
+
+ def test_certificate_error(self):
+ test_pages = [
+ {"url": self.fixtures.where_is("/test.html", on="https"),
+ "error": errors.InsecureCertificateException},
+ {"url": self.test_page},
+ {"url": self.fixtures.where_is("/test.html", on="https"),
+ "error": errors.InsecureCertificateException},
+ ]
+ self.run_test(test_pages)
+
+
class TestNavigate(WindowManagerMixin, MarionetteTestCase):
def setUp(self):
super(TestNavigate, self).setUp()
self.marionette.navigate("about:")
self.test_doc = self.marionette.absolute_url("test.html")
self.iframe_doc = self.marionette.absolute_url("test_iframe.html")
def tearDown(self):
- self.close_all_windows()
+ self.marionette.timeout.reset()
+ self.close_all_tabs()
super(TestNavigate, self).tearDown()
@property
def location_href(self):
# Windows 8 has recently seen a proliferation of intermittent
# test failures to do with failing to compare "about:blank" ==
# u"about:blank". For the sake of consistenty, we encode the
@@ -47,21 +259,16 @@ class TestNavigate(WindowManagerMixin, M
def test_set_location_through_execute_script(self):
self.marionette.execute_script(
"window.location.href = '%s'" % self.test_doc)
Wait(self.marionette).until(
lambda _: self.test_doc == self.location_href)
self.assertEqual("Marionette Test", self.marionette.title)
- def test_navigate(self):
- self.marionette.navigate(self.test_doc)
- self.assertNotEqual("about:", self.location_href)
- self.assertEqual("Marionette Test", self.marionette.title)
-
def test_navigate_chrome_error(self):
with self.marionette.using_context("chrome"):
self.assertRaises(errors.UnsupportedOperationException,
self.marionette.navigate, "about:blank")
self.assertRaises(errors.UnsupportedOperationException, self.marionette.go_back)
self.assertRaises(errors.UnsupportedOperationException, self.marionette.go_forward)
self.assertRaises(errors.UnsupportedOperationException, self.marionette.refresh)
@@ -73,43 +280,16 @@ class TestNavigate(WindowManagerMixin, M
self.assertEqual(self.iframe_doc, self.marionette.get_url())
def test_get_current_url(self):
self.marionette.navigate(self.test_doc)
self.assertEqual(self.test_doc, self.marionette.get_url())
self.marionette.navigate("about:blank")
self.assertEqual("about:blank", self.marionette.get_url())
- # TODO(ato): Remove wait conditions when fixing bug 1330348
- def test_go_back(self):
- self.marionette.navigate(self.test_doc)
- self.assertNotEqual("about:blank", self.location_href)
- self.assertEqual("Marionette Test", self.marionette.title)
- self.marionette.navigate("about:blank")
- self.assertEqual("about:blank", self.location_href)
- self.marionette.go_back()
- Wait(self.marionette).until(lambda m: self.location_href == self.test_doc)
- self.assertNotEqual("about:blank", self.location_href)
- self.assertEqual("Marionette Test", self.marionette.title)
-
- # TODO(ato): Remove wait conditions when fixing bug 1330348
- def test_go_forward(self):
- self.marionette.navigate(self.test_doc)
- self.assertNotEqual("about:blank", self.location_href)
- self.assertEqual("Marionette Test", self.marionette.title)
- self.marionette.navigate("about:blank")
- self.assertEqual("about:blank", self.location_href)
- self.marionette.go_back()
- Wait(self.marionette).until(lambda m: self.location_href == self.test_doc)
- self.assertEqual(self.test_doc, self.location_href)
- self.assertEqual("Marionette Test", self.marionette.title)
- self.marionette.go_forward()
- Wait(self.marionette).until(lambda m: self.location_href == "about:blank")
- self.assertEqual("about:blank", self.location_href)
-
def test_refresh(self):
self.marionette.navigate(self.test_doc)
self.assertEqual("Marionette Test", self.marionette.title)
self.assertTrue(self.marionette.execute_script(
"""var elem = window.document.createElement('div'); elem.id = 'someDiv';
window.document.body.appendChild(elem); return true;"""))
self.assertFalse(self.marionette.execute_script(
"return window.document.getElementById('someDiv') == undefined"))
@@ -132,46 +312,39 @@ class TestNavigate(WindowManagerMixin, M
self.marionette.navigate(frame_html)
self.marionette.find_element(By.NAME, "third")
@skip_if_mobile("Bug 1323755 - Socket timeout")
def test_invalid_protocol(self):
with self.assertRaises(errors.MarionetteException):
self.marionette.navigate("thisprotocoldoesnotexist://")
- def test_should_navigate_to_requested_about_page(self):
- self.marionette.navigate("about:neterror")
- self.assertEqual(self.marionette.get_url(), "about:neterror")
- self.marionette.navigate(self.marionette.absolute_url("test.html"))
- self.marionette.navigate("about:blocked")
- self.assertEqual(self.marionette.get_url(), "about:blocked")
-
def test_find_element_state_complete(self):
self.marionette.navigate(self.test_doc)
state = self.marionette.execute_script(
"return window.document.readyState")
self.assertEqual("complete", state)
self.assertTrue(self.marionette.find_element(By.ID, "mozLink"))
def test_error_when_exceeding_page_load_timeout(self):
+ self.marionette.timeout.page_load = 0.1
with self.assertRaises(errors.TimeoutException):
- self.marionette.timeout.page_load = 0.1
self.marionette.navigate(self.marionette.absolute_url("slow"))
- self.marionette.find_element(By.TAG_NAME, "p")
- def test_navigate_iframe(self):
- self.marionette.navigate(self.iframe_doc)
- self.assertTrue('test_iframe.html' in self.marionette.get_url())
- self.assertTrue(self.marionette.find_element(By.ID, "test_iframe"))
+ def test_navigate_to_same_image_document_twice(self):
+ self.marionette.navigate(self.fixtures.where_is("black.png"))
+ self.assertIn("black.png", self.marionette.title)
+ self.marionette.navigate(self.fixtures.where_is("black.png"))
+ self.assertIn("black.png", self.marionette.title)
- def test_fragment(self):
+ def test_navigate_hash_change(self):
doc = inline("<p id=foo>")
self.marionette.navigate(doc)
self.marionette.execute_script("window.visited = true", sandbox=None)
- self.marionette.navigate("%s#foo" % doc)
+ self.marionette.navigate("{}#foo".format(doc))
self.assertTrue(self.marionette.execute_script(
"return window.visited", sandbox=None))
@skip_if_mobile("Bug 1334095 - Timeout: No new tab has been opened")
def test_about_blank_for_new_docshell(self):
""" Bug 1312674 - Hang when loading about:blank for a new docshell."""
def open_with_link():
link = self.marionette.find_element(By.ID, "new-blank-tab")
@@ -182,33 +355,16 @@ class TestNavigate(WindowManagerMixin, M
new_tab = self.open_tab(trigger=open_with_link)
self.marionette.switch_to_window(new_tab)
self.assertEqual(self.marionette.get_url(), "about:blank")
self.marionette.navigate('about:blank')
self.marionette.close()
self.marionette.switch_to_window(self.start_window)
- def test_error_on_tls_navigation(self):
- self.assertRaises(errors.InsecureCertificateException,
- self.marionette.navigate, self.fixtures.where_is("/test.html", on="https"))
-
- def test_html_document_to_image_document(self):
- self.marionette.navigate(self.fixtures.where_is("test.html"))
- self.marionette.navigate(self.fixtures.where_is("white.png"))
- self.assertIn("white.png", self.marionette.title)
-
- def test_image_document_to_image_document(self):
- self.marionette.navigate(self.fixtures.where_is("test.html"))
-
- self.marionette.navigate(self.fixtures.where_is("white.png"))
- self.assertIn("white.png", self.marionette.title)
- self.marionette.navigate(self.fixtures.where_is("black.png"))
- self.assertIn("black.png", self.marionette.title)
-
@run_if_manage_instance("Only runnable if Marionette manages the instance")
@skip_if_mobile("Bug 1322993 - Missing temporary folder")
def test_focus_after_navigation(self):
self.marionette.quit()
self.marionette.start_session()
self.marionette.navigate(inline("<input autofocus>"))
active_el = self.marionette.execute_script("return document.activeElement")
--- a/testing/marionette/harness/marionette_harness/www/frameset.html
+++ b/testing/marionette/harness/marionette_harness/www/frameset.html
@@ -3,12 +3,12 @@
<title>Unique title</title>
</head>
<frameset cols="*, *, *, *, *, *, *">
<frame name="first" src="page/1"/>
<frame name="second" src="page/2?title=Fish"/>
<frame name="third" src="formPage.html"/>
<frame name="fourth" src="framesetPage2.html"/>
<frame id="fifth" src="xhtmlTest.html"/>
- <frame id="sixth" src="iframes.html"/>
+ <frame id="sixth" src="test_iframe.html"/>
<frame id="sixth.iframe1" src="page/3"/>
</frameset>
</html>
\ No newline at end of file
--- a/testing/marionette/harness/marionette_harness/www/xhtmlTest.html
+++ b/testing/marionette/harness/marionette_harness/www/xhtmlTest.html
@@ -5,17 +5,17 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<head>
<title>XHTML Test Page</title>
</head>
<body>
<div class="navigation">
<p><a href="resultPage.html" target="result" name="windowOne">Open new window</a></p>
<p><a href="iframes.html" target="_blank" name="windowTwo">Create a new anonymous window</a></p>
- <p><a href="iframes.html" name="sameWindow">Open page with iframes in same window</a></p>
+ <p><a href="test_iframe.html" name="sameWindow">Open page with iframes in same window</a></p>
<p><a href="javascriptPage.html" target="result" name="windowThree">Open a window with a close button</a></p>
</div>
<a name="notext"><b></b></a>
<div class="content">
<h1 class="header">XHTML Might Be The Future</h1>
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -217,17 +217,16 @@ function addMessageListenerId(messageNam
function removeMessageListenerId(messageName, handler) {
removeMessageListener(messageName + listenerId, handler);
}
var getTitleFn = dispatch(getTitle);
var getPageSourceFn = dispatch(getPageSource);
var getActiveElementFn = dispatch(getActiveElement);
var clickElementFn = dispatch(clickElement);
-var goBackFn = dispatch(goBack);
var getElementAttributeFn = dispatch(getElementAttribute);
var getElementPropertyFn = dispatch(getElementProperty);
var getElementTextFn = dispatch(getElementText);
var getElementTagNameFn = dispatch(getElementTagName);
var getElementRectFn = dispatch(getElementRect);
var isElementEnabledFn = dispatch(isElementEnabled);
var getCurrentUrlFn = dispatch(getCurrentUrl);
var findElementContentFn = dispatch(findElementContent);
@@ -266,17 +265,17 @@ function startListeners() {
addMessageListenerId("Marionette:actionChain", actionChainFn);
addMessageListenerId("Marionette:multiAction", multiActionFn);
addMessageListenerId("Marionette:get", get);
addMessageListenerId("Marionette:pollForReadyState", pollForReadyState);
addMessageListenerId("Marionette:cancelRequest", cancelRequest);
addMessageListenerId("Marionette:getCurrentUrl", getCurrentUrlFn);
addMessageListenerId("Marionette:getTitle", getTitleFn);
addMessageListenerId("Marionette:getPageSource", getPageSourceFn);
- addMessageListenerId("Marionette:goBack", goBackFn);
+ addMessageListenerId("Marionette:goBack", goBack);
addMessageListenerId("Marionette:goForward", goForward);
addMessageListenerId("Marionette:refresh", refresh);
addMessageListenerId("Marionette:findElementContent", findElementContentFn);
addMessageListenerId("Marionette:findElementsContent", findElementsContentFn);
addMessageListenerId("Marionette:getActiveElement", getActiveElementFn);
addMessageListenerId("Marionette:clickElement", clickElementFn);
addMessageListenerId("Marionette:getElementAttribute", getElementAttributeFn);
addMessageListenerId("Marionette:getElementProperty", getElementPropertyFn);
@@ -371,17 +370,17 @@ function deleteSession(msg) {
removeMessageListenerId("Marionette:actionChain", actionChainFn);
removeMessageListenerId("Marionette:multiAction", multiActionFn);
removeMessageListenerId("Marionette:get", get);
removeMessageListenerId("Marionette:pollForReadyState", pollForReadyState);
removeMessageListenerId("Marionette:cancelRequest", cancelRequest);
removeMessageListenerId("Marionette:getTitle", getTitleFn);
removeMessageListenerId("Marionette:getPageSource", getPageSourceFn);
removeMessageListenerId("Marionette:getCurrentUrl", getCurrentUrlFn);
- removeMessageListenerId("Marionette:goBack", goBackFn);
+ removeMessageListenerId("Marionette:goBack", goBack);
removeMessageListenerId("Marionette:goForward", goForward);
removeMessageListenerId("Marionette:refresh", refresh);
removeMessageListenerId("Marionette:findElementContent", findElementContentFn);
removeMessageListenerId("Marionette:findElementsContent", findElementsContentFn);
removeMessageListenerId("Marionette:getActiveElement", getActiveElementFn);
removeMessageListenerId("Marionette:clickElement", clickElementFn);
removeMessageListenerId("Marionette:getElementAttribute", getElementAttributeFn);
removeMessageListenerId("Marionette:getElementProperty", getElementPropertyFn);
@@ -891,23 +890,25 @@ function multiAction(args, maxLen) {
* when a remoteness update happens 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.
*
* @param {function=} cleanupCallback
* Callback to execute when registered event handlers or observer notifications
* have to be cleaned-up.
* @param {number} command_id
* ID of the currently handled message between the driver and listener.
+ * @param {string=} lastSeenURL
+ * Last URL as seen before the navigation request got triggered.
* @param {number} pageTimeout
* Timeout in seconds the method has to wait for the page being finished loading.
* @param {number} startTime
* Unix timestap when the navitation request got triggred.
*/
function pollForReadyState(msg) {
- let {cleanupCallback, command_id, pageTimeout, startTime} = msg.json;
+ let {cleanupCallback, command_id, lastSeenURL, pageTimeout, startTime} = msg.json;
if (typeof startTime == "undefined") {
startTime = new Date().getTime();
}
if (typeof cleanupCallback == "undefined") {
cleanupCallback = () => {};
}
@@ -915,18 +916,26 @@ function pollForReadyState(msg) {
let endTime = startTime + pageTimeout;
let checkLoad = () => {
navTimer.cancel();
let doc = curContainer.frame.document;
if (pageTimeout === null || new Date().getTime() <= endTime) {
+ // Under some conditions (eg. for error pages) the pagehide event is fired
+ // even with a readyState complete for the formerly loaded page.
+ // To prevent race conditition for goBack and goForward we have to wait
+ // until the last seen page has been fully unloaded.
+ // TODO: Bug 1333458 has to improve this.
+ if (!doc.location || lastSeenURL && doc.location.href === lastSeenURL) {
+ navTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
+
// document fully loaded
- if (doc.readyState === "complete") {
+ } else if (doc.readyState === "complete") {
cleanupCallback();
sendOk(command_id);
// document with an insecure cert
} else if (doc.readyState === "interactive" &&
doc.baseURI.startsWith("about:certerror")) {
cleanupCallback();
sendError(new InsecureCertificateError(), command_id);
@@ -1120,22 +1129,18 @@ function cancelRequest() {
if (onDOMContentLoaded) {
removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
}
}
/**
* Get URL of the top-level browsing context.
*/
-function getCurrentUrl(isB2G) {
- if (isB2G) {
- return curContainer.frame.location.href;
- } else {
- return content.location.href;
- }
+function getCurrentUrl() {
+ return content.location.href;
}
/**
* Get the title of the current browsing context.
*/
function getTitle() {
return curContainer.frame.top.document.title;
}
@@ -1143,29 +1148,124 @@ function getTitle() {
/**
* Get source of the current browsing context's DOM.
*/
function getPageSource() {
return curContainer.frame.document.documentElement.outerHTML;
}
/**
- * Cause the browser to traverse one step backward in the joint history
- * of the current top-level browsing context.
+ * Wait for the current page to be unloaded after a navigation got triggered.
+ *
+ * @param {function} trigger
+ * Callback to execute which triggers a page navigation.
+ * @param {function} doneCallback
+ * Callback to execute when the current page has been unloaded.
+ *
+ * It receives a dictionary with the following items as argument:
+ * loading - Flag if a page load will follow.
+ * lastSeenURL - Last seen URL before the navigation request.
+ * startTime - Time when the navigation request has been triggered.
*/
-function goBack() {
- curContainer.frame.history.back();
+function waitForPageUnloaded(trigger, doneCallback) {
+ let currentURL = curContainer.frame.location.href;
+ let start = new Date().getTime();
+
+ function handleEvent(event) {
+ // In case of a remoteness change it can happen that we are no longer able
+ // to access the document's location. In those cases ignore the event,
+ // but keep the code waiting, and assume in the driver that waiting for the
+ // page load is necessary. Bug 1333458 should improve things.
+ if (typeof event.originalTarget.location == "undefined") {
+ return;
+ }
+
+ switch (event.type) {
+ case "hashchange":
+ removeEventListener("hashchange", handleEvent);
+ removeEventListener("pagehide", handleEvent);
+ removeEventListener("unload", handleEvent);
+
+ doneCallback({loading: false, lastSeenURL: currentURL});
+ break;
+
+ case "pagehide":
+ case "unload":
+ if (event.originalTarget === curContainer.frame.document) {
+ removeEventListener("hashchange", handleEvent);
+ removeEventListener("pagehide", handleEvent);
+ removeEventListener("unload", handleEvent);
+
+ doneCallback({loading: true, lastSeenURL: currentURL, startTime: start});
+ }
+ break;
+ }
+ }
+
+ addEventListener("hashchange", handleEvent, false);
+ addEventListener("pagehide", handleEvent, false);
+ addEventListener("unload", handleEvent, false);
+
+ trigger();
}
/**
- * Go forward in history
+ * Cause the browser to traverse one step backward in the joint history
+ * of the current browsing context.
+ *
+ * @param {number} command_id
+ * ID of the currently handled message between the driver and listener.
+ * @param {number} pageTimeout
+ * Timeout in milliseconds the method has to wait for the page being finished loading.
+ */
+function goBack(msg) {
+ let {command_id, pageTimeout} = msg.json;
+
+ waitForPageUnloaded(() => {
+ curContainer.frame.history.back();
+ }, pageLoadStatus => {
+ if (pageLoadStatus.loading) {
+ pollForReadyState({json: {
+ command_id: command_id,
+ lastSeenURL: pageLoadStatus.lastSeenURL,
+ pageTimeout: pageTimeout,
+ startTime: pageLoadStatus.startTime,
+ }});
+ } else {
+ sendOk(command_id);
+ }
+ });
+}
+
+/**
+ * Cause the browser to traverse one step forward in the joint history
+ * of the current browsing context.
+ *
+ * @param {number} command_id
+ * ID of the currently handled message between the driver and listener.
+ * @param {number} pageTimeout
+ * Timeout in milliseconds the method has to wait for the page being finished loading.
*/
function goForward(msg) {
- curContainer.frame.history.forward();
- sendOk(msg.json.command_id);
+ let {command_id, pageTimeout} = msg.json;
+
+ waitForPageUnloaded(() => {
+ curContainer.frame.history.forward();
+ }, pageLoadStatus => {
+ if (pageLoadStatus.loading) {
+ pollForReadyState({json: {
+ command_id: command_id,
+ lastSeenURL: pageLoadStatus.lastSeenURL,
+ pageTimeout: pageTimeout,
+ startTime: pageLoadStatus.startTime,
+ }});
+ } else {
+ sendOk(command_id);
+ }
+ });
}
/**
* Refresh the page
*/
function refresh(msg) {
let command_id = msg.json.command_id;
curContainer.frame.location.reload(true);