Bug 1525291 - LSNG: Chrome observer notifications for session storage are not distributed to content processes; r=asuth,froydnj
authorJan Varga <jan.varga@gmail.com>
Wed, 06 Feb 2019 06:09:57 +0100
changeset 457566 87f055bd45516f9cbb55c2d761287834fda69eeb
parent 457565 e8dc8aa65fbb703370c6e7a1fe482b7e3cca56b5
child 457567 ce8c71866841cb72b8d82acd3826ef163c48b5db
push id111756
push userjvarga@mozilla.com
push dateThu, 07 Feb 2019 16:56:26 +0000
treeherdermozilla-inbound@87f055bd4551 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth, froydnj
bugs1525291
milestone67.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 1525291 - LSNG: Chrome observer notifications for session storage are not distributed to content processes; r=asuth,froydnj Differential Revision: https://phabricator.services.mozilla.com/D18809
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
dom/ipc/moz.build
dom/localstorage/LSObserver.h
dom/storage/PSessionStorageObserver.ipdl
dom/storage/SessionStorageManager.cpp
dom/storage/SessionStorageManager.h
dom/storage/SessionStorageObserver.cpp
dom/storage/SessionStorageObserver.h
dom/storage/StorageIPC.cpp
dom/storage/StorageIPC.h
dom/storage/StorageObserver.h
dom/storage/moz.build
gfx/vr/ipc/PVR.ipdl
gfx/vr/ipc/VRMessageUtils.h
ipc/ipdl/ipdl/direct_call.py
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -41,16 +41,17 @@
 #include "mozilla/dom/ExternalHelperAppChild.h"
 #include "mozilla/dom/FileCreatorHelper.h"
 #include "mozilla/dom/GetFilesHelper.h"
 #include "mozilla/dom/IPCBlobUtils.h"
 #include "mozilla/dom/JSWindowActorService.h"
 #include "mozilla/dom/LSObject.h"
 #include "mozilla/dom/MemoryReportRequest.h"
 #include "mozilla/dom/PLoginReputationChild.h"
+#include "mozilla/dom/PSessionStorageObserverChild.h"
 #include "mozilla/dom/PostMessageEvent.h"
 #include "mozilla/dom/PushNotifier.h"
 #include "mozilla/dom/RemoteWorkerService.h"
 #include "mozilla/dom/ServiceWorkerManager.h"
 #include "mozilla/dom/TabGroup.h"
 #include "mozilla/dom/nsIContentChild.h"
 #include "mozilla/dom/URLClassifierChild.h"
 #include "mozilla/dom/WorkerDebugger.h"
@@ -3238,16 +3239,30 @@ PLoginReputationChild* ContentChild::All
 }
 
 bool ContentChild::DeallocPLoginReputationChild(PLoginReputationChild* aActor) {
   MOZ_ASSERT(aActor);
   delete aActor;
   return true;
 }
 
+PSessionStorageObserverChild*
+ContentChild::AllocPSessionStorageObserverChild() {
+  MOZ_CRASH(
+      "PSessionStorageObserverChild actors should be manually constructed!");
+}
+
+bool ContentChild::DeallocPSessionStorageObserverChild(
+    PSessionStorageObserverChild* aActor) {
+  MOZ_ASSERT(aActor);
+
+  delete aActor;
+  return true;
+}
+
 // The IPC code will call this method asking us to assign an event target to new
 // actors created by the ContentParent.
 already_AddRefed<nsIEventTarget> ContentChild::GetConstructedEventTarget(
     const Message& aMsg) {
   // Currently we only set targets for PBrowser.
   if (aMsg.type() != PContent::Msg_PBrowserConstructor__ID) {
     return nullptr;
   }
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -629,16 +629,21 @@ class ContentChild final : public PConte
       const URIParams& aUri,
       const nsTArray<IPCURLClassifierFeature>& aFeatures);
   bool DeallocPURLClassifierLocalChild(PURLClassifierLocalChild* aActor);
 
   PLoginReputationChild* AllocPLoginReputationChild(const URIParams& aUri);
 
   bool DeallocPLoginReputationChild(PLoginReputationChild* aActor);
 
+  PSessionStorageObserverChild* AllocPSessionStorageObserverChild();
+
+  bool DeallocPSessionStorageObserverChild(
+      PSessionStorageObserverChild* aActor);
+
   nsTArray<LookAndFeelInt>& LookAndFeelCache() { return mLookAndFeelCache; }
 
   /**
    * Helper function for protocols that use the GPU process when available.
    * Overrides FatalError to just be a warning when communicating with the
    * GPU process since we don't want to crash the content process when the
    * GPU process crashes.
    */
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -84,16 +84,17 @@
 #include "mozilla/intl/LocaleService.h"
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 #include "mozilla/layers/PAPZParent.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/ImageBridgeParent.h"
 #include "mozilla/layers/LayerTreeOwnerTracker.h"
 #include "mozilla/loader/ScriptCacheActors.h"
 #include "mozilla/LoginReputationIPC.h"
+#include "mozilla/dom/StorageIPC.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/media/MediaParent.h"
 #include "mozilla/Move.h"
 #include "mozilla/net/NeckoParent.h"
 #include "mozilla/net/CookieServiceParent.h"
 #include "mozilla/net/PCookieServiceParent.h"
 #include "mozilla/plugins/PluginBridge.h"
 #include "mozilla/Preferences.h"
@@ -5438,16 +5439,42 @@ bool ContentParent::DeallocPLoginReputat
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aActor);
 
   RefPtr<LoginReputationParent> actor =
       dont_AddRef(static_cast<LoginReputationParent*>(aActor));
   return true;
 }
 
+PSessionStorageObserverParent*
+ContentParent::AllocPSessionStorageObserverParent() {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  return mozilla::dom::AllocPSessionStorageObserverParent();
+}
+
+mozilla::ipc::IPCResult ContentParent::RecvPSessionStorageObserverConstructor(
+    PSessionStorageObserverParent* aActor) {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aActor);
+
+  if (!mozilla::dom::RecvPSessionStorageObserverConstructor(aActor)) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+  return IPC_OK();
+}
+
+bool ContentParent::DeallocPSessionStorageObserverParent(
+    PSessionStorageObserverParent* aActor) {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aActor);
+
+  return mozilla::dom::DeallocPSessionStorageObserverParent(aActor);
+}
+
 mozilla::ipc::IPCResult ContentParent::RecvFileCreationRequest(
     const nsID& aID, const nsString& aFullPath, const nsString& aType,
     const nsString& aName, const bool& aLastModifiedPassed,
     const int64_t& aLastModified, const bool& aExistenceCheck,
     const bool& aIsFromNsIFile) {
   // We allow the creation of File via this IPC call only for the 'file' process
   // or for testing.
   if (!mRemoteType.EqualsLiteral(FILE_REMOTE_TYPE) &&
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -565,16 +565,24 @@ class ContentParent final : public PCont
 
   PLoginReputationParent* AllocPLoginReputationParent(const URIParams& aURI);
 
   virtual mozilla::ipc::IPCResult RecvPLoginReputationConstructor(
       PLoginReputationParent* aActor, const URIParams& aURI) override;
 
   bool DeallocPLoginReputationParent(PLoginReputationParent* aActor);
 
+  PSessionStorageObserverParent* AllocPSessionStorageObserverParent();
+
+  virtual mozilla::ipc::IPCResult RecvPSessionStorageObserverConstructor(
+      PSessionStorageObserverParent* aActor) override;
+
+  bool DeallocPSessionStorageObserverParent(
+      PSessionStorageObserverParent* aActor);
+
   virtual bool SendActivate(PBrowserParent* aTab) override {
     return PContentParent::SendActivate(aTab);
   }
 
   virtual bool SendDeactivate(PBrowserParent* aTab) override {
     return PContentParent::SendDeactivate(aTab);
   }
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -41,16 +41,17 @@ include protocol PWebrtcGlobal;
 include protocol PPresentation;
 include protocol PURLClassifier;
 include protocol PURLClassifierLocal;
 include protocol PVRManager;
 include protocol PVideoDecoderManager;
 include protocol PRemoteDecoderManager;
 include protocol PProfiler;
 include protocol PScriptCache;
+include protocol PSessionStorageObserver;
 include DOMTypes;
 include JavaScriptTypes;
 include IPCBlob;
 include PTabContext;
 include URIParams;
 include PluginTypes;
 include ProtocolTypes;
 include PBackgroundSharedTypes;
@@ -347,16 +348,17 @@ nested(upto inside_cpow) sync protocol P
     manages PRemoteSpellcheckEngine;
     manages PWebBrowserPersistDocument;
     manages PWebrtcGlobal;
     manages PPresentation;
     manages PURLClassifier;
     manages PURLClassifierLocal;
     manages PScriptCache;
     manages PLoginReputation;
+    manages PSessionStorageObserver;
 
 both:
     // Depending on exactly how the new browser is being created, it might be
     // created from either the child or parent process!
     //
     // The child creates the PBrowser as part of
     // TabChild::BrowserFrameProvideWindow (which happens when the child's
     // content calls window.open()), and the parent creates the PBrowser as part
@@ -836,16 +838,18 @@ parent:
 
     sync PURLClassifier(Principal principal)
         returns (bool success);
 
     async PURLClassifierLocal(URIParams uri, IPCURLClassifierFeature[] features);
 
     async PLoginReputation(URIParams formURI);
 
+    async PSessionStorageObserver();
+
     // Services remoting
 
     async StartVisitedQuery(URIParams uri);
     async SetURITitle(URIParams uri, nsString title);
 
     async LoadURIExternal(URIParams uri, PBrowser windowContext);
     async ExtProtocolChannelConnectParent(uint32_t registrarId);
 
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -148,16 +148,17 @@ LOCAL_INCLUDES += [
     '/docshell/base',
     '/dom/base',
     '/dom/bindings',
     '/dom/events',
     '/dom/filesystem',
     '/dom/geolocation',
     '/dom/media/webspeech/synth/ipc',
     '/dom/security',
+    '/dom/storage',
     '/extensions/cookie',
     '/extensions/spellcheck/src',
     '/gfx/2d',
     '/hal/sandbox',
     '/js/xpconnect/loader',
     '/js/xpconnect/src',
     '/layout/base',
     '/media/webrtc',
--- a/dom/localstorage/LSObserver.h
+++ b/dom/localstorage/LSObserver.h
@@ -33,33 +33,34 @@ 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 AssertIsOnOwningThread() const { NS_ASSERT_OWNINGTHREAD(LSObserver); }
 
   void SetActor(LSObserverChild* aActor);
 
   void ClearActor() {
     AssertIsOnOwningThread();
     MOZ_ASSERT(mActor);
 
     mActor = nullptr;
   }
 
  private:
+  // Only created by LSObject.
+  explicit LSObserver(const nsACString& aOrigin);
+
   ~LSObserver();
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_localstorage_LSObserver_h
new file mode 100644
--- /dev/null
+++ b/dom/storage/PSessionStorageObserver.ipdl
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* vim: set sw=4 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PContent;
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * Protocol used to relay chrome observer notifications related to clearing data
+ * to SessionStorageManager instances in content processes. A single instance is
+ * created by each content process when LocalStorage NextGen is enabled. When
+ * LSNG is disabled, the notifications are instead propagated via
+ * PBackgroundStorageChild. This does mean there are potentially slight ordering
+ * differences in when the notification will be received and processed. It's
+ * okay for this protocol to be managed by PContent rather than PBackground
+ * because these notifications are both rare and to-child-only. (Legacy
+ * LocalStorage was moved to PBackground from PContent because of parent-process
+ * main-thread contention for the processing of "parent:" messages in a very
+ * performance-sensitive context!)
+ */
+async protocol PSessionStorageObserver
+{
+  manager PContent;
+
+parent:
+  async DeleteMe();
+
+child:
+  async __delete__();
+
+  async Observe(nsCString topic,
+                nsString originAttributesPattern,
+                nsCString originScope);
+};
+
+}
+}
--- a/dom/storage/SessionStorageManager.cpp
+++ b/dom/storage/SessionStorageManager.cpp
@@ -1,18 +1,20 @@
 /* -*- 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 "SessionStorageManager.h"
 
+#include "mozilla/dom/ContentChild.h"
 #include "SessionStorage.h"
 #include "SessionStorageCache.h"
+#include "SessionStorageObserver.h"
 #include "StorageUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 using namespace StorageUtils;
 
 NS_IMPL_ISUPPORTS(SessionStorageManager, nsIDOMStorageManager)
@@ -21,16 +23,40 @@ SessionStorageManager::SessionStorageMan
   StorageObserver* observer = StorageObserver::Self();
   NS_ASSERTION(
       observer,
       "No StorageObserver, cannot observe private data delete notifications!");
 
   if (observer) {
     observer->AddSink(this);
   }
+
+  if (!XRE_IsParentProcess() && NextGenLocalStorageEnabled()) {
+    // When LSNG is enabled the thread IPC bridge doesn't exist, so we have to
+    // create own protocol to distribute chrome observer notifications to
+    // content processes.
+    mObserver = SessionStorageObserver::Get();
+
+    if (!mObserver) {
+      ContentChild* contentActor = ContentChild::GetSingleton();
+      MOZ_ASSERT(contentActor);
+
+      RefPtr<SessionStorageObserver> observer = new SessionStorageObserver();
+
+      SessionStorageObserverChild* actor =
+          new SessionStorageObserverChild(observer);
+
+      MOZ_ALWAYS_TRUE(
+          contentActor->SendPSessionStorageObserverConstructor(actor));
+
+      observer->SetActor(actor);
+
+      mObserver = std::move(observer);
+    }
+  }
 }
 
 SessionStorageManager::~SessionStorageManager() {
   StorageObserver* observer = StorageObserver::Self();
   if (observer) {
     observer->RemoveSink(this);
   }
 }
--- a/dom/storage/SessionStorageManager.h
+++ b/dom/storage/SessionStorageManager.h
@@ -11,16 +11,17 @@
 #include "nsClassHashtable.h"
 #include "nsRefPtrHashtable.h"
 #include "StorageObserver.h"
 
 namespace mozilla {
 namespace dom {
 
 class SessionStorageCache;
+class SessionStorageObserver;
 
 class SessionStorageManager final : public nsIDOMStorageManager,
                                     public StorageObserverSink {
  public:
   SessionStorageManager();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMSTORAGEMANAGER
@@ -40,14 +41,16 @@ class SessionStorageManager final : publ
 
   void ClearStorages(ClearStorageType aType,
                      const OriginAttributesPattern& aPattern,
                      const nsACString& aOriginScope);
 
   typedef nsRefPtrHashtable<nsCStringHashKey, SessionStorageCache>
       OriginKeyHashTable;
   nsClassHashtable<nsCStringHashKey, OriginKeyHashTable> mOATable;
+
+  RefPtr<SessionStorageObserver> mObserver;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_SessionStorageManager_h
new file mode 100644
--- /dev/null
+++ b/dom/storage/SessionStorageObserver.cpp
@@ -0,0 +1,55 @@
+/* -*- 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 "SessionStorageObserver.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+SessionStorageObserver* gSessionStorageObserver = nullptr;
+
+}  // namespace
+
+SessionStorageObserver::SessionStorageObserver() : mActor(nullptr) {
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(NextGenLocalStorageEnabled());
+
+  MOZ_ASSERT(!gSessionStorageObserver);
+  gSessionStorageObserver = this;
+}
+
+SessionStorageObserver::~SessionStorageObserver() {
+  AssertIsOnOwningThread();
+
+  if (mActor) {
+    mActor->SendDeleteMeInternal();
+    MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
+  }
+
+  MOZ_ASSERT(gSessionStorageObserver);
+  gSessionStorageObserver = nullptr;
+}
+
+// static
+SessionStorageObserver* SessionStorageObserver::Get() {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(NextGenLocalStorageEnabled());
+
+  return gSessionStorageObserver;
+}
+
+void SessionStorageObserver::SetActor(SessionStorageObserverChild* aActor) {
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aActor);
+  MOZ_ASSERT(!mActor);
+
+  mActor = aActor;
+}
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/storage/SessionStorageObserver.h
@@ -0,0 +1,63 @@
+/* -*- 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_SessionStorageObserver_h
+#define mozilla_dom_SessionStorageObserver_h
+
+namespace mozilla {
+namespace dom {
+
+class SessionStorageObserverChild;
+
+/**
+ * Effectively just a refcounted life-cycle management wrapper around
+ * SessionStorageObserverChild which exists to receive chrome observer
+ * notifications from the main process.
+ *
+ * ## Lifecycle ##
+ * - Created by SessionStorageManager::SessionStorageManager.  Placed in the
+ *   gSessionStorageObserver variable for subsequent SessionStorageManager's via
+ *   SessionStorageObserver::Get lookup.
+ * - The SessionStorageObserverChild directly handles "Observe" messages,
+ *   shunting them directly to StorageObserver::Notify which distributes them to
+ *   individual observer sinks.
+ * - Destroyed when refcount goes to zero due to all owning
+ *   SessionStorageManager being destroyed.
+ */
+class SessionStorageObserver final {
+  friend class SessionStorageManager;
+
+  SessionStorageObserverChild* mActor;
+
+ public:
+  static SessionStorageObserver* Get();
+
+  NS_INLINE_DECL_REFCOUNTING(SessionStorageObserver)
+
+  void AssertIsOnOwningThread() const {
+    NS_ASSERT_OWNINGTHREAD(SessionStorageObserver);
+  }
+
+  void SetActor(SessionStorageObserverChild* aActor);
+
+  void ClearActor() {
+    AssertIsOnOwningThread();
+    MOZ_ASSERT(mActor);
+
+    mActor = nullptr;
+  }
+
+ private:
+  // Only created by SessionStorageManager.
+  SessionStorageObserver();
+
+  ~SessionStorageObserver();
+};
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif  // mozilla_dom_SessionStorageObserver_h
--- a/dom/storage/StorageIPC.cpp
+++ b/dom/storage/StorageIPC.cpp
@@ -7,16 +7,17 @@
 #include "StorageIPC.h"
 
 #include "LocalStorageManager.h"
 
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/PBackgroundChild.h"
 #include "mozilla/ipc/PBackgroundParent.h"
+#include "mozilla/dom/ContentParent.h"
 #include "mozilla/Unused.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 namespace {
 
@@ -396,16 +397,63 @@ StorageDBChild::ShutdownObserver::Observ
 
     NS_RELEASE(sStorageChild);
     sStorageChild = nullptr;
   }
 
   return NS_OK;
 }
 
+SessionStorageObserverChild::SessionStorageObserverChild(
+    SessionStorageObserver* aObserver)
+    : mObserver(aObserver) {
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(NextGenLocalStorageEnabled());
+  MOZ_ASSERT(aObserver);
+  aObserver->AssertIsOnOwningThread();
+
+  MOZ_COUNT_CTOR(SessionStorageObserverChild);
+}
+
+SessionStorageObserverChild::~SessionStorageObserverChild() {
+  AssertIsOnOwningThread();
+
+  MOZ_COUNT_DTOR(SessionStorageObserverChild);
+}
+
+void SessionStorageObserverChild::SendDeleteMeInternal() {
+  AssertIsOnOwningThread();
+
+  if (mObserver) {
+    mObserver->ClearActor();
+    mObserver = nullptr;
+
+    MOZ_ALWAYS_TRUE(PSessionStorageObserverChild::SendDeleteMe());
+  }
+}
+
+void SessionStorageObserverChild::ActorDestroy(ActorDestroyReason aWhy) {
+  AssertIsOnOwningThread();
+
+  if (mObserver) {
+    mObserver->ClearActor();
+    mObserver = nullptr;
+  }
+}
+
+mozilla::ipc::IPCResult SessionStorageObserverChild::RecvObserve(
+    const nsCString& aTopic, const nsString& aOriginAttributesPattern,
+    const nsCString& aOriginScope) {
+  AssertIsOnOwningThread();
+
+  StorageObserver::Self()->Notify(aTopic.get(), aOriginAttributesPattern,
+                                  aOriginScope);
+  return IPC_OK();
+}
+
 LocalStorageCacheParent::LocalStorageCacheParent(
     const PrincipalInfo& aPrincipalInfo, const nsACString& aOriginKey,
     uint32_t aPrivateBrowsingId)
     : mPrincipalInfo(aPrincipalInfo),
       mOriginKey(aOriginKey),
       mPrivateBrowsingId(aPrivateBrowsingId),
       mActorDestroyed(false) {
   AssertIsOnBackgroundThread();
@@ -1102,16 +1150,66 @@ nsresult StorageDBParent::ObserverSink::
       aOriginScope);
 
   MOZ_ALWAYS_SUCCEEDS(
       mOwningEventTarget->Dispatch(runnable, NS_DISPATCH_NORMAL));
 
   return NS_OK;
 }
 
+SessionStorageObserverParent::SessionStorageObserverParent()
+    : mActorDestroyed(false) {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  StorageObserver* observer = StorageObserver::Self();
+  if (observer) {
+    observer->AddSink(this);
+  }
+}
+
+SessionStorageObserverParent::~SessionStorageObserverParent() {
+  MOZ_ASSERT(mActorDestroyed);
+
+  StorageObserver* observer = StorageObserver::Self();
+  if (observer) {
+    observer->RemoveSink(this);
+  }
+}
+
+void SessionStorageObserverParent::ActorDestroy(ActorDestroyReason aWhy) {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!mActorDestroyed);
+
+  mActorDestroyed = true;
+}
+
+mozilla::ipc::IPCResult SessionStorageObserverParent::RecvDeleteMe() {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!mActorDestroyed);
+
+  IProtocol* mgr = Manager();
+  if (!PSessionStorageObserverParent::Send__delete__(this)) {
+    return IPC_FAIL_NO_REASON(mgr);
+  }
+  return IPC_OK();
+}
+
+nsresult SessionStorageObserverParent::Observe(
+    const char* aTopic, const nsAString& aOriginAttributesPattern,
+    const nsACString& aOriginScope) {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!mActorDestroyed) {
+    mozilla::Unused << SendObserve(nsCString(aTopic),
+                                   nsString(aOriginAttributesPattern),
+                                   nsCString(aOriginScope));
+  }
+  return NS_OK;
+}
+
 /*******************************************************************************
  * Exported functions
  ******************************************************************************/
 
 PBackgroundLocalStorageCacheParent* AllocPBackgroundLocalStorageCacheParent(
     const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
     const nsCString& aOriginKey, const uint32_t& aPrivateBrowsingId) {
   AssertIsOnBackgroundThread();
@@ -1184,10 +1282,40 @@ bool DeallocPBackgroundStorageParent(PBa
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aActor);
 
   StorageDBParent* actor = static_cast<StorageDBParent*>(aActor);
   actor->ReleaseIPDLReference();
   return true;
 }
 
+PSessionStorageObserverParent* AllocPSessionStorageObserverParent() {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  RefPtr<SessionStorageObserverParent> actor =
+      new SessionStorageObserverParent();
+
+  // Transfer ownership to IPDL.
+  return actor.forget().take();
+}
+
+bool RecvPSessionStorageObserverConstructor(
+    PSessionStorageObserverParent* aActor) {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aActor);
+
+  return true;
+}
+
+bool DeallocPSessionStorageObserverParent(
+    PSessionStorageObserverParent* aActor) {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aActor);
+
+  // Transfer ownership back from IPDL.
+  RefPtr<SessionStorageObserverParent> actor =
+      dont_AddRef(static_cast<SessionStorageObserverParent*>(aActor));
+
+  return true;
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/storage/StorageIPC.h
+++ b/dom/storage/StorageIPC.h
@@ -6,16 +6,18 @@
 
 #ifndef mozilla_dom_StorageIPC_h
 #define mozilla_dom_StorageIPC_h
 
 #include "mozilla/dom/PBackgroundLocalStorageCacheChild.h"
 #include "mozilla/dom/PBackgroundLocalStorageCacheParent.h"
 #include "mozilla/dom/PBackgroundStorageChild.h"
 #include "mozilla/dom/PBackgroundStorageParent.h"
+#include "mozilla/dom/PSessionStorageObserverChild.h"
+#include "mozilla/dom/PSessionStorageObserverParent.h"
 #include "StorageDBThread.h"
 #include "LocalStorageCache.h"
 #include "StorageObserver.h"
 #include "mozilla/Mutex.h"
 #include "nsAutoPtr.h"
 
 namespace mozilla {
 
@@ -27,16 +29,18 @@ class BackgroundChildImpl;
 class PrincipalInfo;
 
 }  // namespace ipc
 
 namespace dom {
 
 class LocalStorageManager;
 class PBackgroundStorageParent;
+class PSessionStorageObserverParent;
+class SessionStorageObserver;
 
 class LocalStorageCacheChild final : public PBackgroundLocalStorageCacheChild {
   friend class mozilla::ipc::BackgroundChildImpl;
   friend class LocalStorageCache;
   friend class LocalStorageManager;
 
   // LocalStorageCache effectively owns this instance, although IPC handles its
   // allocation/deallocation.  When the LocalStorageCache destructor runs, it
@@ -162,16 +166,51 @@ class StorageDBChild final : public PBac
   nsTHashtable<nsRefPtrHashKey<LocalStorageCacheBridge>> mLoadingCaches;
 
   // Status of the remote database
   nsresult mStatus;
 
   bool mIPCOpen;
 };
 
+class SessionStorageObserverChild final : public PSessionStorageObserverChild {
+  friend class SessionStorageManager;
+  friend class SessionStorageObserver;
+
+  // SessionStorageObserver effectively owns this instance, although IPC handles
+  // its allocation/deallocation.  When the SessionStorageObserver destructor
+  // runs, it will invoke SendDeleteMeInternal() which will trigger both
+  // instances to drop their mutual references and cause IPC to destroy the
+  // actor after the DeleteMe round-trip.
+  SessionStorageObserver* MOZ_NON_OWNING_REF mObserver;
+
+  NS_DECL_OWNINGTHREAD
+
+ public:
+  void AssertIsOnOwningThread() const {
+    NS_ASSERT_OWNINGTHREAD(LocalStorageCacheChild);
+  }
+
+ private:
+  // Only created by SessionStorageManager.
+  explicit SessionStorageObserverChild(SessionStorageObserver* aObserver);
+
+  ~SessionStorageObserverChild();
+
+  // Only called by SessionStorageObserver.
+  void SendDeleteMeInternal();
+
+  // IPDL methods are only called by IPDL.
+  void ActorDestroy(ActorDestroyReason aWhy) override;
+
+  mozilla::ipc::IPCResult RecvObserve(const nsCString& aTopic,
+                                      const nsString& aOriginAttributesPattern,
+                                      const nsCString& aOriginScope) override;
+};
+
 class LocalStorageCacheParent final
     : public PBackgroundLocalStorageCacheParent {
   const PrincipalInfo mPrincipalInfo;
   const nsCString mOriginKey;
   uint32_t mPrivateBrowsingId;
   bool mActorDestroyed;
 
  public:
@@ -347,16 +386,40 @@ class StorageDBParent final : public PBa
 
   ThreadSafeAutoRefCnt mRefCnt;
   NS_DECL_OWNINGTHREAD
 
   // True when IPC channel is open and Send*() methods are OK to use.
   bool mIPCOpen;
 };
 
+class SessionStorageObserverParent final : public PSessionStorageObserverParent,
+                                           public StorageObserverSink {
+  bool mActorDestroyed;
+
+ public:
+  // Created in AllocPSessionStorageObserverParent.
+  SessionStorageObserverParent();
+
+  NS_INLINE_DECL_REFCOUNTING(mozilla::dom::SessionStorageObserverParent)
+
+ private:
+  // Reference counted.
+  ~SessionStorageObserverParent();
+
+  // IPDL methods are only called by IPDL.
+  void ActorDestroy(ActorDestroyReason aWhy) override;
+
+  mozilla::ipc::IPCResult RecvDeleteMe() override;
+
+  // StorageObserverSink
+  nsresult Observe(const char* aTopic, const nsAString& aOriginAttrPattern,
+                   const nsACString& aOriginScope) override;
+};
+
 PBackgroundLocalStorageCacheParent* AllocPBackgroundLocalStorageCacheParent(
     const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
     const nsCString& aOriginKey, const uint32_t& aPrivateBrowsingId);
 
 mozilla::ipc::IPCResult RecvPBackgroundLocalStorageCacheConstructor(
     mozilla::ipc::PBackgroundParent* aBackgroundActor,
     PBackgroundLocalStorageCacheParent* aActor,
     const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
@@ -368,12 +431,20 @@ bool DeallocPBackgroundLocalStorageCache
 PBackgroundStorageParent* AllocPBackgroundStorageParent(
     const nsString& aProfilePath);
 
 mozilla::ipc::IPCResult RecvPBackgroundStorageConstructor(
     PBackgroundStorageParent* aActor, const nsString& aProfilePath);
 
 bool DeallocPBackgroundStorageParent(PBackgroundStorageParent* aActor);
 
+PSessionStorageObserverParent* AllocPSessionStorageObserverParent();
+
+bool RecvPSessionStorageObserverConstructor(
+    PSessionStorageObserverParent* aActor);
+
+bool DeallocPSessionStorageObserverParent(
+    PSessionStorageObserverParent* aActor);
+
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_StorageIPC_h
--- a/dom/storage/StorageObserver.h
+++ b/dom/storage/StorageObserver.h
@@ -13,18 +13,20 @@
 #include "nsTObserverArray.h"
 #include "nsString.h"
 
 namespace mozilla {
 namespace dom {
 
 class StorageObserver;
 
-// Implementers are StorageManager and StorageDBParent to forward to
-// child processes.
+// Main-thread interface implemented by legacy LocalStorageManager and current
+// SessionStorageManager for direct consumption. Also implemented by legacy
+// StorageDBParent and current SessionStorageObserverParent for propagation to
+// content processes.
 class StorageObserverSink {
  public:
   virtual ~StorageObserverSink() {}
 
  private:
   friend class StorageObserver;
   virtual nsresult Observe(const char* aTopic,
                            const nsAString& aOriginAttributesPattern,
--- a/dom/storage/moz.build
+++ b/dom/storage/moz.build
@@ -24,29 +24,31 @@ EXPORTS.mozilla.dom += [
 UNIFIED_SOURCES += [
     'LocalStorage.cpp',
     'LocalStorageCache.cpp',
     'LocalStorageManager.cpp',
     'PartitionedLocalStorage.cpp',
     'SessionStorage.cpp',
     'SessionStorageCache.cpp',
     'SessionStorageManager.cpp',
+    'SessionStorageObserver.cpp',
     'Storage.cpp',
     'StorageActivityService.cpp',
     'StorageDBThread.cpp',
     'StorageDBUpdater.cpp',
     'StorageIPC.cpp',
     'StorageNotifierService.cpp',
     'StorageObserver.cpp',
     'StorageUtils.cpp',
 ]
 
 IPDL_SOURCES += [
     'PBackgroundLocalStorageCache.ipdl',
     'PBackgroundStorage.ipdl',
+    'PSessionStorageObserver.ipdl',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/dom/base',
 ]
--- a/gfx/vr/ipc/PVR.ipdl
+++ b/gfx/vr/ipc/PVR.ipdl
@@ -5,16 +5,18 @@
 
 using mozilla::TimeStamp from "mozilla/TimeStamp.h";
 using mozilla::gfx::OpenVRControllerType from "gfxVR.h";
 using mozilla::dom::NativeThreadId from "mozilla/dom/TabMessageUtils.h";
 
 include GraphicsMessages;
 include protocol PVRGPU;
 
+include "VRMessageUtils.h";
+
 namespace mozilla {
 namespace gfx {
 
 async protocol PVR
 {
 parent:
   async NewGPUVRManager(Endpoint<PVRGPUParent> endpoint);
   async Init(GfxPrefSetting[] prefs, GfxVarUpdate[] vars, DevicePrefs devicePrefs);
--- a/gfx/vr/ipc/VRMessageUtils.h
+++ b/gfx/vr/ipc/VRMessageUtils.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_gfx_vr_VRMessageUtils_h
 #define mozilla_gfx_vr_VRMessageUtils_h
 
 #include "ipc/IPCMessageUtils.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/GfxMessageUtils.h"
+#include "mozilla/dom/GamepadMessageUtils.h"
 #include "VRManager.h"
 
 #include "gfxVR.h"
 
 namespace IPC {
 
 template <>
 struct ParamTraits<mozilla::gfx::VRDeviceType>
--- a/ipc/ipdl/ipdl/direct_call.py
+++ b/ipc/ipdl/ipdl/direct_call.py
@@ -299,16 +299,18 @@ VIRTUAL_CALL_CLASSES = set([
     ("PBackgroundStorage", "child"),
     ("PBackgroundStorage", "parent"),
     ("PBrowserStream", "parent"),
     ("PExternalHelperApp", "parent"),
     ("PFTPChannel", "child"),
     ("PFTPChannel", "parent"),
     ("PHttpChannel", "child"),
     ("PHttpChannel", "parent"),
+    ("PSessionStorageObserver", "child"),
+    ("PSessionStorageObserver", "parent"),
     ("PWyciwygChannel", "child"),
 
     # bug 1513911
     ("PIndexedDBPermissionRequest", "child"),
 
     # Recv* methods are MOZ_CAN_RUN_SCRIPT and OnMessageReceived is not, so
     # it's not allowed to call them.
     ("PBrowser", "child"),