Bug 1257937 - Make _delayedStartup run after both the outer window and the web content in the initial browser has painted. r?Gijs draft
authorMike Conley <mconley@mozilla.com>
Fri, 18 Mar 2016 15:28:07 -0400
changeset 342302 fc33a4a48cd07789567b2f2fc3685cb8594f1170
parent 342301 a8bf532690746eae8ef90b9e4a9bcf8cdf30f091
child 516551 683bf2163bdeb39042ae3e60f9ab690a5ffdfec5
push id13385
push usermconley@mozilla.com
push dateFri, 18 Mar 2016 20:09:04 +0000
reviewersGijs
bugs1257937
milestone48.0a1
Bug 1257937 - Make _delayedStartup run after both the outer window and the web content in the initial browser has painted. r?Gijs MozReview-Commit-ID: 7tGLtErFKVO
browser/base/content/browser.js
toolkit/content/browser-content.js
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -903,16 +903,23 @@ function RedirectLoad({ target: browser,
     };
     Services.obs.addObserver(delayedStartupFinished,
                              "browser-delayed-startup-finished",
                              false);
   }
 }
 
 var gBrowserInit = {
+  /**
+   * We normally wait until both the parent and the content process have
+   * painted before running _delayedStartup, but if the content process is
+   * hung, we should make sure that _delayedStartup runs anyways. After
+   * contentPaintTimeout ms, we'll stop waiting for the content paint message.
+   */
+  contentPaintTimeout: 1000, // ms
   delayedStartupFinished: false,
 
   onLoad: function() {
     gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver, false);
 
     Services.obs.addObserver(gPluginHandler.NPAPIPluginCrashed, "plugin-crashed", false);
 
     window.addEventListener("AppCommand", HandleAppCommandEvent, true);
@@ -1024,25 +1031,48 @@ var gBrowserInit = {
       let contrastRatio = (backgroundLuminance + 0.05) / (foregroundLuminance + 0.05);
       if (contrastRatio < 3) {
         document.documentElement.setAttribute("darkwindowframe", "true");
       }
     }
 
     ToolbarIconColor.init();
 
-    // Wait until chrome is painted before executing code not critical to making the window visible
+    // Wait until chrome and content is painted before executing code not critical
+    // to making the window visible
     this._boundDelayedStartup = this._delayedStartup.bind(this);
-    window.addEventListener("MozAfterPaint", this._boundDelayedStartup);
+
+    this._parentPaintPromise = new Promise((resolve) => {
+      window.addEventListener("MozAfterPaint", function onParentPaint() {
+        window.removeEventListener("MozAfterPaint", onParentPaint);
+        resolve();
+      });
+    });
+
+    this._contentPaintPromise = new Promise((resolve) => {
+      this._contentPaintTimeoutID = setTimeout(resolve, this.contentPaintTimeout);
+      let mm = window.messageManager;
+      mm.addMessageListener("Browser:FirstContentPaint", function onContentPaint() {
+        mm.removeMessageListener("Browser:FirstContentPaint", onContentPaint);
+        clearTimeout(this._contentPaintTimeoutID);
+        resolve();
+      });
+    });
+
+    Promise.all([this._parentPaintPromise,
+                this._contentPaintPromise]).then(() => {
+      if (this._boundDelayedStartup) {
+        this._boundDelayedStartup();
+      }
+    })
 
     this._loadHandled = true;
   },
 
   _cancelDelayedStartup: function () {
-    window.removeEventListener("MozAfterPaint", this._boundDelayedStartup);
     this._boundDelayedStartup = null;
   },
 
   _delayedStartup: function() {
     let tmp = {};
     Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", tmp);
     let TelemetryTimestamps = tmp.TelemetryTimestamps;
     TelemetryTimestamps.add("delayedStartupStarted");
--- a/toolkit/content/browser-content.js
+++ b/toolkit/content/browser-content.js
@@ -1125,8 +1125,21 @@ var ViewSelectionSource = {
     // replace chars > 0x7f via nsIEntityConverter
     str = str.replace(/[^\0-\u007f]/g, convertEntity);
 
     return str;
   }
 };
 
 ViewSelectionSource.init();
+
+// Some initialization work in the parent for a new browser window is
+// keyed off of the first paint of its content after load has started.
+// Note that we need to wait for the load event to occur first, to avoid
+// MozAfterPaint's that might occur for the blank document that's loaded
+// before the actual load occurs.
+addEventListener("load", function onLoad(e) {
+  removeEventListener("load", onLoad);
+  addEventListener("MozAfterPaint", function onFirstPaint(e) {
+    removeEventListener("MozAfterPaint", onFirstPaint);
+    sendAsyncMessage("Browser:FirstPaint");
+  });
+});