Bug 753448 - Part 1 - Add BrowserNewTabPreloader.jsm; r=gavin
authorTim Taubert <tim.taubert@gmx.de>
Mon, 13 Aug 2012 09:30:18 -0700
changeset 102217 c767ba96126f
parent 102216 9fcf6f5cb2c1
child 102218 a5b58ae99ac9
push id23271
push userttaubert@mozilla.com
push dateTue, 14 Aug 2012 04:18:45 +0000
treeherdermozilla-central@22288130fea2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgavin
bugs753448
milestone17.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 753448 - Part 1 - Add BrowserNewTabPreloader.jsm; r=gavin
browser/app/profile/firefox.js
browser/base/content/tabbrowser.xml
browser/modules/BrowserNewTabPreloader.jsm
browser/modules/Makefile.in
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1131,16 +1131,18 @@ pref("browser.menu.showCharacterEncoding
 
 // Allow using tab-modal prompts when possible.
 pref("prompts.tab_modal.enabled", true);
 // Whether the Panorama should animate going in/out of tabs
 pref("browser.panorama.animate_zoom", true);
 
 // Defines the url to be used for new tabs.
 pref("browser.newtab.url", "about:newtab");
+// Activates preloading of the new tab url.
+pref("browser.newtab.preload", false);
 
 // Toggles the content of 'about:newtab'. Shows the grid when enabled.
 pref("browser.newtabpage.enabled", true);
 
 // Enable the DOM fullscreen API.
 pref("full-screen-api.enabled", true);
 
 // True if the fullscreen API requires approval upon a domain entering fullscreen.
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1808,92 +1808,126 @@
               } while (tab && remainingTabs.indexOf(tab) == -1);
             }
 
             this.selectedTab = tab;
           ]]>
         </body>
       </method>
 
+      <method name="swapNewTabWithBrowser">
+        <parameter name="aNewTab"/>
+        <parameter name="aBrowser"/>
+        <body>
+          <![CDATA[
+            // The browser must be standalone.
+            if (aBrowser.getTabBrowser())
+              throw Cr.NS_ERROR_INVALID_ARG;
+
+            // The tab is definitely not loading.
+            aNewTab.removeAttribute("busy");
+            if (aNewTab == this.selectedTab) {
+              this.mIsBusy = false;
+            }
+
+            this._swapBrowserDocShells(aNewTab, aBrowser);
+
+            // Update the new tab's title.
+            this.setTabTitle(aNewTab);
+
+            if (aNewTab == this.selectedTab) {
+              this.updateCurrentBrowser(true);
+            }
+          ]]>
+        </body>
+      </method>
+
       <method name="swapBrowsersAndCloseOther">
         <parameter name="aOurTab"/>
         <parameter name="aOtherTab"/>
         <body>
           <![CDATA[
             // That's gBrowser for the other window, not the tab's browser!
             var remoteBrowser = aOtherTab.ownerDocument.defaultView.gBrowser;
 
             // First, start teardown of the other browser.  Make sure to not
             // fire the beforeunload event in the process.  Close the other
             // window if this was its last tab.
             if (!remoteBrowser._beginRemoveTab(aOtherTab, true, true))
               return;
 
-            // Unhook our progress listener
-            var ourIndex = aOurTab._tPos;
-            const filter = this.mTabFilters[ourIndex];
-            var tabListener = this.mTabListeners[ourIndex];
-            var ourBrowser = this.getBrowserForTab(aOurTab);
-            ourBrowser.webProgress.removeProgressListener(filter);
-            filter.removeProgressListener(tabListener);
-            var tabListenerBlank = tabListener.mBlank;
-
-            var otherBrowser = aOtherTab.linkedBrowser;
-
-            // Restore current registered open URI.
-            if (ourBrowser.registeredOpenURI) {
-              this._placesAutocomplete.unregisterOpenPage(ourBrowser.registeredOpenURI);
-              delete ourBrowser.registeredOpenURI;
-            }
-            if (otherBrowser.registeredOpenURI) {
-              ourBrowser.registeredOpenURI = otherBrowser.registeredOpenURI;
-              delete otherBrowser.registeredOpenURI;
-            }
-
             // Workarounds for bug 458697
             // Icon might have been set on DOMLinkAdded, don't override that.
+            let ourBrowser = this.getBrowserForTab(aOurTab);
+            let otherBrowser = aOtherTab.linkedBrowser;
             if (!ourBrowser.mIconURL && otherBrowser.mIconURL)
               this.setIcon(aOurTab, otherBrowser.mIconURL);
             var isBusy = aOtherTab.hasAttribute("busy");
             if (isBusy) {
               aOurTab.setAttribute("busy", "true");
               this._tabAttrModified(aOurTab);
               if (aOurTab == this.selectedTab)
                 this.mIsBusy = true;
             }
 
-            // Swap the docshells
-            ourBrowser.swapDocShells(otherBrowser);
+            this._swapBrowserDocShells(aOurTab, otherBrowser);
 
             // Finish tearing down the tab that's going away.
             remoteBrowser._endRemoveTab(aOtherTab);
 
-            // Restore the progress listener
-            tabListener = this.mTabProgressListener(aOurTab, ourBrowser,
-                                                    tabListenerBlank);
-            this.mTabListeners[ourIndex] = tabListener;
-            filter.addProgressListener(tabListener,
-              Components.interfaces.nsIWebProgress.NOTIFY_ALL);
-
-            ourBrowser.webProgress.addProgressListener(filter,
-              Components.interfaces.nsIWebProgress.NOTIFY_ALL);
-
             if (isBusy)
               this.setTabTitleLoading(aOurTab);
             else
               this.setTabTitle(aOurTab);
 
             // If the tab was already selected (this happpens in the scenario
             // of replaceTabWithWindow), notify onLocationChange, etc.
             if (aOurTab == this.selectedTab)
               this.updateCurrentBrowser(true);
           ]]>
         </body>
       </method>
 
+      <method name="_swapBrowserDocShells">
+        <parameter name="aOurTab"/>
+        <parameter name="aOtherBrowser"/>
+        <body>
+          <![CDATA[
+            // Unhook our progress listener
+            let index = aOurTab._tPos;
+            const filter = this.mTabFilters[index];
+            let tabListener = this.mTabListeners[index];
+            let ourBrowser = this.getBrowserForTab(aOurTab);
+            ourBrowser.webProgress.removeProgressListener(filter);
+            filter.removeProgressListener(tabListener);
+
+            // Restore current registered open URI.
+            if (ourBrowser.registeredOpenURI) {
+              this._placesAutocomplete.unregisterOpenPage(ourBrowser.registeredOpenURI);
+              delete ourBrowser.registeredOpenURI;
+            }
+            if (aOtherBrowser.registeredOpenURI) {
+              ourBrowser.registeredOpenURI = aOtherBrowser.registeredOpenURI;
+              delete aOtherBrowser.registeredOpenURI;
+            }
+
+            // Swap the docshells
+            ourBrowser.swapDocShells(aOtherBrowser);
+
+            // Restore the progress listener
+            this.mTabListeners[index] = tabListener =
+              this.mTabProgressListener(aOurTab, ourBrowser, false);
+
+            const notifyAll = Ci.nsIWebProgress.NOTIFY_ALL;
+            filter.addProgressListener(tabListener, notifyAll);
+            ourBrowser.webProgress.addProgressListener(filter, notifyAll);
+          ]]>
+        </body>
+      </method>
+
       <method name="reloadAllTabs">
         <body>
           <![CDATA[
             let tabs = this.visibleTabs;
             let l = tabs.length;
             for (var i = 0; i < l; i++) {
               try {
                 this.getBrowserForTab(tabs[i]).reload();
new file mode 100644
--- /dev/null
+++ b/browser/modules/BrowserNewTabPreloader.jsm
@@ -0,0 +1,160 @@
+/* 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";
+
+let EXPORTED_SYMBOLS = ["BrowserNewTabPreloader"];
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const PREF_NEWTAB_URL = "browser.newtab.url";
+const PREF_NEWTAB_PRELOAD = "browser.newtab.preload";
+
+function BrowserNewTabPreloader() {
+}
+
+BrowserNewTabPreloader.prototype = {
+  _url: null,
+  _window: null,
+  _browser: null,
+  _enabled: null,
+
+  init: function Preloader_init(aWindow) {
+    if (this._window) {
+      return;
+    }
+
+    this._window = aWindow;
+    this._enabled = Preferences.enabled;
+    this._url = Preferences.url;
+    Preferences.addObserver(this);
+
+    if (this._enabled) {
+      this._createBrowser();
+    }
+  },
+
+  uninit: function Preloader_uninit() {
+    if (!this._window) {
+      return;
+    }
+
+    if (this._browser) {
+      this._browser.parentNode.removeChild(this._browser);
+      this._browser = null;
+    }
+
+    this._window = null;
+    Preferences.removeObserver(this);
+  },
+
+  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);
+    }
+  },
+
+  observe: function Preloader_observe(aEnabled, aURL) {
+    if (this._url != aURL) {
+      this._url = aURL;
+
+      if (this._enabled && aEnabled) {
+        // We're still enabled but the newtab URL has changed.
+        this._browser.setAttribute("src", aURL);
+        return;
+      }
+    }
+
+    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;
+    }
+  },
+
+  _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);
+  },
+
+  _isPreloaded: function Preloader_isPreloaded()  {
+    return this._browser &&
+           this._browser.contentDocument &&
+           this._browser.contentDocument.readyState == "complete" &&
+           this._browser.currentURI.spec == this._url;
+  }
+};
+
+let Preferences = {
+  _observers: [],
+
+  get _branch() {
+    delete this._branch;
+    return this._branch = Services.prefs.getBranch("browser.newtab.");
+  },
+
+  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);
+    }
+  },
+
+  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);
+    }
+  },
+
+  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);
+    }
+  }
+};
--- a/browser/modules/Makefile.in
+++ b/browser/modules/Makefile.in
@@ -9,16 +9,17 @@ VPATH   = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 include $(topsrcdir)/config/config.mk
 
 TEST_DIRS += test
 
 EXTRA_JS_MODULES = \
+	BrowserNewTabPreloader.jsm \
 	openLocationLastURL.jsm \
 	NetworkPrioritizer.jsm \
 	NewTabUtils.jsm \
 	offlineAppCache.jsm \
 	TelemetryTimestamps.jsm \
 	Social.jsm \
 	webappsUI.jsm \
 	$(NULL)