Bug 1406922 - Make CycleCollectedJSContext to handle microtasks and make MutationObserver to use them

Tag UXP Issue #1344
pull/24/head
Gaming4JC 3 years ago committed by Roy Tam
parent fb6f6ec903
commit 249667092c
  1. 67
      dom/base/nsDOMMutationObserver.cpp
  2. 23
      dom/base/nsDOMMutationObserver.h
  3. 33
      dom/base/test/test_mutationobservers.html
  4. 67
      xpcom/base/CycleCollectedJSContext.cpp
  5. 22
      xpcom/base/CycleCollectedJSContext.h

@ -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*

@ -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>*

@ -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);

@ -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

@ -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;

Loading…
Cancel
Save