summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGaming4JC <g4jc@hyperbola.info>2020-01-22 21:07:19 -0500
committerGaming4JC <g4jc@hyperbola.info>2020-01-26 15:50:50 -0500
commit2077cdb41e3ef814cbbef482774d8bbec464fb1c (patch)
tree4646b9f4e1568da54c06adcf6ed3a4c27b9d7e60
parent43a8113072b96affb2b7a3a7a4e965547d3d0c41 (diff)
downloadUXP-2077cdb41e3ef814cbbef482774d8bbec464fb1c.tar
UXP-2077cdb41e3ef814cbbef482774d8bbec464fb1c.tar.gz
UXP-2077cdb41e3ef814cbbef482774d8bbec464fb1c.tar.lz
UXP-2077cdb41e3ef814cbbef482774d8bbec464fb1c.tar.xz
UXP-2077cdb41e3ef814cbbef482774d8bbec464fb1c.zip
Bug 1406922 - Make CycleCollectedJSContext to handle microtasks and make MutationObserver to use them
Tag UXP Issue #1344
-rw-r--r--dom/base/nsDOMMutationObserver.cpp67
-rw-r--r--dom/base/nsDOMMutationObserver.h23
-rw-r--r--dom/base/test/test_mutationobservers.html33
-rw-r--r--xpcom/base/CycleCollectedJSContext.cpp67
-rw-r--r--xpcom/base/CycleCollectedJSContext.h22
5 files changed, 164 insertions, 48 deletions
diff --git a/dom/base/nsDOMMutationObserver.cpp b/dom/base/nsDOMMutationObserver.cpp
index 858a30ce5..4c4731c11 100644
--- a/dom/base/nsDOMMutationObserver.cpp
+++ b/dom/base/nsDOMMutationObserver.cpp
@@ -32,8 +32,6 @@ using mozilla::dom::Element;
AutoTArray<RefPtr<nsDOMMutationObserver>, 4>*
nsDOMMutationObserver::sScheduledMutationObservers = nullptr;
-nsDOMMutationObserver* nsDOMMutationObserver::sCurrentObserver = nullptr;
-
uint32_t nsDOMMutationObserver::sMutationLevel = 0;
uint64_t nsDOMMutationObserver::sCount = 0;
@@ -597,10 +595,32 @@ nsDOMMutationObserver::ScheduleForRun()
RescheduleForRun();
}
+class MutationObserverMicroTask final : public MicroTaskRunnable
+{
+public:
+ virtual void Run(AutoSlowOperation& aAso) override
+ {
+ nsDOMMutationObserver::HandleMutations(aAso);
+ }
+
+ virtual bool Suppressed() override
+ {
+ return nsDOMMutationObserver::AllScheduledMutationObserversAreSuppressed();
+ }
+};
+
void
nsDOMMutationObserver::RescheduleForRun()
{
if (!sScheduledMutationObservers) {
+ CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
+ if (!ccjs) {
+ return;
+ }
+
+ RefPtr<MutationObserverMicroTask> momt =
+ new MutationObserverMicroTask();
+ ccjs->DispatchMicroTaskRunnable(momt.forget());
sScheduledMutationObservers = new AutoTArray<RefPtr<nsDOMMutationObserver>, 4>;
}
@@ -862,36 +882,9 @@ nsDOMMutationObserver::HandleMutation()
mCallback->Call(this, mutations, *this);
}
-class AsyncMutationHandler : public mozilla::Runnable
-{
-public:
- NS_IMETHOD Run() override
- {
- nsDOMMutationObserver::HandleMutations();
- return NS_OK;
- }
-};
-
void
-nsDOMMutationObserver::HandleMutationsInternal()
+nsDOMMutationObserver::HandleMutationsInternal(AutoSlowOperation& aAso)
{
- if (!nsContentUtils::IsSafeToRunScript()) {
- nsContentUtils::AddScriptRunner(new AsyncMutationHandler());
- return;
- }
- static RefPtr<nsDOMMutationObserver> sCurrentObserver;
- if (sCurrentObserver && !sCurrentObserver->Suppressed()) {
- // In normal cases sScheduledMutationObservers will be handled
- // after previous mutations are handled. But in case some
- // callback calls a sync API, which spins the eventloop, we need to still
- // process other mutations happening during that sync call.
- // This does *not* catch all cases, but should work for stuff running
- // in separate tabs.
- return;
- }
-
- mozilla::AutoSlowOperation aso;
-
nsTArray<RefPtr<nsDOMMutationObserver> >* suppressedObservers = nullptr;
while (sScheduledMutationObservers) {
@@ -899,20 +892,21 @@ nsDOMMutationObserver::HandleMutationsInternal()
sScheduledMutationObservers;
sScheduledMutationObservers = nullptr;
for (uint32_t i = 0; i < observers->Length(); ++i) {
- sCurrentObserver = static_cast<nsDOMMutationObserver*>((*observers)[i]);
- if (!sCurrentObserver->Suppressed()) {
- sCurrentObserver->HandleMutation();
+ RefPtr<nsDOMMutationObserver> currentObserver =
+ static_cast<nsDOMMutationObserver*>((*observers)[i]);
+ if (!currentObserver->Suppressed()) {
+ currentObserver->HandleMutation();
} else {
if (!suppressedObservers) {
suppressedObservers = new nsTArray<RefPtr<nsDOMMutationObserver> >;
}
- if (!suppressedObservers->Contains(sCurrentObserver)) {
- suppressedObservers->AppendElement(sCurrentObserver);
+ if (!suppressedObservers->Contains(currentObserver)) {
+ suppressedObservers->AppendElement(currentObserver);
}
}
}
delete observers;
- aso.CheckForInterrupt();
+ aAso.CheckForInterrupt();
}
if (suppressedObservers) {
@@ -923,7 +917,6 @@ nsDOMMutationObserver::HandleMutationsInternal()
delete suppressedObservers;
suppressedObservers = nullptr;
}
- sCurrentObserver = nullptr;
}
nsDOMMutationRecord*
diff --git a/dom/base/nsDOMMutationObserver.h b/dom/base/nsDOMMutationObserver.h
index cde32c57b..a8babc603 100644
--- a/dom/base/nsDOMMutationObserver.h
+++ b/dom/base/nsDOMMutationObserver.h
@@ -552,13 +552,29 @@ public:
}
// static methods
- static void HandleMutations()
+ static void HandleMutations(mozilla::AutoSlowOperation& aAso)
{
if (sScheduledMutationObservers) {
- HandleMutationsInternal();
+ HandleMutationsInternal(aAso);
}
}
+ static bool AllScheduledMutationObserversAreSuppressed()
+ {
+ if (sScheduledMutationObservers) {
+ uint32_t len = sScheduledMutationObservers->Length();
+ if (len > 0) {
+ for (uint32_t i = 0; i < len; ++i) {
+ if (!(*sScheduledMutationObservers)[i]->Suppressed()) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
static void EnterMutationHandling();
static void LeaveMutationHandling();
@@ -594,7 +610,7 @@ protected:
return false;
}
- static void HandleMutationsInternal();
+ static void HandleMutationsInternal(mozilla::AutoSlowOperation& aAso);
static void AddCurrentlyHandlingObserver(nsDOMMutationObserver* aObserver,
uint32_t aMutationLevel);
@@ -622,7 +638,6 @@ protected:
static uint64_t sCount;
static AutoTArray<RefPtr<nsDOMMutationObserver>, 4>* sScheduledMutationObservers;
- static nsDOMMutationObserver* sCurrentObserver;
static uint32_t sMutationLevel;
static AutoTArray<AutoTArray<RefPtr<nsDOMMutationObserver>, 4>, 4>*
diff --git a/dom/base/test/test_mutationobservers.html b/dom/base/test/test_mutationobservers.html
index a6de89595..7e4c99423 100644
--- a/dom/base/test/test_mutationobservers.html
+++ b/dom/base/test/test_mutationobservers.html
@@ -362,7 +362,7 @@ function testChildList5() {
is(records[5].previousSibling, c3, "");
is(records[5].nextSibling, c5, "");
observer.disconnect();
- then(testAdoptNode);
+ then(testNestedMutations);
m = null;
});
m.observe(div, { childList: true, subtree: true });
@@ -375,6 +375,37 @@ function testChildList5() {
div.appendChild(emptyDF); // empty document shouldn't cause mutation records
}
+function testNestedMutations() {
+ div.textContent = null;
+ div.appendChild(document.createTextNode("foo"));
+ var m2WasCalled = false;
+ m = new M(function(records, observer) {
+ is(records[0].type, "characterData", "Should have got characterData");
+ observer.disconnect();
+ m = null;
+ m3 = new M(function(records, observer) {
+ ok(m2WasCalled, "m2 should have been called before m3!");
+ is(records[0].type, "characterData", "Should have got characterData");
+ observer.disconnect();
+ then(testAdoptNode);
+ m3 = null;
+ });
+ m3.observe(div, { characterData: true, subtree: true});
+ div.firstChild.data = "foo";
+ });
+ m2 = new M(function(records, observer) {
+ m2WasCalled = true;
+ is(records[0].type, "characterData", "Should have got characterData");
+ observer.disconnect();
+ m2 = null;
+ });
+ m2.observe(div, { characterData: true, subtree: true});
+ div.appendChild(document.createTextNode("foo"));
+ m.observe(div, { characterData: true, subtree: true });
+
+ div.firstChild.data = "bar";
+}
+
function testAdoptNode() {
var d1 = document.implementation.createHTMLDocument(null);
var d2 = document.implementation.createHTMLDocument(null);
diff --git a/xpcom/base/CycleCollectedJSContext.cpp b/xpcom/base/CycleCollectedJSContext.cpp
index 0a85ae6ac..033ca562a 100644
--- a/xpcom/base/CycleCollectedJSContext.cpp
+++ b/xpcom/base/CycleCollectedJSContext.cpp
@@ -440,6 +440,7 @@ CycleCollectedJSContext::CycleCollectedJSContext()
, mDoingStableStates(false)
, mDisableMicroTaskCheckpoint(false)
, mMicroTaskLevel(0)
+ , mMicroTaskRecursionDepth(0)
, mOutOfMemoryState(OOMState::OK)
, mLargeAllocationFailureState(OOMState::OK)
{
@@ -1380,8 +1381,8 @@ CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth)
// Step 4.1: Execute microtasks.
if (!mDisableMicroTaskCheckpoint) {
+ PerformMicroTaskCheckPoint();
if (NS_IsMainThread()) {
- PerformMainThreadMicroTaskCheckpoint();
Promise::PerformMicroTaskCheckpoint();
} else {
Promise::PerformWorkerMicroTaskCheckpoint();
@@ -1661,12 +1662,70 @@ CycleCollectedJSContext::DispatchToMicroTask(already_AddRefed<nsIRunnable> aRunn
mPromiseMicroTaskQueue.push(runnable.forget());
}
+class AsyncMutationHandler final : public mozilla::Runnable
+{
+public:
+ NS_IMETHOD Run() override
+ {
+ CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
+ if (ccjs) {
+ ccjs->PerformMicroTaskCheckPoint();
+ }
+ return NS_OK;
+ }
+};
+
void
-CycleCollectedJSContext::PerformMainThreadMicroTaskCheckpoint()
+CycleCollectedJSContext::PerformMicroTaskCheckPoint()
{
- MOZ_ASSERT(NS_IsMainThread());
+ if (mPendingMicroTaskRunnables.empty()) {
+ // Nothing to do, return early.
+ return;
+ }
+
+ uint32_t currentDepth = RecursionDepth();
+ if (mMicroTaskRecursionDepth >= currentDepth) {
+ // We are already executing microtasks for the current recursion depth.
+ return;
+ }
- nsDOMMutationObserver::HandleMutations();
+ if (NS_IsMainThread() && !nsContentUtils::IsSafeToRunScript()) {
+ // Special case for main thread where DOM mutations may happen when
+ // it is not safe to run scripts.
+ nsContentUtils::AddScriptRunner(new AsyncMutationHandler());
+ return;
+ }
+
+ mozilla::AutoRestore<uint32_t> restore(mMicroTaskRecursionDepth);
+ MOZ_ASSERT(currentDepth > 0);
+ mMicroTaskRecursionDepth = currentDepth;
+
+ AutoSlowOperation aso;
+
+ std::queue<RefPtr<MicroTaskRunnable>> suppressed;
+ while (!mPendingMicroTaskRunnables.empty()) {
+ RefPtr<MicroTaskRunnable> runnable =
+ mPendingMicroTaskRunnables.front().forget();
+ mPendingMicroTaskRunnables.pop();
+ if (runnable->Suppressed()) {
+ suppressed.push(runnable);
+ } else {
+ runnable->Run(aso);
+ }
+ }
+
+ // Put back the suppressed microtasks so that they will be run later.
+ // Note, it is possible that we end up keeping these suppressed tasks around
+ // for some time, but no longer than spinning the event loop nestedly
+ // (sync XHR, alert, etc.)
+ mPendingMicroTaskRunnables.swap(suppressed);
+}
+
+void
+CycleCollectedJSContext::DispatchMicroTaskRunnable(
+ already_AddRefed<MicroTaskRunnable> aRunnable)
+{
+ mPendingMicroTaskRunnables.push(aRunnable);
}
void
diff --git a/xpcom/base/CycleCollectedJSContext.h b/xpcom/base/CycleCollectedJSContext.h
index 2197eae92..4cd1479ed 100644
--- a/xpcom/base/CycleCollectedJSContext.h
+++ b/xpcom/base/CycleCollectedJSContext.h
@@ -33,6 +33,7 @@ struct Class;
} // namespace js
namespace mozilla {
+class AutoSlowOperation;
class JSGCThingParticipant: public nsCycleCollectionParticipant
{
@@ -134,6 +135,17 @@ struct CycleCollectorResults
uint32_t mNumSlices;
};
+class MicroTaskRunnable
+{
+public:
+ MicroTaskRunnable() {}
+ NS_INLINE_DECL_REFCOUNTING(MicroTaskRunnable)
+ virtual void Run(AutoSlowOperation& aAso) = 0;
+ virtual bool Suppressed() { return false; }
+protected:
+ virtual ~MicroTaskRunnable() {}
+};
+
class CycleCollectedJSContext
{
friend class JSGCThingParticipant;
@@ -412,7 +424,7 @@ public:
void LeaveMicroTask()
{
if (--mMicroTaskLevel == 0) {
- PerformMainThreadMicroTaskCheckpoint();
+ PerformMicroTaskCheckPoint();
}
}
@@ -431,7 +443,9 @@ public:
mMicroTaskLevel = aLevel;
}
- void PerformMainThreadMicroTaskCheckpoint();
+ void PerformMicroTaskCheckPoint();
+
+ void DispatchMicroTaskRunnable(already_AddRefed<MicroTaskRunnable> aRunnable);
// Storage for watching rejected promises waiting for some client to
// consume their rejection.
@@ -484,6 +498,10 @@ private:
bool mDisableMicroTaskCheckpoint;
uint32_t mMicroTaskLevel;
+ std::queue<RefPtr<MicroTaskRunnable>> mPendingMicroTaskRunnables;
+
+ uint32_t mMicroTaskRecursionDepth;
+
OOMState mOutOfMemoryState;
OOMState mLargeAllocationFailureState;