Bug 1151909 - Make the inspector actor wait for DOMContentLoaded instead of load. r=pbro
authorAlexandre Poirot <poirot.alex@gmail.com>
Mon, 07 Nov 2016 16:07:26 -0800
changeset 324355 7f137d9fa7ba6f092eb1821b3972ff2f1ea85a12
parent 324354 a105cf2d33a5643d63c516d5ecfe917d8eb5eeee
child 324356 c307daee56b588148e7853a74a3190bf64034499
push id34675
push userapoirot@mozilla.com
push dateMon, 28 Nov 2016 10:02:55 +0000
treeherderautoland@fb4f5c7e082a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspbro
bugs1151909
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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;