Bug 975552 - [Australis] Preload about:customizing like we do with about:newtab to increase transition performance. r=jaws,ttaubert.
authorTim Taubert <ttaubert@mozilla.com>
Tue, 25 Feb 2014 18:51:41 +0100
changeset 171288 ae231333480e32538662ee8c0332423bb7d5afdf
parent 171197 9f98b006e8966216829b2f68a5fda26b04a780b4
child 171289 7a80f82f5ce59830809bc9a26a223a97442bcd57
push id26320
push userttaubert@mozilla.com
push dateSat, 01 Mar 2014 07:52:51 +0000
treeherdermozilla-central@6de7f6039a68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjaws, ttaubert
bugs975552
milestone30.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 975552 - [Australis] Preload about:customizing like we do with about:newtab to increase transition performance. r=jaws,ttaubert.
browser/base/content/browser.js
browser/base/content/tabbrowser.xml
browser/components/nsBrowserGlue.js
browser/modules/CustomizationTabPreloader.jsm
browser/modules/moz.build
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -134,16 +134,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 #ifdef MOZ_SAFE_BROWSING
 XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
   "resource://gre/modules/SafeBrowsing.jsm");
 #endif
 
 XPCOMUtils.defineLazyModuleGetter(this, "gBrowserNewTabPreloader",
   "resource:///modules/BrowserNewTabPreloader.jsm", "BrowserNewTabPreloader");
 
+XPCOMUtils.defineLazyModuleGetter(this, "gCustomizationTabPreloader",
+  "resource:///modules/CustomizationTabPreloader.jsm", "CustomizationTabPreloader");
+
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SitePermissions",
   "resource:///modules/SitePermissions.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
   "resource:///modules/sessionstore/SessionStore.jsm");
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1589,16 +1589,18 @@
             // If we just created a new tab that loads the default
             // newtab url, swap in a preloaded page if possible.
             // Do nothing if we're a private window.
             let docShellsSwapped = false;
             if (aURI == BROWSER_NEW_TAB_URL &&
                 !PrivateBrowsingUtils.isWindowPrivate(window) &&
                 !gMultiProcessBrowser) {
               docShellsSwapped = gBrowserNewTabPreloader.newTab(t);
+            } else if (aURI == "about:customizing") {
+              docShellsSwapped = gCustomizationTabPreloader.newTab(t);
             }
 
             // Dispatch a new tab notification.  We do this once we're
             // entirely done, so that things are in a consistent state
             // even if the event listener opens or closes tabs.
             var evt = document.createEvent("Events");
             evt.initEvent("TabOpen", true, false);
             t.dispatchEvent(evt);
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -45,16 +45,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/PageThumbs.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
                                   "resource://gre/modules/NewTabUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserNewTabPreloader",
                                   "resource:///modules/BrowserNewTabPreloader.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizationTabPreloader",
+                                  "resource:///modules/CustomizationTabPreloader.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "PdfJs",
                                   "resource://pdf.js/PdfJs.jsm");
 
 #ifdef NIGHTLY_BUILD
 XPCOMUtils.defineLazyModuleGetter(this, "ShumwayUtils",
                                   "resource://shumway/ShumwayUtils.jsm");
 #endif
 
@@ -464,16 +467,17 @@ BrowserGlue.prototype = {
     this._migrateUI();
 
     this._syncSearchEngines();
 
     webappsUI.init();
     PageThumbs.init();
     NewTabUtils.init();
     BrowserNewTabPreloader.init();
+    CustomizationTabPreloader.init();
     SignInToWebsiteUX.init();
     PdfJs.init();
 #ifdef NIGHTLY_BUILD
     ShumwayUtils.init();
 #endif
     webrtcUI.init();
     AboutHome.init();
     SessionStore.init();
@@ -645,16 +649,17 @@ BrowserGlue.prototype = {
       let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
                          .getService(Ci.nsIAppStartup);
       appStartup.trackStartupCrashEnd();
     } catch (e) {
       Cu.reportError("Could not end startup crash tracking in quit-application-granted: " + e);
     }
 
     BrowserNewTabPreloader.uninit();
+    CustomizationTabPreloader.uninit();
     webappsUI.uninit();
     SignInToWebsiteUX.uninit();
     webrtcUI.uninit();
   },
 
   // All initial windows have opened.
   _onWindowsRestored: function BG__onWindowsRestored() {
     // Show update notification, if needed.
new file mode 100644
--- /dev/null
+++ b/browser/modules/CustomizationTabPreloader.jsm
@@ -0,0 +1,245 @@
+/* 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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["CustomizationTabPreloader"];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const XUL_PAGE = "data:application/vnd.mozilla.xul+xml;charset=utf-8,<window%20id='win'/>";
+const CUSTOMIZATION_URL = "about:customizing";
+
+// The interval between swapping in a preload docShell and kicking off the
+// next preload in the background.
+const PRELOADER_INTERVAL_MS = 600;
+// The initial delay before we start preloading our first customization page. The
+// timer is started after the first 'browser-delayed-startup' has been sent.
+const PRELOADER_INIT_DELAY_MS = 7000;
+
+const TOPIC_TIMER_CALLBACK = "timer-callback";
+const TOPIC_DELAYED_STARTUP = "browser-delayed-startup-finished";
+
+function createTimer(obj, delay) {
+  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+  timer.init(obj, delay, Ci.nsITimer.TYPE_ONE_SHOT);
+  return timer;
+}
+
+function clearTimer(timer) {
+  if (timer) {
+    timer.cancel();
+  }
+  return null;
+}
+
+this.CustomizationTabPreloader = {
+  init: function() {
+    CustomizationTabPreloaderInternal.init();
+  },
+
+  uninit: function () {
+    CustomizationTabPreloaderInternal.uninit();
+  },
+
+  newTab: function (aTab) {
+    return CustomizationTabPreloaderInternal.newTab(aTab);
+  },
+};
+
+Object.freeze(CustomizationTabPreloader);
+
+this.CustomizationTabPreloaderInternal = {
+  _browser: null,
+  _timer: null,
+  _observing: false,
+
+  init: function () {
+    Services.obs.addObserver(this, TOPIC_DELAYED_STARTUP, false);
+    this._observing = true;
+  },
+
+  uninit: function () {
+    this._timer = clearTimer(this._timer);
+
+    if (this._observing) {
+      Services.obs.removeObserver(this, TOPIC_DELAYED_STARTUP);
+      this._observing = false;
+    }
+
+    HostFrame.destroy();
+
+    if (this._browser) {
+      this._browser.destroy();
+      this._browser = null;
+    }
+  },
+
+  newTab: function (aTab) {
+    let win = aTab.ownerDocument.defaultView;
+    if (win.gBrowser && this._browser) {
+      return this._browser.swapWithNewTab(aTab);
+    }
+
+    return false;
+  },
+
+  observe: function (aSubject, aTopic, aData) {
+    if (aTopic == TOPIC_DELAYED_STARTUP) {
+      Services.obs.removeObserver(this, TOPIC_DELAYED_STARTUP);
+      this._observing = false;
+      this._startTimer();
+    } else if (aTopic == TOPIC_TIMER_CALLBACK) {
+      this._timer = null;
+      this._startPreloader();
+    }
+  },
+
+  _startTimer: function () {
+    this._timer = createTimer(this, PRELOADER_INIT_DELAY_MS);
+  },
+
+  _startPreloader: function () {
+    this._browser = new HiddenBrowser();
+  }
+};
+
+function HiddenBrowser() {
+  this._createBrowser();
+}
+
+HiddenBrowser.prototype = {
+  _timer: null,
+
+  get isPreloaded() {
+    return this._browser &&
+           this._browser.contentDocument &&
+           this._browser.contentDocument.readyState === "complete" &&
+           this._browser.currentURI.spec === CUSTOMIZATION_URL;
+  },
+
+  swapWithNewTab: function (aTab) {
+    if (!this.isPreloaded || this._timer) {
+      return false;
+    }
+
+    let win = aTab.ownerDocument.defaultView;
+    let tabbrowser = win.gBrowser;
+
+    if (!tabbrowser) {
+      return false;
+    }
+
+    // Swap docShells.
+    tabbrowser.swapNewTabWithBrowser(aTab, this._browser);
+
+    // Load all default frame scripts attached to the target window.
+    let mm = aTab.linkedBrowser.messageManager;
+    let scripts = win.messageManager.getDelayedFrameScripts();
+    Array.forEach(scripts, ([script, runGlobal]) => mm.loadFrameScript(script, true, runGlobal));
+
+    // Remove the browser, it will be recreated by a timer.
+    this._removeBrowser();
+
+    // Start a timer that will kick off preloading the next page.
+    this._timer = createTimer(this, PRELOADER_INTERVAL_MS);
+
+    // Signal that we swapped docShells.
+    return true;
+  },
+
+  observe: function () {
+    this._timer = null;
+
+    // Start pre-loading the customization page.
+    this._createBrowser();
+  },
+
+  destroy: function () {
+    this._removeBrowser();
+    this._timer = clearTimer(this._timer);
+  },
+
+  _createBrowser: function () {
+    HostFrame.get().then(aFrame => {
+      let doc = aFrame.document;
+      this._browser = doc.createElementNS(XUL_NS, "browser");
+      this._browser.setAttribute("type", "content");
+      this._browser.setAttribute("src", CUSTOMIZATION_URL);
+      this._browser.style.width = "400px";
+      this._browser.style.height = "400px";
+      doc.getElementById("win").appendChild(this._browser);
+    });
+  },
+
+  _removeBrowser: function () {
+    if (this._browser) {
+      this._browser.remove();
+      this._browser = null;
+    }
+  }
+};
+
+let HostFrame = {
+  _frame: null,
+  _deferred: null,
+
+  get hiddenDOMDocument() {
+    return Services.appShell.hiddenDOMWindow.document;
+  },
+
+  get isReady() {
+    return this.hiddenDOMDocument.readyState === "complete";
+  },
+
+  get: function () {
+    if (!this._deferred) {
+      this._deferred = Promise.defer();
+      this._create();
+    }
+
+    return this._deferred.promise;
+  },
+
+  destroy: function () {
+    if (this._frame) {
+      if (!Cu.isDeadWrapper(this._frame)) {
+        this._frame.removeEventListener("load", this, true);
+        this._frame.remove();
+      }
+
+      this._frame = null;
+      this._deferred = null;
+    }
+  },
+
+  handleEvent: function () {
+    let contentWindow = this._frame.contentWindow;
+    if (contentWindow.location.href === XUL_PAGE) {
+      this._frame.removeEventListener("load", this, true);
+      this._deferred.resolve(contentWindow);
+    } else {
+      contentWindow.location = XUL_PAGE;
+    }
+  },
+
+  _create: function () {
+    if (this.isReady) {
+      let doc = this.hiddenDOMDocument;
+      this._frame = doc.createElementNS(HTML_NS, "iframe");
+      this._frame.addEventListener("load", this, true);
+      doc.documentElement.appendChild(this._frame);
+    } else {
+      let flags = Ci.nsIThread.DISPATCH_NORMAL;
+      Services.tm.currentThread.dispatch(() => this._create(), flags);
+    }
+  }
+};
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -6,16 +6,17 @@
 
 TEST_DIRS += ['test']
 
 EXTRA_JS_MODULES += [
     'BrowserNewTabPreloader.jsm',
     'BrowserUITelemetry.jsm',
     'ContentClick.jsm',
     'ContentLinkHandler.jsm',
+    'CustomizationTabPreloader.jsm',
     'Feeds.jsm',
     'NetworkPrioritizer.jsm',
     'offlineAppCache.jsm',
     'SharedFrame.jsm',
     'SignInToWebsite.jsm',
     'SitePermissions.jsm',
     'Social.jsm',
     'TabCrashReporter.jsm',