зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
fa34bb9ca6
Коммит
3f643a72c3
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче