Bug 1463587: Part 6 - Add an idle flush task to WritableSharedMap. r=erahm draft
authorKris Maglione <maglione.k@gmail.com>
Wed, 27 Jun 2018 16:44:22 -0700
changeset 816390 3e7431c224f1d81411376c4d66922dc4953fcad9
parent 816389 9688e45dbecc09414e10db048b8e4b89ed456897
push id115826
push usermaglione.k@gmail.com
push dateWed, 11 Jul 2018 05:19:48 +0000
reviewerserahm
bugs1463587
milestone63.0a1
Bug 1463587: Part 6 - Add an idle flush task to WritableSharedMap. r=erahm MozReview-Commit-ID: 8Ht7zHo4PD6
dom/ipc/SharedMap.cpp
dom/ipc/SharedMap.h
dom/ipc/tests/test_sharedMap.js
--- a/dom/ipc/SharedMap.cpp
+++ b/dom/ipc/SharedMap.cpp
@@ -452,22 +452,38 @@ WritableSharedMap::Set(JSContext* aCx,
 
 void
 WritableSharedMap::Flush()
 {
   BroadcastChanges();
 }
 
 void
+WritableSharedMap::IdleFlush()
+{
+  mPendingFlush = false;
+  Flush();
+}
+
+nsresult
 WritableSharedMap::KeyChanged(const nsACString& aName)
 {
   if (!mChangedKeys.ContainsSorted(aName)) {
     mChangedKeys.InsertElementSorted(aName);
   }
   mEntryArray.reset();
+
+  if (!mPendingFlush) {
+      MOZ_TRY(NS_IdleDispatchToCurrentThread(
+        NewRunnableMethod("WritableSharedMap::IdleFlush",
+                          this,
+                          &WritableSharedMap::IdleFlush)));
+      mPendingFlush = true;
+  }
+  return NS_OK;
 }
 
 
 JSObject*
 SharedMap::WrapObject(JSContext* aCx, JS::HandleObject aGivenProto)
 {
   return MozSharedMap_Binding::Wrap(aCx, this, aGivenProto);
 }
--- a/dom/ipc/SharedMap.h
+++ b/dom/ipc/SharedMap.h
@@ -33,19 +33,21 @@ namespace ipc {
  * 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.
+ * update, combined updates are broadcast after a delay. Changes are flushed
+ * immediately any time a new process is created. Additionally, any time a key
+ * is changed, a flush task is scheduled for the next time the event loop
+ * becomes idle. Changes can be flushed immediately by calling the flush()
+ * method.
  *
  *
  * 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 DOMEventTargetHelper
@@ -360,27 +362,31 @@ protected:
 
 private:
   // The set of (UTF-8 encoded) keys which have changed, or been deleted, since
   // the last snapshot.
   nsTArray<nsCString> mChangedKeys;
 
   RefPtr<SharedMap> mReadOnly;
 
+  bool mPendingFlush = false;
+
   // Creates a new snapshot of the map, and updates all Entry instance to
   // reference its data.
   Result<Ok, nsresult> Serialize();
 
+  void IdleFlush();
+
   // 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);
+  // mChangedKeys, if not already present, and schedules a flush for the next
+  // time the event loop is idle.
+  nsresult KeyChanged(const nsACString& aName);
 };
 
 } // ipc
 } // dom
 } // mozilla
 
 #endif // dom_ipc_SharedMap_h
--- a/dom/ipc/tests/test_sharedMap.js
+++ b/dom/ipc/tests/test_sharedMap.js
@@ -41,23 +41,25 @@ function checkMap(contents, expected) {
   }
 }
 
 function checkParentMap(expected) {
   info("Checking parent map");
   checkMap(getContents(Services.ppmm.sharedData), expected);
 }
 
-async function checkContentMaps(expected) {
+async function checkContentMaps(expected, parentOnly = false) {
   info("Checking in-process content map");
   checkMap(getContents(Services.cpmm.sharedData), expected);
 
-  info("Checking out-of-process content map");
-  let contents = await contentPage.spawn(undefined, getContents);
-  checkMap(contents, expected);
+  if (!parentOnly) {
+    info("Checking out-of-process content map");
+    let contents = await contentPage.spawn(undefined, getContents);
+    checkMap(contents, expected);
+  }
 }
 
 add_task(async function setup() {
   contentPage = await ExtensionTestUtils.loadContentPage("about:blank");
   registerCleanupFunction(() => contentPage.close());
 });
 
 add_task(async function test_sharedMap() {
@@ -102,32 +104,56 @@ add_task(async function test_sharedMap()
   await checkContentMaps(expected);
 
   info("Add another entry. Check that it is initially only available in the parent");
 
   let oldExpected = Array.from(expected);
 
   setKey("baz-a", {meh: "meh"});
 
+  // When we do several checks in a row, we can't check the values in
+  // the content process, since the async checks may allow the idle
+  // flush task to run, and update it before we're ready.
+
   checkParentMap(expected);
-  await checkContentMaps(oldExpected);
+  checkContentMaps(oldExpected, true);
 
   info("Add another entry. Check that both new entries are only available in the parent");
 
   setKey("baz-a", {meh: 12});
 
   checkParentMap(expected);
-  await checkContentMaps(oldExpected);
+  checkContentMaps(oldExpected, true);
 
   info("Delete an entry. Check that all changes are only visible in the parent");
 
   deleteKey("foo-b");
 
   checkParentMap(expected);
-  await checkContentMaps(oldExpected);
+  checkContentMaps(oldExpected, true);
 
   info("Flush. Check that all entries are available in both parent and children");
 
   sharedData.flush();
 
   checkParentMap(expected);
   await checkContentMaps(expected);
+
+
+  info("Test that entries are automatically flushed on idle:");
+
+  info("Add a new entry. Check that it is initially only available in the parent");
+
+  // Test the idle flush task.
+  oldExpected = Array.from(expected);
+
+  setKey("thing", "stuff");
+
+  checkParentMap(expected);
+  checkContentMaps(oldExpected, true);
+
+  info("Wait for an idle timeout. Check that changes are now visible in all children");
+
+  await new Promise(resolve => ChromeUtils.idleDispatch(resolve));
+
+  checkParentMap(expected);
+  await checkContentMaps(expected);
 });