Bug 753448 - Part 1 - Add BrowserNewTabPreloader.jsm; r=gavin
authorTim Taubert <tim.taubert@gmx.de>
Mon, 13 Aug 2012 09:30:18 -0700
changeset 101982 c767ba96126f
parent 101981 9fcf6f5cb2c1
child 101983 a5b58ae99ac9
push id999
push userttaubert@mozilla.com
push dateMon, 13 Aug 2012 16:38:01 +0000
treeherderfx-team@274f20ffcc5d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgavin
bugs753448
milestone17.0a1
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)