Bug 1285898 - [e10s-multi] Localstorage "storage" event is not fired with multiple content processes. r=asuth
authorAndrea Marchesini <amarchesini@mozilla.com>
Sun, 19 Feb 2017 22:16:48 -0500
changeset 372851 6910cab30bdd58270b39e7cf041c022c2aba6a2c
parent 372850 03531a4a9327fe350cc4a27c0c7799c106b8bfcc
child 372852 044c1bc329e901baa93cab07f400fc2cdd9f7f95
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth
bugs1285898
milestone54.0a1
Bug 1285898 - [e10s-multi] Localstorage "storage" event is not fired with multiple content processes. r=asuth
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/events/StorageEvent.h
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
dom/storage/Storage.cpp
dom/storage/Storage.h
dom/storage/StorageCache.cpp
dom/storage/StorageCache.h
editor/libeditor/tests/test_bug674770-1.html
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -2174,17 +2174,16 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessionStorage)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplicationCache)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuspendedDoc)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIndexedDB)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPrincipal)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleService)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWakeLock)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingStorageEvents)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleRequestExecutor)
   for (IdleRequest* request : tmp->mIdleRequestCallbacks) {
     cb.NoteNativeChild(request, NS_CYCLE_COLLECTION_PARTICIPANT(IdleRequest));
   }
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleObservers)
 
@@ -2255,17 +2254,16 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
     NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplicationCache)
   }
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuspendedDoc)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mIndexedDB)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPrincipal)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDoc)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleService)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mWakeLock)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingStorageEvents)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleObservers)
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mGamepads)
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mCacheStorage)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mVRDisplays)
 
   // Unlink stuff from nsPIDOMWindow
@@ -11899,59 +11897,76 @@ nsGlobalWindow::Observe(nsISupports* aSu
       mNotifyIdleObserversIdleOnThaw = false;
     } else if (AsInner()->IsCurrentInnerWindow()) {
       MOZ_ASSERT(IsInnerWindow());
       ScheduleActiveTimerCallback();
     }
     return NS_OK;
   }
 
-  bool isPrivateBrowsing = IsPrivateBrowsing();
-  if ((!nsCRT::strcmp(aTopic, "dom-storage2-changed") && !isPrivateBrowsing) ||
-      (!nsCRT::strcmp(aTopic, "dom-private-storage2-changed") && isPrivateBrowsing)) {
-    if (!IsInnerWindow() || !AsInner()->IsCurrentInnerWindow()) {
+  // We need these for our private-browsing check below; so save them off.
+  // (And we can't do the private-browsing enforcement check as part of our
+  // outer conditional because otherwise we'll trigger an NS_WARNING if control
+  // flow reaches the bottom of this method.)
+  bool isNonPrivateLocalStorageChange =
+    !nsCRT::strcmp(aTopic, "dom-storage2-changed");
+  bool isPrivateLocalStorageChange =
+    !nsCRT::strcmp(aTopic, "dom-private-storage2-changed");
+  if (isNonPrivateLocalStorageChange || isPrivateLocalStorageChange) {
+    // Enforce that the source storage area's private browsing state matches
+    // this window's state.  These flag checks and their maintenance independent
+    // from the principal's OriginAttributes matter because chrome docshells
+    // that are part of private browsing windows can be private browsing without
+    // having their OriginAttributes set (because they have the system
+    // principal).
+    bool isPrivateBrowsing = IsPrivateBrowsing();
+    if ((isNonPrivateLocalStorageChange && isPrivateBrowsing) ||
+        (isPrivateLocalStorageChange && !isPrivateBrowsing)) {
       return NS_OK;
     }
 
-    nsIPrincipal *principal;
-    nsresult rv;
+    // We require that aData be either u"SessionStorage" or u"localStorage".
+    // Assert under debug, but ignore the bogus event under non-debug.
+    MOZ_ASSERT(aData);
+    if (!aData) {
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    // LocalStorage can only exist on an inner window, and we don't want to
+    // generate events on frozen or otherwise-navigated-away from windows.
+    // (Actually, this code used to try and buffer events for frozen windows,
+    // but it never worked, so we've removed it.  See bug 1285898.)
+    if (!IsInnerWindow() || !AsInner()->IsCurrentInnerWindow() || IsFrozen()) {
+      return NS_OK;
+    }
+
+    nsIPrincipal *principal = GetPrincipal();
+    if (!principal) {
+      return NS_OK;
+    }
 
     RefPtr<StorageEvent> event = static_cast<StorageEvent*>(aSubject);
     if (!event) {
       return NS_ERROR_FAILURE;
     }
 
-    RefPtr<Storage> changingStorage = event->GetStorageArea();
-    if (!changingStorage) {
-      return NS_ERROR_FAILURE;
-    }
-
-    nsCOMPtr<nsIDOMStorage> istorage = changingStorage.get();
-
     bool fireMozStorageChanged = false;
     nsAutoString eventType;
     eventType.AssignLiteral("storage");
-    principal = GetPrincipal();
-    if (!principal) {
-      return NS_OK;
-    }
-
-    if (changingStorage->IsPrivate() != IsPrivateBrowsing()) {
-      return NS_OK;
-    }
-
-    switch (changingStorage->GetType())
-    {
-    case Storage::SessionStorage:
-    {
+
+    if (!NS_strcmp(aData, u"sessionStorage")) {
+      nsCOMPtr<nsIDOMStorage> changingStorage = event->GetStorageArea();
+      MOZ_ASSERT(changingStorage);
+
       bool check = false;
 
       nsCOMPtr<nsIDOMStorageManager> storageManager = do_QueryInterface(GetDocShell());
       if (storageManager) {
-        rv = storageManager->CheckStorage(principal, istorage, &check);
+        nsresult rv = storageManager->CheckStorage(principal, changingStorage,
+                                                   &check);
         if (NS_FAILED(rv)) {
           return rv;
         }
       }
 
       if (!check) {
         // This storage event is not coming from our storage or is coming
         // from a different docshell, i.e. it is a clone, ignore this event.
@@ -11962,68 +11977,58 @@ nsGlobalWindow::Observe(nsISupports* aSu
         PR_LogPrint("nsGlobalWindow %p with sessionStorage %p passing event from %p",
                     this, mSessionStorage.get(), changingStorage.get());
       }
 
       fireMozStorageChanged = mSessionStorage == changingStorage;
       if (fireMozStorageChanged) {
         eventType.AssignLiteral("MozSessionStorageChanged");
       }
-      break;
-    }
-
-    case Storage::LocalStorage:
-    {
-      // Allow event fire only for the same principal storages
-      // XXX We have to use EqualsIgnoreDomain after bug 495337 lands
-      nsIPrincipal* storagePrincipal = changingStorage->GetPrincipal();
+    }
+
+    else {
+      MOZ_ASSERT(!NS_strcmp(aData, u"localStorage"));
+      nsIPrincipal* storagePrincipal = event->GetPrincipal();
+      if (!storagePrincipal) {
+        return NS_OK;
+      }
 
       bool equals = false;
-      rv = storagePrincipal->Equals(principal, &equals);
+      nsresult rv = storagePrincipal->Equals(principal, &equals);
       NS_ENSURE_SUCCESS(rv, rv);
 
-      if (!equals)
+      if (!equals) {
         return NS_OK;
-
-      fireMozStorageChanged = mLocalStorage == changingStorage;
+      }
+
+      fireMozStorageChanged = mLocalStorage == event->GetStorageArea();
+
       if (fireMozStorageChanged) {
         eventType.AssignLiteral("MozLocalStorageChanged");
       }
-      break;
-    }
-    default:
-      return NS_OK;
     }
 
     // Clone the storage event included in the observer notification. We want
     // to dispatch clones rather than the original event.
     ErrorResult error;
-    RefPtr<StorageEvent> newEvent = CloneStorageEvent(eventType, event, error);
+    RefPtr<StorageEvent> clonedEvent =
+      CloneStorageEvent(eventType, event, error);
     if (error.Failed()) {
       return error.StealNSResult();
     }
 
-    newEvent->SetTrusted(true);
+    clonedEvent->SetTrusted(true);
 
     if (fireMozStorageChanged) {
-      WidgetEvent* internalEvent = newEvent->WidgetEventPtr();
+      WidgetEvent* internalEvent = clonedEvent->WidgetEventPtr();
       internalEvent->mFlags.mOnlyChromeDispatch = true;
     }
 
-    if (IsFrozen()) {
-      // This window is frozen, rather than firing the events here,
-      // store the domain in which the change happened and fire the
-      // events if we're ever thawed.
-
-      mPendingStorageEvents.AppendElement(newEvent);
-      return NS_OK;
-    }
-
     bool defaultActionEnabled;
-    DispatchEvent(newEvent, &defaultActionEnabled);
+    DispatchEvent(clonedEvent, &defaultActionEnabled);
 
     return NS_OK;
   }
 
   if (!nsCRT::strcmp(aTopic, "offline-cache-update-added")) {
     if (mApplicationCache)
       return NS_OK;
 
@@ -12104,32 +12109,41 @@ nsGlobalWindow::CloneStorageEvent(const 
   dict.mBubbles = aEvent->Bubbles();
   dict.mCancelable = aEvent->Cancelable();
   aEvent->GetKey(dict.mKey);
   aEvent->GetOldValue(dict.mOldValue);
   aEvent->GetNewValue(dict.mNewValue);
   aEvent->GetUrl(dict.mUrl);
 
   RefPtr<Storage> storageArea = aEvent->GetStorageArea();
-  MOZ_ASSERT(storageArea);
 
   RefPtr<Storage> storage;
-  if (storageArea->GetType() == Storage::LocalStorage) {
+
+  // If null, this is a localStorage event received by IPC.
+  if (!storageArea) {
+    storage = GetLocalStorage(aRv);
+    if (aRv.Failed() || !storage) {
+      return nullptr;
+    }
+
+    // We must apply the current change to the 'local' localStorage.
+    storage->ApplyEvent(aEvent);
+  } else if (storageArea->GetType() == Storage::LocalStorage) {
     storage = GetLocalStorage(aRv);
   } else {
     MOZ_ASSERT(storageArea->GetType() == Storage::SessionStorage);
     storage = GetSessionStorage(aRv);
   }
 
   if (aRv.Failed() || !storage) {
     return nullptr;
   }
 
   MOZ_ASSERT(storage);
-  MOZ_ASSERT(storage->IsForkOf(storageArea));
+  MOZ_ASSERT_IF(storageArea, storage->IsForkOf(storageArea));
 
   dict.mStorageArea = storage;
 
   RefPtr<StorageEvent> event = StorageEvent::Constructor(this, aType, dict);
   return event.forget();
 }
 
 void
@@ -12412,21 +12426,16 @@ nsGlobalWindow::CallOnChildren(Method aM
   }
 }
 
 nsresult
 nsGlobalWindow::FireDelayedDOMEvents()
 {
   FORWARD_TO_INNER(FireDelayedDOMEvents, (), NS_ERROR_UNEXPECTED);
 
-  for (uint32_t i = 0, len = mPendingStorageEvents.Length(); i < len; ++i) {
-    Observe(mPendingStorageEvents[i], "dom-storage2-changed", nullptr);
-    Observe(mPendingStorageEvents[i], "dom-private-storage2-changed", nullptr);
-  }
-
   if (mApplicationCache) {
     static_cast<nsDOMOfflineResourceList*>(mApplicationCache.get())->FirePendingEvents();
   }
 
   // Fires an offline status event if the offline status has changed
   FireOfflineStatusEventIfChanged();
 
   if (mNotifyIdleObserversIdleOnThaw) {
@@ -13286,16 +13295,23 @@ nsGlobalWindow::EventListenerAdded(nsIAt
 {
   if (aType == nsGkAtoms::onvrdisplayactivate ||
       aType == nsGkAtoms::onvrdisplayconnect ||
       aType == nsGkAtoms::onvrdisplaydeactivate ||
       aType == nsGkAtoms::onvrdisplaydisconnect ||
       aType == nsGkAtoms::onvrdisplaypresentchange) {
     NotifyVREventListenerAdded();
   }
+
+  // We need to initialize localStorage in order to receive notifications.
+  if (aType == nsGkAtoms::onstorage) {
+    ErrorResult rv;
+    GetLocalStorage(rv);
+    rv.SuppressException();
+  }
 }
 
 void
 nsGlobalWindow::NotifyVREventListenerAdded()
 {
   MOZ_ASSERT(IsInnerWindow());
   mHasVREvents = true;
   EnableVRUpdates();
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -1938,19 +1938,16 @@ protected:
   RefPtr<mozilla::EventListenerManager> mListenerManager;
   RefPtr<mozilla::dom::Location> mLocation;
   RefPtr<nsHistory>           mHistory;
   RefPtr<mozilla::dom::CustomElementRegistry> mCustomElements;
 
   // These member variables are used on both inner and the outer windows.
   nsCOMPtr<nsIPrincipal> mDocumentPrincipal;
 
-  typedef nsTArray<RefPtr<mozilla::dom::StorageEvent>> nsStorageEventArray;
-  nsStorageEventArray mPendingStorageEvents;
-
   uint32_t mSuspendDepth;
   uint32_t mFreezeDepth;
 
   // the method that was used to focus mFocusedNode
   uint32_t mFocusMethod;
 
   uint32_t mSerial;
 
--- a/dom/events/StorageEvent.h
+++ b/dom/events/StorageEvent.h
@@ -8,16 +8,18 @@
 #define mozilla_dom_StorageEvent_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/StorageEventBinding.h"
 
+class nsIPrincipal;
+
 namespace mozilla {
 namespace dom {
 
 class Storage;
 
 class StorageEvent : public Event
 {
 public:
@@ -29,16 +31,17 @@ public:
 protected:
   virtual ~StorageEvent();
 
   nsString mKey;
   nsString mOldValue;
   nsString mNewValue;
   nsString mUrl;
   RefPtr<Storage> mStorageArea;
+  nsCOMPtr<nsIPrincipal> mPrincipal;
 
 public:
   virtual StorageEvent* AsStorageEvent();
 
   virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   static already_AddRefed<StorageEvent>
   Constructor(EventTarget* aOwner, const nsAString& aType,
@@ -74,14 +77,26 @@ public:
   {
     aRetVal = mUrl;
   }
 
   Storage* GetStorageArea() const
   {
     return mStorageArea;
   }
+
+  // Non WebIDL methods
+  void SetPrincipal(nsIPrincipal* aPrincipal)
+  {
+    MOZ_ASSERT(!mPrincipal);
+    mPrincipal = aPrincipal;
+  }
+
+  nsIPrincipal* GetPrincipal() const
+  {
+    return mPrincipal;
+  }
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_StorageEvent_h
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -29,16 +29,17 @@
 #include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/ExternalHelperAppChild.h"
 #include "mozilla/dom/FileCreatorHelper.h"
 #include "mozilla/dom/FlyWebPublishedServerIPC.h"
 #include "mozilla/dom/GetFilesHelper.h"
 #include "mozilla/dom/MemoryReportRequest.h"
 #include "mozilla/dom/ProcessGlobal.h"
 #include "mozilla/dom/PushNotifier.h"
+#include "mozilla/dom/Storage.h"
 #include "mozilla/dom/StorageIPC.h"
 #include "mozilla/dom/TabGroup.h"
 #include "mozilla/dom/workers/ServiceWorkerManager.h"
 #include "mozilla/dom/nsIContentChild.h"
 #include "mozilla/dom/URLClassifierChild.h"
 #include "mozilla/dom/ipc/BlobChild.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/psm/PSMContentListener.h"
@@ -3030,16 +3031,30 @@ ContentChild::RecvBlobURLRegistration(co
 
 mozilla::ipc::IPCResult
 ContentChild::RecvBlobURLUnregistration(const nsCString& aURI)
 {
   nsHostObjectProtocolHandler::RemoveDataEntry(aURI);
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+ContentChild::RecvDispatchLocalStorageChange(const nsString& aDocumentURI,
+                                             const nsString& aKey,
+                                             const nsString& aOldValue,
+                                             const nsString& aNewValue,
+                                             const IPC::Principal& aPrincipal,
+                                             const bool& aIsPrivate)
+{
+  Storage::DispatchStorageEvent(Storage::LocalStorage,
+                                aDocumentURI, aKey, aOldValue, aNewValue,
+                                aPrincipal, aIsPrivate, nullptr);
+  return IPC_OK();
+}
+
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
 bool
 ContentChild::SendGetA11yContentId()
 {
   return PContentChild::SendGetA11yContentId(&mMsaaID);
 }
 #endif // defined(XP_WIN) && defined(ACCESSIBILITY)
 
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -386,16 +386,24 @@ public:
   const nsAString& GetRemoteType() const;
 
   virtual mozilla::ipc::IPCResult
   RecvInitServiceWorkers(const ServiceWorkerConfiguration& aConfig) override;
 
   virtual mozilla::ipc::IPCResult
   RecvInitBlobURLs(nsTArray<BlobURLRegistrationData>&& aRegistations) override;
 
+  virtual mozilla::ipc::IPCResult
+  RecvDispatchLocalStorageChange(const nsString& aDocumentURI,
+                                 const nsString& aKey,
+                                 const nsString& aOldValue,
+                                 const nsString& aNewValue,
+                                 const IPC::Principal& aPrincipal,
+                                 const bool& aIsPrivate) override;
+
   virtual mozilla::ipc::IPCResult RecvLastPrivateDocShellDestroyed() override;
 
   virtual mozilla::ipc::IPCResult RecvFilePathUpdate(const nsString& aStorageType,
                                                      const nsString& aStorageName,
                                                      const nsString& aPath,
                                                      const nsCString& aReason) override;
 
   virtual mozilla::ipc::IPCResult
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -46,16 +46,17 @@
 #include "mozilla/dom/GetFilesHelper.h"
 #include "mozilla/dom/GeolocationBinding.h"
 #include "mozilla/dom/MemoryReportRequest.h"
 #include "mozilla/dom/Notification.h"
 #include "mozilla/dom/PContentBridgeParent.h"
 #include "mozilla/dom/PContentPermissionRequestParent.h"
 #include "mozilla/dom/PCycleCollectWithLogsParent.h"
 #include "mozilla/dom/ServiceWorkerRegistrar.h"
+#include "mozilla/dom/Storage.h"
 #include "mozilla/dom/StorageIPC.h"
 #include "mozilla/dom/devicestorage/DeviceStorageRequestParent.h"
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/Permissions.h"
 #include "mozilla/dom/PresentationParent.h"
 #include "mozilla/dom/PPresentationParent.h"
 #include "mozilla/dom/PushNotifier.h"
 #include "mozilla/dom/FlyWebPublishedServerIPC.h"
@@ -4764,16 +4765,35 @@ ContentParent::RecvUnstoreAndBroadcastBl
                                                false /* Don't broadcast */);
   BroadcastBlobURLUnregistration(aURI, this);
   mBlobURLs.RemoveElement(aURI);
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
+ContentParent::RecvBroadcastLocalStorageChange(const nsString& aDocumentURI,
+                                               const nsString& aKey,
+                                               const nsString& aOldValue,
+                                               const nsString& aNewValue,
+                                               const Principal& aPrincipal,
+                                               const bool& aIsPrivate)
+{
+  for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+    if (cp != this) {
+      Unused << cp->SendDispatchLocalStorageChange(
+        nsString(aDocumentURI), nsString(aKey), nsString(aOldValue),
+        nsString(aNewValue), IPC::Principal(aPrincipal), aIsPrivate);
+    }
+  }
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
 ContentParent::RecvGetA11yContentId(uint32_t* aContentId)
 {
 #if defined(XP_WIN32) && defined(ACCESSIBILITY)
   *aContentId = a11y::AccessibleWrap::GetContentProcessIdFor(ChildID());
   MOZ_ASSERT(*aContentId);
   return IPC_OK();
 #else
   return IPC_FAIL_NO_REASON(this);
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -570,16 +570,24 @@ public:
   RecvStoreAndBroadcastBlobURLRegistration(const nsCString& aURI,
                                            PBlobParent* aBlobParent,
                                            const Principal& aPrincipal) override;
 
   virtual mozilla::ipc::IPCResult
   RecvUnstoreAndBroadcastBlobURLUnregistration(const nsCString& aURI) override;
 
   virtual mozilla::ipc::IPCResult
+  RecvBroadcastLocalStorageChange(const nsString& aDocumentURI,
+                                  const nsString& aKey,
+                                  const nsString& aOldValue,
+                                  const nsString& aNewValue,
+                                  const IPC::Principal& aPrincipal,
+                                  const bool& aIsPrivate) override;
+
+  virtual mozilla::ipc::IPCResult
   RecvGetA11yContentId(uint32_t* aContentId) override;
 
   virtual int32_t Pid() const override;
 
   virtual PURLClassifierParent*
   AllocPURLClassifierParent(const Principal& aPrincipal,
                             const bool& aUseTrackingProtection,
                             bool* aSuccess) override;
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -671,16 +671,22 @@ child:
 
     async GetFilesResponse(nsID aID, GetFilesResponseResult aResult);
 
     async BlobURLRegistration(nsCString aURI, PBlob aBlob,
                               Principal aPrincipal);
 
     async BlobURLUnregistration(nsCString aURI);
 
+    async DispatchLocalStorageChange(nsString documentURI,
+                                     nsString key,
+                                     nsString oldValue,
+                                     nsString newValue,
+                                     Principal principal,
+                                     bool isPrivate);
 
     async GMPsChanged(GMPCapabilityData[] capabilities);
 
 
     async FileCreationResponse(nsID aID, FileCreationResult aResult);
 
     /**
      * Sending an activate message moves focus to the child.
@@ -1171,16 +1177,23 @@ parent:
                                nsString aName, bool lastModifiedPassed,
                                int64_t lastModified, bool aIsFromNsIFile);
 
      async StoreAndBroadcastBlobURLRegistration(nsCString url, PBlob blob,
                                                 Principal principal);
 
      async UnstoreAndBroadcastBlobURLUnregistration(nsCString url);
 
+     async BroadcastLocalStorageChange(nsString documentURI,
+                                       nsString key,
+                                       nsString oldValue,
+                                       nsString newValue,
+                                       Principal principal,
+                                       bool isPrivate);
+
     /**
      * Messages for communicating child Telemetry to the parent process
      */
     async AccumulateChildHistograms(Accumulation[] accumulations);
     async AccumulateChildKeyedHistograms(KeyedAccumulation[] accumulations);
     async UpdateChildScalars(ScalarAction[] updates);
     async UpdateChildKeyedScalars(KeyedScalarAction[] updates);
 
--- a/dom/storage/Storage.cpp
+++ b/dom/storage/Storage.cpp
@@ -9,27 +9,33 @@
 #include "StorageManager.h"
 
 #include "nsIObserverService.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
 #include "nsICookiePermission.h"
 
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/PermissionMessageUtils.h"
 #include "mozilla/dom/StorageBinding.h"
 #include "mozilla/dom/StorageEvent.h"
 #include "mozilla/dom/StorageEventBinding.h"
 #include "mozilla/Services.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/EnumSet.h"
 #include "nsThreadUtils.h"
 #include "nsContentUtils.h"
 #include "nsServiceManagerUtils.h"
 
 namespace mozilla {
+
+using namespace ipc;
+
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Storage, mManager, mPrincipal, mWindow)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(Storage)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(Storage)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Storage)
@@ -53,17 +59,16 @@ Storage::Storage(nsPIDOMWindowInner* aWi
   , mIsPrivate(aIsPrivate)
   , mIsSessionOnly(false)
 {
   mCache->Preload();
 }
 
 Storage::~Storage()
 {
-  mCache->KeepAlive();
 }
 
 /* virtual */ JSObject*
 Storage::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return StorageBinding::Wrap(aCx, this, aGivenProto);
 }
 
@@ -211,37 +216,101 @@ StorageNotifierRunnable::Run()
 
 } // namespace
 
 void
 Storage::BroadcastChangeNotification(const nsSubstring& aKey,
                                      const nsSubstring& aOldValue,
                                      const nsSubstring& aNewValue)
 {
+  if (!XRE_IsParentProcess() && GetType() == LocalStorage && mPrincipal) {
+    // If we are in a child process, we want to send a message to the parent in
+    // order to broadcast the StorageEvent correctly to any child process.
+    dom::ContentChild* cc = dom::ContentChild::GetSingleton();
+    Unused << NS_WARN_IF(!cc->SendBroadcastLocalStorageChange(
+      mDocumentURI, nsString(aKey), nsString(aOldValue), nsString(aNewValue),
+      IPC::Principal(mPrincipal), mIsPrivate));
+  }
+
+  DispatchStorageEvent(GetType(), mDocumentURI, aKey, aOldValue, aNewValue,
+                       mPrincipal, mIsPrivate, this);
+}
+
+/* static */ void
+Storage::DispatchStorageEvent(StorageType aStorageType,
+                              const nsAString& aDocumentURI,
+                              const nsAString& aKey,
+                              const nsAString& aOldValue,
+                              const nsAString& aNewValue,
+                              nsIPrincipal* aPrincipal,
+                              bool aIsPrivate,
+                              Storage* aStorage)
+{
   StorageEventInit dict;
   dict.mBubbles = false;
   dict.mCancelable = false;
   dict.mKey = aKey;
   dict.mNewValue = aNewValue;
   dict.mOldValue = aOldValue;
-  dict.mStorageArea = this;
-  dict.mUrl = mDocumentURI;
+  dict.mStorageArea = aStorage;
+  dict.mUrl = aDocumentURI;
 
   // Note, this DOM event should never reach JS. It is cloned later in
   // nsGlobalWindow.
   RefPtr<StorageEvent> event =
     StorageEvent::Constructor(nullptr, NS_LITERAL_STRING("storage"), dict);
 
+  event->SetPrincipal(aPrincipal);
+
   RefPtr<StorageNotifierRunnable> r =
     new StorageNotifierRunnable(event,
-                                GetType() == LocalStorage
+                                aStorageType == LocalStorage
                                   ? u"localStorage"
                                   : u"sessionStorage",
-                                IsPrivate());
+                                aIsPrivate);
   NS_DispatchToMainThread(r);
+
+  // If we are in the parent process and we have the principal, we want to
+  // broadcast this event to every other process.
+  if (aStorageType == LocalStorage && XRE_IsParentProcess() && aPrincipal) {
+    for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+      Unused << cp->SendDispatchLocalStorageChange(
+        nsString(aDocumentURI), nsString(aKey), nsString(aOldValue),
+        nsString(aNewValue), IPC::Principal(aPrincipal), aIsPrivate);
+    }
+  }
+}
+
+void
+Storage::ApplyEvent(StorageEvent* aStorageEvent)
+{
+  MOZ_ASSERT(aStorageEvent);
+
+  nsAutoString key;
+  nsAutoString old;
+  nsAutoString value;
+
+  aStorageEvent->GetKey(key);
+  aStorageEvent->GetNewValue(value);
+
+  // No key means clearing the full storage.
+  if (key.IsVoid()) {
+    MOZ_ASSERT(value.IsVoid());
+    mCache->Clear(this);
+    return;
+  }
+
+  // No new value means removing the key.
+  if (value.IsVoid()) {
+    mCache->RemoveItem(this, key, old);
+    return;
+  }
+
+  // Otherwise, we set the new value.
+  mCache->SetItem(this, key, value, old);
 }
 
 static const char kPermissionType[] = "cookie";
 static const char kStorageEnabled[] = "dom.storage.enabled";
 
 bool
 Storage::CanUseStorage(nsIPrincipal& aSubjectPrincipal)
 {
--- a/dom/storage/Storage.h
+++ b/dom/storage/Storage.h
@@ -19,16 +19,17 @@
 class nsIPrincipal;
 class nsPIDOMWindowInner;
 
 namespace mozilla {
 namespace dom {
 
 class StorageManagerBase;
 class StorageCache;
+class StorageEvent;
 
 class Storage final
   : public nsIDOMStorage
   , public nsSupportsWeakReference
   , public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
@@ -124,16 +125,30 @@ public:
   bool IsSessionOnly() const { return mIsSessionOnly; }
 
   bool IsForkOf(const Storage* aOther) const
   {
     MOZ_ASSERT(aOther);
     return mCache == aOther->mCache;
   }
 
+  // aStorage can be null if this method is called by ContentChild.
+  static void
+  DispatchStorageEvent(StorageType aStorageType,
+                       const nsAString& aDocumentURI,
+                       const nsAString& aKey,
+                       const nsAString& aOldValue,
+                       const nsAString& aNewValue,
+                       nsIPrincipal* aPrincipal,
+                       bool aIsPrivate,
+                       Storage* aStorage);
+
+  void
+  ApplyEvent(StorageEvent* aStorageEvent);
+
 protected:
   // The method checks whether the caller can use a storage.
   // CanUseStorage is called before any DOM initiated operation
   // on a storage is about to happen and ensures that the storage's
   // session-only flag is properly set according the current settings.
   // It is an optimization since the privileges check and session only
   // state determination are complex and share the code (comes hand in
   // hand together).
--- a/dom/storage/StorageCache.cpp
+++ b/dom/storage/StorageCache.cpp
@@ -241,83 +241,16 @@ StorageCache::Preload()
     return;
   }
 
   sDatabase->AsyncPreload(this);
 }
 
 namespace {
 
-// This class is passed to timer as a tick observer.  It refers the cache
-// and keeps it alive for a time.
-class StorageCacheHolder : public nsITimerCallback, public nsINamed
-{
-  virtual ~StorageCacheHolder() {}
-
-  NS_DECL_ISUPPORTS
-
-  NS_IMETHOD
-  Notify(nsITimer* aTimer) override
-  {
-    mCache = nullptr;
-    return NS_OK;
-  }
-
-  NS_IMETHOD
-  GetName(nsACString& aName) override
-  {
-    aName.AssignASCII("StorageCacheHolder_timer");
-    return NS_OK;
-  }
-
-  NS_IMETHOD
-  SetName(const char* aName) override
-  {
-    return NS_ERROR_NOT_IMPLEMENTED;
-  }
-
-  RefPtr<StorageCache> mCache;
-
-public:
-  explicit StorageCacheHolder(StorageCache* aCache) : mCache(aCache) {}
-};
-
-NS_IMPL_ISUPPORTS(StorageCacheHolder, nsITimerCallback, nsINamed)
-
-} // namespace
-
-void
-StorageCache::KeepAlive()
-{
-  // Missing reference back to the manager means the cache is not responsible
-  // for its lifetime.  Used for keeping sessionStorage live forever.
-  if (!mManager) {
-    return;
-  }
-
-  if (!NS_IsMainThread()) {
-    // Timer and the holder must be initialized on the main thread.
-    NS_DispatchToMainThread(NewRunnableMethod(this, &StorageCache::KeepAlive));
-    return;
-  }
-
-  nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1");
-  if (!timer) {
-    return;
-  }
-
-  RefPtr<StorageCacheHolder> holder = new StorageCacheHolder(this);
-  timer->InitWithCallback(holder, DOM_STORAGE_CACHE_KEEP_ALIVE_TIME_MS,
-                          nsITimer::TYPE_ONE_SHOT);
-
-  mKeepAliveTimer.swap(timer);
-}
-
-namespace {
-
 // The AutoTimer provided by telemetry headers is only using static,
 // i.e. compile time known ID, but here we know the ID only at run time.
 // Hence a new class.
 class TelemetryAutoTimer
 {
 public:
   explicit TelemetryAutoTimer(Telemetry::HistogramID aId)
     : id(aId), start(TimeStamp::Now())
@@ -675,19 +608,16 @@ StorageCache::LoadItem(const nsAString& 
   data.mKeys.Put(aKey, aValue);
   data.mOriginQuotaUsage += aKey.Length() + aValue.Length();
   return true;
 }
 
 void
 StorageCache::LoadDone(nsresult aRv)
 {
-  // Keep the preloaded cache alive for a time
-  KeepAlive();
-
   MonitorAutoLock monitor(mMonitor);
   mLoadResult = aRv;
   mLoaded = true;
   monitor.Notify();
 }
 
 void
 StorageCache::LoadWait()
--- a/dom/storage/StorageCache.h
+++ b/dom/storage/StorageCache.h
@@ -91,20 +91,16 @@ public:
             nsIPrincipal* aPrincipal, const nsACString& aQuotaOriginScope);
 
   // Copies all data from the other storage.
   void CloneFrom(const StorageCache* aThat);
 
   // Starts async preload of this cache if it persistent and not loaded.
   void Preload();
 
-  // Keeps the cache alive (i.e. present in the manager's hash table) for a
-  // time.
-  void KeepAlive();
-
   // The set of methods that are invoked by DOM storage web API.
   // We are passing the Storage object just to let the cache
   // read properties like mPrivate, mPrincipal and mSessionOnly.
   // Get* methods return error when load from the database has failed.
   nsresult GetLength(const Storage* aStorage, uint32_t* aRetval);
   nsresult GetKey(const Storage* aStorage, uint32_t index, nsAString& aRetval);
   nsresult GetItem(const Storage* aStorage, const nsAString& aKey,
                    nsAString& aRetval);
@@ -191,19 +187,16 @@ private:
   // hash table is handled in the destructor by call to the manager.  Cache
   // could potentially overlive the manager, hence the hard ref.
   RefPtr<StorageManagerBase> mManager;
 
   // Reference to the usage counter object we check on for eTLD+1 quota limit.
   // Obtained from the manager during initialization (Init method).
   RefPtr<StorageUsage> mUsage;
 
-  // Timer that holds this cache alive for a while after it has been preloaded.
-  nsCOMPtr<nsITimer> mKeepAliveTimer;
-
   // Principal the cache has been initially created for, this is used only for
   // sessionStorage access checks since sessionStorage objects are strictly
   // scoped by a principal. localStorage objects on the other hand are scoped
   // by origin only.
   nsCOMPtr<nsIPrincipal> mPrincipal;
 
   // The origin this cache belongs to in the "DB format", i.e. reversed
   nsCString mOriginNoSuffix;
--- a/editor/libeditor/tests/test_bug674770-1.html
+++ b/editor/libeditor/tests/test_bug674770-1.html
@@ -19,17 +19,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 <a href="file_bug674770-1.html" id="link2">test</a>
 </div>
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 SimpleTest.waitForExplicitFinish();
 SimpleTest.waitForFocus(function() {
-  SpecialPowers.pushPrefEnv({"set":[["middlemouse.paste", true], ["dom.ipc.processCount", 1]]}, startTests);
+  SpecialPowers.pushPrefEnv({"set":[["middlemouse.paste", true]]}, startTests);
 });
 
 function startTests() {
   var tests = [
     { description: "Testing link in <div>: ",
       target: function () { return document.querySelector("#link1"); },
       linkShouldWork: true },
     { description: "Testing link in <div contenteditable>: ",