Bug 937659 - Implement page load strategy. draft
authorHenrik Skupin <mail@hskupin.info>
Wed, 19 Apr 2017 13:22:13 +0200
changeset 576024 efcaa16ab49855ac097d35e3dec99b786eadb5e8
parent 576023 18c4fa43c2eb2efb59a76a48ca9a23c4c77860e5
child 628090 df6a5ab0042b79fa0ab5daabdd482425495966ff
push id58244
push userbmo:hskupin@gmail.com
push dateThu, 11 May 2017 07:47:03 +0000
bugs937659
milestone55.0a1
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
testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py
testing/marionette/harness/marionette_harness/www/slow_resource.html
testing/marionette/listener.js
--- 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.