Bug 1463587: Part 2 - Add a shared-memory structured clone key-value store. r?erahm,bz draft
authorKris Maglione <maglione.k@gmail.com>
Fri, 22 Jun 2018 20:35:49 -0700
changeset 816386 dc80a950cf8df699ce688002bc0c45e3988df039
parent 816385 ad6f23c34836b2f61797243ef0198805706e4719
child 816387 468a65f46075e9d23333427ec643e6058f42ce7d
push id115826
push usermaglione.k@gmail.com
push dateWed, 11 Jul 2018 05:19:48 +0000
reviewerserahm, bz
bugs1463587
milestone63.0a1
Bug 1463587: Part 2 - Add a shared-memory structured clone key-value store. r?erahm,bz This class allows one read-write copy of a map in the parent process to share data with multiple read-only copies in child processes. The maps only hold onto data as structured clone blobs, and deserialize them each time a key is read. This commit only provides the bare-bones data structures. Follow-ups will add bindings, change events, and automatic flushes. MozReview-Commit-ID: LimwfmFBNOi
dom/base/ProcessGlobal.cpp
dom/base/ProcessGlobal.h
dom/base/nsFrameMessageManager.cpp
dom/base/nsFrameMessageManager.h
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/PContent.ipdl
dom/ipc/SharedMap.cpp
dom/ipc/SharedMap.h
dom/ipc/StructuredCloneData.cpp
dom/ipc/moz.build
js/xpconnect/loader/AutoMemMap.cpp
js/xpconnect/loader/AutoMemMap.h
js/xpconnect/loader/moz.build
--- a/dom/base/ProcessGlobal.cpp
+++ b/dom/base/ProcessGlobal.cpp
@@ -2,34 +2,41 @@
 /* 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 "ProcessGlobal.h"
 
 #include "nsContentCID.h"
+#include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/MessageManagerBinding.h"
 #include "mozilla/dom/ResolveSystemBinding.h"
+#include "mozilla/dom/ipc/SharedMap.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 bool ProcessGlobal::sWasCreated = false;
 
 ProcessGlobal::ProcessGlobal(nsFrameMessageManager* aMessageManager)
  : MessageManagerGlobal(aMessageManager),
    mInitialized(false)
 {
   mozilla::HoldJSObjects(this);
 }
 
 ProcessGlobal::~ProcessGlobal()
 {
   mAnonymousGlobalScopes.Clear();
+  if (ContentChild* child = ContentChild::GetSingleton()) {
+    // Clear this now so we can be sure it's destroyed before cycle collector
+    // shutdown.
+    child->ClearSharedData();
+  }
   mozilla::DropJSObjects(this);
 }
 
 bool
 ProcessGlobal::DoResolve(JSContext* aCx, JS::Handle<JSObject*> aObj,
                          JS::Handle<jsid> aId,
                          JS::MutableHandle<JS::PropertyDescriptor> aDesc)
 {
@@ -67,16 +74,26 @@ ProcessGlobal::Get()
   }
   ProcessGlobal* global = static_cast<ProcessGlobal*>(service.get());
   if (global) {
     sWasCreated = true;
   }
   return global;
 }
 
+already_AddRefed<mozilla::dom::ipc::SharedMap>
+ProcessGlobal::SharedData()
+{
+  if (ContentChild* child = ContentChild::GetSingleton()) {
+    return do_AddRef(child->SharedData());
+  }
+  auto* ppmm = nsFrameMessageManager::sParentProcessManager;
+  return do_AddRef(ppmm->SharedData()->GetReadOnly());
+}
+
 bool
 ProcessGlobal::WasCreated()
 {
   return sWasCreated;
 }
 
 void
 ProcessGlobal::MarkForCC()
--- a/dom/base/ProcessGlobal.h
+++ b/dom/base/ProcessGlobal.h
@@ -20,16 +20,20 @@
 #include "nsIScriptObjectPrincipal.h"
 #include "nsServiceManagerUtils.h"
 #include "nsWeakReference.h"
 #include "nsWrapperCache.h"
 
 namespace mozilla {
 namespace dom {
 
+namespace ipc {
+  class SharedMap;
+}
+
 class ProcessGlobal :
   public nsIMessageSender,
   public nsMessageManagerScriptExecutor,
   public nsIGlobalObject,
   public nsIScriptObjectPrincipal,
   public nsSupportsWeakReference,
   public ipc::MessageManagerCallback,
   public MessageManagerGlobal,
@@ -79,16 +83,18 @@ public:
   {
     if (!mMessageManager) {
       aError.Throw(NS_ERROR_NULL_POINTER);
       return;
     }
     mMessageManager->GetInitialProcessData(aCx, aInitialProcessData, aError);
   }
 
+  already_AddRefed<ipc::SharedMap> SharedData();
+
   NS_FORWARD_SAFE_NSIMESSAGESENDER(mMessageManager)
 
   virtual void LoadScript(const nsAString& aURL);
 
   virtual JSObject* GetGlobalJSObject() override
   {
     return GetWrapper();
   }
--- a/dom/base/nsFrameMessageManager.cpp
+++ b/dom/base/nsFrameMessageManager.cpp
@@ -43,16 +43,17 @@
 #include "mozilla/dom/ParentProcessMessageManager.h"
 #include "mozilla/dom/PermissionMessageUtils.h"
 #include "mozilla/dom/ProcessGlobal.h"
 #include "mozilla/dom/ProcessMessageManager.h"
 #include "mozilla/dom/ResolveSystemBinding.h"
 #include "mozilla/dom/SameProcessMessageQueue.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/ipc/SharedMap.h"
 #include "mozilla/dom/ipc/StructuredCloneData.h"
 #include "mozilla/dom/DOMStringList.h"
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 #include "nsPrintfCString.h"
 #include "nsXULAppAPI.h"
 #include "nsQueryObject.h"
 #include "xpcprivate.h"
 #include <algorithm>
@@ -994,16 +995,29 @@ nsFrameMessageManager::GetInitialProcess
 
   if (!JS_WrapValue(aCx, &init)) {
     aError.NoteJSContextException(aCx);
     return;
   }
   aInitialProcessData.set(init);
 }
 
+WritableSharedMap*
+nsFrameMessageManager::SharedData()
+{
+  if (!mChrome || !mIsProcessManager) {
+    MOZ_ASSERT(false, "Should only call this binding method on ppmm");
+    return nullptr;
+  }
+  if (!mSharedData) {
+    mSharedData = new WritableSharedMap();
+  }
+  return mSharedData;
+}
+
 already_AddRefed<ProcessMessageManager>
 nsFrameMessageManager::GetProcessMessageManager(ErrorResult& aError)
 {
   RefPtr<ProcessMessageManager> pmm;
   if (mCallback) {
     pmm = mCallback->GetProcessMessageManager();
   }
   return pmm.forget();
--- a/dom/base/nsFrameMessageManager.h
+++ b/dom/base/nsFrameMessageManager.h
@@ -32,16 +32,21 @@
 #include "mozilla/dom/CallbackObject.h"
 #include "mozilla/dom/SameProcessMessageQueue.h"
 #include "mozilla/dom/ipc/StructuredCloneData.h"
 #include "mozilla/jsipc/CpowHolder.h"
 
 class nsFrameLoader;
 
 namespace mozilla {
+
+namespace ipc {
+  class FileDescriptor;
+}
+
 namespace dom {
 
 class nsIContentParent;
 class nsIContentChild;
 class ChildProcessMessageManager;
 class ChromeMessageBroadcaster;
 class ClonedMessageData;
 class MessageBroadcaster;
@@ -49,16 +54,18 @@ class MessageListener;
 class MessageListenerManager;
 class MessageManagerReporter;
 template<typename T> class Optional;
 class ParentProcessMessageManager;
 class ProcessMessageManager;
 
 namespace ipc {
 
+class WritableSharedMap;
+
 // Note: we round the time we spend to the nearest millisecond. So a min value
 // of 1 ms actually captures from 500us and above.
 static const uint32_t kMinTelemetrySyncMessageManagerLatencyMs = 1;
 
 enum class MessageManagerFlags {
   MM_NONE = 0,
   MM_CHROME = 1,
   MM_GLOBAL = 2,
@@ -228,16 +235,18 @@ public:
     SendMessage(aCx, aMessageName, aObj, aObjects, aPrincipal, false, aResult, aError);
   }
 
   // GlobalProcessScriptLoader
   void GetInitialProcessData(JSContext* aCx,
                              JS::MutableHandle<JS::Value> aInitialProcessData,
                              mozilla::ErrorResult& aError);
 
+  mozilla::dom::ipc::WritableSharedMap* SharedData();
+
   NS_DECL_NSIMESSAGESENDER
   NS_DECL_NSICONTENTFRAMEMESSAGEMANAGER
 
   static mozilla::dom::ProcessMessageManager* NewProcessMessageManager(bool aIsRemote);
 
   void ReceiveMessage(nsISupports* aTarget, nsFrameLoader* aTargetFrameLoader,
                       const nsAString& aMessage, bool aIsSync,
                       StructuredCloneData* aCloneData, mozilla::jsipc::CpowHolder* aCpows,
@@ -335,16 +344,17 @@ protected:
   bool mHandlingMessage;
   bool mClosed;    // true if we can no longer send messages
   bool mDisconnected;
   mozilla::dom::ipc::MessageManagerCallback* mCallback;
   nsAutoPtr<mozilla::dom::ipc::MessageManagerCallback> mOwnedCallback;
   nsTArray<nsString> mPendingScripts;
   nsTArray<bool> mPendingScriptsGlobalStates;
   JS::Heap<JS::Value> mInitialProcessData;
+  RefPtr<mozilla::dom::ipc::WritableSharedMap> mSharedData;
 
   void LoadPendingScripts(nsFrameMessageManager* aManager,
                           nsFrameMessageManager* aChildMM);
 public:
   static mozilla::dom::ParentProcessMessageManager* sParentProcessManager;
   static nsFrameMessageManager* sSameProcessParentManager;
   static nsTArray<nsCOMPtr<nsIRunnable> >* sPendingSameProcessAsyncMessages;
 private:
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -43,16 +43,17 @@
 #include "mozilla/dom/ProcessGlobal.h"
 #include "mozilla/dom/PushNotifier.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"
 #include "mozilla/dom/WorkerDebuggerManager.h"
+#include "mozilla/dom/ipc/SharedMap.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/gfx/Logging.h"
 #include "mozilla/psm/PSMContentListener.h"
 #include "mozilla/hal_sandbox/PHalChild.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/FileDescriptorSetChild.h"
 #include "mozilla/ipc/FileDescriptorUtils.h"
 #include "mozilla/ipc/GeckoChildProcessHost.h"
@@ -584,31 +585,42 @@ NS_INTERFACE_MAP_BEGIN(ContentChild)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentChild)
 NS_INTERFACE_MAP_END
 
 
 mozilla::ipc::IPCResult
 ContentChild::RecvSetXPCOMProcessAttributes(const XPCOMInitData& aXPCOMInit,
                                             const StructuredCloneData& aInitialData,
                                             nsTArray<LookAndFeelInt>&& aLookAndFeelIntCache,
-                                            nsTArray<SystemFontListEntry>&& aFontList)
+                                            nsTArray<SystemFontListEntry>&& aFontList,
+                                            const FileDescriptor& aSharedDataMapFile,
+                                            const uint32_t& aSharedDataMapSize)
 {
   if (!sShutdownCanary) {
     return IPC_OK();
   }
 
   mLookAndFeelCache = std::move(aLookAndFeelIntCache);
   mFontList = std::move(aFontList);
   gfx::gfxVars::SetValuesForInitialize(aXPCOMInit.gfxNonDefaultVarUpdates());
   InitXPCOM(aXPCOMInit, aInitialData);
   InitGraphicsDeviceData(aXPCOMInit.contentDeviceData());
 
+  mSharedData = new SharedMap(ProcessGlobal::Get(), aSharedDataMapFile,
+                              aSharedDataMapSize);
+
   return IPC_OK();
 }
 
+void
+ContentChild::ClearSharedData()
+{
+  mSharedData = nullptr;
+}
+
 bool
 ContentChild::Init(MessageLoop* aIOLoop,
                    base::ProcessId aParentPid,
                    const char* aParentBuildID,
                    IPC::Channel* aChannel,
                    uint64_t aChildID,
                    bool aIsForBrowser)
 {
@@ -2532,16 +2544,28 @@ ContentChild::RecvAsyncMessage(const nsS
     ipc::UnpackClonedMessageDataForChild(aData, data);
     cpm->ReceiveMessage(cpm, nullptr, aMsg, false, &data, &cpows, aPrincipal, nullptr,
                         IgnoreErrors());
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
+ContentChild::RecvUpdateSharedData(const FileDescriptor& aMapFile,
+                                   const uint32_t& aMapSize,
+                                   nsTArray<nsCString>&& aChangedKeys)
+{
+  if (mSharedData) {
+    mSharedData->Update(aMapFile, aMapSize, std::move(aChangedKeys));
+  }
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
 ContentChild::RecvGeolocationUpdate(nsIDOMGeoPosition* aPosition)
 {
   nsCOMPtr<nsIGeolocationUpdate> gs =
     do_GetService("@mozilla.org/geolocation/service;1");
   if (!gs) {
     return IPC_OK();
   }
   gs->Update(aPosition);
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -61,16 +61,20 @@ nsresult GetObjDir(nsIFile **aObjDir);
 
 namespace ipc {
 class OptionalURIParams;
 class URIParams;
 }// namespace ipc
 
 namespace dom {
 
+namespace ipc {
+class SharedMap;
+}
+
 class AlertObserver;
 class ConsoleListener;
 class ClonedMessageData;
 class TabChild;
 class GetFilesHelperChild;
 class FileCreatorHelper;
 
 class ContentChild final : public PContentChild
@@ -159,16 +163,20 @@ public:
     mProfileDir = aProfileDir;
   }
 #endif
 
   bool IsAlive() const;
 
   bool IsShuttingDown() const;
 
+  ipc::SharedMap* SharedData() { return mSharedData; };
+
+  void ClearSharedData();
+
   static void AppendProcessId(nsACString& aName);
 
   static void UpdateCookieStatus(nsIChannel *aChannel);
 
   mozilla::ipc::IPCResult
   RecvInitContentBridgeChild(Endpoint<PContentBridgeChild>&& aEndpoint) override;
 
   mozilla::ipc::IPCResult
@@ -389,16 +397,20 @@ public:
 
   virtual mozilla::ipc::IPCResult RecvLoadProcessScript(const nsString& aURL) override;
 
   virtual mozilla::ipc::IPCResult RecvAsyncMessage(const nsString& aMsg,
                                                    InfallibleTArray<CpowEntry>&& aCpows,
                                                    const IPC::Principal& aPrincipal,
                                                    const ClonedMessageData& aData) override;
 
+  mozilla::ipc::IPCResult RecvUpdateSharedData(const FileDescriptor& aMapFile,
+                                               const uint32_t& aMapSize,
+                                               nsTArray<nsCString>&& aChangedKeys) override;
+
   virtual mozilla::ipc::IPCResult RecvGeolocationUpdate(nsIDOMGeoPosition* aPosition) override;
 
   virtual mozilla::ipc::IPCResult RecvGeolocationError(const uint16_t& errorCode) override;
 
   virtual mozilla::ipc::IPCResult RecvUpdateDictionaryList(InfallibleTArray<nsString>&& aDictionaries) override;
 
   virtual mozilla::ipc::IPCResult RecvUpdateFontList(InfallibleTArray<SystemFontListEntry>&& aFontList) override;
 
@@ -606,17 +618,19 @@ public:
           const bool& anonymize,
           const bool& minimizeMemoryUsage,
           const MaybeFileDesc& DMDFile) override;
 
   virtual mozilla::ipc::IPCResult
   RecvSetXPCOMProcessAttributes(const XPCOMInitData& aXPCOMInit,
                                 const StructuredCloneData& aInitialData,
                                 nsTArray<LookAndFeelInt>&& aLookAndFeelIntCache,
-                                nsTArray<SystemFontListEntry>&& aFontList) override;
+                                nsTArray<SystemFontListEntry>&& aFontList,
+                                const FileDescriptor& aSharedDataMapFile,
+                                const uint32_t& aSharedDataMapSize) override;
 
   virtual mozilla::ipc::IPCResult
   RecvProvideAnonymousTemporaryFile(const uint64_t& aID, const FileDescOrError& aFD) override;
 
   mozilla::ipc::IPCResult
   RecvSetPermissionsWithKey(const nsCString& aPermissionKey,
                             nsTArray<IPC::Permission>&& aPerms) override;
 
@@ -806,16 +820,18 @@ private:
   static ContentChild* sSingleton;
 
   class ShutdownCanary;
   static StaticAutoPtr<ShutdownCanary> sShutdownCanary;
 
   nsCOMPtr<nsIDomainPolicy> mPolicy;
   nsCOMPtr<nsITimer> mForceKillTimer;
 
+  RefPtr<ipc::SharedMap> mSharedData;
+
 #ifdef MOZ_GECKO_PROFILER
   RefPtr<ChildProfilerController> mProfilerController;
 #endif
 
 #if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
   nsCOMPtr<nsIFile> mProfileDir;
 #endif
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -55,16 +55,17 @@
 #include "mozilla/dom/ServiceWorkerRegistrar.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/quota/QuotaManagerService.h"
 #include "mozilla/dom/URLClassifierParent.h"
+#include "mozilla/dom/ipc/SharedMap.h"
 #include "mozilla/embedding/printingui/PrintingParent.h"
 #include "mozilla/extensions/StreamFilterParent.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/gfx/GPUProcessManager.h"
 #include "mozilla/hal_sandbox/PHalParent.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/CrashReporterHost.h"
@@ -2304,18 +2305,22 @@ ContentParent::InitInternal(ProcessPrior
 
   // Send the dynamic scalar definitions to the new process.
   TelemetryIPC::GetDynamicScalarDefinitions(xpcomInit.dynamicScalarDefs());
 
   // Must send screen info before send initialData
   ScreenManager& screenManager = ScreenManager::GetSingleton();
   screenManager.CopyScreensToRemote(this);
 
+  ipc::WritableSharedMap* sharedData = nsFrameMessageManager::sParentProcessManager->SharedData();
+  sharedData->Flush();
+
   Unused << SendSetXPCOMProcessAttributes(xpcomInit, initialData, lnfCache,
-                                          fontList);
+                                          fontList, sharedData->CloneMapFile(),
+                                          sharedData->MapSize());
 
   nsCOMPtr<nsIChromeRegistry> registrySvc = nsChromeRegistry::GetService();
   nsChromeRegistryChrome* chromeRegistry =
     static_cast<nsChromeRegistryChrome*>(registrySvc.get());
   chromeRegistry->SendRegisteredChrome(this);
 
   if (gAppData) {
     nsCString version(gAppData->version);
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -450,18 +450,22 @@ child:
 
     async UpdateDictionaryList(nsString[] dictionaries);
 
     async UpdateFontList(SystemFontListEntry[] fontList);
 
     async UpdateAppLocales(nsCString[] appLocales);
     async UpdateRequestedLocales(nsCString[] requestedLocales);
 
+
     async ClearSiteDataReloadNeeded(nsString origin);
 
+    async UpdateSharedData(FileDescriptor mapFile, uint32_t aSize,
+                           nsCString[] changedKeys);
+
     // nsIPermissionManager messages
     async AddPermission(Permission permission);
 
     async FlushMemory(nsString reason);
 
     async GarbageCollect();
     async CycleCollect();
     async UnlinkGhosts();
@@ -498,17 +502,19 @@ child:
      * Send BlobURLRegistrationData to child process.
      */
     async InitBlobURLs(BlobURLRegistrationData[] registrations);
 
     async SetXPCOMProcessAttributes(XPCOMInitData xpcomInit,
                                     StructuredCloneData initialData,
                                     LookAndFeelInt[] lookAndFeelIntCache,
                                     /* used on MacOSX and Linux only: */
-                                    SystemFontListEntry[] systemFontList);
+                                    SystemFontListEntry[] systemFontList,
+                                    FileDescriptor sharedDataMapFile,
+                                    uint32_t sharedDataMapSize);
 
     // Notify child that last-pb-context-exited notification was observed
     async LastPrivateDocShellDestroyed();
 
     async NotifyProcessPriorityChanged(ProcessPriority priority);
     async MinimizeMemoryUsage();
 
     /**
new file mode 100644
--- /dev/null
+++ b/dom/ipc/SharedMap.cpp
@@ -0,0 +1,368 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=99: */
+/* 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 "SharedMap.h"
+
+#include "MemMapSnapshot.h"
+#include "ScriptPreloader-inl.h"
+
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ProcessGlobal.h"
+
+using namespace mozilla::loader;
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+namespace ipc {
+
+// Align to size of uintptr_t here, to be safe. It's probably not strictly
+// necessary, though.
+constexpr size_t kStructuredCloneAlign = sizeof(uintptr_t);
+
+
+static inline void
+AlignTo(size_t* aOffset, size_t aAlign)
+{
+  if (auto mod = *aOffset % aAlign) {
+    *aOffset += aAlign - mod;
+  }
+}
+
+
+SharedMap::SharedMap()
+{}
+
+SharedMap::SharedMap(nsIGlobalObject* aGlobal, const FileDescriptor& aMapFile,
+                     size_t aMapSize)
+{
+  mMapFile.reset(new FileDescriptor(aMapFile));
+  mMapSize = aMapSize;
+}
+
+
+bool
+SharedMap::Has(const nsACString& aName)
+{
+  return mEntries.Contains(aName);
+}
+
+void
+SharedMap::Get(JSContext* aCx,
+               const nsACString& aName,
+               JS::MutableHandleValue aRetVal,
+               ErrorResult& aRv)
+{
+  auto res = MaybeRebuild();
+  if (res.isErr()) {
+    aRv.Throw(res.unwrapErr());
+    return;
+  }
+
+  Entry* entry = mEntries.Get(aName);
+  if (!entry) {
+    aRetVal.setNull();
+    return;
+  }
+
+  entry->Read(aCx, aRetVal, aRv);
+}
+
+void
+SharedMap::Entry::Read(JSContext* aCx,
+                       JS::MutableHandleValue aRetVal,
+                       ErrorResult& aRv)
+{
+  if (mData.is<StructuredCloneData>()) {
+    // We have a temporary buffer for a key that was changed after the last
+    // snapshot. Just decode it directly.
+    auto& holder = mData.as<StructuredCloneData>();
+    holder.Read(aCx, aRetVal, aRv);
+    return;
+  }
+
+  // We have a pointer to a shared memory region containing our structured
+  // clone data. Create a temporary buffer to decode that data, and then
+  // discard it so that we don't keep a separate process-local copy around any
+  // longer than necessary.
+  StructuredCloneData holder;
+  if (!holder.CopyExternalData(Data(), Size())) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+  }
+
+  holder.Read(aCx, aRetVal, aRv);
+}
+
+FileDescriptor
+SharedMap::CloneMapFile()
+{
+  if (mMap.initialized()) {
+    return mMap.cloneHandle();
+  }
+  return *mMapFile;
+}
+
+void
+SharedMap::Update(const FileDescriptor& aMapFile, size_t aMapSize,
+                  nsTArray<nsCString>&& aChangedKeys)
+{
+  MOZ_DIAGNOSTIC_ASSERT(!mWritable);
+
+  mMap.reset();
+  if (mMapFile) {
+    *mMapFile = aMapFile;
+  } else {
+    mMapFile.reset(new FileDescriptor(aMapFile));
+  }
+  mMapSize = aMapSize;
+  mEntries.Clear();
+}
+
+void
+SharedMap::Entry::TakeData(StructuredCloneData&& aHolder)
+{
+  mData = AsVariant(std::move(aHolder));
+
+  mSize = Holder().Data().Size();
+}
+
+void
+SharedMap::Entry::ExtractData(char* aDestPtr, uint32_t aNewOffset)
+{
+  if (mData.is<StructuredCloneData>()) {
+    char* ptr = aDestPtr;
+    Holder().Data().ForEachDataChunk([&](const char* aData, size_t aSize) {
+        memcpy(ptr, aData, aSize);
+        ptr += aSize;
+        return true;
+    });
+    MOZ_ASSERT(ptr - aDestPtr == mSize);
+  } else {
+    memcpy(aDestPtr, Data(), mSize);
+  }
+
+  mData = AsVariant(aNewOffset);
+}
+
+Result<Ok, nsresult>
+SharedMap::MaybeRebuild()
+{
+  if (!mMapFile) {
+    return Ok();
+  }
+
+  // This function maps a shared memory region created by Serialize() and reads
+  // its header block to build a new mEntries hashtable of its contents.
+  //
+  // The entries created by this function contain a pointer to this SharedMap
+  // instance, and the offsets and sizes of their structured clone data within
+  // its shared memory region. When needed, that structured clone data is
+  // retrieved directly as indexes into the SharedMap's shared memory region.
+
+  MOZ_TRY(mMap.init(*mMapFile, mMapSize));
+  mMapFile.reset();
+
+  // We should be able to pass this range as an initializer list or an immediate
+  // param, but gcc currently chokes on that if optimization is enabled, and
+  // initializes everything to 0.
+  Range<uint8_t> range(&mMap.get<uint8_t>()[0], mMap.size());
+  InputBuffer buffer(range);
+
+  uint32_t count;
+  buffer.codeUint32(count);
+
+  for (uint32_t i = 0; i < count; i++) {
+    auto entry = MakeUnique<Entry>(*this);
+    entry->Code(buffer);
+
+    // This buffer was created at runtime, during this session, so any errors
+    // indicate memory corruption, and are fatal.
+    MOZ_RELEASE_ASSERT(!buffer.error());
+
+    // Note: Order of evaluation of function arguments is not guaranteed, so we
+    // can't use entry.release() in place of entry.get() without entry->Name()
+    // sometimes resulting in a null dereference.
+    mEntries.Put(entry->Name(), entry.get());
+    Unused << entry.release();
+  }
+
+  return Ok();
+}
+
+void
+SharedMap::MaybeRebuild() const
+{
+  Unused << const_cast<SharedMap*>(this)->MaybeRebuild();
+}
+
+WritableSharedMap::WritableSharedMap()
+  : SharedMap()
+{
+  mWritable = true;
+  // Serialize the initial empty contents of the map immediately so that we
+  // always have a file descriptor to send to callers of CloneMapFile().
+  Unused << Serialize();
+}
+
+SharedMap*
+WritableSharedMap::GetReadOnly()
+{
+  if (!mReadOnly) {
+    mReadOnly = new SharedMap(ProcessGlobal::Get(), CloneMapFile(),
+                              MapSize());
+  }
+  return mReadOnly;
+}
+
+Result<Ok, nsresult>
+WritableSharedMap::Serialize()
+{
+  // Serializes a new snapshot of the map, initializes a new read-only shared
+  // memory region with its contents, and updates all entries to point to that
+  // new snapshot.
+  //
+  // The layout of the snapshot is as follows:
+  //
+  // - A header containing a uint32 count field containing the number of
+  //   entries in the map, followed by that number of serialized entries, as
+  //   produced by Entry::Code.
+  //
+  // - A data block containing structured clone data for each of the entries'
+  //   values. This data is referenced by absolute byte offsets from the start
+  //   of the shared memory region, encoded in each of the entry header values.
+  //
+  // This serialization format is decoded by the MaybeRebuild() method of
+  // read-only SharedMap() instances, and used to populate their mEntries
+  // hashtables.
+  //
+  // Writable instances never read the header blocks, but instead directly
+  // update their Entry instances to point to the appropriate offsets in the
+  // shared memory region created by this function.
+
+  uint32_t count = mEntries.Count();
+
+  size_t dataSize = 0;
+  size_t headerSize = sizeof(count);
+
+  for (auto& entry : IterHash(mEntries)) {
+    headerSize += entry->HeaderSize();
+
+    dataSize += entry->Size();
+    AlignTo(&dataSize, kStructuredCloneAlign);
+  }
+
+  size_t offset = headerSize;
+  AlignTo(&offset, kStructuredCloneAlign);
+
+  OutputBuffer header;
+  header.codeUint32(count);
+
+  MemMapSnapshot mem;
+  MOZ_TRY(mem.Init(offset + dataSize));
+
+  auto ptr = mem.Get<char>();
+
+  for (auto& entry : IterHash(mEntries)) {
+    AlignTo(&offset, kStructuredCloneAlign);
+
+    entry->ExtractData(&ptr[offset], offset);
+    entry->Code(header);
+
+    offset += entry->Size();
+  }
+
+  // FIXME: We should create a separate OutputBuffer class which can encode to
+  // a static memory region rather than dynamically allocating and then
+  // copying.
+  memcpy(ptr.get(), header.Get(), header.cursor());
+
+  // We've already updated offsets at this point. We need this to succeed.
+  mMap.reset();
+  MOZ_RELEASE_ASSERT(mem.Finalize(mMap).isOk());
+
+  return Ok();
+}
+
+void
+WritableSharedMap::BroadcastChanges()
+{
+  if (mChangedKeys.IsEmpty()) {
+    return;
+  }
+
+  if (!Serialize().isOk()) {
+    return;
+  }
+
+  nsTArray<ContentParent*> parents;
+  ContentParent::GetAll(parents);
+  for (auto& parent : parents) {
+    Unused << parent->SendUpdateSharedData(CloneMapFile(), mMap.size(),
+                                           mChangedKeys);
+  }
+
+  if (mReadOnly) {
+    mReadOnly->Update(CloneMapFile(), mMap.size(),
+                      std::move(mChangedKeys));
+  }
+
+  mChangedKeys.Clear();
+}
+
+void
+WritableSharedMap::Delete(const nsACString& aName)
+{
+  if (mEntries.Remove(aName)) {
+    KeyChanged(aName);
+  }
+}
+
+void
+WritableSharedMap::Set(JSContext* aCx,
+                       const nsACString& aName,
+                       JS::HandleValue aValue,
+                       ErrorResult& aRv)
+{
+  StructuredCloneData holder;
+
+  holder.Write(aCx, aValue, aRv);
+  if (aRv.Failed()) {
+    return;
+  }
+
+  if (!holder.BlobImpls().IsEmpty() ||
+      !holder.InputStreams().IsEmpty()) {
+    aRv.Throw(NS_ERROR_INVALID_ARG);
+    return;
+  }
+
+  Entry* entry = mEntries.LookupOrAdd(aName, *this, aName);
+  entry->TakeData(std::move(holder));
+
+  KeyChanged(aName);
+}
+
+void
+WritableSharedMap::Flush()
+{
+  BroadcastChanges();
+}
+
+void
+WritableSharedMap::KeyChanged(const nsACString& aName)
+{
+  if (!mChangedKeys.ContainsSorted(aName)) {
+    mChangedKeys.InsertElementSorted(aName);
+  }
+}
+
+NS_IMPL_ISUPPORTS0(SharedMap)
+
+} // ipc
+} // dom
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/dom/ipc/SharedMap.h
@@ -0,0 +1,305 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=99: */
+/* 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 dom_ipc_SharedMap_h
+#define dom_ipc_SharedMap_h
+
+#include "mozilla/AutoMemMap.h"
+#include "mozilla/dom/ipc/StructuredCloneData.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Variant.h"
+#include "nsClassHashtable.h"
+#include "nsTArray.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+namespace dom {
+namespace ipc {
+
+/**
+ * Together, the SharedMap and WritableSharedMap classes allow sharing a
+ * dynamically-updated, shared-memory key-value store across processes.
+ *
+ * The maps may only ever be updated in the parent process, via
+ * WritableSharedMap instances. When that map changes, its entire contents are
+ * serialized into a contiguous shared memory buffer, and broadcast to all child
+ * processes, which in turn update their entire map contents wholesale.
+ *
+ * Keys are arbitrary UTF-8 strings (currently exposed to JavaScript as UTF-16),
+ * and values are structured clone buffers. Values are eagerly encoded whenever
+ * they are updated, and lazily decoded each time they're read.
+ *
+ * Updates are batched. Rather than each key change triggering an immediate
+ * update, combined updates are broadcast after a delay. Currently, this
+ * requires an explicit flush() call, or spawning a new content process. In the
+ * future, it will happen automatically in idle tasks.
+ *
+ *
+ * Whenever a read-only SharedMap is updated, it dispatches a "change" event.
+ * The event contains a "changedKeys" property with a list of all keys which
+ * were changed in the last update batch. Change events are never dispatched to
+ * WritableSharedMap instances.
+ */
+class SharedMap : public nsISupports
+{
+  using FileDescriptor = mozilla::ipc::FileDescriptor;
+
+public:
+  NS_DECL_ISUPPORTS
+
+  SharedMap();
+
+  SharedMap(nsIGlobalObject* aGlobal, const FileDescriptor&, size_t);
+
+  // Returns true if the map contains the given (UTF-8) key.
+  bool Has(const nsACString& name);
+
+  // If the map contains the given (UTF-8) key, decodes and returns a new copy
+  // of its value. Otherwise returns null.
+  void Get(JSContext* cx, const nsACString& name, JS::MutableHandleValue aRetVal,
+           ErrorResult& aRv);
+
+
+  /**
+   * Returns a copy of the read-only file descriptor which backs the shared
+   * memory region for this map. The file descriptor may be passed between
+   * processes, and used to update corresponding instances in child processes.
+   */
+  FileDescriptor CloneMapFile();
+
+  /**
+   * Returns the size of the memory mapped region that backs this map. Must be
+   * passed to the SharedMap() constructor or Update() method along with the
+   * descriptor returned by CloneMapFile() in order to initialize or update a
+   * child SharedMap.
+   */
+  size_t MapSize() const { return mMap.size(); }
+
+  /**
+   * Updates this instance to reflect the contents of the shared memory region
+   * in the given map file, and broadcasts a change event for the given set of
+   * changed (UTF-8-encoded) keys.
+   */
+  void Update(const FileDescriptor& aMapFile, size_t aMapSize,
+              nsTArray<nsCString>&& aChangedKeys);
+
+
+protected:
+  virtual ~SharedMap() = default;
+
+  class Entry
+  {
+  public:
+    Entry(Entry&&) = delete;
+
+    explicit Entry(SharedMap& aMap, const nsACString& aName = EmptyCString())
+      : mMap(aMap)
+      , mName(aName)
+      , mData(AsVariant(uint32_t(0)))
+    {
+    }
+
+    ~Entry() = default;
+
+    /**
+     * Encodes or decodes this entry into or from the given buffer.
+     */
+    template<typename Buffer>
+    void Code(Buffer& buffer)
+    {
+      DebugOnly<size_t> startOffset = buffer.cursor();
+
+      buffer.codeString(mName);
+      buffer.codeUint32(DataOffset());
+      buffer.codeUint32(mSize);
+
+      MOZ_ASSERT(buffer.cursor() == startOffset + HeaderSize());
+    }
+
+    /**
+     * Returns the size that this entry will take up in the map header. This
+     * must be equal to the number of bytes encoded by Code().
+     */
+    size_t HeaderSize() const
+    {
+      return (sizeof(uint16_t) + mName.Length() +
+              sizeof(DataOffset()) +
+              sizeof(mSize));
+    }
+
+    /**
+     * Updates the value of this entry to the given structured clone data, of
+     * which it takes ownership. The passed StructuredCloneData object may not
+     * be used after this call.
+     */
+    void TakeData(StructuredCloneData&&);
+
+    /**
+     * This is called while building a new snapshot of the SharedMap. aDestPtr
+     * must point to a buffer within the new snapshot with Size() bytes reserved
+     * for it, and `aNewOffset` must be the offset of that buffer from the start
+     * of the snapshot's memory region.
+     *
+     * This function copies the raw structured clone data for the entry's value
+     * to the new buffer, and updates its internal state for use with the new
+     * data. Its offset is updated to aNewOffset, and any StructuredCloneData
+     * object it holds is destroyed.
+     *
+     * After this call, the entry is only valid in reference to the new
+     * snapshot, and may not be accessed again until the SharedMap mMap has been
+     * updated to point to it.
+     */
+    void ExtractData(char* aDestPtr, uint32_t aNewOffset);
+
+    // Returns the UTF-8-encoded name of the entry, which is used as its key in
+    // the map.
+    const nsCString& Name() const { return mName; }
+
+    // Decodes the entry's value into the current Realm of the given JS context
+    // and puts the result in aRetVal on success.
+    void Read(JSContext* aCx, JS::MutableHandleValue aRetVal,
+              ErrorResult& aRv);
+
+    // Returns the byte size size of the entry's raw structured clone data.
+    uint32_t Size() const { return mSize; }
+
+  private:
+    // Returns a pointer to the entry value's structured clone data within the
+    // SharedMap's mapped memory region. This is *only* valid shen mData
+    // contains a uint32_t.
+    const char* Data() const
+    {
+      return mMap.Data() + DataOffset();
+    }
+
+    // Returns the offset of the entry value's structured clone data within the
+    // SharedMap's mapped memory region. This is *only* valid shen mData
+    // contains a uint32_t.
+    uint32_t& DataOffset()
+    {
+      return mData.as<uint32_t>();
+    }
+    const uint32_t& DataOffset() const
+    {
+      return mData.as<uint32_t>();
+    }
+
+    // Returns the temporary StructuredCloneData object containing the entry's
+    // value. This is *only* value when mData contains a StructuredCloneDAta
+    // object.
+    const StructuredCloneData& Holder() const
+    {
+      return mData.as<StructuredCloneData>();
+    }
+
+    SharedMap& mMap;
+
+    // The entry's (UTF-8 encoded) name, which serves as its key in the map.
+    nsCString mName;
+
+    /**
+     * This member provides a reference to the entry's structured clone data.
+     * Its type varies depending on the state of the entry:
+     *
+     * - For entries which have been snapshotted into a shared memory region,
+     *   this is a uint32_t offset into the parent SharedMap's Data() buffer.
+     *
+     * - For entries which have been changed in a WritableSharedMap instance,
+     *   but not serialized to a shared memory snapshot yet, this is a
+     *   StructuredCloneData instance, containing a process-local copy of the
+     *   data. This will be discarded the next time the map is serialized, and
+     *   replaced with a buffer offset, as described above.
+     */
+    Variant<uint32_t, StructuredCloneData> mData;
+
+    // The size, in bytes, of the entry's structured clone data.
+    uint32_t mSize = 0;
+  };
+
+  // Rebuilds the entry hashtable mEntries from the values serialized in the
+  // current snapshot, if necessary. The hashtable is rebuilt lazily after
+  // construction and after every Update() call, so this function must be called
+  // before any attempt to access mEntries.
+  Result<Ok, nsresult> MaybeRebuild();
+  void MaybeRebuild() const;
+
+  // Note: This header is included by WebIDL binding headers, and therefore
+  // can't include "windows.h". Since FileDescriptor.h does include "windows.h"
+  // on Windows, we can only forward declare FileDescriptor, and can't include
+  // it as an inline member.
+  UniquePtr<FileDescriptor> mMapFile;
+  // The size of the memory-mapped region backed by mMapFile, in bytes.
+  size_t mMapSize = 0;
+
+  mutable nsClassHashtable<nsCStringHashKey, Entry> mEntries;
+
+  // Manages the memory mapping of the current snapshot. This is initialized
+  // lazily after each SharedMap construction or updated, based on the values in
+  // mMapFile and mMapSize.
+  loader::AutoMemMap mMap;
+
+  bool mWritable = false;
+
+  // Returns a pointer to the beginning of the memory mapped snapshot. Entry
+  // offsets are relative to this pointer, and Entry objects access their
+  // structured clone data by indexing this pointer.
+  char* Data() { return mMap.get<char>().get(); }
+};
+
+class WritableSharedMap final : public SharedMap
+{
+public:
+  WritableSharedMap();
+
+  // Sets the value of the given (UTF-8 encoded) key to a structured clone
+  // snapshot of the given value.
+  void Set(JSContext* cx, const nsACString& name, JS::HandleValue value, ErrorResult& aRv);
+
+  // Deletes the given (UTF-8 encoded) key from the map.
+  void Delete(const nsACString& name);
+
+  // Flushes any queued changes to a new snapshot, and broadcasts it to all
+  // child SharedMap instances.
+  void Flush();
+
+
+  /**
+   * Returns the read-only SharedMap instance corresponding to this
+   * WritableSharedMap for use in the parent process.
+   */
+  SharedMap* GetReadOnly();
+
+protected:
+  ~WritableSharedMap() override = default;
+
+private:
+  // The set of (UTF-8 encoded) keys which have changed, or been deleted, since
+  // the last snapshot.
+  nsTArray<nsCString> mChangedKeys;
+
+  RefPtr<SharedMap> mReadOnly;
+
+  // Creates a new snapshot of the map, and updates all Entry instance to
+  // reference its data.
+  Result<Ok, nsresult> Serialize();
+
+  // If there have been any changes since the last snapshot, creates a new
+  // serialization and broadcasts it to all child SharedMap instances.
+  void BroadcastChanges();
+
+  // Marks the given (UTF-8 encoded) key as having changed. This adds it to
+  // mChangedKeys, if not already present. In the future, it will also schedule
+  // a flush the next time the event loop is idle.
+  void KeyChanged(const nsACString& aName);
+};
+
+} // ipc
+} // dom
+} // mozilla
+
+#endif // dom_ipc_SharedMap_h
--- a/dom/ipc/StructuredCloneData.cpp
+++ b/dom/ipc/StructuredCloneData.cpp
@@ -7,29 +7,35 @@
 #include "StructuredCloneData.h"
 
 #include "nsIMutable.h"
 #include "nsIXPConnect.h"
 
 #include "ipc/IPCMessageUtils.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/BlobBinding.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/dom/File.h"
 #include "mozilla/dom/IPCBlobUtils.h"
-#include "mozilla/dom/File.h"
 #include "mozilla/ipc/IPCStreamUtils.h"
 #include "nsContentUtils.h"
 #include "nsJSEnvironment.h"
 #include "MainThreadUtils.h"
 #include "StructuredCloneTags.h"
 #include "jsapi.h"
 
 namespace mozilla {
 namespace dom {
 namespace ipc {
 
+using mozilla::ipc::AutoIPCStream;
+using mozilla::ipc::IPCStream;
+using mozilla::ipc::PBackgroundChild;
+using mozilla::ipc::PBackgroundParent;
+
 StructuredCloneData::StructuredCloneData()
   : StructuredCloneData(StructuredCloneHolder::TransferringSupported)
 {}
 
 StructuredCloneData::StructuredCloneData(StructuredCloneData&& aOther)
   : StructuredCloneData(StructuredCloneHolder::TransferringSupported)
 {
   *this = std::move(aOther);
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -10,16 +10,17 @@ with Files("**"):
 XPIDL_SOURCES += [
     'nsIHangReport.idl',
 ]
 
 XPIDL_MODULE = 'dom'
 
 EXPORTS.mozilla.dom.ipc += [
     'IdType.h',
+    'SharedMap.h',
     'StructuredCloneData.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'CoalescedInputData.h',
     'CoalescedMouseData.h',
     'CoalescedWheelData.h',
     'ContentBridgeChild.h',
@@ -63,16 +64,17 @@ UNIFIED_SOURCES += [
     'FilePickerParent.cpp',
     'MemMapSnapshot.cpp',
     'MemoryReportRequest.cpp',
     'nsIContentChild.cpp',
     'nsIContentParent.cpp',
     'PermissionMessageUtils.cpp',
     'PreallocatedProcessManager.cpp',
     'ProcessPriorityManager.cpp',
+    'SharedMap.cpp',
     'StructuredCloneData.cpp',
     'TabChild.cpp',
     'TabContext.cpp',
     'TabMessageUtils.cpp',
     'TabParent.cpp',
     'URLClassifierParent.cpp',
 ]
 
--- a/js/xpconnect/loader/AutoMemMap.cpp
+++ b/js/xpconnect/loader/AutoMemMap.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AutoMemMap.h"
 #include "ScriptPreloader-inl.h"
 
 #include "mozilla/Unused.h"
+#include "mozilla/ipc/FileDescriptor.h"
 #include "nsIFile.h"
 
 #include <private/pprio.h>
 
 namespace mozilla {
 namespace loader {
 
 using namespace mozilla::ipc;
--- a/js/xpconnect/loader/AutoMemMap.h
+++ b/js/xpconnect/loader/AutoMemMap.h
@@ -5,24 +5,27 @@
 
 #ifndef loader_AutoMemMap_h
 #define loader_AutoMemMap_h
 
 #include "mozilla/FileUtils.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/RangedPtr.h"
 #include "mozilla/Result.h"
-#include "mozilla/ipc/FileDescriptor.h"
 #include "nsIMemoryReporter.h"
 
 #include <prio.h>
 
 class nsIFile;
 
 namespace mozilla {
+namespace ipc {
+  class FileDescriptor;
+}
+
 namespace loader {
 
 using mozilla::ipc::FileDescriptor;
 
 class AutoMemMap
 {
     public:
         AutoMemMap() = default;
--- a/js/xpconnect/loader/moz.build
+++ b/js/xpconnect/loader/moz.build
@@ -20,16 +20,17 @@ SOURCES += [
     'mozJSComponentLoader.cpp'
 ]
 
 IPDL_SOURCES += [
     'PScriptCache.ipdl',
 ]
 
 EXPORTS.mozilla += [
+    'AutoMemMap.h',
     'ScriptPreloader.h',
     'URLPreloader.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'PrecompiledScript.h',
 ]