Bug 1463587: Part 6 - Add an idle flush task to WritableSharedMap. r=erahm
MozReview-Commit-ID: 8Ht7zHo4PD6
--- 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);
});