Bug 910036 - about:newtab shouldn't load thumbnails in background when hidden by preloader. r=gavin
authorDrew Willcoxon <adw@mozilla.com>
Wed, 30 Oct 2013 19:33:02 -0700
changeset 153107 d718d65cbe0bdd947d88a336564f69f80cb655db
parent 153106 8880c3f6815adbbd19576c9d8d2e91a18d559842
child 153108 3b3f094b2c221c4d6eb0e127738d636d28bb1fd0
push id25568
push userryanvm@gmail.com
push dateFri, 01 Nov 2013 18:51:39 +0000
treeherderautoland@ad2a5a4f53ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgavin
bugs910036
milestone28.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 910036 - about:newtab shouldn't load thumbnails in background when hidden by preloader. r=gavin
browser/base/content/newtab/page.js
browser/base/content/newtab/preloaderContent.js
browser/base/content/newtab/sites.js
browser/base/content/test/newtab/browser.ini
browser/base/content/test/newtab/browser_newtab_background_captures.js
browser/base/jar.mn
browser/modules/BrowserNewTabPreloader.jsm
--- a/browser/base/content/newtab/page.js
+++ b/browser/base/content/newtab/page.js
@@ -27,16 +27,25 @@ let gPage = {
     let enabled = gAllPages.enabled;
     if (enabled)
       this._init();
 
     this._updateAttributes(enabled);
   },
 
   /**
+   * True if the page is allowed to capture thumbnails using the background
+   * thumbnail service.
+   */
+  get allowBackgroundCaptures() {
+    return document.documentElement.getAttribute("allow-background-captures") ==
+           "true";
+  },
+
+  /**
    * Listens for notifications specific to this page.
    */
   observe: function Page_observe(aSubject, aTopic, aData) {
     if (aTopic == "nsPref:changed") {
       let enabled = gAllPages.enabled;
       this._updateAttributes(enabled);
 
       // Initialize the whole page if we haven't done that, yet.
@@ -69,16 +78,30 @@ let gPage = {
    * is/gets enabled.
    */
   _init: function Page_init() {
     if (this._initialized)
       return;
 
     this._initialized = true;
 
+    this._mutationObserver = new MutationObserver(() => {
+      if (this.allowBackgroundCaptures) {
+        for (let site of gGrid.sites) {
+          if (site) {
+            site.captureIfMissing();
+          }
+        }
+      }
+    });
+    this._mutationObserver.observe(document.documentElement, {
+      attributes: true,
+      attributeFilter: ["allow-background-captures"],
+    });
+
     gLinks.populateCache(function () {
       // Initialize and render the grid.
       gGrid.init();
 
       // Initialize the drop target shim.
       gDropTargetShim.init();
 
 #ifdef XP_MACOSX
@@ -118,16 +141,17 @@ let gPage = {
   },
 
   /**
    * Handles all page events.
    */
   handleEvent: function Page_handleEvent(aEvent) {
     switch (aEvent.type) {
       case "unload":
+        this._mutationObserver.disconnect();
         gAllPages.unregister(this);
         break;
       case "click":
         gAllPages.enabled = !gAllPages.enabled;
         break;
       case "dragover":
         if (gDrag.isValid(aEvent) && gDrag.draggedSite)
           aEvent.preventDefault();
new file mode 100644
--- /dev/null
+++ b/browser/base/content/newtab/preloaderContent.js
@@ -0,0 +1,31 @@
+/* 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/. */
+
+(function () { // bug 673569 workaround :(
+
+const ALLOW_BG_CAPTURES_MSG = "BrowserNewTabPreloader:allowBackgroundCaptures";
+
+addMessageListener(ALLOW_BG_CAPTURES_MSG, function onMsg(msg) {
+  removeMessageListener(ALLOW_BG_CAPTURES_MSG, onMsg);
+
+  if (content.document.readyState == "complete") {
+    setAllowBackgroundCaptures();
+    return;
+  }
+
+  // This is the case when preloading is disabled.
+  addEventListener("load", function onLoad(event) {
+    if (event.target == content.document) {
+      removeEventListener("load", onLoad, true);
+      setAllowBackgroundCaptures();
+    }
+  }, true);
+});
+
+function setAllowBackgroundCaptures() {
+  content.document.documentElement.setAttribute("allow-background-captures",
+                                                "true");
+}
+
+})();
--- a/browser/base/content/newtab/sites.js
+++ b/browser/base/content/newtab/sites.js
@@ -128,22 +128,31 @@ Site.prototype = {
     link.setAttribute("title", tooltip);
     link.setAttribute("href", url);
     this._querySelector(".newtab-title").textContent = title;
 
     if (this.isPinned())
       this._updateAttributes(true);
     // Capture the page if the thumbnail is missing, which will cause page.js
     // to be notified and call our refreshThumbnail() method.
-    BackgroundPageThumbs.captureIfMissing(this.url);
+    this.captureIfMissing();
     // but still display whatever thumbnail might be available now.
     this.refreshThumbnail();
   },
 
   /**
+   * Captures the site's thumbnail in the background, but only if there's no
+   * existing thumbnail and the page allows background captures.
+   */
+  captureIfMissing: function Site_captureIfMissing() {
+    if (gPage.allowBackgroundCaptures)
+      BackgroundPageThumbs.captureIfMissing(this.url);
+  },
+
+  /**
    * Refreshes the thumbnail for the site.
    */
   refreshThumbnail: function Site_refreshThumbnail() {
     let thumbnailURL = PageThumbs.getThumbnailURL(this.url);
     let thumbnail = this._querySelector(".newtab-thumbnail");
     thumbnail.style.backgroundImage = "url(" + thumbnailURL + ")";
   },
 
--- a/browser/base/content/test/newtab/browser.ini
+++ b/browser/base/content/test/newtab/browser.ini
@@ -1,11 +1,12 @@
 [DEFAULT]
 support-files = head.js
 
+[browser_newtab_background_captures.js]
 [browser_newtab_block.js]
 [browser_newtab_bug721442.js]
 [browser_newtab_bug722273.js]
 [browser_newtab_bug723102.js]
 [browser_newtab_bug723121.js]
 [browser_newtab_bug725996.js]
 [browser_newtab_bug734043.js]
 [browser_newtab_bug735987.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_background_captures.js
@@ -0,0 +1,100 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Verifies that hidden, pre-loaded newtabs don't allow background captures, and
+ * when unhidden, do allow background captures.
+ */
+
+const CAPTURE_PREF = "browser.pagethumbnails.capturing_disabled";
+
+function runTests() {
+  let imports = {};
+  Cu.import("resource://gre/modules/PageThumbs.jsm", imports);
+  Cu.import("resource:///modules/BrowserNewTabPreloader.jsm", imports);
+
+  // Disable captures.
+  let originalDisabledState = Services.prefs.getBoolPref(CAPTURE_PREF);
+  Services.prefs.setBoolPref(CAPTURE_PREF, true);
+
+  // Make sure the thumbnail doesn't exist yet.
+  let siteName = "newtab_background_captures";
+  let url = "http://example.com/#" + siteName;
+  let path = imports.PageThumbsStorage.getFilePathForURL(url);
+  let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+  file.initWithPath(path);
+  try {
+    file.remove(false);
+  }
+  catch (err) {}
+
+  // Add a top site.
+  yield setLinks(siteName);
+
+  // We need a handle to a hidden, pre-loaded newtab so we can verify that it
+  // doesn't allow background captures.  Add a newtab, which triggers creation
+  // of a hidden newtab, and then keep calling BrowserNewTabPreloader.newTab
+  // until it returns true, meaning that it swapped the passed-in tab's docshell
+  // for the hidden newtab docshell.
+  let tab = gWindow.gBrowser.addTab("about:blank");
+  yield addNewTabPageTab();
+  let swapWaitCount = 0;
+  let swapped = imports.BrowserNewTabPreloader.newTab(tab);
+  while (!swapped) {
+    if (++swapWaitCount == 10) {
+      ok(false, "Timed out waiting for newtab docshell swap.");
+      return;
+    }
+    // Give the hidden newtab some time to finish loading.
+    yield wait(2000);
+    info("Checking newtab swap " + swapWaitCount);
+    swapped = imports.BrowserNewTabPreloader.newTab(tab);
+  }
+
+  // The tab's docshell is now the previously hidden newtab docshell.
+  let doc = tab.linkedBrowser.contentDocument;
+  isnot(doc.documentElement.getAttribute("allow-background-captures"), "true",
+        "Pre-loaded docshell just synchronously swapped, so background " +
+        "captures should not be allowed yet");
+
+  // Enable captures.
+  Services.prefs.setBoolPref(CAPTURE_PREF, false);
+
+  // Now that the newtab is visible, its allow-background-captures attribute
+  // should be set eventually.
+  let allowBackgroundCaptures = false;
+  let mutationObserver = new MutationObserver(() => {
+    mutationObserver.disconnect();
+    allowBackgroundCaptures = true;
+    is(doc.documentElement.getAttribute("allow-background-captures"), "true",
+       "allow-background-captures should now be true");
+    info("Waiting for thumbnail to be created after observing " +
+         "allow-background-captures change");
+  });
+  mutationObserver.observe(doc.documentElement, {
+    attributes: true,
+    attributeFilter: ["allow-background-captures"],
+  });
+
+  // And the allow-background-captures change should trigger the thumbnail
+  // capture.
+  Services.obs.addObserver(function onCreate(subj, topic, data) {
+    if (data != url)
+      return;
+    ok(allowBackgroundCaptures,
+       "page-thumbnail:create should be observed after " +
+       "allow-background-captures was set");
+    Services.obs.removeObserver(onCreate, "page-thumbnail:create");
+    // Test finished!
+    Services.prefs.setBoolPref(CAPTURE_PREF, originalDisabledState);
+    file.remove(false);
+    TestRunner.next();
+  }, "page-thumbnail:create", false);
+
+  info("Waiting for allow-background-captures change");
+  yield true;
+}
+
+function wait(ms) {
+  setTimeout(TestRunner.next, ms);
+}
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -59,16 +59,17 @@ browser.jar:
 *       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/chatWindow.xul                (content/chatWindow.xul)
         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/preloaderContent.js    (content/newtab/preloaderContent.js)
 *       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/modules/BrowserNewTabPreloader.jsm
+++ b/browser/modules/BrowserNewTabPreloader.jsm
@@ -30,16 +30,18 @@ const PRELOADER_INIT_DELAY_MS = 5000;
 // causes us to update our list of browsers and tabbrowser sizes. This acts as
 // kind of a damper when too many events are occuring in quick succession.
 const PRELOADER_UPDATE_DELAY_MS = 3000;
 
 const TOPIC_TIMER_CALLBACK = "timer-callback";
 const TOPIC_DELAYED_STARTUP = "browser-delayed-startup-finished";
 const TOPIC_XUL_WINDOW_CLOSED = "xul-window-destroyed";
 
+const FRAME_SCRIPT_URL = "chrome://browser/content/newtab/preloaderContent.js";
+
 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) {
@@ -56,29 +58,36 @@ this.BrowserNewTabPreloader = {
   uninit: function Preloader_uninit() {
     Initializer.stop();
     HostFrame.destroy();
     Preferences.uninit();
     HiddenBrowsers.uninit();
   },
 
   newTab: function Preloader_newTab(aTab) {
+    let swapped = false;
     let win = aTab.ownerDocument.defaultView;
     if (win.gBrowser) {
       let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
                      .getInterface(Ci.nsIDOMWindowUtils);
 
       let {width, height} = utils.getBoundsWithoutFlushing(win.gBrowser);
       let hiddenBrowser = HiddenBrowsers.get(width, height)
       if (hiddenBrowser) {
-        return hiddenBrowser.swapWithNewTab(aTab);
+        swapped = hiddenBrowser.swapWithNewTab(aTab);
       }
+
+      // aTab's browser is now visible and is therefore allowed to make
+      // background captures.
+      let msgMan = aTab.linkedBrowser.messageManager;
+      msgMan.loadFrameScript(FRAME_SCRIPT_URL, false);
+      msgMan.sendAsyncMessage("BrowserNewTabPreloader:allowBackgroundCaptures");
     }
 
-    return false;
+    return swapped;
   }
 };
 
 Object.freeze(BrowserNewTabPreloader);
 
 let Initializer = {
   _timer: null,
   _observing: false,