Bug 1286798 - Part 10: Support for storage events; r=asuth,janv
authorJan Varga <jan.varga@gmail.com>
Thu, 29 Nov 2018 21:47:45 +0100
changeset 505228 75c28b78f8ee02018ccaec9c2f4035d46182d10e
parent 505227 30bf6870616b701d1ddcb090f18e240164d16ff0
child 505229 4e938b6813e3e8dcb10998d89243678463f4b42a
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth, janv
bugs1286798
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1286798 - Part 10: Support for storage events; r=asuth,janv Storage events are fired either directly after getting response from synchronous SetItem call or through observers. When a new onstorage event listener is added, we sycnhronously register an observer in the parent process. There's always only one observer actor per content process. PBackgroundLSDatabase is now managed by a new PBackgroundLSObject protocol. PBackgroundLSObject is needed to eliminate the need to pass the principal info and document URI everytime a write operation occurs. Preparation of an observer shares some states with preparation of a datastore, so common stuff now lives in LSRequestBase and preparation of a datastore now implements a nested state machine. This patch was enhanced by asuth to drop observers only when the last storage listener is removed. EventListenerRemoved is invoked on any removal, not just the final removal, so we need to make sure it's the final removal before dropping observer.
devtools/client/storage/test/browser.ini
devtools/server/tests/browser/browser.ini
dom/base/nsGlobalWindowInner.cpp
dom/base/nsGlobalWindowOuter.cpp
dom/base/test/mochitest.ini
dom/localstorage/ActorsChild.cpp
dom/localstorage/ActorsChild.h
dom/localstorage/ActorsParent.cpp
dom/localstorage/ActorsParent.h
dom/localstorage/LSDatabase.cpp
dom/localstorage/LSDatabase.h
dom/localstorage/LSObject.cpp
dom/localstorage/LSObject.h
dom/localstorage/LSObserver.cpp
dom/localstorage/LSObserver.h
dom/localstorage/LocalStorageCommon.cpp
dom/localstorage/LocalStorageCommon.h
dom/localstorage/PBackgroundLSDatabase.ipdl
dom/localstorage/PBackgroundLSObject.ipdl
dom/localstorage/PBackgroundLSObserver.ipdl
dom/localstorage/PBackgroundLSRequest.ipdl
dom/localstorage/PBackgroundLSSharedTypes.ipdlh
dom/localstorage/moz.build
dom/tests/mochitest/localstorage/mochitest.ini
dom/tests/mochitest/storageevent/mochitest.ini
editor/libeditor/tests/mochitest.ini
ipc/glue/BackgroundChildImpl.cpp
ipc/glue/BackgroundChildImpl.h
ipc/glue/BackgroundParentImpl.cpp
ipc/glue/BackgroundParentImpl.h
ipc/glue/PBackground.ipdl
testing/web-platform/meta/html/browsers/windows/auxiliary-browsing-contexts/opener-closed.html.ini
testing/web-platform/meta/html/browsers/windows/auxiliary-browsing-contexts/opener-noopener.html.ini
testing/web-platform/meta/html/browsers/windows/auxiliary-browsing-contexts/opener-noreferrer.html.ini
testing/web-platform/meta/html/browsers/windows/browsing-context-names/choose-_blank-002.html.ini
testing/web-platform/meta/html/browsers/windows/browsing-context-names/choose-_parent-004.html.ini
testing/web-platform/meta/html/browsers/windows/browsing-context-names/choose-_self-002.html.ini
testing/web-platform/meta/html/browsers/windows/browsing-context-names/choose-_top-001.html.ini
testing/web-platform/meta/html/browsers/windows/browsing-context-names/choose-_top-002.html.ini
testing/web-platform/meta/html/browsers/windows/browsing-context-names/choose-_top-003.html.ini
testing/web-platform/meta/html/browsers/windows/noreferrer-null-opener.html.ini
testing/web-platform/meta/html/browsers/windows/noreferrer-window-name.html.ini
testing/web-platform/meta/webstorage/document-domain.html.ini
testing/web-platform/meta/webstorage/event_basic.html.ini
testing/web-platform/meta/webstorage/event_body_attribute.html.ini
testing/web-platform/meta/webstorage/event_case_sensitive.html.ini
testing/web-platform/meta/webstorage/event_local_key.html.ini
testing/web-platform/meta/webstorage/event_local_newvalue.html.ini
testing/web-platform/meta/webstorage/event_local_oldvalue.html.ini
testing/web-platform/meta/webstorage/event_local_removeitem.html.ini
testing/web-platform/meta/webstorage/event_local_storagearea.html.ini
testing/web-platform/meta/webstorage/event_local_url.html.ini
testing/web-platform/meta/webstorage/event_no_duplicates.html.ini
testing/web-platform/meta/webstorage/event_setattribute.html.ini
toolkit/components/antitracking/test/browser/browser.ini
toolkit/components/antitracking/test/browser/localStorage.html
--- a/devtools/client/storage/test/browser.ini
+++ b/devtools/client/storage/test/browser.ini
@@ -40,33 +40,33 @@ tags = usercontextid
 [browser_storage_cookies_domain.js]
 [browser_storage_cookies_domain_port.js]
 [browser_storage_cookies_edit.js]
 [browser_storage_cookies_edit_keyboard.js]
 [browser_storage_cookies_hostOnly.js]
 [browser_storage_cookies_samesite.js]
 skip-if = true # Bug 1448484 - sameSite1 is "Unset" - Got undefined, expected Unset
 [browser_storage_cookies_tab_navigation.js]
-#[browser_storage_delete.js]
-#[browser_storage_delete_all.js]
-#[browser_storage_delete_tree.js]
-#[browser_storage_delete_usercontextid.js]
-#tags = usercontextid
+[browser_storage_delete.js]
+[browser_storage_delete_all.js]
+[browser_storage_delete_tree.js]
+[browser_storage_delete_usercontextid.js]
+tags = usercontextid
 [browser_storage_dom_cache_disabled.js]
 [browser_storage_dynamic_updates_cookies.js]
-#[browser_storage_dynamic_updates_localStorage.js]
-#[browser_storage_dynamic_updates_sessionStorage.js]
+[browser_storage_dynamic_updates_localStorage.js]
+[browser_storage_dynamic_updates_sessionStorage.js]
 [browser_storage_empty_objectstores.js]
 [browser_storage_file_url.js]
 [browser_storage_indexeddb_delete.js]
 [browser_storage_indexeddb_delete_blocked.js]
 [browser_storage_indexeddb_duplicate_names.js]
 [browser_storage_indexeddb_overflow.js]
-#[browser_storage_localstorage_add.js]
-#[browser_storage_localstorage_edit.js]
+[browser_storage_localstorage_add.js]
+[browser_storage_localstorage_edit.js]
 [browser_storage_localstorage_error.js]
 [browser_storage_localstorage_rapid_add_remove.js]
 [browser_storage_overflow.js]
 [browser_storage_search.js]
 [browser_storage_search_keyboard_trap.js]
 [browser_storage_sessionstorage_add.js]
 [browser_storage_sessionstorage_edit.js]
 [browser_storage_sidebar.js]
--- a/devtools/server/tests/browser/browser.ini
+++ b/devtools/server/tests/browser/browser.ini
@@ -89,16 +89,16 @@ skip-if = true # Needs to be updated for
 skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
 [browser_perf-recording-actor-02.js]
 skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
 [browser_perf-samples-01.js]
 [browser_perf-samples-02.js]
 [browser_spawn_actor_in_parent.js]
 [browser_storage_browser_toolbox_indexeddb.js]
 [browser_storage_cookies-duplicate-names.js]
-#[browser_storage_dynamic_windows.js]
+[browser_storage_dynamic_windows.js]
 [browser_storage_listings.js]
-#[browser_storage_updates.js]
-#skip-if = (verify && debug && (os == 'mac' || os == 'linux'))
+[browser_storage_updates.js]
+skip-if = (verify && debug && (os == 'mac' || os == 'linux'))
 [browser_stylesheets_getTextEmpty.js]
 [browser_stylesheets_nested-iframes.js]
 [browser_register_actor.js]
 [browser_webextension_inspected_window.js]
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -5812,17 +5812,18 @@ nsGlobalWindowInner::ObserveStorageNotif
   }
 
   else {
     MOZ_ASSERT(!NS_strcmp(aStorageType, u"localStorage"));
 
     MOZ_DIAGNOSTIC_ASSERT(StorageUtils::PrincipalsEqual(aEvent->GetPrincipal(),
                                                         principal));
 
-    fireMozStorageChanged = mLocalStorage == aEvent->GetStorageArea();
+    fireMozStorageChanged =
+       mLocalStorage && mLocalStorage == aEvent->GetStorageArea();
 
     if (fireMozStorageChanged) {
       eventType.AssignLiteral("MozLocalStorageChanged");
     }
   }
 
   // Clone the storage event included in the observer notification. We want
   // to dispatch clones rather than the original event.
@@ -5859,26 +5860,28 @@ nsGlobalWindowInner::CloneStorageEvent(c
 
   RefPtr<Storage> storageArea = aEvent->GetStorageArea();
 
   RefPtr<Storage> storage;
 
   // If null, this is a localStorage event received by IPC.
   if (!storageArea) {
     storage = GetLocalStorage(aRv);
-    if (aRv.Failed() || !storage) {
-      return nullptr;
-    }
-
-    if (storage->Type() == Storage::eLocalStorage) {
-      RefPtr<LocalStorage> localStorage =
-        static_cast<LocalStorage*>(storage.get());
-
-      // We must apply the current change to the 'local' localStorage.
-      localStorage->ApplyEvent(aEvent);
+    if (!NextGenLocalStorageEnabled()) {
+      if (aRv.Failed() || !storage) {
+        return nullptr;
+      }
+
+      if (storage->Type() == Storage::eLocalStorage) {
+        RefPtr<LocalStorage> localStorage =
+          static_cast<LocalStorage*>(storage.get());
+
+        // We must apply the current change to the 'local' localStorage.
+        localStorage->ApplyEvent(aEvent);
+      }
     }
   } else if (storageArea->Type() == Storage::eSessionStorage) {
     storage = GetSessionStorage(aRv);
   } else {
     MOZ_ASSERT(storageArea->Type() == Storage::eLocalStorage);
     storage = GetLocalStorage(aRv);
   }
 
@@ -6793,29 +6796,50 @@ nsGlobalWindowInner::EventListenerAdded(
     mTabChild->BeforeUnloadAdded();
   }
 
   // We need to initialize localStorage in order to receive notifications.
   if (aType == nsGkAtoms::onstorage) {
     ErrorResult rv;
     GetLocalStorage(rv);
     rv.SuppressException();
+
+    if (NextGenLocalStorageEnabled() &&
+        mLocalStorage && mLocalStorage->Type() == Storage::eLocalStorage) {
+      auto object = static_cast<LSObject*>(mLocalStorage.get());
+
+      Unused << NS_WARN_IF(NS_FAILED(object->EnsureObserver()));
+    }
   }
 }
 
 void
 nsGlobalWindowInner::EventListenerRemoved(nsAtom* aType)
 {
   if (aType == nsGkAtoms::onbeforeunload &&
       mTabChild &&
       (!mDoc || !(mDoc->GetSandboxFlags() & SANDBOXED_MODALS))) {
     mBeforeUnloadListenerCount--;
     MOZ_ASSERT(mBeforeUnloadListenerCount >= 0);
     mTabChild->BeforeUnloadRemoved();
   }
+
+  if (aType == nsGkAtoms::onstorage) {
+    if (NextGenLocalStorageEnabled() &&
+        mLocalStorage &&
+        mLocalStorage->Type() == Storage::eLocalStorage &&
+        // The remove event is fired even if this isn't the last listener, so
+        // only remove if there are no other listeners left.
+        mListenerManager &&
+        !mListenerManager->HasListenersFor(nsGkAtoms::onstorage)) {
+      auto object = static_cast<LSObject*>(mLocalStorage.get());
+
+      object->DropObserver();
+    }
+  }
 }
 
 void
 nsGlobalWindowInner::NotifyVREventListenerAdded()
 {
   mHasVREvents = true;
   EnableVRUpdates();
 }
@@ -7967,16 +7991,24 @@ nsGlobalWindowInner::StorageAccessGrante
     IgnoredErrorResult error;
     GetLocalStorage(error);
     if (NS_WARN_IF(error.Failed())) {
       return;
     }
 
     MOZ_ASSERT(mLocalStorage &&
                mLocalStorage->Type() == Storage::eLocalStorage);
+
+    if (NextGenLocalStorageEnabled() &&
+        mListenerManager &&
+        mListenerManager->HasListenersFor(nsGkAtoms::onstorage)) {
+      auto object = static_cast<LSObject*>(mLocalStorage.get());
+
+      object->EnsureObserver();
+    }
   }
 }
 
 mozilla::dom::TabGroup*
 nsPIDOMWindowInner::TabGroup()
 {
   return nsGlobalWindowInner::Cast(this)->TabGroupInner();
 }
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -22,16 +22,17 @@
 #include "nsIPermissionManager.h"
 #include "nsIPrefBranch.h"
 #include "nsISecureBrowserUI.h"
 #include "nsIWebProgressListener.h"
 #include "mozilla/AntiTrackingCommon.h"
 #include "mozilla/dom/ContentFrameMessageManager.h"
 #include "mozilla/dom/EventTarget.h"
 #include "mozilla/dom/LocalStorage.h"
+#include "mozilla/dom/LSObject.h"
 #include "mozilla/dom/Storage.h"
 #include "mozilla/dom/IdleRequest.h"
 #include "mozilla/dom/Performance.h"
 #include "mozilla/dom/StorageEvent.h"
 #include "mozilla/dom/StorageEventBinding.h"
 #include "mozilla/dom/StorageNotifierService.h"
 #include "mozilla/dom/StorageUtils.h"
 #include "mozilla/dom/Timeout.h"
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -572,17 +572,17 @@ skip-if = toolkit == 'android' || (verif
 [test_bug962251.html]
 [test_bug976673.html]
 [test_bug982153.html]
 [test_bug999456.html]
 [test_bug1022229.html]
 [test_bug1025933.html]
 [test_bug1037687.html]
 support-files = test_bug1037687_subframe.html
-#[test_bug1043106.html]
+[test_bug1043106.html]
 [test_bug1057176.html]
 [test_bug1060938.html]
 [test_bug1064481.html]
 [test_bug1070015.html]
 [test_bug1075702.html]
 [test_bug1091883.html]
 [test_bug1100912.html]
 support-files = file_bug1100912.html
--- a/dom/localstorage/ActorsChild.cpp
+++ b/dom/localstorage/ActorsChild.cpp
@@ -1,22 +1,88 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "ActorsChild.h"
 
+#include "LocalStorageCommon.h"
 #include "LSDatabase.h"
+#include "LSObject.h"
+#include "LSObserver.h"
 
 namespace mozilla {
 namespace dom {
 
 /*******************************************************************************
+ * LSObjectChild
+ ******************************************************************************/
+
+LSObjectChild::LSObjectChild(LSObject* aObject)
+  : mObject(aObject)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aObject);
+  aObject->AssertIsOnOwningThread();
+
+  MOZ_COUNT_CTOR(LSObjectChild);
+}
+
+LSObjectChild::~LSObjectChild()
+{
+  AssertIsOnOwningThread();
+
+  MOZ_COUNT_DTOR(LSObjectChild);
+}
+
+void
+LSObjectChild::SendDeleteMeInternal()
+{
+  AssertIsOnOwningThread();
+
+  if (mObject) {
+    mObject->ClearActor();
+    mObject = nullptr;
+
+    MOZ_ALWAYS_TRUE(PBackgroundLSObjectChild::SendDeleteMe());
+  }
+}
+
+void
+LSObjectChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+  AssertIsOnOwningThread();
+
+  if (mObject) {
+    mObject->ClearActor();
+#ifdef DEBUG
+    mObject = nullptr;
+#endif
+  }
+}
+
+LSObjectChild::PBackgroundLSDatabaseChild*
+LSObjectChild::AllocPBackgroundLSDatabaseChild(const uint64_t& aDatastoreId)
+{
+  MOZ_CRASH("PBackgroundLSDatabaseChild actor should be manually constructed!");
+}
+
+bool
+LSObjectChild::DeallocPBackgroundLSDatabaseChild(
+                                             PBackgroundLSDatabaseChild* aActor)
+{
+  MOZ_ASSERT(aActor);
+
+  delete aActor;
+  return true;
+}
+
+/*******************************************************************************
  * LSDatabaseChild
  ******************************************************************************/
 
 LSDatabaseChild::LSDatabaseChild(LSDatabase* aDatabase)
   : mDatabase(aDatabase)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aDatabase);
@@ -70,16 +136,96 @@ LSDatabaseChild::RecvRequestAllowToClose
     //       datastore right here, but asynchronously.
     //       However, we probably shouldn't do that if we are shutting down.
   }
 
   return IPC_OK();
 }
 
 /*******************************************************************************
+ * LSObserverChild
+ ******************************************************************************/
+
+LSObserverChild::LSObserverChild(LSObserver* aObserver)
+  : mObserver(aObserver)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aObserver);
+
+  MOZ_COUNT_CTOR(LSObserverChild);
+}
+
+LSObserverChild::~LSObserverChild()
+{
+  AssertIsOnOwningThread();
+
+  MOZ_COUNT_DTOR(LSObserverChild);
+}
+
+void
+LSObserverChild::SendDeleteMeInternal()
+{
+  AssertIsOnOwningThread();
+
+  if (mObserver) {
+    mObserver->ClearActor();
+    mObserver = nullptr;
+
+    MOZ_ALWAYS_TRUE(PBackgroundLSObserverChild::SendDeleteMe());
+  }
+}
+
+void
+LSObserverChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+  AssertIsOnOwningThread();
+
+  if (mObserver) {
+    mObserver->ClearActor();
+#ifdef DEBUG
+    mObserver = nullptr;
+#endif
+  }
+}
+
+mozilla::ipc::IPCResult
+LSObserverChild::RecvObserve(const PrincipalInfo& aPrincipalInfo,
+                             const uint32_t& aPrivateBrowsingId,
+                             const nsString& aDocumentURI,
+                             const nsString& aKey,
+                             const nsString& aOldValue,
+                             const nsString& aNewValue)
+{
+  AssertIsOnOwningThread();
+
+  if (!mObserver) {
+    return IPC_OK();
+  }
+
+  nsresult rv;
+  nsCOMPtr<nsIPrincipal> principal =
+    PrincipalInfoToPrincipal(aPrincipalInfo, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  Storage::NotifyChange(/* aStorage */ nullptr,
+                        principal,
+                        aKey,
+                        aOldValue,
+                        aNewValue,
+                        /* aStorageType */ kLocalStorageType,
+                        aDocumentURI,
+                        /* aIsPrivate */ !!aPrivateBrowsingId,
+                        /* aImmediateDispatch */ true);
+
+  return IPC_OK();
+}
+
+/*******************************************************************************
  * LocalStorageRequestChild
  ******************************************************************************/
 
 LSRequestChild::LSRequestChild(LSRequestChildCallback* aCallback)
   : mCallback(aCallback)
   , mFinishing(false)
 {
   AssertIsOnOwningThread();
--- a/dom/localstorage/ActorsChild.h
+++ b/dom/localstorage/ActorsChild.h
@@ -3,32 +3,74 @@
 /* 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_localstorage_ActorsChild_h
 #define mozilla_dom_localstorage_ActorsChild_h
 
 #include "mozilla/dom/PBackgroundLSDatabaseChild.h"
+#include "mozilla/dom/PBackgroundLSObjectChild.h"
+#include "mozilla/dom/PBackgroundLSObserverChild.h"
 #include "mozilla/dom/PBackgroundLSRequestChild.h"
 
 namespace mozilla {
 
 namespace ipc {
 
 class BackgroundChildImpl;
 
 } // namespace ipc
 
 namespace dom {
 
 class LSDatabase;
 class LSObject;
+class LSObserver;
 class LSRequestChildCallback;
 
+class LSObjectChild final
+  : public PBackgroundLSObjectChild
+{
+  friend class mozilla::ipc::BackgroundChildImpl;
+  friend class LSObject;
+
+  LSObject* mObject;
+
+  NS_DECL_OWNINGTHREAD
+
+public:
+  void
+  AssertIsOnOwningThread() const
+  {
+    NS_ASSERT_OWNINGTHREAD(LSObjectChild);
+  }
+
+private:
+  // Only created by LSObject.
+  explicit LSObjectChild(LSObject* aObject);
+
+  // Only destroyed by mozilla::ipc::BackgroundChildImpl.
+  ~LSObjectChild();
+
+  void
+  SendDeleteMeInternal();
+
+  // IPDL methods are only called by IPDL.
+  void
+  ActorDestroy(ActorDestroyReason aWhy) override;
+
+  PBackgroundLSDatabaseChild*
+  AllocPBackgroundLSDatabaseChild(const uint64_t& aDatastoreId) override;
+
+  bool
+  DeallocPBackgroundLSDatabaseChild(PBackgroundLSDatabaseChild* aActor)
+                                    override;
+};
+
 class LSDatabaseChild final
   : public PBackgroundLSDatabaseChild
 {
   friend class mozilla::ipc::BackgroundChildImpl;
   friend class LSDatabase;
   friend class LSObject;
 
   LSDatabase* mDatabase;
@@ -55,16 +97,57 @@ private:
   // IPDL methods are only called by IPDL.
   void
   ActorDestroy(ActorDestroyReason aWhy) override;
 
   mozilla::ipc::IPCResult
   RecvRequestAllowToClose() override;
 };
 
+class LSObserverChild final
+  : public PBackgroundLSObserverChild
+{
+  friend class mozilla::ipc::BackgroundChildImpl;
+  friend class LSObserver;
+  friend class LSObject;
+
+  LSObserver* mObserver;
+
+  NS_DECL_OWNINGTHREAD
+
+public:
+  void
+  AssertIsOnOwningThread() const
+  {
+    NS_ASSERT_OWNINGTHREAD(LSObserverChild);
+  }
+
+private:
+  // Only created by LSObject.
+  explicit LSObserverChild(LSObserver* aObserver);
+
+  // Only destroyed by mozilla::ipc::BackgroundChildImpl.
+  ~LSObserverChild();
+
+  void
+  SendDeleteMeInternal();
+
+  // IPDL methods are only called by IPDL.
+  void
+  ActorDestroy(ActorDestroyReason aWhy) override;
+
+  mozilla::ipc::IPCResult
+  RecvObserve(const PrincipalInfo& aPrinciplaInfo,
+              const uint32_t& aPrivateBrowsingId,
+              const nsString& aDocumentURI,
+              const nsString& aKey,
+              const nsString& aOldValue,
+              const nsString& aNewValue) override;
+};
+
 class LSRequestChild final
   : public PBackgroundLSRequestChild
 {
   friend class LSObject;
   friend class LSObjectChild;
 
   RefPtr<LSRequestChildCallback> mCallback;
 
--- a/dom/localstorage/ActorsParent.cpp
+++ b/dom/localstorage/ActorsParent.cpp
@@ -9,27 +9,30 @@
 #include "LocalStorageCommon.h"
 #include "LSObject.h"
 #include "mozIStorageConnection.h"
 #include "mozIStorageService.h"
 #include "mozStorageCID.h"
 #include "mozStorageHelper.h"
 #include "mozilla/Unused.h"
 #include "mozilla/dom/PBackgroundLSDatabaseParent.h"
+#include "mozilla/dom/PBackgroundLSObjectParent.h"
+#include "mozilla/dom/PBackgroundLSObserverParent.h"
 #include "mozilla/dom/PBackgroundLSRequestParent.h"
 #include "mozilla/dom/PBackgroundLSSharedTypes.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/dom/quota/UsageInfo.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/PBackgroundParent.h"
 #include "mozilla/ipc/PBackgroundSharedTypes.h"
 #include "nsClassHashtable.h"
 #include "nsDataHashtable.h"
 #include "nsInterfaceHashtable.h"
 #include "nsISimpleEnumerator.h"
+#include "nsRefPtrHashtable.h"
 #include "ReportInternalError.h"
 
 #define DISABLE_ASSERTS_FOR_FUZZING 0
 
 #if DISABLE_ASSERTS_FOR_FUZZING
 #define ASSERT_UNLESS_FUZZING(...) do { } while (0)
 #else
 #define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__)
@@ -910,23 +913,31 @@ public:
 
   void
   GetKey(uint32_t aIndex, nsString& aKey) const;
 
   void
   GetItem(const nsString& aKey, nsString& aValue) const;
 
   void
-  SetItem(const nsString& aKey, const nsString& aValue);
+  SetItem(Database* aDatabase,
+          const nsString& aKey,
+          const nsString& aValue,
+          bool* aChanged,
+          nsString& aOldValue);
 
   void
-  RemoveItem(const nsString& aKey);
+  RemoveItem(Database* aDatabase,
+             const nsString& aKey,
+             bool* aChanged,
+             nsString& aOldValue);
 
   void
-  Clear();
+  Clear(Database* aDatabase,
+        bool* aChanged);
 
   void
   GetKeys(nsTArray<nsString>& aKeys) const;
 
   NS_INLINE_DECL_REFCOUNTING(Datastore)
 
 private:
   // Reference counted.
@@ -934,16 +945,22 @@ private:
 
   void
   ConnectionClosedCallback();
 
   void
   CleanupMetadata();
 
   void
+  NotifyObservers(Database* aDatabase,
+                  const nsString& aKey,
+                  const nsString& aOldValue,
+                  const nsString& aNewValue);
+
+  void
   EnsureTransaction();
 
   static void
   AutoCommitTimerCallback(nsITimer* aTimer, void* aClosure);
 };
 
 class PreparedDatastore
 {
@@ -995,33 +1012,100 @@ public:
     return mInvalidated;
   }
 };
 
 /*******************************************************************************
  * Actor class declarations
  ******************************************************************************/
 
+class Object final
+  : public PBackgroundLSObjectParent
+{
+  const PrincipalInfo mPrincipalInfo;
+  const nsString mDocumentURI;
+  uint32_t mPrivateBrowsingId;
+  bool mActorDestroyed;
+
+public:
+  // Created in AllocPBackgroundLSObjectParent.
+  Object(const PrincipalInfo& aPrincipalInfo,
+         const nsAString& aDocumentURI,
+         uint32_t aPrivateBrowsingId);
+
+  const PrincipalInfo&
+  GetPrincipalInfo() const
+  {
+    return mPrincipalInfo;
+  }
+
+  uint32_t
+  PrivateBrowsingId() const
+  {
+    return mPrivateBrowsingId;
+  }
+
+  const nsString&
+  DocumentURI() const
+  {
+    return mDocumentURI;
+  }
+
+  NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Object)
+
+private:
+  // Reference counted.
+  ~Object();
+
+  // IPDL methods are only called by IPDL.
+  void
+  ActorDestroy(ActorDestroyReason aWhy) override;
+
+  mozilla::ipc::IPCResult
+  RecvDeleteMe() override;
+
+  PBackgroundLSDatabaseParent*
+  AllocPBackgroundLSDatabaseParent(const uint64_t& aDatastoreId) override;
+
+  mozilla::ipc::IPCResult
+  RecvPBackgroundLSDatabaseConstructor(PBackgroundLSDatabaseParent* aActor,
+                                       const uint64_t& aDatastoreId) override;
+
+  bool
+  DeallocPBackgroundLSDatabaseParent(PBackgroundLSDatabaseParent* aActor)
+                                     override;
+};
+
 class Database final
   : public PBackgroundLSDatabaseParent
 {
+  RefPtr<Object> mObject;
   RefPtr<Datastore> mDatastore;
   // Strings share buffers if possible, so it's not a problem to duplicate the
   // origin here.
   nsCString mOrigin;
   bool mAllowedToClose;
   bool mActorDestroyed;
   bool mRequestedAllowToClose;
 #ifdef DEBUG
   bool mActorWasAlive;
 #endif
 
 public:
   // Created in AllocPBackgroundLSDatabaseParent.
-  explicit Database(const nsACString& aOrigin);
+  Database(Object* aObject,
+           const nsACString& aOrigin);
+
+  Object*
+  GetObject() const
+  {
+    AssertIsOnBackgroundThread();
+
+    return mObject;
+  }
 
   const nsCString&
   Origin() const
   {
     return mOrigin;
   }
 
   void
@@ -1057,57 +1141,160 @@ private:
 
   mozilla::ipc::IPCResult
   RecvGetItem(const nsString& aKey, nsString* aValue) override;
 
   mozilla::ipc::IPCResult
   RecvGetKeys(nsTArray<nsString>* aKeys) override;
 
   mozilla::ipc::IPCResult
-  RecvSetItem(const nsString& aKey, const nsString& aValue) override;
+  RecvSetItem(const nsString& aKey,
+              const nsString& aValue,
+              bool* aChanged,
+              nsString* aOldValue) override;
+
+  mozilla::ipc::IPCResult
+  RecvRemoveItem(const nsString& aKey,
+                 bool* aChanged,
+                 nsString* aOldValue) override;
 
   mozilla::ipc::IPCResult
-  RecvRemoveItem(const nsString& aKey) override;
+  RecvClear(bool* aChanged) override;
+};
+
+class Observer final
+  : public PBackgroundLSObserverParent
+{
+  nsCString mOrigin;
+  bool mActorDestroyed;
+
+public:
+  // Created in AllocPBackgroundLSObserverParent.
+  explicit Observer(const nsACString& aOrigin);
+
+  const nsCString&
+  Origin() const
+  {
+    return mOrigin;
+  }
+
+  void
+  Observe(Database* aDatabase,
+          const nsString& aKey,
+          const nsString& aOldValue,
+          const nsString& aNewValue);
+
+  NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Observer)
+
+private:
+  // Reference counted.
+  ~Observer();
+
+  // IPDL methods are only called by IPDL.
+  void
+  ActorDestroy(ActorDestroyReason aWhy) override;
 
   mozilla::ipc::IPCResult
-  RecvClear() override;
+  RecvDeleteMe() override;
 };
 
 class LSRequestBase
   : public DatastoreOperationBase
   , public PBackgroundLSRequestParent
 {
+protected:
+  enum class State
+  {
+    // Just created on the PBackground thread. Next step is Opening.
+    Initial,
+
+    // Waiting to open/opening on the main thread. Next step is either
+    // Nesting if a subclass needs to process more nested states or
+    // SendingReadyMessage if a subclass doesn't need any nested processing.
+    Opening,
+
+    // Doing nested processing.
+    Nesting,
+
+    // Waiting to send/sending the ready message on the PBackground thread. Next
+    // step is WaitingForFinish.
+    SendingReadyMessage,
+
+    // Waiting for the finish message on the PBackground thread. Next step is
+    // SendingResults.
+    WaitingForFinish,
+
+    // Waiting to send/sending results on the PBackground thread. Next step is
+    // Completed.
+    SendingResults,
+
+    // All done.
+    Completed
+  };
+
+  nsCOMPtr<nsIEventTarget> mMainEventTarget;
+  State mState;
+
 public:
-  virtual void
-  Dispatch() = 0;
+  explicit LSRequestBase(nsIEventTarget* aMainEventTarget);
+
+  void
+  Dispatch();
 
 protected:
+  ~LSRequestBase() override;
+
+  virtual nsresult
+  Open() = 0;
+
+  virtual nsresult
+  NestedRun();
+
+  virtual void
+  GetResponse(LSRequestResponse& aResponse) = 0;
+
+  virtual void
+  Cleanup()
+  { }
+
+private:
+  void
+  SendReadyMessage();
+
+  void
+  SendResults();
+
+protected:
+  // Common nsIRunnable implementation that subclasses may not override.
+  NS_IMETHOD
+  Run() final;
+
   // IPDL methods.
   void
   ActorDestroy(ActorDestroyReason aWhy) override;
 
+private:
   mozilla::ipc::IPCResult
   RecvCancel() override;
+
+  mozilla::ipc::IPCResult
+  RecvFinish() override;
 };
 
 class PrepareDatastoreOp
   : public LSRequestBase
   , public OpenDirectoryListener
 {
   class LoadDataOp;
 
-  enum class State
+  enum class NestedState
   {
-    // Just created on the PBackground thread. Next step is OpeningOnMainThread
-    // or OpeningOnOwningThread.
-    Initial,
-
-    // Waiting to open/opening on the main thread. Next step is
+    // The nesting has not yet taken place. Next step is
     // CheckExistingOperations.
-    Opening,
+    BeforeNesting,
 
     // Checking if a prepare datastore operation is already running for given
     // origin on the PBackground thread. Next step is CheckClosingDatastore.
     CheckExistingOperations,
 
     // Checking if a datastore is closing the connection for given origin on
     // the PBackground thread. Next step is PreparationPending.
     CheckClosingDatastore,
@@ -1138,47 +1325,35 @@ class PrepareDatastoreOp
     // DatabaseWorkLoadData.
     BeginLoadData,
 
     // Waiting to do/doing work on the connection thread. This involves waiting
     // for the LoadDataOp to do its work. Eventually the state will transition
     // to SendingReadyMessage.
     DatabaseWorkLoadData,
 
-    // Waiting to send/sending the ready message on the PBackground thread. Next
-    // step is WaitingForFinish.
-    SendingReadyMessage,
-
-    // Waiting for the finish message on the PBackground thread. Next step is
-    // SendingResults.
-    WaitingForFinish,
-
-    // Waiting to send/sending results on the PBackground thread. Next step is
-    // Completed.
-    SendingResults,
-
-    // All done.
-    Completed
+    // The nesting has completed.
+    AfterNesting
   };
 
   nsCOMPtr<nsIEventTarget> mMainEventTarget;
   RefPtr<PrepareDatastoreOp> mDelayedOp;
   RefPtr<DirectoryLock> mDirectoryLock;
   RefPtr<Connection> mConnection;
   RefPtr<Datastore> mDatastore;
   LoadDataOp* mLoadDataOp;
   nsDataHashtable<nsStringHashKey, nsString> mValues;
   const LSRequestPrepareDatastoreParams mParams;
   nsCString mSuffix;
   nsCString mGroup;
   nsCString mMainThreadOrigin;
   nsCString mOrigin;
   nsString mDatabaseFilePath;
   uint32_t mPrivateBrowsingId;
-  State mState;
+  NestedState mNestedState;
   bool mRequestedDirectoryLock;
   bool mInvalidated;
 
 public:
   PrepareDatastoreOp(nsIEventTarget* aMainEventTarget,
                      const LSRequestParams& aParams);
 
   bool
@@ -1209,35 +1384,29 @@ public:
   void
   Invalidate()
   {
     AssertIsOnOwningThread();
 
     mInvalidated = true;
   }
 
-  void
-  Dispatch() override;
-
 private:
   ~PrepareDatastoreOp() override;
 
   nsresult
-  Open();
+  Open() override;
 
   nsresult
   CheckExistingOperations();
 
   nsresult
   CheckClosingDatastore();
 
   nsresult
-  PreparationOpen();
-
-  nsresult
   BeginDatastorePreparation();
 
   nsresult
   QuotaManagerOpen();
 
   nsresult
   OpenDirectory();
 
@@ -1248,43 +1417,37 @@ private:
   DatabaseWork();
 
   nsresult
   VerifyDatabaseInformation(mozIStorageConnection* aConnection);
 
   nsresult
   BeginLoadData();
 
-  void
-  SendReadyMessage();
+  nsresult
+  NestedRun() override;
 
   void
-  SendResults();
+  GetResponse(LSRequestResponse& aResponse) override;
 
   void
-  Cleanup();
+  Cleanup() override;
 
   void
   ConnectionClosedCallback();
 
   void
   CleanupMetadata();
 
   NS_DECL_ISUPPORTS_INHERITED
 
-  NS_IMETHOD
-  Run() override;
-
   // IPDL overrides.
   void
   ActorDestroy(ActorDestroyReason aWhy) override;
 
-  mozilla::ipc::IPCResult
-  RecvFinish() override;
-
   // OpenDirectoryListener overrides.
   void
   DirectoryLockAcquired(DirectoryLock* aLock) override;
 
   void
   DirectoryLockFailed() override;
 };
 
@@ -1310,16 +1473,34 @@ private:
 
   void
   OnFailure(nsresult aResultCode) override;
 
   void
   Cleanup() override;
 };
 
+class PrepareObserverOp
+  : public LSRequestBase
+{
+  const LSRequestPrepareObserverParams mParams;
+  nsCString mOrigin;
+
+public:
+  PrepareObserverOp(nsIEventTarget* aMainEventTarget,
+                    const LSRequestParams& aParams);
+
+private:
+  nsresult
+  Open() override;
+
+  void
+  GetResponse(LSRequestResponse& aResponse) override;
+};
+
 /*******************************************************************************
  * Other class declarations
  ******************************************************************************/
 
 class QuotaClient final
   : public mozilla::dom::quota::Client
 {
   class ClearPrivateBrowsingRunnable;
@@ -1469,16 +1650,27 @@ typedef nsClassHashtable<nsUint64HashKey
 StaticAutoPtr<PreparedDatastoreHashtable> gPreparedDatastores;
 
 typedef nsTArray<Database*> LiveDatabaseArray;
 
 StaticAutoPtr<LiveDatabaseArray> gLiveDatabases;
 
 StaticRefPtr<ConnectionThread> gConnectionThread;
 
+uint64_t gLastObserverId = 0;
+
+typedef nsRefPtrHashtable<nsUint64HashKey, Observer> PreparedObserverHashtable;
+
+StaticAutoPtr<PreparedObserverHashtable> gPreparedObsevers;
+
+typedef nsClassHashtable<nsCStringHashKey, nsTArray<Observer*>>
+  ObserverHashtable;
+
+StaticAutoPtr<ObserverHashtable> gObservers;
+
 bool
 IsOnConnectionThread()
 {
   MOZ_ASSERT(gConnectionThread);
   return gConnectionThread->IsOnConnectionThread();
 }
 
 void
@@ -1489,88 +1681,124 @@ AssertIsOnConnectionThread()
 }
 
 } // namespace
 
 /*******************************************************************************
  * Exported functions
  ******************************************************************************/
 
-PBackgroundLSDatabaseParent*
-AllocPBackgroundLSDatabaseParent(const uint64_t& aDatastoreId)
+PBackgroundLSObjectParent*
+AllocPBackgroundLSObjectParent(const PrincipalInfo& aPrincipalInfo,
+                               const nsString& aDocumentURI,
+                               const uint32_t& aPrivateBrowsingId)
+{
+  AssertIsOnBackgroundThread();
+
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
+    return nullptr;
+  }
+
+  RefPtr<Object> object =
+    new Object(aPrincipalInfo, aDocumentURI, aPrivateBrowsingId);
+
+  // Transfer ownership to IPDL.
+  return object.forget().take();
+}
+
+bool
+RecvPBackgroundLSObjectConstructor(PBackgroundLSObjectParent* aActor,
+                                   const PrincipalInfo& aPrincipalInfo,
+                                   const nsString& aDocumentURI,
+                                   const uint32_t& aPrivateBrowsingId)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+  MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
+
+  return true;
+}
+
+bool
+DeallocPBackgroundLSObjectParent(PBackgroundLSObjectParent* aActor)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  // Transfer ownership back from IPDL.
+  RefPtr<Object> actor = dont_AddRef(static_cast<Object*>(aActor));
+
+  return true;
+}
+
+PBackgroundLSObserverParent*
+AllocPBackgroundLSObserverParent(const uint64_t& aObserverId)
 {
   AssertIsOnBackgroundThread();
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
     return nullptr;
   }
 
-  if (NS_WARN_IF(!gPreparedDatastores)) {
-    ASSERT_UNLESS_FUZZING();
-    return nullptr;
-  }
-
-  PreparedDatastore* preparedDatastore = gPreparedDatastores->Get(aDatastoreId);
-  if (NS_WARN_IF(!preparedDatastore)) {
+  if (NS_WARN_IF(!gPreparedObsevers)) {
     ASSERT_UNLESS_FUZZING();
     return nullptr;
   }
 
-  // If we ever decide to return null from this point on, we need to make sure
-  // that the datastore is closed and the prepared datastore is removed from the
-  // gPreparedDatastores hashtable.
-  // We also assume that IPDL must call RecvPBackgroundLSDatabaseConstructor
-  // once we return a valid actor in this method.
-
-  RefPtr<Database> database = new Database(preparedDatastore->Origin());
+  RefPtr<Observer> observer = gPreparedObsevers->Get(aObserverId);
+  if (NS_WARN_IF(!observer)) {
+    ASSERT_UNLESS_FUZZING();
+    return nullptr;
+  }
+
+  //observer->SetObject(this);
 
   // Transfer ownership to IPDL.
-  return database.forget().take();
+  return observer.forget().take();
 }
 
 bool
-RecvPBackgroundLSDatabaseConstructor(PBackgroundLSDatabaseParent* aActor,
-                                     const uint64_t& aDatastoreId)
+RecvPBackgroundLSObserverConstructor(PBackgroundLSObserverParent* aActor,
+                                     const uint64_t& aObserverId)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aActor);
-  MOZ_ASSERT(gPreparedDatastores);
-  MOZ_ASSERT(gPreparedDatastores->Get(aDatastoreId));
-  MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
-
-  // The actor is now completely built (it has a manager, channel and it's
-  // registered as a subprotocol).
-  // ActorDestroy will be called if we fail here.
-
-  nsAutoPtr<PreparedDatastore> preparedDatastore;
-  gPreparedDatastores->Remove(aDatastoreId, &preparedDatastore);
-  MOZ_ASSERT(preparedDatastore);
-
-  auto* database = static_cast<Database*>(aActor);
-
-  database->SetActorAlive(preparedDatastore->ForgetDatastore());
-
-  // It's possible that AbortOperations was called before the database actor
-  // was created and became live. Let the child know that the database in no
-  // longer valid.
-  if (preparedDatastore->IsInvalidated()) {
-    database->RequestAllowToClose();
+  MOZ_ASSERT(gPreparedObsevers);
+  MOZ_ASSERT(gPreparedObsevers->GetWeak(aObserverId));
+
+  RefPtr<Observer> observer;
+  gPreparedObsevers->Remove(aObserverId, observer.StartAssignment());
+  MOZ_ASSERT(observer);
+
+  if (!gPreparedObsevers->Count()) {
+    gPreparedObsevers = nullptr;
   }
 
+  if (!gObservers) {
+    gObservers = new ObserverHashtable();
+  }
+
+  nsTArray<Observer*>* array;
+  if (!gObservers->Get(observer->Origin(), &array)) {
+    array = new nsTArray<Observer*>();
+    gObservers->Put(observer->Origin(), array);
+  }
+  array->AppendElement(observer);
+
   return true;
 }
 
 bool
-DeallocPBackgroundLSDatabaseParent(PBackgroundLSDatabaseParent* aActor)
+DeallocPBackgroundLSObserverParent(PBackgroundLSObserverParent* aActor)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aActor);
 
   // Transfer ownership back from IPDL.
-  RefPtr<Database> actor = dont_AddRef(static_cast<Database*>(aActor));
+  RefPtr<Observer> actor = dont_AddRef(static_cast<Observer*>(aActor));
 
   return true;
 }
 
 PBackgroundLSRequestParent*
 AllocPBackgroundLSRequestParent(PBackgroundParent* aBackgroundActor,
                                 const LSRequestParams& aParams)
 {
@@ -1599,16 +1827,25 @@ AllocPBackgroundLSRequestParent(PBackgro
       }
       gPrepareDatastoreOps->AppendElement(prepareDatastoreOp);
 
       actor = std::move(prepareDatastoreOp);
 
       break;
     }
 
+    case LSRequestParams::TLSRequestPrepareObserverParams: {
+      RefPtr<PrepareObserverOp> prepareObserverOp =
+        new PrepareObserverOp(mainEventTarget, aParams);
+
+      actor = std::move(prepareObserverOp);
+
+      break;
+    }
+
     default:
       MOZ_CRASH("Should never get here!");
   }
 
   // Transfer ownership to IPDL.
   return actor.forget().take();
 }
 
@@ -2302,67 +2539,117 @@ Datastore::GetItem(const nsString& aKey,
   MOZ_ASSERT(!mClosed);
 
   if (!mValues.Get(aKey, &aValue)) {
     aValue.SetIsVoid(true);
   }
 }
 
 void
-Datastore::SetItem(const nsString& aKey, const nsString& aValue)
+Datastore::SetItem(Database* aDatabase,
+                   const nsString& aKey,
+                   const nsString& aValue,
+                   bool* aChanged,
+                   nsString& aOldValue)
 {
   AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aDatabase);
+  MOZ_ASSERT(aChanged);
   MOZ_ASSERT(!mClosed);
 
-  mValues.Put(aKey, aValue);
-
-  if (!IsPersistent()) {
-    return;
+  nsString oldValue;
+  if (!mValues.Get(aKey, &oldValue)) {
+    oldValue.SetIsVoid(true);
   }
 
-  EnsureTransaction();
-
-  RefPtr<SetItemOp> op = new SetItemOp(mConnection, aKey, aValue);
-  mConnection->Dispatch(op);
+  bool changed;
+  if (oldValue == aValue && oldValue.IsVoid() == aValue.IsVoid()) {
+    changed = false;
+  } else {
+    changed = true;
+
+    mValues.Put(aKey, aValue);
+
+    NotifyObservers(aDatabase, aKey, oldValue, aValue);
+
+    if (IsPersistent()) {
+      EnsureTransaction();
+
+      RefPtr<SetItemOp> op = new SetItemOp(mConnection, aKey, aValue);
+      mConnection->Dispatch(op);
+    }
+  }
+
+  *aChanged = changed;
+  aOldValue = oldValue;
 }
 
 void
-Datastore::RemoveItem(const nsString& aKey)
+Datastore::RemoveItem(Database* aDatabase,
+                      const nsString& aKey,
+                      bool* aChanged,
+                      nsString& aOldValue)
 {
   AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aDatabase);
   MOZ_ASSERT(!mClosed);
 
-  mValues.Remove(aKey);
-
-  if (!IsPersistent()) {
-    return;
+  bool changed;
+  nsString oldValue;
+  if (!mValues.Get(aKey, &oldValue)) {
+    oldValue.SetIsVoid(true);
+    changed = false;
+  } else {
+    changed = true;
+
+    mValues.Remove(aKey);
+
+    NotifyObservers(aDatabase, aKey, oldValue, VoidString());
+
+    if (IsPersistent()) {
+      EnsureTransaction();
+
+      RefPtr<RemoveItemOp> op = new RemoveItemOp(mConnection, aKey);
+      mConnection->Dispatch(op);
+    }
   }
 
-  EnsureTransaction();
-
-  RefPtr<RemoveItemOp> op = new RemoveItemOp(mConnection, aKey);
-  mConnection->Dispatch(op);
+  *aChanged = changed;
+  aOldValue = oldValue;
 }
 
 void
-Datastore::Clear()
+Datastore::Clear(Database* aDatabase,
+                 bool* aChanged)
 {
   AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aChanged);
   MOZ_ASSERT(!mClosed);
 
-  mValues.Clear();
-
-  if (!IsPersistent()) {
-    return;
+  bool changed;
+  if (!mValues.Count()) {
+    changed = false;
+  } else {
+    changed = true;
+
+    mValues.Clear();
+
+    if (aDatabase) {
+      NotifyObservers(aDatabase, VoidString(), VoidString(), VoidString());
+    }
+
+    if (IsPersistent()) {
+      EnsureTransaction();
+
+      RefPtr<ClearOp> op = new ClearOp(mConnection);
+      mConnection->Dispatch(op);
+    }
   }
 
-  EnsureTransaction();
-
-  RefPtr<ClearOp> op = new ClearOp(mConnection);
-  mConnection->Dispatch(op);
+  *aChanged = changed;
 }
 
 void
 Datastore::GetKeys(nsTArray<nsString>& aKeys) const
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mClosed);
 
@@ -2402,16 +2689,45 @@ Datastore::CleanupMetadata()
   gDatastores->Remove(mOrigin);
 
   if (!gDatastores->Count()) {
     gDatastores = nullptr;
   }
 }
 
 void
+Datastore::NotifyObservers(Database* aDatabase,
+                           const nsString& aKey,
+                           const nsString& aOldValue,
+                           const nsString& aNewValue)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aDatabase);
+
+  if (!gObservers) {
+    return;
+  }
+
+  nsTArray<Observer*>* array;
+  if (!gObservers->Get(mOrigin, &array)) {
+    return;
+  }
+
+  MOZ_ASSERT(array);
+
+  PBackgroundParent* databaseBackgroundActor = aDatabase->Manager()->Manager();
+
+  for (Observer* observer : *array) {
+    if (observer->Manager() != databaseBackgroundActor) {
+      observer->Observe(aDatabase, aKey, aOldValue, aNewValue);
+    }
+  }
+}
+
+void
 Datastore::EnsureTransaction()
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(mConnection);
 
   if (!mConnection->InTransaction()) {
     mConnection->Begin(/* aReadonly */ false);
 
@@ -2441,21 +2757,143 @@ Datastore::AutoCommitTimerCallback(nsITi
 
   MOZ_ASSERT(self->mConnection);
   MOZ_ASSERT(self->mConnection->InTransaction());
 
   self->mConnection->Commit();
 }
 
 /*******************************************************************************
+ * Object
+ ******************************************************************************/
+
+Object::Object(const PrincipalInfo& aPrincipalInfo,
+               const nsAString& aDocumentURI,
+               uint32_t aPrivateBrowsingId)
+  : mPrincipalInfo(aPrincipalInfo)
+  , mDocumentURI(aDocumentURI)
+  , mPrivateBrowsingId(aPrivateBrowsingId)
+  , mActorDestroyed(false)
+{
+  AssertIsOnBackgroundThread();
+}
+
+Object::~Object()
+{
+  MOZ_ASSERT(mActorDestroyed);
+}
+
+void
+Object::ActorDestroy(ActorDestroyReason aWhy)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mActorDestroyed);
+
+  mActorDestroyed = true;
+}
+
+mozilla::ipc::IPCResult
+Object::RecvDeleteMe()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mActorDestroyed);
+
+  IProtocol* mgr = Manager();
+  if (!PBackgroundLSObjectParent::Send__delete__(this)) {
+    return IPC_FAIL_NO_REASON(mgr);
+  }
+  return IPC_OK();
+}
+
+PBackgroundLSDatabaseParent*
+Object::AllocPBackgroundLSDatabaseParent(const uint64_t& aDatastoreId)
+{
+  AssertIsOnBackgroundThread();
+
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
+    return nullptr;
+  }
+
+  if (NS_WARN_IF(!gPreparedDatastores)) {
+    ASSERT_UNLESS_FUZZING();
+    return nullptr;
+  }
+
+  PreparedDatastore* preparedDatastore = gPreparedDatastores->Get(aDatastoreId);
+  if (NS_WARN_IF(!preparedDatastore)) {
+    ASSERT_UNLESS_FUZZING();
+    return nullptr;
+  }
+
+  // If we ever decide to return null from this point on, we need to make sure
+  // that the datastore is closed and the prepared datastore is removed from the
+  // gPreparedDatastores hashtable.
+  // We also assume that IPDL must call RecvPBackgroundLSDatabaseConstructor
+  // once we return a valid actor in this method.
+
+  RefPtr<Database> database = new Database(this,
+                                           preparedDatastore->Origin());
+
+  // Transfer ownership to IPDL.
+  return database.forget().take();
+}
+
+mozilla::ipc::IPCResult
+Object::RecvPBackgroundLSDatabaseConstructor(
+                                            PBackgroundLSDatabaseParent* aActor,
+                                            const uint64_t& aDatastoreId)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+  MOZ_ASSERT(gPreparedDatastores);
+  MOZ_ASSERT(gPreparedDatastores->Get(aDatastoreId));
+  MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
+
+  // The actor is now completely built (it has a manager, channel and it's
+  // registered as a subprotocol).
+  // ActorDestroy will be called if we fail here.
+
+  nsAutoPtr<PreparedDatastore> preparedDatastore;
+  gPreparedDatastores->Remove(aDatastoreId, &preparedDatastore);
+  MOZ_ASSERT(preparedDatastore);
+
+  auto* database = static_cast<Database*>(aActor);
+
+  database->SetActorAlive(preparedDatastore->ForgetDatastore());
+
+  // It's possible that AbortOperations was called before the database actor
+  // was created and became live. Let the child know that the database in no
+  // longer valid.
+  if (preparedDatastore->IsInvalidated()) {
+    database->RequestAllowToClose();
+  }
+
+  return IPC_OK();
+}
+
+bool
+Object::DeallocPBackgroundLSDatabaseParent(PBackgroundLSDatabaseParent* aActor)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  // Transfer ownership back from IPDL.
+  RefPtr<Database> actor = dont_AddRef(static_cast<Database*>(aActor));
+
+  return true;
+}
+
+/*******************************************************************************
  * Database
  ******************************************************************************/
 
-Database::Database(const nsACString& aOrigin)
-  : mOrigin(aOrigin)
+Database::Database(Object* aObject,
+                   const nsACString& aOrigin)
+  : mObject(aObject)
+  , mOrigin(aOrigin)
   , mAllowedToClose(false)
   , mActorDestroyed(false)
   , mRequestedAllowToClose(false)
 #ifdef DEBUG
   , mActorWasAlive(false)
 #endif
 {
   AssertIsOnBackgroundThread();
@@ -2618,59 +3056,69 @@ Database::RecvGetItem(const nsString& aK
   }
 
   mDatastore->GetItem(aKey, *aValue);
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
-Database::RecvSetItem(const nsString& aKey, const nsString& aValue)
+Database::RecvSetItem(const nsString& aKey,
+                      const nsString& aValue,
+                      bool* aChanged,
+                      nsString* aOldValue)
 {
   AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aChanged);
+  MOZ_ASSERT(aOldValue);
   MOZ_ASSERT(mDatastore);
 
   if (NS_WARN_IF(mAllowedToClose)) {
     ASSERT_UNLESS_FUZZING();
     return IPC_FAIL_NO_REASON(this);
   }
 
-  mDatastore->SetItem(aKey, aValue);
+  mDatastore->SetItem(this, aKey, aValue, aChanged, *aOldValue);
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
-Database::RecvRemoveItem(const nsString& aKey)
+Database::RecvRemoveItem(const nsString& aKey,
+                         bool* aChanged,
+                         nsString* aOldValue)
 {
   AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aChanged);
+  MOZ_ASSERT(aOldValue);
   MOZ_ASSERT(mDatastore);
 
   if (NS_WARN_IF(mAllowedToClose)) {
     ASSERT_UNLESS_FUZZING();
     return IPC_FAIL_NO_REASON(this);
   }
 
-  mDatastore->RemoveItem(aKey);
+  mDatastore->RemoveItem(this, aKey, aChanged, *aOldValue);
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
-Database::RecvClear()
+Database::RecvClear(bool* aChanged)
 {
   AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aChanged);
   MOZ_ASSERT(mDatastore);
 
   if (NS_WARN_IF(mAllowedToClose)) {
     ASSERT_UNLESS_FUZZING();
     return IPC_FAIL_NO_REASON(this);
   }
 
-  mDatastore->Clear();
+  mDatastore->Clear(this, aChanged);
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 Database::RecvGetKeys(nsTArray<nsString>* aKeys)
 {
   AssertIsOnBackgroundThread();
@@ -2683,19 +3131,214 @@ Database::RecvGetKeys(nsTArray<nsString>
   }
 
   mDatastore->GetKeys(*aKeys);
 
   return IPC_OK();
 }
 
 /*******************************************************************************
+ * Observer
+ ******************************************************************************/
+
+Observer::Observer(const nsACString& aOrigin)
+  : mOrigin(aOrigin)
+  , mActorDestroyed(false)
+{
+  AssertIsOnBackgroundThread();
+}
+
+Observer::~Observer()
+{
+  MOZ_ASSERT(mActorDestroyed);
+}
+
+void
+Observer::Observe(Database* aDatabase,
+                  const nsString& aKey,
+                  const nsString& aOldValue,
+                  const nsString& aNewValue)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aDatabase);
+
+  Object* object = aDatabase->GetObject();
+  MOZ_ASSERT(object);
+
+  Unused << SendObserve(object->GetPrincipalInfo(),
+                        object->PrivateBrowsingId(),
+                        object->DocumentURI(),
+                        aKey,
+                        aOldValue,
+                        aNewValue);
+}
+
+void
+Observer::ActorDestroy(ActorDestroyReason aWhy)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mActorDestroyed);
+
+  mActorDestroyed = true;
+
+  MOZ_ASSERT(gObservers);
+
+  nsTArray<Observer*>* array;
+  gObservers->Get(mOrigin, &array);
+  MOZ_ASSERT(array);
+
+  array->RemoveElement(this);
+
+  if (array->IsEmpty()) {
+    gObservers->Remove(mOrigin);
+  }
+
+  if (!gObservers->Count()) {
+    gObservers = nullptr;
+  }
+}
+
+mozilla::ipc::IPCResult
+Observer::RecvDeleteMe()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mActorDestroyed);
+
+  IProtocol* mgr = Manager();
+  if (!PBackgroundLSObserverParent::Send__delete__(this)) {
+    return IPC_FAIL_NO_REASON(mgr);
+  }
+  return IPC_OK();
+}
+
+/*******************************************************************************
  * LSRequestBase
  ******************************************************************************/
 
+LSRequestBase::LSRequestBase(nsIEventTarget* aMainEventTarget)
+  : mMainEventTarget(aMainEventTarget)
+  , mState(State::Initial)
+{
+}
+
+LSRequestBase::~LSRequestBase()
+{
+  MOZ_ASSERT_IF(MayProceedOnNonOwningThread(),
+                mState == State::Initial || mState == State::Completed);
+}
+
+void
+LSRequestBase::Dispatch()
+{
+  AssertIsOnOwningThread();
+
+  mState = State::Opening;
+
+  if (mMainEventTarget) {
+    MOZ_ALWAYS_SUCCEEDS(mMainEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
+  } else {
+    MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
+  }
+}
+
+nsresult
+LSRequestBase::NestedRun()
+{
+  return NS_OK;
+}
+
+void
+LSRequestBase::SendReadyMessage()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mState == State::SendingReadyMessage);
+
+  if (!MayProceed()) {
+    MaybeSetFailureCode(NS_ERROR_FAILURE);
+
+    Cleanup();
+
+    mState = State::Completed;
+  } else {
+    Unused << SendReady();
+
+    mState = State::WaitingForFinish;
+  }
+}
+
+void
+LSRequestBase::SendResults()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mState == State::SendingResults);
+
+  if (!MayProceed()) {
+    MaybeSetFailureCode(NS_ERROR_FAILURE);
+  } else {
+    LSRequestResponse response;
+
+    if (NS_SUCCEEDED(ResultCode())) {
+      GetResponse(response);
+    } else {
+      response = ResultCode();
+    }
+
+    Unused <<
+      PBackgroundLSRequestParent::Send__delete__(this, response);
+  }
+
+  Cleanup();
+
+  mState = State::Completed;
+}
+
+NS_IMETHODIMP
+LSRequestBase::Run()
+{
+  nsresult rv;
+
+  switch (mState) {
+    case State::Opening:
+      rv = Open();
+      break;
+
+    case State::Nesting:
+      rv = NestedRun();
+      break;
+
+    case State::SendingReadyMessage:
+      SendReadyMessage();
+      return NS_OK;
+
+    case State::SendingResults:
+      SendResults();
+      return NS_OK;
+
+    default:
+      MOZ_CRASH("Bad state!");
+  }
+
+  if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::SendingReadyMessage) {
+    MaybeSetFailureCode(rv);
+
+    // Must set mState before dispatching otherwise we will race with the owning
+    // thread.
+    mState = State::SendingReadyMessage;
+
+    if (IsOnOwningThread()) {
+      SendReadyMessage();
+    } else {
+      MOZ_ALWAYS_SUCCEEDS(
+        OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
+    }
+  }
+
+  return NS_OK;
+}
+
 void
 LSRequestBase::ActorDestroy(ActorDestroyReason aWhy)
 {
   AssertIsOnOwningThread();
 
   NoteComplete();
 }
 
@@ -2707,61 +3350,61 @@ LSRequestBase::RecvCancel()
   IProtocol* mgr = Manager();
   if (!PBackgroundLSRequestParent::Send__delete__(this, NS_ERROR_FAILURE)) {
     return IPC_FAIL_NO_REASON(mgr);
   }
 
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+LSRequestBase::RecvFinish()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mState == State::WaitingForFinish);
+
+  mState = State::SendingResults;
+  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this));
+
+  return IPC_OK();
+}
+
 /*******************************************************************************
  * PrepareDatastoreOp
  ******************************************************************************/
 
 PrepareDatastoreOp::PrepareDatastoreOp(nsIEventTarget* aMainEventTarget,
                                        const LSRequestParams& aParams)
-  : mMainEventTarget(aMainEventTarget)
+  : LSRequestBase(aMainEventTarget)
+  , mMainEventTarget(aMainEventTarget)
   , mLoadDataOp(nullptr)
   , mParams(aParams.get_LSRequestPrepareDatastoreParams())
   , mPrivateBrowsingId(0)
-  , mState(State::Initial)
+  , mNestedState(NestedState::BeforeNesting)
   , mRequestedDirectoryLock(false)
   , mInvalidated(false)
 {
   MOZ_ASSERT(aParams.type() ==
                LSRequestParams::TLSRequestPrepareDatastoreParams);
 }
 
 PrepareDatastoreOp::~PrepareDatastoreOp()
 {
   MOZ_ASSERT(!mDirectoryLock);
   MOZ_ASSERT_IF(MayProceedOnNonOwningThread(),
                 mState == State::Initial || mState == State::Completed);
   MOZ_ASSERT(!mLoadDataOp);
 }
 
-void
-PrepareDatastoreOp::Dispatch()
-{
-  AssertIsOnOwningThread();
-
-  mState = State::Opening;
-
-  if (mMainEventTarget) {
-    MOZ_ALWAYS_SUCCEEDS(mMainEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
-  } else {
-    MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
-  }
-}
-
 nsresult
 PrepareDatastoreOp::Open()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mState == State::Opening);
+  MOZ_ASSERT(mNestedState == NestedState::BeforeNesting);
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
       !MayProceedOnNonOwningThread()) {
     return NS_ERROR_FAILURE;
   }
 
   const PrincipalInfo& principalInfo = mParams.principalInfo();
 
@@ -2797,27 +3440,30 @@ PrepareDatastoreOp::Open()
     return NS_ERROR_FAILURE;
   }
 
   nsresult rv = QuotaClient::RegisterObservers(OwningEventTarget());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  mState = State::CheckExistingOperations;
+  mState = State::Nesting;
+  mNestedState = NestedState::CheckExistingOperations;
+
   MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
 
   return NS_OK;
 }
 
 nsresult
 PrepareDatastoreOp::CheckExistingOperations()
 {
   AssertIsOnOwningThread();
-  MOZ_ASSERT(mState == State::CheckExistingOperations);
+  MOZ_ASSERT(mState == State::Nesting);
+  MOZ_ASSERT(mNestedState == NestedState::CheckExistingOperations);
   MOZ_ASSERT(gPrepareDatastoreOps);
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
       !MayProceed()) {
     return NS_ERROR_FAILURE;
   }
 
   // Normally it's safe to access member variables without a mutex because even
@@ -2825,17 +3471,17 @@ PrepareDatastoreOp::CheckExistingOperati
   // threads at the same time.
   // However, the methods OriginIsKnown and Origin can be called at any time.
   // So we have to make sure the member variable is set on the same thread as
   // those methods are called.
   mOrigin = mMainThreadOrigin;
 
   MOZ_ASSERT(!mOrigin.IsEmpty());
 
-  mState = State::CheckClosingDatastore;
+  mNestedState = NestedState::CheckClosingDatastore;
 
   // See if this PrepareDatastoreOp needs to wait.
   bool foundThis = false;
   for (uint32_t index = gPrepareDatastoreOps->Length(); index > 0; index--) {
     PrepareDatastoreOp* existingOp = (*gPrepareDatastoreOps)[index - 1];
 
     if (existingOp == this) {
       foundThis = true;
@@ -2858,24 +3504,25 @@ PrepareDatastoreOp::CheckExistingOperati
 
   return NS_OK;
 }
 
 nsresult
 PrepareDatastoreOp::CheckClosingDatastore()
 {
   AssertIsOnOwningThread();
-  MOZ_ASSERT(mState == State::CheckClosingDatastore);
+  MOZ_ASSERT(mState == State::Nesting);
+  MOZ_ASSERT(mNestedState == NestedState::CheckClosingDatastore);
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
       !MayProceed()) {
     return NS_ERROR_FAILURE;
   }
 
-  mState = State::PreparationPending;
+  mNestedState = NestedState::PreparationPending;
 
   RefPtr<Datastore> datastore;
   if (gDatastores &&
       (datastore = gDatastores->Get(mOrigin)) &&
       datastore->IsClosed()) {
     datastore->WaitForConnectionToComplete(this);
 
     return NS_OK;
@@ -2888,47 +3535,51 @@ PrepareDatastoreOp::CheckClosingDatastor
 
   return NS_OK;
 }
 
 nsresult
 PrepareDatastoreOp::BeginDatastorePreparation()
 {
   AssertIsOnOwningThread();
-  MOZ_ASSERT(mState == State::PreparationPending);
+  MOZ_ASSERT(mState == State::Nesting);
+  MOZ_ASSERT(mNestedState == NestedState::PreparationPending);
 
   if (gDatastores && (mDatastore = gDatastores->Get(mOrigin))) {
     MOZ_ASSERT(!mDatastore->IsClosed());
 
     mState = State::SendingReadyMessage;
+    mNestedState = NestedState::AfterNesting;
+
     Unused << this->Run();
 
     return NS_OK;
   }
 
   if (QuotaManager::Get()) {
     nsresult rv = OpenDirectory();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     return NS_OK;
   }
 
-  mState = State::QuotaManagerPending;
+  mNestedState = NestedState::QuotaManagerPending;
   QuotaManager::GetOrCreate(this, mMainEventTarget);
 
   return NS_OK;
 }
 
 nsresult
 PrepareDatastoreOp::QuotaManagerOpen()
 {
   AssertIsOnOwningThread();
-  MOZ_ASSERT(mState == State::QuotaManagerPending);
+  MOZ_ASSERT(mState == State::Nesting);
+  MOZ_ASSERT(mNestedState == NestedState::QuotaManagerPending);
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
       !MayProceed()) {
     return NS_ERROR_FAILURE;
   }
 
   if (NS_WARN_IF(!QuotaManager::Get())) {
     return NS_ERROR_FAILURE;
@@ -2941,41 +3592,43 @@ PrepareDatastoreOp::QuotaManagerOpen()
 
   return NS_OK;
 }
 
 nsresult
 PrepareDatastoreOp::OpenDirectory()
 {
   AssertIsOnOwningThread();
-  MOZ_ASSERT(mState == State::PreparationPending ||
-             mState == State::QuotaManagerPending);
+  MOZ_ASSERT(mState == State::Nesting);
+  MOZ_ASSERT(mNestedState == NestedState::PreparationPending ||
+             mNestedState == NestedState::QuotaManagerPending);
   MOZ_ASSERT(!mOrigin.IsEmpty());
   MOZ_ASSERT(!mDirectoryLock);
   MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
   MOZ_ASSERT(QuotaManager::Get());
 
-  mState = State::DirectoryOpenPending;
+  mNestedState = NestedState::DirectoryOpenPending;
   QuotaManager::Get()->OpenDirectory(PERSISTENCE_TYPE_DEFAULT,
                                      mGroup,
                                      mOrigin,
                                      mozilla::dom::quota::Client::LS,
                                      /* aExclusive */ false,
                                      this);
 
   mRequestedDirectoryLock = true;
 
   return NS_OK;
 }
 
 nsresult
 PrepareDatastoreOp::SendToIOThread()
 {
   AssertIsOnOwningThread();
-  MOZ_ASSERT(mState == State::DirectoryOpenPending);
+  MOZ_ASSERT(mState == State::Nesting);
+  MOZ_ASSERT(mNestedState == NestedState::DirectoryOpenPending);
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
       !MayProceed()) {
     return NS_ERROR_FAILURE;
   }
 
   // Skip all disk related stuff and transition to SendingReadyMessage if we
   // are preparing a datastore for private browsing.
@@ -2983,40 +3636,43 @@ PrepareDatastoreOp::SendToIOThread()
   // don't do any stuff on disk. The thing is that without a directory lock,
   // quota manager wouldn't call AbortOperations for our private browsing
   // origin when a clear origin operation is requested. AbortOperations
   // requests all databases to close and the datastore is destroyed in the end.
   // Any following LocalStorage API call will trigger preparation of a new
   // (empty) datastore.
   if (mPrivateBrowsingId) {
     mState = State::SendingReadyMessage;
+    mNestedState = NestedState::AfterNesting;
+
     Unused << this->Run();
 
     return NS_OK;
   }
 
   QuotaManager* quotaManager = QuotaManager::Get();
   MOZ_ASSERT(quotaManager);
 
   // Must set this before dispatching otherwise we will race with the IO thread.
-  mState = State::DatabaseWorkOpen;
+  mNestedState = NestedState::DatabaseWorkOpen;
 
   nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
 nsresult
 PrepareDatastoreOp::DatabaseWork()
 {
   AssertIsOnIOThread();
-  MOZ_ASSERT(mState == State::DatabaseWorkOpen);
+  MOZ_ASSERT(mState == State::Nesting);
+  MOZ_ASSERT(mNestedState == NestedState::DatabaseWorkOpen);
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
       !MayProceedOnNonOwningThread()) {
     return NS_ERROR_FAILURE;
   }
 
   QuotaManager* quotaManager = QuotaManager::Get();
   MOZ_ASSERT(quotaManager);
@@ -3085,17 +3741,17 @@ PrepareDatastoreOp::DatabaseWork()
   }
 
   // Must close connection before dispatching otherwise we might race with the
   // connection thread which needs to open the same database.
   MOZ_ALWAYS_SUCCEEDS(connection->Close());
 
   // Must set mState before dispatching otherwise we will race with the owning
   // thread.
-  mState = State::BeginLoadData;
+  mNestedState = NestedState::BeginLoadData;
 
   rv = OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
@@ -3137,17 +3793,18 @@ PrepareDatastoreOp::VerifyDatabaseInform
 
   return NS_OK;
 }
 
 nsresult
 PrepareDatastoreOp::BeginLoadData()
 {
   AssertIsOnOwningThread();
-  MOZ_ASSERT(mState == State::BeginLoadData);
+  MOZ_ASSERT(mState == State::Nesting);
+  MOZ_ASSERT(mNestedState == NestedState::BeginLoadData);
   MOZ_ASSERT(!mConnection);
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
       !MayProceed()) {
     return NS_ERROR_FAILURE;
   }
 
   if (!gConnectionThread) {
@@ -3157,109 +3814,117 @@ PrepareDatastoreOp::BeginLoadData()
   mConnection = gConnectionThread->CreateConnection(mOrigin,
                                                     mDatabaseFilePath);
   MOZ_ASSERT(mConnection);
 
   MOZ_ASSERT(!mConnection->InTransaction());
 
   // Must set this before dispatching otherwise we will race with the
   // connection thread.
-  mState = State::DatabaseWorkLoadData;
+  mNestedState = NestedState::DatabaseWorkLoadData;
 
   // Can't assign to mLoadDataOp directly since that's a weak reference and
   // LoadDataOp is reference counted.
   RefPtr<LoadDataOp> loadDataOp = new LoadDataOp(this);
 
   // This add refs loadDataOp.
   mConnection->Dispatch(loadDataOp);
 
   // This is cleared in LoadDataOp::Cleanup() before the load data op is
   // destroyed.
   mLoadDataOp = loadDataOp;
 
   return NS_OK;
 }
 
-void
-PrepareDatastoreOp::SendReadyMessage()
+nsresult
+PrepareDatastoreOp::NestedRun()
 {
-  AssertIsOnOwningThread();
-  MOZ_ASSERT(mState == State::SendingReadyMessage);
-
-  if (!MayProceed()) {
-    MaybeSetFailureCode(NS_ERROR_FAILURE);
-
-    Cleanup();
-
-    mState = State::Completed;
-  } else {
-    Unused << SendReady();
-
-    mState = State::WaitingForFinish;
+  nsresult rv;
+
+  switch (mNestedState) {
+    case NestedState::CheckExistingOperations:
+      rv = CheckExistingOperations();
+      break;
+
+    case NestedState::CheckClosingDatastore:
+      rv = CheckClosingDatastore();
+      break;
+
+    case NestedState::PreparationPending:
+      rv = BeginDatastorePreparation();
+      break;
+
+    case NestedState::QuotaManagerPending:
+      rv = QuotaManagerOpen();
+      break;
+
+    case NestedState::DatabaseWorkOpen:
+      rv = DatabaseWork();
+      break;
+
+    case NestedState::BeginLoadData:
+      rv = BeginLoadData();
+      break;
+
+    default:
+      MOZ_CRASH("Bad state!");
   }
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    mNestedState = NestedState::AfterNesting;
+
+    return rv;
+  }
+
+  return NS_OK;
 }
 
 void
-PrepareDatastoreOp::SendResults()
+PrepareDatastoreOp::GetResponse(LSRequestResponse& aResponse)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::SendingResults);
-
-  if (!MayProceed()) {
-    MaybeSetFailureCode(NS_ERROR_FAILURE);
-  } else {
-    LSRequestResponse response;
-
-    if (NS_SUCCEEDED(ResultCode())) {
-      if (!mDatastore) {
-        mDatastore = new Datastore(mOrigin,
-                                   mPrivateBrowsingId,
-                                   mDirectoryLock.forget(),
-                                   mConnection.forget(),
-                                   mValues);
-
-        if (!gDatastores) {
-          gDatastores = new DatastoreHashtable();
-        }
-
-        MOZ_ASSERT(!gDatastores->Get(mOrigin));
-        gDatastores->Put(mOrigin, mDatastore);
-      }
-
-      uint64_t datastoreId = ++gLastDatastoreId;
-
-      nsAutoPtr<PreparedDatastore> preparedDatastore(
-        new PreparedDatastore(mDatastore, mOrigin));
-
-      if (!gPreparedDatastores) {
-        gPreparedDatastores = new PreparedDatastoreHashtable();
-      }
-      gPreparedDatastores->Put(datastoreId, preparedDatastore);
-
-      if (mInvalidated) {
-        preparedDatastore->Invalidate();
-      }
-
-      preparedDatastore.forget();
-
-      LSRequestPrepareDatastoreResponse prepareDatastoreResponse;
-      prepareDatastoreResponse.datastoreId() = datastoreId;
-
-      response = prepareDatastoreResponse;
-    } else {
-      response = ResultCode();
+  MOZ_ASSERT(NS_SUCCEEDED(ResultCode()));
+
+  if (!mDatastore) {
+    mDatastore = new Datastore(mOrigin,
+                               mPrivateBrowsingId,
+                               mDirectoryLock.forget(),
+                               mConnection.forget(),
+                               mValues);
+
+    if (!gDatastores) {
+      gDatastores = new DatastoreHashtable();
     }
 
-    Unused <<
-      PBackgroundLSRequestParent::Send__delete__(this, response);
+    MOZ_ASSERT(!gDatastores->Get(mOrigin));
+    gDatastores->Put(mOrigin, mDatastore);
+  }
+
+  uint64_t datastoreId = ++gLastDatastoreId;
+
+  nsAutoPtr<PreparedDatastore> preparedDatastore(
+    new PreparedDatastore(mDatastore, mOrigin));
+
+  if (!gPreparedDatastores) {
+    gPreparedDatastores = new PreparedDatastoreHashtable();
   }
-
-  Cleanup();
-
-  mState = State::Completed;
+  gPreparedDatastores->Put(datastoreId, preparedDatastore);
+
+  if (mInvalidated) {
+    preparedDatastore->Invalidate();
+  }
+
+  preparedDatastore.forget();
+
+  LSRequestPrepareDatastoreResponse prepareDatastoreResponse;
+  prepareDatastoreResponse.datastoreId() = datastoreId;
+
+  aResponse = prepareDatastoreResponse;
 }
 
 void
 PrepareDatastoreOp::Cleanup()
 {
   AssertIsOnOwningThread();
 
   if (mDatastore) {
@@ -3332,151 +3997,83 @@ PrepareDatastoreOp::CleanupMetadata()
 
   if (gPrepareDatastoreOps->IsEmpty()) {
     gPrepareDatastoreOps = nullptr;
   }
 }
 
 NS_IMPL_ISUPPORTS_INHERITED0(PrepareDatastoreOp, LSRequestBase)
 
-NS_IMETHODIMP
-PrepareDatastoreOp::Run()
-{
-  nsresult rv;
-
-  switch (mState) {
-    case State::Opening:
-      rv = Open();
-      break;
-
-    case State::CheckExistingOperations:
-      rv = CheckExistingOperations();
-      break;
-
-    case State::CheckClosingDatastore:
-      rv = CheckClosingDatastore();
-      break;
-
-    case State::PreparationPending:
-      rv = BeginDatastorePreparation();
-      break;
-
-    case State::QuotaManagerPending:
-      rv = QuotaManagerOpen();
-      break;
-
-    case State::DatabaseWorkOpen:
-      rv = DatabaseWork();
-      break;
-
-    case State::BeginLoadData:
-      rv = BeginLoadData();
-      break;
-
-    case State::SendingReadyMessage:
-      SendReadyMessage();
-      return NS_OK;
-
-    case State::SendingResults:
-      SendResults();
-      return NS_OK;
-
-    default:
-      MOZ_CRASH("Bad state!");
-  }
-
-  if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::SendingReadyMessage) {
-    MaybeSetFailureCode(rv);
-
-    // Must set mState before dispatching otherwise we will race with the owning
-    // thread.
-    mState = State::SendingReadyMessage;
-
-    if (IsOnOwningThread()) {
-      SendReadyMessage();
-    } else {
-      MOZ_ALWAYS_SUCCEEDS(
-        OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
-    }
-  }
-
-  return NS_OK;
-}
-
 void
 PrepareDatastoreOp::ActorDestroy(ActorDestroyReason aWhy)
 {
   AssertIsOnOwningThread();
 
   LSRequestBase::ActorDestroy(aWhy);
 
   if (mLoadDataOp) {
     mLoadDataOp->NoteComplete();
   }
 }
 
-mozilla::ipc::IPCResult
-PrepareDatastoreOp::RecvFinish()
-{
-  AssertIsOnOwningThread();
-  MOZ_ASSERT(mState == State::WaitingForFinish);
-
-  mState = State::SendingResults;
-  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this));
-
-  return IPC_OK();
-}
-
 void
 PrepareDatastoreOp::DirectoryLockAcquired(DirectoryLock* aLock)
 {
   AssertIsOnOwningThread();
-  MOZ_ASSERT(mState == State::DirectoryOpenPending);
+  MOZ_ASSERT(mState == State::Nesting);
+  MOZ_ASSERT(mNestedState == NestedState::DirectoryOpenPending);
   MOZ_ASSERT(!mDirectoryLock);
 
   mDirectoryLock = aLock;
 
   nsresult rv = SendToIOThread();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     MaybeSetFailureCode(rv);
 
     // The caller holds a strong reference to us, no need for a self reference
     // before calling Run().
 
     mState = State::SendingReadyMessage;
+    mNestedState = NestedState::AfterNesting;
+
     MOZ_ALWAYS_SUCCEEDS(Run());
 
     return;
   }
 }
 
 void
 PrepareDatastoreOp::DirectoryLockFailed()
 {
   AssertIsOnOwningThread();
-  MOZ_ASSERT(mState == State::DirectoryOpenPending);
+  MOZ_ASSERT(mState == State::Nesting);
+  MOZ_ASSERT(mNestedState == NestedState::DirectoryOpenPending);
   MOZ_ASSERT(!mDirectoryLock);
 
   MaybeSetFailureCode(NS_ERROR_FAILURE);
 
   // The caller holds a strong reference to us, no need for a self reference
   // before calling Run().
 
   mState = State::SendingReadyMessage;
+  mNestedState = NestedState::AfterNesting;
+
   MOZ_ALWAYS_SUCCEEDS(Run());
 }
 
 nsresult
 PrepareDatastoreOp::
 LoadDataOp::DoDatastoreWork()
 {
   AssertIsOnConnectionThread();
   MOZ_ASSERT(mConnection);
   MOZ_ASSERT(mPrepareDatastoreOp);
-  MOZ_ASSERT(mPrepareDatastoreOp->mState == State::DatabaseWorkLoadData);
+  MOZ_ASSERT(mPrepareDatastoreOp->mState == State::Nesting);
+  MOZ_ASSERT(mPrepareDatastoreOp->mNestedState ==
+               NestedState::DatabaseWorkLoadData);
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
       !MayProceedOnNonOwningThread()) {
     return NS_ERROR_FAILURE;
   }
 
   Connection::CachedStatement stmt;
   nsresult rv = mConnection->GetCachedStatement(NS_LITERAL_CSTRING(
@@ -3511,35 +4108,41 @@ LoadDataOp::DoDatastoreWork()
 }
 
 void
 PrepareDatastoreOp::
 LoadDataOp::OnSuccess()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mPrepareDatastoreOp);
-  MOZ_ASSERT(mPrepareDatastoreOp->mState == State::DatabaseWorkLoadData);
+  MOZ_ASSERT(mPrepareDatastoreOp->mState == State::Nesting);
+  MOZ_ASSERT(mPrepareDatastoreOp->mNestedState ==
+               NestedState::DatabaseWorkLoadData);
   MOZ_ASSERT(mPrepareDatastoreOp->mLoadDataOp == this);
 
   mPrepareDatastoreOp->mState = State::SendingReadyMessage;
+  mPrepareDatastoreOp->mNestedState = NestedState::AfterNesting;
 
   MOZ_ALWAYS_SUCCEEDS(mPrepareDatastoreOp->Run());
 }
 
 void
 PrepareDatastoreOp::
 LoadDataOp::OnFailure(nsresult aResultCode)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mPrepareDatastoreOp);
-  MOZ_ASSERT(mPrepareDatastoreOp->mState == State::DatabaseWorkLoadData);
+  MOZ_ASSERT(mPrepareDatastoreOp->mState == State::Nesting);
+  MOZ_ASSERT(mPrepareDatastoreOp->mNestedState ==
+               NestedState::DatabaseWorkLoadData);
   MOZ_ASSERT(mPrepareDatastoreOp->mLoadDataOp == this);
 
   mPrepareDatastoreOp->SetFailureCode(aResultCode);
   mPrepareDatastoreOp->mState = State::SendingReadyMessage;
+  mPrepareDatastoreOp->mNestedState = NestedState::AfterNesting;
 
   MOZ_ALWAYS_SUCCEEDS(mPrepareDatastoreOp->Run());
 }
 
 void
 PrepareDatastoreOp::
 LoadDataOp::Cleanup()
 {
@@ -3549,16 +4152,91 @@ LoadDataOp::Cleanup()
 
   mPrepareDatastoreOp->mLoadDataOp = nullptr;
   mPrepareDatastoreOp = nullptr;
 
   ConnectionDatastoreOperationBase::Cleanup();
 }
 
 /*******************************************************************************
+ * PrepareObserverOp
+ ******************************************************************************/
+
+PrepareObserverOp::PrepareObserverOp(nsIEventTarget* aMainEventTarget,
+                                     const LSRequestParams& aParams)
+  : LSRequestBase(aMainEventTarget)
+  , mParams(aParams.get_LSRequestPrepareObserverParams())
+{
+  MOZ_ASSERT(aParams.type() ==
+               LSRequestParams::TLSRequestPrepareObserverParams);
+}
+
+nsresult
+PrepareObserverOp::Open()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == State::Opening);
+
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
+      !MayProceedOnNonOwningThread()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  const PrincipalInfo& principalInfo = mParams.principalInfo();
+
+  if (principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
+    QuotaManager::GetInfoForChrome(nullptr, nullptr, &mOrigin);
+  } else {
+    MOZ_ASSERT(principalInfo.type() == PrincipalInfo::TContentPrincipalInfo);
+
+    nsresult rv;
+    nsCOMPtr<nsIPrincipal> principal =
+      PrincipalInfoToPrincipal(principalInfo, &rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = QuotaManager::GetInfoFromPrincipal(principal,
+                                            nullptr,
+                                            nullptr,
+                                            &mOrigin);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  mState = State::SendingReadyMessage;
+  MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
+
+  return NS_OK;
+}
+
+void
+PrepareObserverOp::GetResponse(LSRequestResponse& aResponse)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mState == State::SendingResults);
+  MOZ_ASSERT(NS_SUCCEEDED(ResultCode()));
+
+  uint64_t observerId = ++gLastObserverId;
+
+  RefPtr<Observer> observer = new Observer(mOrigin);
+
+  if (!gPreparedObsevers) {
+    gPreparedObsevers = new PreparedObserverHashtable();
+  }
+  gPreparedObsevers->Put(observerId, observer);
+
+  LSRequestPrepareObserverResponse prepareObserverResponse;
+  prepareObserverResponse.observerId() = observerId;
+
+  aResponse = prepareObserverResponse;
+}
+
+/*******************************************************************************
  * QuotaClient
  ******************************************************************************/
 
 QuotaClient* QuotaClient::sInstance = nullptr;
 bool QuotaClient::sPrivateBrowsingObserverRegistered = false;
 
 QuotaClient::QuotaClient()
   : mShutdownRequested(false)
@@ -3894,16 +4572,21 @@ QuotaClient::ShutdownWorkThreads()
   }
 
   if (gLiveDatabases) {
     for (Database* database : *gLiveDatabases) {
       database->RequestAllowToClose();
     }
   }
 
+  if (gPreparedObsevers) {
+    gPreparedObsevers->Clear();
+    gPreparedObsevers = nullptr;
+  }
+
   // This should release any local storage related quota objects or directory
   // locks.
   MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() {
     // Don't have to check gPreparedDatastores since we nulled it out above.
     return !gPrepareDatastoreOps && !gDatastores && !gLiveDatabases;
   }));
 
   // And finally, shutdown the connection thread.
@@ -3921,17 +4604,18 @@ ClearPrivateBrowsingRunnable::Run()
   AssertIsOnBackgroundThread();
 
   if (gDatastores) {
     for (auto iter = gDatastores->ConstIter(); !iter.Done(); iter.Next()) {
       Datastore* datastore = iter.Data();
       MOZ_ASSERT(datastore);
 
       if (datastore->PrivateBrowsingId()) {
-        datastore->Clear();
+        bool dummy;
+        datastore->Clear(nullptr, &dummy);
       }
     }
   }
 
   return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS(QuotaClient::PrivateBrowsingObserver, nsIObserver)
--- a/dom/localstorage/ActorsParent.h
+++ b/dom/localstorage/ActorsParent.h
@@ -7,40 +7,58 @@
 #ifndef mozilla_dom_localstorage_ActorsParent_h
 #define mozilla_dom_localstorage_ActorsParent_h
 
 namespace mozilla {
 
 namespace ipc {
 
 class PBackgroundParent;
+class PrincipalInfo;
 
 } // namespace ipc
 
 namespace dom {
 
 class LSRequestParams;
-class PBackgroundLSDatabaseParent;
+class PBackgroundLSObjectParent;
+class PBackgroundLSObserverParent;
 class PBackgroundLSRequestParent;
 
 namespace quota {
 
 class Client;
 
 } // namespace quota
 
-PBackgroundLSDatabaseParent*
-AllocPBackgroundLSDatabaseParent(const uint64_t& aDatastoreId);
+PBackgroundLSObjectParent*
+AllocPBackgroundLSObjectParent(
+                              const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+                              const nsString& aDocumentURI,
+                              const uint32_t& aPrivateBrowsingId);
 
 bool
-RecvPBackgroundLSDatabaseConstructor(PBackgroundLSDatabaseParent* aActor,
-                                     const uint64_t& aDatastoreId);
+RecvPBackgroundLSObjectConstructor(
+                              PBackgroundLSObjectParent* aActor,
+                              const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+                              const nsString& aDocumentURI,
+                              const uint32_t& aPrivateBrowsingId);
 
 bool
-DeallocPBackgroundLSDatabaseParent(PBackgroundLSDatabaseParent* aActor);
+DeallocPBackgroundLSObjectParent(PBackgroundLSObjectParent* aActor);
+
+PBackgroundLSObserverParent*
+AllocPBackgroundLSObserverParent(const uint64_t& aObserverId);
+
+bool
+RecvPBackgroundLSObserverConstructor(PBackgroundLSObserverParent* aActor,
+                                     const uint64_t& aObservereId);
+
+bool
+DeallocPBackgroundLSObserverParent(PBackgroundLSObserverParent* aActor);
 
 PBackgroundLSRequestParent*
 AllocPBackgroundLSRequestParent(
                               mozilla::ipc::PBackgroundParent* aBackgroundActor,
                               const LSRequestParams& aParams);
 
 bool
 RecvPBackgroundLSRequestConstructor(PBackgroundLSRequestParent* aActor,
--- a/dom/localstorage/LSDatabase.cpp
+++ b/dom/localstorage/LSDatabase.cpp
@@ -116,51 +116,73 @@ LSDatabase::GetKeys(nsTArray<nsString>& 
   }
 
   aKeys.SwapElements(result);
   return NS_OK;
 }
 
 nsresult
 LSDatabase::SetItem(const nsAString& aKey,
-                    const nsAString& aValue)
+                    const nsAString& aValue,
+                    bool* aChanged,
+                    nsAString& aOldValue)
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(aChanged);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(!mAllowedToClose);
 
-  if (NS_WARN_IF(!mActor->SendSetItem(nsString(aKey), nsString(aValue)))) {
+  bool changed;
+  nsString oldValue;
+  if (NS_WARN_IF(!mActor->SendSetItem(nsString(aKey),
+                                      nsString(aValue),
+                                      &changed,
+                                      &oldValue))) {
     return NS_ERROR_FAILURE;
   }
 
+  *aChanged = changed;
+  aOldValue = oldValue;
   return NS_OK;
 }
 
 nsresult
-LSDatabase::RemoveItem(const nsAString& aKey)
+LSDatabase::RemoveItem(const nsAString& aKey,
+                       bool* aChanged,
+                       nsAString& aOldValue)
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(aChanged);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(!mAllowedToClose);
 
-  if (NS_WARN_IF(!mActor->SendRemoveItem(nsString(aKey)))) {
+  bool changed;
+  nsString oldValue;
+  if (NS_WARN_IF(!mActor->SendRemoveItem(nsString(aKey),
+                                         &changed,
+                                         &oldValue))) {
     return NS_ERROR_FAILURE;
   }
 
+  *aChanged = changed;
+  aOldValue = oldValue;
   return NS_OK;
 }
 
 nsresult
-LSDatabase::Clear()
+LSDatabase::Clear(bool* aChanged)
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(aChanged);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(!mAllowedToClose);
 
-  if (NS_WARN_IF(!mActor->SendClear())) {
+  bool changed;
+  if (NS_WARN_IF(!mActor->SendClear(&changed))) {
     return NS_ERROR_FAILURE;
   }
 
+  *aChanged = changed;
   return NS_OK;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/localstorage/LSDatabase.h
+++ b/dom/localstorage/LSDatabase.h
@@ -63,23 +63,27 @@ public:
   GetItem(const nsAString& aKey,
           nsAString& aResult);
 
   nsresult
   GetKeys(nsTArray<nsString>& aKeys);
 
   nsresult
   SetItem(const nsAString& aKey,
-          const nsAString& aValue);
+          const nsAString& aValue,
+          bool* aChanged,
+          nsAString& aOldValue);
 
   nsresult
-  RemoveItem(const nsAString& aKey);
+  RemoveItem(const nsAString& aKey,
+             bool* aChanged,
+             nsAString& aOldValue);
 
   nsresult
-  Clear();
+  Clear(bool* aChanged);
 
 private:
   ~LSDatabase();
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/localstorage/LSObject.cpp
+++ b/dom/localstorage/LSObject.cpp
@@ -122,24 +122,35 @@ private:
   OnResponse(const LSRequestResponse& aResponse) override;
 };
 
 } // namespace
 
 LSObject::LSObject(nsPIDOMWindowInner* aWindow,
                    nsIPrincipal* aPrincipal)
   : Storage(aWindow, aPrincipal)
+  , mActor(nullptr)
+  , mPrivateBrowsingId(0)
+  , mActorFailed(false)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(NextGenLocalStorageEnabled());
 }
 
 LSObject::~LSObject()
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT_IF(mActorFailed, !mActor);
+
+  DropObserver();
+
+  if (mActor) {
+    mActor->SendDeleteMeInternal();
+    MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
+  }
 }
 
 // static
 nsresult
 LSObject::Create(nsPIDOMWindowInner* aWindow,
                  Storage** aStorage)
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -160,18 +171,41 @@ LSObject::Create(nsPIDOMWindowInner* aWi
   nsAutoPtr<PrincipalInfo> principalInfo(new PrincipalInfo());
   nsresult rv = PrincipalToPrincipalInfo(principal, principalInfo);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   MOZ_ASSERT(principalInfo->type() == PrincipalInfo::TContentPrincipalInfo);
 
+  nsCString origin;
+  rv = QuotaManager::GetInfoFromPrincipal(principal, nullptr, nullptr, &origin);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  uint32_t privateBrowsingId;
+  rv = principal->GetPrivateBrowsingId(&privateBrowsingId);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsString documentURI;
+  if (nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc()) {
+    rv = doc->GetDocumentURI(documentURI);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
   RefPtr<LSObject> object = new LSObject(aWindow, principal);
   object->mPrincipalInfo = std::move(principalInfo);
+  object->mPrivateBrowsingId = privateBrowsingId;
+  object->mOrigin = origin;
+  object->mDocumentURI = documentURI;
 
   object.forget(aStorage);
   return NS_OK;
 }
 
 // static
 already_AddRefed<nsIEventTarget>
 LSObject::GetSyncLoopEventTarget()
@@ -234,18 +268,23 @@ LSObject::Type() const
 
   return eLocalStorage;
 }
 
 bool
 LSObject::IsForkOf(const Storage* aStorage) const
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(aStorage);
 
-  return false;
+  if (aStorage->Type() != eLocalStorage) {
+    return false;
+  }
+
+  return static_cast<const LSObject*>(aStorage)->mOrigin == mOrigin;
 }
 
 int64_t
 LSObject::GetOriginQuotaUsage() const
 {
   AssertIsOnOwningThread();
 
   return 0;
@@ -346,60 +385,77 @@ LSObject::SetItem(const nsAString& aKey,
   AssertIsOnOwningThread();
 
   nsresult rv = EnsureDatabase();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aError.Throw(rv);
     return;
   }
 
-  rv = mDatabase->SetItem(aKey, aValue);
+  bool changed;
+  nsString oldValue;
+  rv = mDatabase->SetItem(aKey, aValue, &changed, oldValue);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aError.Throw(rv);
     return;
   }
+
+  if (changed) {
+    OnChange(aKey, oldValue, aValue);
+  }
 }
 
 void
 LSObject::RemoveItem(const nsAString& aKey,
                      nsIPrincipal& aSubjectPrincipal,
                      ErrorResult& aError)
 {
   AssertIsOnOwningThread();
 
   nsresult rv = EnsureDatabase();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aError.Throw(rv);
     return;
   }
 
-  rv = mDatabase->RemoveItem(aKey);
+  bool changed;
+  nsString oldValue;
+  rv = mDatabase->RemoveItem(aKey, &changed, oldValue);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aError.Throw(rv);
     return;
   }
+
+  if (changed) {
+    OnChange(aKey, oldValue, VoidString());
+  }
 }
 
 void
 LSObject::Clear(nsIPrincipal& aSubjectPrincipal,
                 ErrorResult& aError)
 {
   AssertIsOnOwningThread();
 
   nsresult rv = EnsureDatabase();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aError.Throw(rv);
     return;
   }
 
-  rv = mDatabase->Clear();
+  bool changed;
+  rv = mDatabase->Clear(&changed);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aError.Throw(rv);
     return;
   }
+
+  if (changed) {
+    OnChange(VoidString(), VoidString(), VoidString());
+  }
 }
 
 NS_IMPL_ADDREF_INHERITED(LSObject, Storage)
 NS_IMPL_RELEASE_INHERITED(LSObject, Storage)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LSObject)
 NS_INTERFACE_MAP_END_INHERITING(Storage)
 
@@ -410,74 +466,115 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(LSObject, Storage)
   tmp->AssertIsOnOwningThread();
   tmp->DropDatabase();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 nsresult
+LSObject::DoRequestSynchronously(const LSRequestParams& aParams,
+                                 LSRequestResponse& aResponse)
+{
+  // We don't need this yet, but once the request successfully finishes, it's
+  // too late to initialize PBackground child on the owning thread, because
+  // it can fail and parent would keep an extra strong ref to the datastore or
+  // observer.
+  PBackgroundChild* backgroundActor =
+    BackgroundChild::GetOrCreateForCurrentThread();
+  if (NS_WARN_IF(!backgroundActor)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  RefPtr<RequestHelper> helper = new RequestHelper(this, aParams);
+
+  // This will start and finish the request on the DOM File thread.
+  // The owning thread is synchronously blocked while the request is
+  // asynchronously processed on the DOM File thread.
+  nsresult rv = helper->StartAndReturnResponse(aResponse);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (aResponse.type() == LSRequestResponse::Tnsresult) {
+    return aResponse.get_nsresult();
+  }
+
+  return NS_OK;
+}
+
+nsresult
 LSObject::EnsureDatabase()
 {
   AssertIsOnOwningThread();
 
   if (mDatabase && !mDatabase->IsAllowedToClose()) {
     return NS_OK;
   }
 
   mDatabase = nullptr;
 
-  // We don't need this yet, but once the request successfully finishes, it's
-  // too late to initialize PBackground child on the owning thread, because
-  // it can fail and parent would keep an extra strong ref to the datastore.
-  PBackgroundChild* backgroundActor =
-    BackgroundChild::GetOrCreateForCurrentThread();
-  if (NS_WARN_IF(!backgroundActor)) {
+  if (mActorFailed) {
     return NS_ERROR_FAILURE;
   }
 
+  if (!mActor) {
+    PBackgroundChild* backgroundActor =
+      BackgroundChild::GetOrCreateForCurrentThread();
+    if (NS_WARN_IF(!backgroundActor)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    LSObjectChild* actor = new LSObjectChild(this);
+
+    mActor =
+      static_cast<LSObjectChild*>(
+        backgroundActor->SendPBackgroundLSObjectConstructor(
+                                                           actor,
+                                                           *mPrincipalInfo,
+                                                           mDocumentURI,
+                                                           mPrivateBrowsingId));
+
+    if (NS_WARN_IF(!mActor)) {
+      mActorFailed = true;
+      return NS_ERROR_FAILURE;
+    }
+  }
+
   LSRequestPrepareDatastoreParams params;
   params.principalInfo() = *mPrincipalInfo;
 
-  RefPtr<RequestHelper> helper = new RequestHelper(this, params);
-
   LSRequestResponse response;
 
-  // This will start and finish the request on the DOM File thread.
-  // The owning thread is synchronously blocked while the request is
-  // asynchronously processed on the DOM File thread.
-  nsresult rv = helper->StartAndReturnResponse(response);
+  nsresult rv = DoRequestSynchronously(params, response);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  if (response.type() == LSRequestResponse::Tnsresult) {
-    return response.get_nsresult();
-  }
-
   MOZ_ASSERT(response.type() ==
                LSRequestResponse::TLSRequestPrepareDatastoreResponse);
 
   const LSRequestPrepareDatastoreResponse& prepareDatastoreResponse =
     response.get_LSRequestPrepareDatastoreResponse();
 
   uint64_t datastoreId = prepareDatastoreResponse.datastoreId();
 
   // The datastore is now ready on the parent side (prepared by the asynchronous
   // request on the DOM File thread).
   // Let's create a direct connection to the datastore (through a database
   // actor) from the owning thread.
   // Note that we now can't error out, otherwise parent will keep an extra
   // strong reference to the datastore.
+
   RefPtr<LSDatabase> database = new LSDatabase();
 
   LSDatabaseChild* actor = new LSDatabaseChild(database);
 
   MOZ_ALWAYS_TRUE(
-    backgroundActor->SendPBackgroundLSDatabaseConstructor(actor, datastoreId));
+    mActor->SendPBackgroundLSDatabaseConstructor(actor, datastoreId));
 
   database->SetActor(actor);
 
   mDatabase = std::move(database);
 
   return NS_OK;
 }
 
@@ -489,16 +586,101 @@ LSObject::DropDatabase()
   if (mDatabase) {
     if (!mDatabase->IsAllowedToClose()) {
       mDatabase->AllowToClose();
     }
     mDatabase = nullptr;
   }
 }
 
+nsresult
+LSObject::EnsureObserver()
+{
+  AssertIsOnOwningThread();
+
+  if (mObserver) {
+    return NS_OK;
+  }
+
+  mObserver = LSObserver::Get(mOrigin);
+
+  if (mObserver) {
+    return NS_OK;
+  }
+
+  LSRequestPrepareObserverParams params;
+  params.principalInfo() = *mPrincipalInfo;
+
+  LSRequestResponse response;
+
+  nsresult rv = DoRequestSynchronously(params, response);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  MOZ_ASSERT(response.type() ==
+               LSRequestResponse::TLSRequestPrepareObserverResponse);
+
+  const LSRequestPrepareObserverResponse& prepareObserverResponse =
+    response.get_LSRequestPrepareObserverResponse();
+
+  uint64_t observerId = prepareObserverResponse.observerId();
+
+  // The obsserver is now ready on the parent side (prepared by the asynchronous
+  // request on the DOM File thread).
+  // Let's create a direct connection to the observer (through an observer
+  // actor) from the owning thread.
+  // Note that we now can't error out, otherwise parent will keep an extra
+  // strong reference to the observer.
+
+  PBackgroundChild* backgroundActor = BackgroundChild::GetForCurrentThread();
+  MOZ_ASSERT(backgroundActor);
+
+  RefPtr<LSObserver> observer = new LSObserver(mOrigin);
+
+  LSObserverChild* actor = new LSObserverChild(observer);
+
+  MOZ_ALWAYS_TRUE(
+    backgroundActor->SendPBackgroundLSObserverConstructor(actor, observerId));
+
+  observer->SetActor(actor);
+
+  mObserver = std::move(observer);
+
+  return NS_OK;
+}
+
+void
+LSObject::DropObserver()
+{
+  AssertIsOnOwningThread();
+
+  if (mObserver) {
+    mObserver = nullptr;
+  }
+}
+
+void
+LSObject::OnChange(const nsAString& aKey,
+                   const nsAString& aOldValue,
+                   const nsAString& aNewValue)
+{
+  AssertIsOnOwningThread();
+
+  NotifyChange(/* aStorage */ this,
+               Principal(),
+               aKey,
+               aOldValue,
+               aNewValue,
+               /* aStorageType */ kLocalStorageType,
+               mDocumentURI,
+               /* aIsPrivate */ !!mPrivateBrowsingId,
+               /* aImmediateDispatch */ false);
+}
+
 void
 LSObject::LastRelease()
 {
   AssertIsOnOwningThread();
 
   DropDatabase();
 }
 
--- a/dom/localstorage/LSObject.h
+++ b/dom/localstorage/LSObject.h
@@ -4,44 +4,58 @@
  * 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_localstorage_LSObject_h
 #define mozilla_dom_localstorage_LSObject_h
 
 #include "mozilla/dom/Storage.h"
 
+class nsGlobalWindowInner;
 class nsIPrincipal;
 class nsPIDOMWindowInner;
 
 namespace mozilla {
 
 class ErrorResult;
 
 namespace ipc {
 
 class PrincipalInfo;
 
 } // namespace ipc
 
 namespace dom {
 
 class LSDatabase;
+class LSObjectChild;
+class LSObserver;
 class LSRequestChild;
 class LSRequestChildCallback;
 class LSRequestParams;
+class LSRequestResponse;
 
 class LSObject final
   : public Storage
 {
   typedef mozilla::ipc::PrincipalInfo PrincipalInfo;
 
+  friend nsGlobalWindowInner;
+
   nsAutoPtr<PrincipalInfo> mPrincipalInfo;
 
   RefPtr<LSDatabase> mDatabase;
+  RefPtr<LSObserver> mObserver;
+
+  LSObjectChild* mActor;
+
+  uint32_t mPrivateBrowsingId;
+  nsCString mOrigin;
+  nsString mDocumentURI;
+  bool mActorFailed;
 
 public:
   static nsresult
   Create(nsPIDOMWindowInner* aWindow,
          Storage** aStorage);
 
   /**
    * Used for requests from the parent process to the parent process; in that
@@ -55,16 +69,24 @@ public:
   CancelSyncLoop();
 
   void
   AssertIsOnOwningThread() const
   {
     NS_ASSERT_OWNINGTHREAD(LSObject);
   }
 
+  void
+  ClearActor()
+  {
+    AssertIsOnOwningThread();
+
+    mActor = nullptr;
+  }
+
   LSRequestChild*
   StartRequest(nsIEventTarget* aMainEventTarget,
                const LSRequestParams& aParams,
                LSRequestChildCallback* aCallback);
 
   // Storage overrides.
   StorageType
   Type() const override;
@@ -114,21 +136,36 @@ public:
 
 private:
   LSObject(nsPIDOMWindowInner* aWindow,
            nsIPrincipal* aPrincipal);
 
   ~LSObject();
 
   nsresult
+  DoRequestSynchronously(const LSRequestParams& aParams,
+                         LSRequestResponse& aResponse);
+
+  nsresult
   EnsureDatabase();
 
   void
   DropDatabase();
 
+  nsresult
+  EnsureObserver();
+
+  void
+  DropObserver();
+
+  void
+  OnChange(const nsAString& aKey,
+           const nsAString& aOldValue,
+           const nsAString& aNewValue);
+
   // Storage overrides.
   void
   LastRelease() override;
 };
 
 } // namespace dom
 } // namespace mozilla
 
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/LSObserver.cpp
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "LSObserver.h"
+
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "nsContentUtils.h"
+#include "nsIScriptObjectPrincipal.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+typedef nsDataHashtable<nsCStringHashKey, LSObserver*> LSObserverHashtable;
+
+StaticAutoPtr<LSObserverHashtable> gLSObservers;
+
+} // namespace
+
+LSObserver::LSObserver(const nsACString& aOrigin)
+  : mActor(nullptr)
+  , mOrigin(aOrigin)
+{
+  AssertIsOnOwningThread();
+
+  if (!gLSObservers) {
+    gLSObservers = new LSObserverHashtable();
+  }
+
+  MOZ_ASSERT(!gLSObservers->Get(mOrigin));
+  gLSObservers->Put(mOrigin, this);
+}
+
+LSObserver::~LSObserver()
+{
+  AssertIsOnOwningThread();
+
+  if (mActor) {
+    mActor->SendDeleteMeInternal();
+    MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
+  }
+
+  MOZ_ASSERT(gLSObservers);
+  MOZ_ASSERT(gLSObservers->Get(mOrigin));
+  gLSObservers->Remove(mOrigin);
+
+  if (!gLSObservers->Count()) {
+    gLSObservers = nullptr;
+  }
+}
+
+// static
+LSObserver*
+LSObserver::Get(const nsACString& aOrigin)
+{
+  return gLSObservers ? gLSObservers->Get(aOrigin) : nullptr;
+}
+
+void
+LSObserver::SetActor(LSObserverChild* aActor)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aActor);
+  MOZ_ASSERT(!mActor);
+
+  mActor = aActor;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/LSObserver.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_localstorage_LSObserver_h
+#define mozilla_dom_localstorage_LSObserver_h
+
+namespace mozilla {
+namespace dom {
+
+class LSObserverChild;
+
+class LSObserver final
+{
+  friend class LSObject;
+
+  LSObserverChild* mActor;
+
+  const nsCString mOrigin;
+
+public:
+  explicit LSObserver(const nsACString& aOrigin);
+
+  static LSObserver*
+  Get(const nsACString& aOrigin);
+
+  NS_INLINE_DECL_REFCOUNTING(LSObserver)
+
+  void
+  AssertIsOnOwningThread() const
+  {
+    NS_ASSERT_OWNINGTHREAD(LSDatabase);
+  }
+
+  void
+  SetActor(LSObserverChild* aActor);
+
+  void
+  ClearActor()
+  {
+    AssertIsOnOwningThread();
+    MOZ_ASSERT(mActor);
+
+    mActor = nullptr;
+  }
+
+private:
+  ~LSObserver();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_localstorage_LSObserver_h
--- a/dom/localstorage/LocalStorageCommon.cpp
+++ b/dom/localstorage/LocalStorageCommon.cpp
@@ -10,16 +10,18 @@ namespace mozilla {
 namespace dom {
 
 namespace {
 
 Atomic<int32_t> gNextGenLocalStorageEnabled(-1);
 
 } // namespace
 
+const char16_t* kLocalStorageType = u"localStorage";
+
 bool
 NextGenLocalStorageEnabled()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (gNextGenLocalStorageEnabled == -1) {
     bool enabled = Preferences::GetBool("dom.storage.next_gen", false);
     gNextGenLocalStorageEnabled = enabled ? 1 : 0;
--- a/dom/localstorage/LocalStorageCommon.h
+++ b/dom/localstorage/LocalStorageCommon.h
@@ -179,16 +179,18 @@
  * available only in the chrome code or tests. The manager is represented by
  * the "LocalStorageManager2" class that implements the "nsIDOMStorageManager"
  * interface.
  */
 
 namespace mozilla {
 namespace dom {
 
+extern const char16_t* kLocalStorageType;
+
 bool
 NextGenLocalStorageEnabled();
 
 bool
 CachedNextGenLocalStorageEnabled();
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/localstorage/PBackgroundLSDatabase.ipdl
+++ b/dom/localstorage/PBackgroundLSDatabase.ipdl
@@ -1,20 +1,20 @@
 /* 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 PBackground;
+include protocol PBackgroundLSObject;
 
 namespace mozilla {
 namespace dom {
 
 sync protocol PBackgroundLSDatabase
 {
-  manager PBackground;
+  manager PBackgroundLSObject;
 
 parent:
   // The DeleteMe message is used to avoid a race condition between the parent
   // actor and the child actor. The PBackgroundLSDatabase protocol could be
   // simply destroyed by sending the __delete__ message from the child side.
   // However, that would destroy the child actor immediatelly and the parent
   // could be sending a message to the child at the same time resulting in a
   // routing error since the child actor wouldn't exist anymore. A routing
@@ -32,21 +32,24 @@ parent:
     returns (nsString key);
 
   sync GetItem(nsString key)
     returns (nsString value);
 
   sync GetKeys()
     returns (nsString[] keys);
 
-  sync SetItem(nsString key, nsString value);
+  sync SetItem(nsString key, nsString value)
+    returns (bool changed, nsString oldValue);
 
-  sync RemoveItem(nsString key);
+  sync RemoveItem(nsString key)
+    returns (bool changed, nsString oldValue);
 
-  sync Clear();
+  sync Clear()
+    returns (bool changed);
 
 child:
   async __delete__();
 
   async RequestAllowToClose();
 };
 
 } // namespace dom
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/PBackgroundLSObject.ipdl
@@ -0,0 +1,27 @@
+/* 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 PBackground;
+include protocol PBackgroundLSDatabase;
+
+namespace mozilla {
+namespace dom {
+
+sync protocol PBackgroundLSObject
+{
+  manager PBackground;
+
+  manages PBackgroundLSDatabase;
+
+parent:
+  async DeleteMe();
+
+  async PBackgroundLSDatabase(uint64_t datastoreId);
+
+child:
+  async __delete__();
+};
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/PBackgroundLSObserver.ipdl
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PBackground;
+
+include PBackgroundSharedTypes;
+
+namespace mozilla {
+namespace dom {
+
+async protocol PBackgroundLSObserver
+{
+  manager PBackground;
+
+parent:
+  async DeleteMe();
+
+child:
+  async __delete__();
+
+  async Observe(PrincipalInfo principalInfo,
+                uint32_t privateBrowsingId,
+                nsString documentURI,
+                nsString key,
+                nsString oldValue,
+                nsString newValue);
+};
+
+} // namespace dom
+} // namespace mozilla
--- a/dom/localstorage/PBackgroundLSRequest.ipdl
+++ b/dom/localstorage/PBackgroundLSRequest.ipdl
@@ -7,20 +7,26 @@ include protocol PBackground;
 namespace mozilla {
 namespace dom {
 
 struct LSRequestPrepareDatastoreResponse
 {
   uint64_t datastoreId;
 };
 
+struct LSRequestPrepareObserverResponse
+{
+  uint64_t observerId;
+};
+
 union LSRequestResponse
 {
   nsresult;
   LSRequestPrepareDatastoreResponse;
+  LSRequestPrepareObserverResponse;
 };
 
 protocol PBackgroundLSRequest
 {
   manager PBackground;
 
 parent:
   // The Cancel message is used to avoid a possible dead lock caused by a CPOW
--- a/dom/localstorage/PBackgroundLSSharedTypes.ipdlh
+++ b/dom/localstorage/PBackgroundLSSharedTypes.ipdlh
@@ -7,15 +7,21 @@ include PBackgroundSharedTypes;
 namespace mozilla {
 namespace dom {
 
 struct LSRequestPrepareDatastoreParams
 {
   PrincipalInfo principalInfo;
 };
 
+struct LSRequestPrepareObserverParams
+{
+  PrincipalInfo principalInfo;
+};
+
 union LSRequestParams
 {
   LSRequestPrepareDatastoreParams;
+  LSRequestPrepareObserverParams;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/localstorage/moz.build
+++ b/dom/localstorage/moz.build
@@ -7,30 +7,34 @@
 EXPORTS.mozilla.dom.localstorage += [
     'ActorsParent.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'LocalStorageCommon.h',
     'LocalStorageManager2.h',
     'LSObject.h',
+    'LSObserver.h',
 ]
 
 UNIFIED_SOURCES += [
     'ActorsChild.cpp',
     'ActorsParent.cpp',
     'LocalStorageCommon.cpp',
     'LocalStorageManager2.cpp',
     'LSDatabase.cpp',
     'LSObject.cpp',
+    'LSObserver.cpp',
     'ReportInternalError.cpp',
 ]
 
 IPDL_SOURCES += [
     'PBackgroundLSDatabase.ipdl',
+    'PBackgroundLSObject.ipdl',
+    'PBackgroundLSObserver.ipdl',
     'PBackgroundLSRequest.ipdl',
     'PBackgroundLSSharedTypes.ipdlh',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
--- a/dom/tests/mochitest/localstorage/mochitest.ini
+++ b/dom/tests/mochitest/localstorage/mochitest.ini
@@ -22,18 +22,18 @@ support-files =
 #[test_bug600307-DBOps.html]
 [test_bug746272-1.html]
 [test_bug746272-2.html]
 skip-if = os == "android" || verify # bug 962029
 [test_cookieBlock.html]
 #[test_cookieSession.html]
 [test_embededNulls.html]
 [test_keySync.html]
-#[test_localStorageBase.html]
-#skip-if = e10s
+[test_localStorageBase.html]
+skip-if = e10s
 #[test_localStorageBaseSessionOnly.html]
 [test_localStorageCookieSettings.html]
 #[test_localStorageEnablePref.html]
 [test_localStorageKeyOrder.html]
 [test_localStorageOriginsDiff.html]
 [test_localStorageOriginsDomainDiffs.html]
 [test_localStorageOriginsEquals.html]
 skip-if = toolkit == 'android'
--- a/dom/tests/mochitest/storageevent/mochitest.ini
+++ b/dom/tests/mochitest/storageevent/mochitest.ini
@@ -6,12 +6,12 @@ support-files =
   frameSessionStorageMasterEqual.html
   frameSessionStorageMasterNotEqual.html
   frameSessionStorageSlaveEqual.html
   frameSessionStorageSlaveNotEqual.html
   interOriginFrame.js
   interOriginTest2.js
 
 [test_storageLocalStorageEventCheckNoPropagation.html]
-#[test_storageLocalStorageEventCheckPropagation.html]
-#[test_storageNotifications.html]
+[test_storageLocalStorageEventCheckPropagation.html]
+[test_storageNotifications.html]
 [test_storageSessionStorageEventCheckNoPropagation.html]
 [test_storageSessionStorageEventCheckPropagation.html]
--- a/editor/libeditor/tests/mochitest.ini
+++ b/editor/libeditor/tests/mochitest.ini
@@ -136,19 +136,19 @@ skip-if = toolkit == 'android' #bug 9577
 skip-if = os == 'android'
 [test_bug636465.html]
 skip-if = os == 'android'
 [test_bug638596.html]
 [test_bug641466.html]
 [test_bug645914.html]
 [test_bug646194.html]
 [test_bug668599.html]
-#[test_bug674770-1.html]
-#subsuite = clipboard
-#skip-if = toolkit == 'android' || verify
+[test_bug674770-1.html]
+subsuite = clipboard
+skip-if = toolkit == 'android' || verify
 [test_bug674770-2.html]
 subsuite = clipboard
 skip-if = toolkit == 'android'
 [test_bug674861.html]
 [test_bug676401.html]
 [test_bug677752.html]
 [test_bug681229.html]
 subsuite = clipboard
--- a/ipc/glue/BackgroundChildImpl.cpp
+++ b/ipc/glue/BackgroundChildImpl.cpp
@@ -11,17 +11,18 @@
 #include "FileDescriptorSetChild.h"
 #ifdef MOZ_WEBRTC
 #include "CamerasChild.h"
 #endif
 #include "mozilla/media/MediaChild.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/SchedulerGroup.h"
 #include "mozilla/dom/ClientManagerActors.h"
-#include "mozilla/dom/PBackgroundLSDatabaseChild.h"
+#include "mozilla/dom/PBackgroundLSObjectChild.h"
+#include "mozilla/dom/PBackgroundLSObserverChild.h"
 #include "mozilla/dom/PBackgroundLSRequestChild.h"
 #include "mozilla/dom/PBackgroundSDBConnectionChild.h"
 #include "mozilla/dom/PFileSystemRequestChild.h"
 #include "mozilla/dom/FileSystemTaskBase.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
 #include "mozilla/dom/cache/ActorUtils.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryChild.h"
 #include "mozilla/dom/indexedDB/PBackgroundIndexedDBUtilsChild.h"
@@ -238,26 +239,45 @@ BackgroundChildImpl::DeallocPBackgroundS
                                           PBackgroundSDBConnectionChild* aActor)
 {
   MOZ_ASSERT(aActor);
 
   delete aActor;
   return true;
 }
 
-BackgroundChildImpl::PBackgroundLSDatabaseChild*
-BackgroundChildImpl::AllocPBackgroundLSDatabaseChild(
-                                                    const uint64_t& aDatastoreId)
+BackgroundChildImpl::PBackgroundLSObjectChild*
+BackgroundChildImpl::AllocPBackgroundLSObjectChild(
+                                            const PrincipalInfo& aPrincipalInfo,
+                                            const nsString& aDocumentURI,
+                                            const uint32_t& aPrivateBrowsingId)
 {
-  MOZ_CRASH("PBackgroundLSDatabaseChild actor should be manually constructed!");
+  MOZ_CRASH("PBackgroundLSObjectChild actor should be manually constructed!");
 }
 
 bool
-BackgroundChildImpl::DeallocPBackgroundLSDatabaseChild(
-                                             PBackgroundLSDatabaseChild* aActor)
+BackgroundChildImpl::DeallocPBackgroundLSObjectChild(
+                                               PBackgroundLSObjectChild* aActor)
+{
+  MOZ_ASSERT(aActor);
+
+  delete aActor;
+  return true;
+}
+
+BackgroundChildImpl::PBackgroundLSObserverChild*
+BackgroundChildImpl::AllocPBackgroundLSObserverChild(
+                                                    const uint64_t& aObserverId)
+{
+  MOZ_CRASH("PBackgroundLSObserverChild actor should be manually constructed!");
+}
+
+bool
+BackgroundChildImpl::DeallocPBackgroundLSObserverChild(
+                                             PBackgroundLSObserverChild* aActor)
 {
   MOZ_ASSERT(aActor);
 
   delete aActor;
   return true;
 }
 
 BackgroundChildImpl::PBackgroundLSRequestChild*
--- a/ipc/glue/BackgroundChildImpl.h
+++ b/ipc/glue/BackgroundChildImpl.h
@@ -73,21 +73,30 @@ protected:
   virtual PBackgroundSDBConnectionChild*
   AllocPBackgroundSDBConnectionChild(const PrincipalInfo& aPrincipalInfo)
                                      override;
 
   virtual bool
   DeallocPBackgroundSDBConnectionChild(PBackgroundSDBConnectionChild* aActor)
                                        override;
 
-  virtual PBackgroundLSDatabaseChild*
-  AllocPBackgroundLSDatabaseChild(const uint64_t& aCacheId) override;
+  virtual PBackgroundLSObjectChild*
+  AllocPBackgroundLSObjectChild(const PrincipalInfo& aPrincipalInfo,
+                                const nsString& aDocumentURI,
+                                const uint32_t& aPrivateBrowsingId) override;
 
   virtual bool
-  DeallocPBackgroundLSDatabaseChild(PBackgroundLSDatabaseChild* aActor)
+  DeallocPBackgroundLSObjectChild(PBackgroundLSObjectChild* aActor)
+                                  override;
+
+  virtual PBackgroundLSObserverChild*
+  AllocPBackgroundLSObserverChild(const uint64_t& aObserverId) override;
+
+  virtual bool
+  DeallocPBackgroundLSObserverChild(PBackgroundLSObserverChild* aActor)
                                     override;
 
   virtual PBackgroundLSRequestChild*
   AllocPBackgroundLSRequestChild(const LSRequestParams& aParams) override;
 
   virtual bool
   DeallocPBackgroundLSRequestChild(PBackgroundLSRequestChild* aActor) override;
 
--- a/ipc/glue/BackgroundParentImpl.cpp
+++ b/ipc/glue/BackgroundParentImpl.cpp
@@ -289,51 +289,96 @@ BackgroundParentImpl::DeallocPBackground
 {
   AssertIsInMainProcess();
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aActor);
 
   return mozilla::dom::DeallocPBackgroundSDBConnectionParent(aActor);
 }
 
-BackgroundParentImpl::PBackgroundLSDatabaseParent*
-BackgroundParentImpl::AllocPBackgroundLSDatabaseParent(
-                                                    const uint64_t& aDatastoreId)
+BackgroundParentImpl::PBackgroundLSObjectParent*
+BackgroundParentImpl::AllocPBackgroundLSObjectParent(
+                                            const PrincipalInfo& aPrincipalInfo,
+                                            const nsString& aDocumentURI,
+                                            const uint32_t& aPrivateBrowsingId)
 {
   AssertIsInMainProcess();
   AssertIsOnBackgroundThread();
 
-  return mozilla::dom::AllocPBackgroundLSDatabaseParent(aDatastoreId);
+  return mozilla::dom::AllocPBackgroundLSObjectParent(aPrincipalInfo,
+                                                      aDocumentURI,
+                                                      aPrivateBrowsingId);
 }
 
 mozilla::ipc::IPCResult
-BackgroundParentImpl::RecvPBackgroundLSDatabaseConstructor(
-                                            PBackgroundLSDatabaseParent* aActor,
-                                            const uint64_t& aDatastoreId)
+BackgroundParentImpl::RecvPBackgroundLSObjectConstructor(
+                                            PBackgroundLSObjectParent* aActor,
+                                            const PrincipalInfo& aPrincipalInfo,
+                                            const nsString& aDocumentURI,
+                                            const uint32_t& aPrivateBrowsingId)
 {
   AssertIsInMainProcess();
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aActor);
 
-  if (!mozilla::dom::RecvPBackgroundLSDatabaseConstructor(aActor,
-                                                          aDatastoreId)) {
+  if (!mozilla::dom::RecvPBackgroundLSObjectConstructor(aActor,
+                                                        aPrincipalInfo,
+                                                        aDocumentURI,
+                                                        aPrivateBrowsingId)) {
     return IPC_FAIL_NO_REASON(this);
   }
   return IPC_OK();
 }
 
 bool
-BackgroundParentImpl::DeallocPBackgroundLSDatabaseParent(
-                                            PBackgroundLSDatabaseParent* aActor)
+BackgroundParentImpl::DeallocPBackgroundLSObjectParent(
+                                              PBackgroundLSObjectParent* aActor)
 {
   AssertIsInMainProcess();
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aActor);
 
-  return mozilla::dom::DeallocPBackgroundLSDatabaseParent(aActor);
+  return mozilla::dom::DeallocPBackgroundLSObjectParent(aActor);
+}
+
+BackgroundParentImpl::PBackgroundLSObserverParent*
+BackgroundParentImpl::AllocPBackgroundLSObserverParent(
+                                                    const uint64_t& aObserverId)
+{
+  AssertIsInMainProcess();
+  AssertIsOnBackgroundThread();
+
+  return mozilla::dom::AllocPBackgroundLSObserverParent(aObserverId);
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPBackgroundLSObserverConstructor(
+                                            PBackgroundLSObserverParent* aActor,
+                                            const uint64_t& aObserverId)
+{
+  AssertIsInMainProcess();
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  if (!mozilla::dom::RecvPBackgroundLSObserverConstructor(aActor,
+                                                          aObserverId)) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+  return IPC_OK();
+}
+
+bool
+BackgroundParentImpl::DeallocPBackgroundLSObserverParent(
+                                            PBackgroundLSObserverParent* aActor)
+{
+  AssertIsInMainProcess();
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  return mozilla::dom::DeallocPBackgroundLSObserverParent(aActor);
 }
 
 BackgroundParentImpl::PBackgroundLSRequestParent*
 BackgroundParentImpl::AllocPBackgroundLSRequestParent(
                                                  const LSRequestParams& aParams)
 {
   AssertIsInMainProcess();
   AssertIsOnBackgroundThread();
--- a/ipc/glue/BackgroundParentImpl.h
+++ b/ipc/glue/BackgroundParentImpl.h
@@ -72,25 +72,41 @@ protected:
                                          PBackgroundSDBConnectionParent* aActor,
                                          const PrincipalInfo& aPrincipalInfo)
                                          override;
 
   virtual bool
   DeallocPBackgroundSDBConnectionParent(PBackgroundSDBConnectionParent* aActor)
                                         override;
 
-  virtual PBackgroundLSDatabaseParent*
-  AllocPBackgroundLSDatabaseParent(const uint64_t& aCacheId) override;
+  virtual PBackgroundLSObjectParent*
+  AllocPBackgroundLSObjectParent(const PrincipalInfo& aPrincipalInfo,
+                                 const nsString& aDocumentURI,
+                                 const uint32_t& aPrivateBrowsingId) override;
 
   virtual mozilla::ipc::IPCResult
-  RecvPBackgroundLSDatabaseConstructor(PBackgroundLSDatabaseParent* aActor,
-                                       const uint64_t& aDatastoreId) override;
+  RecvPBackgroundLSObjectConstructor(PBackgroundLSObjectParent* aActor,
+                                     const PrincipalInfo& aPrincipalInfo,
+                                     const nsString& aDocumentURI,
+                                     const uint32_t& aPrivateBrowsingId)
+                                     override;
 
   virtual bool
-  DeallocPBackgroundLSDatabaseParent(PBackgroundLSDatabaseParent* aActor)
+  DeallocPBackgroundLSObjectParent(PBackgroundLSObjectParent* aActor)
+                                   override;
+
+  virtual PBackgroundLSObserverParent*
+  AllocPBackgroundLSObserverParent(const uint64_t& aObserverId) override;
+
+  virtual mozilla::ipc::IPCResult
+  RecvPBackgroundLSObserverConstructor(PBackgroundLSObserverParent* aActor,
+                                       const uint64_t& aObserverId) override;
+
+  virtual bool
+  DeallocPBackgroundLSObserverParent(PBackgroundLSObserverParent* aActor)
                                      override;
 
   virtual PBackgroundLSRequestParent*
   AllocPBackgroundLSRequestParent(const LSRequestParams& aParams) override;
 
   virtual mozilla::ipc::IPCResult
   RecvPBackgroundLSRequestConstructor(PBackgroundLSRequestParent* aActor,
                                       const LSRequestParams& aParams) override;
--- a/ipc/glue/PBackground.ipdl
+++ b/ipc/glue/PBackground.ipdl
@@ -1,17 +1,18 @@
 /* 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 PAsmJSCacheEntry;
 include protocol PBackgroundIDBFactory;
 include protocol PBackgroundIndexedDBUtils;
 include protocol PBackgroundSDBConnection;
-include protocol PBackgroundLSDatabase;
+include protocol PBackgroundLSObject;
+include protocol PBackgroundLSObserver;
 include protocol PBackgroundLSRequest;
 include protocol PBackgroundLocalStorageCache;
 include protocol PBackgroundStorage;
 include protocol PBackgroundTest;
 include protocol PBroadcastChannel;
 include protocol PCache;
 include protocol PCacheStorage;
 include protocol PCacheStreamControl;
@@ -70,17 +71,18 @@ namespace mozilla {
 namespace ipc {
 
 sync protocol PBackground
 {
   manages PAsmJSCacheEntry;
   manages PBackgroundIDBFactory;
   manages PBackgroundIndexedDBUtils;
   manages PBackgroundSDBConnection;
-  manages PBackgroundLSDatabase;
+  manages PBackgroundLSObject;
+  manages PBackgroundLSObserver;
   manages PBackgroundLSRequest;
   manages PBackgroundLocalStorageCache;
   manages PBackgroundStorage;
   manages PBackgroundTest;
   manages PBroadcastChannel;
   manages PCache;
   manages PCacheStorage;
   manages PCacheStreamControl;
@@ -119,17 +121,21 @@ parent:
 
   async PBackgroundIndexedDBUtils();
 
   // Use only for testing!
   async FlushPendingFileDeletions();
 
   async PBackgroundSDBConnection(PrincipalInfo principalInfo);
 
-  async PBackgroundLSDatabase(uint64_t datastoreId);
+  async PBackgroundLSObject(PrincipalInfo principalInfo,
+                            nsString documentURI,
+                            uint32_t privateBrowsingId);
+
+  async PBackgroundLSObserver(uint64_t observerId);
 
   async PBackgroundLSRequest(LSRequestParams params);
 
   async PBackgroundLocalStorageCache(PrincipalInfo principalInfo,
                                      nsCString originKey,
                                      uint32_t privateBrowsingId);
 
   async PBackgroundStorage(nsString profilePath);
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/windows/auxiliary-browsing-contexts/opener-closed.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[opener-closed.html]
-  disabled: temporary
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/windows/auxiliary-browsing-contexts/opener-noopener.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[opener-noopener.html]
-  disabled: temporary
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/windows/auxiliary-browsing-contexts/opener-noreferrer.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[opener-noreferrer.html]
-  disabled: temporary
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/windows/browsing-context-names/choose-_blank-002.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[choose-_blank-002.html]
-  disabled: temporary
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/windows/browsing-context-names/choose-_parent-004.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[choose-_parent-004.html]
-  disabled: temporary
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/windows/browsing-context-names/choose-_self-002.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[choose-_self-002.html]
-  disabled: temporary
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/windows/browsing-context-names/choose-_top-001.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[choose-_top-001.html]
-  disabled: temporary
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/windows/browsing-context-names/choose-_top-002.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[choose-_top-002.html]
-  disabled: temporary
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/windows/browsing-context-names/choose-_top-003.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[choose-_top-003.html]
-  disabled: temporary
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/windows/noreferrer-null-opener.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[noreferrer-null-opener.html]
-  disabled: temporary
--- a/testing/web-platform/meta/html/browsers/windows/noreferrer-window-name.html.ini
+++ b/testing/web-platform/meta/html/browsers/windows/noreferrer-window-name.html.ini
@@ -1,2 +1,3 @@
 [noreferrer-window-name.html]
-  disabled: temporary
+  disabled:
+    if verify: fails in verify mode
--- a/testing/web-platform/meta/webstorage/document-domain.html.ini
+++ b/testing/web-platform/meta/webstorage/document-domain.html.ini
@@ -1,2 +1,2 @@
 [document-domain.html]
-  disabled: temporary
+  expected: TIMEOUT
deleted file mode 100644
--- a/testing/web-platform/meta/webstorage/event_basic.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[event_basic.html]
-  disabled: temporary
deleted file mode 100644
--- a/testing/web-platform/meta/webstorage/event_body_attribute.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[event_body_attribute.html]
-  disabled: temporary
deleted file mode 100644
--- a/testing/web-platform/meta/webstorage/event_case_sensitive.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[event_case_sensitive.html]
-  disabled: temporary
deleted file mode 100644
--- a/testing/web-platform/meta/webstorage/event_local_key.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[event_local_key.html]
-  disabled: temporary
deleted file mode 100644
--- a/testing/web-platform/meta/webstorage/event_local_newvalue.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[event_local_newvalue.html]
-  disabled: temporary
deleted file mode 100644
--- a/testing/web-platform/meta/webstorage/event_local_oldvalue.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[event_local_oldvalue.html]
-  disabled: temporary
deleted file mode 100644
--- a/testing/web-platform/meta/webstorage/event_local_removeitem.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[event_local_removeitem.html]
-  disabled: temporary
deleted file mode 100644
--- a/testing/web-platform/meta/webstorage/event_local_storagearea.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[event_local_storagearea.html]
-  disabled: temporary
deleted file mode 100644
--- a/testing/web-platform/meta/webstorage/event_local_url.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[event_local_url.html]
-  disabled: temporary
deleted file mode 100644
--- a/testing/web-platform/meta/webstorage/event_no_duplicates.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[event_no_duplicates.html]
-  disabled: temporary
deleted file mode 100644
--- a/testing/web-platform/meta/webstorage/event_setattribute.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[event_setattribute.html]
-  disabled: temporary
--- a/toolkit/components/antitracking/test/browser/browser.ini
+++ b/toolkit/components/antitracking/test/browser/browser.ini
@@ -63,13 +63,13 @@ skip-if = serviceworker_e10s
 [browser_storageAccessPromiseResolveHandlerUserInteraction.js]
 [browser_storageAccessRemovalNavigateSubframe.js]
 skip-if = serviceworker_e10s
 [browser_storageAccessSandboxed.js]
 skip-if = serviceworker_e10s
 [browser_storageAccessWithHeuristics.js]
 [browser_allowPermissionForTracker.js]
 [browser_denyPermissionForTracker.js]
-#[browser_localStorageEvents.js]
-#support-files = localStorage.html
+[browser_localStorageEvents.js]
+support-files = localStorage.html
 [browser_partitionedLocalStorage.js]
-#[browser_partitionedLocalStorage_events.js]
-#support-files = localStorageEvents.html
+[browser_partitionedLocalStorage_events.js]
+support-files = localStorageEvents.html
--- a/toolkit/components/antitracking/test/browser/localStorage.html
+++ b/toolkit/components/antitracking/test/browser/localStorage.html
@@ -1,15 +1,22 @@
 <h1>Here a tracker!</h1>
 <script>
 
 if (window.opener) {
   SpecialPowers.wrap(document).userInteractionForTesting();
   localStorage.foo = "opener" + Math.random();
-  window.close();
+  // Don't call window.close immediatelly. It can happen that adding the
+  // "storage" event listener below takes more time than usual (it may need to
+  // synchronously subscribe in the parent process to receive storage
+  // notifications). Spending more time in the initial script can prevent
+  // the "load" event from being fired for the window opened by "open and test".
+  setTimeout(() => {
+    window.close();
+  }, 0);
 }
 
 if (parent) {
   window.onmessage = e => {
     if (e.data == "test") {
       let status;
       try {
         localStorage.foo = "value" + Math.random();