Bug 1463587: Part 3 - Add bindings for SharedMap, and expose it via process message managers. r=erahm,baku,bz
☠☠ backed out by 6085b77ada27 ☠ ☠
authorKris Maglione <maglione.k@gmail.com>
Fri, 29 Jun 2018 14:55:27 -0700
changeset 481426 2ebaf5f8c6055b11b11d7ec334d54ee941115d48
parent 481425 c27295337b4c16e2a178106a3aa873d2a0e5a1f4
child 481427 cebf1f055d1dfb505e96cebf7e4284b35a419dd6
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerserahm, baku, bz
bugs1463587
milestone63.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 1463587: Part 3 - Add bindings for SharedMap, and expose it via process message managers. r=erahm,baku,bz This is the first basic implementation of a shared-memory key-value store for JS message managers. It has one read-write endpoint in the parent process, and separate read-only endpoints for each child-process message manager. Changes to the parent endpoint are broadcast to the children as snapshots. Each snapshot triggers a "change" event with a list of changed keys. It currently has the following limitations: - It only supports basic structured clone data. There's no support for blobs, input streams, message ports... Blob support will be added in a follow-up patch. - Changes are currently only broadcast to child endpoints when flush() is explicitly called in the parent, or when new child processes are launched. In a follow-up, this will be changed to automatically flush after changes when the event loop is idle. - All set operations clone their inputs synchronously, which means that there's no trivial way for callers to batch multiple changes to a single key without some additional effort. It might be useful to add a delayed-serialization option to the .set() call in a follow-up, for callers who are sure they know what they're doing. MozReview-Commit-ID: IM8a3UgejXU
dom/bindings/Bindings.conf
dom/chrome-webidl/MessageManager.webidl
dom/chrome-webidl/MozSharedMap.webidl
dom/chrome-webidl/moz.build
dom/ipc/SharedMap.cpp
dom/ipc/SharedMap.h
dom/ipc/SharedMapChangeEvent.h
dom/ipc/moz.build
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -572,16 +572,29 @@ DOMInterfaces = {
     'nativeType': 'mozilla::dom::HTMLCanvasPrintState',
 },
 
 'MozChannel': {
     'nativeType': 'nsIChannel',
     'notflattened': True
 },
 
+'MozSharedMap': {
+    'nativeType': 'mozilla::dom::ipc::SharedMap',
+},
+
+'MozWritableSharedMap': {
+    'headerFile': 'mozilla/dom/ipc/SharedMap.h',
+    'nativeType': 'mozilla::dom::ipc::WritableSharedMap',
+},
+
+'MozSharedMapChangeEvent': {
+    'nativeType': 'mozilla::dom::ipc::SharedMapChangeEvent',
+},
+
 'MozStorageAsyncStatementParams': {
     'headerFile': 'mozilla/storage/mozStorageAsyncStatementParams.h',
     'nativeType': 'mozilla::storage::AsyncStatementParams',
 },
 
 'MozStorageStatementParams': {
     'headerFile': 'mozilla/storage/mozStorageStatementParams.h',
     'nativeType': 'mozilla::storage::StatementParams',
--- a/dom/chrome-webidl/MessageManager.webidl
+++ b/dom/chrome-webidl/MessageManager.webidl
@@ -452,16 +452,18 @@ interface GlobalProcessScriptLoader : Pr
    * attribute of its childprocessmessagemanager.
    *
    * This value will always be a JS object if it's not null or undefined. Different
    * users are expected to set properties on this object. The property name should be
    * unique enough that other Gecko consumers won't accidentally choose it.
    */
   [Throws]
   readonly attribute any initialProcessData;
+
+  readonly attribute MozWritableSharedMap sharedData;
 };
 
 [ChromeOnly, Global, NeedResolve]
 interface ContentFrameMessageManager : EventTarget
 {
   /**
    * The current top level window in the frame or null.
    */
@@ -489,16 +491,18 @@ ContentFrameMessageManager implements Me
 interface ContentProcessMessageManager
 {
   /**
    * Read out a copy of the object that was initialized in the parent
    * process via ProcessScriptLoader.initialProcessData.
    */
   [Throws]
   readonly attribute any initialProcessData;
+
+  readonly attribute MozSharedMap sharedData;
 };
 // MessageManagerGlobal inherits from SyncMessageSender, which is a real interface, not a
 // mixin. This will need to change when we implement mixins according to the current
 // WebIDL spec.
 ContentProcessMessageManager implements MessageManagerGlobal;
 
 /**
  * Message "broadcasters" don't have a single "other side" that they send messages to, but
new file mode 100644
--- /dev/null
+++ b/dom/chrome-webidl/MozSharedMap.webidl
@@ -0,0 +1,54 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+typedef any StructuredClonable;
+
+[ChromeOnly]
+interface MozSharedMapChangeEvent : Event {
+  [Cached, Constant]
+  readonly attribute sequence<DOMString> changedKeys;
+};
+
+dictionary MozSharedMapChangeEventInit : EventInit {
+  required sequence<DOMString> changedKeys;
+};
+
+[ChromeOnly]
+interface MozSharedMap : EventTarget {
+  boolean has(DOMString name);
+
+  [Throws]
+  StructuredClonable get(DOMString name);
+
+  iterable<DOMString, StructuredClonable>;
+};
+
+[ChromeOnly]
+interface MozWritableSharedMap : MozSharedMap {
+  /**
+   * Sets the given key to the given structured-clonable value. The value is
+   * synchronously structured cloned, and the serialized value is saved in the
+   * map.
+   *
+   * Unless flush() is called, the new value will be broadcast to content
+   * processes after a short delay.
+   */
+  [Throws]
+  void set(DOMString name, StructuredClonable value);
+
+  /**
+   * Removes the given key from the map.
+   *
+   * Unless flush() is called, the removal will be broadcast to content
+   * processes after a short delay.
+   */
+  void delete(DOMString name);
+
+  /**
+   * Broadcasts any pending changes to all content processes.
+   */
+  void flush();
+};
--- a/dom/chrome-webidl/moz.build
+++ b/dom/chrome-webidl/moz.build
@@ -32,16 +32,17 @@ PREPROCESSED_WEBIDL_FILES = [
 WEBIDL_FILES = [
     'ChannelWrapper.webidl',
     'DominatorTree.webidl',
     'HeapSnapshot.webidl',
     'InspectorUtils.webidl',
     'MatchGlob.webidl',
     'MatchPattern.webidl',
     'MessageManager.webidl',
+    'MozSharedMap.webidl',
     'MozStorageAsyncStatementParams.webidl',
     'MozStorageStatementParams.webidl',
     'MozStorageStatementRow.webidl',
     'PrecompiledScript.webidl',
     'PromiseDebugging.webidl',
     'StructuredCloneHolder.webidl',
     'WebExtensionContentScript.webidl',
     'WebExtensionPolicy.webidl',
--- a/dom/ipc/SharedMap.cpp
+++ b/dom/ipc/SharedMap.cpp
@@ -1,21 +1,23 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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 "SharedMap.h"
+#include "SharedMapChangeEvent.h"
 
 #include "MemMapSnapshot.h"
 #include "ScriptPreloader-inl.h"
 
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/ProcessGlobal.h"
+#include "mozilla/dom/ScriptSettings.h"
 
 using namespace mozilla::loader;
 
 namespace mozilla {
 
 using namespace ipc;
 
 namespace dom {
@@ -31,20 +33,22 @@ AlignTo(size_t* aOffset, size_t aAlign)
 {
   if (auto mod = *aOffset % aAlign) {
     *aOffset += aAlign - mod;
   }
 }
 
 
 SharedMap::SharedMap()
+  : DOMEventTargetHelper()
 {}
 
 SharedMap::SharedMap(nsIGlobalObject* aGlobal, const FileDescriptor& aMapFile,
                      size_t aMapSize)
+  : DOMEventTargetHelper(aGlobal)
 {
   mMapFile.reset(new FileDescriptor(aMapFile));
   mMapSize = aMapSize;
 }
 
 
 bool
 SharedMap::Has(const nsACString& aName)
@@ -116,16 +120,81 @@ SharedMap::Update(const FileDescriptor& 
   mMap.reset();
   if (mMapFile) {
     *mMapFile = aMapFile;
   } else {
     mMapFile.reset(new FileDescriptor(aMapFile));
   }
   mMapSize = aMapSize;
   mEntries.Clear();
+  mEntryArray.reset();
+
+
+  AutoEntryScript aes(GetParentObject(), "SharedMap change event");
+  JSContext* cx = aes.cx();
+
+  RootedDictionary<MozSharedMapChangeEventInit> init(cx);
+  if (!init.mChangedKeys.SetCapacity(aChangedKeys.Length(), fallible)) {
+    NS_WARNING("Failed to dispatch SharedMap change event");
+    return;
+  }
+  for (auto& key : aChangedKeys) {
+    Unused << init.mChangedKeys.AppendElement(NS_ConvertUTF8toUTF16(key),
+                                              fallible);
+  }
+
+  RefPtr<SharedMapChangeEvent> event =
+    SharedMapChangeEvent::Constructor(this, NS_LITERAL_STRING("change"), init);
+  event->SetTrusted(true);
+
+  DispatchEvent(*event);
+}
+
+
+const nsTArray<SharedMap::Entry*>&
+SharedMap::EntryArray() const
+{
+  if (mEntryArray.isNothing()) {
+    MaybeRebuild();
+
+    mEntryArray.emplace(mEntries.Count());
+    auto& array = mEntryArray.ref();
+    for (auto& entry : IterHash(mEntries)) {
+      array.AppendElement(entry);
+    }
+  }
+
+  return mEntryArray.ref();
+}
+
+const nsString
+SharedMap::GetKeyAtIndex(uint32_t aIndex) const
+{
+  return NS_ConvertUTF8toUTF16(EntryArray()[aIndex]->Name());
+}
+
+JS::Value
+SharedMap::GetValueAtIndex(uint32_t aIndex) const
+{
+  JSObject* wrapper = GetWrapper();
+  MOZ_ASSERT(wrapper,
+             "Should never see GetValueAtIndex on a SharedMap without a live "
+             "wrapper");
+  if (!wrapper) {
+    return JS::NullValue();
+  }
+
+  AutoJSContext cx;
+
+  JSAutoRealm ar(cx, wrapper);
+
+  JS::RootedValue val(cx);
+  EntryArray()[aIndex]->Read(cx, &val, IgnoreErrors());
+
+  return val;
 }
 
 void
 SharedMap::Entry::TakeData(StructuredCloneData&& aHolder)
 {
   mData = AsVariant(std::move(aHolder));
 
   mSize = Holder().Data().Size();
@@ -357,15 +426,44 @@ WritableSharedMap::Flush()
 }
 
 void
 WritableSharedMap::KeyChanged(const nsACString& aName)
 {
   if (!mChangedKeys.ContainsSorted(aName)) {
     mChangedKeys.InsertElementSorted(aName);
   }
+  mEntryArray.reset();
+}
+
+
+JSObject*
+SharedMap::WrapObject(JSContext* aCx, JS::HandleObject aGivenProto)
+{
+  return MozSharedMap_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+JSObject*
+WritableSharedMap::WrapObject(JSContext* aCx, JS::HandleObject aGivenProto)
+{
+  return MozWritableSharedMap_Binding::Wrap(aCx, this, aGivenProto);
 }
 
-NS_IMPL_ISUPPORTS0(SharedMap)
+/* static */ already_AddRefed<SharedMapChangeEvent>
+SharedMapChangeEvent::Constructor(EventTarget* aEventTarget,
+                                  const nsAString& aType,
+                                  const MozSharedMapChangeEventInit& aInit)
+{
+  RefPtr<SharedMapChangeEvent> event = new SharedMapChangeEvent(aEventTarget);
+
+  bool trusted = event->Init(aEventTarget);
+  event->InitEvent(aType, aInit.mBubbles, aInit.mCancelable);
+  event->SetTrusted(trusted);
+  event->SetComposed(aInit.mComposed);
+
+  event->mChangedKeys = aInit.mChangedKeys;
+
+  return event.forget();
+}
 
 } // ipc
 } // dom
 } // mozilla
--- a/dom/ipc/SharedMap.h
+++ b/dom/ipc/SharedMap.h
@@ -2,18 +2,21 @@
 /* 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 dom_ipc_SharedMap_h
 #define dom_ipc_SharedMap_h
 
+#include "mozilla/dom/MozSharedMapBinding.h"
+
 #include "mozilla/AutoMemMap.h"
 #include "mozilla/dom/ipc/StructuredCloneData.h"
+#include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/Variant.h"
 #include "nsClassHashtable.h"
 #include "nsTArray.h"
 
 class nsIGlobalObject;
 
@@ -40,36 +43,69 @@ namespace ipc {
  * 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
+class SharedMap : public DOMEventTargetHelper
 {
   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);
 
 
+  // Conversion helpers for WebIDL callers
+  bool Has(const nsAString& aName)
+  {
+    return Has(NS_ConvertUTF16toUTF8(aName));
+  }
+
+  void Get(JSContext* aCx, const nsAString& aName, JS::MutableHandleValue aRetVal,
+           ErrorResult& aRv)
+  {
+    return Get(aCx, NS_ConvertUTF16toUTF8(aName), aRetVal, aRv);
+  }
+
+
+  /**
+   * WebIDL iterator glue.
+   */
+  uint32_t GetIterableLength() const
+  {
+    return EntryArray().Length();
+  }
+
+  /**
+   * These functions return the key or value, respectively, at the given index.
+   * The index *must* be less than the value returned by GetIterableLength(), or
+   * the program will crash.
+   */
+  const nsString GetKeyAtIndex(uint32_t aIndex) const;
+  // Note: This function should only be called if the instance has a live,
+  // cached wrapper. If it does not, this function will return null, and assert
+  // in debug builds.
+  // The returned value will always be in the same Realm as that wrapper.
+  JS::Value GetValueAtIndex(uint32_t aIndex) const;
+
+
   /**
    * 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();
 
   /**
@@ -84,18 +120,20 @@ public:
    * 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);
 
 
+  JSObject* WrapObject(JSContext* aCx, JS::HandleObject aGivenProto) override;
+
 protected:
-  virtual ~SharedMap() = default;
+  ~SharedMap() override = default;
 
   class Entry
   {
   public:
     Entry(Entry&&) = delete;
 
     explicit Entry(SharedMap& aMap, const nsACString& aName = EmptyCString())
       : mMap(aMap)
@@ -217,32 +255,35 @@ protected:
      *   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;
   };
 
+  const nsTArray<Entry*>& EntryArray() const;
+
   // 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;
+  mutable Maybe<nsTArray<Entry*>> mEntryArray;
 
   // 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;
 
@@ -250,36 +291,53 @@ protected:
   // 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);
 
+
+  // Conversion helpers for WebIDL callers
+  void Set(JSContext* aCx, const nsAString& aName, JS::HandleValue aValue, ErrorResult& aRv)
+  {
+    return Set(aCx, NS_ConvertUTF16toUTF8(aName), aValue, aRv);
+  }
+
+  void Delete(const nsAString& aName)
+  {
+    return Delete(NS_ConvertUTF16toUTF8(aName));
+  }
+
+
   // 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();
 
+
+  JSObject* WrapObject(JSContext* aCx, JS::HandleObject aGivenProto) override;
+
 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;
 
new file mode 100644
--- /dev/null
+++ b/dom/ipc/SharedMapChangeEvent.h
@@ -0,0 +1,54 @@
+/* -*- 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_SharedMapChangeEvent_h
+#define dom_ipc_SharedMapChangeEvent_h
+
+#include "mozilla/dom/MozSharedMapBinding.h"
+
+#include "mozilla/dom/Event.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace dom {
+namespace ipc {
+
+class SharedMapChangeEvent final : public Event
+{
+public:
+  NS_INLINE_DECL_REFCOUNTING_INHERITED(SharedMapChangeEvent, Event)
+
+  JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
+  {
+    return MozSharedMapChangeEvent_Binding::Wrap(aCx, this, aGivenProto);
+  }
+
+  static already_AddRefed<SharedMapChangeEvent>
+  Constructor(EventTarget* aEventTarget,
+              const nsAString& aType,
+              const MozSharedMapChangeEventInit& aInit);
+
+  void GetChangedKeys(nsTArray<nsString>& aChangedKeys) const
+  {
+    aChangedKeys.AppendElements(mChangedKeys);
+  }
+
+protected:
+  ~SharedMapChangeEvent() override = default;
+
+private:
+  explicit SharedMapChangeEvent(EventTarget* aEventTarget)
+    : Event(aEventTarget, nullptr, nullptr)
+  {}
+
+  nsTArray<nsString> mChangedKeys;
+};
+
+} // ipc
+} // dom
+} // mozilla
+
+#endif // dom_ipc_SharedMapChangeEvent_h
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -11,16 +11,17 @@ XPIDL_SOURCES += [
     'nsIHangReport.idl',
 ]
 
 XPIDL_MODULE = 'dom'
 
 EXPORTS.mozilla.dom.ipc += [
     'IdType.h',
     'SharedMap.h',
+    'SharedMapChangeEvent.h',
     'SharedStringMap.h',
     'StructuredCloneData.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'CoalescedInputData.h',
     'CoalescedMouseData.h',
     'CoalescedWheelData.h',