Bug 1151909 - Make the inspector actor wait for DOMContentLoaded instead of load. r=pbro draft
authorAlexandre Poirot <poirot.alex@gmail.com>
Mon, 07 Nov 2016 16:07:26 -0800
changeset 444598 71ebb82a308238b62f55441ccfe4d68f2aeca69c
parent 444478 05328d3102efd4d5fc0696489734d7771d24459f
child 444599 0d1d2b60f9cd347b8ac04cd3e8a0104be6ced00c
push id37302
push userbmo:poirot.alex@gmail.com
push dateMon, 28 Nov 2016 09:49:58 +0000
reviewerspbro
bugs1151909
milestone53.0a1
Bug 1151909 - Make the inspector actor wait for DOMContentLoaded instead of load. r=pbro MozReview-Commit-ID: IV4v5ql8GJ9
devtools/client/framework/test/shared-head.js
devtools/client/inspector/markup/test/head.js
devtools/client/inspector/test/browser.ini
devtools/client/inspector/test/browser_inspector_startup.js
devtools/server/actors/inspector.js
--- a/devtools/client/framework/test/shared-head.js
+++ b/devtools/client/framework/test/shared-head.js
@@ -589,8 +589,39 @@ function waitForTitleChange(toolbox) {
   toolbox.win.parent.addEventListener("message", function onmessage(event) {
     if (event.data.name == "set-host-title") {
       toolbox.win.parent.removeEventListener("message", onmessage);
       deferred.resolve();
     }
   });
   return deferred.promise;
 }
+
+/**
+ * Create an HTTP server that can be used to simulate custom requests within
+ * a test.  It is automatically cleaned up when the test ends, so no need to
+ * call `destroy`.
+ *
+ * See https://developer.mozilla.org/en-US/docs/Httpd.js/HTTP_server_for_unit_tests
+ * for more information about how to register handlers.
+ *
+ * The server can be accessed like:
+ *
+ *   const server = createTestHTTPServer();
+ *   let url = "http://localhost: " + server.identity.primaryPort + "/path";
+ *
+ * @returns {HttpServer}
+ */
+function createTestHTTPServer() {
+  const {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
+  let server = new HttpServer();
+
+  registerCleanupFunction(function* cleanup() {
+    let destroyed = defer();
+    server.stop(() => {
+      destroyed.resolve();
+    });
+    yield destroyed.promise;
+  });
+
+  server.start(-1);
+  return server;
+}
--- a/devtools/client/inspector/markup/test/head.js
+++ b/devtools/client/inspector/markup/test/head.js
@@ -415,47 +415,16 @@ function* waitForMultipleChildrenUpdates
       inspector.markup._queuedChildUpdates.size) {
     yield waitForChildrenUpdated(inspector);
     return yield waitForMultipleChildrenUpdates(inspector);
   }
   return undefined;
 }
 
 /**
- * Create an HTTP server that can be used to simulate custom requests within
- * a test.  It is automatically cleaned up when the test ends, so no need to
- * call `destroy`.
- *
- * See https://developer.mozilla.org/en-US/docs/Httpd.js/HTTP_server_for_unit_tests
- * for more information about how to register handlers.
- *
- * The server can be accessed like:
- *
- *   const server = createTestHTTPServer();
- *   let url = "http://localhost: " + server.identity.primaryPort + "/path";
- *
- * @returns {HttpServer}
- */
-function createTestHTTPServer() {
-  const {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
-  let server = new HttpServer();
-
-  registerCleanupFunction(function* cleanup() {
-    let destroyed = defer();
-    server.stop(() => {
-      destroyed.resolve();
-    });
-    yield destroyed.promise;
-  });
-
-  server.start(-1);
-  return server;
-}
-
-/**
  * Registers new backend tab actor.
  *
  * @param {DebuggerClient} client RDP client object (toolbox.target.client)
  * @param {Object} options Configuration object with the following options:
  *
  * - moduleUrl {String}: URL of the module that contains actor implementation.
  * - prefix {String}: prefix of the actor.
  * - actorClass {ActorClassWithSpec}: Constructor object for the actor.
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -163,10 +163,11 @@ subsuite = clipboard
 [browser_inspector_search-label.js]
 [browser_inspector_search-reserved.js]
 [browser_inspector_search-selection.js]
 [browser_inspector_search-sidebar.js]
 [browser_inspector_select-docshell.js]
 [browser_inspector_select-last-selected.js]
 [browser_inspector_search-navigation.js]
 [browser_inspector_sidebarstate.js]
+[browser_inspector_startup.js]
 [browser_inspector_switch-to-inspector-on-pick.js]
 [browser_inspector_textbox-menu.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_startup.js
@@ -0,0 +1,84 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the inspector loads early without waiting for load events.
+
+const server = createTestHTTPServer();
+
+// Register a slow image handler so we can simulate a long time between
+// a reload and the load event firing.
+server.registerContentType("gif", "image/gif");
+function onPageResourceRequest() {
+  return new Promise(done => {
+    server.registerPathHandler("/slow.gif", function (metadata, response) {
+      info("Image has been requested");
+      response.processAsync();
+      done(response);
+    });
+  });
+}
+
+// Test page load events.
+const TEST_URL = "data:text/html," +
+  "<!DOCTYPE html>" +
+  "<head><meta charset='utf-8' /></head>" +
+  "<body>" +
+  "<p>Page loading slowly</p>" +
+  "<img src='http://localhost:" + server.identity.primaryPort + "/slow.gif' />" +
+  "</body>" +
+  "</html>";
+
+add_task(function* () {
+  let {inspector, tab} = yield openInspectorForURL("about:blank");
+
+  let domContentLoaded = waitForLinkedBrowserEvent(tab, "DOMContentLoaded");
+  let pageLoaded = waitForLinkedBrowserEvent(tab, "load");
+
+  let markupLoaded = inspector.once("markuploaded");
+  let onRequest = onPageResourceRequest();
+
+  info("Navigate to the slow loading page");
+  let activeTab = inspector.toolbox.target.activeTab;
+  yield activeTab.navigateTo(TEST_URL);
+
+  info("Wait for request made to the image");
+  let response = yield onRequest;
+
+  // The request made to the image shouldn't block the DOMContentLoaded event
+  info("Wait for DOMContentLoaded");
+  yield domContentLoaded;
+
+  // Nor does it prevent the inspector from loading
+  info("Wait for markup-loaded");
+  yield markupLoaded;
+
+  ok(inspector.markup, "There is a markup view");
+  is(inspector.markup._elt.children.length, 1, "The markup view is rendering");
+  is(yield contentReadyState(tab), "interactive",
+     "Page is still loading but the inspector is ready");
+
+  // Ends page load by unblocking the image request
+  response.finish();
+
+  // We should then receive the page load event
+  info("Wait for load");
+  yield pageLoaded;
+});
+
+function waitForLinkedBrowserEvent(tab, event) {
+  let def = defer();
+  tab.linkedBrowser.addEventListener(event, function cb() {
+    tab.linkedBrowser.removeEventListener(event, cb, true);
+    def.resolve();
+  }, true);
+  return def.promise;
+}
+
+function contentReadyState(tab) {
+  return ContentTask.spawn(tab.linkedBrowser, null, function () {
+    return content.document.readyState;
+  });
+}
--- a/devtools/server/actors/inspector.js
+++ b/devtools/server/actors/inspector.js
@@ -790,17 +790,17 @@ var WalkerActor = protocol.ActorClassWit
     // list contains orphaned nodes that were so retained.
     this._retainedOrphans = new Set();
 
     this.onMutations = this.onMutations.bind(this);
     this.onFrameLoad = this.onFrameLoad.bind(this);
     this.onFrameUnload = this.onFrameUnload.bind(this);
 
     events.on(tabActor, "will-navigate", this.onFrameUnload);
-    events.on(tabActor, "navigate", this.onFrameLoad);
+    events.on(tabActor, "window-ready", this.onFrameLoad);
 
     // Ensure that the root document node actor is ready and
     // managed.
     this.rootNode = this.document();
 
     this.layoutChangeObserver = getLayoutChangesObserver(this.tabActor);
     this._onReflows = this._onReflows.bind(this);
     this.layoutChangeObserver.on("reflows", this._onReflows);
@@ -881,17 +881,17 @@ var WalkerActor = protocol.ActorClassWit
       this.rootDoc = null;
       this.rootNode = null;
       this.layoutHelpers = null;
       this._orphaned = null;
       this._retainedOrphans = null;
       this._refMap = null;
 
       events.off(this.tabActor, "will-navigate", this.onFrameUnload);
-      events.off(this.tabActor, "navigate", this.onFrameLoad);
+      events.off(this.tabActor, "window-ready", this.onFrameLoad);
 
       this.onFrameLoad = null;
       this.onFrameUnload = null;
 
       this.walkerSearch.destroy();
 
       this.layoutChangeObserver.off("reflows", this._onReflows);
       this.layoutChangeObserver.off("resize", this._onResize);
@@ -2342,16 +2342,23 @@ var WalkerActor = protocol.ActorClassWit
       type: "inlineTextChild",
       target: parentActor.actorID,
       inlineTextChild:
         inlineTextChild ? inlineTextChild.form() : undefined
     });
   },
 
   onFrameLoad: function ({ window, isTopLevel }) {
+    let { readyState } = window.document;
+    if (readyState != "interactive" && readyState != "complete") {
+      window.addEventListener("DOMContentLoaded",
+        this.onFrameLoad.bind(this, { window, isTopLevel }),
+        { once: true });
+      return;
+    }
     if (isTopLevel) {
       // If we initialize the inspector while the document is loading,
       // we may already have a root document set in the constructor.
       if (this.rootDoc && !Cu.isDeadWrapper(this.rootDoc) &&
           this.rootDoc.defaultView) {
         this.onFrameUnload({ window: this.rootDoc.defaultView });
       }
       this.rootDoc = window.document;