Bug 1406922 - Make CycleCollectedJSContext to handle microtasks and make MutationObserver to use them, r=baku,bevis

--HG--
extra : rebase_source : 62ad4503ceaa6b1c438f4fb7fc0c5b65b3e77665
This commit is contained in:
Olli Pettay 2017-10-11 15:31:38 +03:00
Родитель fa34bb9ca6
Коммит 3f643a72c3
5 изменённых файлов: 166 добавлений и 50 удалений

Просмотреть файл

@ -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;
@ -599,10 +597,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>;
}
@ -864,37 +884,9 @@ nsDOMMutationObserver::HandleMutation()
mCallback->Call(this, mutations, *this);
}
class AsyncMutationHandler : public mozilla::Runnable
{
public:
AsyncMutationHandler() : mozilla::Runnable("AsyncMutationHandler") {}
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) {
@ -902,20 +894,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) {
@ -926,7 +919,6 @@ nsDOMMutationObserver::HandleMutationsInternal()
delete suppressedObservers;
suppressedObservers = nullptr;
}
sCurrentObserver = nullptr;
}
nsDOMMutationRecord*

Просмотреть файл

@ -575,13 +575,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();
@ -613,7 +629,7 @@ protected:
return mOwner && nsGlobalWindow::Cast(mOwner)->IsInSyncOperation();
}
static void HandleMutationsInternal();
static void HandleMutationsInternal(mozilla::AutoSlowOperation& aAso);
static void AddCurrentlyHandlingObserver(nsDOMMutationObserver* aObserver,
uint32_t aMutationLevel);
@ -641,7 +657,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);

Просмотреть файл

@ -59,6 +59,7 @@ CycleCollectedJSContext::CycleCollectedJSContext()
, mDoingStableStates(false)
, mDisableMicroTaskCheckpoint(false)
, mMicroTaskLevel(0)
, mMicroTaskRecursionDepth(0)
{
MOZ_COUNT_CTOR(CycleCollectedJSContext);
nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
@ -359,8 +360,8 @@ CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth)
// Step 4.1: Execute microtasks.
if (!mDisableMicroTaskCheckpoint) {
PerformMicroTaskCheckPoint();
if (NS_IsMainThread()) {
PerformMainThreadMicroTaskCheckpoint();
Promise::PerformMicroTaskCheckpoint();
} else {
Promise::PerformWorkerMicroTaskCheckpoint();
@ -438,12 +439,72 @@ CycleCollectedJSContext::DispatchToMicroTask(already_AddRefed<nsIRunnable> aRunn
mPromiseMicroTaskQueue.push(runnable.forget());
}
void
CycleCollectedJSContext::PerformMainThreadMicroTaskCheckpoint()
class AsyncMutationHandler final : public mozilla::Runnable
{
MOZ_ASSERT(NS_IsMainThread());
public:
AsyncMutationHandler() : mozilla::Runnable("AsyncMutationHandler") {}
nsDOMMutationObserver::HandleMutations();
NS_IMETHOD Run() override
{
CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
if (ccjs) {
ccjs->PerformMicroTaskCheckPoint();
}
return NS_OK;
}
};
void
CycleCollectedJSContext::PerformMicroTaskCheckPoint()
{
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;
}
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);
}
} // namespace mozilla

Просмотреть файл

@ -27,6 +27,7 @@ class nsThread;
class nsWrapperCache;
namespace mozilla {
class AutoSlowOperation;
class CycleCollectedJSRuntime;
@ -66,6 +67,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
: public LinkedListElement<CycleCollectedJSContext>
{
@ -207,7 +219,7 @@ public:
void LeaveMicroTask()
{
if (--mMicroTaskLevel == 0) {
PerformMainThreadMicroTaskCheckpoint();
PerformMicroTaskCheckPoint();
}
}
@ -226,7 +238,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.
@ -270,6 +284,9 @@ private:
bool mDisableMicroTaskCheckpoint;
uint32_t mMicroTaskLevel;
std::queue<RefPtr<MicroTaskRunnable>> mPendingMicroTaskRunnables;
uint32_t mMicroTaskRecursionDepth;
};
class MOZ_STACK_CLASS nsAutoMicroTask