Bug 780123 - [New Tab Page] Use the hiddenWindow to preload a single newtab page that then serves multiple windows; r=jaws
authorTim Taubert <ttaubert@mozilla.com>
Wed, 19 Sep 2012 16:20:44 +0200
changeset 107482 90cc14017766749cf5635c9a2c244d8d75902020
parent 107356 80499f04e875f18710102fefbfb97dc7bbf4cad0
child 107483 43f5c824aa3dcd0f4633afb0370099495c004f66
push id23489
push userttaubert@mozilla.com
push dateThu, 20 Sep 2012 09:37:23 +0000
treeherderautoland@2208b83cc81d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjaws
bugs780123
milestone18.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 780123 - [New Tab Page] Use the hiddenWindow to preload a single newtab page that then serves multiple windows; r=jaws
browser/base/content/browser.js
browser/base/content/newtab/preload.xhtml
browser/base/jar.mn
browser/components/nsBrowserGlue.js
browser/modules/BrowserNewTabPreloader.jsm
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -149,21 +149,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 #ifdef MOZ_SAFE_BROWSING
 XPCOMUtils.defineLazyGetter(this, "SafeBrowsing", function() {
   let tmp = {};
   Cu.import("resource:///modules/SafeBrowsing.jsm", tmp);
   return tmp.SafeBrowsing;
 });
 #endif
 
-XPCOMUtils.defineLazyGetter(this, "gBrowserNewTabPreloader", function () {
-  let tmp = {};
-  Cu.import("resource:///modules/BrowserNewTabPreloader.jsm", tmp);
-  return new tmp.BrowserNewTabPreloader();
-});
+XPCOMUtils.defineLazyModuleGetter(this, "gBrowserNewTabPreloader",
+  "resource://gre/modules/BrowserNewTabPreloader.jsm", "BrowserNewTabPreloader");
 
 let gInitialPages = [
   "about:blank",
   "about:newtab",
   "about:home",
   "about:privatebrowsing",
   "about:sessionrestore"
 ];
@@ -1404,22 +1401,16 @@ var gBrowserInit = {
     if (document.mozFullScreen)
       onMozEnteredDomFullscreen();
 
 #ifdef MOZ_SERVICES_SYNC
     // initialize the sync UI
     gSyncUI.init();
 #endif
 
-    // Don't preload new tab pages when the toolbar is hidden
-    // (i.e. when the current window is a popup window).
-    if (window.toolbar.visible) {
-      gBrowserNewTabPreloader.init(window);
-    }
-
     gBrowserThumbnails.init();
     TabView.init();
 
     setUrlAndSearchBarWidthForConditionalForwardButton();
     window.addEventListener("resize", function resizeHandler(event) {
       if (event.target == window)
         setUrlAndSearchBarWidthForConditionalForwardButton();
     });
@@ -1558,20 +1549,16 @@ var gBrowserInit = {
     CombinedStopReload.uninit();
 
     gGestureSupport.init(false);
 
     FullScreen.cleanup();
 
     Services.obs.removeObserver(gPluginHandler.pluginCrashed, "plugin-crashed");
 
-    if (!__lookupGetter__("gBrowserNewTabPreloader")) {
-      gBrowserNewTabPreloader.uninit();
-    }
-
     try {
       gBrowser.removeProgressListener(window.XULBrowserWindow);
       gBrowser.removeTabsProgressListener(window.TabsProgressListener);
     } catch (ex) {
     }
 
     PlacesStarButton.uninit();
 
new file mode 100644
--- /dev/null
+++ b/browser/base/content/newtab/preload.xhtml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+
+<!-- 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/. -->
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title></title>
+  </head>
+  <body></body>
+</html>
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -41,16 +41,17 @@ browser.jar:
 *       content/browser/browser.css                   (content/browser.css)
 *       content/browser/browser.js                    (content/browser.js)
 *       content/browser/browser.xul                   (content/browser.xul)
 *       content/browser/browser-tabPreviews.xml       (content/browser-tabPreviews.xml)
         content/browser/content.js                    (content/content.js)
         content/browser/newtab/newTab.xul             (content/newtab/newTab.xul)
 *       content/browser/newtab/newTab.js              (content/newtab/newTab.js)
         content/browser/newtab/newTab.css             (content/newtab/newTab.css)
+        content/browser/newtab/preload.xhtml          (content/newtab/preload.xhtml)
 *       content/browser/pageinfo/pageInfo.xul         (content/pageinfo/pageInfo.xul)
 *       content/browser/pageinfo/pageInfo.js          (content/pageinfo/pageInfo.js)
         content/browser/pageinfo/pageInfo.css         (content/pageinfo/pageInfo.css)
         content/browser/pageinfo/pageInfo.xml         (content/pageinfo/pageInfo.xml)
         content/browser/pageinfo/feeds.js             (content/pageinfo/feeds.js)
         content/browser/pageinfo/feeds.xml            (content/pageinfo/feeds.xml)
         content/browser/pageinfo/permissions.js       (content/pageinfo/permissions.js)
         content/browser/pageinfo/security.js          (content/pageinfo/security.js)
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -33,16 +33,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource:///modules/webappsUI.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
                                   "resource:///modules/PageThumbs.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
                                   "resource:///modules/NewTabUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserNewTabPreloader",
+                                  "resource:///modules/BrowserNewTabPreloader.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "PdfJs",
                                   "resource://pdf.js/PdfJs.jsm");
 
 const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
 const PREF_PLUGINS_UPDATEURL  = "plugins.update.url";
 
 // We try to backup bookmarks at idle times, to avoid doing that at shutdown.
 // Number of idle seconds before trying to backup bookmarks.  15 minutes.
@@ -340,16 +343,17 @@ BrowserGlue.prototype = {
 
     // handle any UI migration
     this._migrateUI();
 
     UserAgentOverrides.init();
     webappsUI.init();
     PageThumbs.init();
     NewTabUtils.init();
+    BrowserNewTabPreloader.init();
     SignInToWebsiteUX.init();
     PdfJs.init();
 
     Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
   },
 
   // the first browser window has finished initializing
   _onFirstWindowLoaded: function BG__onFirstWindowLoaded() {
@@ -365,16 +369,17 @@ BrowserGlue.prototype = {
 #endif
   },
 
   // profile shutdown handler (contains profile cleanup routines)
   _onProfileShutdown: function BG__onProfileShutdown() {
     this._shutdownPlaces();
     this._sanitizer.onShutdown();
     PageThumbs.uninit();
+    BrowserNewTabPreloader.uninit();
   },
 
   // All initial windows have opened.
   _onWindowsRestored: function BG__onWindowsRestored() {
     // Show about:rights notification, if needed.
     if (this._shouldShowRights()) {
       this._showRightsNotification();
 #ifdef MOZ_TELEMETRY_REPORTING
--- a/browser/modules/BrowserNewTabPreloader.jsm
+++ b/browser/modules/BrowserNewTabPreloader.jsm
@@ -2,159 +2,223 @@
  * 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";
 
 let EXPORTED_SYMBOLS = ["BrowserNewTabPreloader"];
 
 const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/Services.jsm");
-
-const PREF_NEWTAB_URL = "browser.newtab.url";
-const PREF_NEWTAB_PRELOAD = "browser.newtab.preload";
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-function BrowserNewTabPreloader() {
-}
-
-BrowserNewTabPreloader.prototype = {
-  _url: null,
-  _window: null,
-  _browser: null,
-  _enabled: null,
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const PREF_BRANCH = "browser.newtab.";
 
-  init: function Preloader_init(aWindow) {
-    if (this._window) {
-      return;
-    }
+let BrowserNewTabPreloader =  {
+  init: function Preloader_init() {
+    Preferences.init();
 
-    this._window = aWindow;
-    this._enabled = Preferences.enabled;
-    this._url = Preferences.url;
-    Preferences.addObserver(this);
-
-    if (this._enabled) {
-      this._createBrowser();
+    if (Preferences.enabled) {
+      HiddenBrowser.create();
     }
   },
 
   uninit: function Preloader_uninit() {
-    if (!this._window) {
-      return;
+    HostFrame.destroy();
+    Preferences.uninit();
+    HiddenBrowser.destroy();
+  },
+
+  newTab: function Preloader_newTab(aTab) {
+    HiddenBrowser.swapWithNewTab(aTab);
+  }
+};
+
+Object.freeze(BrowserNewTabPreloader);
+
+let Preferences = {
+  _enabled: null,
+  _branch: null,
+  _url: null,
+
+  get enabled() {
+    if (this._enabled === null) {
+      this._enabled = this._branch.getBoolPref("preload") &&
+                      !this._branch.prefHasUserValue("url") &&
+                      this.url && this.url != "about:blank";
+    }
+
+    return this._enabled;
+  },
+
+  get url() {
+    if (this._url === null) {
+      this._url = this._branch.getCharPref("url");
     }
 
+    return this._url;
+  },
+
+  init: function Preferences_init() {
+    this._branch = Services.prefs.getBranch(PREF_BRANCH);
+    this._branch.addObserver("", this, false);
+  },
+
+  uninit: function Preferences_uninit() {
+    this._branch.removeObserver("", this);
+    this._branch = null;
+  },
+
+  observe: function Preferences_observe(aSubject, aTopic, aData) {
+    let {url, enabled} = this;
+    this._url = this._enabled = null;
+
+    if (enabled && !this.enabled) {
+      HiddenBrowser.destroy();
+    } else if (!enabled && this.enabled) {
+      HiddenBrowser.create();
+    } else if (this._browser && url != this.url) {
+      HiddenBrowser.update(this.url);
+    }
+  },
+};
+
+let HiddenBrowser = {
+  get isPreloaded() {
+    return this._browser &&
+           this._browser.contentDocument &&
+           this._browser.contentDocument.readyState == "complete" &&
+           this._browser.currentURI.spec == Preferences.url;
+  },
+
+  swapWithNewTab: function HiddenBrowser_swapWithNewTab(aTab) {
+    if (this.isPreloaded) {
+      let tabbrowser = aTab.ownerDocument.defaultView.gBrowser;
+      if (tabbrowser) {
+        tabbrowser.swapNewTabWithBrowser(aTab, this._browser);
+      }
+    }
+  },
+
+  create: function HiddenBrowser_create() {
+    HostFrame.getFrame(function (aFrame) {
+      let doc = aFrame.document;
+      this._browser = doc.createElementNS(XUL_NS, "browser");
+      this._browser.setAttribute("type", "content");
+      this._browser.setAttribute("src", Preferences.url);
+      doc.documentElement.appendChild(this._browser);
+    }.bind(this));
+  },
+
+  update: function HiddenBrowser_update(aURL) {
+    this._browser.setAttribute("src", aURL);
+  },
+
+  destroy: function HiddenBrowser_destroy() {
     if (this._browser) {
       this._browser.parentNode.removeChild(this._browser);
       this._browser = null;
     }
+  }
+};
 
-    this._window = null;
-    Preferences.removeObserver(this);
+let HostFrame = {
+  _listener: null,
+  _privilegedFrame: null,
+
+  _privilegedContentTypes: {
+    "application/vnd.mozilla.xul+xml": true,
+    "application/xhtml+xml": true
   },
 
-  newTab: function Preloader_newTab(aTab) {
-    if (!this._window || !this._enabled) {
-      return;
-    }
-
-    let tabbrowser = this._window.gBrowser;
-    if (tabbrowser && this._isPreloaded()) {
-      tabbrowser.swapNewTabWithBrowser(aTab, this._browser);
-    }
+  get _frame() {
+    delete this._frame;
+    return this._frame = Services.appShell.hiddenDOMWindow;
   },
 
-  observe: function Preloader_observe(aEnabled, aURL) {
-    if (this._url != aURL) {
-      this._url = aURL;
+  get _isReady() {
+    let readyState = this._frame.document.readyState;
+    return (readyState == "complete" || readyState == "interactive");
+  },
 
-      if (this._enabled && aEnabled) {
-        // We're still enabled but the newtab URL has changed.
-        this._browser.setAttribute("src", aURL);
-        return;
-      }
+  get _isPrivileged() {
+    return (this._frame.location.protocol == "chrome:" &&
+            this._frame.document.contentType in this._privilegedContentTypes);
+  },
+
+  getFrame: function HostFrame_getFrame(aCallback) {
+    if (this._isReady && !this._isPrivileged) {
+      this._createPrivilegedFrame();
     }
 
-    if (this._enabled && !aEnabled) {
-      // We got disabled. Remove the browser.
-      this._browser.parentNode.removeChild(this._browser);
-      this._browser = null;
-      this._enabled = false;
-    } else if (!this._enabled && aEnabled) {
-      // We got enabled. Create a browser and start preloading.
-      this._createBrowser();
-      this._enabled = true;
+    if (this._isReady) {
+      aCallback(this._frame);
+    } else {
+      this._waitUntilLoaded(aCallback);
     }
   },
 
-  _createBrowser: function Preloader_createBrowser() {
-    let document = this._window.document;
-    this._browser = document.createElement("browser");
-    this._browser.setAttribute("type", "content");
-    this._browser.setAttribute("src", this._url);
-    this._browser.collapsed = true;
-
-    let panel = document.getElementById("browser-panel");
-    panel.appendChild(this._browser);
+  destroy: function HostFrame_destroy() {
+    delete this._frame;
+    this._listener = null;
   },
 
-  _isPreloaded: function Preloader_isPreloaded()  {
-    return this._browser &&
-           this._browser.contentDocument &&
-           this._browser.contentDocument.readyState == "complete" &&
-           this._browser.currentURI.spec == this._url;
+  _createPrivilegedFrame: function HostFrame_createPrivilegedFrame() {
+    let doc = this._frame.document;
+    let iframe = doc.createElement("iframe");
+    iframe.setAttribute("src", "chrome://browser/content/newtab/preload.xhtml");
+    doc.documentElement.appendChild(iframe);
+    this._frame = iframe.contentWindow;
+  },
+
+  _waitUntilLoaded: function HostFrame_waitUntilLoaded(aCallback) {
+    this._listener = new HiddenWindowLoadListener(this._frame, function () {
+      HostFrame.getFrame(aCallback);
+    });
   }
 };
 
-let Preferences = {
-  _observers: [],
+
+function HiddenWindowLoadListener(aWindow, aCallback) {
+  this._window = aWindow;
+  this._callback = aCallback;
+
+  let docShell = Services.appShell.hiddenWindow.docShell;
+  this._webProgress = docShell.QueryInterface(Ci.nsIWebProgress);
+  this._webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_ALL);
+}
 
-  get _branch() {
-    delete this._branch;
-    return this._branch = Services.prefs.getBranch("browser.newtab.");
+HiddenWindowLoadListener.prototype = {
+  _window: null,
+  _callback: null,
+  _webProgress: null,
+
+  _destroy: function HiddenWindowLoadListener_destroy() {
+    this._webProgress.removeProgressListener(this);
+    this._window = null;
+    this._callback = null;
+    this._webProgress = null;
   },
 
-  get enabled() {
-    if (!this._branch.getBoolPref("preload")) {
-      return false;
-    }
-
-    if (this._branch.prefHasUserValue("url")) {
-      return false;
-    }
-
-    let url = this.url;
-    return url && url != "about:blank";
-  },
-
-  get url() {
-    return this._branch.getCharPref("url");
-  },
-
-  addObserver: function Preferences_addObserver(aObserver) {
-    let index = this._observers.indexOf(aObserver);
-    if (index == -1) {
-      if (this._observers.length == 0) {
-        this._branch.addObserver("", this, false);
-      }
-      this._observers.push(aObserver);
+  onStateChange:
+  function HiddenWindowLoadListener_onStateChange(aWebProgress, aRequest,
+                                                  aStateFlags, aStatus) {
+    if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+        aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
+        aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW &&
+        this._window == aWebProgress.DOMWindow) {
+      this._callback();
+      this._destroy();
     }
   },
 
-  removeObserver: function Preferences_removeObserver(aObserver) {
-    let index = this._observers.indexOf(aObserver);
-    if (index > -1) {
-      if (this._observers.length == 1) {
-        this._branch.removeObserver("", this);
-      }
-      this._observers.splice(index, 1);
-    }
-  },
+  onStatusChange: function () {},
+  onLocationChange: function () {},
+  onProgressChange: function () {},
+  onSecurityChange: function () {},
 
-  observe: function Preferences_observe(aSubject, aTopic, aData) {
-    let url = this.url;
-    let enabled = this.enabled;
-
-    for (let obs of this._observers) {
-      obs.observe(enabled, url);
-    }
-  }
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+                                         Ci.nsISupportsWeakReference])
 };