Merge the last PGO-green inbound changeset to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 15 Apr 2013 21:49:22 -0400
changeset 128849 1d9c510b3742cfb00df7307fe8a7c93f39f8eba3
parent 128790 41bb93c802d120fed95e75d0498451ed498962a1 (current diff)
parent 128848 1bcb465f740fe015ffa394b04cd97276cda095b9 (diff)
child 128852 a228816895fda87605b30cb076092b063d1988fb
child 128882 19504efbd4183c7549c37bb5c96b0e138cfdee3d
push id24543
push userryanvm@gmail.com
push dateTue, 16 Apr 2013 01:49:32 +0000
treeherdermozilla-central@1d9c510b3742 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone23.0a1
first release with
nightly linux32
1d9c510b3742 / 23.0a1 / 20130416030901 / files
nightly linux64
1d9c510b3742 / 23.0a1 / 20130416030901 / files
nightly mac
1d9c510b3742 / 23.0a1 / 20130416030901 / files
nightly win32
1d9c510b3742 / 23.0a1 / 20130416030901 / files
nightly win64
1d9c510b3742 / 23.0a1 / 20130416030901 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge the last PGO-green inbound changeset to m-c.
browser/base/content/test/browser_aboutHome.js
dom/interfaces/storage/nsIDOMStorageItem.idl
dom/interfaces/storage/nsIDOMStorageObsolete.idl
dom/src/storage/StorageChild.cpp
dom/src/storage/StorageChild.h
dom/src/storage/StorageParent.cpp
dom/src/storage/StorageParent.h
dom/src/storage/nsDOMStorage.cpp
dom/src/storage/nsDOMStorage.h
dom/src/storage/nsDOMStorageBaseDB.cpp
dom/src/storage/nsDOMStorageBaseDB.h
dom/src/storage/nsDOMStorageDBWrapper.cpp
dom/src/storage/nsDOMStorageDBWrapper.h
dom/src/storage/nsDOMStorageMemoryDB.cpp
dom/src/storage/nsDOMStorageMemoryDB.h
dom/src/storage/nsDOMStoragePersistentDB.cpp
dom/src/storage/nsDOMStoragePersistentDB.h
dom/src/storage/nsLocalStorageCache.cpp
dom/src/storage/nsLocalStorageCache.h
dom/tests/mochitest/localstorage/frameBug624047.html
dom/tests/mochitest/localstorage/test_bug624047.html
mobile/android/chrome/content/browser.js
services/common/preferences.js
services/common/tests/unit/test_preferences.js
services/healthreport/providers.jsm
services/metrics/dataprovider.jsm
--- a/browser/base/content/abouthealthreport/abouthealth.js
+++ b/browser/base/content/abouthealthreport/abouthealth.js
@@ -1,17 +1,17 @@
 /* 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";
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
-Cu.import("resource://services-common/preferences.js");
+Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 const reporter = Cc["@mozilla.org/datareporting/service;1"]
                    .getService(Ci.nsISupports)
                    .wrappedJSObject
                    .healthReporter;
 
 const policy = Cc["@mozilla.org/datareporting/service;1"]
--- a/browser/base/content/browser-gestureSupport.js
+++ b/browser/base/content/browser-gestureSupport.js
@@ -187,25 +187,27 @@ let gGestureSupport = {
     if (canGoBack)
       aEvent.allowedDirections |= isLTR ? aEvent.DIRECTION_LEFT :
                                           aEvent.DIRECTION_RIGHT;
     if (canGoForward)
       aEvent.allowedDirections |= isLTR ? aEvent.DIRECTION_RIGHT :
                                           aEvent.DIRECTION_LEFT;
 
     let isVerticalSwipe = false;
-    if (aEvent.direction == aEvent.DIRECTION_UP) {
-      isVerticalSwipe = true;
-      // Force a synchronous scroll to the top of the page.
-      content.scrollTo(content.scrollX, 0);
-    }
-    else if (aEvent.direction == aEvent.DIRECTION_DOWN) {
-      isVerticalSwipe = true;
-      // Force a synchronous scroll to the bottom of the page.
-      content.scrollTo(content.scrollX, content.scrollMaxY);
+    if (gHistorySwipeAnimation.active) {
+      if (aEvent.direction == aEvent.DIRECTION_UP) {
+        isVerticalSwipe = true;
+        // Force a synchronous scroll to the top of the page.
+        content.scrollTo(content.scrollX, 0);
+      }
+      else if (aEvent.direction == aEvent.DIRECTION_DOWN) {
+        isVerticalSwipe = true;
+        // Force a synchronous scroll to the bottom of the page.
+        content.scrollTo(content.scrollX, content.scrollMaxY);
+      }
     }
 
     gHistorySwipeAnimation.startAnimation(isVerticalSwipe);
 
     this._doUpdate = function GS__doUpdate(aEvent) {
       gHistorySwipeAnimation.updateAnimation(aEvent.delta);
     };
 
--- a/browser/base/content/test/browser_aboutHome.js
+++ b/browser/base/content/test/browser_aboutHome.js
@@ -20,19 +20,19 @@ registerCleanupFunction(function() {
 });
 
 let gTests = [
 
 {
   desc: "Check that clearing cookies does not clear storage",
   setup: function ()
   {
-    Cc["@mozilla.org/dom/storagemanager;1"]
-      .getService(Ci.nsIObserver)
-      .observe(null, "cookie-changed", "cleared");
+    Cc["@mozilla.org/observer-service;1"]
+      .getService(Ci.nsIObserverService)
+      .notifyObservers(null, "cookie-changed", "cleared");
   },
   run: function (aSnippetsMap)
   {
     isnot(aSnippetsMap.get("snippets-last-update"), null,
           "snippets-last-update should have a value");
   }
 },
 
--- a/browser/base/content/test/browser_datareporting_notification.js
+++ b/browser/base/content/test/browser_datareporting_notification.js
@@ -1,16 +1,16 @@
 /* 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 sendNotifyRequest(name) {
   let ns = {};
   Components.utils.import("resource://gre/modules/services/datareporting/policy.jsm", ns);
-  Components.utils.import("resource://services-common/preferences.js", ns);
+  Components.utils.import("resource://gre/modules/Preferences.jsm", ns);
 
   let service = Components.classes["@mozilla.org/datareporting/service;1"]
                                   .getService(Components.interfaces.nsISupports)
                                   .wrappedJSObject;
   ok(service.healthReporter, "Health Reporter instance is available.");
 
   let policyPrefs = new ns.Preferences("testing." + name + ".");
   ok(service._prefs, "Health Reporter prefs are available.");
--- a/browser/components/sessionstore/src/SessionStorage.jsm
+++ b/browser/components/sessionstore/src/SessionStorage.jsm
@@ -80,17 +80,22 @@ let DomStorage = {
    *        A tab's docshell (containing the sessionStorage)
    * @param aStorageData
    *        Storage data to be restored
    */
   write: function DomStorage_write(aDocShell, aStorageData) {
     for (let [host, data] in Iterator(aStorageData)) {
       let uri = Services.io.newURI(host, null, null);
       let principal = Services.scriptSecurityManager.getDocShellCodebasePrincipal(uri, aDocShell);
-      let storage = aDocShell.getSessionStorageForPrincipal(principal, "", true);
+      let storageManager = aDocShell.QueryInterface(Components.interfaces.nsIDOMStorageManager);
+
+      // There is no need to pass documentURI, it's only used to fill documentURI property of
+			// domstorage event, which in this case has no consumer.  Prevention of events in case
+			// of missing documentURI will be solved in a followup bug to bug 600307.
+      let storage = storageManager.createStorage(principal, "", aDocShell.usePrivateBrowsing);
 
       for (let [key, value] in Iterator(data)) {
         try {
           storage.setItem(key, value);
         } catch (e) {
           // throws e.g. for URIs that can't have sessionStorage
           Cu.reportError(e);
         }
@@ -105,22 +110,18 @@ let DomStorage = {
    * @param aDocShell
    *        A tab's docshell (containing the sessionStorage)
    */
   _readEntry: function DomStorage_readEntry(aPrincipal, aDocShell) {
     let hostData = {};
     let storage;
 
     try {
-      // 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(aPrincipal, "", false);
+      let storageManager = aDocShell.QueryInterface(Components.interfaces.nsIDOMStorageManager);
+      storage = storageManager.getStorage(aPrincipal);
     } catch (e) {
       // sessionStorage might throw if it's turned off, see bug 458954
     }
 
     if (storage && storage.length) {
        for (let i = 0; i < storage.length; i++) {
         try {
           let key = storage.key(i);
--- a/browser/metro/base/content/browser-ui.js
+++ b/browser/metro/base/content/browser-ui.js
@@ -879,16 +879,23 @@ var BrowserUI = {
     }
     // FIXME Bug 720575 - Don't capture thumbnails for SVG or XML documents as
     //       that currently regresses Talos SVG tests.
     let doc = aBrowser.contentDocument;
     if (doc instanceof SVGDocument || doc instanceof XMLDocument) {
       return false;
     }
 
+    // Don't capture pages in snapped mode, this produces 2/3 black
+    // thumbs or stretched out ones
+    //   Ci.nsIWinMetroUtils.snapped is inaccessible on
+    //   desktop/nonwindows systems
+    if(Elements.windowState.getAttribute("viewstate") == "snapped") {
+      return false;
+    }
     // There's no point in taking screenshot of loading pages.
     if (aBrowser.docShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE) {
       return false;
     }
 
     // Don't take screenshots of about: pages.
     if (aBrowser.currentURI.schemeIs("about")) {
       return false;
--- a/browser/metro/base/content/helperui/MenuUI.js
+++ b/browser/metro/base/content/helperui/MenuUI.js
@@ -377,24 +377,26 @@ MenuPopup.prototype = {
 
     window.removeEventListener("keypress", this, true);
     window.removeEventListener("mousedown", this, true);
     Elements.stack.removeEventListener("PopupChanged", this, false);
 
     let self = this;
     this._panel.addEventListener("transitionend", function () {
       self._panel.removeEventListener("transitionend", arguments.callee);
+      self._panel.removeAttribute("hiding");
       self._panel.hidden = true;
-      self._popupState = null;
+
       let event = document.createEvent("Events");
       event.initEvent("popuphidden", true, false);
       document.dispatchEvent(event);
     });
 
-    this._panel.removeAttribute("showing");
+    this._panel.setAttribute("hiding", "true");
+    setTimeout(()=>this._panel.removeAttribute("showing"), 0);
   },
 
   _position: function _position(aPositionOptions) {
     let aX = aPositionOptions.xPos;
     let aY = aPositionOptions.yPos;
     let aSource = aPositionOptions.source;
 
     // Set these first so they are set when we do misc. calculations below.
@@ -403,17 +405,16 @@ MenuPopup.prototype = {
     }
     if (aPositionOptions.maxHeight) {
       this._popup.style.maxHeight = aPositionOptions.maxHeight + "px";
     }
 
     let width = this._popup.boxObject.width;
     let height = this._popup.boxObject.height;
     let halfWidth = width / 2;
-    let halfHeight = height / 2;
     let screenWidth = ContentAreaObserver.width;
     let screenHeight = ContentAreaObserver.height;
 
     // Add padding on the side of the menu per the user's hand preference
     let leftHand = MetroUtils.handPreference == MetroUtils.handPreferenceLeft;
     if (aSource && aSource == Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH) {
       this._commands.setAttribute("left-hand", leftHand);
     }
--- a/browser/metro/base/tests/mochitest/browser_context_menu_tests.js
+++ b/browser/metro/base/tests/mochitest/browser_context_menu_tests.js
@@ -25,17 +25,17 @@ function emptyClipboard() {
   Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard)
                                        .emptyClipboard(Ci.nsIClipboard.kGlobalClipboard);
 }
 
 function checkContextMenuPositionRange(aElement, aMinLeft, aMaxLeft, aMinTop, aMaxTop) {
   ok(aElement.left > aMinLeft && aElement.left < aMaxLeft,
     "Left position is " + aElement.left + ", expected between " + aMinLeft + " and " + aMaxLeft);
 
-  ok(aElement.top > aMinTop && aElement.top < aMaxTop, 
+  ok(aElement.top > aMinTop && aElement.top < aMaxTop,
     "Top position is " + aElement.top + ", expected between " + aMinTop + " and " + aMaxTop);
 }
 
 gTests.push({
   desc: "text context menu",
   run: function test() {
     netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
 
@@ -342,17 +342,19 @@ gTests.push({
     ok(ContextMenuUI._menuPopup._visible, "is visible");
 
     let notificationBox = Browser.getNotificationBox();
     let notification = notificationBox.getNotificationWithValue("popup-blocked");
     let notificationHeight = notification.boxObject.height;
 
     checkContextMenuPositionRange(ContextMenuUI._panel, 65, 80, notificationHeight +  155, notificationHeight + 180);
 
-    ContextMenuUI._menuPopup.hide();
+    promise = waitForEvent(document, "popuphidden");
+    ContextMenuUI.hide();
+    yield promise;
 
     Browser.closeTab(Browser.selectedTab);
   }
 });
 
 // Image context menu tests
 gTests.push({
   desc: "image context menu",
@@ -492,31 +494,32 @@ gTests.push({
     let event = yield tabPromise;
 
     purgeEventQueue();
 
     let imagetab = Browser.getTabFromChrome(event.originalTarget);
     ok(imagetab != null, "tab created");
 
     Browser.closeTab(imagetab);
+    yield waitForEvent(imagetab.chromeTab.parentNode, "TabRemove");
   }
 });
 
 gTests.push({
   desc: "tests for subframe positioning",
   run: function test() {
     info(chromeRoot + "browser_context_menu_tests_03.html");
     yield addTab(chromeRoot + "browser_context_menu_tests_03.html");
 
     let win = Browser.selectedTab.browser.contentWindow;
 
     // Sometimes the context ui is visible, sometimes it isn't.
     try {
       yield waitForCondition(function () {
-        return ContextUI.isVisible;  
+        return ContextUI.isVisible;
       }, 500, 50);
     } catch (ex) {}
 
     ContextUI.dismiss();
 
     let frame1 = win.document.getElementById("frame1");
     let link1 = frame1.contentDocument.getElementById("link1");
 
--- a/browser/metro/base/tests/mochitest/head.js
+++ b/browser/metro/base/tests/mochitest/head.js
@@ -469,29 +469,31 @@ function purgeEventQueue() {
 =============================================================================*/
 let gCurrentTest = null;
 let gTests = [];
 
 function runTests() {
   waitForExplicitFinish();
   Task.spawn(function() {
     while((gCurrentTest = gTests.shift())){
-      info(gCurrentTest.desc);
+      info("START " + gCurrentTest.desc);
       try {
         if ('function' == typeof gCurrentTest.setUp) {
+          info("SETUP " + gCurrentTest.desc);
           yield Task.spawn(gCurrentTest.setUp.bind(gCurrentTest));
         }
         yield Task.spawn(gCurrentTest.run.bind(gCurrentTest));
         if ('function' == typeof gCurrentTest.tearDown) {
+          info("TEARDOWN " + gCurrentTest.desc);
           yield Task.spawn(gCurrentTest.tearDown.bind(gCurrentTest));
         }
       } catch (ex) {
         ok(false, "runTests: Task failed - " + ex);
       } finally {
-        info("END "+gCurrentTest.desc);
+        info("END " + gCurrentTest.desc);
       }
     }
     finish();
   });
 }
 
 function stubMethod(aObj, aMethod) {
   let origFunc = aObj[aMethod];
--- a/browser/metro/theme/platform.css
+++ b/browser/metro/theme/platform.css
@@ -155,31 +155,35 @@ menulist {
   border-color: @field_disabled_foreground_color@ !important;
 }
 
 /* Popup Menus ------------------------------------------------------------- */
 
 .menu-container {
   position: absolute;
   opacity: 0;
-  transition: opacity ease-out 0.2s;
 }
 
 .menu-container[showingfrom="below"] {
   transform: translateY(@metro_spacing_normal@);
 }
 
 .menu-container[showingfrom="above"] {
   transform: translateY(-@metro_spacing_normal@);
 }
 
+.menu-container[hiding],
+.menu-container[showing] {
+  transition: opacity ease-out 0.2s,
+              transform ease-out 0.2s;
+}
+
 .menu-container[showing] {
   opacity: 1;
   transform: none;
-  transition: all ease-out 0.2s;
 }
 
 .menu-popup > richlistbox {
   padding: 3px 0;
   border: #000 solid @metro_border_thick@;
   -moz-appearance: none;
   display: -moz-box;
 }
--- a/build/unix/build-clang/build-clang.py
+++ b/build/unix/build-clang/build-clang.py
@@ -93,18 +93,26 @@ def build_tooltool_manifest():
 isDarwin = platform.system() == "Darwin"
 
 def build_one_stage_aux(stage_dir, is_stage_one):
     os.mkdir(stage_dir)
 
     build_dir = stage_dir + "/build"
     inst_dir = stage_dir + "/clang"
 
+    targets = ["x86", "x86_64"]
+    # The Darwin equivalents of binutils appear to have intermittent problems
+    # with objects in compiler-rt that are compiled for arm.  Since the arm
+    # support is only necessary for iOS (which we don't support), only enable
+    # arm support on Linux.
+    if not isDarwin:
+        targets.append("arm")
+
     configure_opts = ["--enable-optimized",
-                      "--enable-targets=x86,x86_64,arm",
+                      "--enable-targets=" + ",".join(targets),
                       "--disable-assertions",
                       "--prefix=%s" % inst_dir,
                       "--with-gcc-toolchain=/tools/gcc-4.7.2-0moz1"]
     build_package(llvm_source_dir, build_dir, configure_opts,
                   [])
 
 if isDarwin:
     os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.7'
--- a/content/base/src/DirectionalityUtils.cpp
+++ b/content/base/src/DirectionalityUtils.cpp
@@ -380,17 +380,19 @@ GetDirectionFromText(const nsTextFragmen
  *            node is about to change, pass in the changed node, so that we
  *            know not to return it
  * @return the text node containing the character that determined the direction
  */
 static nsINode*
 WalkDescendantsSetDirectionFromText(Element* aElement, bool aNotify = true,
                                     nsINode* aChangedNode = nullptr)
 {
-  MOZ_ASSERT(aElement, "aElement is null");
+  MOZ_ASSERT(aElement, "Must have an element");
+  MOZ_ASSERT(aElement->HasDirAuto(), "Element must have dir=auto");
+
   if (DoesNotParticipateInAutoDirection(aElement)) {
     return nullptr;
   }
 
   nsIContent* child = aElement->GetFirstChild();
   while (child) {
     if (child->IsElement() &&
         DoesNotAffectDirectionOfAncestors(child->AsElement())) {
@@ -423,16 +425,17 @@ class nsTextNodeDirectionalityMap
   nsTextNodeDirectionalityMapDtor(void *aObject, nsIAtom* aPropertyName,
                                   void *aPropertyValue, void* aData)
   {
     nsINode* textNode = static_cast<nsINode * >(aObject);
     textNode->ClearHasTextNodeDirectionalityMap();
 
     nsTextNodeDirectionalityMap* map =
       reinterpret_cast<nsTextNodeDirectionalityMap * >(aPropertyValue);
+    map->EnsureMapIsClear(textNode);
     delete map;
   }
 
 public:
   nsTextNodeDirectionalityMap(nsINode* aTextNode)
   {
     MOZ_ASSERT(aTextNode, "Null text node");
     MOZ_COUNT_CTOR(nsTextNodeDirectionalityMap);
@@ -506,27 +509,42 @@ private:
       nsTextNodeDirectionalityMap::AddEntryToMap(newTextNode, rootNode);
     } else {
       rootNode->ClearHasDirAutoSet();
       rootNode->UnsetProperty(nsGkAtoms::dirAutoSetBy);
     }
     return PL_DHASH_REMOVE;
   }
 
+  static PLDHashOperator ClearEntry(nsPtrHashKey<Element>* aEntry, void* aData)
+  {
+    Element* rootNode = aEntry->GetKey();
+    rootNode->ClearHasDirAutoSet();
+    rootNode->UnsetProperty(nsGkAtoms::dirAutoSetBy);
+    return PL_DHASH_REMOVE;
+  }
+
 public:
   void UpdateAutoDirection(Directionality aDir)
   {
     mElements.EnumerateEntries(SetNodeDirection, &aDir);
   }
 
   void ResetAutoDirection(nsINode* aTextNode)
   {
     mElements.EnumerateEntries(ResetNodeDirection, aTextNode);
   }
 
+  void EnsureMapIsClear(nsINode* aTextNode)
+  {
+    uint32_t clearedEntries =
+      mElements.EnumerateEntries(ClearEntry, aTextNode);
+    MOZ_ASSERT(clearedEntries == 0, "Map should be empty already");
+  }
+
   static void RemoveElementFromMap(nsINode* aTextNode, Element* aElement)
   {
     if (aTextNode->HasTextNodeDirectionalityMap()) {
       GetDirectionalityMap(aTextNode)->RemoveEntry(aTextNode, aElement);
     }
   }
 
   static void AddEntryToMap(nsINode* aTextNode, Element* aElement)
@@ -547,16 +565,23 @@ public:
   }
 
   static void ResetTextNodeDirection(nsINode* aTextNode)
   {
     MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(),
                "Map missing in ResetTextNodeDirection");
     GetDirectionalityMap(aTextNode)->ResetAutoDirection(aTextNode);
   }
+
+  static void EnsureMapIsClearFor(nsINode* aTextNode)
+  {
+    if (aTextNode->HasTextNodeDirectionalityMap()) {
+      GetDirectionalityMap(aTextNode)->EnsureMapIsClear(aTextNode);
+    }
+  }
 };
 
 Directionality
 RecomputeDirectionality(Element* aElement, bool aNotify)
 {
   MOZ_ASSERT(!aElement->HasDirAuto(),
              "RecomputeDirectionality called with dir=auto");
 
@@ -650,16 +675,17 @@ WalkDescendantsResetAutoDirection(Elemen
   while (child) {
     if (child->HasDirAuto()) {
       child = child->GetNextNonChildNode(aElement);
       continue;
     }
 
     if (child->HasTextNodeDirectionalityMap()) {
       nsTextNodeDirectionalityMap::ResetTextNodeDirection(child);
+      nsTextNodeDirectionalityMap::EnsureMapIsClearFor(child);
     }
     child = child->GetNextNode(aElement);
   }
 }
 
 void
 WalkDescendantsSetDirAuto(Element* aElement, bool aNotify)
 {
@@ -726,55 +752,59 @@ void SetAncestorDirectionIfAuto(nsINode*
   Element* parent = aTextNode->GetParentElement();
   while (parent && parent->NodeOrAncestorHasDirAuto()) {
     if (DoesNotParticipateInAutoDirection(parent) || parent->HasFixedDir()) {
       break;
     }
 
     if (parent->HasDirAuto()) {
       bool resetDirection = false;
+      nsINode* directionWasSetByTextNode =
+        static_cast<nsINode*>(parent->GetProperty(nsGkAtoms::dirAutoSetBy));
 
       if (!parent->HasDirAutoSet()) {
         // Fast path if parent's direction is not yet set by any descendant
+        MOZ_ASSERT(!directionWasSetByTextNode,
+                   "dirAutoSetBy property should be null");
         resetDirection = true;
       } else {
         // If parent's direction is already set, we need to know if
         // aTextNode is before or after the text node that had set it.
         // We will walk parent's descendants in tree order starting from
         // aTextNode to optimize for the most common case where text nodes are
         // being appended to tree.
-        nsINode* directionWasSetByTextNode =
-          static_cast<nsINode*>(parent->GetProperty(nsGkAtoms::dirAutoSetBy));
         if (!directionWasSetByTextNode) {
           resetDirection = true;
         } else if (directionWasSetByTextNode != aTextNode) {
           nsIContent* child = aTextNode->GetNextNode(parent);
           while (child) {
             if (child->IsElement() &&
                 DoesNotAffectDirectionOfAncestors(child->AsElement())) {
               child = child->GetNextNonChildNode(parent);
               continue;
             }
 
             if (child == directionWasSetByTextNode) {
               // we found the node that set the element's direction after our
               // text node, so we need to reset the direction
               resetDirection = true;
-              nsTextNodeDirectionalityMap::RemoveElementFromMap(
-                directionWasSetByTextNode, parent
-              );
               break;
             }
 
             child = child->GetNextNode(parent);
           }
         }
       }
 
       if (resetDirection) {
+        if (directionWasSetByTextNode) {
+          nsTextNodeDirectionalityMap::RemoveElementFromMap(
+            directionWasSetByTextNode, parent
+          );
+        }
         parent->SetDirectionality(aDir, aNotify);
         nsTextNodeDirectionalityMap::AddEntryToMap(aTextNode, parent);
         SetDirectionalityOnDescendants(parent, aDir, aNotify);
       }
 
       // Since we found an element with dir=auto, we can stop walking the
       // parent chain: none of its ancestors will have their direction set by
       // any of its descendants.
@@ -785,16 +815,17 @@ void SetAncestorDirectionIfAuto(nsINode*
 }
 
 void
 SetDirectionFromChangedTextNode(nsIContent* aTextNode, uint32_t aOffset,
                                 const PRUnichar* aBuffer, uint32_t aLength,
                                 bool aNotify)
 {
   if (!NodeAffectsDirAutoAncestor(aTextNode)) {
+    nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode);
     return;
   }
 
   uint32_t firstStrong;
   Directionality oldDir = GetDirectionFromText(aTextNode->GetText(),
                                                &firstStrong);
   if (aOffset > firstStrong) {
     return;
@@ -841,16 +872,17 @@ SetDirectionFromNewTextNode(nsIContent* 
     SetAncestorDirectionIfAuto(aTextNode, dir);
   }
 }
 
 void
 ResetDirectionSetByTextNode(nsTextNode* aTextNode)
 {
   if (!NodeAffectsDirAutoAncestor(aTextNode)) {
+    nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode);
     return;
   }
 
   Directionality dir = GetDirectionFromText(aTextNode->GetText());
   if (dir != eDir_NotSet && aTextNode->HasTextNodeDirectionalityMap()) {
     nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode);
   }
 }
@@ -905,16 +937,21 @@ OnSetDirAttr(Element* aElement, const ns
     //      or "rtl". Element::HasDirAuto() encapsulates all that, so doing it
     //      here is simpler.
     WalkDescendantsClearAncestorDirAuto(aElement);
   }
 
   if (aElement->HasDirAuto()) {
     WalkDescendantsSetDirAuto(aElement, aNotify);
   } else {
+    if (aElement->HasDirAutoSet()) {
+      nsINode* setByNode =
+        static_cast<nsINode*>(aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
+      nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
+    }
     SetDirectionalityOnDescendants(aElement,
                                    RecomputeDirectionality(aElement, aNotify),
                                    aNotify);
   }
 }
 
 void
 SetDirOnBind(mozilla::dom::Element* aElement, nsIContent* aParent)
--- a/content/events/src/nsDOMMouseScrollEvent.cpp
+++ b/content/events/src/nsDOMMouseScrollEvent.cpp
@@ -19,16 +19,17 @@ nsDOMMouseScrollEvent::nsDOMMouseScrollE
     mEvent->time = PR_Now();
     mEvent->refPoint.x = mEvent->refPoint.y = 0;
     static_cast<nsMouseEvent*>(mEvent)->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
   }
 
   if(mEvent->eventStructType == NS_MOUSE_SCROLL_EVENT) {
     mDetail = static_cast<nsMouseScrollEvent*>(mEvent)->delta;
   }
+  SetIsDOMBinding();
 }
 
 nsDOMMouseScrollEvent::~nsDOMMouseScrollEvent()
 {
   if (mEventIsInternal && mEvent) {
     switch (mEvent->eventStructType)
     {
       case NS_MOUSE_SCROLL_EVENT:
@@ -73,25 +74,29 @@ nsDOMMouseScrollEvent::InitMouseScrollEv
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsDOMMouseScrollEvent::GetAxis(int32_t* aResult)
 {
   NS_ENSURE_ARG_POINTER(aResult);
+  *aResult = Axis();
+  return NS_OK;
+}
 
+int32_t
+nsDOMMouseScrollEvent::Axis()
+{
   if (mEvent->eventStructType == NS_MOUSE_SCROLL_EVENT) {
-    *aResult = static_cast<nsMouseScrollEvent*>(mEvent)->isHorizontal ?
-                 static_cast<int32_t>(HORIZONTAL_AXIS) :
-                 static_cast<int32_t>(VERTICAL_AXIS);
-  } else {
-    *aResult = 0;
+    return static_cast<nsMouseScrollEvent*>(mEvent)->isHorizontal ?
+             static_cast<int32_t>(HORIZONTAL_AXIS) :
+             static_cast<int32_t>(VERTICAL_AXIS);
   }
-  return NS_OK;
+  return 0;
 }
 
 nsresult NS_NewDOMMouseScrollEvent(nsIDOMEvent** aInstancePtrResult,
                                    mozilla::dom::EventTarget* aOwner,
                                    nsPresContext* aPresContext,
                                    nsInputEvent *aEvent) 
 {
   nsDOMMouseScrollEvent* it =
--- a/content/events/src/nsDOMMouseScrollEvent.h
+++ b/content/events/src/nsDOMMouseScrollEvent.h
@@ -3,27 +3,50 @@
  * 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/. */
 
 #ifndef nsDOMMouseScrollEvent_h__
 #define nsDOMMouseScrollEvent_h__
 
 #include "nsIDOMMouseScrollEvent.h"
 #include "nsDOMMouseEvent.h"
+#include "mozilla/dom/MouseScrollEventBinding.h"
 
 class nsDOMMouseScrollEvent : public nsDOMMouseEvent,
                               public nsIDOMMouseScrollEvent
 {
 public:
   nsDOMMouseScrollEvent(mozilla::dom::EventTarget* aOwner,
                         nsPresContext* aPresContext, nsInputEvent* aEvent);
   virtual ~nsDOMMouseScrollEvent();
 
   NS_DECL_ISUPPORTS_INHERITED
 
   // nsIDOMMouseScrollEvent Interface
   NS_DECL_NSIDOMMOUSESCROLLEVENT
   
   // Forward to base class
   NS_FORWARD_TO_NSDOMMOUSEEVENT
+
+  virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope)
+  {
+    return mozilla::dom::MouseScrollEventBinding::Wrap(aCx, aScope, this);
+  }
+
+  int32_t Axis();
+
+  void InitMouseScrollEvent(const nsAString& aType, bool aCanBubble,
+                            bool aCancelable, nsIDOMWindow* aView,
+                            int32_t aDetail, int32_t aScreenX, int32_t aScreenY,
+                            int32_t aClientX, int32_t aClientY,
+                            bool aCtrlKey, bool aAltKey, bool aShiftKey,
+                            bool aMetaKey, uint16_t aButton,
+                            nsIDOMEventTarget* aRelatedTarget, int32_t aAxis,
+                            mozilla::ErrorResult& aRv)
+  {
+    aRv = InitMouseScrollEvent(aType, aCanBubble, aCancelable, aView,
+                               aDetail, aScreenX, aScreenY, aClientX, aClientY,
+                               aCtrlKey, aAltKey, aShiftKey, aMetaKey, aButton,
+                               aRelatedTarget, aAxis);
+  }
 };
 
 #endif // nsDOMMouseScrollEvent_h__
--- a/content/events/src/nsDOMNotifyPaintEvent.cpp
+++ b/content/events/src/nsDOMNotifyPaintEvent.cpp
@@ -20,16 +20,17 @@ nsDOMNotifyPaintEvent::nsDOMNotifyPaintE
 : nsDOMEvent(aOwner, aPresContext, aEvent)
 {
   if (mEvent) {
     mEvent->message = aEventType;
   }
   if (aInvalidateRequests) {
     mInvalidateRequests.MoveElementsFrom(aInvalidateRequests->mRequests);
   }
+  SetIsDOMBinding();
 }
 
 DOMCI_DATA(NotifyPaintEvent, nsDOMNotifyPaintEvent)
 
 NS_INTERFACE_MAP_BEGIN(nsDOMNotifyPaintEvent)
   NS_INTERFACE_MAP_ENTRY(nsIDOMNotifyPaintEvent)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(NotifyPaintEvent)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEvent)
@@ -49,43 +50,55 @@ nsDOMNotifyPaintEvent::GetRegion()
     r.SimplifyOutward(10);
   }
   return r;
 }
 
 NS_IMETHODIMP
 nsDOMNotifyPaintEvent::GetBoundingClientRect(nsIDOMClientRect** aResult)
 {
+  *aResult = BoundingClientRect().get();
+  return NS_OK;
+}
+
+already_AddRefed<nsClientRect>
+nsDOMNotifyPaintEvent::BoundingClientRect()
+{
   nsRefPtr<nsClientRect> rect = new nsClientRect(ToSupports(this));
 
   if (mPresContext) {
     rect->SetLayoutRect(GetRegion().GetBounds());
   }
 
-  rect.forget(aResult);
-  return NS_OK;
+  return rect.forget();
 }
 
 NS_IMETHODIMP
 nsDOMNotifyPaintEvent::GetClientRects(nsIDOMClientRectList** aResult)
 {
+  *aResult = ClientRects().get();
+  return NS_OK;
+}
+
+already_AddRefed<nsClientRectList>
+nsDOMNotifyPaintEvent::ClientRects()
+{
   nsISupports* parent = ToSupports(this);
   nsRefPtr<nsClientRectList> rectList = new nsClientRectList(parent);
 
   nsRegion r = GetRegion();
   nsRegionRectIterator iter(r);
   for (const nsRect* rgnRect = iter.Next(); rgnRect; rgnRect = iter.Next()) {
     nsRefPtr<nsClientRect> rect = new nsClientRect(parent);
     
     rect->SetLayoutRect(*rgnRect);
     rectList->Append(rect);
   }
 
-  rectList.forget(aResult);
-  return NS_OK;
+  return rectList.forget();
 }
 
 NS_IMETHODIMP
 nsDOMNotifyPaintEvent::GetPaintRequests(nsIDOMPaintRequestList** aResult)
 {
   nsRefPtr<nsPaintRequestList> requests = PaintRequests();
   requests.forget(aResult);
   return NS_OK;
--- a/content/events/src/nsDOMNotifyPaintEvent.h
+++ b/content/events/src/nsDOMNotifyPaintEvent.h
@@ -4,18 +4,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsDOMNotifyPaintEvent_h_
 #define nsDOMNotifyPaintEvent_h_
 
 #include "nsIDOMNotifyPaintEvent.h"
 #include "nsDOMEvent.h"
 #include "nsPresContext.h"
+#include "mozilla/dom/NotifyPaintEventBinding.h"
 
 class nsPaintRequestList;
+class nsClientRectList;
+class nsClientRect;
 
 class nsDOMNotifyPaintEvent : public nsDOMEvent,
                               public nsIDOMNotifyPaintEvent
 {
 public:
   nsDOMNotifyPaintEvent(mozilla::dom::EventTarget* aOwner,
                         nsPresContext*           aPresContext,
                         nsEvent*                 aEvent,
@@ -30,17 +33,25 @@ public:
   NS_FORWARD_TO_NSDOMEVENT_NO_SERIALIZATION_NO_DUPLICATION
   NS_IMETHOD DuplicatePrivateData()
   {
     return nsDOMEvent::DuplicatePrivateData();
   }
   NS_IMETHOD_(void) Serialize(IPC::Message* aMsg, bool aSerializeInterfaceType);
   NS_IMETHOD_(bool) Deserialize(const IPC::Message* aMsg, void** aIter);
 
+  virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope)
+  {
+    return mozilla::dom::NotifyPaintEventBinding::Wrap(aCx, aScope, this);
+  }
+
+  already_AddRefed<nsClientRectList> ClientRects();
+
+  already_AddRefed<nsClientRect> BoundingClientRect();
+
   already_AddRefed<nsPaintRequestList> PaintRequests();
-
 private:
   nsRegion GetRegion();
 
   nsTArray<nsInvalidateRequestList::Request> mInvalidateRequests;
 };
 
 #endif // nsDOMNotifyPaintEvent_h_
--- a/content/events/src/nsDOMTouchEvent.cpp
+++ b/content/events/src/nsDOMTouchEvent.cpp
@@ -80,16 +80,17 @@ nsDOMTouchEvent::nsDOMTouchEvent(mozilla
       nsIDOMTouch *touch = aEvent->touches[i];
       dom::Touch *domtouch = static_cast<dom::Touch*>(touch);
       domtouch->InitializePoints(mPresContext, aEvent);
     }
   } else {
     mEventIsInternal = true;
     mEvent->time = PR_Now();
   }
+  SetIsDOMBinding();
 }
 
 nsDOMTouchEvent::~nsDOMTouchEvent()
 {
   if (mEventIsInternal && mEvent) {
     delete static_cast<nsTouchEvent*>(mEvent);
     mEvent = nullptr;
   }
@@ -224,38 +225,38 @@ nsDOMTouchEvent::GetChangedTouches(nsIDO
   }
   mChangedTouches = new nsDOMTouchList(changedTouches);
   return CallQueryInterface(mChangedTouches, aChangedTouches);
 }
 
 NS_IMETHODIMP
 nsDOMTouchEvent::GetAltKey(bool* aAltKey)
 {
-  *aAltKey = static_cast<nsInputEvent*>(mEvent)->IsAlt();
+  *aAltKey = AltKey();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMTouchEvent::GetMetaKey(bool* aMetaKey)
 {
-  *aMetaKey = static_cast<nsInputEvent*>(mEvent)->IsMeta();
+  *aMetaKey = MetaKey();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMTouchEvent::GetCtrlKey(bool* aCtrlKey)
 {
-  *aCtrlKey = static_cast<nsInputEvent*>(mEvent)->IsControl();
+  *aCtrlKey = CtrlKey();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMTouchEvent::GetShiftKey(bool* aShiftKey)
 {
-  *aShiftKey = static_cast<nsInputEvent*>(mEvent)->IsShift();
+  *aShiftKey = ShiftKey();
   return NS_OK;
 }
 
 #ifdef XP_WIN
 namespace mozilla {
 namespace widget {
 extern int32_t IsTouchDeviceSupportPresent();
 } }
--- a/content/events/src/nsDOMTouchEvent.h
+++ b/content/events/src/nsDOMTouchEvent.h
@@ -6,16 +6,17 @@
 #define nsDOMTouchEvent_h_
 
 #include "nsDOMUIEvent.h"
 #include "nsIDOMTouchEvent.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "mozilla/Attributes.h"
 #include "nsJSEnvironment.h"
+#include "mozilla/dom/TouchEventBinding.h"
 
 class nsDOMTouchList MOZ_FINAL : public nsIDOMTouchList
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(nsDOMTouchList)
   NS_DECL_NSIDOMTOUCHLIST
 
@@ -48,16 +49,81 @@ public:
   virtual ~nsDOMTouchEvent();
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsDOMTouchEvent, nsDOMUIEvent)
   NS_DECL_NSIDOMTOUCHEVENT
 
   NS_FORWARD_TO_NSDOMUIEVENT
 
+  virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope)
+  {
+    return mozilla::dom::TouchEventBinding::Wrap(aCx, aScope, this);
+  }
+
+  already_AddRefed<nsIDOMTouchList> GetTouches()
+  {
+    nsCOMPtr<nsIDOMTouchList> t;
+    GetTouches(getter_AddRefs(t));
+    return t.forget();
+  }
+
+  already_AddRefed<nsIDOMTouchList> GetTargetTouches()
+  {
+    nsCOMPtr<nsIDOMTouchList> t;
+    GetTargetTouches(getter_AddRefs(t));
+    return t.forget();
+  }
+
+  already_AddRefed<nsIDOMTouchList> GetChangedTouches()
+  {
+    nsCOMPtr<nsIDOMTouchList> t;
+    GetChangedTouches(getter_AddRefs(t));
+    return t.forget();
+  }
+
+  bool AltKey()
+  {
+    return static_cast<nsInputEvent*>(mEvent)->IsAlt();
+  }
+
+  bool MetaKey()
+  {
+    return static_cast<nsInputEvent*>(mEvent)->IsMeta();
+  }
+
+  bool CtrlKey()
+  {
+    return static_cast<nsInputEvent*>(mEvent)->IsControl();
+  }
+
+  bool ShiftKey()
+  {
+    return static_cast<nsInputEvent*>(mEvent)->IsShift();
+  }
+
+  void InitTouchEvent(const nsAString& aType,
+                      bool aCanBubble,
+                      bool aCancelable,
+                      nsIDOMWindow* aView,
+                      int32_t aDetail,
+                      bool aCtrlKey,
+                      bool aAltKey,
+                      bool aShiftKey,
+                      bool aMetaKey,
+                      nsIDOMTouchList* aTouches,
+                      nsIDOMTouchList* aTargetTouches,
+                      nsIDOMTouchList* aChangedTouches,
+                      mozilla::ErrorResult& aRv)
+  {
+    aRv = InitTouchEvent(aType, aCanBubble, aCancelable, aView, aDetail,
+                         aCtrlKey, aAltKey, aShiftKey, aMetaKey,
+                         aTouches, aTargetTouches, aChangedTouches);
+  }
+
   static bool PrefEnabled();
 protected:
   nsCOMPtr<nsIDOMTouchList> mTouches;
   nsCOMPtr<nsIDOMTouchList> mTargetTouches;
   nsCOMPtr<nsIDOMTouchList> mChangedTouches;
 };
 
 #endif /* !defined(nsDOMTouchEvent_h_) */
--- a/content/events/src/nsEventDispatcher.cpp
+++ b/content/events/src/nsEventDispatcher.cpp
@@ -11,17 +11,16 @@
 #include "nsContentUtils.h"
 #include "nsError.h"
 #include "nsMutationEvent.h"
 #include NEW_H
 #include "nsINode.h"
 #include "nsPIDOMWindow.h"
 #include "nsFrameLoader.h"
 #include "nsDOMTouchEvent.h"
-#include "nsDOMStorage.h"
 #include "GeckoProfiler.h"
 #include "GeneratedEvents.h"
 #include "mozilla/dom/EventTarget.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 #define NS_TARGET_CHAIN_FORCE_CONTENT_DISPATCH  (1 << 0)
--- a/content/html/content/src/nsGenericHTMLElement.cpp
+++ b/content/html/content/src/nsGenericHTMLElement.cpp
@@ -795,27 +795,25 @@ nsGenericHTMLElement::AfterSetAttr(int32
         Directionality dirValue = (Directionality)aValue->GetEnumValue();
         if (dirValue == eDir_Auto) {
           SetHasDirAuto();
           ClearHasFixedDir();
         } else {
           dir = dirValue;
           SetDirectionality(dir, aNotify);
           ClearHasDirAuto();
-          ClearHasDirAutoSet();
           SetHasFixedDir();
         }
       } else {
         ClearHasValidDir();
         ClearHasFixedDir();
         if (NodeInfo()->Equals(nsGkAtoms::bdi)) {
           SetHasDirAuto();
         } else {
           ClearHasDirAuto();
-          ClearHasDirAutoSet();
           dir = RecomputeDirectionality(this, aNotify);
         }
       }
       SetDirectionalityOnDescendants(this, dir, aNotify);
     }
   }
 
   return nsGenericHTMLElementBase::AfterSetAttr(aNamespaceID, aName,
--- a/content/html/document/src/ImageDocument.cpp
+++ b/content/html/document/src/ImageDocument.cpp
@@ -108,18 +108,18 @@ protected:
 
   nsresult CheckOverflowing(bool changeState);
 
   void UpdateTitleAndCharset();
 
   nsresult ScrollImageTo(int32_t aX, int32_t aY, bool restoreImage);
 
   float GetRatio() {
-    return std::min((float)mVisibleWidth / mImageWidth,
-                  (float)mVisibleHeight / mImageHeight);
+    return std::min(mVisibleWidth / mImageWidth,
+                    mVisibleHeight / mImageHeight);
   }
 
   void ResetZoomLevel();
   float GetZoomLevel();
 
   enum eModeClasses {
     eNone,
     eShrinkToFit,
@@ -127,18 +127,18 @@ protected:
   };
   void SetModeClass(eModeClasses mode);
 
   nsresult OnStartContainer(imgIRequest* aRequest, imgIContainer* aImage);
   nsresult OnStopRequest(imgIRequest *aRequest, nsresult aStatus);
 
   nsCOMPtr<nsIContent>          mImageContent;
 
-  int32_t                       mVisibleWidth;
-  int32_t                       mVisibleHeight;
+  float                         mVisibleWidth;
+  float                         mVisibleHeight;
   int32_t                       mImageWidth;
   int32_t                       mImageHeight;
 
   bool                          mResizeImageByDefault;
   bool                          mClickResizingEnabled;
   bool                          mImageIsOverflowing;
   // mImageIsResized is true if the image is currently resized
   bool                          mImageIsResized;
@@ -723,18 +723,18 @@ ImageDocument::CheckOverflowing(bool cha
     nsIPresShell *shell = GetShell();
     if (!shell) {
       return NS_OK;
     }
 
     nsPresContext *context = shell->GetPresContext();
     nsRect visibleArea = context->GetVisibleArea();
 
-    mVisibleWidth = nsPresContext::AppUnitsToIntCSSPixels(visibleArea.width);
-    mVisibleHeight = nsPresContext::AppUnitsToIntCSSPixels(visibleArea.height);
+    mVisibleWidth = nsPresContext::AppUnitsToFloatCSSPixels(visibleArea.width);
+    mVisibleHeight = nsPresContext::AppUnitsToFloatCSSPixels(visibleArea.height);
   }
 
   bool imageWasOverflowing = mImageIsOverflowing;
   mImageIsOverflowing =
     mImageWidth > mVisibleWidth || mImageHeight > mVisibleHeight;
   bool windowBecameBigEnough = imageWasOverflowing && !mImageIsOverflowing;
 
   if (changeState || mShouldResize || mFirstResize ||
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -89,17 +89,17 @@
 #include "nsIDocShellTreeItem.h"
 #include "nsIChannel.h"
 #include "IHistory.h"
 
 // we want to explore making the document own the load group
 // so we can associate the document URI with the load group.
 // until this point, we have an evil hack:
 #include "nsIHttpChannelInternal.h"  
-
+#include "nsPILoadGroupInternal.h"
 
 // Local Includes
 #include "nsDocShellLoadInfo.h"
 #include "nsCDefaultURIFixup.h"
 #include "nsDocShellEnumerator.h"
 #include "nsSHistory.h"
 #include "nsDocShellEditorData.h"
 
@@ -844,18 +844,16 @@ nsDocShell::Init()
     NS_ASSERTION(mLoadGroup, "Something went wrong!");
 
     mContentListener = new nsDSURIContentListener(this);
     NS_ENSURE_TRUE(mContentListener, NS_ERROR_OUT_OF_MEMORY);
 
     rv = mContentListener->Init();
     NS_ENSURE_SUCCESS(rv, rv);
 
-    mStorages.Init();
-
     // We want to hold a strong ref to the loadgroup, so it better hold a weak
     // ref to us...  use an InterfaceRequestorProxy to do this.
     nsCOMPtr<InterfaceRequestorProxy> proxy =
         new InterfaceRequestorProxy(static_cast<nsIInterfaceRequestor*>
                                                (this));
     NS_ENSURE_TRUE(proxy, NS_ERROR_OUT_OF_MEMORY);
     mLoadGroup->SetNotificationCallbacks(proxy);
 
@@ -912,16 +910,17 @@ NS_INTERFACE_MAP_BEGIN(nsDocShell)
     NS_INTERFACE_MAP_ENTRY(nsIContentViewerContainer)
     NS_INTERFACE_MAP_ENTRY(nsIWebPageDescriptor)
     NS_INTERFACE_MAP_ENTRY(nsIAuthPromptProvider)
     NS_INTERFACE_MAP_ENTRY(nsIObserver)
     NS_INTERFACE_MAP_ENTRY(nsILoadContext)
     NS_INTERFACE_MAP_ENTRY(nsIWebShellServices)
     NS_INTERFACE_MAP_ENTRY(nsILinkHandler)
     NS_INTERFACE_MAP_ENTRY(nsIClipboardCommands)
+    NS_INTERFACE_MAP_ENTRY(nsIDOMStorageManager)
 NS_INTERFACE_MAP_END_INHERITING(nsDocLoader)
 
 ///*****************************************************************************
 // nsDocShell::nsIInterfaceRequestor
 //*****************************************************************************   
 NS_IMETHODIMP nsDocShell::GetInterface(const nsIID & aIID, void **aSink)
 {
     NS_PRECONDITION(aSink, "null out param");
@@ -2591,209 +2590,80 @@ nsDocShell::HistoryTransactionRemoved(in
             static_cast<nsDocShell*>(shell.get())->
                 HistoryTransactionRemoved(aIndex);
         }
     }
 
     return NS_OK;
 }
 
+nsIDOMStorageManager*
+nsDocShell::TopSessionStorageManager()
+{
+    nsresult rv;
+
+    nsCOMPtr<nsIDocShellTreeItem> topItem;
+    rv = GetSameTypeRootTreeItem(getter_AddRefs(topItem));
+    if (NS_FAILED(rv)) {
+        return nullptr;
+    }
+
+    if (!topItem) {
+        return nullptr;
+    }
+
+    nsDocShell* topDocShell = static_cast<nsDocShell*>(topItem.get());
+    if (topDocShell != this) {
+        return topDocShell->TopSessionStorageManager();
+    }
+
+    if (!mSessionStorageManager) {
+        mSessionStorageManager =
+            do_CreateInstance("@mozilla.org/dom/sessionStorage-manager;1");
+    }
+
+    return mSessionStorageManager;
+}
+
 NS_IMETHODIMP
 nsDocShell::GetSessionStorageForPrincipal(nsIPrincipal* aPrincipal,
                                           const nsAString& aDocumentURI,
                                           bool aCreate,
                                           nsIDOMStorage** aStorage)
 {
-    NS_ENSURE_ARG_POINTER(aStorage);
-    *aStorage = nullptr;
-
-    if (!aPrincipal)
-        return NS_OK;
-
-    nsresult rv;
-
-    nsCOMPtr<nsIDocShellTreeItem> topItem;
-    rv = GetSameTypeRootTreeItem(getter_AddRefs(topItem));
-    if (NS_FAILED(rv))
-        return rv;
-
-    if (!topItem)
-        return NS_ERROR_FAILURE;
-
-    nsDocShell* topDocShell = static_cast<nsDocShell*>(topItem.get());
-    if (topDocShell != this)
-        return topDocShell->GetSessionStorageForPrincipal(aPrincipal,
-                                                          aDocumentURI,
-                                                          aCreate,
-                                                          aStorage);
-
-    nsXPIDLCString origin;
-    rv = aPrincipal->GetOrigin(getter_Copies(origin));
-    if (NS_FAILED(rv))
-        return rv;
-
-    if (origin.IsEmpty())
-        return NS_OK;
-
-    if (!mStorages.Get(origin, aStorage) && aCreate) {
-        nsCOMPtr<nsIDOMStorage> newstorage =
-            do_CreateInstance("@mozilla.org/dom/storage;2");
-        if (!newstorage)
-            return NS_ERROR_OUT_OF_MEMORY;
-
-        nsCOMPtr<nsPIDOMStorage> pistorage = do_QueryInterface(newstorage);
-        if (!pistorage)
-            return NS_ERROR_FAILURE;
-
-        rv = pistorage->InitAsSessionStorage(aPrincipal, aDocumentURI, mInPrivateBrowsing);
-        if (NS_FAILED(rv))
-            return rv;
-
-        mStorages.Put(origin, newstorage);
-
-        newstorage.swap(*aStorage);
-#if defined(PR_LOGGING) && defined(DEBUG)
-        PR_LOG(gDocShellLog, PR_LOG_DEBUG,
-               ("nsDocShell[%p]: created a new sessionStorage %p",
-                this, *aStorage));
-#endif
-    }
-    else if (*aStorage) {
-        nsCOMPtr<nsPIDOMStorage> piStorage = do_QueryInterface(*aStorage);
-        if (piStorage) {
-            nsCOMPtr<nsIPrincipal> storagePrincipal = piStorage->Principal();
-
-            // The origin string used to map items in the hash table is 
-            // an implicit security check. That check is double-confirmed 
-            // by checking the principal a storage was demanded for 
-            // really is the principal for which that storage was originally 
-            // created. Originally, the check was hidden in the CanAccess 
-            // method but it's implementation has changed.
-            bool equals;
-            nsresult rv = aPrincipal->EqualsIgnoringDomain(storagePrincipal, &equals);
-            NS_ASSERTION(NS_SUCCEEDED(rv) && equals,
-                         "GetSessionStorageForPrincipal got a storage "
-                         "that could not be accessed!");
-
-            if (NS_FAILED(rv) || !equals) {
-                NS_RELEASE(*aStorage);
-                return NS_ERROR_DOM_SECURITY_ERR;
-            }
-        }
-
-#if defined(PR_LOGGING) && defined(DEBUG)
-        PR_LOG(gDocShellLog, PR_LOG_DEBUG,
-               ("nsDocShell[%p]: returns existing sessionStorage %p",
-                this, *aStorage));
-#endif
+    nsCOMPtr<nsIDOMStorageManager> manager = TopSessionStorageManager();
+    if (!manager) {
+        return NS_ERROR_UNEXPECTED;
     }
 
     if (aCreate) {
-        // We are asked to create a new storage object. This indicates
-        // that a new windows wants it. At this moment we "fork" the existing
-        // storage object (what it means is described in the paragraph bellow).
-        // We must create a single object per a single window to distinguish
-        // a window originating oparations on the storage object to succesfully
-        // prevent dispatch of a storage event to this same window that ivoked
-        // a change in its storage. We also do this to correctly fill
-        // documentURI property in the storage event.
-        //
-        // The difference between clone and fork is that clone creates
-        // a completelly new and independent storage, but fork only creates
-        // a new object wrapping the storage implementation and data and
-        // the forked storage then behaves completelly the same way as
-        // the storage it has been forked of, all such forked storage objects
-        // shares their state and data and change on one such object affects
-        // all others the same way.
-        nsCOMPtr<nsPIDOMStorage> piStorage = do_QueryInterface(*aStorage);
-        nsCOMPtr<nsIDOMStorage> fork = piStorage->Fork(aDocumentURI);
-#if defined(PR_LOGGING) && defined(DEBUG)
-        PR_LOG(gDocShellLog, PR_LOG_DEBUG,
-               ("nsDocShell[%p]: forked sessionStorage %p to %p",
-                this, *aStorage, fork.get()));
-#endif
-        fork.swap(*aStorage);
-    }
-
-    return NS_OK;
+        return manager->CreateStorage(aPrincipal, aDocumentURI,
+                                      mInPrivateBrowsing, aStorage);
+    }
+
+    return manager->GetStorage(aPrincipal, mInPrivateBrowsing, aStorage);
 }
 
 nsresult
 nsDocShell::AddSessionStorage(nsIPrincipal* aPrincipal,
                               nsIDOMStorage* aStorage)
 {
-    NS_ENSURE_ARG_POINTER(aStorage);
-
-    if (!aPrincipal)
-        return NS_OK;
-
-    nsCOMPtr<nsIDocShellTreeItem> topItem;
-    nsresult rv = GetSameTypeRootTreeItem(getter_AddRefs(topItem));
-    if (NS_FAILED(rv))
-        return rv;
-
-    if (topItem) {
-        nsCOMPtr<nsIDocShell> topDocShell = do_QueryInterface(topItem);
-        if (topDocShell == this) {
-            nsXPIDLCString origin;
-            rv = aPrincipal->GetOrigin(getter_Copies(origin));
-            if (NS_FAILED(rv))
-                return rv;
-
-            if (origin.IsEmpty())
-                return NS_ERROR_FAILURE;
-
-            // Do not replace an existing session storage.
-            if (mStorages.GetWeak(origin))
-                return NS_ERROR_NOT_AVAILABLE;
-
-#if defined(PR_LOGGING) && defined(DEBUG)
-            PR_LOG(gDocShellLog, PR_LOG_DEBUG,
-                   ("nsDocShell[%p]: was added a sessionStorage %p",
-                    this, aStorage));
-#endif
-            mStorages.Put(origin, aStorage);
-        }
-        else {
-            return topDocShell->AddSessionStorage(aPrincipal, aStorage);
-        }
-    }
-
-    return NS_OK;
-}
-
-static PLDHashOperator
-CloneSessionStorages(nsCStringHashKey::KeyType aKey, nsIDOMStorage* aStorage,
-                     void* aUserArg)
-{
-    nsIDocShell *docShell = static_cast<nsIDocShell*>(aUserArg);
     nsCOMPtr<nsPIDOMStorage> pistorage = do_QueryInterface(aStorage);
-
-    if (pistorage) {
-        nsCOMPtr<nsIDOMStorage> storage = pistorage->Clone();
-        docShell->AddSessionStorage(pistorage->Principal(), storage);
-    }
-
-    return PL_DHASH_NEXT;
-}
-
-NS_IMETHODIMP
-nsDocShell::CloneSessionStoragesTo(nsIDocShell* aDocShell)
-{
-    aDocShell->ClearSessionStorages();
-    mStorages.EnumerateRead(CloneSessionStorages, aDocShell);
-
-    return NS_OK;
-}
-
-NS_IMETHODIMP
-nsDocShell::ClearSessionStorages()
-{
-    mStorages.Clear();
-    return NS_OK;
+    nsIPrincipal* storagePrincipal = pistorage->GetPrincipal();
+    if (storagePrincipal != aPrincipal) {
+        NS_ERROR("Wanting to add a sessionStorage for different principal");
+        return NS_ERROR_DOM_SECURITY_ERR;
+    }
+
+    nsCOMPtr<nsIDOMStorageManager> manager = TopSessionStorageManager();
+    if (!manager) {
+        return NS_ERROR_UNEXPECTED;
+    }
+
+    return manager->CloneStorage(aStorage);
 }
 
 NS_IMETHODIMP
 nsDocShell::GetCurrentDocumentChannel(nsIChannel** aResult)
 {
     NS_IF_ADDREF(*aResult = GetCurrentDocChannel()); 
     return NS_OK;
 }
@@ -6673,20 +6543,25 @@ nsDocShell::EndPageLoad(nsIWebProgress *
     nsresult rv = aChannel->GetURI(getter_AddRefs(url));
     if (NS_FAILED(rv)) return rv;
 
     nsCOMPtr<nsITimedChannel> timingChannel =
         do_QueryInterface(aChannel);
     if (timingChannel) {
         TimeStamp channelCreationTime;
         rv = timingChannel->GetChannelCreation(&channelCreationTime);
-        if (NS_SUCCEEDED(rv) && !channelCreationTime.IsNull())
+        if (NS_SUCCEEDED(rv) && !channelCreationTime.IsNull()) {
             Telemetry::AccumulateTimeDelta(
                 Telemetry::TOTAL_CONTENT_PAGE_LOAD_TIME,
                 channelCreationTime);
+            nsCOMPtr<nsPILoadGroupInternal> internalLoadGroup =
+                do_QueryInterface(mLoadGroup);
+            if (internalLoadGroup)
+                internalLoadGroup->OnEndPageLoad(aChannel);
+        }
     }
 
     // Timing is picked up by the window, we don't need it anymore
     mTiming = nullptr;
 
     // clean up reload state for meta charset
     if (eCharsetReloadRequested == mCharsetReloadState)
         mCharsetReloadState = eCharsetReloadStopOrigional;
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -18,16 +18,17 @@
 #include "nsIDocShell.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsIDocShellTreeNode.h"
 #include "nsIBaseWindow.h"
 #include "nsIScrollable.h"
 #include "nsITextScroll.h"
 #include "nsIDocShellTreeOwner.h"
 #include "nsIContentViewerContainer.h"
+#include "nsIDOMStorageManager.h"
 
 #include "nsDocLoader.h"
 #include "nsIURILoader.h"
 
 #include "nsWeakReference.h"
 
 // Local Includes
 #include "nsDSURIContentListener.h"
@@ -141,17 +142,18 @@ class nsDocShell : public nsDocLoader,
                    public nsIRefreshURI,
                    public nsIWebProgressListener,
                    public nsIWebPageDescriptor,
                    public nsIAuthPromptProvider,
                    public nsIObserver,
                    public nsILoadContext,
                    public nsIWebShellServices,
                    public nsILinkHandler,
-                   public nsIClipboardCommands
+                   public nsIClipboardCommands,
+                   public nsIDOMStorageManager
 {
     friend class nsDSURIContentListener;
 
 public:
     // Object Management
     nsDocShell();
 
     NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
@@ -173,16 +175,17 @@ public:
     NS_DECL_NSIWEBPROGRESSLISTENER
     NS_DECL_NSIREFRESHURI
     NS_DECL_NSICONTENTVIEWERCONTAINER
     NS_DECL_NSIWEBPAGEDESCRIPTOR
     NS_DECL_NSIAUTHPROMPTPROVIDER
     NS_DECL_NSIOBSERVER
     NS_DECL_NSICLIPBOARDCOMMANDS
     NS_DECL_NSIWEBSHELLSERVICES
+    NS_FORWARD_SAFE_NSIDOMSTORAGEMANAGER(TopSessionStorageManager())
 
     NS_IMETHOD Stop() {
         // Need this here because otherwise nsIWebNavigation::Stop
         // overrides the docloader's Stop()
         return nsDocLoader::Stop();
     }
 
     // Need to implement (and forward) nsISecurityEventSink, because
@@ -623,20 +626,18 @@ protected:
     
     // Call this when a URI load is handed to us (via OnLinkClick or
     // InternalLoad).  This makes sure that we're not inside unload, or that if
     // we are it's still OK to load this URI.
     bool IsOKToLoadURI(nsIURI* aURI);
     
     void ReattachEditorToWindow(nsISHEntry *aSHEntry);
 
-    nsresult GetSessionStorageForURI(nsIURI* aURI,
-                                     const nsSubstring& aDocumentURI,
-                                     bool create,
-                                     nsIDOMStorage** aStorage);
+    nsCOMPtr<nsIDOMStorageManager> mSessionStorageManager;
+    nsIDOMStorageManager* TopSessionStorageManager();
 
     // helpers for executing commands
     nsresult GetControllerForCommand(const char *inCommand,
                                      nsIController** outController);
     nsresult IsCommandEnabled(const char * inCommand, bool* outEnabled);
     nsresult DoCommand(const char * inCommand);
     nsresult EnsureCommandHandler();
 
@@ -671,19 +672,16 @@ protected:
         eFrameTypeBrowser,
         eFrameTypeApp
     };
 
     FrameType GetInheritedFrameType();
 
     bool HasUnloadedParent();
 
-    // hash of session storages, keyed by domain
-    nsInterfaceHashtable<nsCStringHashKey, nsIDOMStorage> mStorages;
-
     // Dimensions of the docshell
     nsIntRect                  mBounds;
     nsString                   mName;
     nsString                   mTitle;
 
     /**
      * Content-Type Hint of the most-recently initiated load. Used for
      * session history entries.
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -35,17 +35,17 @@ interface nsISHEntry;
 interface nsILayoutHistoryState;
 interface nsISecureBrowserUI;
 interface nsIDOMStorage;
 interface nsIPrincipal;
 interface nsIWebBrowserPrint;
 interface nsIVariant;
 interface nsIPrivacyTransitionObserver;
 
-[scriptable, builtinclass, uuid(4277354d-5069-4278-935a-5d596ce9bfbf)]
+[scriptable, builtinclass, uuid(2b192c9c-dea4-4696-a445-1bef7bc0db6d)]
 interface nsIDocShell : nsIDocShellTreeItem
 {
   /**
    * Loads a given URI.  This will give priority to loading the requested URI
    * in the object implementing	this interface.  If it can't be loaded here
    * however, the URL dispatcher will go through its normal process of content
    * loading.
    *
@@ -402,50 +402,41 @@ interface nsIDocShell : nsIDocShellTreeI
    * Notification that entries have been removed from the beginning of a
    * nsSHistory which has this as its rootDocShell.
    *
    * @param numEntries - The number of entries removed
    */
   void historyPurged(in long numEntries);
 
   /*
+   * @deprecated, use nsIDocShell.QueryInterface(nsIDOMStorageManager) instead.
+   *
    * Retrieves the WebApps session storage object for the supplied principal.
    *
    * @param principal returns a storage for this principal
    * @param documentURI new storage will be created with reference to this
    *                    document.documentURI that will appear in storage event
    * @param create If true and a session storage object doesn't
    *               already exist, a new one will be created.
    */
   nsIDOMStorage getSessionStorageForPrincipal(in nsIPrincipal principal,
                                               in DOMString documentURI,
                                               in boolean create);
 
   /*
+   * @deprecated, use nsIDocShell.QueryInterface(nsIDOMStorageManager) instead.
+   *
    * Add a WebApps session storage object to the docshell.
    *
    * @param principal the principal the storage object is associated with
    * @param storage the storage object to add
    */
   void addSessionStorage(in nsIPrincipal principal, in nsIDOMStorage storage);
 
   /**
-   * Clones all session storage objects and attaches them to the given docshell.
-   * Useful when duplicating tabs and their states.
-   *
-   * @param docShell the docshell to clone the sessionstorage objects to
-   */
-  void cloneSessionStoragesTo(in nsIDocShell docShell);
-
-  /**
-   * Removes all WebApps session storage objects attached to the docshell.
-   */
-  void clearSessionStorages();
-
-  /**
    * Gets the channel for the currently loaded document, if any. 
    * For a new document load, this will be the channel of the previous document
    * until after OnLocationChange fires.
    */
   readonly attribute nsIChannel currentDocumentChannel;
 
   /**
    * Set the offset of this child in its container.
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -224,17 +224,17 @@
 #include "nsIDOMSVGLength.h"
 #include "nsIDOMSVGNumber.h"
 #include "nsIDOMSVGRect.h"
 #include "nsIDOMSVGZoomEvent.h"
 
 #include "nsIImageDocument.h"
 
 // Storage includes
-#include "nsDOMStorage.h"
+#include "DOMStorage.h"
 
 // Device Storage
 #include "nsIDOMDeviceStorage.h"
 
 // Drag and drop
 #include "nsIDOMDataTransfer.h"
 
 // Geolocation
@@ -753,29 +753,24 @@ static nsDOMClassInfoData sClassInfoData
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   // WhatWG Storage
 
   // mrbkap says we don't need WANT_ADDPROPERTY on Storage objects
   // since a call to addProperty() is always followed by a call to
   // setProperty(), except in the case when a getter or setter is set
   // for a property. But we don't care about getters or setters here.
-  NS_DEFINE_CLASSINFO_DATA(StorageObsolete, nsDOMGenericSH,
-                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
-
   NS_DEFINE_CLASSINFO_DATA(Storage, nsStorage2SH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS |
                            nsIXPCScriptable::WANT_NEWRESOLVE |
                            nsIXPCScriptable::WANT_GETPROPERTY |
                            nsIXPCScriptable::WANT_SETPROPERTY |
                            nsIXPCScriptable::WANT_DELPROPERTY |
                            nsIXPCScriptable::DONT_ENUM_STATIC_PROPS |
                            nsIXPCScriptable::WANT_NEWENUMERATE)
-  NS_DEFINE_CLASSINFO_DATA(StorageItem, nsDOMGenericSH,
-                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(XULCommandEvent, nsEventSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(CommandEvent, nsEventSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(OfflineResourceList, nsOfflineResourceListSH,
                            ARRAY_SCRIPTABLE_FLAGS)
@@ -2064,29 +2059,20 @@ nsDOMClassInfo::Init()
   DOM_CLASSINFO_MAP_BEGIN(XPathNSResolver, nsIDOMXPathNSResolver)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMXPathNSResolver)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(XPathResult, nsIDOMXPathResult)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMXPathResult)
   DOM_CLASSINFO_MAP_END
 
-  DOM_CLASSINFO_MAP_BEGIN(StorageObsolete, nsIDOMStorageObsolete)
-    DOM_CLASSINFO_MAP_ENTRY(nsIDOMStorageObsolete)
-  DOM_CLASSINFO_MAP_END
-
   DOM_CLASSINFO_MAP_BEGIN(Storage, nsIDOMStorage)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMStorage)
   DOM_CLASSINFO_MAP_END
 
-  DOM_CLASSINFO_MAP_BEGIN(StorageItem, nsIDOMStorageItem)
-    DOM_CLASSINFO_MAP_ENTRY(nsIDOMStorageItem)
-    DOM_CLASSINFO_MAP_ENTRY(nsIDOMToString)
-  DOM_CLASSINFO_MAP_END
-
   DOM_CLASSINFO_MAP_BEGIN(XULCommandEvent, nsIDOMXULCommandEvent)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMXULCommandEvent)
     DOM_CLASSINFO_UI_EVENT_MAP_ENTRIES
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(CommandEvent, nsIDOMCommandEvent)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMCommandEvent)
     DOM_CLASSINFO_EVENT_MAP_ENTRIES
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -131,19 +131,17 @@ DOMCI_CLASS(WindowUtils)
 DOMCI_CLASS(XSLTProcessor)
 
 // DOM Level 3 XPath objects
 DOMCI_CLASS(XPathExpression)
 DOMCI_CLASS(XPathNSResolver)
 DOMCI_CLASS(XPathResult)
 
 // WhatWG WebApps Objects
-DOMCI_CLASS(StorageObsolete)
 DOMCI_CLASS(Storage)
-DOMCI_CLASS(StorageItem)
 
 DOMCI_CLASS(XULCommandEvent)
 DOMCI_CLASS(CommandEvent)
 DOMCI_CLASS(OfflineResourceList)
 
 DOMCI_CLASS(Blob)
 DOMCI_CLASS(File)
 
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -13,22 +13,25 @@
 // Local Includes
 #include "nsGlobalWindow.h"
 #include "Navigator.h"
 #include "nsScreen.h"
 #include "nsHistory.h"
 #include "nsPerformance.h"
 #include "nsDOMNavigationTiming.h"
 #include "nsBarProps.h"
-#include "nsDOMStorage.h"
+#include "nsIDOMStorage.h"
+#include "nsIDOMStorageManager.h"
+#include "DOMStorage.h"
 #include "nsDOMOfflineResourceList.h"
 #include "nsError.h"
 #include "nsIIdleService.h"
 #include "nsIPowerManagerService.h"
 #include "nsISizeOfEventTarget.h"
+#include "nsIPermissionManager.h"
 
 #ifdef XP_WIN
 #ifdef GetClassName
 #undef GetClassName
 #endif // GetClassName
 #endif // XP_WIN
 
 // Helper Classes
@@ -2515,17 +2518,46 @@ nsGlobalWindow::SetNewDocument(nsIDocume
     if (itemType != nsIDocShellTreeItem::typeChrome ||
         nsContentUtils::IsSystemPrincipal(mDoc->NodePrincipal())) {
       newInnerWindow->mHasNotifiedGlobalCreated = true;
       nsContentUtils::AddScriptRunner(
         NS_NewRunnableMethod(this, &nsGlobalWindow::DispatchDOMWindowCreated));
     }
   }
 
-  return NS_OK;
+  PreloadLocalStorage();
+
+  return NS_OK;
+}
+
+void
+nsGlobalWindow::PreloadLocalStorage()
+{
+  if (!Preferences::GetBool(kStorageEnabled)) {
+    return;
+  }
+
+  if (IsChromeWindow()) {
+    return;
+  }
+
+  nsIPrincipal* principal = GetPrincipal();
+  if (!principal) {
+    return;
+  }
+
+  nsresult rv;
+
+  nsCOMPtr<nsIDOMStorageManager> storageManager =
+    do_GetService("@mozilla.org/dom/localStorage-manager;1", &rv);
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  storageManager->PrecacheStorage(principal);
 }
 
 void
 nsGlobalWindow::DispatchDOMWindowCreated()
 {
   if (!mDoc || !mDocument) {
     return;
   }
@@ -2703,29 +2735,16 @@ nsGlobalWindow::DetachFromDocShell()
   NS_ASSERTION(!mNavigator, "Non-null mNavigator in outer window!");
 
   if (mFrames) {
     mFrames->SetDocShell(nullptr);
   }
 
   MaybeForgiveSpamCount();
   CleanUp(false);
-
-    if (mLocalStorage) {
-      nsCOMPtr<nsIPrivacyTransitionObserver> obs = do_GetInterface(mLocalStorage);
-      if (obs) {
-        mDocShell->AddWeakPrivacyTransitionObserver(obs);
-      }
-    }
-    if (mSessionStorage) {
-      nsCOMPtr<nsIPrivacyTransitionObserver> obs = do_GetInterface(mSessionStorage);
-      if (obs) {
-        mDocShell->AddWeakPrivacyTransitionObserver(obs);
-      }
-    }
 }
 
 void
 nsGlobalWindow::SetOpenerWindow(nsIDOMWindow* aOpener,
                                 bool aOriginalOpener)
 {
   FORWARD_TO_OUTER_VOID(SetOpenerWindow, (aOpener, aOriginalOpener));
 
@@ -8821,65 +8840,67 @@ nsGlobalWindow::GetSessionStorage(nsIDOM
 #endif
     nsCOMPtr<nsPIDOMStorage> piStorage = do_QueryInterface(mSessionStorage);
     if (piStorage) {
       bool canAccess = piStorage->CanAccess(principal);
       NS_ASSERTION(canAccess,
                    "window %x owned sessionStorage "
                    "that could not be accessed!");
       if (!canAccess) {
-          mSessionStorage = nullptr;
+        mSessionStorage = nullptr;
       }
     }
   }
 
   if (!mSessionStorage) {
     *aSessionStorage = nullptr;
 
     nsString documentURI;
     if (mDocument) {
       mDocument->GetDocumentURI(documentURI);
     }
 
     // If the document has the sandboxed origin flag set
-    // don't allow access to localStorage.
+    // don't allow access to sessionStorage.
     if (!mDoc) {
       return NS_ERROR_FAILURE;
     }
 
     if (mDoc->GetSandboxFlags() & SANDBOXED_ORIGIN) {
       return NS_ERROR_DOM_SECURITY_ERR;
     }
 
-    nsresult rv = docShell->GetSessionStorageForPrincipal(principal,
-                                                          documentURI,
-                                                          true,
-                                                          getter_AddRefs(mSessionStorage));
+    nsresult rv;
+
+    nsCOMPtr<nsIDOMStorageManager> storageManager = do_QueryInterface(docShell, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell);
+
+    rv = storageManager->CreateStorage(principal,
+                                       documentURI,
+                                       loadContext && loadContext->UsePrivateBrowsing(),
+                                       getter_AddRefs(mSessionStorage));
     NS_ENSURE_SUCCESS(rv, rv);
 
 #ifdef PR_LOGGING
     if (PR_LOG_TEST(gDOMLeakPRLog, PR_LOG_DEBUG)) {
       PR_LogPrint("nsGlobalWindow %p tried to get a new sessionStorage %p", this, mSessionStorage.get());
     }
 #endif
 
     if (!mSessionStorage) {
       return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
     }
-
-    nsCOMPtr<nsIPrivacyTransitionObserver> obs = do_GetInterface(mSessionStorage);
-    if (obs) {
-      docShell->AddWeakPrivacyTransitionObserver(obs);
-    }
   }
 
 #ifdef PR_LOGGING
-    if (PR_LOG_TEST(gDOMLeakPRLog, PR_LOG_DEBUG)) {
-      PR_LogPrint("nsGlobalWindow %p returns %p sessionStorage", this, mSessionStorage.get());
-    }
+  if (PR_LOG_TEST(gDOMLeakPRLog, PR_LOG_DEBUG)) {
+    PR_LogPrint("nsGlobalWindow %p returns %p sessionStorage", this, mSessionStorage.get());
+  }
 #endif
 
   NS_ADDREF(*aSessionStorage = mSessionStorage);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsGlobalWindow::GetLocalStorage(nsIDOMStorage ** aLocalStorage)
@@ -8893,51 +8914,48 @@ nsGlobalWindow::GetLocalStorage(nsIDOMSt
     return NS_OK;
   }
 
   if (!mLocalStorage) {
     *aLocalStorage = nullptr;
 
     nsresult rv;
 
-    if (!nsDOMStorage::CanUseStorage())
+    if (!DOMStorage::CanUseStorage()) {
       return NS_ERROR_DOM_SECURITY_ERR;
+    }
 
     nsIPrincipal *principal = GetPrincipal();
-    if (!principal)
+    if (!principal) {
       return NS_OK;
+    }
 
     nsCOMPtr<nsIDOMStorageManager> storageManager =
-      do_GetService("@mozilla.org/dom/storagemanager;1", &rv);
+      do_GetService("@mozilla.org/dom/localStorage-manager;1", &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    nsString documentURI;
-    if (mDocument) {
-      mDocument->GetDocumentURI(documentURI);
-    }
-
     // If the document has the sandboxed origin flag set
     // don't allow access to localStorage.
     if (mDoc && (mDoc->GetSandboxFlags() & SANDBOXED_ORIGIN)) {
       return NS_ERROR_DOM_SECURITY_ERR;
     }
 
+    nsString documentURI;
+    if (mDocument) {
+      mDocument->GetDocumentURI(documentURI);
+    }
+
     nsIDocShell* docShell = GetDocShell();
     nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell);
 
-    rv = storageManager->GetLocalStorageForPrincipal(principal,
-                                                     documentURI,
-                                                     loadContext && loadContext->UsePrivateBrowsing(),
-                                                     getter_AddRefs(mLocalStorage));
+    rv = storageManager->CreateStorage(principal,
+                                       documentURI,
+                                       loadContext && loadContext->UsePrivateBrowsing(),
+                                       getter_AddRefs(mLocalStorage));
     NS_ENSURE_SUCCESS(rv, rv);
-
-    nsCOMPtr<nsIPrivacyTransitionObserver> obs = do_GetInterface(mLocalStorage);
-    if (obs && docShell) {
-      docShell->AddWeakPrivacyTransitionObserver(obs);
-    }
   }
 
   NS_ADDREF(*aLocalStorage = mLocalStorage);
   return NS_OK;
 }
 
 //*****************************************************************************
 // nsGlobalWindow::nsIDOMStorageIndexedDB
@@ -9501,66 +9519,65 @@ nsGlobalWindow::Observe(nsISupports* aSu
 
     nsCOMPtr<nsIDOMStorageEvent> event = do_QueryInterface(aSubject, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsIDOMStorage> changingStorage;
     rv = event->GetStorageArea(getter_AddRefs(changingStorage));
     NS_ENSURE_SUCCESS(rv, rv);
 
+    bool fireMozStorageChanged = false;
+    principal = GetPrincipal();
+    if (!principal) {
+      return NS_OK;
+    }
+
     nsCOMPtr<nsPIDOMStorage> pistorage = do_QueryInterface(changingStorage);
-    nsPIDOMStorage::nsDOMStorageType storageType = pistorage->StorageType();
 
     nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(GetDocShell());
     bool isPrivate = loadContext && loadContext->UsePrivateBrowsing();
     if (pistorage->IsPrivate() != isPrivate) {
       return NS_OK;
     }
 
-    bool fireMozStorageChanged = false;
-    principal = GetPrincipal();
-    switch (storageType)
+    switch (pistorage->GetType())
     {
     case nsPIDOMStorage::SessionStorage:
     {
-      nsCOMPtr<nsIDOMStorage> storage = mSessionStorage;
-      if (!storage) {
-        nsIDocShell* docShell = GetDocShell();
-        if (principal && docShell) {
-          // No need to pass documentURI here, it's only needed when we want
-          // to create a new storage, the third paramater would be true
-          docShell->GetSessionStorageForPrincipal(principal,
-                                                  EmptyString(),
-                                                  false,
-                                                  getter_AddRefs(storage));
-        }
+      bool check = false;
+
+      nsCOMPtr<nsIDOMStorageManager> storageManager = do_QueryInterface(GetDocShell());
+      if (storageManager) {
+        rv = storageManager->CheckStorage(principal, changingStorage, &check);
+        NS_ENSURE_SUCCESS(rv, rv);
       }
 
-      if (!pistorage->IsForkOf(storage)) {
-        // This storage event is coming from a different doc shell,
-        // i.e. it is a clone, ignore this event.
+      if (!check) {
+        // This storage event is not coming from our storage or is coming
+        // from a different docshell, i.e. it is a clone, ignore this event.
         return NS_OK;
       }
 
 #ifdef PR_LOGGING
       if (PR_LOG_TEST(gDOMLeakPRLog, PR_LOG_DEBUG)) {
         PR_LogPrint("nsGlobalWindow %p with sessionStorage %p passing event from %p", this, mSessionStorage.get(), pistorage.get());
       }
 #endif
 
       fireMozStorageChanged = SameCOMIdentity(mSessionStorage, changingStorage);
       break;
     }
+
     case nsPIDOMStorage::LocalStorage:
     {
       // Allow event fire only for the same principal storages
       // XXX We have to use EqualsIgnoreDomain after bug 495337 lands
-      nsIPrincipal *storagePrincipal = pistorage->Principal();
-      bool equals;
-
+      nsIPrincipal* storagePrincipal = pistorage->GetPrincipal();
+
+      bool equals = false;
       rv = storagePrincipal->Equals(principal, &equals);
       NS_ENSURE_SUCCESS(rv, rv);
 
       if (!equals)
         return NS_OK;
 
       fireMozStorageChanged = SameCOMIdentity(mLocalStorage, changingStorage);
       break;
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -41,17 +41,16 @@
 #include "nsIDOMDocument.h"
 #include "nsIPrincipal.h"
 #include "nsIXPCScriptable.h"
 #include "nsPoint.h"
 #include "nsSize.h"
 #include "nsRect.h"
 #include "mozFlushType.h"
 #include "prclist.h"
-#include "nsIDOMStorageObsolete.h"
 #include "nsIDOMStorageEvent.h"
 #include "nsIDOMStorageIndexedDB.h"
 #include "nsIDOMOfflineResourceList.h"
 #include "nsIArray.h"
 #include "nsFrameMessageManager.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/TimeStamp.h"
 #include "nsIDOMTouchEvent.h"
@@ -1017,16 +1016,18 @@ protected:
   nsDOMWindowList* GetWindowList();
 
   // Helper for getComputedStyle and getDefaultComputedStyle
   nsresult GetComputedStyleHelper(nsIDOMElement* aElt,
                                   const nsAString& aPseudoElt,
                                   bool aDefaultStylesOnly,
                                   nsIDOMCSSStyleDeclaration** aReturn);
 
+  void PreloadLocalStorage();
+
   // When adding new member variables, be careful not to create cycles
   // through JavaScript.  If there is any chance that a member variable
   // could own objects that are implemented in JavaScript, then those
   // objects will keep the global object (this object) alive.  To prevent
   // these cycles, ownership of such members must be released in
   // |CleanUp| and |DetachFromDocShell|.
 
   // This member is also used on both inner and outer windows, but
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -584,16 +584,21 @@ DOMInterfaces = {
     'nativeType': 'nsDOMMessageEvent',
 },
 
 'MouseEvent': {
     'nativeType': 'nsDOMMouseEvent',
     'hasXPConnectImpls': True,
 },
 
+'MouseScrollEvent': {
+    'nativeType': 'nsDOMMouseScrollEvent',
+},
+
+
 'MozChannel': [
 {
     'nativeType': 'nsIChannel',
     'notflattened': True
 },
 {
     'workers': True,
 }],
@@ -629,16 +634,20 @@ DOMInterfaces = {
     'resultNotAddRefed': [ 'root', 'referenceNode' ],
 },
 
 'NodeList': {
     'nativeType': 'nsINodeList',
     'resultNotAddRefed': [ 'item' ]
 },
 
+'NotifyPaintEvent': {
+    'nativeType': 'nsDOMNotifyPaintEvent',
+},
+
 'PaintRequest': {
     'nativeType': 'nsPaintRequest',
 },
 
 'PaintRequestList': {
     'nativeType': 'nsPaintRequestList',
     'headerFile': 'nsPaintRequest.h',
     'resultNotAddRefed': [ 'item' ]
@@ -978,16 +987,20 @@ DOMInterfaces = {
     'workers': True,
     'implicitJSContext': [ 'encode' ],
 }],
 
 'TimeRanges': {
     'wrapperCache': False
 },
 
+'TouchEvent': {
+    'nativeType': 'nsDOMTouchEvent',
+},
+
 'TransitionEvent': {
     'nativeType': 'nsDOMTransitionEvent',
 },
 
 'TreeWalker': {
     'wrapperCache': False,
     'resultNotAddRefed': [ 'root', 'currentNode' ],
 },
--- a/dom/indexedDB/CheckPermissionsHelper.cpp
+++ b/dom/indexedDB/CheckPermissionsHelper.cpp
@@ -12,17 +12,16 @@
 #include "nsIObserverService.h"
 #include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIURI.h"
 
 #include "CheckQuotaHelper.h"
 #include "nsContentUtils.h"
-#include "nsDOMStorage.h"
 #include "nsNetUtil.h"
 #include "nsThreadUtils.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 
 #include "IndexedDatabaseManager.h"
 
--- a/dom/interfaces/storage/moz.build
+++ b/dom/interfaces/storage/moz.build
@@ -3,19 +3,17 @@
 # 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/.
 
 XPIDL_SOURCES += [
     'nsIDOMStorage.idl',
     'nsIDOMStorageEvent.idl',
     'nsIDOMStorageIndexedDB.idl',
-    'nsIDOMStorageItem.idl',
     'nsIDOMStorageManager.idl',
-    'nsIDOMStorageObsolete.idl',
     'nsIDOMToString.idl',
 ]
 
 XPIDL_MODULE = 'dom_storage'
 
 XPIDL_FLAGS += [
     '-I$(topsrcdir)/dom/interfaces/base',
     '-I$(topsrcdir)/dom/interfaces/events',
deleted file mode 100644
--- a/dom/interfaces/storage/nsIDOMStorageItem.idl
+++ /dev/null
@@ -1,28 +0,0 @@
-/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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/. */
-
-#include "domstubs.idl"
-
-/**
- * Interface for a client side storage item. See
- * http://www.whatwg.org/specs/web-apps/current-work/#scs-client-side
- * for more information.
- *
- * A respresentation of a storage object item.
- */
-
-[scriptable, uuid(0CC37C78-4C5F-48E1-ADFC-7480B8FE9DC4)]
-interface nsIDOMStorageItem : nsISupports
-{
-  /**
-   * Indicates whether a key is available only in a secure context.
-   */
-  attribute boolean secure;
-
-  /**
-   * The value associated with the item.
-   */
-  attribute DOMString value;
-};
--- a/dom/interfaces/storage/nsIDOMStorageManager.idl
+++ b/dom/interfaces/storage/nsIDOMStorageManager.idl
@@ -3,30 +3,96 @@
  * 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/. */
 
 #include "nsISupports.idl"
 
 interface nsIDOMStorage;
 interface nsIPrincipal;
 
-[scriptable, uuid(b16b207c-d883-43f5-a27e-548e7f2f5c20)]
+/**
+ * General purpose interface that has two implementations, for localStorage
+ * resp. sessionStorage with "@mozilla.org/dom/localStorage-manager;1" resp.
+ * "@mozilla.org/dom/sessionStorage-manager;1" contract IDs.
+ */
+[scriptable, uuid(8096f9ea-fa61-4960-b5d7-fb30ac42c8d8)]
 interface nsIDOMStorageManager : nsISupports
 {
   /**
-   * Return the amount of disk space used by a domain.  Usage is checked
-   * against the domain of the page that set the key (the owner domain), not
-   * the domain of the storage object.
-   *
-   * @param aOwnerDomain The domain to check.
-   * @returns the space usage of the domain, in bytes.
+   * This starts async preloading of a storage cache for scope
+   * defined by the principal.
    */
-  long getUsage(in AString aOwnerDomain);
+  void precacheStorage(in nsIPrincipal aPrincipal);
 
   /**
+   * Returns instance of DOM storage object for given principal.
+   * A new object is always returned and it is ensured there is
+   * a storage for the scope created.
+   *
+   * @param aPrincipal
+   *    Principal to bound storage to.
+   * @param aDocumentURI
+   *    URL of the demanding document, used for DOM storage event only.
+   * @param aPrivate
+   *    Whether the demanding document is running in Private Browsing mode or not.
+   */
+  nsIDOMStorage createStorage(in nsIPrincipal aPrincipal,
+                              in DOMString aDocumentURI,
+                              [optional] in bool aPrivate);
+  /**
+   * Returns instance of DOM storage object for given principal.
+   * If there is no storage managed for the scope, then null is returned and
+   * no object is created.  Otherwise, an object (new) for the existing storage
+   * scope is returned.
+   *
+   * @param aPrincipal
+   *    Principal to bound storage to.
+   * @param aPrivate
+   *    Whether the demanding document is running in Private Browsing mode or not.
+   */
+  nsIDOMStorage getStorage(in nsIPrincipal aPrincipal,
+                           [optional] in bool aPrivate);
+
+  /**
+   * Clones given storage into this storage manager.
+   *
+   * @param aStorageToCloneFrom
+   *    The storage to copy all items from into this manager.  Manager will then
+   *    return a new and independent object that contains snapshot of data from
+   *    the moment this method was called.  Modification to this new object will
+   *    not affect the original storage content we cloned from and vice versa.
+   */
+  void cloneStorage(in nsIDOMStorage aStorageToCloneFrom);
+
+  /**
+   * Returns true if the storage belongs to the given principal and is managed
+   * (i.e. has been created and is cached) by this storage manager.
+   *
+   * @param aPrincipal
+   *    Principal to check the storage against.
+   * @param aStorage
+   *    The storage object to examine.
+   *
+   * @result
+   *    true when the storage object is bound with the principal and is managed
+   *         by this storage manager.
+   *    false otherwise
+   */
+  bool checkStorage(in nsIPrincipal aPrincipal,
+                    in nsIDOMStorage aStorage);
+
+  /**
+   * @deprecated
+   *
    * Returns instance of localStorage object for aURI's origin.
    * This method ensures there is always only a single instance
    * for a single origin.
+   *
+   * Currently just forwards to the createStorage method of this
+   * interface.
+   *
+   * Extension developers are strongly encouraged to use getStorage
+   * or createStorage method instead.
    */
   nsIDOMStorage getLocalStorageForPrincipal(in nsIPrincipal aPrincipal,
                                             in DOMString aDocumentURI,
                                             [optional] in bool aPrivate);
 };
deleted file mode 100644
--- a/dom/interfaces/storage/nsIDOMStorageObsolete.idl
+++ /dev/null
@@ -1,65 +0,0 @@
-/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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/. */
-
-#include "domstubs.idl"
-
-/**
- * Interface for client side storage. See
- * http://www.whatwg.org/specs/web-apps/current-work/#scs-client-side
- * for more information.
- *
- * A storage object stores an arbitrary set of key-value pairs, which
- * may be retrieved, modified and removed as needed. A key may only
- * exist once within a storage object, and only one value may be
- * associated with a particular key. Keys are stored in a particular
- * order with the condition that this order not change by merely changing
- * the value associated with a key, but the order may change when a
- * key is added or removed.
- */
-
-interface nsIDOMStorageItem;
-
-[scriptable, uuid(18013CF9-B104-49cf-9484-C2A7A845457E)]
-interface nsIDOMStorageObsolete : nsISupports
-{
-  /**
-   * The number of keys stored.
-   */
-  readonly attribute unsigned long length;
-
-  /**
-   * Retrieve the name of the key at a particular index.
-   *
-   * @param index index of the item to retrieve
-   * @returns the key at index
-   * @throws INDEX_SIZE_ERR if there is no key at that index
-   */
-  DOMString key(in unsigned long index);
-
-  /**
-   * Retrieve an item with a given key
-   *
-   * @param key key to retrieve
-   * @returns found item or null if the key was not found
-   */
-  nsIDOMStorageItem getItem(in DOMString key);
-
-  /**
-   * Assign a value with a key. If the key does not exist already, a new
-   * key is added associated with that value. If the key already exists,
-   * then the existing value is replaced with a new value.
-   *
-   * @param key key to set
-   * @param data data to associate with the key
-   */
-  void setItem(in DOMString key, in DOMString data);
-
-  /**
-   * Remove a key and its corresponding value.
-   *
-   * @param key key to remove
-   */
-  void removeItem(in DOMString key);
-};
--- a/dom/interfaces/storage/nsPIDOMStorage.h
+++ b/dom/interfaces/storage/nsPIDOMStorage.h
@@ -3,51 +3,52 @@
 /* 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/. */
 
 #ifndef __nsPIDOMStorage_h_
 #define __nsPIDOMStorage_h_
 
 #include "nsISupports.h"
+#include "nsString.h"
 #include "nsTArray.h"
 
-class nsIDOMStorageObsolete;
-class nsIURI;
 class nsIPrincipal;
 
+namespace mozilla {
+namespace dom {
+
+class DOMStorageCache;
+class DOMStorageManager;
+
+} // ::dom
+} // ::mozilla
+
+// {09198A51-5D27-4992-97E4-38A9CEA2A65D}
 #define NS_PIDOMSTORAGE_IID \
-{ 0x9c292365, 0x6ae4, 0x461b,                           \
-  { 0x86, 0x98, 0xf5, 0x23, 0x6f, 0xfa, 0xd2, 0x30 } }
+  { 0x9198a51, 0x5d27, 0x4992, \
+    { 0x97, 0xe4, 0x38, 0xa9, 0xce, 0xa2, 0xa6, 0x5d } }
 
 class nsPIDOMStorage : public nsISupports
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_PIDOMSTORAGE_IID)
 
-  typedef enum {
-    Unknown = 0,
+  enum StorageType {
     LocalStorage = 1,
     SessionStorage = 2
-  } nsDOMStorageType;
+  };
 
-  virtual nsresult InitAsSessionStorage(nsIPrincipal *aPrincipal, const nsSubstring &aDocumentURI,
-                                        bool aPrivate) = 0;
-  virtual nsresult InitAsLocalStorage(nsIPrincipal *aPrincipal, const nsSubstring &aDocumentURI,
-                                      bool aPrivate) = 0;
+  virtual StorageType GetType() const = 0;
+  virtual mozilla::dom::DOMStorageManager* GetManager() const = 0;
+  virtual const mozilla::dom::DOMStorageCache* GetCache() const = 0;
 
-  virtual already_AddRefed<nsIDOMStorage> Clone() = 0;
-  virtual already_AddRefed<nsIDOMStorage> Fork(const nsSubstring &aDocumentURI) = 0;
-  virtual bool IsForkOf(nsIDOMStorage* aThat) = 0;
-
-  virtual nsTArray<nsString> *GetKeys() = 0;
+  virtual nsTArray<nsString>* GetKeys() = 0;
 
-  virtual nsIPrincipal* Principal() = 0;
+  virtual nsIPrincipal* GetPrincipal() = 0;
+  virtual bool PrincipalEquals(nsIPrincipal* principal) = 0;
   virtual bool CanAccess(nsIPrincipal *aPrincipal) = 0;
-
-  virtual nsDOMStorageType StorageType() = 0;
-
   virtual bool IsPrivate() = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMStorage, NS_PIDOMSTORAGE_IID)
 
 #endif // __nsPIDOMStorage_h_
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -14,17 +14,17 @@
 
 #include "ContentChild.h"
 #include "CrashReporterChild.h"
 #include "TabChild.h"
 
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/ExternalHelperAppChild.h"
 #include "mozilla/dom/PCrashReporterChild.h"
-#include "mozilla/dom/StorageChild.h"
+#include "mozilla/dom/DOMStorageIPC.h"
 #include "mozilla/Hal.h"
 #include "mozilla/hal_sandbox/PHalChild.h"
 #include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "mozilla/ipc/TestShellChild.h"
 #include "mozilla/ipc/XPCShellEnvironment.h"
 #include "mozilla/jsipc/PContextWrapperChild.h"
 #include "mozilla/layers/CompositorChild.h"
 #include "mozilla/layers/ImageBridgeChild.h"
@@ -834,26 +834,26 @@ ContentChild::AllocPSms()
 bool
 ContentChild::DeallocPSms(PSmsChild* aSms)
 {
     delete aSms;
     return true;
 }
 
 PStorageChild*
-ContentChild::AllocPStorage(const StorageConstructData& aData)
+ContentChild::AllocPStorage()
 {
     NS_NOTREACHED("We should never be manually allocating PStorageChild actors");
     return nullptr;
 }
 
 bool
 ContentChild::DeallocPStorage(PStorageChild* aActor)
 {
-    StorageChild* child = static_cast<StorageChild*>(aActor);
+    DOMStorageDBChild* child = static_cast<DOMStorageDBChild*>(aActor);
     child->ReleaseIPDLReference();
     return true;
 }
 
 PBluetoothChild*
 ContentChild::AllocPBluetooth()
 {
 #ifdef MOZ_B2G_BT
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -137,17 +137,17 @@ public:
             const bool& aForceSave,
             const int64_t& aContentLength,
             const OptionalURIParams& aReferrer);
     virtual bool DeallocPExternalHelperApp(PExternalHelperAppChild *aService);
 
     virtual PSmsChild* AllocPSms();
     virtual bool DeallocPSms(PSmsChild*);
 
-    virtual PStorageChild* AllocPStorage(const StorageConstructData& aData);
+    virtual PStorageChild* AllocPStorage();
     virtual bool DeallocPStorage(PStorageChild* aActor);
 
     virtual PBluetoothChild* AllocPBluetooth();
     virtual bool DeallocPBluetooth(PBluetoothChild* aActor);
 
     virtual PSpeechSynthesisChild* AllocPSpeechSynthesis();
     virtual bool DeallocPSpeechSynthesis(PSpeechSynthesisChild* aActor);
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -24,17 +24,17 @@
 #include "IDBFactory.h"
 #include "IndexedDBParent.h"
 #include "IndexedDatabaseManager.h"
 #include "mozIApplication.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/dom/ExternalHelperAppParent.h"
 #include "mozilla/dom/PMemoryReportRequestParent.h"
 #include "mozilla/dom/power/PowerManagerService.h"
-#include "mozilla/dom/StorageParent.h"
+#include "mozilla/dom/DOMStorageIPC.h"
 #include "mozilla/dom/bluetooth/PBluetoothParent.h"
 #include "mozilla/dom/devicestorage/DeviceStorageRequestParent.h"
 #include "SmsParent.h"
 #include "mozilla/Hal.h"
 #include "mozilla/hal_sandbox/PHalParent.h"
 #include "mozilla/ipc/TestShellParent.h"
 #include "mozilla/layers/CompositorParent.h"
 #include "mozilla/layers/ImageBridgeParent.h"
@@ -1964,25 +1964,26 @@ ContentParent::AllocPSms()
 bool
 ContentParent::DeallocPSms(PSmsParent* aSms)
 {
     static_cast<SmsParent*>(aSms)->Release();
     return true;
 }
 
 PStorageParent*
-ContentParent::AllocPStorage(const StorageConstructData& aData)
+ContentParent::AllocPStorage()
 {
-    return new StorageParent(aData);
+    return new DOMStorageDBParent();
 }
 
 bool
 ContentParent::DeallocPStorage(PStorageParent* aActor)
 {
-    delete aActor;
+    DOMStorageDBParent* child = static_cast<DOMStorageDBParent*>(aActor);
+    child->ReleaseIPDLReference();
     return true;
 }
 
 PBluetoothParent*
 ContentParent::AllocPBluetooth()
 {
 #ifdef MOZ_B2G_BT
     if (!AssertAppProcessPermission(this, "bluetooth")) {
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -280,17 +280,17 @@ private:
             const bool& aForceSave,
             const int64_t& aContentLength,
             const OptionalURIParams& aReferrer);
     virtual bool DeallocPExternalHelperApp(PExternalHelperAppParent* aService);
 
     virtual PSmsParent* AllocPSms();
     virtual bool DeallocPSms(PSmsParent*);
 
-    virtual PStorageParent* AllocPStorage(const StorageConstructData& aData);
+    virtual PStorageParent* AllocPStorage();
     virtual bool DeallocPStorage(PStorageParent* aActor);
 
     virtual PBluetoothParent* AllocPBluetooth();
     virtual bool DeallocPBluetooth(PBluetoothParent* aActor);
     virtual bool RecvPBluetoothConstructor(PBluetoothParent* aActor);
 
     virtual PSpeechSynthesisParent* AllocPSpeechSynthesis();
     virtual bool DeallocPSpeechSynthesis(PSpeechSynthesisParent* aActor);
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -45,33 +45,16 @@ using mozilla::void_t;
 using mozilla::dom::AudioChannelType;
 using mozilla::dom::NativeThreadId;
 using mozilla::layout::ScrollingBehavior;
 using gfxIntSize;
 
 namespace mozilla {
 namespace dom {
 
-// Data required to clone an existing DOMStorageImpl in the parent
-struct StorageClone
-{
-    // Existing cross-process storage actor to clone
-    PStorage actor;
-    // Result of calling IsCallerSecure() in the child
-    bool callerSecure;
-};
-
-// When creating a new PStorage protocol, an existing one can be
-// cloned (see nsDOMStorage2::Clone)
-union StorageConstructData
-{
-    null_t;
-    StorageClone;
-};
-
 struct FontListEntry {
     nsString  familyName;
     nsString  faceName;
     nsCString filepath;
     uint16_t  weight;
     int16_t   stretch;
     uint8_t   italic;
     uint8_t   index;
@@ -396,17 +379,17 @@ parent:
     PIndexedDB();
 
     PNecko();
 
     PSms();
 
     PSpeechSynthesis();
 
-    PStorage(StorageConstructData data);
+    PStorage();
 
     PBluetooth();
 
     // Services remoting
 
     async StartVisitedQuery(URIParams uri);
     async VisitURI(URIParams uri, OptionalURIParams referrer, uint32_t flags);
     async SetURITitle(URIParams uri, nsString title);
--- a/dom/push/src/PushService.js
+++ b/dom/push/src/PushService.js
@@ -12,17 +12,17 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
-Cu.import("resource://gre/modules/services-common/preferences.js");
+Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
 
 const prefs = new Preferences("services.push.");
 
 const kPUSHDB_DB_NAME = "push";
 const kPUSHDB_DB_VERSION = 1; // Change this if the IndexedDB format changes
 const kPUSHDB_STORE_NAME = "push";
 const kCONFLICT_RETRY_ATTEMPTS = 3; // If channelID registration says 409, how
new file mode 100644
--- /dev/null
+++ b/dom/src/storage/DOMStorage.cpp
@@ -0,0 +1,353 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "DOMStorage.h"
+#include "DOMStorageCache.h"
+#include "DOMStorageManager.h"
+
+#include "nsIDOMStorageEvent.h"
+#include "nsIObserverService.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIPermissionManager.h"
+#include "nsIPrincipal.h"
+#include "nsICookiePermission.h"
+
+#include "nsDOMClassInfoID.h"
+#include "mozilla/Services.h"
+#include "mozilla/Preferences.h"
+#include "GeneratedEvents.h"
+#include "nsThreadUtils.h"
+#include "nsContentUtils.h"
+#include "nsServiceManagerUtils.h"
+
+DOMCI_DATA(Storage, mozilla::dom::DOMStorage)
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ADDREF(DOMStorage)
+NS_IMPL_RELEASE(DOMStorage)
+
+NS_INTERFACE_MAP_BEGIN(DOMStorage)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMStorage)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMStorage)
+  NS_INTERFACE_MAP_ENTRY(nsPIDOMStorage)
+  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(Storage)
+NS_INTERFACE_MAP_END
+
+DOMStorage::DOMStorage(DOMStorageManager* aManager,
+                       DOMStorageCache* aCache,
+                       const nsAString& aDocumentURI,
+                       nsIPrincipal* aPrincipal,
+                       bool aIsPrivate)
+: mManager(aManager)
+, mCache(aCache)
+, mDocumentURI(aDocumentURI)
+, mPrincipal(aPrincipal)
+, mIsPrivate(aIsPrivate)
+, mIsSessionOnly(false)
+{
+  mCache->Preload();
+}
+
+DOMStorage::~DOMStorage()
+{
+  mCache->KeepAlive();
+}
+
+// nsIDOMStorage (web content public API implementation)
+
+NS_IMETHODIMP
+DOMStorage::GetLength(uint32_t* aLength)
+{
+  if (!CanUseStorage(this)) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  return mCache->GetLength(this, aLength);
+}
+
+NS_IMETHODIMP
+DOMStorage::Key(uint32_t aIndex, nsAString& aRetval)
+{
+  if (!CanUseStorage(this)) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  return mCache->GetKey(this, aIndex, aRetval);
+}
+
+NS_IMETHODIMP
+DOMStorage::GetItem(const nsAString& aKey, nsAString& aRetval)
+{
+  if (!CanUseStorage(this)) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  return mCache->GetItem(this, aKey, aRetval);
+}
+
+NS_IMETHODIMP
+DOMStorage::SetItem(const nsAString& aKey, const nsAString& aData)
+{
+  if (!CanUseStorage(this)) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  Telemetry::Accumulate(GetType() == LocalStorage
+      ? Telemetry::LOCALDOMSTORAGE_KEY_SIZE_BYTES
+      : Telemetry::SESSIONDOMSTORAGE_KEY_SIZE_BYTES, aKey.Length());
+  Telemetry::Accumulate(GetType() == LocalStorage
+      ? Telemetry::LOCALDOMSTORAGE_VALUE_SIZE_BYTES
+      : Telemetry::SESSIONDOMSTORAGE_VALUE_SIZE_BYTES, aData.Length());
+
+  nsString old;
+  nsresult rv = mCache->SetItem(this, aKey, nsString(aData), old);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  if (rv != NS_SUCCESS_DOM_NO_OPERATION) {
+    BroadcastChangeNotification(aKey, old, aData);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMStorage::RemoveItem(const nsAString& aKey)
+{
+  if (!CanUseStorage(this)) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  nsAutoString old;
+  nsresult rv = mCache->RemoveItem(this, aKey, old);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  if (rv != NS_SUCCESS_DOM_NO_OPERATION) {
+    BroadcastChangeNotification(aKey, old, NullString());
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMStorage::Clear()
+{
+  if (!CanUseStorage(this)) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  nsresult rv = mCache->Clear(this);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  if (rv != NS_SUCCESS_DOM_NO_OPERATION) {
+    BroadcastChangeNotification(NullString(), NullString(), NullString());
+  }
+
+  return NS_OK;
+}
+
+namespace {
+
+class StorageNotifierRunnable : public nsRunnable
+{
+public:
+  StorageNotifierRunnable(nsISupports* aSubject)
+    : mSubject(aSubject)
+  { }
+
+  NS_DECL_NSIRUNNABLE
+
+private:
+  nsCOMPtr<nsISupports> mSubject;
+};
+
+NS_IMETHODIMP
+StorageNotifierRunnable::Run()
+{
+  nsCOMPtr<nsIObserverService> observerService =
+    mozilla::services::GetObserverService();
+  if (observerService) {
+    observerService->NotifyObservers(mSubject, "dom-storage2-changed", nullptr);
+  }
+  return NS_OK;
+}
+
+} // anonymous namespace
+
+void
+DOMStorage::BroadcastChangeNotification(const nsSubstring& aKey,
+                                        const nsSubstring& aOldValue,
+                                        const nsSubstring& aNewValue)
+{
+  nsCOMPtr<nsIDOMEvent> domEvent;
+  // Note, this DOM event should never reach JS. It is cloned later in
+  // nsGlobalWindow.
+  NS_NewDOMStorageEvent(getter_AddRefs(domEvent), nullptr, nullptr, nullptr);
+
+  nsCOMPtr<nsIDOMStorageEvent> event = do_QueryInterface(domEvent);
+  nsresult rv = event->InitStorageEvent(NS_LITERAL_STRING("storage"),
+                                        false,
+                                        false,
+                                        aKey,
+                                        aOldValue,
+                                        aNewValue,
+                                        mDocumentURI,
+                                        static_cast<nsIDOMStorage*>(this));
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  nsRefPtr<StorageNotifierRunnable> r = new StorageNotifierRunnable(event);
+  NS_DispatchToMainThread(r);
+}
+
+static const uint32_t ASK_BEFORE_ACCEPT = 1;
+static const uint32_t ACCEPT_SESSION = 2;
+static const uint32_t BEHAVIOR_REJECT = 2;
+
+static const char kPermissionType[] = "cookie";
+static const char kStorageEnabled[] = "dom.storage.enabled";
+static const char kCookiesBehavior[] = "network.cookie.cookieBehavior";
+static const char kCookiesLifetimePolicy[] = "network.cookie.lifetimePolicy";
+
+// static, public
+bool
+DOMStorage::CanUseStorage(DOMStorage* aStorage)
+{
+  // This method is responsible for correct setting of mIsSessionOnly.
+  // It doesn't work with mIsPrivate flag at all, since it is checked
+  // regardless mIsSessionOnly flag in DOMStorageCache code.
+  if (aStorage) {
+    aStorage->mIsSessionOnly = false;
+  }
+
+  if (!mozilla::Preferences::GetBool(kStorageEnabled)) {
+    return false;
+  }
+
+  // chrome can always use aStorage regardless of permission preferences
+  if (nsContentUtils::IsCallerChrome()) {
+    return true;
+  }
+
+  nsCOMPtr<nsIPrincipal> subjectPrincipal;
+  nsresult rv = nsContentUtils::GetSecurityManager()->
+                  GetSubjectPrincipal(getter_AddRefs(subjectPrincipal));
+  NS_ENSURE_SUCCESS(rv, false);
+
+  // if subjectPrincipal were null we'd have returned after
+  // IsCallerChrome().
+
+  nsCOMPtr<nsIPermissionManager> permissionManager =
+    do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
+  if (!permissionManager) {
+    return false;
+  }
+
+  uint32_t perm;
+  permissionManager->TestPermissionFromPrincipal(subjectPrincipal,
+                                                 kPermissionType, &perm);
+
+  if (perm == nsIPermissionManager::DENY_ACTION) {
+    return false;
+  }
+
+  if (perm == nsICookiePermission::ACCESS_SESSION) {
+    if (aStorage) {
+      aStorage->mIsSessionOnly = true;
+    }
+  } else if (perm != nsIPermissionManager::ALLOW_ACTION) {
+    uint32_t cookieBehavior = Preferences::GetUint(kCookiesBehavior);
+    uint32_t lifetimePolicy = Preferences::GetUint(kCookiesLifetimePolicy);
+
+    // Treat "ask every time" as "reject always".
+    if ((cookieBehavior == BEHAVIOR_REJECT || lifetimePolicy == ASK_BEFORE_ACCEPT)) {
+      return false;
+    }
+
+    if (lifetimePolicy == ACCEPT_SESSION && aStorage) {
+      aStorage->mIsSessionOnly = true;
+    }
+  }
+
+  if (aStorage) {
+    return aStorage->CanAccess(subjectPrincipal);
+  }
+
+  return true;
+}
+
+// nsPIDOMStorage
+
+nsPIDOMStorage::StorageType
+DOMStorage::GetType() const
+{
+  return mManager->Type();
+}
+
+nsIPrincipal*
+DOMStorage::GetPrincipal()
+{
+  return mPrincipal;
+}
+
+// Defined in DOMStorageManager.cpp
+extern bool
+PrincipalsEqual(nsIPrincipal* aObjectPrincipal, nsIPrincipal* aSubjectPrincipal);
+
+bool
+DOMStorage::PrincipalEquals(nsIPrincipal* aPrincipal)
+{
+  return PrincipalsEqual(mPrincipal, aPrincipal);
+}
+
+bool
+DOMStorage::CanAccess(nsIPrincipal* aPrincipal)
+{
+  // Allow C++ callers to access the storage
+  if (!aPrincipal) {
+    return true;
+  }
+
+  // For content, either the code base or domain must be the same.  When code
+  // base is the same, this is enough to say it is safe for a page to access
+  // this storage.
+
+  bool subsumes;
+  nsresult rv = aPrincipal->SubsumesIgnoringDomain(mPrincipal, &subsumes);
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+
+  if (!subsumes) {
+    nsresult rv = aPrincipal->Subsumes(mPrincipal, &subsumes);
+    if (NS_FAILED(rv)) {
+      return false;
+    }
+  }
+
+  return subsumes;
+}
+
+nsTArray<nsString>*
+DOMStorage::GetKeys()
+{
+  if (!CanUseStorage(this)) {
+    return new nsTArray<nsString>(); // return just an empty array
+  }
+
+  return mCache->GetKeys(this);
+}
+
+} // ::dom
+} // ::mozilla
new file mode 100644
--- /dev/null
+++ b/dom/src/storage/DOMStorage.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef nsDOMStorage_h___
+#define nsDOMStorage_h___
+
+#include "nsIDOMStorage.h"
+#include "nsPIDOMStorage.h"
+#include "nsWeakReference.h"
+#include "nsAutoPtr.h"
+
+namespace mozilla {
+namespace dom {
+
+class DOMStorageManager;
+class DOMStorageCache;
+
+class DOMStorage MOZ_FINAL : public nsIDOMStorage
+                           , public nsPIDOMStorage
+                           , public nsSupportsWeakReference
+{
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMSTORAGE
+
+  // nsPIDOMStorage
+  virtual StorageType GetType() const;
+  virtual DOMStorageManager* GetManager() const { return mManager; }
+  virtual const DOMStorageCache* GetCache() const { return mCache; }
+
+  virtual nsTArray<nsString>* GetKeys();
+  virtual nsIPrincipal* GetPrincipal();
+  virtual bool PrincipalEquals(nsIPrincipal* aPrincipal);
+  virtual bool CanAccess(nsIPrincipal* aPrincipal);
+  virtual bool IsPrivate() { return mIsPrivate; }
+
+  DOMStorage(DOMStorageManager* aManager,
+             DOMStorageCache* aCache,
+             const nsAString& aDocumentURI,
+             nsIPrincipal* aPrincipal,
+             bool aIsPrivate);
+  ~DOMStorage();
+
+  // The method checks whether the caller can use a storage.
+  // CanUseStorage is called before any DOM initiated operation
+  // on a storage is about to happen and ensures that the storage's
+  // session-only flag is properly set according the current settings.
+  // It is an optimization since the privileges check and session only
+  // state determination are complex and share the code (comes hand in
+  // hand together).
+  static bool CanUseStorage(DOMStorage* aStorage = nullptr);
+
+  bool IsPrivate() const { return mIsPrivate; }
+  bool IsSessionOnly() const { return mIsSessionOnly; }
+
+private:
+  friend class DOMStorageManager;
+  friend class DOMStorageCache;
+
+  nsRefPtr<DOMStorageManager> mManager;
+  nsRefPtr<DOMStorageCache> mCache;
+  nsString mDocumentURI;
+
+  // Principal this DOMStorage (i.e. localStorage or sessionStorage) has
+  // been created for
+  nsCOMPtr<nsIPrincipal> mPrincipal;
+
+  // Whether this storage is running in private-browsing window.
+  bool mIsPrivate : 1;
+
+  // Whether storage is set to persist data only per session, may change
+  // dynamically and is set by CanUseStorage function that is called
+  // before any operation on the storage.
+  bool mIsSessionOnly : 1;
+
+  void BroadcastChangeNotification(const nsSubstring& aKey,
+                                   const nsSubstring& aOldValue,
+                                   const nsSubstring& aNewValue);
+};
+
+} // ::dom
+} // ::mozilla
+
+#endif /* nsDOMStorage_h___ */
new file mode 100644
--- /dev/null
+++ b/dom/src/storage/DOMStorageCache.cpp
@@ -0,0 +1,801 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "DOMStorageCache.h"
+
+#include "DOMStorage.h"
+#include "DOMStorageDBThread.h"
+#include "DOMStorageIPC.h"
+#include "DOMStorageManager.h"
+
+#include "nsDOMString.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/unused.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla {
+namespace dom {
+
+#define DOM_STORAGE_CACHE_KEEP_ALIVE_TIME_MS 20000
+
+// static
+DOMStorageDBBridge* DOMStorageCache::sDatabase = nullptr;
+
+namespace { // anon
+
+const uint32_t kDefaultSet = 0;
+const uint32_t kPrivateSet = 1;
+const uint32_t kSessionSet = 2;
+
+inline uint32_t
+GetDataSetIndex(bool aPrivate, bool aSessionOnly)
+{
+  if (aPrivate) {
+    return kPrivateSet;
+  }
+
+  if (aSessionOnly) {
+    return kSessionSet;
+  }
+
+  return kDefaultSet;
+}
+
+inline uint32_t
+GetDataSetIndex(const DOMStorage* aStorage)
+{
+  return GetDataSetIndex(aStorage->IsPrivate(), aStorage->IsSessionOnly());
+}
+
+} // anon
+
+// DOMStorageCacheBridge
+
+NS_IMPL_THREADSAFE_ADDREF(DOMStorageCacheBridge)
+
+// Since there is no consumer of return value of Release, we can turn this 
+// method to void to make implementation of asynchronous DOMStorageCache::Release
+// much simpler.
+NS_IMETHODIMP_(void) DOMStorageCacheBridge::Release(void)
+{
+  MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
+  nsrefcnt count = NS_AtomicDecrementRefcnt(mRefCnt);
+  NS_LOG_RELEASE(this, count, "DOMStorageCacheBridge");
+  if (0 == count) {
+    mRefCnt = 1; /* stabilize */
+    /* enable this to find non-threadsafe destructors: */
+    /* NS_ASSERT_OWNINGTHREAD(_class); */
+    delete (this);
+  }
+}
+
+// DOMStorageCache
+
+DOMStorageCache::DOMStorageCache(const nsACString* aScope)
+: mManager(nullptr)
+, mScope(*aScope)
+, mMonitor("DOMStorageCache")
+, mLoaded(false)
+, mLoadResult(NS_OK)
+, mInitialized(false)
+, mSessionOnlyDataSetActive(false)
+, mPreloadTelemetryRecorded(false)
+{
+  MOZ_COUNT_CTOR(DOMStorageCache);
+}
+
+DOMStorageCache::~DOMStorageCache()
+{
+  if (mManager) {
+    mManager->DropCache(this);
+  }
+
+  MOZ_COUNT_DTOR(DOMStorageCache);
+}
+
+NS_IMETHODIMP_(void)
+DOMStorageCache::Release(void)
+{
+  // We must actually release on the main thread since the cache removes it
+  // self from the manager's hash table.  And we don't want to lock access to
+  // that hash table.
+  if (NS_IsMainThread()) {
+    DOMStorageCacheBridge::Release();
+    return;
+  }
+
+  nsRefPtr<nsRunnableMethod<DOMStorageCacheBridge, void, false> > event =
+    NS_NewNonOwningRunnableMethod(static_cast<DOMStorageCacheBridge*>(this),
+                                  &DOMStorageCacheBridge::Release);
+
+  nsresult rv = NS_DispatchToMainThread(event);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("DOMStorageCache::Release() on a non-main thread");
+    DOMStorageCacheBridge::Release();
+  }
+}
+
+void
+DOMStorageCache::Init(DOMStorageManager* aManager,
+                      bool aPersistent,
+                      nsIPrincipal* aPrincipal,
+                      const nsACString& aQuotaScope)
+{
+  if (mInitialized) {
+    return;
+  }
+
+  mManager = aManager;
+  mInitialized = true;
+  mPrincipal = aPrincipal;
+  mPersistent = aPersistent;
+  mQuotaScope = aQuotaScope.IsEmpty() ? mScope : aQuotaScope;
+
+  if (mPersistent) {
+    Preload();
+  }
+}
+
+inline bool
+DOMStorageCache::Persist(const DOMStorage* aStorage) const
+{
+  return mPersistent &&
+         !aStorage->IsSessionOnly() &&
+         !aStorage->IsPrivate();
+}
+
+namespace { // anon
+
+PLDHashOperator
+CloneSetData(const nsAString& aKey, const nsString aValue, void* aArg)
+{
+  DOMStorageCache::Data* target = static_cast<DOMStorageCache::Data*>(aArg);
+  target->mKeys.Put(aKey, aValue);
+
+  return PL_DHASH_NEXT;
+}
+
+} // anon
+
+DOMStorageCache::Data&
+DOMStorageCache::DataSet(const DOMStorage* aStorage)
+{
+  uint32_t index = GetDataSetIndex(aStorage);
+
+  if (index == kSessionSet && !mSessionOnlyDataSetActive) {
+    // Session only data set is demanded but not filled with
+    // current data set, copy to session only set now.
+
+    WaitForPreload(Telemetry::LOCALDOMSTORAGE_SESSIONONLY_PRELOAD_BLOCKING_MS);
+
+    Data& defaultSet = mData[kDefaultSet];
+    Data& sessionSet = mData[kSessionSet];
+
+    defaultSet.mKeys.EnumerateRead(CloneSetData, &sessionSet);
+
+    mSessionOnlyDataSetActive = true;
+
+    // This updates sessionSet.mOriginQuotaUsage and also updates global usage
+    // for all session only data
+    ProcessUsageDelta(kSessionSet, defaultSet.mOriginQuotaUsage);
+  }
+
+  return mData[index];
+}
+
+bool
+DOMStorageCache::ProcessUsageDelta(const DOMStorage* aStorage, int64_t aDelta)
+{
+  return ProcessUsageDelta(GetDataSetIndex(aStorage), aDelta);
+}
+
+bool
+DOMStorageCache::ProcessUsageDelta(uint32_t aGetDataSetIndex, const int64_t aDelta)
+{
+  // Check limit per this origin
+  Data& data = mData[aGetDataSetIndex];
+  uint64_t newOriginUsage = data.mOriginQuotaUsage + aDelta;
+  if (aDelta > 0 && newOriginUsage > DOMStorageManager::GetQuota()) {
+    return false;
+  }
+
+  // Now check eTLD+1 limit
+  GetDatabase();
+  if (sDatabase) {
+    DOMStorageUsage* usage = sDatabase->GetScopeUsage(mQuotaScope);
+    if (!usage->CheckAndSetETLD1UsageDelta(aGetDataSetIndex, aDelta)) {
+      return false;
+    }
+  }
+
+  // Update size in our data set
+  data.mOriginQuotaUsage = newOriginUsage;
+  return true;
+}
+
+void
+DOMStorageCache::Preload()
+{
+  if (mLoaded || !mPersistent) {
+    return;
+  }
+
+  if (!StartDatabase()) {
+    mLoaded = true;
+    mLoadResult = NS_ERROR_FAILURE;
+    return;
+  }
+
+  sDatabase->AsyncPreload(this);
+  sDatabase->GetScopeUsage(mQuotaScope);
+}
+
+namespace { // anon
+
+// This class is passed to timer as a tick observer.  It refers the cache
+// and keeps it alive for a time.
+class DOMStorageCacheHolder : public nsITimerCallback
+{
+  NS_DECL_ISUPPORTS
+
+  NS_IMETHODIMP
+  Notify(nsITimer* aTimer)
+  {
+    mCache = nullptr;
+    return NS_OK;
+  }
+
+  virtual ~DOMStorageCacheHolder() {}
+
+  nsRefPtr<DOMStorageCache> mCache;
+
+public:
+  DOMStorageCacheHolder(DOMStorageCache* aCache) : mCache(aCache) {}
+};
+
+NS_IMPL_ISUPPORTS1(DOMStorageCacheHolder, nsITimerCallback)
+
+} // anon
+
+void
+DOMStorageCache::KeepAlive()
+{
+  // Missing reference back to the manager means the cache is not responsible
+  // for its lifetime.  Used for keeping sessionStorage live forever.
+  if (!mManager) {
+    return;
+  }
+
+  if (!NS_IsMainThread()) {
+    // Timer and the holder must be initialized on the main thread.
+    nsRefPtr<nsRunnableMethod<DOMStorageCache> > event =
+      NS_NewRunnableMethod(this, &DOMStorageCache::KeepAlive);
+
+    NS_DispatchToMainThread(event);
+    return;
+  }
+
+  nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1");
+  if (!timer) {
+    return;
+  }
+
+  nsRefPtr<DOMStorageCacheHolder> holder = new DOMStorageCacheHolder(this);
+  timer->InitWithCallback(holder, DOM_STORAGE_CACHE_KEEP_ALIVE_TIME_MS,
+                          nsITimer::TYPE_ONE_SHOT);
+
+  mKeepAliveTimer.swap(timer);
+}
+
+namespace { // anon
+
+// The AutoTimer provided by telemetry headers is only using static,
+// i.e. compile time known ID, but here we know the ID only at run time.
+// Hence a new class.
+class TelemetryAutoTimer
+{
+public:
+  TelemetryAutoTimer(Telemetry::ID aId)
+    : id(aId), start(TimeStamp::Now()) {}
+  ~TelemetryAutoTimer()
+    { Telemetry::AccumulateDelta_impl<Telemetry::Millisecond>::compute(id, start); }
+private:
+  Telemetry::ID id;
+  const TimeStamp start;
+};
+
+} // anon
+
+void
+DOMStorageCache::WaitForPreload(Telemetry::ID aTelemetryID)
+{
+  if (!mPersistent) {
+    return;
+  }
+
+  bool loaded = mLoaded;
+
+  // Telemetry of rates of pending preloads
+  if (!mPreloadTelemetryRecorded) {
+    mPreloadTelemetryRecorded = true;
+    Telemetry::Accumulate(
+      Telemetry::LOCALDOMSTORAGE_PRELOAD_PENDING_ON_FIRST_ACCESS,
+      !loaded);
+  }
+
+  if (loaded) {
+    return;
+  }
+
+  // Measure which operation blocks and for how long
+  TelemetryAutoTimer timer(aTelemetryID);
+
+  // If preload already started (i.e. we got some first data, but not all)
+  // SyncPreload will just wait for it to finish rather then synchronously
+  // read from the database.  It seems to me more optimal.
+
+  // TODO place for A/B testing (force main thread load vs. let preload finish)
+  sDatabase->SyncPreload(this);
+}
+
+nsresult
+DOMStorageCache::GetLength(const DOMStorage* aStorage, uint32_t* aRetval)
+{
+  Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_GETLENGTH_MS> autoTimer;
+
+  if (Persist(aStorage)) {
+    WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETLENGTH_BLOCKING_MS);
+    if (NS_FAILED(mLoadResult)) {
+      return mLoadResult;
+    }
+  }
+
+  *aRetval = DataSet(aStorage).mKeys.Count();
+  return NS_OK;
+}
+
+namespace { // anon
+
+class IndexFinderData
+{
+public:
+  IndexFinderData(uint32_t aIndex, nsAString& aRetval)
+    : mIndex(aIndex), mKey(aRetval)
+  {
+    mKey.SetIsVoid(true);
+  }
+
+  uint32_t mIndex;
+  nsAString& mKey;
+};
+
+PLDHashOperator
+FindKeyOrder(const nsAString& aKey, const nsString aValue, void* aArg)
+{
+  IndexFinderData* data = static_cast<IndexFinderData*>(aArg);
+
+  if (data->mIndex--) {
+    return PL_DHASH_NEXT;
+  }
+
+  data->mKey = aKey;
+  return PL_DHASH_STOP;
+}
+
+} // anon
+
+nsresult
+DOMStorageCache::GetKey(const DOMStorage* aStorage, uint32_t aIndex, nsAString& aRetval)
+{
+  // XXX: This does a linear search for the key at index, which would
+  // suck if there's a large numer of indexes. Do we care? If so,
+  // maybe we need to have a lazily populated key array here or
+  // something?
+  Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_GETKEY_MS> autoTimer;
+
+  if (Persist(aStorage)) {
+    WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETKEY_BLOCKING_MS);
+    if (NS_FAILED(mLoadResult)) {
+      return mLoadResult;
+    }
+  }
+
+  IndexFinderData data(aIndex, aRetval);
+  DataSet(aStorage).mKeys.EnumerateRead(FindKeyOrder, &data);
+  return NS_OK;
+}
+
+namespace { // anon
+
+static PLDHashOperator
+KeysArrayBuilder(const nsAString& aKey, const nsString aValue, void* aArg)
+{
+  nsTArray<nsString>* keys = static_cast<nsTArray<nsString>* >(aArg);
+
+  keys->AppendElement(aKey);
+  return PL_DHASH_NEXT;
+}
+
+} // anon
+
+nsTArray<nsString>*
+DOMStorageCache::GetKeys(const DOMStorage* aStorage)
+{
+  Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_GETALLKEYS_MS> autoTimer;
+
+  if (Persist(aStorage)) {
+    WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETALLKEYS_BLOCKING_MS);
+  }
+
+  nsTArray<nsString>* result = new nsTArray<nsString>();
+  if (NS_SUCCEEDED(mLoadResult)) {
+    DataSet(aStorage).mKeys.EnumerateRead(KeysArrayBuilder, result);
+  }
+
+  return result;
+}
+
+nsresult
+DOMStorageCache::GetItem(const DOMStorage* aStorage, const nsAString& aKey,
+                         nsAString& aRetval)
+{
+  Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_GETVALUE_MS> autoTimer;
+
+  if (Persist(aStorage)) {
+    WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETVALUE_BLOCKING_MS);
+    if (NS_FAILED(mLoadResult)) {
+      return mLoadResult;
+    }
+  }
+
+  // not using AutoString since we don't want to copy buffer to result
+  nsString value;
+  if (!DataSet(aStorage).mKeys.Get(aKey, &value)) {
+    SetDOMStringToNull(value);
+  }
+
+  aRetval = value;
+
+  return NS_OK;
+}
+
+nsresult
+DOMStorageCache::SetItem(const DOMStorage* aStorage, const nsAString& aKey,
+                         const nsString& aValue, nsString& aOld)
+{
+  Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_SETVALUE_MS> autoTimer;
+
+  if (Persist(aStorage)) {
+    WaitForPreload(Telemetry::LOCALDOMSTORAGE_SETVALUE_BLOCKING_MS);
+    if (NS_FAILED(mLoadResult)) {
+      return mLoadResult;
+    }
+  }
+
+  Data& data = DataSet(aStorage);
+  if (!data.mKeys.Get(aKey, &aOld)) {
+    SetDOMStringToNull(aOld);
+  }
+
+  // Check the quota first
+  const int64_t delta = static_cast<int64_t>(aValue.Length()) -
+                        static_cast<int64_t>(aOld.Length());
+  if (!ProcessUsageDelta(aStorage, delta)) {
+    return NS_ERROR_DOM_QUOTA_REACHED;
+  }
+
+  if (aValue == aOld && DOMStringIsNull(aValue) == DOMStringIsNull(aOld)) {
+    return NS_SUCCESS_DOM_NO_OPERATION;
+  }
+
+  data.mKeys.Put(aKey, aValue);
+
+  if (Persist(aStorage)) {
+    if (DOMStringIsNull(aOld)) {
+      return sDatabase->AsyncAddItem(this, aKey, aValue);
+    }
+
+    return sDatabase->AsyncUpdateItem(this, aKey, aValue);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+DOMStorageCache::RemoveItem(const DOMStorage* aStorage, const nsAString& aKey,
+                            nsString& aOld)
+{
+  Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_REMOVEKEY_MS> autoTimer;
+
+  if (Persist(aStorage)) {
+    WaitForPreload(Telemetry::LOCALDOMSTORAGE_REMOVEKEY_BLOCKING_MS);
+    if (NS_FAILED(mLoadResult)) {
+      return mLoadResult;
+    }
+  }
+
+  Data& data = DataSet(aStorage);
+  if (!data.mKeys.Get(aKey, &aOld)) {
+    SetDOMStringToNull(aOld);
+    return NS_SUCCESS_DOM_NO_OPERATION;
+  }
+
+  // Recalculate the cached data size
+  const int64_t delta = -(static_cast<int64_t>(aOld.Length()));
+  unused << ProcessUsageDelta(aStorage, delta);
+  data.mKeys.Remove(aKey);
+
+  if (Persist(aStorage)) {
+    return sDatabase->AsyncRemoveItem(this, aKey);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+DOMStorageCache::Clear(const DOMStorage* aStorage)
+{
+  Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_CLEAR_MS> autoTimer;
+
+  bool refresh = false;
+  if (Persist(aStorage)) {
+    // We need to preload all data (know the size) before we can proceeed
+    // to correctly decrease cached usage number.
+    // XXX as in case of unload, this is not technically needed now, but
+    // after super-scope quota introduction we have to do this.  Get telemetry
+    // right now.
+    WaitForPreload(Telemetry::LOCALDOMSTORAGE_CLEAR_BLOCKING_MS);
+    if (NS_FAILED(mLoadResult)) {
+      // When we failed to load data from the database, force delete of the
+      // scope data and make use of the storage possible again.
+      refresh = true;
+      mLoadResult = NS_OK;
+    }
+  }
+
+  Data& data = DataSet(aStorage);
+  bool hadData = !!data.mKeys.Count();
+
+  if (hadData) {
+    unused << ProcessUsageDelta(aStorage, -data.mOriginQuotaUsage);
+    data.mKeys.Clear();
+  }
+
+  if (Persist(aStorage) && (refresh || hadData)) {
+    return sDatabase->AsyncClear(this);
+  }
+
+  return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
+}
+
+void
+DOMStorageCache::CloneFrom(const DOMStorageCache* aThat)
+{
+  mLoaded = aThat->mLoaded;
+  mInitialized = aThat->mInitialized;
+  mPersistent = aThat->mPersistent;
+  mSessionOnlyDataSetActive = aThat->mSessionOnlyDataSetActive;
+
+  for (uint32_t i = 0; i < kDataSetCount; ++i) {
+    aThat->mData[i].mKeys.EnumerateRead(CloneSetData, &mData[i]);
+    ProcessUsageDelta(i, aThat->mData[i].mOriginQuotaUsage);
+  }
+}
+
+// Defined in DOMStorageManager.cpp
+extern bool
+PrincipalsEqual(nsIPrincipal* aObjectPrincipal, nsIPrincipal* aSubjectPrincipal);
+
+bool
+DOMStorageCache::CheckPrincipal(nsIPrincipal* aPrincipal) const
+{
+  return PrincipalsEqual(mPrincipal, aPrincipal);
+}
+
+void
+DOMStorageCache::UnloadItems(uint32_t aUnloadFlags)
+{
+  if (aUnloadFlags & kUnloadDefault) {
+    // Must wait for preload to pass correct usage to ProcessUsageDelta
+    // XXX this is not technically needed right now since there is just
+    // per-origin isolated quota handling, but when we introduce super-
+    // -scope quotas, we have to do this.  Better to start getting
+    // telemetry right now.
+    WaitForPreload(Telemetry::LOCALDOMSTORAGE_UNLOAD_BLOCKING_MS);
+
+    mData[kDefaultSet].mKeys.Clear();
+    ProcessUsageDelta(kDefaultSet, -mData[kDefaultSet].mOriginQuotaUsage);
+  }
+
+  if (aUnloadFlags & kUnloadPrivate) {
+    mData[kPrivateSet].mKeys.Clear();
+    ProcessUsageDelta(kPrivateSet, -mData[kPrivateSet].mOriginQuotaUsage);
+  }
+
+  if (aUnloadFlags & kUnloadSession) {
+    mData[kSessionSet].mKeys.Clear();
+    ProcessUsageDelta(kSessionSet, -mData[kSessionSet].mOriginQuotaUsage);
+    mSessionOnlyDataSetActive = false;
+  }
+
+#ifdef DOM_STORAGE_TESTS
+  if (aUnloadFlags & kTestReload) {
+    WaitForPreload(Telemetry::LOCALDOMSTORAGE_UNLOAD_BLOCKING_MS);
+
+    mData[kDefaultSet].mKeys.Clear();
+    mLoaded = false; // This is only used in testing code
+    Preload();
+  }
+#endif
+}
+
+// DOMStorageCacheBridge
+
+uint32_t
+DOMStorageCache::LoadedCount()
+{
+  MonitorAutoLock monitor(mMonitor);
+  Data& data = mData[kDefaultSet];
+  return data.mKeys.Count();
+}
+
+bool
+DOMStorageCache::LoadItem(const nsAString& aKey, const nsString& aValue)
+{
+  MonitorAutoLock monitor(mMonitor);
+  if (mLoaded) {
+    return false;
+  }
+
+  Data& data = mData[kDefaultSet];
+  if (data.mKeys.Get(aKey, nullptr)) {
+    return true; // don't stop, just don't override
+  }
+
+  data.mKeys.Put(aKey, aValue);
+  data.mOriginQuotaUsage += aKey.Length() + aValue.Length();
+  return true;
+}
+
+void
+DOMStorageCache::LoadDone(nsresult aRv)
+{
+  // Keep the preloaded cache alive for a time
+  KeepAlive();
+
+  MonitorAutoLock monitor(mMonitor);
+  mLoadResult = aRv;
+  mLoaded = true;
+  monitor.Notify();
+}
+
+void
+DOMStorageCache::LoadWait()
+{
+  MonitorAutoLock monitor(mMonitor);
+  while (!mLoaded) {
+    monitor.Wait();
+  }
+}
+
+// DOMStorageUsage
+
+DOMStorageUsage::DOMStorageUsage(const nsACString& aScope)
+  : mScope(aScope)
+{
+  mUsage[kDefaultSet] = mUsage[kPrivateSet] = mUsage[kSessionSet] = 0LL;
+}
+
+namespace { // anon
+
+class LoadUsageRunnable : public nsRunnable
+{
+public:
+  LoadUsageRunnable(int64_t* aUsage, const int64_t aDelta)
+    : mTarget(aUsage)
+    , mDelta(aDelta)
+  {}
+
+private:
+  int64_t* mTarget;
+  int64_t mDelta;
+
+  NS_IMETHOD Run() { *mTarget = mDelta; return NS_OK; }
+};
+
+} // anon
+
+void
+DOMStorageUsage::LoadUsage(const int64_t aUsage)
+{
+  // Using kDefaultSet index since it is the index for the persitent data
+  // stored in the database we have just loaded usage for.
+  if (!NS_IsMainThread()) {
+    // In single process scenario we get this call from the DB thread
+    nsRefPtr<LoadUsageRunnable> r =
+      new LoadUsageRunnable(mUsage + kDefaultSet, aUsage);
+    NS_DispatchToMainThread(r);
+  } else {
+    // On a child process we get this on the main thread already
+    mUsage[kDefaultSet] += aUsage;
+  }
+}
+
+bool
+DOMStorageUsage::CheckAndSetETLD1UsageDelta(uint32_t aDataSetIndex, const int64_t aDelta)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  int64_t newUsage = mUsage[aDataSetIndex] + aDelta;
+  if (aDelta > 0 && newUsage > DOMStorageManager::GetQuota()) {
+    return false;
+  }
+
+  mUsage[aDataSetIndex] = newUsage;
+  return true;
+}
+
+
+// static
+DOMStorageDBBridge*
+DOMStorageCache::StartDatabase()
+{
+  if (sDatabase) {
+    return sDatabase;
+  }
+
+  if (XRE_GetProcessType() == GeckoProcessType_Default) {
+    nsAutoPtr<DOMStorageDBThread> db(new DOMStorageDBThread());
+
+    nsresult rv = db->Init();
+    if (NS_FAILED(rv)) {
+      return nullptr;
+    }
+
+    sDatabase = db.forget();
+  } else {
+    nsRefPtr<DOMStorageDBChild> db = new DOMStorageDBChild(
+        DOMLocalStorageManager::Self());
+
+    nsresult rv = db->Init();
+    if (NS_FAILED(rv)) {
+      return nullptr;
+    }
+
+    db.forget(&sDatabase);
+  }
+
+  return sDatabase;
+}
+
+// static
+DOMStorageDBBridge*
+DOMStorageCache::GetDatabase()
+{
+  return sDatabase;
+}
+
+// static
+nsresult
+DOMStorageCache::StopDatabase()
+{
+  if (!sDatabase) {
+    return NS_OK;
+  }
+
+  nsresult rv = sDatabase->Shutdown();
+  if (XRE_GetProcessType() == GeckoProcessType_Default) {
+    delete sDatabase;
+  } else {
+    DOMStorageDBChild* child = static_cast<DOMStorageDBChild*>(sDatabase);
+    NS_RELEASE(child);
+  }
+
+  sDatabase = nullptr;
+  return rv;
+}
+
+} // ::dom
+} // ::mozilla
new file mode 100644
--- /dev/null
+++ b/dom/src/storage/DOMStorageCache.h
@@ -0,0 +1,253 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef nsDOMStorageCache_h___
+#define nsDOMStorageCache_h___
+
+#include "nsIPrincipal.h"
+#include "nsITimer.h"
+
+#include "nsString.h"
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/Telemetry.h"
+#include "nsAutoPtr.h"
+
+namespace mozilla {
+namespace dom {
+
+class DOMStorage;
+class DOMStorageManager;
+class DOMStorageDBBridge;
+
+// Interface class on which only the database or IPC may call.
+// Used to populate the cache with DB data.
+class DOMStorageCacheBridge
+{
+public:
+  NS_IMETHOD_(nsrefcnt) AddRef(void);
+  NS_IMETHOD_(void) Release(void);
+
+  virtual ~DOMStorageCacheBridge() {}
+
+  // The scope (origin) in the database usage format (reversed)
+  virtual const nsCString& Scope() const = 0;
+
+  // Whether the cache is already fully loaded
+  virtual bool Loaded() = 0;
+
+  // How many items has so far been loaded into the cache, used
+  // for optimization purposes
+  virtual uint32_t LoadedCount() = 0;
+
+  // Called by the database to load a key and its value to the cache
+  virtual bool LoadItem(const nsAString& aKey, const nsString& aValue) = 0;
+
+  // Called by the database after all keys and values has been loaded
+  // to this cache
+  virtual void LoadDone(nsresult aRv) = 0;
+
+  // Use to synchronously wait until the cache gets fully loaded with data,
+  // this method exits after LoadDone has been called
+  virtual void LoadWait() = 0;
+
+protected:
+  nsAutoRefCnt mRefCnt;
+};
+
+// Implementation of scope cache that is responsible for preloading data
+// for persistent storage (localStorage) and hold data for non-private,
+// private and session-only cookie modes.  It is also responsible for
+// persisting data changes using the database, works as a write-back cache.
+class DOMStorageCache : public DOMStorageCacheBridge
+{
+public:
+  NS_IMETHOD_(void) Release(void);
+
+  DOMStorageCache(const nsACString* aScope);
+  virtual ~DOMStorageCache();
+
+  void Init(DOMStorageManager* aManager, bool aPersistent, nsIPrincipal* aPrincipal,
+            const nsACString& aQuotaScope);
+
+  // Copies all data from the other storage.
+  void CloneFrom(const DOMStorageCache* aThat);
+
+  // Starts async preload of this cache if it persistent and not loaded.
+  void Preload();
+
+  // Keeps the cache alive (i.e. present in the manager's hash table) for a time.
+  void KeepAlive();
+
+  // The set of methods that are invoked by DOM storage web API.
+  // We are passing the DOMStorage object just to let the cache
+  // read properties like mPrivate and mSessionOnly.
+  // Get* methods return error when load from the database has failed.
+  nsresult GetLength(const DOMStorage* aStorage, uint32_t* aRetval);
+  nsresult GetKey(const DOMStorage* aStorage, uint32_t index, nsAString& aRetval);
+  nsresult GetItem(const DOMStorage* aStorage, const nsAString& aKey, nsAString& aRetval);
+  nsresult SetItem(const DOMStorage* aStorage, const nsAString& aKey, const nsString& aValue, nsString& aOld);
+  nsresult RemoveItem(const DOMStorage* aStorage, const nsAString& aKey, nsString& aOld);
+  nsresult Clear(const DOMStorage* aStorage);
+
+  nsTArray<nsString>* GetKeys(const DOMStorage* aStorage);
+
+  // Whether the principal equals principal the cache was created for
+  bool CheckPrincipal(nsIPrincipal* aPrincipal) const;
+  nsIPrincipal* Principal() const { return mPrincipal; }
+
+  // Starts the database engine thread or the IPC bridge
+  static DOMStorageDBBridge* StartDatabase();
+  static DOMStorageDBBridge* GetDatabase();
+
+  // Stops the thread and flushes all uncommited data
+  static nsresult StopDatabase();
+
+  // DOMStorageCacheBridge
+
+  virtual const nsCString& Scope() const { return mScope; }
+  virtual bool Loaded() { return mLoaded; }
+  virtual uint32_t LoadedCount();
+  virtual bool LoadItem(const nsAString& aKey, const nsString& aValue);
+  virtual void LoadDone(nsresult aRv);
+  virtual void LoadWait();
+
+  // Cache keeps 3 sets of data: regular, private and session-only.
+  // This class keeps keys and values for a set and also caches
+  // size of the data for quick per-origin quota checking.
+  class Data
+  {
+  public:
+    Data() : mOriginQuotaUsage(0) { mKeys.Init(); }
+    int64_t mOriginQuotaUsage;
+    nsDataHashtable<nsStringHashKey, nsString> mKeys;
+  };
+
+public:
+  // Number of data sets we keep: default, private, session
+  static const uint32_t kDataSetCount = 3;
+
+private:
+  // API to clear the cache data, this is invoked by chrome operations
+  // like cookie deletion.
+  friend class DOMStorageManager;
+
+  static const uint32_t kUnloadDefault = 1 << 0;
+  static const uint32_t kUnloadPrivate = 1 << 1;
+  static const uint32_t kUnloadSession = 1 << 2;
+  static const uint32_t kUnloadComplete =
+    kUnloadDefault | kUnloadPrivate | kUnloadSession;
+
+#ifdef DOM_STORAGE_TESTS
+  static const uint32_t kTestReload    = 1 << 15;
+#endif
+
+  void UnloadItems(uint32_t aUnloadFlags);
+
+private:
+  // Synchronously blocks until the cache is fully loaded from the database
+  void WaitForPreload(mozilla::Telemetry::ID aTelemetryID);
+
+  // Helper to get one of the 3 data sets (regular, private, session)
+  Data& DataSet(const DOMStorage* aStorage);
+
+  // Whether the storage change is about to persist
+  bool Persist(const DOMStorage* aStorage) const;
+
+  // Changes the quota usage on the given data set if it fits the quota.
+  // If not, then false is returned and no change to the set must be done.
+  bool ProcessUsageDelta(uint32_t aGetDataSetIndex, const int64_t aDelta);
+  bool ProcessUsageDelta(const DOMStorage* aStorage, const int64_t aDelta);
+
+private:
+  // When a cache is reponsible for its life time (in case of localStorage data
+  // cache) we need to refer our manager since removal of the cache from the hash
+  // table is handled in the destructor by call to the manager.
+  // Cache could potentially overlive the manager, hence the hard ref.
+  nsRefPtr<DOMStorageManager> mManager;
+
+  // Timer that holds this cache alive for a while after it has been preloaded.
+  nsCOMPtr<nsITimer> mKeepAliveTimer;
+
+  // Principal the cache has been initially created for, this is used only
+  // for sessionStorage access checks since sessionStorage objects are strictly
+  // scoped by a principal.  localStorage objects on the other hand are scoped by
+  // origin only.
+  nsCOMPtr<nsIPrincipal> mPrincipal;
+
+  // The scope this cache belongs to in the "DB format", i.e. reversed
+  nsCString mScope;
+
+  // The eTLD+1 scope used to count quota usage.
+  nsCString mQuotaScope;
+
+  // Non-private Browsing, Private Browsing and Session Only sets.
+  Data mData[kDataSetCount];
+
+  // This monitor is used to wait for full load of data.
+  mozilla::Monitor mMonitor;
+
+  // Flag that is initially false.  When the cache is about to work with
+  // the database (i.e. it is persistent) this flags is set to true after
+  // all keys and coresponding values are loaded from the database.
+  // This flag never goes from true back to false.
+  bool mLoaded;
+
+  // Result of load from the database.  Valid after mLoaded flag has been set.
+  nsresult mLoadResult;
+
+  // Init() method has been called
+  bool mInitialized : 1;
+
+  // This cache is about to be bound with the database (i.e. it has
+  // to load from the DB first and has to persist when modifying the
+  // default data set.)
+  bool mPersistent : 1;
+
+  // - False when the session-only data set was never used.
+  // - True after access to session-only data has been made for the first time.
+  // We also fill session-only data set with the default one at that moment.
+  // Drops back to false when session-only data are cleared from chrome.
+  bool mSessionOnlyDataSetActive : 1;
+
+  // Whether we have already captured state of the cache preload on our first access.
+  bool mPreloadTelemetryRecorded : 1;
+
+  // DOMStorageDBThread on the parent or single process,
+  // DOMStorageDBChild on the child process.
+  static DOMStorageDBBridge* sDatabase;
+};
+
+// DOMStorageUsage
+// Infrastructure to manage and check eTLD+1 quota
+class DOMStorageUsageBridge
+{
+public:
+  virtual ~DOMStorageUsageBridge() {}
+
+  virtual const nsCString& Scope() = 0;
+  virtual void LoadUsage(const int64_t aUsage) = 0;
+};
+
+class DOMStorageUsage : public DOMStorageUsageBridge
+{
+public:
+  DOMStorageUsage(const nsACString& aScope);
+
+  bool CheckAndSetETLD1UsageDelta(uint32_t aDataSetIndex, int64_t aUsageDelta);
+
+private:
+  virtual const nsCString& Scope() { return mScope; }
+  virtual void LoadUsage(const int64_t aUsage);
+
+  nsCString mScope;
+  int64_t mUsage[DOMStorageCache::kDataSetCount];
+};
+
+} // ::dom
+} // ::mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/src/storage/DOMStorageDBThread.cpp
@@ -0,0 +1,1341 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "DOMStorageDBThread.h"
+#include "DOMStorageCache.h"
+
+#include "nsIEffectiveTLDService.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+#include "mozStorageCID.h"
+#include "mozStorageHelper.h"
+#include "mozIStorageService.h"
+#include "mozIStorageBindingParamsArray.h"
+#include "mozIStorageBindingParams.h"
+#include "mozIStorageValueArray.h"
+#include "mozIStorageFunction.h"
+#include "nsIObserverService.h"
+#include "nsIVariant.h"
+#include "mozilla/Services.h"
+
+// How long we collect write oprerations
+// before they are flushed to the database
+// In milliseconds.
+#define FLUSHING_INTERVAL_MS 5000
+
+// Write Ahead Log's maximum size is 512KB
+#define MAX_WAL_SIZE_BYTES 512 * 1024
+
+namespace mozilla {
+namespace dom {
+
+DOMStorageDBBridge::DOMStorageDBBridge()
+{
+  mUsages.Init();
+}
+
+DOMStorageUsage*
+DOMStorageDBBridge::GetScopeUsage(const nsACString& aScope)
+{
+  DOMStorageUsage* usage;
+  if (mUsages.Get(aScope, &usage)) {
+    return usage;
+  }
+
+  usage = new DOMStorageUsage(aScope);
+  AsyncGetUsage(usage);
+  mUsages.Put(aScope, usage);
+
+  return usage;
+}
+
+
+DOMStorageDBThread::DOMStorageDBThread()
+: mThread(nullptr)
+, mMonitor("DOMStorageThreadMonitor")
+, mStopIOThread(false)
+, mDBReady(false)
+, mStatus(NS_OK)
+, mWorkerStatements(mWorkerConnection)
+, mReaderStatements(mReaderConnection)
+, mDirtyEpoch(0)
+, mFlushImmediately(false)
+, mPriorityCounter(0)
+{
+  mScopesHavingData.Init();
+}
+
+nsresult
+DOMStorageDBThread::Init()
+{
+  nsresult rv;
+
+  // Need to determine location on the main thread, since
+  // NS_GetSpecialDirectory access the atom table that can
+  // be accessed only on the main thread.
+  rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+                              getter_AddRefs(mDatabaseFile));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mDatabaseFile->Append(NS_LITERAL_STRING("webappsstore.sqlite"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Ensure mozIStorageService init on the main thread first.
+  nsCOMPtr<mozIStorageService> service =
+    do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Need to keep the lock to avoid setting mThread later then
+  // the thread body executes.
+  MonitorAutoLock monitor(mMonitor);
+
+  mThread = PR_CreateThread(PR_USER_THREAD, &DOMStorageDBThread::ThreadFunc, this,
+                            PR_PRIORITY_LOW, PR_LOCAL_THREAD, PR_JOINABLE_THREAD,
+                            262144);
+  if (!mThread) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+DOMStorageDBThread::Shutdown()
+{
+  if (!mThread) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_SHUTDOWN_DATABASE_MS> timer;
+
+  {
+    MonitorAutoLock monitor(mMonitor);
+
+    // After we stop, no other operations can be accepted
+    mFlushImmediately = true;
+    mStopIOThread = true;
+    monitor.Notify();
+  }
+
+  PR_JoinThread(mThread);
+  mThread = nullptr;
+
+  return mStatus;
+}
+
+void
+DOMStorageDBThread::SyncPreload(DOMStorageCacheBridge* aCache, bool aForceSync)
+{
+  if (!aForceSync && aCache->LoadedCount()) {
+    // Preload already started for this cache, just wait for it to finish.
+    // LoadWait will exit after LoadDone on the cache has been called.
+    SetHigherPriority();
+    aCache->LoadWait();
+    SetDefaultPriority();
+    return;
+  }
+
+  // Bypass sync load when an update is pending in the queue to write, we would
+  // get incosistent data in the cache.  Also don't allow sync main-thread preload
+  // when DB open and init is still pending on the background thread.
+  if (mWALModeEnabled && mDBReady) {
+    bool pendingTasks;
+    {
+      MonitorAutoLock monitor(mMonitor);
+      pendingTasks = mPendingTasks.IsScopeUpdatePending(aCache->Scope()) ||
+                     mPendingTasks.IsScopeClearPending(aCache->Scope());
+    }
+
+    if (!pendingTasks) {
+      // WAL is enabled, thus do the load synchronously on the main thread.
+      DBOperation preload(DBOperation::opPreload, aCache);
+      preload.PerformAndFinalize(this);
+      return;
+    }
+  }
+
+  // Need to go asynchronously since WAL is not allowed or scheduled updates
+  // need to be flushed first.
+  // Schedule preload for this cache as the first operation.
+  nsresult rv = InsertDBOp(new DBOperation(DBOperation::opPreloadUrgent, aCache));
+
+  // LoadWait exits after LoadDone of the cache has been called.
+  if (NS_SUCCEEDED(rv)) {
+    aCache->LoadWait();
+  }
+}
+
+void
+DOMStorageDBThread::AsyncFlush()
+{
+  MonitorAutoLock monitor(mMonitor);
+  mFlushImmediately = true;
+  monitor.Notify();
+}
+
+bool
+DOMStorageDBThread::ShouldPreloadScope(const nsACString& aScope)
+{
+  MonitorAutoLock monitor(mMonitor);
+  return mScopesHavingData.Contains(aScope);
+}
+
+namespace { // anon
+
+PLDHashOperator
+GetScopesHavingDataEnum(nsCStringHashKey* aKey, void* aArg)
+{
+  InfallibleTArray<nsCString>* scopes =
+      static_cast<InfallibleTArray<nsCString>*>(aArg);
+  scopes->AppendElement(aKey->GetKey());
+  return PL_DHASH_NEXT;
+}
+
+} // anon
+
+void
+DOMStorageDBThread::GetScopesHavingData(InfallibleTArray<nsCString>* aScopes)
+{
+  MonitorAutoLock monitor(mMonitor);
+  mScopesHavingData.EnumerateEntries(GetScopesHavingDataEnum, aScopes);
+}
+
+nsresult
+DOMStorageDBThread::InsertDBOp(DOMStorageDBThread::DBOperation* aOperation)
+{
+  MonitorAutoLock monitor(mMonitor);
+
+  // Sentinel to don't forget to delete the operation when we exit early.
+  nsAutoPtr<DOMStorageDBThread::DBOperation> opScope(aOperation);
+
+  if (mStopIOThread) {
+    // Thread use after shutdown demanded.
+    MOZ_ASSERT(false);
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  if (NS_FAILED(mStatus)) {
+    MonitorAutoUnlock unlock(mMonitor);
+    aOperation->Finalize(mStatus);
+    return mStatus;
+  }
+
+  switch (aOperation->Type()) {
+  case DBOperation::opPreload:
+  case DBOperation::opPreloadUrgent:
+    if (mPendingTasks.IsScopeUpdatePending(aOperation->Scope())) {
+      // If there is a pending update operation for the scope first do the flush
+      // before we preload the cache.  This may happen in an extremely rare case
+      // when a child process throws away its cache before flush on the parent
+      // has finished.  If we would preloaded the cache as a priority operation 
+      // before the pending flush, we would have got an inconsistent cache content.
+      mFlushImmediately = true;
+    } else if (mPendingTasks.IsScopeClearPending(aOperation->Scope())) {
+      // The scope is scheduled to be cleared, so just quickly load as empty.
+      // We need to do this to prevent load of the DB data before the scope has
+      // actually been cleared from the database.  Preloads are processed
+      // immediately before update and clear operations on the database that
+      // are flushed periodically in batches.
+      MonitorAutoUnlock unlock(mMonitor);
+      aOperation->Finalize(NS_OK);
+      return NS_OK;
+    }
+    // NO BREAK
+
+  case DBOperation::opGetUsage:
+    if (aOperation->Type() == DBOperation::opPreloadUrgent) {
+      SetHigherPriority(); // Dropped back after urgent preload execution
+      mPreloads.InsertElementAt(0, aOperation);
+    } else {
+      mPreloads.AppendElement(aOperation);
+    }
+
+    // DB operation adopted, don't delete it.
+    opScope.forget();
+
+    // Immediately start executing this.
+    monitor.Notify();
+    break;
+
+  default:
+    // Update operations are first collected, coalesced and then flushed
+    // after a short time.
+    mPendingTasks.Add(aOperation);
+
+    // DB operation adopted, don't delete it.
+    opScope.forget();
+
+    ScheduleFlush();
+    break;
+  }
+
+  return NS_OK;
+}
+
+void
+DOMStorageDBThread::SetHigherPriority()
+{
+  ++mPriorityCounter;
+  PR_SetThreadPriority(mThread, PR_PRIORITY_URGENT);
+}
+
+void
+DOMStorageDBThread::SetDefaultPriority()
+{
+  if (--mPriorityCounter <= 0) {
+    PR_SetThreadPriority(mThread, PR_PRIORITY_LOW);
+  }
+}
+
+void
+DOMStorageDBThread::ThreadFunc(void* aArg)
+{
+  PR_SetCurrentThreadName("localStorage DB");
+
+  DOMStorageDBThread* thread = static_cast<DOMStorageDBThread*>(aArg);
+  thread->ThreadFunc();
+}
+
+void
+DOMStorageDBThread::ThreadFunc()
+{
+  nsresult rv = InitDatabase();
+
+  MonitorAutoLock lockMonitor(mMonitor);
+
+  if (NS_FAILED(rv)) {
+    mStatus = rv;
+    mStopIOThread = true;
+    return;
+  }
+
+  while (MOZ_LIKELY(!mStopIOThread || mPreloads.Length() || mPendingTasks.HasTasks())) {
+    if (MOZ_UNLIKELY(TimeUntilFlush() == 0)) {
+      // Flush time is up or flush has been forced, do it now.
+      UnscheduleFlush();
+      if (mPendingTasks.Prepare()) {
+        {
+          MonitorAutoUnlock unlockMonitor(mMonitor);
+          rv = mPendingTasks.Execute(this);
+        }
+
+        if (!mPendingTasks.Finalize(rv)) {
+          mStatus = rv;
+          NS_WARNING("localStorage DB access broken");
+        }
+      }
+      NotifyFlushCompletion();
+    } else if (MOZ_LIKELY(mPreloads.Length())) {
+      nsAutoPtr<DBOperation> op(mPreloads[0]);
+      mPreloads.RemoveElementAt(0);
+      {
+        MonitorAutoUnlock unlockMonitor(mMonitor);
+        op->PerformAndFinalize(this);
+      }
+
+      if (op->Type() == DBOperation::opPreloadUrgent) {
+        SetDefaultPriority(); // urgent preload unscheduled
+      }
+    } else if (MOZ_UNLIKELY(!mStopIOThread)) {
+      lockMonitor.Wait(TimeUntilFlush());
+    }
+  } // thread loop
+
+  mStatus = ShutdownDatabase();
+}
+
+extern void
+ReverseString(const nsCSubstring& aSource, nsCSubstring& aResult);
+
+namespace { // anon
+
+class nsReverseStringSQLFunction MOZ_FINAL : public mozIStorageFunction
+{
+  NS_DECL_ISUPPORTS
+  NS_DECL_MOZISTORAGEFUNCTION
+};
+
+NS_IMPL_ISUPPORTS1(nsReverseStringSQLFunction, mozIStorageFunction)
+
+NS_IMETHODIMP
+nsReverseStringSQLFunction::OnFunctionCall(
+    mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
+{
+  nsresult rv;
+
+  nsAutoCString stringToReverse;
+  rv = aFunctionArguments->GetUTF8String(0, stringToReverse);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsAutoCString result;
+  ReverseString(stringToReverse, result);
+
+  nsCOMPtr<nsIWritableVariant> outVar(do_CreateInstance(
+      NS_VARIANT_CONTRACTID, &rv));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = outVar->SetAsAUTF8String(result);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  *aResult = outVar.get();
+  outVar.forget();
+  return NS_OK;
+}
+
+} // anon
+
+nsresult
+DOMStorageDBThread::OpenDatabaseConnection()
+{
+  nsresult rv;
+
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  nsCOMPtr<mozIStorageService> service
+      = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<mozIStorageConnection> connection;
+  rv = service->OpenUnsharedDatabase(mDatabaseFile, getter_AddRefs(mWorkerConnection));
+  if (rv == NS_ERROR_FILE_CORRUPTED) {
+    // delete the db and try opening again
+    rv = mDatabaseFile->Remove(false);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = service->OpenUnsharedDatabase(mDatabaseFile, getter_AddRefs(mWorkerConnection));
+  }
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+DOMStorageDBThread::InitDatabase()
+{
+  Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_INIT_DATABASE_MS> timer;
+
+  nsresult rv;
+
+  // Here we are on the worker thread. This opens the worker connection.
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  rv = OpenDatabaseConnection();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = TryJournalMode();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Create a read-only clone
+  (void)mWorkerConnection->Clone(true, getter_AddRefs(mReaderConnection));
+  NS_ENSURE_TRUE(mReaderConnection, NS_ERROR_FAILURE);
+
+  mozStorageTransaction transaction(mWorkerConnection, false);
+
+  // Ensure Gecko 1.9.1 storage table
+  rv = mWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+         "CREATE TABLE IF NOT EXISTS webappsstore2 ("
+         "scope TEXT, "
+         "key TEXT, "
+         "value TEXT, "
+         "secure INTEGER, "
+         "owner TEXT)"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        "CREATE UNIQUE INDEX IF NOT EXISTS scope_key_index"
+        " ON webappsstore2(scope, key)"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<mozIStorageFunction> function1(new nsReverseStringSQLFunction());
+  NS_ENSURE_TRUE(function1, NS_ERROR_OUT_OF_MEMORY);
+
+  rv = mWorkerConnection->CreateFunction(NS_LITERAL_CSTRING("REVERSESTRING"), 1, function1);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  bool exists;
+
+  // Check if there is storage of Gecko 1.9.0 and if so, upgrade that storage
+  // to actual webappsstore2 table and drop the obsolete table. First process
+  // this newer table upgrade to priority potential duplicates from older
+  // storage table.
+  rv = mWorkerConnection->TableExists(NS_LITERAL_CSTRING("webappsstore"),
+                                &exists);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (exists) {
+    rv = mWorkerConnection->ExecuteSimpleSQL(
+      NS_LITERAL_CSTRING("INSERT OR IGNORE INTO "
+                         "webappsstore2(scope, key, value, secure, owner) "
+                         "SELECT REVERSESTRING(domain) || '.:', key, value, secure, owner "
+                         "FROM webappsstore"));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = mWorkerConnection->ExecuteSimpleSQL(
+      NS_LITERAL_CSTRING("DROP TABLE webappsstore"));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  // Check if there is storage of Gecko 1.8 and if so, upgrade that storage
+  // to actual webappsstore2 table and drop the obsolete table. Potential
+  // duplicates will be ignored.
+  rv = mWorkerConnection->TableExists(NS_LITERAL_CSTRING("moz_webappsstore"),
+                                &exists);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (exists) {
+    rv = mWorkerConnection->ExecuteSimpleSQL(
+      NS_LITERAL_CSTRING("INSERT OR IGNORE INTO "
+                         "webappsstore2(scope, key, value, secure, owner) "
+                         "SELECT REVERSESTRING(domain) || '.:', key, value, secure, domain "
+                         "FROM moz_webappsstore"));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = mWorkerConnection->ExecuteSimpleSQL(
+      NS_LITERAL_CSTRING("DROP TABLE moz_webappsstore"));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  rv = transaction.Commit();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Database open and all initiation operation are done.  Switching this flag
+  // to true allow main thread to read directly from the database.
+  // If we would allow this sooner, we would have opened a window where main thread
+  // read might operate on a totaly broken and incosistent database.
+  mDBReady = true;
+
+  // List scopes having any stored data
+  nsCOMPtr<mozIStorageStatement> stmt;
+  rv = mWorkerConnection->CreateStatement(NS_LITERAL_CSTRING("SELECT DISTINCT scope FROM webappsstore2"),
+                                    getter_AddRefs(stmt));
+  NS_ENSURE_SUCCESS(rv, rv);
+  mozStorageStatementScoper scope(stmt);
+
+  while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) {
+    nsAutoCString foundScope;
+    rv = stmt->GetUTF8String(0, foundScope);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    MonitorAutoLock monitor(mMonitor);
+    mScopesHavingData.PutEntry(foundScope);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+DOMStorageDBThread::SetJournalMode(bool aIsWal)
+{
+  nsresult rv;
+
+  nsAutoCString stmtString(
+    MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = ");
+  if (aIsWal) {
+    stmtString.AppendLiteral("wal");
+  } else {
+    stmtString.AppendLiteral("truncate");
+  }
+
+  nsCOMPtr<mozIStorageStatement> stmt;
+  rv = mWorkerConnection->CreateStatement(stmtString, getter_AddRefs(stmt));
+  NS_ENSURE_SUCCESS(rv, rv);
+  mozStorageStatementScoper scope(stmt);
+
+  bool hasResult = false;
+  rv = stmt->ExecuteStep(&hasResult);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (!hasResult) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsAutoCString journalMode;
+  rv = stmt->GetUTF8String(0, journalMode);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if ((aIsWal && !journalMode.EqualsLiteral("wal")) ||
+      (!aIsWal && !journalMode.EqualsLiteral("truncate"))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+DOMStorageDBThread::TryJournalMode()
+{
+  nsresult rv;
+
+  rv = SetJournalMode(true);
+  if (NS_FAILED(rv)) {
+    mWALModeEnabled = false;
+
+    rv = SetJournalMode(false);
+    NS_ENSURE_SUCCESS(rv, rv);
+  } else {
+    mWALModeEnabled = true;
+
+    rv = ConfigureWALBehavior();
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+DOMStorageDBThread::ConfigureWALBehavior()
+{
+  // Get the DB's page size
+  nsCOMPtr<mozIStorageStatement> stmt;
+  nsresult rv = mWorkerConnection->CreateStatement(NS_LITERAL_CSTRING(
+    MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size"
+  ), getter_AddRefs(stmt));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  bool hasResult = false;
+  rv = stmt->ExecuteStep(&hasResult);
+  NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_FAILURE);
+
+  int32_t pageSize = 0;
+  rv = stmt->GetInt32(0, &pageSize);
+  NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && pageSize > 0, NS_ERROR_UNEXPECTED);
+
+  // Set the threshold for auto-checkpointing the WAL.
+  // We don't want giant logs slowing down reads & shutdown.
+  int32_t thresholdInPages = static_cast<int32_t>(MAX_WAL_SIZE_BYTES / pageSize);
+  nsAutoCString thresholdPragma("PRAGMA wal_autocheckpoint = ");
+  thresholdPragma.AppendInt(thresholdInPages);
+  rv = mWorkerConnection->ExecuteSimpleSQL(thresholdPragma);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Set the maximum WAL log size to reduce footprint on mobile (large empty
+  // WAL files will be truncated)
+  nsAutoCString journalSizePragma("PRAGMA journal_size_limit = ");
+  // bug 600307: mak recommends setting this to 3 times the auto-checkpoint threshold
+  journalSizePragma.AppendInt(MAX_WAL_SIZE_BYTES * 3);
+  rv = mWorkerConnection->ExecuteSimpleSQL(journalSizePragma);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+DOMStorageDBThread::ShutdownDatabase()
+{
+  // Has to be called on the worker thread.
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  nsresult rv = mStatus;
+
+  mDBReady = false;
+
+  // Finalize the cached statements.
+  mReaderStatements.FinalizeStatements();
+  mWorkerStatements.FinalizeStatements();
+
+  if (mReaderConnection) {
+    // No need to sync access to mReaderConnection since the main thread
+    // is right now joining this thread, unable to execute any events.
+    mReaderConnection->Close();
+    mReaderConnection = nullptr;
+  }
+
+  if (mWorkerConnection) {
+    rv = mWorkerConnection->Close();
+    mWorkerConnection = nullptr;
+  }
+
+  return rv;
+}
+
+void
+DOMStorageDBThread::ScheduleFlush()
+{
+  if (mDirtyEpoch) {
+    return; // Already scheduled
+  }
+
+  mDirtyEpoch = PR_IntervalNow() | 1; // Must be non-zero to indicate we are scheduled
+
+  // Wake the monitor from indefinite sleep...
+  mMonitor.Notify();
+}
+
+void
+DOMStorageDBThread::UnscheduleFlush()
+{
+  // We are just about to do the flush, drop flags
+  mFlushImmediately = false;
+  mDirtyEpoch = 0;
+}
+
+PRIntervalTime
+DOMStorageDBThread::TimeUntilFlush()
+{
+  if (mFlushImmediately) {
+    return 0; // Do it now regardless the timeout.
+  }
+
+  MOZ_STATIC_ASSERT(PR_INTERVAL_NO_TIMEOUT != 0,
+      "PR_INTERVAL_NO_TIMEOUT must be non-zero");
+
+  if (!mDirtyEpoch) {
+    return PR_INTERVAL_NO_TIMEOUT; // No pending task...
+  }
+
+  static const PRIntervalTime kMaxAge = PR_MillisecondsToInterval(FLUSHING_INTERVAL_MS);
+
+  PRIntervalTime now = PR_IntervalNow() | 1;
+  PRIntervalTime age = now - mDirtyEpoch;
+  if (age > kMaxAge) {
+    return 0; // It is time.
+  }
+
+  return kMaxAge - age; // Time left, this is used to sleep the monitor
+}
+
+void
+DOMStorageDBThread::NotifyFlushCompletion()
+{
+#ifdef DOM_STORAGE_TESTS
+  if (!NS_IsMainThread()) {
+    nsRefPtr<nsRunnableMethod<DOMStorageDBThread, void, false> > event =
+      NS_NewNonOwningRunnableMethod(this, &DOMStorageDBThread::NotifyFlushCompletion);
+    NS_DispatchToMainThread(event);
+    return;
+  }
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->NotifyObservers(nullptr, "domstorage-test-flushed", nullptr);
+  }
+#endif
+}
+
+// DOMStorageDBThread::DBOperation
+
+DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType,
+                                             DOMStorageCacheBridge* aCache,
+                                             const nsAString& aKey,
+                                             const nsAString& aValue)
+: mType(aType)
+, mCache(aCache)
+, mKey(aKey)
+, mValue(aValue)
+{
+  MOZ_COUNT_CTOR(DOMStorageDBThread::DBOperation);
+}
+
+DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType,
+                                             DOMStorageUsageBridge* aUsage)
+: mType(aType)
+, mUsage(aUsage)
+{
+  MOZ_COUNT_CTOR(DOMStorageDBThread::DBOperation);
+}
+
+DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType,
+                                             const nsACString& aScope)
+: mType(aType)
+, mCache(nullptr)
+, mScope(aScope)
+{
+  MOZ_COUNT_CTOR(DOMStorageDBThread::DBOperation);
+}
+
+DOMStorageDBThread::DBOperation::~DBOperation()
+{
+  MOZ_COUNT_DTOR(DOMStorageDBThread::DBOperation);
+}
+
+const nsCString
+DOMStorageDBThread::DBOperation::Scope()
+{
+  if (mCache) {
+    return mCache->Scope();
+  }
+
+  return mScope;
+}
+
+const nsCString
+DOMStorageDBThread::DBOperation::Target()
+{
+  switch (mType) {
+    case opAddItem:
+    case opUpdateItem:
+    case opRemoveItem:
+      return Scope() + NS_LITERAL_CSTRING("|") + NS_ConvertUTF16toUTF8(mKey);
+
+    default:
+      return Scope();
+  }
+}
+
+void
+DOMStorageDBThread::DBOperation::PerformAndFinalize(DOMStorageDBThread* aThread)
+{
+  Finalize(Perform(aThread));
+}
+
+nsresult
+DOMStorageDBThread::DBOperation::Perform(DOMStorageDBThread* aThread)
+{
+  nsresult rv;
+
+  switch (mType) {
+  case opPreload:
+  case opPreloadUrgent:
+  {
+    // Already loaded?
+    if (mCache->Loaded()) {
+      break;
+    }
+
+    StatementCache* statements;
+    if (MOZ_UNLIKELY(NS_IsMainThread())) {
+      statements = &aThread->mReaderStatements;
+    } else {
+      statements = &aThread->mWorkerStatements;
+    }
+
+    // OFFSET is an optimization when we have to do a sync load
+    // and cache has already loaded some parts asynchronously.
+    // It skips keys we have already loaded.
+    nsCOMPtr<mozIStorageStatement> stmt = statements->GetCachedStatement(
+        "SELECT key, value FROM webappsstore2 "
+        "WHERE scope = :scope ORDER BY key "
+        "LIMIT -1 OFFSET :offset");
+    NS_ENSURE_STATE(stmt);
+    mozStorageStatementScoper scope(stmt);
+
+    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
+                                    mCache->Scope());
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("offset"),
+                               static_cast<int32_t>(mCache->LoadedCount()));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    bool exists;
+    while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) {
+      nsAutoString key;
+      rv = stmt->GetString(0, key);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      nsAutoString value;
+      rv = stmt->GetString(1, value);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      if (!mCache->LoadItem(key, value)) {
+        break;
+      }
+    }
+
+    mCache->LoadDone(NS_OK);
+    break;
+  }
+
+  case opGetUsage:
+  {
+    nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
+      "SELECT SUM(LENGTH(key) + LENGTH(value)) FROM webappsstore2"
+      " WHERE scope LIKE :scope"
+    );
+    NS_ENSURE_STATE(stmt);
+
+    mozStorageStatementScoper scope(stmt);
+
+    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
+                                    mUsage->Scope() + NS_LITERAL_CSTRING("%"));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    bool exists;
+    rv = stmt->ExecuteStep(&exists);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    int64_t usage;
+    if (exists) {
+      rv = stmt->GetInt64(0, &usage);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    mUsage->LoadUsage(usage);
+    break;
+  }
+
+  case opAddItem:
+  case opUpdateItem:
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
+      "INSERT OR REPLACE INTO webappsstore2 (scope, key, value) "
+      "VALUES (:scope, :key, :value) "
+    );
+    NS_ENSURE_STATE(stmt);
+
+    mozStorageStatementScoper scope(stmt);
+
+    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
+                                    mCache->Scope());
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"),
+                                mKey);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = stmt->BindStringByName(NS_LITERAL_CSTRING("value"),
+                                mValue);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = stmt->Execute();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    aThread->mScopesHavingData.PutEntry(Scope());
+    break;
+  }
+
+  case opRemoveItem:
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
+      "DELETE FROM webappsstore2 "
+      "WHERE scope = :scope "
+        "AND key = :key "
+    );
+    NS_ENSURE_STATE(stmt);
+    mozStorageStatementScoper scope(stmt);
+
+    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
+                                    mCache->Scope());
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"),
+                                mKey);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = stmt->Execute();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    break;
+  }
+
+  case opClear:
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
+      "DELETE FROM webappsstore2 "
+      "WHERE scope = :scope"
+    );
+    NS_ENSURE_STATE(stmt);
+    mozStorageStatementScoper scope(stmt);
+
+    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
+                                    mCache->Scope());
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = stmt->Execute();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    aThread->mScopesHavingData.RemoveEntry(Scope());
+    break;
+  }
+
+  case opClearAll:
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
+      "DELETE FROM webappsstore2"
+    );
+    NS_ENSURE_STATE(stmt);
+    mozStorageStatementScoper scope(stmt);
+
+    rv = stmt->Execute();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    aThread->mScopesHavingData.Clear();
+    break;
+  }
+
+  case opClearMatchingScope:
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
+      "DELETE FROM webappsstore2"
+      " WHERE scope GLOB :scope"
+    );
+    NS_ENSURE_STATE(stmt);
+    mozStorageStatementScoper scope(stmt);
+
+    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
+                                    mScope + NS_LITERAL_CSTRING("*"));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = stmt->Execute();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    break;
+  }
+
+  default:
+    NS_ERROR("Unknown task type");
+    break;
+  }
+
+  return NS_OK;
+}
+
+void
+DOMStorageDBThread::DBOperation::Finalize(nsresult aRv)
+{
+  switch (mType) {
+  case opPreloadUrgent:
+  case opPreload:
+    if (NS_FAILED(aRv)) {
+      // When we are here, something failed when loading from the database.
+      // Notify that the storage is loaded to prevent deadlock of the main thread,
+      // even though it is actually empty or incomplete.
+      NS_WARNING("Failed to preload localStorage");
+    }
+
+    mCache->LoadDone(aRv);
+    break;
+
+  case opGetUsage:
+    if (NS_FAILED(aRv)) {
+      mUsage->LoadUsage(0);
+    }
+
+    break;
+
+  default:
+    if (NS_FAILED(aRv)) {
+      NS_WARNING("localStorage update/clear operation failed,"
+                 " data may not persist or clean up");
+    }
+
+    break;
+  }
+}
+
+// DOMStorageDBThread::PendingOperations
+
+DOMStorageDBThread::PendingOperations::PendingOperations()
+: mFlushFailureCount(0)
+{
+  mClears.Init();
+  mUpdates.Init();
+}
+
+bool
+DOMStorageDBThread::PendingOperations::HasTasks()
+{
+  return !!mUpdates.Count() || !!mClears.Count();
+}
+
+namespace { // anon
+
+PLDHashOperator
+ForgetUpdatesForScope(const nsACString& aMapping,
+                      nsAutoPtr<DOMStorageDBThread::DBOperation>& aPendingTask,
+                      void* aArg)
+{
+  DOMStorageDBThread::DBOperation* newOp = static_cast<DOMStorageDBThread::DBOperation*>(aArg);
+
+  if (newOp->Type() == DOMStorageDBThread::DBOperation::opClear &&
+      aPendingTask->Scope() != newOp->Scope()) {
+    return PL_DHASH_NEXT;
+  }
+
+  if (newOp->Type() == DOMStorageDBThread::DBOperation::opClearMatchingScope &&
+      !StringBeginsWith(aPendingTask->Scope(), newOp->Scope())) {
+    return PL_DHASH_NEXT;
+  }
+
+  return PL_DHASH_REMOVE;
+}
+
+} // anon
+
+bool
+DOMStorageDBThread::PendingOperations::CheckForCoalesceOpportunity(DBOperation* aNewOp,
+                                                                   DBOperation::OperationType aPendingType,
+                                                                   DBOperation::OperationType aNewType)
+{
+  if (aNewOp->Type() != aNewType) {
+    return false;
+  }
+
+  DOMStorageDBThread::DBOperation* pendingTask;
+  if (!mUpdates.Get(aNewOp->Target(), &pendingTask)) {
+    return false;
+  }
+
+  if (pendingTask->Type() != aPendingType) {
+    return false;
+  }
+
+  return true;
+}
+
+void
+DOMStorageDBThread::PendingOperations::Add(DOMStorageDBThread::DBOperation* aOperation)
+{
+  // Optimize: when a key to remove has never been written to disk
+  // just bypass this operation.  A kew is new when an operation scheduled
+  // to write it to the database is of type opAddItem.
+  if (CheckForCoalesceOpportunity(aOperation, DBOperation::opAddItem, DBOperation::opRemoveItem)) {
+    mUpdates.Remove(aOperation->Target());
+    delete aOperation;
+    return;
+  }
+
+  // Optimize: when changing a key that is new and has never been
+  // written to disk, keep type of the operation to store it at opAddItem.
+  // This allows optimization to just forget adding a new key when
+  // it is removed from the storage before flush.
+  if (CheckForCoalesceOpportunity(aOperation, DBOperation::opAddItem, DBOperation::opUpdateItem)) {
+    aOperation->mType = DBOperation::opAddItem;
+  }
+
+  // Optimize: to prevent lose of remove operation on a key when doing
+  // remove/set/remove on a previously existing key we have to change
+  // opAddItem to opUpdateItem on the new operation when there is opRemoveItem
+  // pending for the key.
+  if (CheckForCoalesceOpportunity(aOperation, DBOperation::opRemoveItem, DBOperation::opAddItem)) {
+    aOperation->mType = DBOperation::opUpdateItem;
+  }
+
+  switch (aOperation->Type())
+  {
+  // Operations on single keys
+
+  case DBOperation::opAddItem:
+  case DBOperation::opUpdateItem:
+  case DBOperation::opRemoveItem:
+    // Override any existing operation for the target (=scope+key).
+    mUpdates.Put(aOperation->Target(), aOperation);
+    break;
+
+  // Clear operations
+
+  case DBOperation::opClear:
+  case DBOperation::opClearMatchingScope:
+    // Drop all update (insert/remove) operations for equivavelent or matching scope.
+    // We do this as an optimization as well as a must based on the logic,
+    // if we would not delete the update tasks, changes would have been stored
+    // to the database after clear operations have been executed.
+    mUpdates.Enumerate(ForgetUpdatesForScope, aOperation);
+    mClears.Put(aOperation->Target(), aOperation);
+    break;
+
+  case DBOperation::opClearAll:
+    // Drop simply everything, this is a super-operation.
+    mUpdates.Clear();
+    mClears.Clear();
+    mClears.Put(aOperation->Target(), aOperation);
+    break;
+
+  default:
+    MOZ_ASSERT(false);
+    break;
+  }
+}
+
+namespace { // anon
+
+PLDHashOperator
+CollectTasks(const nsACString& aMapping, nsAutoPtr<DOMStorageDBThread::DBOperation>& aOperation, void* aArg)
+{
+  nsTArray<nsAutoPtr<DOMStorageDBThread::DBOperation> >* tasks =
+    static_cast<nsTArray<nsAutoPtr<DOMStorageDBThread::DBOperation> >*>(aArg);
+
+  tasks->AppendElement(aOperation.forget());
+  return PL_DHASH_NEXT;
+}
+
+} // anon
+
+bool
+DOMStorageDBThread::PendingOperations::Prepare()
+{
+  // Called under the lock
+
+  // First collect clear operations and then updates, we can
+  // do this since whenever a clear operation for a scope is
+  // scheduled, we drop all updates matching that scope. So,
+  // all scope-related update operations we have here now were
+  // scheduled after the clear operations.
+  mClears.Enumerate(CollectTasks, &mExecList);
+  mClears.Clear();
+
+  mUpdates.Enumerate(CollectTasks, &mExecList);
+  mUpdates.Clear();
+
+  return !!mExecList.Length();
+}
+
+nsresult
+DOMStorageDBThread::PendingOperations::Execute(DOMStorageDBThread* aThread)
+{
+  // Called outside the lock
+
+  mozStorageTransaction transaction(aThread->mWorkerConnection, false);
+
+  nsresult rv;
+
+  for (uint32_t i = 0; i < mExecList.Length(); ++i) {
+    DOMStorageDBThread::DBOperation* task = mExecList[i];
+    rv = task->Perform(aThread);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+  }
+
+  rv = transaction.Commit();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+bool
+DOMStorageDBThread::PendingOperations::Finalize(nsresult aRv)
+{
+  // Called under the lock
+
+  // The list is kept on a failure to retry it
+  if (NS_FAILED(aRv)) {
+    // XXX Followup: we may try to reopen the database and flush these
+    // pending tasks, however testing showed that even though I/O is actually
+    // broken some amount of operations is left in sqlite+system buffers and
+    // seems like successfully flushed to disk.
+    // Tested by removing a flash card and disconnecting from network while
+    // using a network drive on Windows system.
+    NS_WARNING("Flush operation on localStorage database failed");
+
+    ++mFlushFailureCount;
+
+    return mFlushFailureCount >= 5;
+  }
+
+  mFlushFailureCount = 0;
+  mExecList.Clear();
+  return true;
+}
+
+namespace { // anon
+
+class FindPendingOperationForScopeData
+{
+public:
+  FindPendingOperationForScopeData(const nsACString& aScope) : mScope(aScope), mFound(false) {}
+  nsCString mScope;
+  bool mFound;
+};
+
+PLDHashOperator
+FindPendingClearForScope(const nsACString& aMapping,
+                         DOMStorageDBThread::DBOperation* aPendingOperation,
+                         void* aArg)
+{
+  FindPendingOperationForScopeData* data =
+    static_cast<FindPendingOperationForScopeData*>(aArg);
+
+  if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClearAll) {
+    data->mFound = true;
+    return PL_DHASH_STOP;
+  }
+
+  if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClear &&
+      data->mScope == aPendingOperation->Scope()) {
+    data->mFound = true;
+    return PL_DHASH_STOP;
+  }
+
+  if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClearMatchingScope &&
+      StringBeginsWith(data->mScope, aPendingOperation->Scope())) {
+    data->mFound = true;
+    return PL_DHASH_STOP;
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+} // anon
+
+bool
+DOMStorageDBThread::PendingOperations::IsScopeClearPending(const nsACString& aScope)
+{
+  // Called under the lock
+
+  FindPendingOperationForScopeData data(aScope);
+  mClears.EnumerateRead(FindPendingClearForScope, &data);
+  if (data.mFound) {
+    return true;
+  }
+
+  for (uint32_t i = 0; i < mExecList.Length(); ++i) {
+    DOMStorageDBThread::DBOperation* task = mExecList[i];
+    FindPendingClearForScope(EmptyCString(), task, &data);
+
+    if (data.mFound) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+namespace { // anon
+
+PLDHashOperator
+FindPendingUpdateForScope(const nsACString& aMapping,
+                          DOMStorageDBThread::DBOperation* aPendingOperation,
+                          void* aArg)
+{
+  FindPendingOperationForScopeData* data =
+    static_cast<FindPendingOperationForScopeData*>(aArg);
+
+  if ((aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opAddItem ||
+       aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opUpdateItem ||
+       aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opRemoveItem) &&
+       data->mScope == aPendingOperation->Scope()) {
+    data->mFound = true;
+    return PL_DHASH_STOP;
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+} // anon
+
+bool
+DOMStorageDBThread::PendingOperations::IsScopeUpdatePending(const nsACString& aScope)
+{
+  // Called under the lock
+
+  FindPendingOperationForScopeData data(aScope);
+  mUpdates.EnumerateRead(FindPendingUpdateForScope, &data);
+  if (data.mFound) {
+    return true;
+  }
+
+  for (uint32_t i = 0; i < mExecList.Length(); ++i) {
+    DOMStorageDBThread::DBOperation* task = mExecList[i];
+    FindPendingUpdateForScope(EmptyCString(), task, &data);
+
+    if (data.mFound) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+} // ::dom
+} // ::mozilla
new file mode 100644
--- /dev/null
+++ b/dom/src/storage/DOMStorageDBThread.h
@@ -0,0 +1,353 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef DOMStorageDBThread_h___
+#define DOMStorageDBThread_h___
+
+#include "prthread.h"
+#include "prinrval.h"
+#include "nsTArray.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/storage/StatementCache.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsClassHashtable.h"
+#include "nsIFile.h"
+
+class mozIStorageConnection;
+
+namespace mozilla {
+namespace dom {
+
+class DOMStorageCacheBridge;
+class DOMStorageUsageBridge;
+class DOMStorageUsage;
+
+typedef mozilla::storage::StatementCache<mozIStorageStatement> StatementCache;
+
+// Interface used by the cache to post operations to the asynchronous
+// database thread or process.
+class DOMStorageDBBridge
+{
+public:
+  DOMStorageDBBridge();
+  virtual ~DOMStorageDBBridge() {}
+
+  // Ensures the database engine is started
+  virtual nsresult Init() = 0;
+
+  // Releases the database and disallows its usage
+  virtual nsresult Shutdown() = 0;
+
+  // Asynchronously fills the cache with data from the database for first use.
+  // When |aPriority| is true, the preload operation is scheduled as the first one.
+  // This method is responsible to keep hard reference to the cache for the time of
+  // the preload or, when preload cannot be performed, call LoadDone() immediately.
+  virtual void AsyncPreload(DOMStorageCacheBridge* aCache, bool aPriority = false) = 0;
+
+  // Asynchronously fill the |usage| object with actual usage of data by its scope.
+  // The scope is eTLD+1 tops, never deeper subdomains.
+  virtual void AsyncGetUsage(DOMStorageUsageBridge* aUsage) = 0;
+
+  // Synchronously fills the cache, when |aForceSync| is false and cache already got some
+  // data before, the method waits for the running preload to finish
+  virtual void SyncPreload(DOMStorageCacheBridge* aCache, bool aForceSync = false) = 0;
+
+  // Called when an existing key is modified in the storage, schedules update to the database
+  virtual nsresult AsyncAddItem(DOMStorageCacheBridge* aCache, const nsAString& aKey, const nsAString& aValue) = 0;
+
+  // Called when an existing key is modified in the storage, schedules update to the database
+  virtual nsresult AsyncUpdateItem(DOMStorageCacheBridge* aCache, const nsAString& aKey, const nsAString& aValue) = 0;
+
+  // Called when an item is removed from the storage, schedules delete of the key
+  virtual nsresult AsyncRemoveItem(DOMStorageCacheBridge* aCache, const nsAString& aKey) = 0;
+
+  // Called when the whole storage is cleared by the DOM API, schedules delete of the scope
+  virtual nsresult AsyncClear(DOMStorageCacheBridge* aCache) = 0;
+
+  // Called when chrome deletes e.g. cookies, schedules delete of the whole database
+  virtual void AsyncClearAll() = 0;
+
+  // Called when only a domain and its subdomains or an app data is about to clear
+  virtual void AsyncClearMatchingScope(const nsACString& aScope) = 0;
+
+  // Forces scheduled DB operations to be early flushed to the disk
+  virtual void AsyncFlush() = 0;
+
+  // Check whether the scope has any data stored on disk and is thus allowed to preload
+  virtual bool ShouldPreloadScope(const nsACString& aScope) = 0;
+
+  // Get the complete list of scopes having data
+  virtual void GetScopesHavingData(InfallibleTArray<nsCString>* aScopes) = 0;
+
+  // Returns object keeping usage cache for the scope.
+  DOMStorageUsage* GetScopeUsage(const nsACString& aScope);
+
+protected:
+  // Keeps usage cache objects for eTLD+1 scopes we have touched.
+  nsClassHashtable<nsCStringHashKey, DOMStorageUsage> mUsages;
+};
+
+// The implementation of the the database engine, this directly works
+// with the sqlite or any other db API we are based on
+// This class is resposible for collecting and processing asynchronous 
+// DB operations over caches (DOMStorageCache) communicating though 
+// DOMStorageCacheBridge interface class
+class DOMStorageDBThread MOZ_FINAL : public DOMStorageDBBridge
+{
+public:
+  class PendingOperations;
+
+  // Representation of a singe database task, like adding and removing keys,
+  // (pre)loading the whole origin data, cleaning.
+  class DBOperation
+  {
+  public:
+    typedef enum {
+      // Only operation that reads data from the database
+      opPreload,
+      // The same as opPreload, just executed with highest priority
+      opPreloadUrgent,
+
+      // Load usage of a scope
+      opGetUsage,
+
+      // Operations invoked by the DOM content API
+      opAddItem,
+      opUpdateItem,
+      opRemoveItem,
+      opClear,
+
+      // Operations invoked by chrome
+      opClearAll,
+      opClearMatchingScope,
+    } OperationType;
+
+    DBOperation(const OperationType aType,
+                DOMStorageCacheBridge* aCache = nullptr,
+                const nsAString& aKey = EmptyString(),
+                const nsAString& aValue = EmptyString());
+    DBOperation(const OperationType aType,
+                DOMStorageUsageBridge* aUsage);
+    DBOperation(const OperationType aType,
+                const nsACString& aScope);
+    ~DBOperation();
+
+    // Executes the operation, doesn't necessarity have to be called on the I/O thread
+    void PerformAndFinalize(DOMStorageDBThread* aThread);
+
+    // Finalize the operation, i.e. do any internal cleanup and finish calls
+    void Finalize(nsresult aRv);
+
+    // The operation type
+    OperationType Type() { return mType; }
+
+    // The operation scope (=origin)
+    const nsCString Scope();
+
+    // |Scope + key| the operation is working with
+    const nsCString Target();
+
+  private:
+    // The operation implementation body
+    nsresult Perform(DOMStorageDBThread* aThread);
+
+    friend class PendingOperations;
+    OperationType mType;
+    nsRefPtr<DOMStorageCacheBridge> mCache;
+    DOMStorageUsageBridge* mUsage;
+    nsString mKey;
+    nsString mValue;
+    nsCString mScope;
+  };
+
+  // Encapsulation of collective and coalescing logic for all pending operations
+  // except preloads that are handled separately as priority operations
+  class PendingOperations {
+  public:
+    PendingOperations();
+
+    // Method responsible for coalescing redundant update operations with the same
+    // |Target()| or clear operations with the same or matching |Scope()|
+    void Add(DBOperation* aOperation);
+
+    // True when there are some scheduled operations to flush on disk
+    bool HasTasks();
+
+    // Moves collected operations to a local flat list to allow execution of the operation
+    // list out of the thread lock
+    bool Prepare();
+
+    // Executes the previously |Prepared()'ed| list of operations, retuns result, but doesn't
+    // handle it in any way in case of a failure
+    nsresult Execute(DOMStorageDBThread* aThread);
+
+    // Finalizes the pending operation list, returns false when too many operations failed
+    // to flush what indicates a long standing issue with the database access.
+    bool Finalize(nsresult aRv);
+
+    // true when a clear that deletes the given |scope| is among the pending operations;
+    // when a preload for that scope is being scheduled, it must be finished right away
+    bool IsScopeClearPending(const nsACString& aScope);
+
+    // Checks whether there is a pending update (or clear, actually) operation for this scope.
+    bool IsScopeUpdatePending(const nsACString& aScope);
+
+  private:
+    // Returns true iff new operation is of type newType and there is a pending 
+    // operation of type pendingType for the same key (target).
+    bool CheckForCoalesceOpportunity(DBOperation* aNewOp,
+                                     DBOperation::OperationType aPendingType,
+                                     DBOperation::OperationType aNewType);
+
+    // List of all clearing operations, executed first
+    nsClassHashtable<nsCStringHashKey, DBOperation> mClears;
+
+    // List of all update/insert operations, executed as second
+    nsClassHashtable<nsCStringHashKey, DBOperation> mUpdates;
+
+    // Collection of all tasks, valid only between Prepare() and Execute()
+    nsTArray<nsAutoPtr<DBOperation> > mExecList;
+
+    // Number of failing flush attempts
+    uint32_t mFlushFailureCount;
+  };
+
+public:
+  DOMStorageDBThread();
+  virtual ~DOMStorageDBThread() {}
+
+  virtual nsresult Init();
+  virtual nsresult Shutdown();
+
+  virtual void AsyncPreload(DOMStorageCacheBridge* aCache, bool aPriority = false)
+    { InsertDBOp(new DBOperation(aPriority ? DBOperation::opPreloadUrgent : DBOperation::opPreload, aCache)); }
+
+  virtual void SyncPreload(DOMStorageCacheBridge* aCache, bool aForce = false);
+
+  virtual void AsyncGetUsage(DOMStorageUsageBridge * aUsage)
+    { InsertDBOp(new DBOperation(DBOperation::opGetUsage, aUsage)); }
+
+  virtual nsresult AsyncAddItem(DOMStorageCacheBridge* aCache, const nsAString& aKey, const nsAString& aValue)
+    { return InsertDBOp(new DBOperation(DBOperation::opAddItem, aCache, aKey, aValue)); }
+
+  virtual nsresult AsyncUpdateItem(DOMStorageCacheBridge* aCache, const nsAString& aKey, const nsAString& aValue)
+    { return InsertDBOp(new DBOperation(DBOperation::opUpdateItem, aCache, aKey, aValue)); }
+
+  virtual nsresult AsyncRemoveItem(DOMStorageCacheBridge* aCache, const nsAString& aKey)
+    { return InsertDBOp(new DBOperation(DBOperation::opRemoveItem, aCache, aKey)); }
+
+  virtual nsresult AsyncClear(DOMStorageCacheBridge* aCache)
+    { return InsertDBOp(new DBOperation(DBOperation::opClear, aCache)); }
+
+  virtual void AsyncClearAll()
+    { InsertDBOp(new DBOperation(DBOperation::opClearAll)); }
+
+  virtual void AsyncClearMatchingScope(const nsACString& aScope)
+    { InsertDBOp(new DBOperation(DBOperation::opClearMatchingScope, aScope)); }
+
+  virtual void AsyncFlush();
+
+  virtual bool ShouldPreloadScope(const nsACString& aScope);
+  virtual void GetScopesHavingData(InfallibleTArray<nsCString>* aScopes);
+
+private:
+  nsCOMPtr<nsIFile> mDatabaseFile;
+  PRThread* mThread;
+
+  // The monitor we drive the thread with
+  Monitor mMonitor;
+
+  // Flag to stop, protected by the monitor
+  bool mStopIOThread;
+
+  // Whether WAL is enabled
+  bool mWALModeEnabled;
+
+  // Whether DB has already been open, avoid races between main thread reads
+  // and pending DB init in the background I/O thread
+  bool mDBReady;
+
+  // State of the database initiation
+  nsresult mStatus;
+
+  // List of scopes having data, for optimization purposes only
+  nsTHashtable<nsCStringHashKey> mScopesHavingData;
+
+  StatementCache mWorkerStatements;
+  StatementCache mReaderStatements;
+
+  // Connection used by the worker thread for all read and write ops
+  nsCOMPtr<mozIStorageConnection> mWorkerConnection;
+
+  // Connection used only on the main thread for sync read operations
+  nsCOMPtr<mozIStorageConnection> mReaderConnection;
+
+  // Time the first pending operation has been added to the pending operations
+  // list
+  PRIntervalTime mDirtyEpoch;
+
+  // Flag to force immediate flush of all pending operations
+  bool mFlushImmediately;
+
+  // List of preloading operations, in chronological or priority order.
+  // Executed prioritly over pending update operations.
+  nsTArray<DBOperation*> mPreloads;
+
+  // Collector of pending update operations
+  PendingOperations mPendingTasks;
+
+  // Counter of calls for thread priority rising.
+  int32_t mPriorityCounter;
+
+  // Helper to direct an operation to one of the arrays above;
+  // also checks IsScopeClearPending for preloads
+  nsresult InsertDBOp(DBOperation* aOperation);
+
+  // Opens the database, first thing we do after start of the thread.
+  nsresult OpenDatabaseConnection();
+  nsresult InitDatabase();
+  nsresult ShutdownDatabase();
+
+  // Tries to establish WAL mode
+  nsresult SetJournalMode(bool aIsWal);
+  nsresult TryJournalMode();
+
+  // Sets the threshold for auto-checkpointing the WAL.
+  nsresult ConfigureWALBehavior();
+
+  void SetHigherPriority();
+  void SetDefaultPriority();
+
+  // Ensures we flush pending tasks in some reasonble time
+  void ScheduleFlush();
+
+  // Called when flush of pending tasks is being executed
+  void UnscheduleFlush();
+
+  // This method is used for two purposes:
+  // 1. as a value passed to monitor.Wait() method
+  // 2. as in indicator that flush has to be performed
+  //
+  // Return:
+  // - PR_INTERVAL_NO_TIMEOUT when no pending tasks are scheduled
+  // - larger then zero when tasks have been scheduled, but it is
+  //   still not time to perform the flush ; it is actual interval
+  //   time to wait until the flush has to happen
+  // - 0 when it is time to do the flush
+  PRIntervalTime TimeUntilFlush();
+
+  // Notifies to the main thread that flush has completed
+  void NotifyFlushCompletion();
+
+  // Thread loop
+  static void ThreadFunc(void* aArg);
+  void ThreadFunc();
+};
+
+} // ::dom
+} // ::mozilla
+
+#endif /* DOMStorageDBThread_h___ */
new file mode 100644
--- /dev/null
+++ b/dom/src/storage/DOMStorageIPC.cpp
@@ -0,0 +1,712 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "DOMStorageIPC.h"
+
+#include "DOMStorageManager.h"
+
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/unused.h"
+
+namespace mozilla {
+namespace dom {
+
+// ----------------------------------------------------------------------------
+// Child
+// ----------------------------------------------------------------------------
+
+NS_IMPL_THREADSAFE_ADDREF(DOMStorageDBChild)
+
+NS_IMETHODIMP_(nsrefcnt) DOMStorageDBChild::Release(void)
+{
+  NS_PRECONDITION(0 != mRefCnt, "dup release");
+  nsrefcnt count = --mRefCnt;
+  NS_LOG_RELEASE(this, count, "DOMStorageDBChild");
+  if (count == 1 && mIPCOpen) {
+    Send__delete__(this);
+    return 0;
+  }
+  if (count == 0) {
+    mRefCnt = 1;
+    delete this;
+    return 0;
+  }
+  return count;
+}
+
+void
+DOMStorageDBChild::AddIPDLReference()
+{
+  NS_ABORT_IF_FALSE(!mIPCOpen, "Attempting to retain multiple IPDL references");
+  mIPCOpen = true;
+  AddRef();
+}
+
+void
+DOMStorageDBChild::ReleaseIPDLReference()
+{
+  NS_ABORT_IF_FALSE(mIPCOpen, "Attempting to release non-existent IPDL reference");
+  mIPCOpen = false;
+  Release();
+}
+
+DOMStorageDBChild::DOMStorageDBChild(DOMLocalStorageManager* aManager)
+  : mManager(aManager)
+  , mStatus(NS_OK)
+  , mIPCOpen(false)
+{
+  mLoadingCaches.Init();
+}
+
+DOMStorageDBChild::~DOMStorageDBChild()
+{
+}
+
+nsTHashtable<nsCStringHashKey>&
+DOMStorageDBChild::ScopesHavingData()
+{
+  if (!mScopesHavingData.IsInitialized()) {
+    mScopesHavingData.Init();
+  }
+
+  return mScopesHavingData;
+}
+
+nsresult
+DOMStorageDBChild::Init()
+{
+  ContentChild* child = ContentChild::GetSingleton();
+  AddIPDLReference();
+  child->SendPStorageConstructor(this);
+  return NS_OK;
+}
+
+nsresult
+DOMStorageDBChild::Shutdown()
+{
+  // There is nothing to do here, IPC will release automatically and
+  // the actual thread running on the parent process will also stop
+  // automatically in profile-before-change topic observer.
+  return NS_OK;
+}
+
+void
+DOMStorageDBChild::AsyncPreload(DOMStorageCacheBridge* aCache, bool aPriority)
+{
+  if (mIPCOpen) {
+    // Adding ref to cache for the time of preload.  This ensures a reference to
+    // to the cache and that all keys will load into this cache object.
+    mLoadingCaches.PutEntry(aCache);
+    SendAsyncPreload(aCache->Scope(), aPriority);
+  } else {
+    // No IPC, no love.  But the LoadDone call is expected.
+    aCache->LoadDone(NS_ERROR_UNEXPECTED);
+  }
+}
+
+void
+DOMStorageDBChild::AsyncGetUsage(DOMStorageUsageBridge* aUsage)
+{
+  if (mIPCOpen) {
+    SendAsyncGetUsage(aUsage->Scope());
+  }
+}
+
+void
+DOMStorageDBChild::SyncPreload(DOMStorageCacheBridge* aCache, bool aForceSync)
+{
+  if (NS_FAILED(mStatus)) {
+    aCache->LoadDone(mStatus);
+    return;
+  }
+
+  if (!mIPCOpen) {
+    aCache->LoadDone(NS_ERROR_UNEXPECTED);
+    return;
+  }
+
+  // There is no way to put the child process to a wait state to receive all
+  // incoming async responses from the parent, hence we have to do a sync preload
+  // instead.  We are smart though, we only demand keys that are left to load in
+  // case the async preload has already loaded some keys.
+  InfallibleTArray<nsString> keys, values;
+  nsresult rv;
+  SendPreload(aCache->Scope(), aCache->LoadedCount(), &keys, &values, &rv);
+
+  for (uint32_t i = 0; i < keys.Length(); ++i) {
+    aCache->LoadItem(keys[i], values[i]);
+  }
+
+  aCache->LoadDone(rv);
+}
+
+nsresult
+DOMStorageDBChild::AsyncAddItem(DOMStorageCacheBridge* aCache,
+                                const nsAString& aKey,
+                                const nsAString& aValue)
+{
+  if (NS_FAILED(mStatus) || !mIPCOpen) {
+    return mStatus;
+  }
+
+  SendAsyncAddItem(aCache->Scope(), nsString(aKey), nsString(aValue));
+  ScopesHavingData().PutEntry(aCache->Scope());
+  return NS_OK;
+}
+
+nsresult
+DOMStorageDBChild::AsyncUpdateItem(DOMStorageCacheBridge* aCache,
+                                   const nsAString& aKey,
+                                   const nsAString& aValue)
+{
+  if (NS_FAILED(mStatus) || !mIPCOpen) {
+    return mStatus;
+  }
+
+  SendAsyncUpdateItem(aCache->Scope(), nsString(aKey), nsString(aValue));
+  ScopesHavingData().PutEntry(aCache->Scope());
+  return NS_OK;
+}
+
+nsresult
+DOMStorageDBChild::AsyncRemoveItem(DOMStorageCacheBridge* aCache,
+                                   const nsAString& aKey)
+{
+  if (NS_FAILED(mStatus) || !mIPCOpen) {
+    return mStatus;
+  }
+
+  SendAsyncRemoveItem(aCache->Scope(), nsString(aKey));
+  return NS_OK;
+}
+
+nsresult
+DOMStorageDBChild::AsyncClear(DOMStorageCacheBridge* aCache)
+{
+  if (NS_FAILED(mStatus) || !mIPCOpen) {
+    return mStatus;
+  }
+
+  SendAsyncClear(aCache->Scope());
+  ScopesHavingData().RemoveEntry(aCache->Scope());
+  return NS_OK;
+}
+
+bool
+DOMStorageDBChild::ShouldPreloadScope(const nsACString& aScope)
+{
+  // Return true if we didn't receive the aScope list yet.
+  // I tend to rather preserve a bit of early-after-start performance
+  // then a bit of memory here.
+  return !mScopesHavingData.IsInitialized() ||
+         mScopesHavingData.Contains(aScope);
+}
+
+bool
+DOMStorageDBChild::RecvObserve(const nsCString& aTopic,
+                               const nsCString& aScopePrefix)
+{
+  DOMStorageObserver::Self()->Notify(aTopic.get(), aScopePrefix);
+  return true;
+}
+
+bool
+DOMStorageDBChild::RecvScopesHavingData(const InfallibleTArray<nsCString>& aScopes)
+{
+  for (uint32_t i = 0; i < aScopes.Length(); ++i) {
+    ScopesHavingData().PutEntry(aScopes[i]);
+  }
+
+  return true;
+}
+
+bool
+DOMStorageDBChild::RecvLoadItem(const nsCString& aScope,
+                                const nsString& aKey,
+                                const nsString& aValue)
+{
+  DOMStorageCache* aCache = mManager->GetCache(aScope);
+  if (aCache) {
+    aCache->LoadItem(aKey, aValue);
+  }
+
+  return true;
+}
+
+bool
+DOMStorageDBChild::RecvLoadDone(const nsCString& aScope, const nsresult& aRv)
+{
+  DOMStorageCache* aCache = mManager->GetCache(aScope);
+  if (aCache) {
+    aCache->LoadDone(aRv);
+
+    // Just drop reference to this cache now since the load is done.
+    mLoadingCaches.RemoveEntry(static_cast<DOMStorageCacheBridge*>(aCache));
+  }
+
+  return true;
+}
+
+bool
+DOMStorageDBChild::RecvLoadUsage(const nsCString& aScope, const int64_t& aUsage)
+{
+  DOMStorageDBBridge* db = DOMStorageCache::GetDatabase();
+  if (!db) {
+    return false;
+  }
+
+  DOMStorageUsageBridge* scopeUsage = db->GetScopeUsage(aScope);
+  scopeUsage->LoadUsage(aUsage);
+  return true;
+}
+
+bool
+DOMStorageDBChild::RecvError(const nsresult& aRv)
+{
+  mStatus = aRv;
+  return true;
+}
+
+// ----------------------------------------------------------------------------
+// Parent
+// ----------------------------------------------------------------------------
+
+NS_IMPL_THREADSAFE_ADDREF(DOMStorageDBParent)
+NS_IMPL_THREADSAFE_RELEASE(DOMStorageDBParent)
+
+void
+DOMStorageDBParent::AddIPDLReference()
+{
+  NS_ABORT_IF_FALSE(!mIPCOpen, "Attempting to retain multiple IPDL references");
+  mIPCOpen = true;
+  AddRef();
+}
+
+void
+DOMStorageDBParent::ReleaseIPDLReference()
+{
+  NS_ABORT_IF_FALSE(mIPCOpen, "Attempting to release non-existent IPDL reference");
+  mIPCOpen = false;
+  Release();
+}
+
+namespace { // anon
+
+class SendScopesHavingDataRunnable : public nsRunnable
+{
+public:
+  SendScopesHavingDataRunnable(DOMStorageDBParent* aParent)
+    : mParent(aParent)
+  {}
+
+private:
+  NS_IMETHOD Run()
+  {
+    if (!mParent->IPCOpen()) {
+      return NS_OK;
+    }
+
+    DOMStorageDBBridge* db = DOMStorageCache::GetDatabase();
+    if (db) {
+      InfallibleTArray<nsCString> scopes;
+      db->GetScopesHavingData(&scopes);
+      mozilla::unused << mParent->SendScopesHavingData(scopes);
+    }
+
+    return NS_OK;
+  }
+
+  nsRefPtr<DOMStorageDBParent> mParent;
+};
+
+} // anon
+
+DOMStorageDBParent::DOMStorageDBParent()
+: mIPCOpen(false)
+{
+  DOMStorageObserver* observer = DOMStorageObserver::Self();
+  if (observer) {
+    observer->AddSink(this);
+  }
+
+  // We are always open by IPC only
+  AddIPDLReference();
+
+  // Cannot send directly from here since the channel
+  // is not completely built at this moment.
+  nsRefPtr<SendScopesHavingDataRunnable> r =
+    new SendScopesHavingDataRunnable(this);
+  NS_DispatchToCurrentThread(r);
+}
+
+DOMStorageDBParent::~DOMStorageDBParent()
+{
+  DOMStorageObserver* observer = DOMStorageObserver::Self();
+  if (observer) {
+    observer->RemoveSink(this);
+  }
+}
+
+DOMStorageDBParent::CacheParentBridge*
+DOMStorageDBParent::NewCache(const nsACString& aScope)
+{
+  return new CacheParentBridge(this, aScope);
+}
+
+bool
+DOMStorageDBParent::RecvAsyncPreload(const nsCString& aScope, const bool& aPriority)
+{
+  DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+  if (!db) {
+    return false;
+  }
+
+  db->AsyncPreload(NewCache(aScope), aPriority);
+  return true;
+}
+
+bool
+DOMStorageDBParent::RecvAsyncGetUsage(const nsCString& aScope)
+{
+  DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+  if (!db) {
+    return false;
+  }
+
+  // The object releases it self in LoadUsage method
+  UsageParentBridge* usage = new UsageParentBridge(this, aScope);
+  db->AsyncGetUsage(usage);
+  return true;
+}
+
+namespace { // anon
+
+// We need another implementation of DOMStorageCacheBridge to do
+// synchronous IPC preload.  This class just receives Load* notifications
+// and fills the returning arguments of RecvPreload with the database
+// values for us.
+class SyncLoadCacheHelper : public DOMStorageCacheBridge
+{
+public:
+  SyncLoadCacheHelper(const nsCString& aScope,
+                      uint32_t aAlreadyLoadedCount,
+                      InfallibleTArray<nsString>* aKeys,
+                      InfallibleTArray<nsString>* aValues,
+                      nsresult* rv)
+  : mMonitor("DOM Storage SyncLoad IPC")
+  , mScope(aScope)
+  , mKeys(aKeys)
+  , mValues(aValues)
+  , mRv(rv)
+  , mLoaded(false)
+  , mLoadedCount(aAlreadyLoadedCount)
+  {
+    // Precaution
+    *mRv = NS_ERROR_UNEXPECTED;
+  }
+
+  virtual const nsCString& Scope() const { return mScope; }
+  virtual bool Loaded() { return mLoaded; }
+  virtual uint32_t LoadedCount() { return mLoadedCount; }
+  virtual bool LoadItem(const nsAString& aKey, const nsString& aValue)
+  {
+    // Called on the aCache background thread
+    if (mLoaded) {
+      return false;
+    }
+
+    ++mLoadedCount;
+    mKeys->AppendElement(aKey);
+    mValues->AppendElement(aValue);
+    return true;
+  }
+
+  virtual void LoadDone(nsresult aRv)
+  {
+    // Called on the aCache background thread
+    MonitorAutoLock monitor(mMonitor);
+    mLoaded = true;
+    *mRv = aRv;
+    monitor.Notify();
+  }
+
+  virtual void LoadWait()
+  {
+    // Called on the main thread, exits after LoadDone() call
+    MonitorAutoLock monitor(mMonitor);
+    while (!mLoaded) {
+      monitor.Wait();
+    }
+  }
+
+private:
+  Monitor mMonitor;
+  nsCString mScope;
+  InfallibleTArray<nsString>* mKeys;
+  InfallibleTArray<nsString>* mValues;
+  nsresult* mRv;
+  bool mLoaded;
+  uint32_t mLoadedCount;
+};
+
+} // anon
+
+bool
+DOMStorageDBParent::RecvPreload(const nsCString& aScope,
+                                const uint32_t& aAlreadyLoadedCount,
+                                InfallibleTArray<nsString>* aKeys,
+                                InfallibleTArray<nsString>* aValues,
+                                nsresult* aRv)
+{
+  DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+  if (!db) {
+    return false;
+  }
+
+  nsRefPtr<SyncLoadCacheHelper> cache(
+    new SyncLoadCacheHelper(aScope, aAlreadyLoadedCount, aKeys, aValues, aRv));
+
+  db->SyncPreload(cache, true);
+  return true;
+}
+
+bool
+DOMStorageDBParent::RecvAsyncAddItem(const nsCString& aScope,
+                                     const nsString& aKey,
+                                     const nsString& aValue)
+{
+  DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+  if (!db) {
+    return false;
+  }
+
+  nsresult rv = db->AsyncAddItem(NewCache(aScope), aKey, aValue);
+  if (NS_FAILED(rv) && mIPCOpen) {
+    mozilla::unused << SendError(rv);
+  }
+
+  return true;
+}
+
+bool
+DOMStorageDBParent::RecvAsyncUpdateItem(const nsCString& aScope,
+                                        const nsString& aKey,
+                                        const nsString& aValue)
+{
+  DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+  if (!db) {
+    return false;
+  }
+
+  nsresult rv = db->AsyncUpdateItem(NewCache(aScope), aKey, aValue);
+  if (NS_FAILED(rv) && mIPCOpen) {
+    mozilla::unused << SendError(rv);
+  }
+
+  return true;
+}
+
+bool
+DOMStorageDBParent::RecvAsyncRemoveItem(const nsCString& aScope,
+                                        const nsString& aKey)
+{
+  DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+  if (!db) {
+    return false;
+  }
+
+  nsresult rv = db->AsyncRemoveItem(NewCache(aScope), aKey);
+  if (NS_FAILED(rv) && mIPCOpen) {
+    mozilla::unused << SendError(rv);
+  }
+
+  return true;
+}
+
+bool
+DOMStorageDBParent::RecvAsyncClear(const nsCString& aScope)
+{
+  DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+  if (!db) {
+    return false;
+  }
+
+  nsresult rv = db->AsyncClear(NewCache(aScope));
+  if (NS_FAILED(rv) && mIPCOpen) {
+    mozilla::unused << SendError(rv);
+  }
+
+  return true;
+}
+
+bool
+DOMStorageDBParent::RecvAsyncFlush()
+{
+  DOMStorageDBBridge* db = DOMStorageCache::GetDatabase();
+  if (!db) {
+    return false;
+  }
+
+  db->AsyncFlush();
+  return true;
+}
+
+// DOMStorageObserverSink
+
+nsresult
+DOMStorageDBParent::Observe(const char* aTopic,
+                            const nsACString& aScopePrefix)
+{
+  if (mIPCOpen) {
+    mozilla::unused << SendObserve(nsDependentCString(aTopic),
+                                   nsCString(aScopePrefix));
+  }
+
+  return NS_OK;
+}
+
+namespace { // anon
+
+// Results must be sent back on the main thread
+class LoadRunnable : public nsRunnable
+{
+public:
+  enum TaskType {
+    loadItem,
+    loadDone
+  };
+
+  LoadRunnable(DOMStorageDBParent* aParent,
+               TaskType aType,
+               const nsACString& aScope,
+               const nsAString& aKey = EmptyString(),
+               const nsAString& aValue = EmptyString())
+  : mParent(aParent)
+  , mType(aType)
+  , mScope(aScope)
+  , mKey(aKey)
+  , mValue(aValue)
+  { }
+
+  LoadRunnable(DOMStorageDBParent* aParent,
+               TaskType aType,
+               const nsACString& aScope,
+               nsresult aRv)
+  : mParent(aParent)
+  , mType(aType)
+  , mScope(aScope)
+  , mRv(aRv)
+  { }
+
+private:
+  nsRefPtr<DOMStorageDBParent> mParent;
+  TaskType mType;
+  nsCString mScope;
+  nsString mKey;
+  nsString mValue;
+  nsresult mRv;
+
+  NS_IMETHOD Run()
+  {
+    if (!mParent->IPCOpen()) {
+      return NS_OK;
+    }
+
+    switch (mType)
+    {
+    case loadItem:
+      mozilla::unused << mParent->SendLoadItem(mScope, mKey, mValue);
+      break;
+    case loadDone:
+      mozilla::unused << mParent->SendLoadDone(mScope, mRv);
+      break;
+    }
+
+    return NS_OK;
+  }
+};
+
+} // anon
+
+// DOMStorageDBParent::CacheParentBridge
+
+bool
+DOMStorageDBParent::CacheParentBridge::LoadItem(const nsAString& aKey, const nsString& aValue)
+{
+  if (mLoaded) {
+    return false;
+  }
+
+  ++mLoadedCount;
+
+  nsRefPtr<LoadRunnable> r =
+    new LoadRunnable(mParent, LoadRunnable::loadItem, mScope, aKey, aValue);
+  NS_DispatchToMainThread(r);
+  return true;
+}
+
+void
+DOMStorageDBParent::CacheParentBridge::LoadDone(nsresult aRv)
+{
+  // Prevent send of duplicate LoadDone.
+  if (mLoaded) {
+    return;
+  }
+
+  mLoaded = true;
+
+  nsRefPtr<LoadRunnable> r =
+    new LoadRunnable(mParent, LoadRunnable::loadDone, mScope, aRv);
+  NS_DispatchToMainThread(r);
+}
+
+void
+DOMStorageDBParent::CacheParentBridge::LoadWait()
+{
+  // Should never be called on this implementation
+  MOZ_ASSERT(false);
+}
+
+// DOMStorageDBParent::UsageParentBridge
+
+namespace { // anon
+
+class UsageRunnable : public nsRunnable
+{
+public:
+  UsageRunnable(DOMStorageDBParent* aParent, const nsACString& aScope, const int64_t& aUsage)
+  : mParent(aParent)
+  , mScope(aScope)
+  , mUsage(aUsage)
+  {}
+
+private:
+  NS_IMETHOD Run()
+  {
+    if (!mParent->IPCOpen()) {
+      return NS_OK;
+    }
+
+    mozilla::unused << mParent->SendLoadUsage(mScope, mUsage);
+    return NS_OK;
+  }
+
+  nsRefPtr<DOMStorageDBParent> mParent;
+  nsCString mScope;
+  int64_t mUsage;
+};
+
+} // anon
+
+void
+DOMStorageDBParent::UsageParentBridge::LoadUsage(const int64_t aUsage)
+{
+  nsRefPtr<UsageRunnable> r = new UsageRunnable(mParent, mScope, aUsage);
+  NS_DispatchToMainThread(r);
+  delete this;
+}
+
+} // ::dom
+} // ::mozilla
new file mode 100644
--- /dev/null
+++ b/dom/src/storage/DOMStorageIPC.h
@@ -0,0 +1,191 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef nsDOMStorageIPC_h___
+#define nsDOMStorageIPC_h___
+
+#include "mozilla/dom/PStorageChild.h"
+#include "mozilla/dom/PStorageParent.h"
+#include "DOMStorageDBThread.h"
+#include "DOMStorageCache.h"
+#include "DOMStorageObserver.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace dom {
+
+class DOMLocalStorageManager;
+
+// Child side of the IPC protocol, exposes as DB interface but
+// is responsible to send all requests to the parent process
+// and expects asynchronous answers. Those are then transparently
+// forwarded back to consumers on the child process.
+class DOMStorageDBChild MOZ_FINAL : public DOMStorageDBBridge
+                                  , public PStorageChild
+{
+public:
+  DOMStorageDBChild(DOMLocalStorageManager* aManager);
+  virtual ~DOMStorageDBChild();
+
+  NS_IMETHOD_(nsrefcnt) AddRef(void);
+  NS_IMETHOD_(nsrefcnt) Release(void);
+
+  void AddIPDLReference();
+  void ReleaseIPDLReference();
+
+  virtual nsresult Init();
+  virtual nsresult Shutdown();
+
+  virtual void AsyncPreload(DOMStorageCacheBridge* aCache, bool aPriority = false);
+  virtual void AsyncGetUsage(DOMStorageUsageBridge* aUsage);
+
+  virtual void SyncPreload(DOMStorageCacheBridge* aCache, bool aForceSync = false);
+
+  virtual nsresult AsyncAddItem(DOMStorageCacheBridge* aCache, const nsAString& aKey, const nsAString& aValue);
+  virtual nsresult AsyncUpdateItem(DOMStorageCacheBridge* aCache, const nsAString& aKey, const nsAString& aValue);
+  virtual nsresult AsyncRemoveItem(DOMStorageCacheBridge* aCache, const nsAString& aKey);
+  virtual nsresult AsyncClear(DOMStorageCacheBridge* aCache);
+
+  virtual void AsyncClearAll()
+    { mScopesHavingData.Clear(); /* NO-OP on the child process otherwise */ }
+
+  virtual void AsyncClearMatchingScope(const nsACString& aScope)
+    { /* NO-OP on the child process */ }
+
+  virtual void AsyncFlush()
+    { SendAsyncFlush(); }
+
+  virtual bool ShouldPreloadScope(const nsACString& aScope);
+  virtual void GetScopesHavingData(InfallibleTArray<nsCString>* aScopes)
+    { NS_NOTREACHED("Not implemented for child process"); }
+
+private:
+  bool RecvObserve(const nsCString& aTopic,
+                   const nsCString& aScopePrefix);
+  bool RecvLoadItem(const nsCString& aScope,
+                    const nsString& aKey,
+                    const nsString& aValue);
+  bool RecvLoadDone(const nsCString& aScope,
+                    const nsresult& aRv);
+  bool RecvScopesHavingData(const InfallibleTArray<nsCString>& aScopes);
+  bool RecvLoadUsage(const nsCString& aScope,
+                     const int64_t& aUsage);
+  bool RecvError(const nsresult& aRv);
+
+  nsTHashtable<nsCStringHashKey>& ScopesHavingData();
+
+  nsAutoRefCnt mRefCnt;
+
+  // Held to get caches to forward answers to.
+  nsRefPtr<DOMLocalStorageManager> mManager;
+
+  // Scopes having data hash, for optimization purposes only
+  nsTHashtable<nsCStringHashKey> mScopesHavingData;
+
+  // List of caches waiting for preload.  This ensures the contract that
+  // AsyncPreload call references the cache for time of the preload.
+  nsTHashtable<nsRefPtrHashKey<DOMStorageCacheBridge> > mLoadingCaches;
+
+  // Status of the remote database
+  nsresult mStatus;
+
+  bool mIPCOpen;
+};
+
+
+// Receives async requests from child processes and is responsible
+// to send back responses from the DB thread.  Exposes as a fake
+// DOMStorageCache consumer.
+// Also responsible for forwardning all chrome operation notifications
+// such as cookie cleaning etc to the child process.
+class DOMStorageDBParent MOZ_FINAL : public PStorageParent
+                                   , public DOMStorageObserverSink
+{
+public:
+  DOMStorageDBParent();
+  virtual ~DOMStorageDBParent();
+
+  NS_IMETHOD_(nsrefcnt) AddRef(void);
+  NS_IMETHOD_(nsrefcnt) Release(void);
+
+  void AddIPDLReference();
+  void ReleaseIPDLReference();
+
+  bool IPCOpen() { return mIPCOpen; }
+
+public:
+  // Fake cache class receiving async callbacks from DB thread, sending
+  // them back to appropriate cache object on the child process.
+  class CacheParentBridge : public DOMStorageCacheBridge {
+  public:
+    CacheParentBridge(DOMStorageDBParent* aParentDB, const nsACString& aScope)
+      : mParent(aParentDB), mScope(aScope), mLoaded(false), mLoadedCount(0) {}
+    virtual ~CacheParentBridge() {}
+
+    // DOMStorageCacheBridge
+    virtual const nsCString& Scope() const
+      { return mScope; }
+    virtual bool Loaded()
+      { return mLoaded; }
+    virtual uint32_t LoadedCount()
+      { return mLoadedCount; }
+
+    virtual bool LoadItem(const nsAString& aKey, const nsString& aValue);
+    virtual void LoadDone(nsresult aRv);
+    virtual void LoadWait();
+
+  private:
+    nsRefPtr<DOMStorageDBParent> mParent;
+    nsCString mScope;
+    bool mLoaded;
+    uint32_t mLoadedCount;
+  };
+
+  // Fake usage class receiving async callbacks from DB thread
+  class UsageParentBridge : public DOMStorageUsageBridge
+  {
+  public:
+    UsageParentBridge(DOMStorageDBParent* aParentDB, const nsACString& aScope)
+      : mParent(aParentDB), mScope(aScope) {}
+    virtual ~UsageParentBridge() {}
+
+    // DOMStorageUsageBridge
+    virtual const nsCString& Scope() { return mScope; }
+    virtual void LoadUsage(const int64_t usage);
+
+  private:
+    nsRefPtr<DOMStorageDBParent> mParent;
+    nsCString mScope;
+  };
+
+private:
+  // IPC
+  bool RecvAsyncPreload(const nsCString& aScope, const bool& aPriority);
+  bool RecvPreload(const nsCString& aScope, const uint32_t& aAlreadyLoadedCount,
+                   InfallibleTArray<nsString>* aKeys, InfallibleTArray<nsString>* aValues,
+                   nsresult* aRv);
+  bool RecvAsyncGetUsage(const nsCString& aScope);
+  bool RecvAsyncAddItem(const nsCString& aScope, const nsString& aKey, const nsString& aValue);
+  bool RecvAsyncUpdateItem(const nsCString& aScope, const nsString& aKey, const nsString& aValue);
+  bool RecvAsyncRemoveItem(const nsCString& aScope, const nsString& aKey);
+  bool RecvAsyncClear(const nsCString& aScope);
+  bool RecvAsyncFlush();
+
+  // DOMStorageObserverSink
+  virtual nsresult Observe(const char* aTopic, const nsACString& aScopePrefix);
+
+private:
+  CacheParentBridge* NewCache(const nsACString& aScope);
+
+  nsAutoRefCnt mRefCnt;
+	
+	// True when IPC channel is open and Send*() methods are OK to use.
+  bool mIPCOpen;
+};
+
+} // ::dom
+} // ::mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/src/storage/DOMStorageManager.cpp
@@ -0,0 +1,631 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "DOMStorageManager.h"
+#include "DOMStorage.h"
+#include "DOMStorageDBThread.h"
+
+#include "nsIScriptSecurityManager.h"
+#include "nsIEffectiveTLDService.h"
+
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+#include "nsXULAppAPI.h"
+#include "nsThreadUtils.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "mozilla/Preferences.h"
+
+// Only allow relatively small amounts of data since performance of
+// the synchronous IO is very bad.
+// We are enforcing simple per-origin quota only.
+#define DEFAULT_QUOTA_LIMIT (5 * 1024)
+
+namespace mozilla {
+namespace dom {
+
+namespace { // anon
+
+int32_t gQuotaLimit = DEFAULT_QUOTA_LIMIT;
+
+} // anon
+
+DOMLocalStorageManager*
+DOMLocalStorageManager::sSelf = nullptr;
+
+// static
+uint32_t
+DOMStorageManager::GetQuota()
+{
+  static bool preferencesInitialized = false;
+  if (!preferencesInitialized) {
+    mozilla::Preferences::AddIntVarCache(&gQuotaLimit, "dom.storage.default_quota",
+                                         DEFAULT_QUOTA_LIMIT);
+    preferencesInitialized = true;
+  }
+
+  return gQuotaLimit * 1024; // pref is in kBs
+}
+
+void
+ReverseString(const nsCSubstring& aSource, nsCSubstring& aResult)
+{
+  nsACString::const_iterator sourceBegin, sourceEnd;
+  aSource.BeginReading(sourceBegin);
+  aSource.EndReading(sourceEnd);
+
+  aResult.SetLength(aSource.Length());
+  nsACString::iterator destEnd;
+  aResult.EndWriting(destEnd);
+
+  while (sourceBegin != sourceEnd) {
+    *(--destEnd) = *sourceBegin;
+    ++sourceBegin;
+  }
+}
+
+nsresult
+CreateReversedDomain(const nsACString& aAsciiDomain,
+                     nsACString& aKey)
+{
+  if (aAsciiDomain.IsEmpty()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  ReverseString(aAsciiDomain, aKey);
+
+  aKey.AppendLiteral(".");
+  return NS_OK;
+}
+
+bool
+PrincipalsEqual(nsIPrincipal* aObjectPrincipal, nsIPrincipal* aSubjectPrincipal)
+{
+  if (!aSubjectPrincipal) {
+    return true;
+  }
+
+  if (!aObjectPrincipal) {
+    return false;
+  }
+
+  bool equals;
+  nsresult rv = aSubjectPrincipal->EqualsIgnoringDomain(aObjectPrincipal, &equals);
+
+  NS_ASSERTION(NS_SUCCEEDED(rv) && equals,
+               "Trying to get DOM storage for wrong principal!");
+
+  if (NS_FAILED(rv) || !equals) {
+    return false;
+  }
+
+  return true;
+}
+
+NS_IMPL_ISUPPORTS1(DOMStorageManager,
+                   nsIDOMStorageManager)
+
+DOMStorageManager::DOMStorageManager(nsPIDOMStorage::StorageType aType)
+  : mType(aType)
+{
+  mCaches.Init(10);
+  DOMStorageObserver* observer = DOMStorageObserver::Self();
+  NS_ASSERTION(observer, "No DOMStorageObserver, cannot observe private data delete notifications!");
+
+  if (observer) {
+    observer->AddSink(this);
+  }
+}
+
+DOMStorageManager::~DOMStorageManager()
+{
+  DOMStorageObserver* observer = DOMStorageObserver::Self();
+  if (observer) {
+    observer->RemoveSink(this);
+  }
+}
+
+namespace { // anon
+
+nsresult
+CreateScopeKey(nsIPrincipal* aPrincipal,
+               nsACString& aKey)
+{
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (!uri) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  nsAutoCString domainScope;
+  rv = uri->GetAsciiHost(domainScope);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (domainScope.IsEmpty()) {
+    // About pages have an empty host but a valid path.  Since they are handled
+    // internally by our own redirector, we can trust them and use path as key.
+    // if file:/// protocol, let's make the exact directory the domain
+    bool isScheme = false;
+    if ((NS_SUCCEEDED(uri->SchemeIs("about", &isScheme)) && isScheme) ||
+        (NS_SUCCEEDED(uri->SchemeIs("moz-safe-about", &isScheme)) && isScheme)) {
+      rv = uri->GetPath(domainScope);
+      NS_ENSURE_SUCCESS(rv, rv);
+      // While the host is always canonicalized to lowercase, the path is not,
+      // thus need to force the casing.
+      ToLowerCase(domainScope);
+    } else if (NS_SUCCEEDED(uri->SchemeIs("file", &isScheme)) && isScheme) {
+      nsCOMPtr<nsIURL> url = do_QueryInterface(uri, &rv);
+      NS_ENSURE_SUCCESS(rv, rv);
+      rv = url->GetDirectory(domainScope);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+  }
+
+  nsAutoCString key;
+
+  rv = CreateReversedDomain(domainScope, key);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  nsAutoCString scheme;
+  rv = uri->GetScheme(scheme);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  key.Append(NS_LITERAL_CSTRING(":") + scheme);
+
+  int32_t port = NS_GetRealPort(uri);
+  if (port != -1) {
+    key.Append(nsPrintfCString(":%d", port));
+  }
+
+  bool unknownAppId;
+  rv = aPrincipal->GetUnknownAppId(&unknownAppId);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!unknownAppId) {
+    uint32_t appId;
+    rv = aPrincipal->GetAppId(&appId);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    bool isInBrowserElement;
+    rv = aPrincipal->GetIsInBrowserElement(&isInBrowserElement);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (appId == nsIScriptSecurityManager::NO_APP_ID && !isInBrowserElement) {
+      aKey.Assign(key);
+      return NS_OK;
+    }
+
+    aKey.Truncate();
+    aKey.AppendInt(appId);
+    aKey.Append(NS_LITERAL_CSTRING(":") + (isInBrowserElement ?
+                NS_LITERAL_CSTRING("t") : NS_LITERAL_CSTRING("f")) +
+                NS_LITERAL_CSTRING(":") + key);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CreateQuotaDBKey(nsIPrincipal* aPrincipal,
+                 nsACString& aKey)
+{
+  nsresult rv;
+
+  nsAutoCString subdomainsDBKey;
+  nsCOMPtr<nsIEffectiveTLDService> eTLDService(do_GetService(
+    NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIURI> uri;
+  rv = aPrincipal->GetURI(getter_AddRefs(uri));
+  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED);
+
+  nsAutoCString eTLDplusOne;
+  rv = eTLDService->GetBaseDomain(uri, 0, eTLDplusOne);
+  if (NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS == rv) {
+    // XXX bug 357323 - what to do for localhost/file exactly?
+    rv = uri->GetAsciiHost(eTLDplusOne);
+  }
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  CreateReversedDomain(eTLDplusOne, subdomainsDBKey);
+
+  bool unknownAppId;
+  rv = aPrincipal->GetUnknownAppId(&unknownAppId);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!unknownAppId) {
+    uint32_t appId;
+    rv = aPrincipal->GetAppId(&appId);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    bool isInBrowserElement;
+    rv = aPrincipal->GetIsInBrowserElement(&isInBrowserElement);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (appId == nsIScriptSecurityManager::NO_APP_ID && !isInBrowserElement) {
+      aKey.Assign(subdomainsDBKey);
+      return NS_OK;
+    }
+
+    aKey.Truncate();
+    aKey.AppendInt(appId);
+    aKey.Append(NS_LITERAL_CSTRING(":") + (isInBrowserElement ?
+                NS_LITERAL_CSTRING("t") : NS_LITERAL_CSTRING("f")) +
+                NS_LITERAL_CSTRING(":") + subdomainsDBKey);
+  }
+
+  return NS_OK;
+}
+
+} // anon
+
+DOMStorageCache*
+DOMStorageManager::GetCache(const nsACString& aScope) const
+{
+  DOMStorageCacheHashKey* entry = mCaches.GetEntry(aScope);
+  if (!entry) {
+    return nullptr;
+  }
+
+  return entry->cache();
+}
+
+already_AddRefed<DOMStorageCache>
+DOMStorageManager::PutCache(const nsACString& aScope,
+                            nsIPrincipal* aPrincipal)
+{
+  DOMStorageCacheHashKey* entry = mCaches.PutEntry(aScope);
+  nsRefPtr<DOMStorageCache> cache = entry->cache();
+
+  nsAutoCString quotaScope;
+  CreateQuotaDBKey(aPrincipal, quotaScope);
+
+  switch (mType) {
+  case SessionStorage:
+    // Lifetime handled by the manager, don't persist
+    entry->HardRef();
+    cache->Init(nullptr, false, aPrincipal, quotaScope);
+    break;
+
+  case LocalStorage:
+    // Lifetime handled by the cache, do persist
+    cache->Init(this, true, aPrincipal, quotaScope);
+    break;
+
+  default:
+    MOZ_ASSERT(false);
+  }
+
+  return cache.forget();
+}
+
+void
+DOMStorageManager::DropCache(DOMStorageCache* aCache)
+{
+  if (!NS_IsMainThread()) {
+    NS_WARNING("DOMStorageManager::DropCache called on a non-main thread, shutting down?");
+  }
+
+  mCaches.RemoveEntry(aCache->Scope());
+}
+
+nsresult
+DOMStorageManager::GetStorageInternal(bool aCreate,
+                                      nsIPrincipal* aPrincipal,
+                                      const nsAString& aDocumentURI,
+                                      bool aPrivate,
+                                      nsIDOMStorage** aRetval)
+{
+  nsresult rv;
+
+  nsAutoCString scope;
+  rv = CreateScopeKey(aPrincipal, scope);
+  if (NS_FAILED(rv)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsRefPtr<DOMStorageCache> cache = GetCache(scope);
+
+  // Get or create a cache for the given scope
+  if (!cache) {
+    if (!aCreate) {
+      *aRetval = nullptr;
+      return NS_OK;
+    }
+
+    if (!aRetval) {
+      // This is demand to just preload the cache, if the scope has
+      // no data stored, bypass creation and preload of the cache.
+      DOMStorageDBBridge* db = DOMStorageCache::GetDatabase();
+      if (db) {
+        if (!db->ShouldPreloadScope(scope)) {
+          return NS_OK;
+        }
+      } else {
+        if (scope.Equals(NS_LITERAL_CSTRING("knalb.:about"))) {
+          return NS_OK;
+        }
+      }
+    }
+
+    // There is always a single instance of a cache per scope
+    // in a single instance of a DOM storage manager.
+    cache = PutCache(scope, aPrincipal);
+  } else if (mType == SessionStorage) {
+    if (!cache->CheckPrincipal(aPrincipal)) {
+      return NS_ERROR_DOM_SECURITY_ERR;
+    }
+  }
+
+  if (aRetval) {
+    *aRetval = new DOMStorage(this, cache, aDocumentURI, aPrincipal, aPrivate);
+    NS_ADDREF(*aRetval);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMStorageManager::PrecacheStorage(nsIPrincipal* aPrincipal)
+{
+  return GetStorageInternal(true, aPrincipal, EmptyString(), false, nullptr);
+}
+
+NS_IMETHODIMP
+DOMStorageManager::CreateStorage(nsIPrincipal* aPrincipal,
+                                 const nsAString& aDocumentURI,
+                                 bool aPrivate,
+                                 nsIDOMStorage** aRetval)
+{
+  return GetStorageInternal(true, aPrincipal, aDocumentURI, aPrivate, aRetval);
+}
+
+NS_IMETHODIMP
+DOMStorageManager::GetStorage(nsIPrincipal* aPrincipal,
+                              bool aPrivate,
+                              nsIDOMStorage** aRetval)
+{
+  return GetStorageInternal(false, aPrincipal, EmptyString(), aPrivate, aRetval);
+}
+
+NS_IMETHODIMP
+DOMStorageManager::CloneStorage(nsIDOMStorage* aStorage)
+{
+  if (mType != SessionStorage) {
+    // Cloning is supported only for sessionStorage
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
+  nsCOMPtr<nsPIDOMStorage> pstorage = do_QueryInterface(aStorage);
+  if (!pstorage) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  const DOMStorageCache* origCache = pstorage->GetCache();
+
+  DOMStorageCache* existingCache = GetCache(origCache->Scope());
+  if (existingCache) {
+    // Do not replace an existing sessionStorage.
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  // Since this manager is sessionStorage manager, PutCache hard references
+  // the cache in our hashtable.
+  nsRefPtr<DOMStorageCache> newCache = PutCache(origCache->Scope(),
+                                                origCache->Principal());
+
+  newCache->CloneFrom(origCache);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMStorageManager::CheckStorage(nsIPrincipal* aPrincipal,
+                                nsIDOMStorage* aStorage,
+                                bool* aRetval)
+{
+  nsCOMPtr<nsPIDOMStorage> pstorage = do_QueryInterface(aStorage);
+  if (!pstorage) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  *aRetval = false;
+
+  if (!aPrincipal) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsAutoCString scope;
+  nsresult rv = CreateScopeKey(aPrincipal, scope);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  DOMStorageCache* cache = GetCache(scope);
+  if (cache != pstorage->GetCache()) {
+    return NS_OK;
+  }
+
+  if (!pstorage->PrincipalEquals(aPrincipal)) {
+    return NS_OK;
+  }
+
+  *aRetval = true;
+  return NS_OK;
+}
+
+// Obsolete nsIDOMStorageManager methods
+
+NS_IMETHODIMP
+DOMStorageManager::GetLocalStorageForPrincipal(nsIPrincipal* aPrincipal,
+                                               const nsAString& aDocumentURI,
+                                               bool aPrivate,
+                                               nsIDOMStorage** aRetval)
+{
+  if (mType != LocalStorage) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  return CreateStorage(aPrincipal, aDocumentURI, aPrivate, aRetval);
+}
+
+namespace { // anon
+
+class ClearCacheEnumeratorData
+{
+public:
+  ClearCacheEnumeratorData(uint32_t aFlags)
+    : mUnloadFlags(aFlags)
+  {}
+
+  uint32_t mUnloadFlags;
+  nsCString mKeyPrefix;
+};
+
+} // anon
+
+PLDHashOperator
+DOMStorageManager::ClearCacheEnumerator(DOMStorageCacheHashKey* aEntry, void* aClosure)
+{
+  DOMStorageCache* cache = aEntry->cache();
+  nsCString& key = const_cast<nsCString&>(cache->Scope());
+
+  ClearCacheEnumeratorData* data = static_cast<ClearCacheEnumeratorData*>(aClosure);
+
+  if (data->mKeyPrefix.IsEmpty() || StringBeginsWith(key, data->mKeyPrefix)) {
+    cache->UnloadItems(data->mUnloadFlags);
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+nsresult
+DOMStorageManager::Observe(const char* aTopic, const nsACString& aScopePrefix)
+{
+  // Clear everything, caches + database
+  if (!strcmp(aTopic, "cookie-cleared")) {
+    ClearCacheEnumeratorData data(DOMStorageCache::kUnloadComplete);
+    mCaches.EnumerateEntries(ClearCacheEnumerator, &data);
+
+    return NS_OK;
+  }
+
+  // Clear from caches everything that has been stored
+  // while in session-only mode
+  if (!strcmp(aTopic, "session-only-cleared")) {
+    ClearCacheEnumeratorData data(DOMStorageCache::kUnloadSession);
+    data.mKeyPrefix = aScopePrefix;
+    mCaches.EnumerateEntries(ClearCacheEnumerator, &data);
+
+    return NS_OK;
+  }
+
+  // Clear everything (including so and pb data) from caches and database
+  // for the gived domain and subdomains.
+  if (!strcmp(aTopic, "domain-data-cleared")) {
+    ClearCacheEnumeratorData data(DOMStorageCache::kUnloadComplete);
+    data.mKeyPrefix = aScopePrefix;
+    mCaches.EnumerateEntries(ClearCacheEnumerator, &data);
+
+    return NS_OK;
+  }
+
+  // Clear all private-browsing caches
+  if (!strcmp(aTopic, "private-browsing-data-cleared")) {
+    ClearCacheEnumeratorData data(DOMStorageCache::kUnloadPrivate);
+    mCaches.EnumerateEntries(ClearCacheEnumerator, &data);
+
+    return NS_OK;
+  }
+
+  // Clear localStorage data beloging to an app.
+  if (!strcmp(aTopic, "app-data-cleared")) {
+
+    // sessionStorage is expected to stay
+    if (mType == SessionStorage) {
+      return NS_OK;
+    }
+
+    ClearCacheEnumeratorData data(DOMStorageCache::kUnloadComplete);
+    data.mKeyPrefix = aScopePrefix;
+    mCaches.EnumerateEntries(ClearCacheEnumerator, &data);
+
+    return NS_OK;
+  }
+
+  if (!strcmp(aTopic, "profile-change")) {
+    // For case caches are still referenced - clear them completely
+    ClearCacheEnumeratorData data(DOMStorageCache::kUnloadComplete);
+    mCaches.EnumerateEntries(ClearCacheEnumerator, &data);
+
+    mCaches.Clear();
+    return NS_OK;
+  }
+
+#ifdef DOM_STORAGE_TESTS
+  if (!strcmp(aTopic, "test-reload")) {
+    if (mType != LocalStorage) {
+      return NS_OK;
+    }
+
+    // This immediately completely reloads all caches from the database.
+    ClearCacheEnumeratorData data(DOMStorageCache::kTestReload);
+    mCaches.EnumerateEntries(ClearCacheEnumerator, &data);
+    return NS_OK;
+  }
+
+  if (!strcmp(aTopic, "test-flushed")) {
+    if (XRE_GetProcessType() != GeckoProcessType_Default) {
+      nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+      if (obs) {
+        obs->NotifyObservers(nullptr, "domstorage-test-flushed", nullptr);
+      }
+    }
+
+    return NS_OK;
+  }
+#endif
+
+  NS_ERROR("Unexpected topic");
+  return NS_ERROR_UNEXPECTED;
+}
+
+// DOMLocalStorageManager
+
+DOMLocalStorageManager::DOMLocalStorageManager()
+  : DOMStorageManager(LocalStorage)
+{
+  NS_ASSERTION(!sSelf, "Somebody is trying to do_CreateInstance(\"@mozilla/dom/localStorage-manager;1\"");
+  sSelf = this;
+
+  if (XRE_GetProcessType() != GeckoProcessType_Default) {
+    // Do this only on the child process.  The thread IPC bridge
+    // is also used to communicate chrome observer notifications.
+    // Note: must be called after we set sSelf
+    DOMStorageCache::StartDatabase();
+  }
+}
+
+DOMLocalStorageManager::~DOMLocalStorageManager()
+{
+  sSelf = nullptr;
+}
+
+// DOMSessionStorageManager
+
+DOMSessionStorageManager::DOMSessionStorageManager()
+  : DOMStorageManager(SessionStorage)
+{
+  if (XRE_GetProcessType() != GeckoProcessType_Default) {
+    // Do this only on the child process.  The thread IPC bridge
+    // is also used to communicate chrome observer notifications.
+    DOMStorageCache::StartDatabase();
+  }
+}
+
+} // ::dom
+} // ::mozilla
new file mode 100644
--- /dev/null
+++ b/dom/src/storage/DOMStorageManager.h
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef nsDOMStorageManager_h__
+#define nsDOMStorageManager_h__
+
+#include "nsIDOMStorageManager.h"
+#include "DOMStorageObserver.h"
+
+#include "nsPIDOMStorage.h"
+#include "DOMStorageCache.h"
+
+#include "nsTHashtable.h"
+
+namespace mozilla {
+namespace dom {
+
+const nsPIDOMStorage::StorageType SessionStorage = nsPIDOMStorage::SessionStorage;
+const nsPIDOMStorage::StorageType LocalStorage = nsPIDOMStorage::LocalStorage;
+
+class DOMStorage;
+
+class DOMStorageManager : public nsIDOMStorageManager
+                        , public DOMStorageObserverSink
+{
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMSTORAGEMANAGER
+
+public:
+  virtual nsPIDOMStorage::StorageType Type() { return mType; }
+
+  // Reads the preference for DOM storage quota
+  static uint32_t GetQuota();
+  // Gets (but not ensures) cache for the given scope
+  DOMStorageCache* GetCache(const nsACString& aScope) const;
+
+protected:
+  DOMStorageManager(nsPIDOMStorage::StorageType aType);
+  virtual ~DOMStorageManager();
+
+private:
+  // DOMStorageObserverSink, handler to various chrome clearing notification
+  virtual nsresult Observe(const char* aTopic, const nsACString& aScopePrefix);
+
+  // Since nsTHashtable doesn't like multiple inheritance, we have to aggregate
+  // DOMStorageCache into the entry.
+  class DOMStorageCacheHashKey : public nsCStringHashKey
+  {
+  public:
+    DOMStorageCacheHashKey(const nsACString* aKey)
+      : nsCStringHashKey(aKey)
+      , mCache(new DOMStorageCache(aKey))
+    {}
+
+    DOMStorageCacheHashKey(const DOMStorageCacheHashKey& aOther)
+      : nsCStringHashKey(aOther)
+    {
+      NS_ERROR("Shouldn't be called");
+    }
+
+    DOMStorageCache* cache() { return mCache; }
+    // Keep the cache referenced forever, used for sessionStorage.
+    void HardRef() { mCacheRef = mCache; }
+
+  private:
+    // weak ref only since cache references its manager.
+    DOMStorageCache* mCache;
+    // hard ref when this is sessionStorage to keep it alive forever.
+    nsRefPtr<DOMStorageCache> mCacheRef;
+  };
+
+  // Ensures cache for a scope, when it doesn't exist it is created and initalized,
+  // this also starts preload of persistent data.
+  already_AddRefed<DOMStorageCache> PutCache(const nsACString& aScope,
+                                             nsIPrincipal* aPrincipal);
+
+  // Helper for creation of DOM storage objects
+  nsresult GetStorageInternal(bool aCreate,
+                              nsIPrincipal* aPrincipal,
+                              const nsAString& aDocumentURI,
+                              bool aPrivate,
+                              nsIDOMStorage** aRetval);
+
+  // Scope->cache map
+  nsTHashtable<DOMStorageCacheHashKey> mCaches;
+  const nsPIDOMStorage::StorageType mType;
+
+  static PLDHashOperator ClearCacheEnumerator(DOMStorageCacheHashKey* aCache,
+                                              void* aClosure);
+
+protected:
+  friend class DOMStorageCache;
+  // Releases cache since it is no longer referrered by any DOMStorage object.
+  virtual void DropCache(DOMStorageCache* aCache);
+};
+
+// Derived classes to allow two different contract ids, one for localStorage and
+// one for sessionStorage management.  localStorage manager is used as service
+// scoped to the application while sessionStorage managers are instantiated by each
+// top doc shell in the application since sessionStorages are isolated per top level
+// browsing context.  The code may easily by shared by both.
+
+class DOMLocalStorageManager MOZ_FINAL : public DOMStorageManager
+{
+public:
+  DOMLocalStorageManager();
+  virtual ~DOMLocalStorageManager();
+
+  // Global getter of localStorage manager service
+  static DOMLocalStorageManager* Self() { return sSelf; }
+
+private:
+  static DOMLocalStorageManager* sSelf;
+};
+
+class DOMSessionStorageManager MOZ_FINAL : public DOMStorageManager
+{
+public:
+  DOMSessionStorageManager();
+};
+
+} // ::dom
+} // ::mozilla
+
+#endif /* nsDOMStorageManager_h__ */
new file mode 100644
--- /dev/null
+++ b/dom/src/storage/DOMStorageObserver.cpp
@@ -0,0 +1,332 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "DOMStorageObserver.h"
+
+#include "DOMStorageDBThread.h"
+#include "DOMStorageCache.h"
+
+#include "nsIObserverService.h"
+#include "nsIURI.h"
+#include "nsIURL.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIPermission.h"
+#include "nsIIDNService.h"
+#include "mozIApplicationClearPrivateDataParams.h"
+#include "nsICookiePermission.h"
+
+#include "nsPrintfCString.h"
+#include "nsXULAppAPI.h"
+#include "nsEscape.h"
+#include "nsNetCID.h"
+#include "mozilla/Services.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+static const char kStartupTopic[] = "sessionstore-windows-restored";
+static const uint32_t kStartupDelay = 0;
+
+NS_IMPL_ISUPPORTS2(DOMStorageObserver,
+                   nsIObserver,
+                   nsISupportsWeakReference)
+
+DOMStorageObserver* DOMStorageObserver::sSelf = nullptr;
+
+extern nsresult
+CreateReversedDomain(const nsACString& aAsciiDomain, nsACString& aKey);
+
+// static
+nsresult
+DOMStorageObserver::Init()
+{
+  if (sSelf) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (!obs) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  sSelf = new DOMStorageObserver();
+  NS_ADDREF(sSelf);
+
+  // Chrome clear operations.
+  obs->AddObserver(sSelf, kStartupTopic, true);
+  obs->AddObserver(sSelf, "cookie-changed", true);
+  obs->AddObserver(sSelf, "perm-changed", true);
+  obs->AddObserver(sSelf, "browser:purge-domain-data", true);
+  obs->AddObserver(sSelf, "last-pb-context-exited", true);
+  obs->AddObserver(sSelf, "webapps-clear-data", true);
+
+  // Shutdown
+  obs->AddObserver(sSelf, "profile-after-change", true);
+  obs->AddObserver(sSelf, "profile-before-change", true);
+  obs->AddObserver(sSelf, "xpcom-shutdown", true);
+
+#ifdef DOM_STORAGE_TESTS
+  // Testing
+  obs->AddObserver(sSelf, "domstorage-test-flush-force", true);
+  if (XRE_GetProcessType() == GeckoProcessType_Default) {
+    // Only to forward to child process.
+    obs->AddObserver(sSelf, "domstorage-test-flushed", true);
+  }
+
+  obs->AddObserver(sSelf, "domstorage-test-reload", true);
+#endif
+
+  return NS_OK;
+}
+
+// static
+nsresult
+DOMStorageObserver::Shutdown()
+{
+  if (!sSelf) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  NS_RELEASE(sSelf);
+  return NS_OK;
+}
+
+void
+DOMStorageObserver::AddSink(DOMStorageObserverSink* aObs)
+{
+  mSinks.AppendElement(aObs);
+}
+
+void
+DOMStorageObserver::RemoveSink(DOMStorageObserverSink* aObs)
+{
+  mSinks.RemoveElement(aObs);
+}
+
+void
+DOMStorageObserver::Notify(const char* aTopic, const nsACString& aData)
+{
+  for (uint32_t i = 0; i < mSinks.Length(); ++i) {
+    DOMStorageObserverSink* sink = mSinks[i];
+    sink->Observe(aTopic, aData);
+  }
+}
+
+NS_IMETHODIMP
+DOMStorageObserver::Observe(nsISupports* aSubject,
+                            const char* aTopic,
+                            const PRUnichar* aData)
+{
+  nsresult rv;
+
+  // Start the thread that opens the database.
+  if (!strcmp(aTopic, kStartupTopic)) {
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    obs->RemoveObserver(this, kStartupTopic);
+
+    mDBThreadStartDelayTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+    if (!mDBThreadStartDelayTimer) {
+      return NS_ERROR_UNEXPECTED;
+    }
+
+    mDBThreadStartDelayTimer->Init(this, nsITimer::TYPE_ONE_SHOT, kStartupDelay);
+
+    return NS_OK;
+  }
+
+  // Timer callback used to start the database a short timer after startup
+  if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) {
+    nsCOMPtr<nsITimer> timer = do_QueryInterface(aSubject);
+    if (!timer) {
+      return NS_ERROR_UNEXPECTED;
+    }
+
+    if (timer == mDBThreadStartDelayTimer) {
+      mDBThreadStartDelayTimer = nullptr;
+
+      DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+      NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
+    }
+
+    return NS_OK;
+  }
+
+  // Clear everything, caches + database
+  if (!strcmp(aTopic, "cookie-changed")) {
+    if (!NS_LITERAL_STRING("cleared").Equals(aData)) {
+      return NS_OK;
+    }
+
+    DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+    NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
+
+    db->AsyncClearAll();
+
+    Notify("cookie-cleared");
+
+    return NS_OK;
+  }
+
+  // Clear from caches everything that has been stored
+  // while in session-only mode
+  if (!strcmp(aTopic, "perm-changed")) {
+    // Check for cookie permission change
+    nsCOMPtr<nsIPermission> perm(do_QueryInterface(aSubject));
+    if (!perm) {
+      return NS_OK;
+    }
+
+    nsAutoCString type;
+    perm->GetType(type);
+    if (type != NS_LITERAL_CSTRING("cookie")) {
+      return NS_OK;
+    }
+
+    uint32_t cap = 0;
+    perm->GetCapability(&cap);
+    if (!(cap & nsICookiePermission::ACCESS_SESSION) ||
+        !NS_LITERAL_STRING("deleted").Equals(nsDependentString(aData))) {
+      return NS_OK;
+    }
+
+    nsAutoCString host;
+    perm->GetHost(host);
+    if (host.IsEmpty()) {
+      return NS_OK;
+    }
+
+    nsAutoCString scope;
+    rv = CreateReversedDomain(host, scope);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    Notify("session-only-cleared", scope);
+
+    return NS_OK;
+  }
+
+  // Clear everything (including so and pb data) from caches and database
+  // for the gived domain and subdomains.
+  if (!strcmp(aTopic, "browser:purge-domain-data")) {
+    // Convert the domain name to the ACE format
+    nsAutoCString aceDomain;
+    nsCOMPtr<nsIIDNService> converter = do_GetService(NS_IDNSERVICE_CONTRACTID);
+    if (converter) {
+      rv = converter->ConvertUTF8toACE(NS_ConvertUTF16toUTF8(aData), aceDomain);
+      NS_ENSURE_SUCCESS(rv, rv);
+    } else {
+      // In case the IDN service is not available, this is the best we can come up with!
+      NS_EscapeURL(NS_ConvertUTF16toUTF8(aData),
+                   esc_OnlyNonASCII | esc_AlwaysCopy,
+                   aceDomain);
+    }
+
+    nsAutoCString scopePrefix;
+    rv = CreateReversedDomain(aceDomain, scopePrefix);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+    NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
+
+    db->AsyncClearMatchingScope(scopePrefix);
+
+    Notify("domain-data-cleared", scopePrefix);
+
+    return NS_OK;
+  }
+
+  // Clear all private-browsing caches
+  if (!strcmp(aTopic, "last-pb-context-exited")) {
+    Notify("private-browsing-data-cleared");
+
+    return NS_OK;
+  }
+
+  // Clear data beloging to an app.
+  if (!strcmp(aTopic, "webapps-clear-data")) {
+    nsCOMPtr<mozIApplicationClearPrivateDataParams> params =
+      do_QueryInterface(aSubject);
+    if (!params) {
+      NS_ERROR("'webapps-clear-data' notification's subject should be a mozIApplicationClearPrivateDataParams");
+      return NS_ERROR_UNEXPECTED;
+    }
+
+    uint32_t appId;
+    bool browserOnly;
+
+    rv = params->GetAppId(&appId);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = params->GetBrowserOnly(&browserOnly);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    MOZ_ASSERT(appId != nsIScriptSecurityManager::UNKNOWN_APP_ID);
+
+    DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+    NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
+
+    nsAutoCString scope;
+    scope.AppendInt(appId);
+    scope.Append(NS_LITERAL_CSTRING(":t:"));
+    db->AsyncClearMatchingScope(scope);
+    Notify("app-data-cleared", scope);
+
+    if (!browserOnly) {
+      scope.Truncate();
+      scope.AppendInt(appId);
+      scope.Append(NS_LITERAL_CSTRING(":f:"));
+      db->AsyncClearMatchingScope(scope);
+      Notify("app-data-cleared", scope);
+    }
+
+    return NS_OK;
+  }
+
+  if (!strcmp(aTopic, "profile-after-change")) {
+    Notify("profile-change");
+
+    return NS_OK;
+  }
+
+  if (!strcmp(aTopic, "profile-before-change") ||
+      !strcmp(aTopic, "xpcom-shutdown")) {
+    rv = DOMStorageCache::StopDatabase();
+    if (NS_FAILED(rv)) {
+      NS_WARNING("Error while stopping DOMStorage DB background thread");
+    }
+
+    return NS_OK;
+  }
+
+#ifdef DOM_STORAGE_TESTS
+  if (!strcmp(aTopic, "domstorage-test-flush-force")) {
+    DOMStorageDBBridge* db = DOMStorageCache::GetDatabase();
+    if (db) {
+      db->AsyncFlush();
+    }
+
+    return NS_OK;
+  }
+
+  if (!strcmp(aTopic, "domstorage-test-flushed")) {
+    // Only used to propagate to IPC children
+    Notify("test-flushed");
+
+    return NS_OK;
+  }
+
+  if (!strcmp(aTopic, "domstorage-test-reload")) {
+    Notify("test-reload");
+
+    return NS_OK;
+  }
+#endif
+
+  NS_ERROR("Unexpected topic");
+  return NS_ERROR_UNEXPECTED;
+}
+
+} // ::dom
+} // ::mozilla
new file mode 100644
--- /dev/null
+++ b/dom/src/storage/DOMStorageObserver.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef nsIDOMStorageObserver_h__
+#define nsIDOMStorageObserver_h__
+
+#include "nsIObserver.h"
+#include "nsITimer.h"
+#include "nsWeakReference.h"
+#include "nsTArray.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace dom {
+
+class DOMStorageObserver;
+
+// Implementers are DOMStorageManager and DOMStorageDBParent to forward to
+// child processes.
+class DOMStorageObserverSink
+{
+public:
+  virtual ~DOMStorageObserverSink() {}
+
+private:
+  friend class DOMStorageObserver;
+  virtual nsresult Observe(const char* aTopic, const nsACString& aScopePrefix) = 0;
+};
+
+// Statically (though layout statics) initialized observer receiving and processing
+// chrome clearing notifications, such as cookie deletion etc.
+class DOMStorageObserver : public nsIObserver
+                         , public nsSupportsWeakReference
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  virtual ~DOMStorageObserver() {}
+
+  static nsresult Init();
+  static nsresult Shutdown();
+  static DOMStorageObserver* Self() { return sSelf; }
+
+  void AddSink(DOMStorageObserverSink* aObs);
+  void RemoveSink(DOMStorageObserverSink* aObs);
+  void Notify(const char* aTopic, const nsACString& aData = EmptyCString());
+
+private:
+  static DOMStorageObserver* sSelf;
+
+  // Weak references
+  nsTArray<DOMStorageObserverSink*> mSinks;
+  nsCOMPtr<nsITimer> mDBThreadStartDelayTimer;
+};
+
+} // ::dom
+} // ::mozilla
+
+#endif
--- a/dom/src/storage/Makefile.in
+++ b/dom/src/storage/Makefile.in
@@ -10,33 +10,35 @@ VPATH          = @srcdir@
 FAIL_ON_WARNINGS := 1
 
 include $(DEPTH)/config/autoconf.mk
 
 LIBRARY_NAME   = jsdomstorage_s
 LIBXUL_LIBRARY = 1
 
 CPPSRCS = \
-       nsDOMStorage.cpp \
-       nsDOMStorageBaseDB.cpp \
-       nsDOMStorageDBWrapper.cpp \
-       nsLocalStorageCache.cpp \
-       nsDOMStoragePersistentDB.cpp \
-       nsDOMStorageMemoryDB.cpp \
-       StorageChild.cpp \
-       StorageParent.cpp \
+       DOMStorage.cpp \
+       DOMStorageCache.cpp \
+       DOMStorageDBThread.cpp \
+       DOMStorageObserver.cpp \
+       DOMStorageManager.cpp \
+       DOMStorageIPC.cpp \
        $(NULL)
 
 EXPORTS_NAMESPACES = mozilla/dom
-EXPORTS_mozilla/dom = StorageChild.h StorageParent.h
+EXPORTS_mozilla/dom = DOMStorageIPC.h
 
 # we don't want the shared lib, but we want to force the creation of a static lib.
 FORCE_STATIC_LIB = 1
 
 LOCAL_INCLUDES = \
                 -I$(topsrcdir)/dom/base \
 		-I$(topsrcdir)/content/events/src
 
 DEFINES += -D_IMPL_NS_LAYOUT
 
+ifdef ENABLE_TESTS
+DEFINES += -DDOM_STORAGE_TESTS
+endif
+
 include $(topsrcdir)/config/config.mk
 include $(topsrcdir)/ipc/chromium/chromium-config.mk
 include $(topsrcdir)/config/rules.mk
--- a/dom/src/storage/PStorage.ipdl
+++ b/dom/src/storage/PStorage.ipdl
@@ -1,67 +1,43 @@
 /* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
 /* vim: set sw=4 ts=8 et tw=80 ft=cpp : */
 /* 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/. */
 
 include protocol PContent;
 
-using mozilla::null_t;
-
 namespace mozilla {
 namespace dom {
 
-struct ItemData
-{
-  nsString value;
-  bool secure;
-};
-
-// A cross-process GetValue result -- either null, or containing the parameters
-// with which to initialize an nsIDOMStorageItem.
-union StorageItem
-{
-  null_t;
-  ItemData;
-};
-
-// This protocol is little more than a thin wrapper around the DOMStorageBase
-// class in nsDOMStorage.h.  The child implementation simply forwards the
-// arguments for any given call to the parent, and returns the result.
+/* This protocol bridges async access to the database thread running on the parent process
+ * and caches running on the child process.
+ */
 sync protocol PStorage
 {
   manager PContent;
 
 parent:
-  __delete__();
+  async __delete__();
+
+  sync  Preload(nsCString scope, uint32_t alreadyLoadedCount)
+    returns (nsString[] keys, nsString[] values, nsresult rv);
 
-  Init(bool useDB, bool sessionOnly, bool isPrivate,
-       nsCString scopeDBKey, nsCString quotaDBKey, uint32_t storageType);
+  async AsyncPreload(nsCString scope, bool priority);
+  async AsyncGetUsage(nsCString scope);
+  async AsyncAddItem(nsCString scope, nsString key, nsString value);
+  async AsyncUpdateItem(nsCString scope, nsString key, nsString value);
+  async AsyncRemoveItem(nsCString scope, nsString key);
+  async AsyncClear(nsCString scope);
+  async AsyncFlush();
   
-  sync GetKeys(bool callerSecure)
-      returns (nsString[] keys);
-  sync GetLength(bool callerSecure, bool sessionOnly)
-      returns (uint32_t length, nsresult rv);
-  sync GetKey(bool callerSecure, bool sessionOnly, uint32_t index)
-      returns (nsString key, nsresult rv);
-  sync GetValue(bool callerSecure, bool sessionOnly, nsString key)
-      returns (StorageItem item, nsresult rv);
-  sync SetValue(bool callerSecure, bool sessionOnly, nsString key, nsString data)
-      returns (nsString oldValue, nsresult rv);
-  sync RemoveValue(bool callerSecure, bool sessionOnly, nsString key)
-      returns (nsString oldValue, nsresult rv);
-  sync Clear(bool callerSecure, bool sessionOnly)
-      returns (int32_t oldCount, nsresult rv);
-
-  sync GetDBValue(nsString key)
-      returns (nsString value, bool secure, nsresult rv);
-  sync SetDBValue(nsString key, nsString value, bool secure)
-      returns (nsresult rv);
-  sync SetSecure(nsString key, bool secure)
-      returns (nsresult rv);
-
-  UpdatePrivateState(bool enabled);
+child:
+  async Observe(nsCString topic, nsCString scopePrefix);
+  async ScopesHavingData(nsCString[] scopes);
+  async LoadItem(nsCString scope, nsString key, nsString value);
+  async LoadDone(nsCString scope, nsresult rv);
+  async LoadUsage(nsCString scope, int64_t usage);
+  async Error(nsresult rv);
 };
 
 }
 }
deleted file mode 100644
--- a/dom/src/storage/StorageChild.cpp
+++ /dev/null
@@ -1,250 +0,0 @@
-/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
-/* vim: set sw=4 ts=8 et tw=80 ft=cpp : */
-/* 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/. */
-
-#include "StorageChild.h"
-#include "mozilla/dom/ContentChild.h"
-#include "nsError.h"
-
-#include "GeckoProfiler.h"
-
-namespace mozilla {
-namespace dom {
-
-NS_IMPL_CYCLE_COLLECTION_1(StorageChild, mStorage)
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(StorageChild)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StorageChild)
-  NS_INTERFACE_MAP_ENTRY(nsIPrivacyTransitionObserver)
-  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrivacyTransitionObserver)
-NS_INTERFACE_MAP_END
-
-NS_IMETHODIMP_(nsrefcnt) StorageChild::Release(void)
-{
-  NS_PRECONDITION(0 != mRefCnt, "dup release");
-  NS_ASSERT_OWNINGTHREAD(StorageChild);
-  nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(StorageChild)::Upcast(this);
-  nsrefcnt count = mRefCnt.decr(base);
-  NS_LOG_RELEASE(this, count, "StorageChild");
-  if (count == 1 && mIPCOpen) {
-    Send__delete__(this);
-    return 0;
-  }
-  if (count == 0) {
-    mRefCnt.stabilizeForDeletion();
-    delete this;
-    return 0;
-  }
-  return count;
-}
-
-StorageChild::StorageChild(nsDOMStorage* aOwner)
-: mStorage(aOwner)
-, mIPCOpen(false)
-{
-}
-
-StorageChild::StorageChild(nsDOMStorage* aOwner, StorageChild& aOther)
-: DOMStorageBase(aOther)
-, mStorage(aOwner)
-, mIPCOpen(false)
-{
-}
-
-void
-StorageChild::AddIPDLReference()
-{
-  NS_ABORT_IF_FALSE(!mIPCOpen, "Attempting to retain multiple IPDL references");
-  mIPCOpen = true;
-  AddRef();
-}
-
-void
-StorageChild::ReleaseIPDLReference()
-{
-  NS_ABORT_IF_FALSE(mIPCOpen, "Attempting to release non-existent IPDL reference");
-  mIPCOpen = false;
-  Release();
-}
-
-bool
-StorageChild::CacheStoragePermissions()
-{
-  nsDOMStorage* storage = static_cast<nsDOMStorage*>(mStorage.get());
-  return storage->CacheStoragePermissions();
-}
-
-void
-StorageChild::InitRemote()
-{
-  ContentChild* child = ContentChild::GetSingleton();
-  AddIPDLReference();
-  child->SendPStorageConstructor(this, null_t());
-  SendInit(mUseDB, mSessionOnly, mInPrivateBrowsing, mScopeDBKey,
-           mQuotaDBKey, mStorageType);
-}
-
-void
-StorageChild::InitAsSessionStorage(nsIPrincipal* aPrincipal, bool aPrivate)
-{
-  DOMStorageBase::InitAsSessionStorage(aPrincipal, aPrivate);
-  InitRemote();
-}
-
-void
-StorageChild::InitAsLocalStorage(nsIPrincipal* aPrincipal, bool aPrivate)
-{
-  DOMStorageBase::InitAsLocalStorage(aPrincipal, aPrivate);
-  InitRemote();
-}
-
-nsTArray<nsString>*
-StorageChild::GetKeys(bool aCallerSecure)
-{
-  InfallibleTArray<nsString> remoteKeys;
-  SendGetKeys(aCallerSecure, &remoteKeys);
-  nsTArray<nsString>* keys = new nsTArray<nsString>;
-  *keys = remoteKeys;
-  return keys;
-}
-
-nsresult
-StorageChild::GetLength(bool aCallerSecure, uint32_t* aLength)
-{
-  nsresult rv;
-  SendGetLength(aCallerSecure, mSessionOnly, aLength, &rv);
-  return rv;
-}
-
-nsresult
-StorageChild::GetKey(bool aCallerSecure, uint32_t aIndex, nsAString& aKey)
-{
-  nsresult rv;
-  nsString key;
-  SendGetKey(aCallerSecure, mSessionOnly, aIndex, &key, &rv);
-  if (NS_FAILED(rv))
-    return rv;
-  aKey = key;
-  return NS_OK;
-}
-
-// Unlike other cross-process forwarding methods, GetValue needs to replicate
-// the following behaviour of DOMStorageImpl::GetValue:
-//
-// - if a security error occurs, or the item isn't found, return null without
-//   propogating the error.
-//
-// If DOMStorageImpl::GetValue ever changes its behaviour, this should be kept
-// in sync.
-nsIDOMStorageItem*
-StorageChild::GetValue(bool aCallerSecure, const nsAString& aKey, nsresult* rv)
-{
-  PROFILER_LABEL("StorageChild", "GetValue");
-  nsresult rv2 = *rv = NS_OK;
-  StorageItem storageItem;
-  SendGetValue(aCallerSecure, mSessionOnly, nsString(aKey), &storageItem, &rv2);
-  if (rv2 == NS_ERROR_DOM_SECURITY_ERR || rv2 == NS_ERROR_DOM_NOT_FOUND_ERR)
-    return nullptr;
-  *rv = rv2;
-  if (NS_FAILED(*rv) || storageItem.type() == StorageItem::Tnull_t)
-    return nullptr;
-  const ItemData& data = storageItem.get_ItemData();
-  nsIDOMStorageItem* item = new nsDOMStorageItem(this, aKey, data.value(),
-                                                 data.secure());
-  return item;
-}
-
-nsresult
-StorageChild::SetValue(bool aCallerSecure, const nsAString& aKey,
-                       const nsAString& aData, nsAString& aOldData)
-{
-  nsresult rv;
-  nsString oldData;
-  SendSetValue(aCallerSecure, mSessionOnly, nsString(aKey), nsString(aData),
-               &oldData, &rv);
-  if (NS_FAILED(rv))
-    return rv;
-  aOldData = oldData;
-  return NS_OK;
-}
-
-nsresult
-StorageChild::RemoveValue(bool aCallerSecure, const nsAString& aKey,
-                          nsAString& aOldData)
-{
-  nsresult rv;
-  nsString oldData;
-  SendRemoveValue(aCallerSecure, mSessionOnly, nsString(aKey), &oldData, &rv);
-  if (NS_FAILED(rv))
-    return rv;
-  aOldData = oldData;
-  return NS_OK;
-}
-
-nsresult
-StorageChild::Clear(bool aCallerSecure, int32_t* aOldCount)
-{
-  nsresult rv;
-  int32_t oldCount;
-  SendClear(aCallerSecure, mSessionOnly, &oldCount, &rv);
-  if (NS_FAILED(rv))
-    return rv;
-  *aOldCount = oldCount;
-  return NS_OK;
-}
-
-nsresult
-StorageChild::GetDBValue(const nsAString& aKey, nsAString& aValue,
-                         bool* aSecure)
-{
-  nsresult rv;
-  nsString value;
-  SendGetDBValue(nsString(aKey), &value, aSecure, &rv);
-  aValue = value;
-  return rv;
-}
-
-nsresult
-StorageChild::SetDBValue(const nsAString& aKey,
-                         const nsAString& aValue,
-                         bool aSecure)
-{
-  nsresult rv;
-  SendSetDBValue(nsString(aKey), nsString(aValue), aSecure, &rv);
-  return rv;
-}
-
-nsresult
-StorageChild::SetSecure(const nsAString& aKey, bool aSecure)
-{
-  nsresult rv;
-  SendSetSecure(nsString(aKey), aSecure, &rv);
-  return rv;
-}
-
-nsresult
-StorageChild::CloneFrom(bool aCallerSecure, DOMStorageBase* aThat)
-{
-  StorageChild* other = static_cast<StorageChild*>(aThat);
-  ContentChild* child = ContentChild::GetSingleton();
-  StorageClone clone(nullptr, other, aCallerSecure);
-  AddIPDLReference();
-  child->SendPStorageConstructor(this, clone);
-  SendInit(mUseDB, mSessionOnly, mInPrivateBrowsing,
-           mScopeDBKey, mQuotaDBKey, mStorageType);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-StorageChild::PrivateModeChanged(bool enabled)
-{
-  mInPrivateBrowsing = enabled;
-  SendUpdatePrivateState(enabled);
-  return NS_OK;
-}
-
-}
-}
deleted file mode 100644
--- a/dom/src/storage/StorageChild.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
-/* vim: set sw=4 ts=8 et tw=80 ft=cpp : */
-/* 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/. */
-
-#ifndef mozilla_dom_StorageChild_h
-#define mozilla_dom_StorageChild_h
-
-#include "mozilla/dom/PStorageChild.h"
-#include "nsDOMStorage.h"
-#include "nsCycleCollectionParticipant.h"
-
-namespace mozilla {
-namespace dom {
-
-class StorageChild : public PStorageChild
-                   , public DOMStorageBase
-                   , public nsSupportsWeakReference
-{
-public:
-  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(StorageChild, nsIPrivacyTransitionObserver)
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_NSIPRIVACYTRANSITIONOBSERVER
-  
-  StorageChild(nsDOMStorage* aOwner);
-  StorageChild(nsDOMStorage* aOwner, StorageChild& aOther);
-
-  virtual void InitAsSessionStorage(nsIPrincipal* aPrincipal, bool aPrivate);
-  virtual void InitAsLocalStorage(nsIPrincipal* aPrincipal, bool aPrivate);
-
-  virtual bool CacheStoragePermissions();
-  
-  virtual nsTArray<nsString>* GetKeys(bool aCallerSecure);
-  virtual nsresult GetLength(bool aCallerSecure, uint32_t* aLength);
-  virtual nsresult GetKey(bool aCallerSecure, uint32_t aIndex, nsAString& aKey);
-  virtual nsIDOMStorageItem* GetValue(bool aCallerSecure, const nsAString& aKey,
-                                      nsresult* rv);
-  virtual nsresult SetValue(bool aCallerSecure, const nsAString& aKey,
-                            const nsAString& aData, nsAString& aOldValue);
-  virtual nsresult RemoveValue(bool aCallerSecure, const nsAString& aKey,
-                               nsAString& aOldValue);
-  virtual nsresult Clear(bool aCallerSecure, int32_t* aOldCount);
-
-  virtual nsresult GetDBValue(const nsAString& aKey,
-                              nsAString& aValue,
-                              bool* aSecure);
-  virtual nsresult SetDBValue(const nsAString& aKey,
-                              const nsAString& aValue,
-                              bool aSecure);
-  virtual nsresult SetSecure(const nsAString& aKey, bool aSecure);
-
-  virtual nsresult CloneFrom(bool aCallerSecure, DOMStorageBase* aThat);
-
-  void AddIPDLReference();
-  void ReleaseIPDLReference();
-
-private:
-  void InitRemote();
-
-  // Unimplemented
-  StorageChild(const StorageChild&);
-
-  nsCOMPtr<nsIDOMStorageObsolete> mStorage;
-  bool mIPCOpen;
-};
-
-}
-}
-
-#endif
deleted file mode 100644
--- a/dom/src/storage/StorageParent.cpp
+++ /dev/null
@@ -1,157 +0,0 @@
-/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
-/* vim: set sw=4 ts=8 et tw=80 ft=cpp : */
-/* 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/. */
-
-#include "StorageParent.h"
-#include "mozilla/dom/PContentParent.h"
-#include "mozilla/unused.h"
-#include "nsDOMString.h"
-
-using mozilla::unused;
-
-namespace mozilla {
-namespace dom {
-
-StorageParent::StorageParent(const StorageConstructData& aData)
-{
-  if (aData.type() == StorageConstructData::Tnull_t) {
-    mStorage = new DOMStorageImpl(nullptr);
-  } else {
-    const StorageClone& clone = aData.get_StorageClone();
-    StorageParent* other = static_cast<StorageParent*>(clone.actorParent());
-    mStorage = new DOMStorageImpl(nullptr, *other->mStorage.get());
-    mStorage->CloneFrom(clone.callerSecure(), other->mStorage);
-  }
-}
-
-bool
-StorageParent::RecvInit(const bool& aUseDB,
-                        const bool& aSessionOnly,
-                        const bool& aPrivate,
-                        const nsCString& aScopeDBKey,
-                        const nsCString& aQuotaDBKey,
-                        const uint32_t& aStorageType)
-{
-  mStorage->InitFromChild(aUseDB, aSessionOnly, aPrivate,
-                          aScopeDBKey, aQuotaDBKey,
-                          aStorageType);
-  return true;
-}
-
-bool
-StorageParent::RecvUpdatePrivateState(const bool& aEnabled)
-{
-  mStorage->PrivateModeChanged(aEnabled);
-  return true;
-}
-
-bool
-StorageParent::RecvGetKeys(const bool& aCallerSecure, InfallibleTArray<nsString>* aKeys)
-{
-  // Callers are responsible for deallocating the array returned by mStorage->GetKeys
-  nsAutoPtr<nsTArray<nsString> > keys(mStorage->GetKeys(aCallerSecure));
-  aKeys->SwapElements(*keys);
-  return true;
-}
-
-bool
-StorageParent::RecvGetLength(const bool& aCallerSecure, const bool& aSessionOnly,
-                             uint32_t* aLength, nsresult* rv)
-{
-  mStorage->SetSessionOnly(aSessionOnly);
-  *rv = mStorage->GetLength(aCallerSecure, aLength);
-  return true;
-}
-
-bool
-StorageParent::RecvGetKey(const bool& aCallerSecure, const bool& aSessionOnly,
-                          const uint32_t& aIndex, nsString* aKey, nsresult* rv)
-{
-  mStorage->SetSessionOnly(aSessionOnly);
-  *rv = mStorage->GetKey(aCallerSecure, aIndex, *aKey);
-  return true;
-}
-
-bool
-StorageParent::RecvGetValue(const bool& aCallerSecure, const bool& aSessionOnly,
-                            const nsString& aKey, StorageItem* aItem,
-                            nsresult* rv)
-{
-  mStorage->SetSessionOnly(aSessionOnly);
-
-  // We need to ensure that a proper null representation is sent to the child
-  // if no item is found or an error occurs.
-
-  *rv = NS_OK;
-  nsCOMPtr<nsIDOMStorageItem> item = mStorage->GetValue(aCallerSecure, aKey, rv);
-  if (NS_FAILED(*rv) || !item) {
-    *aItem = null_t();
-    return true;
-  }
-
-  ItemData data(EmptyString(), false);
-  nsDOMStorageItem* internalItem = static_cast<nsDOMStorageItem*>(item.get());
-  data.value() = internalItem->GetValueInternal();
-  if (aCallerSecure)
-    data.secure() = internalItem->IsSecure();
-  *aItem = data;
-  return true;
-}
-
-bool
-StorageParent::RecvSetValue(const bool& aCallerSecure, const bool& aSessionOnly,
-                            const nsString& aKey, const nsString& aData,
-                            nsString* aOldValue, nsresult* rv)
-{
-  mStorage->SetSessionOnly(aSessionOnly);
-  *rv = mStorage->SetValue(aCallerSecure, aKey, aData, *aOldValue);
-  return true;
-}
-
-bool
-StorageParent::RecvRemoveValue(const bool& aCallerSecure, const bool& aSessionOnly,
-                               const nsString& aKey, nsString* aOldValue,
-                               nsresult* rv)
-{
-  mStorage->SetSessionOnly(aSessionOnly);
-  *rv = mStorage->RemoveValue(aCallerSecure, aKey, *aOldValue);
-  return true;
-}
-
-bool
-StorageParent::RecvClear(const bool& aCallerSecure, const bool& aSessionOnly,
-                         int32_t* aOldCount, nsresult* rv)
-{
-  mStorage->SetSessionOnly(aSessionOnly);
-  *rv = mStorage->Clear(aCallerSecure, aOldCount);
-  return true;
-}
-
-bool
-StorageParent::RecvGetDBValue(const nsString& aKey, nsString* aValue,
-                              bool* aSecure, nsresult* rv)
-{
-  *rv = mStorage->GetDBValue(aKey, *aValue, aSecure);
-  return true;
-}
-
-bool
-StorageParent::RecvSetDBValue(const nsString& aKey, const nsString& aValue,
-                              const bool& aSecure, nsresult* rv)
-{
-  *rv = mStorage->SetDBValue(aKey, aValue, aSecure);
-  return true;
-}
-
-bool
-StorageParent::RecvSetSecure(const nsString& aKey, const bool& aSecure,
-                             nsresult* rv)
-{
-  *rv = mStorage->SetSecure(aKey, aSecure);
-  return true;
-}
-
-}
-}
deleted file mode 100644
--- a/dom/src/storage/StorageParent.h
+++ /dev/null
@@ -1,60 +0,0 @@
-/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
-/* vim: set sw=4 ts=8 et tw=80 ft=cpp : */
-/* 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/. */
-
-#ifndef mozilla_dom_StorageParent_h
-#define mozilla_dom_StorageParent_h
-
-#include "mozilla/dom/PStorageParent.h"
-#include "nsDOMStorage.h"
-
-namespace mozilla {
-namespace dom {
-
-class StorageConstructData;
-
-class StorageParent : public PStorageParent
-{
-public:
-  StorageParent(const StorageConstructData& aData);
-
-private:
-  bool RecvGetKeys(const bool& aCallerSecure, InfallibleTArray<nsString>* aKeys);
-  bool RecvGetLength(const bool& aCallerSecure, const bool& aSessionOnly,
-                     uint32_t* aLength, nsresult* rv);
-  bool RecvGetKey(const bool& aCallerSecure, const bool& aSessionOnly,
-                  const uint32_t& aIndex,nsString* aKey, nsresult* rv);
-  bool RecvGetValue(const bool& aCallerSecure, const bool& aSessionOnly,
-                    const nsString& aKey, StorageItem* aItem, nsresult* rv);
-  bool RecvSetValue(const bool& aCallerSecure, const bool& aSessionOnly,
-                    const nsString& aKey, const nsString& aData,
-                    nsString* aOldValue, nsresult* rv);
-  bool RecvRemoveValue(const bool& aCallerSecure, const bool& aSessionOnly,
-                       const nsString& aKey, nsString* aOldData, nsresult* rv);
-  bool RecvClear(const bool& aCallerSecure, const bool& aSessionOnly,
-                 int32_t* aOldCount, nsresult* rv);
-
-  bool RecvGetDBValue(const nsString& aKey, nsString* aValue, bool* aSecure,
-                      nsresult* rv);
-  bool RecvSetDBValue(const nsString& aKey, const nsString& aValue,
-                      const bool& aSecure, nsresult* rv);
-  bool RecvSetSecure(const nsString& aKey, const bool& aSecure, nsresult* rv);
-
-  bool RecvInit(const bool& aUseDB,
-                const bool& aSessionOnly,
-                const bool& aPrivate,
-                const nsCString& aScopeDBKey,
-                const nsCString& aQuotaDBKey,
-                const uint32_t& aStorageType);
-
-  bool RecvUpdatePrivateState(const bool& aEnabled);
-
-  nsRefPtr<DOMStorageImpl> mStorage;
-};
-
-}
-}
-
-#endif
deleted file mode 100644
--- a/dom/src/storage/nsDOMStorage.cpp
+++ /dev/null
@@ -1,1808 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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/. */
-
-#include "mozilla/DebugOnly.h"
-
-#include "StorageChild.h"
-#include "StorageParent.h"
-#include "mozilla/dom/ContentChild.h"
-#include "nsXULAppAPI.h"
-using mozilla::dom::StorageChild;
-using mozilla::dom::ContentChild;
-
-#include "prnetdb.h"
-#include "nsCOMPtr.h"
-#include "nsError.h"
-#include "nsDOMClassInfoID.h"
-#include "nsDOMJSUtils.h"
-#include "nsUnicharUtils.h"
-#include "nsDOMStorage.h"
-#include "nsEscape.h"
-#include "nsContentUtils.h"
-#include "nsIScriptSecurityManager.h"
-#include "nsIPrincipal.h"
-#include "nsIURI.h"
-#include "nsReadableUtils.h"
-#include "nsIObserverService.h"
-#include "nsNetUtil.h"
-#include "nsICookiePermission.h"
-#include "nsIPermission.h"
-#include "nsIPermissionManager.h"
-#include "nsCycleCollectionParticipant.h"
-#include "nsIJSContextStack.h"
-#include "nsDOMString.h"
-#include "nsNetCID.h"
-#include "mozilla/Preferences.h"
-#include "nsThreadUtils.h"
-#include "mozilla/Telemetry.h"
-#include "DictionaryHelpers.h"
-#include "GeneratedEvents.h"
-#include "mozIApplicationClearPrivateDataParams.h"
-
-// calls FlushAndEvictFromCache(false)
-#define NS_DOMSTORAGE_FLUSH_TIMER_TOPIC "domstorage-flush-timer"
-
-// calls FlushAndEvictFromCache(false)
-#define NS_DOMSTORAGE_FLUSH_FORCE_TOPIC "domstorage-flush-force"
-
-using namespace mozilla;
-
-static const uint32_t ASK_BEFORE_ACCEPT = 1;
-static const uint32_t ACCEPT_SESSION = 2;
-static const uint32_t BEHAVIOR_REJECT = 2;
-
-static const char kPermissionType[] = "cookie";
-static const char kStorageEnabled[] = "dom.storage.enabled";
-static const char kCookiesBehavior[] = "network.cookie.cookieBehavior";
-static const char kCookiesLifetimePolicy[] = "network.cookie.lifetimePolicy";
-
-//
-// Helper that tells us whether the caller is secure or not.
-//
-
-static bool
-IsCallerSecure()
-{
-  nsCOMPtr<nsIPrincipal> subjectPrincipal;
-  nsresult rv = nsContentUtils::GetSecurityManager()->
-                  GetSubjectPrincipal(getter_AddRefs(subjectPrincipal));
-  NS_ENSURE_SUCCESS(rv, false);
-
-  if (!subjectPrincipal) {
-    // No subject principal means no code is running. Default to not
-    // being secure in that case.
-
-    return false;
-  }
-
-  nsCOMPtr<nsIURI> codebase;
-  subjectPrincipal->GetURI(getter_AddRefs(codebase));
-
-  if (!codebase) {
-    return false;
-  }
-
-  nsCOMPtr<nsIURI> innerUri = NS_GetInnermostURI(codebase);
-
-  if (!innerUri) {
-    return false;
-  }
-
-  bool isHttps = false;
-  rv = innerUri->SchemeIs("https", &isHttps);
-
-  return NS_SUCCEEDED(rv) && isHttps;
-}
-
-nsSessionStorageEntry::nsSessionStorageEntry(KeyTypePointer aStr)
-  : nsStringHashKey(aStr), mItem(nullptr)
-{
-}
-
-nsSessionStorageEntry::nsSessionStorageEntry(const nsSessionStorageEntry& aToCopy)
-  : nsStringHashKey(aToCopy), mItem(nullptr)
-{
-  NS_ERROR("We're horked.");
-}
-
-nsSessionStorageEntry::~nsSessionStorageEntry()
-{
-}
-
-//
-// nsDOMStorageManager
-//
-
-nsDOMStorageManager* nsDOMStorageManager::gStorageManager;
-
-nsDOMStorageManager::nsDOMStorageManager()
-{
-}
-
-NS_IMPL_ISUPPORTS3(nsDOMStorageManager,
-                   nsIDOMStorageManager,
-                   nsIObserver,
-                   nsISupportsWeakReference)
-
-//static
-nsresult
-nsDOMStorageManager::Initialize()
-{
-  gStorageManager = new nsDOMStorageManager();
-  if (!gStorageManager)
-    return NS_ERROR_OUT_OF_MEMORY;
-
-  gStorageManager->mStorages.Init();
-  NS_ADDREF(gStorageManager);
-
-  // No observers needed in non-chrome
-  if (XRE_GetProcessType() != GeckoProcessType_Default)
-    return NS_OK;
-
-  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
-  if (!os)
-    return NS_OK;
-
-  nsresult rv;
-  rv = os->AddObserver(gStorageManager, "cookie-changed", true);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = os->AddObserver(gStorageManager, "profile-after-change", true);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = os->AddObserver(gStorageManager, "perm-changed", true);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = os->AddObserver(gStorageManager, "browser:purge-domain-data", true);
-  NS_ENSURE_SUCCESS(rv, rv);
-  // Used for temporary table flushing
-  rv = os->AddObserver(gStorageManager, "profile-before-change", true);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = os->AddObserver(gStorageManager, NS_DOMSTORAGE_FLUSH_TIMER_TOPIC, true);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = os->AddObserver(gStorageManager, "last-pb-context-exited", true);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = os->AddObserver(gStorageManager, "webapps-clear-data", true);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
-//static
-nsDOMStorageManager*
-nsDOMStorageManager::GetInstance()
-{
-  NS_ASSERTION(gStorageManager,
-               "nsDOMStorageManager::GetInstance() called before Initialize()");
-  NS_IF_ADDREF(gStorageManager);
-  return gStorageManager;
-}
-
-//static
-void
-nsDOMStorageManager::Shutdown()
-{
-  NS_IF_RELEASE(gStorageManager);
-  gStorageManager = nullptr;
-
-  ShutdownDB();
-}
-
-//static
-void
-nsDOMStorageManager::ShutdownDB()
-{
-  delete DOMStorageImpl::gStorageDB;
-  DOMStorageImpl::gStorageDB = nullptr;
-}
-
-static PLDHashOperator
-ClearStorage(nsDOMStorageEntry* aEntry, void* userArg)
-{
-  aEntry->mStorage->ClearAll();
-  return PL_DHASH_REMOVE;
-}
-
-static PLDHashOperator
-ClearStorageIfDomainMatches(nsDOMStorageEntry* aEntry, void* userArg)
-{
-  nsAutoCString* aKey = static_cast<nsAutoCString*> (userArg);
-  if (StringBeginsWith(aEntry->mStorage->GetScopeDBKey(), *aKey)) {
-    aEntry->mStorage->ClearAll();
-  }
-  return PL_DHASH_REMOVE;
-}
-
-nsresult
-nsDOMStorageManager::Observe(nsISupports *aSubject,
-                             const char *aTopic,
-                             const PRUnichar *aData)
-{
-  if (!strcmp(aTopic, "profile-after-change")) {
-  } else if (!strcmp(aTopic, "cookie-changed") &&
-             !nsCRT::strcmp(aData, NS_LITERAL_STRING("cleared").get())) {
-    mStorages.EnumerateEntries(ClearStorage, nullptr);
-
-    nsresult rv = DOMStorageImpl::InitDB();
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    return DOMStorageImpl::gStorageDB->RemoveAll();
-  } else if (!strcmp(aTopic, "perm-changed")) {
-    // Check for cookie permission change
-    nsCOMPtr<nsIPermission> perm(do_QueryInterface(aSubject));
-    if (perm) {
-      nsAutoCString type;
-      perm->GetType(type);
-      if (type != NS_LITERAL_CSTRING("cookie"))
-        return NS_OK;
-
-      uint32_t cap = 0;
-      perm->GetCapability(&cap);
-      if (!(cap & nsICookiePermission::ACCESS_SESSION) ||
-          nsDependentString(aData) != NS_LITERAL_STRING("deleted"))
-        return NS_OK;
-
-      nsAutoCString host;
-      perm->GetHost(host);
-      if (host.IsEmpty())
-        return NS_OK;
-
-      nsresult rv = DOMStorageImpl::InitDB();
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      return DOMStorageImpl::gStorageDB->DropSessionOnlyStoragesForHost(host);
-    }
-  } else if (!strcmp(aTopic, "timer-callback")) {
-    nsCOMPtr<nsIObserverService> obsserv = mozilla::services::GetObserverService();
-    if (obsserv)
-      obsserv->NotifyObservers(nullptr, NS_DOMSTORAGE_FLUSH_TIMER_TOPIC, nullptr);
-  } else if (!strcmp(aTopic, "browser:purge-domain-data")) {
-    // Convert the domain name to the ACE format
-    nsAutoCString aceDomain;
-    nsresult rv;
-    nsCOMPtr<nsIIDNService> converter = do_GetService(NS_IDNSERVICE_CONTRACTID);
-    if (converter) {
-      rv = converter->ConvertUTF8toACE(NS_ConvertUTF16toUTF8(aData), aceDomain);
-      NS_ENSURE_SUCCESS(rv, rv);
-    } else {
-      // In case the IDN service is not available, this is the best we can come up with!
-      NS_EscapeURL(NS_ConvertUTF16toUTF8(aData),
-                   esc_OnlyNonASCII | esc_AlwaysCopy,
-                   aceDomain);
-    }
-
-    nsAutoCString key;
-    rv = nsDOMStorageDBWrapper::CreateReversedDomain(aceDomain, key);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    // Clear the storage entries for matching domains
-    mStorages.EnumerateEntries(ClearStorageIfDomainMatches, &key);
-
-    rv = DOMStorageImpl::InitDB();
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    DOMStorageImpl::gStorageDB->RemoveOwner(aceDomain);
-  } else if (!strcmp(aTopic, "profile-before-change")) {
-    if (DOMStorageImpl::gStorageDB) {
-      DebugOnly<nsresult> rv =
-        DOMStorageImpl::gStorageDB->FlushAndEvictFromCache(true);
-      NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
-                       "DOMStorage: cache commit failed");
-      DOMStorageImpl::gStorageDB->Close();
-      nsDOMStorageManager::ShutdownDB();
-    }
-  } else if (!strcmp(aTopic, NS_DOMSTORAGE_FLUSH_TIMER_TOPIC)) {
-    if (DOMStorageImpl::gStorageDB) {
-      DebugOnly<nsresult> rv =
-        DOMStorageImpl::gStorageDB->FlushAndEvictFromCache(false);
-      NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
-                       "DOMStorage: cache commit failed");
-    }
-  } else if (!strcmp(aTopic, NS_DOMSTORAGE_FLUSH_FORCE_TOPIC)) {
-    if (DOMStorageImpl::gStorageDB) {
-      DebugOnly<nsresult> rv =
-        DOMStorageImpl::gStorageDB->FlushAndEvictFromCache(false);
-      NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
-                       "DOMStorage: cache  commit failed");
-    }
-  } else if (!strcmp(aTopic, "last-pb-context-exited")) {
-    if (DOMStorageImpl::gStorageDB) {
-      return DOMStorageImpl::gStorageDB->DropPrivateBrowsingStorages();
-    }
-  } else if (!strcmp(aTopic, "webapps-clear-data")) {
-    if (!DOMStorageImpl::gStorageDB) {
-      return NS_OK;
-    }
-
-    nsCOMPtr<mozIApplicationClearPrivateDataParams> params =
-      do_QueryInterface(aSubject);
-    if (!params) {
-      NS_ERROR("'webapps-clear-data' notification's subject should be a mozIApplicationClearPrivateDataParams");
-      return NS_ERROR_UNEXPECTED;
-    }
-
-    uint32_t appId;
-    bool browserOnly;
-
-    nsresult rv = params->GetAppId(&appId);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = params->GetBrowserOnly(&browserOnly);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    MOZ_ASSERT(appId != nsIScriptSecurityManager::UNKNOWN_APP_ID);
-
-    return DOMStorageImpl::gStorageDB->RemoveAllForApp(appId, browserOnly);
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsDOMStorageManager::GetUsage(const nsAString& aDomain,
-                              int32_t *aUsage)
-{
-  nsresult rv = DOMStorageImpl::InitDB();
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return DOMStorageImpl::gStorageDB->GetUsage(NS_ConvertUTF16toUTF8(aDomain),
-                                              aUsage, false);
-}
-
-NS_IMETHODIMP
-nsDOMStorageManager::GetLocalStorageForPrincipal(nsIPrincipal *aPrincipal,
-                                                 const nsSubstring &aDocumentURI,
-                                                 bool aPrivate,
-                                                 nsIDOMStorage **aResult)
-{
-  NS_ENSURE_ARG_POINTER(aPrincipal);
-  *aResult = nullptr;
-
-  nsresult rv;
-
-  nsRefPtr<nsDOMStorage2> storage = new nsDOMStorage2();
-  if (!storage)
-    return NS_ERROR_OUT_OF_MEMORY;
-
-  rv = storage->InitAsLocalStorage(aPrincipal, aDocumentURI, aPrivate);
-  if (NS_FAILED(rv))
-    return rv;
-
-  *aResult = storage.get();
-  storage.forget();
-
-  return NS_OK;
-}
-
-void
-nsDOMStorageManager::AddToStoragesHash(DOMStorageImpl* aStorage)
-{
-  nsDOMStorageEntry* entry = mStorages.PutEntry(aStorage);
-  if (entry)
-    entry->mStorage = aStorage;
-}
-
-void
-nsDOMStorageManager::RemoveFromStoragesHash(DOMStorageImpl* aStorage)
-{
-  nsDOMStorageEntry* entry = mStorages.GetEntry(aStorage);
-  if (entry)
-    mStorages.RemoveEntry(aStorage);
-}
-
-//
-// nsDOMStorage
-//
-
-nsDOMStorageDBWrapper* DOMStorageImpl::gStorageDB = nullptr;
-
-nsDOMStorageEntry::nsDOMStorageEntry(KeyTypePointer aStr)
-  : nsPtrHashKey<const void>(aStr), mStorage(nullptr)
-{
-}
-
-nsDOMStorageEntry::nsDOMStorageEntry(const nsDOMStorageEntry& aToCopy)
-  : nsPtrHashKey<const void>(aToCopy), mStorage(nullptr)
-{
-  NS_ERROR("DOMStorage horked.");
-}
-
-nsDOMStorageEntry::~nsDOMStorageEntry()
-{
-}
-
-NS_IMPL_CYCLE_COLLECTION_1(nsDOMStorage, mStorageImpl)
-
-DOMCI_DATA(StorageObsolete, nsDOMStorage)
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMStorage)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMStorage)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMStorage)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMStorageObsolete)
-  NS_INTERFACE_MAP_ENTRY(nsIDOMStorageObsolete)
-  NS_INTERFACE_MAP_ENTRY(nsPIDOMStorage)
-  NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
-  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(StorageObsolete)
-NS_INTERFACE_MAP_END
-
-NS_IMETHODIMP
-nsDOMStorage::GetInterface(const nsIID & aIID, void **result)
-{
-  nsresult rv = mStorageImpl->QueryInterface(aIID, result);
-  if (NS_SUCCEEDED(rv))
-    return rv;
-  return QueryInterface(aIID, result);;
-}
-
-nsresult
-NS_NewDOMStorage2(nsISupports* aOuter, REFNSIID aIID, void** aResult)
-{
-  nsDOMStorage2* storage = new nsDOMStorage2();
-  return storage->QueryInterface(aIID, aResult);
-}
-
-DOMStorageBase::DOMStorageBase()
-  : mStorageType(nsPIDOMStorage::Unknown)
-  , mUseDB(false)
-  , mSessionOnly(true)
-  , mInPrivateBrowsing(false)
-{
-}
-
-DOMStorageBase::DOMStorageBase(DOMStorageBase& aThat)
-  : mStorageType(aThat.mStorageType)
-  , mUseDB(false) // Clones don't use the DB
-  , mSessionOnly(true)
-  , mScopeDBKey(aThat.mScopeDBKey)
-  , mQuotaDBKey(aThat.mQuotaDBKey)
-  , mInPrivateBrowsing(aThat.mInPrivateBrowsing)
-{
-}
-
-void
-DOMStorageBase::InitAsSessionStorage(nsIPrincipal* aPrincipal, bool aPrivate)
-{
-  MOZ_ASSERT(mQuotaDBKey.IsEmpty());
-  mUseDB = false;
-  mScopeDBKey.Truncate();
-  mStorageType = nsPIDOMStorage::SessionStorage;
-  mInPrivateBrowsing = aPrivate;
-}
-
-void
-DOMStorageBase::InitAsLocalStorage(nsIPrincipal* aPrincipal, bool aPrivate)
-{
-  nsDOMStorageDBWrapper::CreateScopeDBKey(aPrincipal, mScopeDBKey);
-
-  // XXX Bug 357323, we have to solve the issue how to define
-  // origin for file URLs. In that case CreateOriginScopeDBKey
-  // fails (the result is empty) and we must avoid database use
-  // in that case because it produces broken entries w/o owner.
-  mUseDB = !mScopeDBKey.IsEmpty();
-
-  nsDOMStorageDBWrapper::CreateQuotaDBKey(aPrincipal, mQuotaDBKey);
-  mStorageType = nsPIDOMStorage::LocalStorage;
-  mInPrivateBrowsing = aPrivate;
-}
-
-PLDHashOperator
-SessionStorageTraverser(nsSessionStorageEntry* aEntry, void* userArg) {
-  nsCycleCollectionTraversalCallback *cb = 
-      static_cast<nsCycleCollectionTraversalCallback*>(userArg);
-
-  cb->NoteXPCOMChild((nsIDOMStorageItem *) aEntry->mItem);
-
-  return PL_DHASH_NEXT;
-}
-
-NS_IMPL_CYCLE_COLLECTION_UNLINK_0(DOMStorageImpl)
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMStorageImpl)
-{
-  if (tmp->mItems.IsInitialized()) {
-    tmp->mItems.EnumerateEntries(SessionStorageTraverser, &cb);
-  }
-}
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMStorageImpl)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMStorageImpl)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMStorageImpl)
-  NS_INTERFACE_MAP_ENTRY(nsIPrivacyTransitionObserver)
-  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrivacyTransitionObserver)
-NS_INTERFACE_MAP_END
-
-DOMStorageImpl::DOMStorageImpl(nsDOMStorage* aStorage)
-{
-  Init(aStorage);
-}
-
-DOMStorageImpl::DOMStorageImpl(nsDOMStorage* aStorage, DOMStorageImpl& aThat)
-  : DOMStorageBase(aThat)
-{
-  Init(aStorage);
-}
-
-void
-DOMStorageImpl::Init(nsDOMStorage* aStorage)
-{
-  mItemsCachedVersion = 0;
-  mItems.Init(8);
-  mOwner = aStorage;
-  if (nsDOMStorageManager::gStorageManager)
-    nsDOMStorageManager::gStorageManager->AddToStoragesHash(this);
-}
-
-DOMStorageImpl::~DOMStorageImpl()
-{
-  if (nsDOMStorageManager::gStorageManager)
-    nsDOMStorageManager::gStorageManager->RemoveFromStoragesHash(this);
-}
-
-nsresult
-DOMStorageImpl::InitDB()
-{
-  if (!gStorageDB) {
-    gStorageDB = new nsDOMStorageDBWrapper();
-    if (!gStorageDB)
-      return NS_ERROR_OUT_OF_MEMORY;
-
-    nsresult rv = gStorageDB->Init();
-    if (NS_FAILED(rv)) {
-      // Failed to initialize the DB, delete it and null out the
-      // pointer so we don't end up attempting to use an
-      // un-initialized DB later on.
-
-      delete gStorageDB;
-      gStorageDB = nullptr;
-
-      return rv;
-    }
-  }
-
-  return NS_OK;
-}
-
-void
-DOMStorageImpl::InitFromChild(bool aUseDB,
-                              bool aSessionOnly, bool aPrivate,
-                              const nsACString& aScopeDBKey,
-                              const nsACString& aQuotaDBKey,
-                              uint32_t aStorageType)
-{
-  mUseDB = aUseDB;
-  mSessionOnly = aSessionOnly;
-  mInPrivateBrowsing = aPrivate;
-  mScopeDBKey = aScopeDBKey;
-  mQuotaDBKey = aQuotaDBKey;
-  mStorageType = static_cast<nsPIDOMStorage::nsDOMStorageType>(aStorageType);
-}
-
-void
-DOMStorageImpl::SetSessionOnly(bool aSessionOnly)
-{
-  mSessionOnly = aSessionOnly;
-}
-
-bool
-DOMStorageImpl::CacheStoragePermissions()
-{
-  // If this is a cross-process situation, we don't have a real storage owner.
-  // All the correct checks have been done on the child, so we just need to
-  // make sure that our session-only status is correctly updated.
-  if (!mOwner)
-    return CanUseStorage();
-  
-  return mOwner->CacheStoragePermissions();
-}
-
-nsresult
-DOMStorageImpl::GetCachedValue(const nsAString& aKey, nsAString& aValue,
-                               bool* aSecure)
-{
-  aValue.Truncate();
-  *aSecure = false;
-
-  nsSessionStorageEntry *entry = mItems.GetEntry(aKey);
-  if (!entry)
-    return NS_ERROR_NOT_AVAILABLE;
-
-  aValue = entry->mItem->GetValueInternal();
-  *aSecure = entry->mItem->IsSecure();
-
-  return NS_OK;
-}
-
-nsresult
-DOMStorageImpl::GetDBValue(const nsAString& aKey, nsAString& aValue,
-                           bool* aSecure)
-{
-  aValue.Truncate();
-
-  if (!UseDB())
-    return NS_OK;
-
-  nsresult rv = InitDB();
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsAutoString value;
-  rv = gStorageDB->GetKeyValue(this, aKey, value, aSecure);
-
-  if (rv == NS_ERROR_DOM_NOT_FOUND_ERR) {
-    SetDOMStringToNull(aValue);
-  }
-
-  if (NS_FAILED(rv))
-    return rv;
-
-  aValue.Assign(value);
-
-  return NS_OK;
-}
-
-nsresult
-DOMStorageImpl::SetDBValue(const nsAString& aKey,
-                           const nsAString& aValue,
-                           bool aSecure)
-{
-  if (!UseDB())
-    return NS_OK;
-
-  nsresult rv = InitDB();
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  CacheKeysFromDB();
-
-  rv = gStorageDB->SetKey(this, aKey, aValue, aSecure);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
-nsresult
-DOMStorageImpl::SetSecure(const nsAString& aKey, bool aSecure)
-{
-  if (UseDB()) {
-    nsresult rv = InitDB();
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    return gStorageDB->SetSecure(this, aKey, aSecure);
-  }
-
-  nsSessionStorageEntry *entry = mItems.GetEntry(aKey);
-  NS_ASSERTION(entry, "Don't use SetSecure() with nonexistent keys!");
-
-  if (entry) {
-    entry->mItem->SetSecureInternal(aSecure);
-  }  
-
-  return NS_OK;
-}
-
-static PLDHashOperator
-ClearStorageItem(nsSessionStorageEntry* aEntry, void* userArg)
-{
-  aEntry->mItem->SetValueInternal(EmptyString());
-  return PL_DHASH_NEXT;
-}
-
-void
-DOMStorageImpl::ClearAll()
-{
-  mItems.EnumerateEntries(ClearStorageItem, nullptr);
-  mItemsCachedVersion = 0;
-}
-
-struct CopyArgs {
-  DOMStorageImpl* storage;
-  bool callerSecure;
-};
-
-static PLDHashOperator
-CopyStorageItems(nsSessionStorageEntry* aEntry, void* userArg)
-{
-  // When copying items from one impl to another, we may not
-  // have an mOwner that we can call SetItem on. Therefore we need
-  // to replicate its behaviour.
-  
-  CopyArgs* args = static_cast<CopyArgs*>(userArg);
-
-  nsAutoString unused;
-  nsresult rv = args->storage->SetValue(args->callerSecure, aEntry->GetKey(),
-                                        aEntry->mItem->GetValueInternal(), unused);
-  if (NS_FAILED(rv))
-    return PL_DHASH_NEXT;
-
-  if (aEntry->mItem->IsSecure()) {
-    args->storage->SetSecure(aEntry->GetKey(), true);
-  }
-
-  return PL_DHASH_NEXT;
-}
-
-nsresult
-DOMStorageImpl::CloneFrom(bool aCallerSecure, DOMStorageBase* aThat)
-{
-  // For various reasons, we no longer call SetItem in CopyStorageItems,
-  // so we need to ensure that the storage permissions are correct.
-  if (!CacheStoragePermissions())
-    return NS_ERROR_DOM_SECURITY_ERR;
-  
-  DOMStorageImpl* that = static_cast<DOMStorageImpl*>(aThat);
-  CopyArgs args = { this, aCallerSecure };
-  that->mItems.EnumerateEntries(CopyStorageItems, &args);
-  return NS_OK;
-}
-
-nsresult
-DOMStorageImpl::CacheKeysFromDB()
-{
-  // cache all the keys in the hash. This is used by the Length and Key methods
-  // use this cache for better performance. The disadvantage is that the
-  // order may break if someone changes the keys in the database directly.
-  if (gStorageDB->IsScopeDirty(this)) {
-    nsresult rv = InitDB();
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    mItems.Clear();
-
-    rv = gStorageDB->GetAllKeys(this, &mItems);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    gStorageDB->MarkScopeCached(this);
-  }
-
-  return NS_OK;
-}
-
-struct KeysArrayBuilderStruct
-{
-  bool callerIsSecure;
-  nsTArray<nsString> *keys;
-};
-
-static PLDHashOperator
-KeysArrayBuilder(nsSessionStorageEntry* aEntry, void* userArg)
-{
-  KeysArrayBuilderStruct *keystruct = (KeysArrayBuilderStruct *)userArg;
-  
-  if (keystruct->callerIsSecure || !aEntry->mItem->IsSecure())
-    keystruct->keys->AppendElement(aEntry->GetKey());
-
-  return PL_DHASH_NEXT;
-}
-
-nsTArray<nsString>*
-DOMStorageImpl::GetKeys(bool aCallerSecure)
-{
-  if (UseDB())
-    CacheKeysFromDB();
-
-  KeysArrayBuilderStruct keystruct;
-  keystruct.callerIsSecure = aCallerSecure;
-  keystruct.keys = new nsTArray<nsString>();
-  if (keystruct.keys)
-    mItems.EnumerateEntries(KeysArrayBuilder, &keystruct);
- 
-  return keystruct.keys;
-}
-
-class ItemCounterState
-{
- public:
-  ItemCounterState(bool aIsCallerSecure)
-  : mIsCallerSecure(aIsCallerSecure), mCount(0)
-  {
-  }
-
-  bool mIsCallerSecure;
-  uint32_t mCount;
- private:
-  ItemCounterState(); // Not to be implemented
-};
-
-static PLDHashOperator
-ItemCounter(nsSessionStorageEntry* aEntry, void* userArg)
-{
-  ItemCounterState *state = (ItemCounterState *)userArg;
-
-  if (state->mIsCallerSecure || !aEntry->mItem->IsSecure()) {
-    ++state->mCount;
-  }
-
-  return PL_DHASH_NEXT;
-}
-
-nsresult
-DOMStorageImpl::GetLength(bool aCallerSecure, uint32_t* aLength)
-{
-  // Force reload of items from database.  This ensures sync localStorages for
-  // same origins among different windows.
-  if (UseDB())
-    CacheKeysFromDB();
-
-  ItemCounterState state(aCallerSecure);
-
-  mItems.EnumerateEntries(ItemCounter, &state);
-
-  *aLength = state.mCount;
-  return NS_OK;
-}
-
-class IndexFinderData
-{
- public:
-  IndexFinderData(bool aIsCallerSecure, uint32_t aWantedIndex)
-  : mIsCallerSecure(aIsCallerSecure), mIndex(0), mWantedIndex(aWantedIndex),
-    mItem(nullptr)
-  {
-  }
-
-  bool mIsCallerSecure;
-  uint32_t mIndex;
-  uint32_t mWantedIndex;
-  nsSessionStorageEntry *mItem;
-
- private:
-  IndexFinderData(); // Not to be implemented
-};
-
-static PLDHashOperator
-IndexFinder(nsSessionStorageEntry* aEntry, void* userArg)
-{
-  IndexFinderData *data = (IndexFinderData *)userArg;
-
-  if (data->mIndex == data->mWantedIndex &&
-      (data->mIsCallerSecure || !aEntry->mItem->IsSecure())) {
-    data->mItem = aEntry;
-
-    return PL_DHASH_STOP;
-  }
-
-  ++data->mIndex;
-
-  return PL_DHASH_NEXT;
-}
-
-nsresult
-DOMStorageImpl::GetKey(bool aCallerSecure, uint32_t aIndex, nsAString& aKey)
-{
-  // XXX: This does a linear search for the key at index, which would
-  // suck if there's a large numer of indexes. Do we care? If so,
-  // maybe we need to have a lazily populated key array here or
-  // something?
-
-  if (UseDB()) {
-    CacheKeysFromDB();
-  }
-
-  IndexFinderData data(aCallerSecure, aIndex);
-  mItems.EnumerateEntries(IndexFinder, &data);
-
-  if (!data.mItem) {
-    // aIndex was larger than the number of accessible keys. Return null.
-    aKey.SetIsVoid(true);
-    return NS_OK;
-  }
-
-  aKey = data.mItem->GetKey();
-  return NS_OK;
-}
-
-// The behaviour of this function must be kept in sync with StorageChild::GetValue.
-// See the explanatory comment there for more details.
-nsIDOMStorageItem*
-DOMStorageImpl::GetValue(bool aCallerSecure, const nsAString& aKey,
-                         nsresult* aResult)
-{
-  nsSessionStorageEntry *entry = mItems.GetEntry(aKey);
-  nsIDOMStorageItem* item = nullptr;
-  if (entry) {
-    if (aCallerSecure || !entry->mItem->IsSecure()) {
-      item = entry->mItem;
-    }
-  }
-  else if (UseDB()) {
-    bool secure;
-    nsAutoString value;
-    nsresult rv = GetDBValue(aKey, value, &secure);
-    // return null if access isn't allowed or the key wasn't found
-    if (rv == NS_ERROR_DOM_SECURITY_ERR || rv == NS_ERROR_DOM_NOT_FOUND_ERR ||
-        (!aCallerSecure && secure))
-      return nullptr;
-
-    *aResult = rv;
-    NS_ENSURE_SUCCESS(rv, nullptr);
-
-    nsRefPtr<nsDOMStorageItem> newitem =
-        new nsDOMStorageItem(this, aKey, value, secure);
-    if (newitem && (entry = mItems.PutEntry(aKey))) {
-      item = entry->mItem = newitem;
-    }
-    else {
-      *aResult = NS_ERROR_OUT_OF_MEMORY;
-    }
-  }
-  return item;
-}
-
-nsresult
-DOMStorageImpl::SetValue(bool aIsCallerSecure, const nsAString& aKey,
-                         const nsAString& aData, nsAString& aOldValue)
-{
-  nsresult rv;
-  nsString oldValue;
-  SetDOMStringToNull(oldValue);
-
-  // First store the value to the database, we need to do this before we update
-  // the mItems cache.  SetDBValue is using the old cached value to decide
-  // on quota checking.
-  if (UseDB()) {
-    rv = SetDBValue(aKey, aData, aIsCallerSecure);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  nsSessionStorageEntry *entry = mItems.GetEntry(aKey);
-  if (entry) {
-    if (entry->mItem->IsSecure() && !aIsCallerSecure) {
-      return NS_ERROR_DOM_SECURITY_ERR;
-    }
-    oldValue = entry->mItem->GetValueInternal();
-    entry->mItem->SetValueInternal(aData);
-  }
-  else {
-    nsRefPtr<nsDOMStorageItem> newitem =
-        new nsDOMStorageItem(this, aKey, aData, aIsCallerSecure);
-    if (!newitem)
-      return NS_ERROR_OUT_OF_MEMORY;
-    entry = mItems.PutEntry(aKey);
-    NS_ENSURE_TRUE(entry, NS_ERROR_OUT_OF_MEMORY);
-    entry->mItem = newitem;
-  }
-  aOldValue = oldValue;
-  return NS_OK;
-}
-
-nsresult
-DOMStorageImpl::RemoveValue(bool aCallerSecure, const nsAString& aKey,
-                            nsAString& aOldValue)
-{
-  nsString oldValue;
-  nsSessionStorageEntry *entry = mItems.GetEntry(aKey);
-
-  if (entry && entry->mItem->IsSecure() && !aCallerSecure) {
-    return NS_ERROR_DOM_SECURITY_ERR;
-  }
-
-  if (UseDB()) {
-    nsresult rv = InitDB();
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    CacheKeysFromDB();
-    entry = mItems.GetEntry(aKey);
-
-    nsAutoString value;
-    bool secureItem;
-    rv = GetDBValue(aKey, value, &secureItem);
-    NS_ENSURE_SUCCESS(rv, rv);
-    if (!aCallerSecure && secureItem)
-      return NS_ERROR_DOM_SECURITY_ERR;
-
-    oldValue = value;
-
-    rv = gStorageDB->RemoveKey(this, aKey);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-  else if (entry) {
-    // clear string as StorageItems may be referencing this item
-    oldValue = entry->mItem->GetValueInternal();
-    entry->mItem->ClearValue();
-  }
-
-  if (entry) {
-    mItems.RawRemoveEntry(entry);
-  }
-  aOldValue = oldValue;
-  return NS_OK;
-}
-
-static PLDHashOperator
-CheckSecure(nsSessionStorageEntry* aEntry, void* userArg)
-{
-  bool* secure = (bool*)userArg;
-  if (aEntry->mItem->IsSecure()) {
-    *secure = true;
-    return PL_DHASH_STOP;
-  }
-
-  return PL_DHASH_NEXT;
-}
-
-nsresult
-DOMStorageImpl::Clear(bool aCallerSecure, int32_t* aOldCount)
-{
-  if (UseDB())
-    CacheKeysFromDB();
-
-  int32_t oldCount = mItems.Count();
-
-  bool foundSecureItem = false;
-  mItems.EnumerateEntries(CheckSecure, &foundSecureItem);
-
-  if (foundSecureItem && !aCallerSecure) {
-    return NS_ERROR_DOM_SECURITY_ERR;
-  }
-
-  if (UseDB()) {
-    nsresult rv = InitDB();
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = gStorageDB->ClearStorage(this);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  *aOldCount = oldCount;
-  mItems.Clear();
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-DOMStorageImpl::PrivateModeChanged(bool enabled)
-{
-  mInPrivateBrowsing = enabled;
-  CanUseStorage(); // cause mSessionOnly to update as well
-  mItems.Clear();
-  mItemsCachedVersion = 0;
-  return NS_OK;
-}
-
-nsDOMStorage::nsDOMStorage()
-  : mStorageType(nsPIDOMStorage::Unknown)
-  , mEventBroadcaster(nullptr)
-{
-  if (XRE_GetProcessType() != GeckoProcessType_Default)
-    mStorageImpl = new StorageChild(this);
-  else
-    mStorageImpl = new DOMStorageImpl(this);
-}
-
-nsDOMStorage::nsDOMStorage(nsDOMStorage& aThat)
-  : mStorageType(aThat.mStorageType)
-  , mPrincipal(aThat.mPrincipal)
-  , mEventBroadcaster(nullptr)
-{
-  if (XRE_GetProcessType() != GeckoProcessType_Default) {
-    StorageChild* other = static_cast<StorageChild*>(aThat.mStorageImpl.get());
-    mStorageImpl = new StorageChild(this, *other);
-  } else {
-    DOMStorageImpl* other = static_cast<DOMStorageImpl*>(aThat.mStorageImpl.get());
-    mStorageImpl = new DOMStorageImpl(this, *other);
-  }
-}
-
-nsDOMStorage::~nsDOMStorage()
-{
-}
-
-nsresult
-nsDOMStorage::InitAsSessionStorage(nsIPrincipal *aPrincipal, const nsSubstring &aDocumentURI,
-                                   bool aPrivate)
-{
-  nsCOMPtr<nsIURI> uri;
-  nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (!uri) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  mDocumentURI = aDocumentURI;
-  mPrincipal = aPrincipal;
-
-  mStorageType = SessionStorage;
-
-  mStorageImpl->InitAsSessionStorage(mPrincipal, aPrivate);
-  return NS_OK;
-}
-
-nsresult
-nsDOMStorage::InitAsLocalStorage(nsIPrincipal *aPrincipal, const nsSubstring &aDocumentURI,
-                                 bool aPrivate)
-{
-  nsCOMPtr<nsIURI> uri;
-  nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (!uri) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  mDocumentURI = aDocumentURI;
-  mPrincipal = aPrincipal;
-
-  mStorageType = LocalStorage;
-
-  mStorageImpl->InitAsLocalStorage(aPrincipal, aPrivate);
-  return NS_OK;
-}
-
-bool
-DOMStorageBase::CanUseStorage()
-{
-  return nsDOMStorage::CanUseStorage(this);
-}
-
-//static
-bool
-nsDOMStorage::CanUseStorage(DOMStorageBase* aStorage /* = NULL */)
-{
-  if (aStorage) {
-    // check if the calling domain can use storage. Downgrade to session
-    // only if only session storage may be used.
-    aStorage->mSessionOnly = false;
-  }
-
-  if (!Preferences::GetBool(kStorageEnabled)) {
-    return false;
-  }
-
-  // chrome can always use storage regardless of permission preferences
-  if (nsContentUtils::IsCallerChrome())
-    return true;
-
-  nsCOMPtr<nsIPrincipal> subjectPrincipal;
-  nsresult rv = nsContentUtils::GetSecurityManager()->
-                  GetSubjectPrincipal(getter_AddRefs(subjectPrincipal));
-  NS_ENSURE_SUCCESS(rv, false);
-
-  // if subjectPrincipal were null we'd have returned after
-  // IsCallerChrome().
-
-  nsCOMPtr<nsIPermissionManager> permissionManager =
-    do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
-  if (!permissionManager)
-    return false;
-
-  uint32_t perm;
-  permissionManager->TestPermissionFromPrincipal(subjectPrincipal,
-                                                 kPermissionType, &perm);
-
-  if (perm == nsIPermissionManager::DENY_ACTION)
-    return false;
-
-  // In private browsing mode we ougth to behave as in session-only cookies
-  // mode to prevent detection of being in private browsing mode and ensuring
-  // that there will be no traces left.
-  if (perm == nsICookiePermission::ACCESS_SESSION ||
-      (aStorage && aStorage->IsPrivate())) {
-    if (aStorage)
-      aStorage->mSessionOnly = true;
-  }
-  else if (perm != nsIPermissionManager::ALLOW_ACTION) {
-    uint32_t cookieBehavior = Preferences::GetUint(kCookiesBehavior);
-    uint32_t lifetimePolicy = Preferences::GetUint(kCookiesLifetimePolicy);
-
-    // Treat "ask every time" as "reject always".
-    if ((cookieBehavior == BEHAVIOR_REJECT || lifetimePolicy == ASK_BEFORE_ACCEPT))
-      return false;
-
-    if (lifetimePolicy == ACCEPT_SESSION && aStorage)
-      aStorage->mSessionOnly = true;
-  }
-
-  return true;
-}
-
-bool
-nsDOMStorage::CacheStoragePermissions()
-{
-  // Bug 488446, disallowing storage use when in session only mode.
-  // This is temporary fix before we find complete solution for storage
-  // behavior in private browsing mode or session-only cookies mode.
-  if (!mStorageImpl->CanUseStorage())
-    return false;
-
-  nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
-  if (!ssm)
-    return false;
-
-  nsCOMPtr<nsIPrincipal> subjectPrincipal;
-  nsresult rv = ssm->GetSubjectPrincipal(getter_AddRefs(subjectPrincipal));
-  NS_ENSURE_SUCCESS(rv, false);
-
-  return CanAccess(subjectPrincipal);
-}
-
-NS_IMETHODIMP
-nsDOMStorage::GetLength(uint32_t *aLength)
-{
-  if (!CacheStoragePermissions())
-    return NS_ERROR_DOM_SECURITY_ERR;
-  
-  return mStorageImpl->GetLength(IsCallerSecure(), aLength);
-}
-
-NS_IMETHODIMP
-nsDOMStorage::Key(uint32_t aIndex, nsAString& aKey)
-{
-  if (!CacheStoragePermissions())
-    return NS_ERROR_DOM_SECURITY_ERR;
-
-  return mStorageImpl->GetKey(IsCallerSecure(), aIndex, aKey);
-}
-
-nsIDOMStorageItem*
-nsDOMStorage::GetNamedItem(const nsAString& aKey, nsresult* aResult)
-{
-  if (!CacheStoragePermissions()) {
-    *aResult = NS_ERROR_DOM_SECURITY_ERR;
-    return nullptr;
-  }
-
-  *aResult = NS_OK;
-  return mStorageImpl->GetValue(IsCallerSecure(), aKey, aResult);
-}
-
-nsresult
-nsDOMStorage::GetItem(const nsAString& aKey, nsAString &aData)
-{
-  nsresult rv;
-
-  // IMPORTANT:
-  // CacheStoragePermissions() is called inside of
-  // GetItem(nsAString, nsIDOMStorageItem)
-  // To call it particularly in this method would just duplicate
-  // the call. If the code changes, make sure that call to
-  // CacheStoragePermissions() is put here!
-
-  nsCOMPtr<nsIDOMStorageItem> item;
-  rv = GetItem(aKey, getter_AddRefs(item));
-  if (NS_FAILED(rv))
-    return rv;
-
-  if (item) {
-    rv = item->GetValue(aData);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-  else
-    SetDOMStringToNull(aData);
-
-  return NS_OK;
-}
-
-static Telemetry::ID
-TelemetryIDForKey(nsPIDOMStorage::nsDOMStorageType type)
-{
-  switch (type) {
-  default:
-    MOZ_ASSERT(false);
-    // We need to return something to satisfy the compiler.
-    // Fallthrough.
-  case nsPIDOMStorage::LocalStorage:
-    return Telemetry::LOCALDOMSTORAGE_KEY_SIZE_BYTES;
-  case nsPIDOMStorage::SessionStorage:
-    return Telemetry::SESSIONDOMSTORAGE_KEY_SIZE_BYTES;
-  }
-}
-
-static Telemetry::ID
-TelemetryIDForValue(nsPIDOMStorage::nsDOMStorageType type)
-{
-  switch (type) {
-  default:
-    MOZ_ASSERT(false);
-    // We need to return something to satisfy the compiler.
-    // Fallthrough.
-  case nsPIDOMStorage::LocalStorage:
-    return Telemetry::LOCALDOMSTORAGE_VALUE_SIZE_BYTES;
-  case nsPIDOMStorage::SessionStorage:
-    return Telemetry::SESSIONDOMSTORAGE_VALUE_SIZE_BYTES;
-  }
-}
-
-NS_IMETHODIMP
-nsDOMStorage::GetItem(const nsAString& aKey, nsIDOMStorageItem **aItem)
-{
-  nsresult rv;
-
-  NS_IF_ADDREF(*aItem = GetNamedItem(aKey, &rv));
-
-  return rv;
-}
-
-NS_IMETHODIMP
-nsDOMStorage::SetItem(const nsAString& aKey, const nsAString& aData)
-{
-  if (!CacheStoragePermissions())
-    return NS_ERROR_DOM_SECURITY_ERR;
-
-  Telemetry::Accumulate(TelemetryIDForKey(mStorageType), aKey.Length());
-  Telemetry::Accumulate(TelemetryIDForValue(mStorageType), aData.Length());
-
-  nsString oldValue;
-  nsresult rv = mStorageImpl->SetValue(IsCallerSecure(), aKey, aData, oldValue);
-  if (NS_FAILED(rv))
-    return rv;
-
-  if (oldValue != aData && mEventBroadcaster)
-    mEventBroadcaster->BroadcastChangeNotification(aKey, oldValue, aData);
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP nsDOMStorage::RemoveItem(const nsAString& aKey)
-{
-  if (!CacheStoragePermissions())
-    return NS_ERROR_DOM_SECURITY_ERR;
-
-  nsString oldValue;
-  nsresult rv = mStorageImpl->RemoveValue(IsCallerSecure(), aKey, oldValue);
-  if (rv == NS_ERROR_DOM_NOT_FOUND_ERR)
-    return NS_OK;
-  if (NS_FAILED(rv))
-    return rv;
-
-  if (!oldValue.IsEmpty() && mEventBroadcaster) {
-    nsAutoString nullString;
-    SetDOMStringToNull(nullString);
-    mEventBroadcaster->BroadcastChangeNotification(aKey, oldValue, nullString);
-  }
-
-  return NS_OK;
-}
-
-nsresult
-nsDOMStorage::Clear()
-{
-  if (!CacheStoragePermissions())
-    return NS_ERROR_DOM_SECURITY_ERR;
-
-  int32_t oldCount;
-  nsresult rv = mStorageImpl->Clear(IsCallerSecure(), &oldCount);
-  if (NS_FAILED(rv))
-    return rv;
-  
-  if (oldCount && mEventBroadcaster) {
-    nsAutoString nullString;
-    SetDOMStringToNull(nullString);
-    mEventBroadcaster->BroadcastChangeNotification(nullString, nullString, nullString);
-  }
-
-  return NS_OK;
-}
-
-already_AddRefed<nsIDOMStorage>
-nsDOMStorage::Clone()
-{
-  NS_ASSERTION(false, "Old DOMStorage doesn't implement cloning");
-  return nullptr;
-}
-
-already_AddRefed<nsIDOMStorage>
-nsDOMStorage::Fork(const nsSubstring &aDocumentURI)
-{
-  NS_ASSERTION(false, "Old DOMStorage doesn't implement forking");
-  return nullptr;
-}
-
-bool nsDOMStorage::IsForkOf(nsIDOMStorage* aThat)
-{
-  NS_ASSERTION(false, "Old DOMStorage doesn't implement forking");
-  return false;
-}
-
-nsresult
-nsDOMStorage::CloneFrom(nsDOMStorage* aThat)
-{
-  return mStorageImpl->CloneFrom(IsCallerSecure(), aThat->mStorageImpl);
-}
-
-nsTArray<nsString> *
-nsDOMStorage::GetKeys()
-{
-  return mStorageImpl->GetKeys(IsCallerSecure());
-}
-
-nsIPrincipal*
-nsDOMStorage::Principal()
-{
-  return nullptr;
-}
-
-bool
-nsDOMStorage::CanAccessSystem(nsIPrincipal *aPrincipal)
-{
-  if (!aPrincipal)
-    return true;
-
-  nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
-  if (!ssm)
-    return false;
-
-  bool isSystem;
-  nsresult rv = ssm->IsSystemPrincipal(aPrincipal, &isSystem);
-
-  return NS_SUCCEEDED(rv) && isSystem;
-}
-
-bool
-nsDOMStorage::CanAccess(nsIPrincipal *aPrincipal)
-{
-  // Allow C++ callers to access the storage
-  if (!aPrincipal)
-    return true;
-
-  // Allow more powerful principals (e.g. system) to access the storage
-
-  // For content, either the code base or domain must be the same.  When code
-  // base is the same, this is enough to say it is safe for a page to access
-  // this storage.
-
-  bool subsumes;
-  nsresult rv = aPrincipal->SubsumesIgnoringDomain(mPrincipal, &subsumes);
-  if (NS_FAILED(rv))
-    return false;
-
-  if (!subsumes) {
-    nsresult rv = aPrincipal->Subsumes(mPrincipal, &subsumes);
-    if (NS_FAILED(rv))
-      return false;
-  }
-
-  return subsumes;
-}
-
-nsPIDOMStorage::nsDOMStorageType
-nsDOMStorage::StorageType()
-{
-  return mStorageType;
-}
-
-//
-// nsDOMStorage2
-//
-
-NS_IMPL_CYCLE_COLLECTION_1(nsDOMStorage2, mStorage)
-
-DOMCI_DATA(Storage, nsDOMStorage2)
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMStorage2)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMStorage2)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMStorage2)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMStorage)
-  NS_INTERFACE_MAP_ENTRY(nsIDOMStorage)
-  NS_INTERFACE_MAP_ENTRY(nsPIDOMStorage)
-  NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
-  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(Storage)
-NS_INTERFACE_MAP_END
-
-NS_IMETHODIMP
-nsDOMStorage2::GetInterface(const nsIID & aIID, void **result)
-{
-  nsresult rv = mStorage->GetInterface(aIID, result);
-  if (NS_SUCCEEDED(rv))
-    return rv;
-  return QueryInterface(aIID, result);;
-}
-
-nsDOMStorage2::nsDOMStorage2()
-{
-}
-
-nsDOMStorage2::nsDOMStorage2(nsDOMStorage2& aThat)
-{
-  mStorage = new nsDOMStorage(*aThat.mStorage.get());
-  mPrincipal = aThat.mPrincipal;
-}
-
-nsresult
-nsDOMStorage2::InitAsSessionStorage(nsIPrincipal *aPrincipal, const nsSubstring &aDocumentURI,
-                                    bool aPrivate)
-{
-  mStorage = new nsDOMStorage();
-  if (!mStorage)
-    return NS_ERROR_OUT_OF_MEMORY;
-
-  mPrincipal = aPrincipal;
-  mDocumentURI = aDocumentURI;
-
-  return mStorage->InitAsSessionStorage(aPrincipal, aDocumentURI, aPrivate);
-}
-
-nsresult
-nsDOMStorage2::InitAsLocalStorage(nsIPrincipal *aPrincipal, const nsSubstring &aDocumentURI,
-                                  bool aPrivate)
-{
-  mStorage = new nsDOMStorage();
-  if (!mStorage)
-    return NS_ERROR_OUT_OF_MEMORY;
-
-  mPrincipal = aPrincipal;
-  mDocumentURI = aDocumentURI;
-
-  return mStorage->InitAsLocalStorage(aPrincipal, aDocumentURI, aPrivate);
-}
-
-already_AddRefed<nsIDOMStorage>
-nsDOMStorage2::Clone()
-{
-  nsDOMStorage2* storage = new nsDOMStorage2(*this);
-  if (!storage)
-    return nullptr;
-
-  storage->mStorage->CloneFrom(mStorage);
-  NS_ADDREF(storage);
-
-  return storage;
-}
-
-already_AddRefed<nsIDOMStorage>
-nsDOMStorage2::Fork(const nsSubstring &aDocumentURI)
-{
-  nsRefPtr<nsDOMStorage2> storage = new nsDOMStorage2();
-  storage->InitAsSessionStorageFork(mPrincipal, aDocumentURI, mStorage);
-  return storage.forget();
-}
-
-bool nsDOMStorage2::IsForkOf(nsIDOMStorage* aThat)
-{
-  if (!aThat)
-    return false;
-
-  nsDOMStorage2* storage = static_cast<nsDOMStorage2*>(aThat);
-  return mStorage == storage->mStorage;
-}
-
-void
-nsDOMStorage2::InitAsSessionStorageFork(nsIPrincipal *aPrincipal, const nsSubstring &aDocumentURI, nsDOMStorage* aStorage)
-{
-  mPrincipal = aPrincipal;
-  mDocumentURI = aDocumentURI;
-  mStorage = aStorage;
-}
-
-nsTArray<nsString> *
-nsDOMStorage2::GetKeys()
-{
-  return mStorage->GetKeys();
-}
-
-nsIPrincipal*
-nsDOMStorage2::Principal()
-{
-  return mPrincipal;
-}
-
-bool
-nsDOMStorage2::CanAccess(nsIPrincipal *aPrincipal)
-{
-  return mStorage->CanAccess(aPrincipal);
-}
-
-nsPIDOMStorage::nsDOMStorageType
-nsDOMStorage2::StorageType()
-{
-  if (mStorage)
-    return mStorage->StorageType();
-
-  return nsPIDOMStorage::Unknown;
-}
-
-bool
-nsDOMStorage2::IsPrivate()
-{
-  return mStorage && mStorage->IsPrivate();
-}
-
-namespace {
-
-class StorageNotifierRunnable : public nsRunnable
-{
-public:
-  StorageNotifierRunnable(nsISupports* aSubject)
-    : mSubject(aSubject)
-  { }
-
-  NS_DECL_NSIRUNNABLE
-
-private:
-  nsCOMPtr<nsISupports> mSubject;
-};
-
-NS_IMETHODIMP
-StorageNotifierRunnable::Run()
-{
-  nsCOMPtr<nsIObserverService> observerService =
-    mozilla::services::GetObserverService();
-  if (observerService) {
-    observerService->NotifyObservers(mSubject, "dom-storage2-changed", nullptr);
-  }
-  return NS_OK;
-}
-
-} // anonymous namespace
-
-void
-nsDOMStorage2::BroadcastChangeNotification(const nsSubstring &aKey,
-                                           const nsSubstring &aOldValue,
-                                           const nsSubstring &aNewValue)
-{
-  nsresult rv;
-  nsCOMPtr<nsIDOMEvent> domEvent;
-  // Note, this DOM event should never reach JS. It is cloned later in
-  // nsGlobalWindow.
-  NS_NewDOMStorageEvent(getter_AddRefs(domEvent), nullptr, nullptr, nullptr);
-  nsCOMPtr<nsIDOMStorageEvent> event = do_QueryInterface(domEvent);
-  rv = event->InitStorageEvent(NS_LITERAL_STRING("storage"),
-                               false,
-                               false,
-                               aKey,
-                               aOldValue,
-                               aNewValue,
-                               mDocumentURI,
-                               static_cast<nsIDOMStorage*>(this));
-  if (NS_FAILED(rv)) {
-    return;
-  }
-
-  nsRefPtr<StorageNotifierRunnable> r = new StorageNotifierRunnable(event);
-  NS_DispatchToMainThread(r);
-}
-
-NS_IMETHODIMP
-nsDOMStorage2::GetLength(uint32_t *aLength)
-{
-  return mStorage->GetLength(aLength);
-}
-
-NS_IMETHODIMP
-nsDOMStorage2::Key(uint32_t aIndex, nsAString& aKey)
-{
-  return mStorage->Key(aIndex, aKey);
-}
-
-NS_IMETHODIMP
-nsDOMStorage2::GetItem(const nsAString& aKey, nsAString &aData)
-{
-  return mStorage->GetItem(aKey, aData);
-}
-
-NS_IMETHODIMP
-nsDOMStorage2::SetItem(const nsAString& aKey, const nsAString& aData)
-{
-  mStorage->mEventBroadcaster = this;
-  return mStorage->SetItem(aKey, aData);
-}
-
-NS_IMETHODIMP
-nsDOMStorage2::RemoveItem(const nsAString& aKey)
-{
-  mStorage->mEventBroadcaster = this;
-  return mStorage->RemoveItem(aKey);
-}
-