Bug 674926 - refactor the webProgressListener used to keep track of whether to save tab thumbnails; r=dietrich
authorTim Taubert <tim.taubert@gmx.de>
Sun, 21 Aug 2011 16:29:43 +0200
changeset 76936 7837b186b10b0233bba56a39d41a0a56af8d4e6c
parent 76935 8579b0386cee3b7fc821a5d73f7020537382828c
child 76937 1ea40cc5b3f3677f5ffa60eef3e879302f5253bf
push id78
push userclegnitto@mozilla.com
push dateFri, 16 Dec 2011 17:32:24 +0000
treeherdermozilla-release@79d24e644fdd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdietrich
bugs674926
milestone9.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 674926 - refactor the webProgressListener used to keep track of whether to save tab thumbnails; r=dietrich
browser/base/content/tabview/content.js
browser/base/content/tabview/modules/utils.jsm
browser/base/content/tabview/storagePolicy.js
browser/base/content/tabview/tabview.js
browser/base/content/tabview/thumbnailStorage.js
browser/base/content/tabview/ui.js
browser/base/content/test/tabview/Makefile.in
browser/base/content/test/tabview/browser_tabview_bug627239.js
browser/base/content/test/tabview/browser_tabview_storage_policy.js
--- a/browser/base/content/tabview/content.js
+++ b/browser/base/content/tabview/content.js
@@ -29,18 +29,157 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
-addEventListener("DOMWillOpenModalDialog", function (event) {
-  // (event.isTrusted == true) when the event is generated by a user action
-  // and does not originate from a script.
-  if (event.isTrusted) {
+"use strict";
+
+const Cu = Components.utils;
+
+Cu.import("resource:///modules/tabview/utils.jsm");
+
+let ifaceReq = docShell.QueryInterface(Ci.nsIInterfaceRequestor);
+let webProgress = ifaceReq.getInterface(Ci.nsIWebProgress);
+
+// ----------
+// WindowEventHandler
+//
+// Handles events dispatched by the content window.
+let WindowEventHandler = {
+  // ----------
+  // Function: onLoad
+  // Sends an asynchronous message when the "onload" event for the current page
+  // is fired.
+  onLoad: function WEH_onLoad(event) {
+    sendAsyncMessage("Panorama:Load");
+  },
+
+  // ----------
+  // Function: onDOMWillOpenModalDialog
+  // Sends a synchronous message when the "onDOMWillOpenModalDialog" event
+  // is fired right before a modal dialog will be opened by the current page.
+  onDOMWillOpenModalDialog: function WEH_onDOMWillOpenModalDialog(event) {
+    // (event.isTrusted == true) when the event is generated by a user action
+    // and does not originate from a script.
+    if (!event.isTrusted)
+      return;
+
     // we're intentionally sending a synchronous message to handle this event
     // as quick as possible, switch the selected tab and hide the tabview
     // before the modal dialog is shown
     sendSyncMessage("Panorama:DOMWillOpenModalDialog");
   }
-}, true);
+};
+
+// add event listeners
+addEventListener("load", WindowEventHandler.onLoad);
+addEventListener("DOMWillOpenModalDialog", WindowEventHandler.onDOMWillOpenModalDialog);
+
+// ----------
+// WindowMessageHandler
+//
+// Handles messages sent by the chrome process.
+let WindowMessageHandler = {
+  // ----------
+  // Function: isDocumentLoaded
+  // Checks if the currently active document is loaded.
+  isDocumentLoaded: function WMH_isDocumentLoaded(cx) {
+    let isLoaded = (content.document.readyState == "complete" &&
+                    !webProgress.isLoadingDocument);
+
+    sendAsyncMessage(cx.name, {isLoaded: isLoaded});
+  }
+};
+
+// add message listeners
+addMessageListener("Panorama:isDocumentLoaded", WindowMessageHandler.isDocumentLoaded);
+
+// ----------
+// WebProgressListener
+//
+// Observe the web progress of content pages loaded into this browser. When the
+// state of a page changes we check if we're still allowed to store page
+// information permanently.
+let WebProgressListener = {
+  // ----------
+  // Function: onStateChange
+  // Called by the webProgress when its state changes.
+  onStateChange: function WPL_onStateChange(webProgress, request, flag, status) {
+    // The browser just started loading (again). Explicitly grant storage
+    // because the browser might have been blocked before (e.g. when navigating
+    // from a https-page to a http-page).
+    if (flag & Ci.nsIWebProgressListener.STATE_START) {
+      // ensure the dom window is the top one
+      if (this._isTopWindow(webProgress))
+        sendAsyncMessage("Panorama:StoragePolicy:granted");
+    }
+
+    // The browser finished loading - check the cache control headers. Send
+    // a message if we're not allowed to store information about this page.
+    if (flag & Ci.nsIWebProgressListener.STATE_STOP) {
+      // ensure the dom window is the top one
+      if (this._isTopWindow(webProgress) &&
+          request && request instanceof Ci.nsIHttpChannel) {
+        request.QueryInterface(Ci.nsIHttpChannel);
+
+        let exclude = false;
+        let reason = "";
+
+        // Check if the "Cache-Control" header is "no-store". In this case we're
+        // not allowed to store information about the current page.
+        if (this._isNoStoreResponse(request)) {
+          exclude = true;
+          reason = "no-store";
+        }
+        // Otherwise we'll deny storage if we're currently viewing a https
+        // page without a "Cache-Control: public" header.
+        else if (request.URI.schemeIs("https")) {
+          let cacheControlHeader = this._getCacheControlHeader(request);
+          if (cacheControlHeader && !(/public/i).test(cacheControlHeader)) {
+            exclude = true;
+            reason = "https";
+          }
+        }
+
+        if (exclude)
+          sendAsyncMessage("Panorama:StoragePolicy:denied", {reason: reason});
+      }
+    }
+  },
+
+  // ----------
+  // Function: _isTopWindow
+  // Returns whether the DOMWindow associated with the webProgress is the
+  // top content window (and not an iframe or similar).
+  _isTopWindow: function WPL__isTopWindow(webProgress) {
+    // can throw if there's no associated DOMWindow
+    return !!Utils.attempt(function () webProgress.DOMWindow == content);
+  },
+
+  // ----------
+  // Function: _isNoStoreResponse
+  // Checks if the "Cache-Control" header is "no-store".
+  _isNoStoreResponse: function WPL__isNoStoreResponse(req) {
+    // can throw if called before the response has been received
+    return !!Utils.attempt(function () req.isNoStoreResponse());
+  },
+
+  // ----------
+  // Function: _getCacheControlHeader
+  // Returns the value of the "Cache-Control" header.
+  _getCacheControlHeader: function WPL__getCacheControlHeader(req) {
+    // can throw when the "Cache-Control" header doesn't exist
+    return Utils.attempt(function () req.getResponseHeader("Cache-Control"));
+  },
+
+  // ----------
+  // Implements progress listener interface.
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+                                         Ci.nsISupportsWeakReference,
+                                         Ci.nsISupports])
+};
+
+// add web progress listener
+webProgress.addProgressListener(WebProgressListener, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
--- a/browser/base/content/tabview/modules/utils.jsm
+++ b/browser/base/content/tabview/modules/utils.jsm
@@ -771,10 +771,27 @@ let Utils = {
           if (copy !== undefined)
             target[name] = copy;
         }
       }
     }
 
     // Return the modified object
     return target;
+  },
+
+  // ----------
+  // Function: attempt
+  // Tries to execute a number of functions. Returns immediately the return
+  // value of the first non-failed function without executing successive
+  // functions, or null.
+  attempt: function () {
+    let args = arguments;
+
+    for (let i = 0; i < args.length; i++) {
+      try {
+        return args[i]();
+      } catch (e) {}
+    }
+
+    return null;
   }
 };
new file mode 100644
--- /dev/null
+++ b/browser/base/content/tabview/storagePolicy.js
@@ -0,0 +1,201 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is storagePolicy.js.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Tim Taubert <ttaubert@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// **********
+// Title: storagePolicy.js
+
+// ##########
+// Class: StoragePolicy
+// Singleton for implementing a storage policy for sensitive data.
+let StoragePolicy = {
+  // Pref that controls whether we can store SSL content on disk
+  PREF_DISK_CACHE_SSL: "browser.cache.disk_cache_ssl",
+
+  // Used to keep track of disk_cache_ssl preference
+  _enablePersistentHttpsCaching: null,
+
+  // Used to keep track of browsers whose data we shouldn't store permanently
+  _deniedBrowsers: [],
+
+  // ----------
+  // Function: toString
+  // Prints [StoragePolicy] for debug use.
+  toString: function StoragePolicy_toString() {
+    return "[StoragePolicy]";
+  },
+
+  // ----------
+  // Function: init
+  // Initializes the StoragePolicy object.
+  init: function StoragePolicy_init() {
+    // store the preference value
+    this._enablePersistentHttpsCaching =
+      Services.prefs.getBoolPref(this.PREF_DISK_CACHE_SSL);
+
+    Services.prefs.addObserver(this.PREF_DISK_CACHE_SSL, this, false);
+
+    // tabs are already loaded before UI is initialized so cache-control
+    // values are unknown. We add browsers with https to the list for now.
+    if (!this._enablePersistentHttpsCaching)
+      Array.forEach(gBrowser.browsers, this._initializeBrowser.bind(this));
+
+    // make sure to remove tab browsers when tabs get closed
+    this._onTabClose = this._onTabClose.bind(this);
+    gBrowser.tabContainer.addEventListener("TabClose", this._onTabClose, false);
+
+    let mm = gWindow.messageManager;
+
+    // add message listeners for storage granted
+    this._onGranted = this._onGranted.bind(this);
+    mm.addMessageListener("Panorama:StoragePolicy:granted", this._onGranted);
+
+    // add message listeners for storage denied
+    this._onDenied = this._onDenied.bind(this);
+    mm.addMessageListener("Panorama:StoragePolicy:denied", this._onDenied);
+  },
+
+  // ----------
+  // Function: _initializeBrowser
+  // Initializes the given browser and checks if we need to add it to our
+  // internal exclusion list.
+  _initializeBrowser: function StoragePolicy__initializeBrowser(browser) {
+    let self = this;
+
+    function checkExclusion() {
+      if (browser.currentURI.schemeIs("https"))
+        self._deniedBrowsers.push(browser);
+    }
+
+    function waitForDocumentLoad() {
+      mm.addMessageListener("Panorama:Load", function onLoad(cx) {
+        mm.removeMessageListener(cx.name, onLoad);
+        checkExclusion(browser);
+      });
+    }
+
+    this._isDocumentLoaded(browser, function (isLoaded) {
+      if (isLoaded)
+        checkExclusion();
+      else
+        waitForDocumentLoad();
+    });
+  },
+
+  // ----------
+  // Function: _isDocumentLoaded
+  // Check if the given browser's document is loaded.
+  _isDocumentLoaded: function StoragePolicy__isDocumentLoaded(browser, callback) {
+    let mm = browser.messageManager;
+    let message = "Panorama:isDocumentLoaded";
+
+    mm.addMessageListener(message, function onMessage(cx) {
+      mm.removeMessageListener(cx.name, onMessage);
+      callback(cx.json.isLoaded);
+    });
+
+    mm.sendAsyncMessage(message);
+  },
+
+  // ----------
+  // Function: uninit
+  // Is called by UI.init() when the browser windows is closed.
+  uninit: function StoragePolicy_uninit() {
+    Services.prefs.removeObserver(this.PREF_DISK_CACHE_SSL, this);
+    gBrowser.removeTabsProgressListener(this);
+    gBrowser.tabContainer.removeEventListener("TabClose", this._onTabClose, false);
+
+    let mm = gWindow.messageManager;
+
+    // remove message listeners
+    mm.removeMessageListener("Panorama:StoragePolicy:granted", this._onGranted);
+    mm.removeMessageListener("Panorama:StoragePolicy:denied", this._onDenied);
+  },
+
+  // ----------
+  // Function: _onGranted
+  // Handle the 'granted' message and remove the given browser from the list
+  // of denied browsers.
+  _onGranted: function StoragePolicy__onGranted(cx) {
+    let index = this._deniedBrowsers.indexOf(cx.target);
+
+    if (index > -1)
+      this._deniedBrowsers.splice(index, 1);
+  },
+
+  // ----------
+  // Function: _onDenied
+  // Handle the 'denied' message and add the given browser to the list of denied
+  // browsers.
+  _onDenied: function StoragePolicy__onDenied(cx) {
+    // exclusion is optional because cache-control is not no-store or public and
+    // the protocol is https. don't exclude when persistent https caching is
+    // enabled.
+    if ("https" == cx.json.reason && this._enablePersistentHttpsCaching)
+      return;
+
+    let browser = cx.target;
+
+    if (this._deniedBrowsers.indexOf(browser) == -1)
+      this._deniedBrowsers.push(browser);
+  },
+
+  // ----------
+  // Function: _onTabClose
+  // Remove the browser from our internal exclusion list when a tab gets closed.
+  _onTabClose: function StoragePolicy__onTabClose(event) {
+    let browser = event.target.linkedBrowser;
+    let index = this._deniedBrowsers.indexOf(browser);
+
+    if (index > -1)
+      this._deniedBrowsers.splice(index, 1);
+  },
+
+  // ----------
+  // Function: canStoreThumbnailForTab
+  // Returns whether we're allowed to store the thumbnail of the given tab.
+  canStoreThumbnailForTab: function StoragePolicy_canStoreThumbnailForTab(tab) {
+    return (this._deniedBrowsers.indexOf(tab.linkedBrowser) == -1);
+  },
+
+  // ----------
+  // Function: observe
+  // Observe pref changes.
+  observe: function StoragePolicy_observe(subject, topic, data) {
+    this._enablePersistentHttpsCaching =
+      Services.prefs.getBoolPref(this.PREF_DISK_CACHE_SSL);
+  }
+};
--- a/browser/base/content/tabview/tabview.js
+++ b/browser/base/content/tabview/tabview.js
@@ -66,16 +66,17 @@ let AllTabs = {
     gBrowser.tabContainer.removeEventListener(this._events[eventName], callback, false);
   }
 };
 
 # NB: Certain files need to evaluate before others
 
 #include iq.js
 #include storage.js
+#include storagePolicy.js
 #include items.js
 #include groupitems.js
 #include tabitems.js
 #include drag.js
 #include trench.js
 #include thumbnailStorage.js
 #include ui.js
 #include search.js
--- a/browser/base/content/tabview/thumbnailStorage.js
+++ b/browser/base/content/tabview/thumbnailStorage.js
@@ -39,36 +39,26 @@
 // Title: thumbnailStorage.js
 
 // ##########
 // Class: ThumbnailStorage
 // Singleton for persistent storage of thumbnail data.
 let ThumbnailStorage = {
   CACHE_CLIENT_IDENTIFIER: "tabview-cache",
   CACHE_PREFIX: "moz-panorama:",
-  PREF_DISK_CACHE_SSL: "browser.cache.disk_cache_ssl",
 
   // Holds the cache session reference
   _cacheSession: null,
 
   // Holds the string input stream reference
   _stringInputStream: null,
 
   // Holds the storage stream reference
   _storageStream: null,
 
-  // Holds the progress listener reference
-  _progressListener: null,
-
-  // Used to keep track of disk_cache_ssl preference
-  enablePersistentHttpsCaching: null,
-
-  // Used to keep track of browsers whose thumbs we shouldn't save
-  excludedBrowsers: [],
-
   // ----------
   // Function: toString
   // Prints [ThumbnailStorage] for debug use.
   toString: function ThumbnailStorage_toString() {
     return "[ThumbnailStorage]";
   },
 
   // ----------
@@ -82,50 +72,16 @@ let ThumbnailStorage = {
     this._cacheSession = cacheService.createSession(
       this.CACHE_CLIENT_IDENTIFIER, Ci.nsICache.STORE_ON_DISK, true);
     this._stringInputStream = Components.Constructor(
       "@mozilla.org/io/string-input-stream;1", "nsIStringInputStream",
       "setData");
     this._storageStream = Components.Constructor(
       "@mozilla.org/storagestream;1", "nsIStorageStream", 
       "init");
-
-    // store the preference value
-    this.enablePersistentHttpsCaching =
-      Services.prefs.getBoolPref(this.PREF_DISK_CACHE_SSL);
-
-    Services.prefs.addObserver(this.PREF_DISK_CACHE_SSL, this, false);
-
-    let self = this;
-    // tabs are already loaded before UI is initialized so cache-control
-    // values are unknown.  We add browsers with https to the list for now.
-    gBrowser.browsers.forEach(function(browser) {
-      let checkAndAddToList = function(browserObj) {
-        if (!self.enablePersistentHttpsCaching &&
-            browserObj.currentURI.schemeIs("https"))
-          self.excludedBrowsers.push(browserObj);
-      };
-      if (browser.contentDocument.readyState != "complete" ||
-          browser.webProgress.isLoadingDocument) {
-        browser.addEventListener("load", function onLoad() {
-          browser.removeEventListener("load", onLoad, true);
-          checkAndAddToList(browser);
-        }, true);
-      } else {
-        checkAndAddToList(browser);
-      }
-    });
-    gBrowser.addTabsProgressListener(this);
-  },
-
-  // Function: uninit
-  // Should be called when window is unloaded.
-  uninit: function ThumbnailStorage_uninit() {
-    gBrowser.removeTabsProgressListener(this);
-    Services.prefs.removeObserver(this.PREF_DISK_CACHE_SSL, this);
   },
 
   // ----------
   // Function: _openCacheEntry
   // Opens a cache entry for the given <url> and requests access <access>.
   // Calls <successCallback>(entry) when the entry was successfully opened with
   // requested access rights. Otherwise calls <errorCallback>().
   _openCacheEntry: function ThumbnailStorage__openCacheEntry(url, access, successCallback, errorCallback) {
@@ -146,32 +102,26 @@ let ThumbnailStorage = {
       let status = Cr.NS_OK;
       onCacheEntryAvailable(entry, entry.accessGranted, status);
     } else {
       let listener = new CacheListener(onCacheEntryAvailable);
       this._cacheSession.asyncOpenCacheEntry(key, access, listener);
     }
   },
 
-  // Function: _shouldSaveThumbnail
-  // Checks whether to save tab's thumbnail or not.
-  _shouldSaveThumbnail : function ThumbnailStorage__shouldSaveThumbnail(tab) {
-    return (this.excludedBrowsers.indexOf(tab.linkedBrowser) == -1);
-  },
-
   // ----------
   // Function: saveThumbnail
   // Saves the <imageData> to the cache using the given <url> as key.
   // Calls <callback>(status, data) when finished, passing true or false
   // (indicating whether the operation succeeded).
   saveThumbnail: function ThumbnailStorage_saveThumbnail(tab, imageData, callback) {
     Utils.assert(tab, "tab");
     Utils.assert(imageData, "imageData");
-    
-    if (!this._shouldSaveThumbnail(tab)) {
+
+    if (!StoragePolicy.canStoreThumbnailForTab(tab)) {
       tab._tabViewTabItem._sendToSubscribers("deniedToCacheImageData");
       if (callback)
         callback(false);
       return;
     }
 
     let self = this;
 
@@ -283,71 +233,16 @@ let ThumbnailStorage = {
     }
 
     let onCacheEntryUnavailable = function() {
       completed(false);
     }
 
     this._openCacheEntry(url, Ci.nsICache.ACCESS_READ,
         onCacheEntryAvailable, onCacheEntryUnavailable);
-  },
-
-  // ----------
-  // Function: observe
-  // Implements the observer interface.
-  observe: function ThumbnailStorage_observe(subject, topic, data) {
-    this.enablePersistentHttpsCaching =
-      Services.prefs.getBoolPref(this.PREF_DISK_CACHE_SSL);
-  },
-
-  // ----------
-  // Implements progress listener interface.
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
-                                         Ci.nsISupportsWeakReference,
-                                         Ci.nsISupports]),
-
-  onStateChange: function ThumbnailStorage_onStateChange(
-    browser, webProgress, request, flag, status) {
-    if (flag & Ci.nsIWebProgressListener.STATE_START &&
-        flag & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
-      // ensure the dom window is the top one
-      if (webProgress.DOMWindow.parent == webProgress.DOMWindow) {
-        let index = this.excludedBrowsers.indexOf(browser);
-        if (index != -1)
-          this.excludedBrowsers.splice(index, 1);
-      }
-    }
-    if (flag & Ci.nsIWebProgressListener.STATE_STOP &&
-        flag & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
-      // ensure the dom window is the top one
-      if (webProgress.DOMWindow.parent == webProgress.DOMWindow &&
-          request && request instanceof Ci.nsIHttpChannel) {
-        request.QueryInterface(Ci.nsIHttpChannel);
-
-        let inhibitPersistentThumb = false;
-        if (request.isNoStoreResponse()) {
-           inhibitPersistentThumb = true;
-        } else if (!this.enablePersistentHttpsCaching &&
-                   request.URI.schemeIs("https")) {
-          let cacheControlHeader;
-          try {
-            cacheControlHeader = request.getResponseHeader("Cache-Control");
-          } catch(e) {
-            // this error would occur when "Cache-Control" doesn't exist in
-            // the eaders
-          }
-          if (cacheControlHeader && !(/public/i).test(cacheControlHeader))
-            inhibitPersistentThumb = true;
-        }
-
-        if (inhibitPersistentThumb &&
-            this.excludedBrowsers.indexOf(browser) == -1)
-          this.excludedBrowsers.push(browser);
-      }
-    }
   }
 }
 
 // ##########
 // Class: CacheListener
 // Generic CacheListener for feeding to asynchronous cache calls.
 // Calls <callback>(entry, access, status) when the requested cache entry
 // is available.
--- a/browser/base/content/tabview/ui.js
+++ b/browser/base/content/tabview/ui.js
@@ -170,16 +170,19 @@ let UI = {
       this._initPageDirection();
 
       // ___ thumbnail storage
       ThumbnailStorage.init();
 
       // ___ storage
       Storage.init();
 
+      // ___ storage policy
+      StoragePolicy.init();
+
       if (Storage.readWindowBusyState(gWindow))
         this.storageBusy();
 
       let data = Storage.readUIData(gWindow);
       this._storageSanity(data);
       this._pageBounds = data.pageBounds;
 
       // ___ currentTab
@@ -318,17 +321,17 @@ let UI = {
       func();
     });
     this._cleanupFunctions = [];
 
     // additional clean up
     TabItems.uninit();
     GroupItems.uninit();
     Storage.uninit();
-    ThumbnailStorage.uninit();
+    StoragePolicy.uninit();
 
     this._removeTabActionHandlers();
     this._currentTab = null;
     this._pageBounds = null;
     this._reorderTabItemsOnShow = null;
     this._reorderTabsOnHide = null;
     this._frameInitialized = false;
   },
--- a/browser/base/content/test/tabview/Makefile.in
+++ b/browser/base/content/test/tabview/Makefile.in
@@ -109,17 +109,16 @@ include $(topsrcdir)/config/rules.mk
                  browser_tabview_bug624953.js \
                  browser_tabview_bug625195.js \
                  browser_tabview_bug625269.js \
                  browser_tabview_bug625424.js \
                  browser_tabview_bug626368.js \
                  browser_tabview_bug626455.js \
                  browser_tabview_bug626525.js \
                  browser_tabview_bug626791.js \
-                 browser_tabview_bug627239.js \
                  browser_tabview_bug627288.js \
                  browser_tabview_bug627736.js \
                  browser_tabview_bug628061.js \
                  browser_tabview_bug628165.js \
                  browser_tabview_bug628270.js \
                  browser_tabview_bug629189.js \
                  browser_tabview_bug629195.js \
                  browser_tabview_bug630102.js \
@@ -161,16 +160,17 @@ include $(topsrcdir)/config/rules.mk
                  browser_tabview_group.js \
                  browser_tabview_launch.js \
                  browser_tabview_multiwindow_search.js \
                  browser_tabview_privatebrowsing.js \
                  browser_tabview_rtl.js \
                  browser_tabview_search.js \
                  browser_tabview_snapping.js \
                  browser_tabview_startup_transitions.js \
+                 browser_tabview_storage_policy.js \
                  browser_tabview_undo_group.js \
                  dummy_page.html \
                  head.js \
                  search1.html \
                  search2.html \
                  test_bug600645.html \
                  test_bug644097.html \
                  $(NULL)
rename from browser/base/content/test/tabview/browser_tabview_bug627239.js
rename to browser/base/content/test/tabview/browser_tabview_storage_policy.js
--- a/browser/base/content/test/tabview/browser_tabview_bug627239.js
+++ b/browser/base/content/test/tabview/browser_tabview_storage_policy.js
@@ -1,128 +1,129 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const PREF_DISK_CACHE_SSL = "browser.cache.disk_cache_ssl";
+
 let contentWindow;
-let enablePersistentHttpsCaching;
 let newTab;
 
 function test() {
   waitForExplicitFinish();
 
   newTab = gBrowser.addTab();
 
   HttpRequestObserver.register();
 
   registerCleanupFunction(function () {
     HttpRequestObserver.unregister();
     if (gBrowser.tabs[1])
       gBrowser.removeTab(gBrowser.tabs[1]);
     hideTabView();
 
-    contentWindow.ThumbnailStorage.enablePersistentHttpsCaching =
-        enablePersistentHttpsCaching;
+    Services.prefs.clearUserPref(PREF_DISK_CACHE_SSL);
   });
 
   showTabView(function() {
     contentWindow = TabView.getContentWindow();
     test1();
   });
 }
 
 
 function test1() {
   // page with cache-control: no-store, should not save thumbnail
   HttpRequestObserver.cacheControlValue = "no-store";
-  newTab.linkedBrowser.loadURI("http://www.example.com/browser/browser/base/content/test/tabview/dummy_page.html");
 
-  afterAllTabsLoaded(function() {
+  whenStorageDenied(newTab, function () {
     let tabItem = newTab._tabViewTabItem;
 
-    ok(!contentWindow.ThumbnailStorage._shouldSaveThumbnail(newTab), 
+    ok(!contentWindow.StoragePolicy.canStoreThumbnailForTab(newTab), 
        "Should not save the thumbnail for tab");
 
     whenDeniedToCacheImageData(tabItem, test2);
     tabItem.save(true);
     HttpRequestObserver.cacheControlValue = null;
   });
+
+  newTab.linkedBrowser.loadURI("http://www.example.com/browser/browser/base/content/test/tabview/dummy_page.html");
 }
 
 function test2() {
   // page with cache-control: private, should save thumbnail
   HttpRequestObserver.cacheControlValue = "private";
 
   newTab.linkedBrowser.loadURI("http://www.example.com/");
   afterAllTabsLoaded(function() {
     let tabItem = newTab._tabViewTabItem;
 
-    ok(contentWindow.ThumbnailStorage._shouldSaveThumbnail(newTab), 
+    ok(contentWindow.StoragePolicy.canStoreThumbnailForTab(newTab), 
        "Should save the thumbnail for tab");
 
     whenSavedCachedImageData(tabItem, test3);
     tabItem.save(true);
   });
 }
 
 function test3() {
   // page with cache-control: private with https caching enabled, should save thumbnail
   HttpRequestObserver.cacheControlValue = "private";
 
-  enablePersistentHttpsCaching =
-    contentWindow.ThumbnailStorage.enablePersistentHttpsCaching;
-  contentWindow.ThumbnailStorage.enablePersistentHttpsCaching = true;
+  Services.prefs.setBoolPref(PREF_DISK_CACHE_SSL, true);
 
   newTab.linkedBrowser.loadURI("https://example.com/browser/browser/base/content/test/tabview/dummy_page.html");
   afterAllTabsLoaded(function() {
     let tabItem = newTab._tabViewTabItem;
 
-    ok(contentWindow.ThumbnailStorage._shouldSaveThumbnail(newTab),
+    ok(contentWindow.StoragePolicy.canStoreThumbnailForTab(newTab),
        "Should save the thumbnail for tab");
 
     whenSavedCachedImageData(tabItem, test4);
     tabItem.save(true);
   });
 }
 
 function test4() {
   // page with cache-control: public with https caching disabled, should save thumbnail
   HttpRequestObserver.cacheControlValue = "public";
 
-  contentWindow.ThumbnailStorage.enablePersistentHttpsCaching = false;
+  Services.prefs.setBoolPref(PREF_DISK_CACHE_SSL, false);
 
   newTab.linkedBrowser.loadURI("https://example.com/browser/browser/base/content/test/tabview/");
   afterAllTabsLoaded(function() {
     let tabItem = newTab._tabViewTabItem;
 
-    ok(contentWindow.ThumbnailStorage._shouldSaveThumbnail(newTab),
+    ok(contentWindow.StoragePolicy.canStoreThumbnailForTab(newTab),
        "Should save the thumbnail for tab");
 
     whenSavedCachedImageData(tabItem, test5);
     tabItem.save(true);
   });
 }
 
 function test5() {
   // page with cache-control: private with https caching disabled, should not save thumbnail
   HttpRequestObserver.cacheControlValue = "private";
- 
-  newTab.linkedBrowser.loadURI("https://example.com/");
-  afterAllTabsLoaded(function() {
+
+  whenStorageDenied(newTab, function () {
     let tabItem = newTab._tabViewTabItem;
 
-    ok(!contentWindow.ThumbnailStorage._shouldSaveThumbnail(newTab),
-       "Should not the thumbnail for tab");
+    ok(!contentWindow.StoragePolicy.canStoreThumbnailForTab(newTab),
+       "Should not save the thumbnail for tab");
 
     whenDeniedToCacheImageData(tabItem, function () {
       hideTabView(function () {
         gBrowser.removeTab(gBrowser.tabs[1]);
         finish();
       });
     });
     tabItem.save(true);
   });
+
+  newTab.linkedBrowser.loadURI("https://example.com/");
 }
 
 let HttpRequestObserver = {
   cacheControlValue: null,
 
   observe: function(subject, topic, data) {
     if (topic == "http-on-examine-response" && this.cacheControlValue) {
       let httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
@@ -147,8 +148,17 @@ function whenSavedCachedImageData(tabIte
 }
 
 function whenDeniedToCacheImageData(tabItem, callback) {
   tabItem.addSubscriber("deniedToCacheImageData", function onDenied() {
     tabItem.removeSubscriber("deniedToCacheImageData", onDenied);
     callback();
   });
 }
+
+function whenStorageDenied(tab, callback) {
+  let mm = tab.linkedBrowser.messageManager;
+
+  mm.addMessageListener("Panorama:StoragePolicy:denied", function onDenied() {
+    mm.removeMessageListener("Panorama:StoragePolicy:denied", onDenied);
+    executeSoon(callback);
+  });
+}