Bug 742047 - Part 1 - Create SessionStorage.jsm and migrate existing code; r=zpao
authorTim Taubert <tim.taubert@gmx.de>
Sun, 03 Jun 2012 11:45:51 +0200
changeset 95679 ebd52606c461f5ada17e67e6a089ba8f2c578928
parent 95639 d0ebcaa7efb5fc96b2a4f7ef08434f6af969e4f2
child 95680 329aa567fec9b634602db7dcfaf47a81bc5bf7eb
push id22827
push userrcampbell@mozilla.com
push dateSun, 03 Jun 2012 20:41:58 +0000
treeherdermozilla-central@0e4f8e1a141b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerszpao
bugs742047
milestone15.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 742047 - Part 1 - Create SessionStorage.jsm and migrate existing code; r=zpao
browser/components/sessionstore/src/SessionStorage.jsm
browser/components/sessionstore/src/SessionStore.jsm
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/src/SessionStorage.jsm
@@ -0,0 +1,109 @@
+/* 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/. */
+
+let EXPORTED_SYMBOLS = ["SessionStorage"];
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
+  "resource:///modules/sessionstore/SessionStore.jsm");
+
+let SessionStorage = {
+  /**
+   * Updates all sessionStorage "super cookies"
+   * @param aTabData
+   *        The data object for a specific tab
+   * @param aHistory
+   *        That tab's session history
+   * @param aDocShell
+   *        That tab's docshell (containing the sessionStorage)
+   * @param aFullData
+   *        always return privacy sensitive data (use with care)
+   * @param aIsPinned
+   *        the tab is pinned and should be treated differently for privacy
+   */
+  serialize: function ssto_serialize(aTabData, aHistory, aDocShell, aFullData,
+                                     aIsPinned) {
+    let storageData = {};
+    let hasContent = false;
+
+    for (let i = 0; i < aHistory.count; i++) {
+      let uri;
+      try {
+        uri = aHistory.getEntryAtIndex(i, false).URI;
+      }
+      catch (ex) {
+        // Chances are that this is getEntryAtIndex throwing, as seen in bug 669196.
+        // We've already asserted in _collectTabData, so we won't show that again.
+        continue;
+      }
+      // sessionStorage is saved per origin (cf. nsDocShell::GetSessionStorageForURI)
+      let domain = uri.spec;
+      try {
+        if (uri.host)
+          domain = uri.prePath;
+      }
+      catch (ex) { /* this throws for host-less URIs (such as about: or jar:) */ }
+      if (storageData[domain] ||
+          !(aFullData || SessionStore.checkPrivacyLevel(uri.schemeIs("https"), aIsPinned)))
+        continue;
+
+      let storage, storageItemCount = 0;
+      try {
+        var principal = Services.scriptSecurityManager.getCodebasePrincipal(uri);
+
+        // Using getSessionStorageForPrincipal instead of getSessionStorageForURI
+        // just to be able to pass aCreate = false, that avoids creation of the
+        // sessionStorage object for the page earlier than the page really
+        // requires it. It was causing problems while accessing a storage when
+        // a page later changed its domain.
+        storage = aDocShell.getSessionStorageForPrincipal(principal, "", false);
+        if (storage)
+          storageItemCount = storage.length;
+      }
+      catch (ex) { /* sessionStorage might throw if it's turned off, see bug 458954 */ }
+      if (storageItemCount == 0)
+        continue;
+
+      let data = storageData[domain] = {};
+      for (let j = 0; j < storageItemCount; j++) {
+        try {
+          let key = storage.key(j);
+          let item = storage.getItem(key);
+          data[key] = item;
+        }
+        catch (ex) { /* this currently throws for secured items (cf. bug 442048) */ }
+      }
+      hasContent = true;
+    }
+
+    if (hasContent)
+      aTabData.storage = storageData;
+  },
+
+  /**
+   * restores all sessionStorage "super cookies"
+   * @param aStorageData
+   *        Storage data to be restored
+   * @param aDocShell
+   *        A tab's docshell (containing the sessionStorage)
+   */
+  deserialize: function ssto_deserialize(aStorageData, aDocShell) {
+    for (let url in aStorageData) {
+      let uri = Services.io.newURI(url, null, null);
+      let storage = aDocShell.getSessionStorageForURI(uri, "");
+      for (let key in aStorageData[url]) {
+        try {
+          storage.setItem(key, aStorageData[url][key]);
+        }
+        catch (ex) { Cu.reportError(ex); } // throws e.g. for URIs that can't have sessionStorage
+      }
+    }
+  }
+};
+
+Object.freeze(SessionStorage);
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -81,16 +81,18 @@ Cu.import("resource:///modules/Telemetry
 Cu.import("resource://gre/modules/TelemetryStopwatch.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ScratchpadManager",
   "resource:///modules/devtools/scratchpad-manager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DocumentUtils",
   "resource:///modules/sessionstore/DocumentUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
+ "resource:///modules/sessionstore/SessionStorage.jsm");
 
 #ifdef MOZ_CRASHREPORTER
 XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
   "@mozilla.org/xre/app-info;1", "nsICrashReporter");
 #endif
 
 function debug(aMsg) {
   aMsg = ("SessionStore: " + aMsg).replace(/\S{80}/g, "$&\n");
@@ -195,16 +197,20 @@ let SessionStore = {
   },
 
   persistTabAttribute: function ss_persistTabAttribute(aName) {
     SessionStoreInternal.persistTabAttribute(aName);
   },
 
   restoreLastSession: function ss_restoreLastSession() {
     SessionStoreInternal.restoreLastSession();
+  },
+
+  checkPrivacyLevel: function ss_checkPrivacyLevel(aIsHTTPS, aUseDefaultPref) {
+    return SessionStoreInternal.checkPrivacyLevel(aIsHTTPS, aUseDefaultPref);
   }
 };
 
 // Freeze the SessionStore object. We don't want anyone to modify it.
 Object.freeze(SessionStore);
 
 let SessionStoreInternal = {
   QueryInterface: XPCOMUtils.generateQI([
@@ -1983,18 +1989,18 @@ let SessionStoreInternal = {
     }
 
     if (aTab.__SS_extdata)
       tabData.extData = aTab.__SS_extdata;
     else if (tabData.extData)
       delete tabData.extData;
 
     if (history && browser.docShell instanceof Ci.nsIDocShell)
-      this._serializeSessionStorage(tabData, history, browser.docShell, aFullData,
-                                    aTab.pinned);
+      SessionStorage.serialize(tabData, history, browser.docShell, aFullData,
+                               aTab.pinned);
 
     return tabData;
   },
 
   /**
    * Get an object that is a serialized representation of a History entry
    * Used for data storage
    * @param aEntry
@@ -2049,17 +2055,17 @@ let SessionStoreInternal = {
     var x = {}, y = {};
     aEntry.getScrollPosition(x, y);
     if (x.value != 0 || y.value != 0)
       entry.scroll = x.value + "," + y.value;
 
     try {
       var prefPostdata = this._prefBranch.getIntPref("sessionstore.postdata");
       if (aEntry.postData && (aFullData || prefPostdata &&
-            this._checkPrivacyLevel(aEntry.URI.schemeIs("https"), aIsPinned))) {
+            this.checkPrivacyLevel(aEntry.URI.schemeIs("https"), aIsPinned))) {
         aEntry.postData.QueryInterface(Ci.nsISeekableStream).
                         seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
         var stream = Cc["@mozilla.org/binaryinputstream;1"].
                      createInstance(Ci.nsIBinaryInputStream);
         stream.setInputStream(aEntry.postData);
         var postBytes = stream.readByteArray(stream.available());
         var postdata = String.fromCharCode.apply(null, postBytes);
         if (aFullData || prefPostdata == -1 ||
@@ -2131,88 +2137,16 @@ let SessionStoreInternal = {
       if (children.length)
         entry.children = children;
     }
 
     return entry;
   },
 
   /**
-   * Updates all sessionStorage "super cookies"
-   * @param aTabData
-   *        The data object for a specific tab
-   * @param aHistory
-   *        That tab's session history
-   * @param aDocShell
-   *        That tab's docshell (containing the sessionStorage)
-   * @param aFullData
-   *        always return privacy sensitive data (use with care)
-   * @param aIsPinned
-   *        the tab is pinned and should be treated differently for privacy
-   */
-  _serializeSessionStorage:
-    function ssi_serializeSessionStorage(aTabData, aHistory, aDocShell, aFullData, aIsPinned) {
-    let storageData = {};
-    let hasContent = false;
-
-    for (let i = 0; i < aHistory.count; i++) {
-      let uri;
-      try {
-        uri = aHistory.getEntryAtIndex(i, false).URI;
-      }
-      catch (ex) {
-        // Chances are that this is getEntryAtIndex throwing, as seen in bug 669196.
-        // We've already asserted in _collectTabData, so we won't show that again.
-        continue;
-      }
-      // sessionStorage is saved per origin (cf. nsDocShell::GetSessionStorageForURI)
-      let domain = uri.spec;
-      try {
-        if (uri.host)
-          domain = uri.prePath;
-      }
-      catch (ex) { /* this throws for host-less URIs (such as about: or jar:) */ }
-      if (storageData[domain] ||
-          !(aFullData || this._checkPrivacyLevel(uri.schemeIs("https"), aIsPinned)))
-        continue;
-
-      let storage, storageItemCount = 0;
-      try {
-        var principal = Services.scriptSecurityManager.getCodebasePrincipal(uri);
-
-        // Using getSessionStorageForPrincipal instead of getSessionStorageForURI
-        // just to be able to pass aCreate = false, that avoids creation of the
-        // sessionStorage object for the page earlier than the page really
-        // requires it. It was causing problems while accessing a storage when
-        // a page later changed its domain.
-        storage = aDocShell.getSessionStorageForPrincipal(principal, "", false);
-        if (storage)
-          storageItemCount = storage.length;
-      }
-      catch (ex) { /* sessionStorage might throw if it's turned off, see bug 458954 */ }
-      if (storageItemCount == 0)
-        continue;
-
-      let data = storageData[domain] = {};
-      for (let j = 0; j < storageItemCount; j++) {
-        try {
-          let key = storage.key(j);
-          let item = storage.getItem(key);
-          data[key] = item;
-        }
-        catch (ex) { /* XXXzeniko this currently throws for secured items (cf. bug 442048) */ }
-      }
-      hasContent = true;
-    }
-
-    if (hasContent)
-      aTabData.storage = storageData;
-  },
-
-  /**
    * go through all tabs and store the current scroll positions
    * and innerHTML content of WYSIWYG editors
    * @param aWindow
    *        Window reference
    */
   _updateTextAndScrollData: function ssi_updateTextAndScrollData(aWindow) {
     var browsers = aWindow.gBrowser.browsers;
     this._windows[aWindow.__SSi].tabs.forEach(function (tabData, i) {
@@ -2290,17 +2224,17 @@ let SessionStoreInternal = {
       if (aData.children && aData.children[i])
         this._updateTextAndScrollDataForFrame(aWindow, aContent.frames[i],
                                               aData.children[i], aUpdateFormData,
                                               aFullData, aIsPinned);
     }
     var isHTTPS = this._getURIFromString((aContent.parent || aContent).
                                          document.location.href).schemeIs("https");
     let isAboutSR = aContent.top.document.location.href == "about:sessionrestore";
-    if (aFullData || this._checkPrivacyLevel(isHTTPS, aIsPinned) || isAboutSR) {
+    if (aFullData || this.checkPrivacyLevel(isHTTPS, aIsPinned) || isAboutSR) {
       if (aFullData || aUpdateFormData) {
         let formData = DocumentUtils.getFormData(aContent.document);
 
         // We want to avoid saving data for about:sessionrestore as a string.
         // Since it's stored in the form as stringified JSON, stringifying further
         // causes an explosion of escape characters. cf. bug 467409
         if (formData && isAboutSR) {
           formData.id["sessionData"] = JSON.parse(formData.id["sessionData"]);
@@ -2412,17 +2346,17 @@ let SessionStoreInternal = {
    *        aCheckPrivacy
    */
   _extractHostsForCookiesFromHostScheme:
     function ssi_extractHostsForCookiesFromHostScheme(aHost, aScheme, aHosts, aCheckPrivacy, aIsPinned) {
     // host and scheme may not be set (for about: urls for example), in which
     // case testing scheme will be sufficient.
     if (/https?/.test(aScheme) && !aHosts[aHost] &&
         (!aCheckPrivacy ||
-         this._checkPrivacyLevel(aScheme == "https", aIsPinned))) {
+         this.checkPrivacyLevel(aScheme == "https", aIsPinned))) {
       // By setting this to true or false, we can determine when looking at
       // the host in _updateCookies if we should check for privacy.
       aHosts[aHost] = aIsPinned;
     }
     else if (aScheme == "file") {
       aHosts[aHost] = true;
     }
   },
@@ -2483,18 +2417,18 @@ let SessionStoreInternal = {
         }
         catch (ex) {
           debug("getCookiesFromHost failed. Host: " + host);
         }
         while (list && list.hasMoreElements()) {
           var cookie = list.getNext().QueryInterface(Ci.nsICookie2);
           // window._hosts will only have hosts with the right privacy rules,
           // so there is no need to do anything special with this call to
-          // _checkPrivacyLevel.
-          if (cookie.isSession && _this._checkPrivacyLevel(cookie.isSecure, isPinned)) {
+          // checkPrivacyLevel.
+          if (cookie.isSession && _this.checkPrivacyLevel(cookie.isSecure, isPinned)) {
             // use the cookie's host, path, and name as keys into a hash,
             // to make sure we serialize each cookie only once
             if (!(cookie.host in jscookies &&
                   cookie.path in jscookies[cookie.host] &&
                   cookie.name in jscookies[cookie.host][cookie.path])) {
               var jscookie = { "host": cookie.host, "value": cookie.value };
               // only add attributes with non-default values (saving a few bits)
               if (cookie.path) jscookie.path = cookie.path;
@@ -3122,17 +3056,17 @@ let SessionStoreInternal = {
       browser.docShell["allow" + aCapability] = disallow.indexOf(aCapability) == -1;
     });
     for (let name in this.xulAttributes)
       tab.removeAttribute(name);
     for (let name in tabData.attributes)
       tab.setAttribute(name, tabData.attributes[name]);
 
     if (tabData.storage && browser.docShell instanceof Ci.nsIDocShell)
-      this._deserializeSessionStorage(tabData.storage, browser.docShell);
+      SessionStorage.deserialize(tabData.storage, browser.docShell);
 
     // notify the tabbrowser that the tab chrome has been restored
     var event = aWindow.document.createEvent("Events");
     event.initEvent("SSTabRestoring", true, false);
     tab.dispatchEvent(event);
 
     // Restore the history in the next tab
     aWindow.setTimeout(function(){
@@ -3422,36 +3356,16 @@ let SessionStoreInternal = {
                                                        childDocIdents), i);
       }
     }
 
     return shEntry;
   },
 
   /**
-   * restores all sessionStorage "super cookies"
-   * @param aStorageData
-   *        Storage data to be restored
-   * @param aDocShell
-   *        A tab's docshell (containing the sessionStorage)
-   */
-  _deserializeSessionStorage: function ssi_deserializeSessionStorage(aStorageData, aDocShell) {
-    for (let url in aStorageData) {
-      let uri = this._getURIFromString(url);
-      let storage = aDocShell.getSessionStorageForURI(uri, "");
-      for (let key in aStorageData[url]) {
-        try {
-          storage.setItem(key, aStorageData[url][key]);
-        }
-        catch (ex) { Cu.reportError(ex); } // throws e.g. for URIs that can't have sessionStorage
-      }
-    }
-  },
-
-  /**
    * Restore properties to a loaded document
    */
   restoreDocument: function ssi_restoreDocument(aWindow, aBrowser, aEvent) {
     // wait for the top frame to be loaded completely
     if (!aEvent || !aEvent.originalTarget || !aEvent.originalTarget.defaultView ||
         aEvent.originalTarget.defaultView != aEvent.originalTarget.defaultView.top) {
       return;
     }
@@ -3924,17 +3838,17 @@ let SessionStoreInternal = {
    * don't save sensitive data if the user doesn't want to
    * (distinguishes between encrypted and non-encrypted sites)
    * @param aIsHTTPS
    *        Bool is encrypted
    * @param aUseDefaultPref
    *        don't do normal check for deferred
    * @returns bool
    */
-  _checkPrivacyLevel: function ssi_checkPrivacyLevel(aIsHTTPS, aUseDefaultPref) {
+  checkPrivacyLevel: function ssi_checkPrivacyLevel(aIsHTTPS, aUseDefaultPref) {
     let pref = "sessionstore.privacy_level";
     // If we're in the process of quitting and we're not autoresuming the session
     // then we should treat it as a deferred session. We have a different privacy
     // pref for that case.
     if (!aUseDefaultPref && this._loadState == STATE_QUITTING && !this._doResumeSession())
       pref = "sessionstore.privacy_level_deferred";
     return this._prefBranch.getIntPref(pref) < (aIsHTTPS ? PRIVACY_ENCRYPTED : PRIVACY_FULL);
   },