mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1179909: Refactor stable state handling. r=smaug
This is motivated by three separate but related problems: 1. Our concept of recursion depth is broken for things that run from AfterProcessNextEvent observers (e.g. Promises). We decrement the recursionDepth counter before firing observers, so a Promise callback running at the lowest event loop depth has a recursion depth of 0 (whereas a regular nsIRunnable would be 1). This is a problem because it's impossible to distinguish a Promise running after a sync XHR's onreadystatechange handler from a top-level event (since the former runs with depth 2 - 1 = 1, and the latter runs with just 1). 2. The nsIThreadObserver mechanism that is used by a lot of code to run "after" the current event is a poor fit for anything that runs script. First, the order the observers fire in is the order they were added, not anything fixed by spec. Additionally, running script can cause the event loop to spin, which is a big source of pain here (bholley has some nasty bug caused by this). 3. We run Promises from different points in the code for workers and main thread. The latter runs from XPConnect's nsIThreadObserver callbacks, while the former runs from a hardcoded call to run Promises in the worker event loop. What workers do is particularly problematic because it means we can't get the right recursion depth no matter what we do to nsThread. The solve this, this patch does the following: 1. Consolidate some handling of microtasks and all handling of stable state from appshell and WorkerPrivate into CycleCollectedJSRuntime. 2. Make the recursionDepth counter only available to CycleCollectedJSRuntime (and its consumers) and remove it from the nsIThreadInternal and nsIThreadObserver APIs. 3. Adjust the recursionDepth counter so that microtasks run with the recursionDepth of the task they are associated with. 4. Introduce the concept of metastable state to replace appshell's RunBeforeNextEvent. Metastable state is reached after every microtask or task is completed. This provides the semantics that bent and I want for IndexedDB, where transactions autocommit at the end of a microtask and do not "spill" from one microtask into a subsequent microtask. This differs from appshell's RunBeforeNextEvent in two ways: a) It fires between microtasks, which was the motivation for starting this. b) It no longer ensures that we're at the same event loop depth in the native event queue. bent decided we don't care about this. 5. Reorder stable state to happen after microtasks such as Promises, per HTML. Right now we call the regular thread observers, including appshell, before the main thread observer (XPConnect), so stable state tasks happen before microtasks.
This commit is contained in:
parent
bd090a5647
commit
1ffb03e8dc
@ -5207,16 +5207,18 @@ nsContentUtils::AddScriptRunner(nsIRunnable* aRunnable)
|
||||
|
||||
/* static */
|
||||
void
|
||||
nsContentUtils::RunInStableState(already_AddRefed<nsIRunnable> aRunnable,
|
||||
DispatchFailureHandling aHandling)
|
||||
nsContentUtils::RunInStableState(already_AddRefed<nsIRunnable> aRunnable)
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> runnable = aRunnable;
|
||||
nsCOMPtr<nsIAppShell> appShell(do_GetService(kAppShellCID));
|
||||
if (!appShell) {
|
||||
MOZ_ASSERT(aHandling == DispatchFailureHandling::IgnoreFailure);
|
||||
return;
|
||||
}
|
||||
appShell->RunInStableState(runnable.forget());
|
||||
MOZ_ASSERT(CycleCollectedJSRuntime::Get(), "Must be on a script thread!");
|
||||
CycleCollectedJSRuntime::Get()->RunInStableState(Move(aRunnable));
|
||||
}
|
||||
|
||||
/* static */
|
||||
void
|
||||
nsContentUtils::RunInMetastableState(already_AddRefed<nsIRunnable> aRunnable)
|
||||
{
|
||||
MOZ_ASSERT(CycleCollectedJSRuntime::Get(), "Must be on a script thread!");
|
||||
CycleCollectedJSRuntime::Get()->RunInMetastableState(Move(aRunnable));
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1597,12 +1597,6 @@ public:
|
||||
*/
|
||||
static void WarnScriptWasIgnored(nsIDocument* aDocument);
|
||||
|
||||
/**
|
||||
* Whether to assert that RunInStableState() succeeds, or ignore failure,
|
||||
* which may happen late in shutdown.
|
||||
*/
|
||||
enum class DispatchFailureHandling { AssertSuccess, IgnoreFailure };
|
||||
|
||||
/**
|
||||
* Add a "synchronous section", in the form of an nsIRunnable run once the
|
||||
* event loop has reached a "stable state". |aRunnable| must not cause any
|
||||
@ -1614,9 +1608,19 @@ public:
|
||||
* finishes. If called multiple times per task/event, all the runnables will
|
||||
* be executed, in the order in which RunInStableState() was called.
|
||||
*/
|
||||
static void RunInStableState(already_AddRefed<nsIRunnable> aRunnable,
|
||||
DispatchFailureHandling aHandling =
|
||||
DispatchFailureHandling::AssertSuccess);
|
||||
static void RunInStableState(already_AddRefed<nsIRunnable> aRunnable);
|
||||
|
||||
/* Add a "synchronous section", in the form of an nsIRunnable run once the
|
||||
* event loop has reached a "metastable state". |aRunnable| must not cause any
|
||||
* queued events to be processed (i.e. must not spin the event loop).
|
||||
* We've reached a metastable state when the currently executing task or
|
||||
* microtask has finished. This is not specced at this time.
|
||||
* In practice this runs aRunnable once the currently executing task or
|
||||
* microtask finishes. If called multiple times per microtask, all the
|
||||
* runnables will be executed, in the order in which RunInMetastableState()
|
||||
* was called
|
||||
*/
|
||||
static void RunInMetastableState(already_AddRefed<nsIRunnable> aRunnable);
|
||||
|
||||
/**
|
||||
* Retrieve information about the viewport as a data structure.
|
||||
|
@ -88,8 +88,7 @@
|
||||
#include "nsViewportInfo.h"
|
||||
#include "nsIFormControl.h"
|
||||
#include "nsIScriptError.h"
|
||||
#include "nsIAppShell.h"
|
||||
#include "nsWidgetsCID.h"
|
||||
//#include "nsWidgetsCID.h"
|
||||
#include "FrameLayerBuilder.h"
|
||||
#include "nsDisplayList.h"
|
||||
#include "nsROCSSPrimitiveValue.h"
|
||||
@ -3528,30 +3527,6 @@ nsDOMWindowUtils::DispatchEventToChromeOnly(nsIDOMEventTarget* aTarget,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMWindowUtils::RunInStableState(nsIRunnable *aRunnable)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
|
||||
|
||||
nsCOMPtr<nsIRunnable> runnable = aRunnable;
|
||||
nsContentUtils::RunInStableState(runnable.forget());
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMWindowUtils::RunBeforeNextEvent(nsIRunnable *runnable)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
|
||||
|
||||
nsCOMPtr<nsIAppShell> appShell(do_GetService(kAppShellCID));
|
||||
if (!appShell) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
return appShell->RunBeforeNextEvent(runnable);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMWindowUtils::RequestCompositorProperty(const nsAString& property,
|
||||
float* aResult)
|
||||
|
@ -25417,15 +25417,13 @@ DEBUGThreadSlower::OnDispatchedEvent(nsIThreadInternal* /* aThread */)
|
||||
|
||||
NS_IMETHODIMP
|
||||
DEBUGThreadSlower::OnProcessNextEvent(nsIThreadInternal* /* aThread */,
|
||||
bool /* aMayWait */,
|
||||
uint32_t /* aRecursionDepth */)
|
||||
bool /* aMayWait */)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DEBUGThreadSlower::AfterProcessNextEvent(nsIThreadInternal* /* aThread */,
|
||||
uint32_t /* aRecursionDepth */,
|
||||
bool /* aEventWasProcessed */)
|
||||
{
|
||||
MOZ_ASSERT(kDEBUGThreadSleepMS);
|
||||
|
@ -12,7 +12,6 @@
|
||||
#include "mozilla/dom/IDBFileHandleBinding.h"
|
||||
#include "mozilla/dom/MetadataHelper.h"
|
||||
#include "mozilla/EventDispatcher.h"
|
||||
#include "nsIAppShell.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsWidgetsCID.h"
|
||||
|
||||
@ -20,12 +19,6 @@ namespace mozilla {
|
||||
namespace dom {
|
||||
namespace indexedDB {
|
||||
|
||||
namespace {
|
||||
|
||||
NS_DEFINE_CID(kAppShellCID2, NS_APPSHELL_CID);
|
||||
|
||||
} // namespace
|
||||
|
||||
IDBFileHandle::IDBFileHandle(FileMode aMode,
|
||||
RequestMode aRequestMode,
|
||||
IDBMutableFile* aMutableFile)
|
||||
@ -51,15 +44,8 @@ IDBFileHandle::Create(FileMode aMode,
|
||||
|
||||
fileHandle->BindToOwner(aMutableFile);
|
||||
|
||||
nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID2);
|
||||
if (NS_WARN_IF(!appShell)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsresult rv = appShell->RunBeforeNextEvent(fileHandle);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return nullptr;
|
||||
}
|
||||
nsCOMPtr<nsIRunnable> runnable = do_QueryObject(fileHandle);
|
||||
nsContentUtils::RunInMetastableState(runnable.forget());
|
||||
|
||||
fileHandle->SetCreating();
|
||||
|
||||
@ -68,7 +54,7 @@ IDBFileHandle::Create(FileMode aMode,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
rv = service->Enqueue(fileHandle, nullptr);
|
||||
nsresult rv = service->Enqueue(fileHandle, nullptr);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -16,11 +16,9 @@
|
||||
#include "mozilla/dom/DOMError.h"
|
||||
#include "mozilla/dom/DOMStringList.h"
|
||||
#include "mozilla/ipc/BackgroundChild.h"
|
||||
#include "nsIAppShell.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsTHashtable.h"
|
||||
#include "nsWidgetsCID.h"
|
||||
#include "ProfilerHelpers.h"
|
||||
#include "ReportInternalError.h"
|
||||
#include "WorkerFeature.h"
|
||||
@ -36,36 +34,6 @@ namespace indexedDB {
|
||||
using namespace mozilla::dom::workers;
|
||||
using namespace mozilla::ipc;
|
||||
|
||||
namespace {
|
||||
|
||||
NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
|
||||
|
||||
bool
|
||||
RunBeforeNextEvent(IDBTransaction* aTransaction)
|
||||
{
|
||||
MOZ_ASSERT(aTransaction);
|
||||
|
||||
if (NS_IsMainThread()) {
|
||||
nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
|
||||
MOZ_ASSERT(appShell);
|
||||
|
||||
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(appShell->RunBeforeNextEvent(aTransaction)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
|
||||
MOZ_ASSERT(workerPrivate);
|
||||
|
||||
if (NS_WARN_IF(!workerPrivate->RunBeforeNextEvent(aTransaction))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class IDBTransaction::WorkerFeature final
|
||||
: public mozilla::dom::workers::WorkerFeature
|
||||
{
|
||||
@ -222,15 +190,8 @@ IDBTransaction::CreateVersionChange(
|
||||
|
||||
transaction->SetScriptOwner(aDatabase->GetScriptOwner());
|
||||
|
||||
if (NS_WARN_IF(!RunBeforeNextEvent(transaction))) {
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
#ifdef DEBUG
|
||||
// Silence assertions.
|
||||
transaction->mSentCommitOrAbort = true;
|
||||
#endif
|
||||
aActor->SendDeleteMeInternal(/* aFailedConstructor */ true);
|
||||
return nullptr;
|
||||
}
|
||||
nsCOMPtr<nsIRunnable> runnable = do_QueryObject(transaction);
|
||||
nsContentUtils::RunInMetastableState(runnable.forget());
|
||||
|
||||
transaction->mBackgroundActor.mVersionChangeBackgroundActor = aActor;
|
||||
transaction->mNextObjectStoreId = aNextObjectStoreId;
|
||||
@ -262,10 +223,8 @@ IDBTransaction::Create(IDBDatabase* aDatabase,
|
||||
|
||||
transaction->SetScriptOwner(aDatabase->GetScriptOwner());
|
||||
|
||||
if (NS_WARN_IF(!RunBeforeNextEvent(transaction))) {
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
return nullptr;
|
||||
}
|
||||
nsCOMPtr<nsIRunnable> runnable = do_QueryObject(transaction);
|
||||
nsContentUtils::RunInMetastableState(runnable.forget());
|
||||
|
||||
transaction->mCreating = true;
|
||||
|
||||
|
@ -49,7 +49,7 @@ interface nsIJSRAIIHelper;
|
||||
interface nsIContentPermissionRequest;
|
||||
interface nsIObserver;
|
||||
|
||||
[scriptable, uuid(7a37e173-ea6e-495e-8702-013f8063352a)]
|
||||
[scriptable, uuid(6064615a-a782-4d08-86db-26ef3851208a)]
|
||||
interface nsIDOMWindowUtils : nsISupports {
|
||||
|
||||
/**
|
||||
@ -1722,32 +1722,6 @@ interface nsIDOMWindowUtils : nsISupports {
|
||||
*/
|
||||
attribute boolean paintFlashing;
|
||||
|
||||
/**
|
||||
* Add a "synchronous section", in the form of an nsIRunnable run once the
|
||||
* event loop has reached a "stable state". |runnable| must not cause any
|
||||
* queued events to be processed (i.e. must not spin the event loop).
|
||||
* We've reached a stable state when the currently executing task/event has
|
||||
* finished, see:
|
||||
* http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#synchronous-section
|
||||
* In practice this runs aRunnable once the currently executing event
|
||||
* finishes. If called multiple times per task/event, all the runnables will
|
||||
* be executed, in the order in which runInStableState() was called.
|
||||
*
|
||||
* XXX - This can wreak havoc if you're not using this for very simple
|
||||
* purposes, eg testing or setting a flag.
|
||||
*/
|
||||
void runInStableState(in nsIRunnable runnable);
|
||||
|
||||
/**
|
||||
* Run the given runnable before the next iteration of the event loop (this
|
||||
* includes native events too). If a nested loop is spawned within the current
|
||||
* event then the runnable will not be run until that loop has terminated.
|
||||
*
|
||||
* XXX - This can wreak havoc if you're not using this for very simple
|
||||
* purposes, eg testing or setting a flag.
|
||||
*/
|
||||
void runBeforeNextEvent(in nsIRunnable runnable);
|
||||
|
||||
/*
|
||||
* Returns the value of a given property animated on the compositor thread.
|
||||
* If the property is NOT currently being animated on the compositor thread,
|
||||
|
@ -633,9 +633,7 @@ AudioDestinationNode::ScheduleStableStateNotification()
|
||||
NS_NewRunnableMethod(this, &AudioDestinationNode::NotifyStableState);
|
||||
// Dispatch will fail if this is called on AudioNode destruction during
|
||||
// shutdown, in which case failure can be ignored.
|
||||
nsContentUtils::RunInStableState(event.forget(),
|
||||
nsContentUtils::
|
||||
DispatchFailureHandling::IgnoreFailure);
|
||||
nsContentUtils::RunInStableState(event.forget());
|
||||
}
|
||||
|
||||
double
|
||||
|
@ -510,6 +510,7 @@ Promise::PerformMicroTaskCheckpoint()
|
||||
if (cx.isSome()) {
|
||||
JS_CheckForInterrupt(cx.ref());
|
||||
}
|
||||
runtime->AfterProcessMicrotask();
|
||||
} while (!microtaskQueue.empty());
|
||||
|
||||
return true;
|
||||
|
@ -169,6 +169,26 @@ function promiseAsync_ResolveThenTimeout() {
|
||||
ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
|
||||
}
|
||||
|
||||
function promiseAsync_SyncXHR()
|
||||
{
|
||||
var handlerExecuted = false;
|
||||
|
||||
Promise.resolve().then(function() {
|
||||
handlerExecuted = true;
|
||||
|
||||
// Allow other assertions to run so the test could fail before the next one.
|
||||
setTimeout(runTest, 0);
|
||||
});
|
||||
|
||||
ok(!handlerExecuted, "Handlers are not called until the next microtask.");
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "testXHR.txt", false);
|
||||
xhr.send(null);
|
||||
|
||||
todo(!handlerExecuted, "Sync XHR should not trigger microtask execution.");
|
||||
}
|
||||
|
||||
function promiseDoubleThen() {
|
||||
var steps = 0;
|
||||
var promise = new Promise(function(r1, r2) {
|
||||
@ -756,6 +776,7 @@ var tests = [ promiseResolve, promiseReject,
|
||||
promiseAsync_TimeoutResolveThen,
|
||||
promiseAsync_ResolveTimeoutThen,
|
||||
promiseAsync_ResolveThenTimeout,
|
||||
promiseAsync_SyncXHR,
|
||||
promiseDoubleThen, promiseThenException,
|
||||
promiseThenCatchThen, promiseRejectThenCatchThen,
|
||||
promiseRejectThenCatchThen2,
|
||||
|
@ -362,15 +362,13 @@ DOMStorageDBThread::ThreadObserver::OnDispatchedEvent(nsIThreadInternal *thread)
|
||||
|
||||
NS_IMETHODIMP
|
||||
DOMStorageDBThread::ThreadObserver::OnProcessNextEvent(nsIThreadInternal *thread,
|
||||
bool mayWait,
|
||||
uint32_t recursionDepth)
|
||||
bool mayWait)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DOMStorageDBThread::ThreadObserver::AfterProcessNextEvent(nsIThreadInternal *thread,
|
||||
uint32_t recursionDepth,
|
||||
bool eventWasProcessed)
|
||||
{
|
||||
return NS_OK;
|
||||
|
@ -996,6 +996,15 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
virtual void AfterProcessTask(uint32_t aRecursionDepth) override
|
||||
{
|
||||
// Only perform the Promise microtask checkpoint on the outermost event
|
||||
// loop. Don't run it, for example, during sync XHR or importScripts.
|
||||
if (aRecursionDepth == 2) {
|
||||
CycleCollectedJSRuntime::AfterProcessTask(aRecursionDepth);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
WorkerPrivate* mWorkerPrivate;
|
||||
};
|
||||
|
@ -2588,22 +2588,6 @@ WorkerPrivate::SyncLoopInfo::SyncLoopInfo(EventTarget* aEventTarget)
|
||||
{
|
||||
}
|
||||
|
||||
struct WorkerPrivate::PreemptingRunnableInfo final
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> mRunnable;
|
||||
uint32_t mRecursionDepth;
|
||||
|
||||
PreemptingRunnableInfo()
|
||||
{
|
||||
MOZ_COUNT_CTOR(WorkerPrivate::PreemptingRunnableInfo);
|
||||
}
|
||||
|
||||
~PreemptingRunnableInfo()
|
||||
{
|
||||
MOZ_COUNT_DTOR(WorkerPrivate::PreemptingRunnableInfo);
|
||||
}
|
||||
};
|
||||
|
||||
template <class Derived>
|
||||
nsIDocument*
|
||||
WorkerPrivateParent<Derived>::GetDocument() const
|
||||
@ -5187,10 +5171,6 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
|
||||
// Process a single runnable from the main queue.
|
||||
MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(mThread, false));
|
||||
|
||||
// Only perform the Promise microtask checkpoint on the outermost event
|
||||
// loop. Don't run it, for example, during sync XHR or importScripts.
|
||||
(void)Promise::PerformMicroTaskCheckpoint();
|
||||
|
||||
normalRunnablesPending = NS_HasPendingEvents(mThread);
|
||||
if (normalRunnablesPending && GlobalScope()) {
|
||||
// Now *might* be a good time to GC. Let the JS engine make the decision.
|
||||
@ -5210,85 +5190,28 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
|
||||
}
|
||||
|
||||
void
|
||||
WorkerPrivate::OnProcessNextEvent(uint32_t aRecursionDepth)
|
||||
WorkerPrivate::OnProcessNextEvent()
|
||||
{
|
||||
AssertIsOnWorkerThread();
|
||||
MOZ_ASSERT(aRecursionDepth);
|
||||
|
||||
uint32_t recursionDepth = CycleCollectedJSRuntime::Get()->RecursionDepth();
|
||||
MOZ_ASSERT(recursionDepth);
|
||||
|
||||
// Normally we process control runnables in DoRunLoop or RunCurrentSyncLoop.
|
||||
// However, it's possible that non-worker C++ could spin its own nested event
|
||||
// loop, and in that case we must ensure that we continue to process control
|
||||
// runnables here.
|
||||
if (aRecursionDepth > 1 &&
|
||||
mSyncLoopStack.Length() < aRecursionDepth - 1) {
|
||||
if (recursionDepth > 1 &&
|
||||
mSyncLoopStack.Length() < recursionDepth - 1) {
|
||||
ProcessAllControlRunnables();
|
||||
}
|
||||
|
||||
// Run any preempting runnables that match this depth.
|
||||
if (!mPreemptingRunnableInfos.IsEmpty()) {
|
||||
nsTArray<PreemptingRunnableInfo> pendingRunnableInfos;
|
||||
|
||||
for (uint32_t index = 0;
|
||||
index < mPreemptingRunnableInfos.Length();
|
||||
index++) {
|
||||
PreemptingRunnableInfo& preemptingRunnableInfo =
|
||||
mPreemptingRunnableInfos[index];
|
||||
|
||||
if (preemptingRunnableInfo.mRecursionDepth == aRecursionDepth) {
|
||||
preemptingRunnableInfo.mRunnable->Run();
|
||||
preemptingRunnableInfo.mRunnable = nullptr;
|
||||
} else {
|
||||
PreemptingRunnableInfo* pending = pendingRunnableInfos.AppendElement();
|
||||
pending->mRunnable.swap(preemptingRunnableInfo.mRunnable);
|
||||
pending->mRecursionDepth = preemptingRunnableInfo.mRecursionDepth;
|
||||
}
|
||||
}
|
||||
|
||||
mPreemptingRunnableInfos.SwapElements(pendingRunnableInfos);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WorkerPrivate::AfterProcessNextEvent(uint32_t aRecursionDepth)
|
||||
WorkerPrivate::AfterProcessNextEvent()
|
||||
{
|
||||
AssertIsOnWorkerThread();
|
||||
MOZ_ASSERT(aRecursionDepth);
|
||||
}
|
||||
|
||||
bool
|
||||
WorkerPrivate::RunBeforeNextEvent(nsIRunnable* aRunnable)
|
||||
{
|
||||
AssertIsOnWorkerThread();
|
||||
MOZ_ASSERT(aRunnable);
|
||||
MOZ_ASSERT_IF(!mPreemptingRunnableInfos.IsEmpty(),
|
||||
NS_HasPendingEvents(mThread));
|
||||
|
||||
const uint32_t recursionDepth =
|
||||
mThread->RecursionDepth(WorkerThreadFriendKey());
|
||||
|
||||
PreemptingRunnableInfo* preemptingRunnableInfo =
|
||||
mPreemptingRunnableInfos.AppendElement();
|
||||
|
||||
preemptingRunnableInfo->mRunnable = aRunnable;
|
||||
|
||||
// Due to the weird way that the thread recursion counter is implemented we
|
||||
// subtract one from the recursion level if we have one.
|
||||
preemptingRunnableInfo->mRecursionDepth =
|
||||
recursionDepth ? recursionDepth - 1 : 0;
|
||||
|
||||
// Ensure that we have a pending event so that the runnable will be guaranteed
|
||||
// to run.
|
||||
if (mPreemptingRunnableInfos.Length() == 1 && !NS_HasPendingEvents(mThread)) {
|
||||
nsRefPtr<DummyRunnable> dummyRunnable = new DummyRunnable(this);
|
||||
if (NS_FAILED(Dispatch(dummyRunnable.forget()))) {
|
||||
NS_WARNING("RunBeforeNextEvent called after the thread is shutting "
|
||||
"down!");
|
||||
mPreemptingRunnableInfos.Clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
MOZ_ASSERT(CycleCollectedJSRuntime::Get()->RecursionDepth());
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -949,9 +949,6 @@ class WorkerPrivate : public WorkerPrivateParent<WorkerPrivate>
|
||||
// modifications are done with mMutex held *only* in DEBUG builds.
|
||||
nsTArray<nsAutoPtr<SyncLoopInfo>> mSyncLoopStack;
|
||||
|
||||
struct PreemptingRunnableInfo;
|
||||
nsTArray<PreemptingRunnableInfo> mPreemptingRunnableInfos;
|
||||
|
||||
nsCOMPtr<nsITimer> mTimer;
|
||||
|
||||
nsCOMPtr<nsITimer> mGCTimer;
|
||||
@ -1362,10 +1359,10 @@ public:
|
||||
ClearMainEventQueue(WorkerRanOrNot aRanOrNot);
|
||||
|
||||
void
|
||||
OnProcessNextEvent(uint32_t aRecursionDepth);
|
||||
OnProcessNextEvent();
|
||||
|
||||
void
|
||||
AfterProcessNextEvent(uint32_t aRecursionDepth);
|
||||
AfterProcessNextEvent();
|
||||
|
||||
void
|
||||
AssertValidSyncLoop(nsIEventTarget* aSyncLoopTarget)
|
||||
@ -1392,11 +1389,6 @@ public:
|
||||
return mWorkerScriptExecutedSuccessfully;
|
||||
}
|
||||
|
||||
// Just like nsIAppShell::RunBeforeNextEvent. May only be called on the worker
|
||||
// thread.
|
||||
bool
|
||||
RunBeforeNextEvent(nsIRunnable* aRunnable);
|
||||
|
||||
void
|
||||
MaybeDispatchLoadFailedRunnable();
|
||||
|
||||
|
@ -310,8 +310,7 @@ WorkerThread::Observer::OnDispatchedEvent(nsIThreadInternal* /* aThread */)
|
||||
|
||||
NS_IMETHODIMP
|
||||
WorkerThread::Observer::OnProcessNextEvent(nsIThreadInternal* /* aThread */,
|
||||
bool aMayWait,
|
||||
uint32_t aRecursionDepth)
|
||||
bool aMayWait)
|
||||
{
|
||||
mWorkerPrivate->AssertIsOnWorkerThread();
|
||||
|
||||
@ -321,23 +320,22 @@ WorkerThread::Observer::OnProcessNextEvent(nsIThreadInternal* /* aThread */,
|
||||
// PrimaryWorkerRunnable::Run() and don't want to process the event in
|
||||
// mWorkerPrivate yet.
|
||||
if (aMayWait) {
|
||||
MOZ_ASSERT(aRecursionDepth == 2);
|
||||
MOZ_ASSERT(CycleCollectedJSRuntime::Get()->RecursionDepth() == 2);
|
||||
MOZ_ASSERT(!BackgroundChild::GetForCurrentThread());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mWorkerPrivate->OnProcessNextEvent(aRecursionDepth);
|
||||
mWorkerPrivate->OnProcessNextEvent();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
WorkerThread::Observer::AfterProcessNextEvent(nsIThreadInternal* /* aThread */,
|
||||
uint32_t aRecursionDepth,
|
||||
bool /* aEventWasProcessed */)
|
||||
{
|
||||
mWorkerPrivate->AssertIsOnWorkerThread();
|
||||
|
||||
mWorkerPrivate->AfterProcessNextEvent(aRecursionDepth);
|
||||
mWorkerPrivate->AfterProcessNextEvent();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,11 @@ function ok(a, msg) {
|
||||
postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
|
||||
}
|
||||
|
||||
function todo(a, msg) {
|
||||
dump("TODO: " + !a + " => " + a + " " + msg + "\n");
|
||||
postMessage({type: 'status', status: !a, msg: a + ": " + msg });
|
||||
}
|
||||
|
||||
function is(a, b, msg) {
|
||||
dump("IS: " + (a===b) + " => " + a + " | " + b + " " + msg + "\n");
|
||||
postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg });
|
||||
@ -148,7 +153,7 @@ function promiseAsync_ResolveThenTimeout() {
|
||||
ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
|
||||
}
|
||||
|
||||
function promiseAsync_SyncHXRAndImportScripts()
|
||||
function promiseAsync_SyncXHRAndImportScripts()
|
||||
{
|
||||
var handlerExecuted = false;
|
||||
|
||||
@ -790,7 +795,7 @@ var tests = [
|
||||
promiseAsync_TimeoutResolveThen,
|
||||
promiseAsync_ResolveTimeoutThen,
|
||||
promiseAsync_ResolveThenTimeout,
|
||||
promiseAsync_SyncHXRAndImportScripts,
|
||||
promiseAsync_SyncXHRAndImportScripts,
|
||||
promiseDoubleThen,
|
||||
promiseThenException,
|
||||
promiseThenCatchThen,
|
||||
|
@ -55,8 +55,7 @@ function cleanUpAndFinish() {
|
||||
|
||||
function frameUpdate(aRequest) {
|
||||
if (!gDispatched) {
|
||||
var util = window.getInterface(Ci.nsIDOMWindowUtils);
|
||||
util.runBeforeNextEvent(function() {
|
||||
Promise.resolve().then(function() {
|
||||
gRanEvent = true;
|
||||
});
|
||||
gDispatched = true;
|
||||
|
@ -433,15 +433,13 @@ MessagePumpForNonMainUIThreads::OnDispatchedEvent(nsIThreadInternal *thread)
|
||||
|
||||
NS_IMETHODIMP
|
||||
MessagePumpForNonMainUIThreads::OnProcessNextEvent(nsIThreadInternal *thread,
|
||||
bool mayWait,
|
||||
uint32_t recursionDepth)
|
||||
bool mayWait)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MessagePumpForNonMainUIThreads::AfterProcessNextEvent(nsIThreadInternal *thread,
|
||||
uint32_t recursionDepth,
|
||||
bool eventWasProcessed)
|
||||
{
|
||||
return NS_OK;
|
||||
|
@ -3574,6 +3574,59 @@ XPCJSRuntime::NoteCustomGCThingXPCOMChildren(const js::Class* clasp, JSObject* o
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
XPCJSRuntime::BeforeProcessTask(bool aMightBlock)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
// If ProcessNextEvent was called during a Promise "then" callback, we
|
||||
// must process any pending microtasks before blocking in the event loop,
|
||||
// otherwise we may deadlock until an event enters the queue later.
|
||||
if (aMightBlock) {
|
||||
if (Promise::PerformMicroTaskCheckpoint()) {
|
||||
// If any microtask was processed, we post a dummy event in order to
|
||||
// force the ProcessNextEvent call not to block. This is required
|
||||
// to support nested event loops implemented using a pattern like
|
||||
// "while (condition) thread.processNextEvent(true)", in case the
|
||||
// condition is triggered here by a Promise "then" callback.
|
||||
|
||||
class DummyRunnable : public nsRunnable {
|
||||
public:
|
||||
NS_IMETHOD Run() { return NS_OK; }
|
||||
};
|
||||
|
||||
NS_DispatchToMainThread(new DummyRunnable());
|
||||
}
|
||||
}
|
||||
|
||||
// Start the slow script timer.
|
||||
mSlowScriptCheckpoint = mozilla::TimeStamp::NowLoRes();
|
||||
mSlowScriptSecondHalf = false;
|
||||
js::ResetStopwatches(Get()->Runtime());
|
||||
|
||||
// Push a null JSContext so that we don't see any script during
|
||||
// event processing.
|
||||
PushNullJSContext();
|
||||
|
||||
CycleCollectedJSRuntime::BeforeProcessTask(aMightBlock);
|
||||
}
|
||||
|
||||
void
|
||||
XPCJSRuntime::AfterProcessTask(uint32_t aNewRecursionDepth)
|
||||
{
|
||||
// Now that we're back to the event loop, reset the slow script checkpoint.
|
||||
mSlowScriptCheckpoint = mozilla::TimeStamp();
|
||||
mSlowScriptSecondHalf = false;
|
||||
|
||||
// Call cycle collector occasionally.
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
nsJSContext::MaybePokeCC();
|
||||
|
||||
CycleCollectedJSRuntime::AfterProcessTask(aNewRecursionDepth);
|
||||
|
||||
PopNullJSContext();
|
||||
}
|
||||
|
||||
/***************************************************************************/
|
||||
|
||||
void
|
||||
|
@ -26,7 +26,6 @@
|
||||
|
||||
#include "nsDOMMutationObserver.h"
|
||||
#include "nsICycleCollectorListener.h"
|
||||
#include "nsThread.h"
|
||||
#include "mozilla/XPTInterfaceInfoManager.h"
|
||||
#include "nsIObjectInputStream.h"
|
||||
#include "nsIObjectOutputStream.h"
|
||||
@ -37,9 +36,7 @@ using namespace mozilla::dom;
|
||||
using namespace xpc;
|
||||
using namespace JS;
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsXPConnect,
|
||||
nsIXPConnect,
|
||||
nsIThreadObserver)
|
||||
NS_IMPL_ISUPPORTS(nsXPConnect, nsIXPConnect)
|
||||
|
||||
nsXPConnect* nsXPConnect::gSelf = nullptr;
|
||||
bool nsXPConnect::gOnceAliveNowDead = false;
|
||||
@ -61,8 +58,7 @@ const char XPC_XPCONNECT_CONTRACTID[] = "@mozilla.org/js/xpc/XPConnect;1";
|
||||
|
||||
nsXPConnect::nsXPConnect()
|
||||
: mRuntime(nullptr),
|
||||
mShuttingDown(false),
|
||||
mEventDepth(0)
|
||||
mShuttingDown(false)
|
||||
{
|
||||
mRuntime = XPCJSRuntime::newXPCJSRuntime(this);
|
||||
|
||||
@ -120,11 +116,6 @@ nsXPConnect::InitStatics()
|
||||
// balanced by explicit call to ReleaseXPConnectSingleton()
|
||||
NS_ADDREF(gSelf);
|
||||
|
||||
// Set XPConnect as the main thread observer.
|
||||
if (NS_FAILED(nsThread::SetMainThreadObserver(gSelf))) {
|
||||
MOZ_CRASH();
|
||||
}
|
||||
|
||||
// Fire up the SSM.
|
||||
nsScriptSecurityManager::InitStatics();
|
||||
gScriptSecurityManager = nsScriptSecurityManager::GetScriptSecurityManager();
|
||||
@ -152,8 +143,6 @@ nsXPConnect::ReleaseXPConnectSingleton()
|
||||
{
|
||||
nsXPConnect* xpc = gSelf;
|
||||
if (xpc) {
|
||||
nsThread::SetMainThreadObserver(nullptr);
|
||||
|
||||
nsrefcnt cnt;
|
||||
NS_RELEASE2(xpc, cnt);
|
||||
}
|
||||
@ -933,81 +922,6 @@ nsXPConnect::JSToVariant(JSContext* ctx, HandleValue value, nsIVariant** _retval
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class DummyRunnable : public nsRunnable {
|
||||
public:
|
||||
NS_IMETHOD Run() { return NS_OK; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsXPConnect::OnProcessNextEvent(nsIThreadInternal* aThread, bool aMayWait,
|
||||
uint32_t aRecursionDepth)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
// If ProcessNextEvent was called during a Promise "then" callback, we
|
||||
// must process any pending microtasks before blocking in the event loop,
|
||||
// otherwise we may deadlock until an event enters the queue later.
|
||||
if (aMayWait) {
|
||||
if (Promise::PerformMicroTaskCheckpoint()) {
|
||||
// If any microtask was processed, we post a dummy event in order to
|
||||
// force the ProcessNextEvent call not to block. This is required
|
||||
// to support nested event loops implemented using a pattern like
|
||||
// "while (condition) thread.processNextEvent(true)", in case the
|
||||
// condition is triggered here by a Promise "then" callback.
|
||||
NS_DispatchToMainThread(new DummyRunnable());
|
||||
}
|
||||
}
|
||||
|
||||
// Record this event.
|
||||
mEventDepth++;
|
||||
|
||||
// Start the slow script timer.
|
||||
mRuntime->OnProcessNextEvent();
|
||||
|
||||
// Push a null JSContext so that we don't see any script during
|
||||
// event processing.
|
||||
bool ok = PushNullJSContext();
|
||||
NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsXPConnect::AfterProcessNextEvent(nsIThreadInternal* aThread,
|
||||
uint32_t aRecursionDepth,
|
||||
bool aEventWasProcessed)
|
||||
{
|
||||
// Watch out for unpaired events during observer registration.
|
||||
if (MOZ_UNLIKELY(mEventDepth == 0))
|
||||
return NS_OK;
|
||||
mEventDepth--;
|
||||
|
||||
// Now that we're back to the event loop, reset the slow script checkpoint.
|
||||
mRuntime->OnAfterProcessNextEvent();
|
||||
|
||||
// Call cycle collector occasionally.
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
nsJSContext::MaybePokeCC();
|
||||
|
||||
nsContentUtils::PerformMainThreadMicroTaskCheckpoint();
|
||||
|
||||
Promise::PerformMicroTaskCheckpoint();
|
||||
|
||||
PopNullJSContext();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsXPConnect::OnDispatchedEvent(nsIThreadInternal* aThread)
|
||||
{
|
||||
NS_NOTREACHED("Why tell us?");
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsXPConnect::SetReportAllJSExceptions(bool newval)
|
||||
{
|
||||
|
@ -153,7 +153,6 @@
|
||||
#include "nsJSPrincipals.h"
|
||||
#include "nsIScriptObjectPrincipal.h"
|
||||
#include "xpcObjectHelper.h"
|
||||
#include "nsIThreadInternal.h"
|
||||
|
||||
#include "SandboxPrivate.h"
|
||||
#include "BackstagePass.h"
|
||||
@ -240,14 +239,12 @@ static inline bool IS_WN_REFLECTOR(JSObject* obj)
|
||||
// returned as function call result values they are not addref'd. Exceptions
|
||||
// to this rule are noted explicitly.
|
||||
|
||||
class nsXPConnect final : public nsIXPConnect,
|
||||
public nsIThreadObserver
|
||||
class nsXPConnect final : public nsIXPConnect
|
||||
{
|
||||
public:
|
||||
// all the interface method declarations...
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIXPCONNECT
|
||||
NS_DECL_NSITHREADOBSERVER
|
||||
|
||||
// non-interface implementation
|
||||
public:
|
||||
@ -324,13 +321,6 @@ private:
|
||||
XPCJSRuntime* mRuntime;
|
||||
bool mShuttingDown;
|
||||
|
||||
// nsIThreadInternal doesn't remember which observers it called
|
||||
// OnProcessNextEvent on when it gets around to calling AfterProcessNextEvent.
|
||||
// So if XPConnect gets initialized mid-event (which can happen), we'll get
|
||||
// an 'after' notification without getting an 'on' notification. If we don't
|
||||
// watch out for this, we'll do an unmatched |pop| on the context stack.
|
||||
uint16_t mEventDepth;
|
||||
|
||||
static uint32_t gReportAllJSExceptions;
|
||||
|
||||
public:
|
||||
@ -489,6 +479,9 @@ public:
|
||||
NoteCustomGCThingXPCOMChildren(const js::Class* aClasp, JSObject* aObj,
|
||||
nsCycleCollectionTraversalCallback& aCb) const override;
|
||||
|
||||
virtual void BeforeProcessTask(bool aMightBlock) override;
|
||||
virtual void AfterProcessTask(uint32_t aNewRecursionDepth) override;
|
||||
|
||||
/**
|
||||
* Infrastructure for classes that need to defer part of the finalization
|
||||
* until after the GC has run, for example for objects that we don't want to
|
||||
@ -615,16 +608,6 @@ public:
|
||||
|
||||
PRTime GetWatchdogTimestamp(WatchdogTimestampCategory aCategory);
|
||||
|
||||
void OnProcessNextEvent() {
|
||||
mSlowScriptCheckpoint = mozilla::TimeStamp::NowLoRes();
|
||||
mSlowScriptSecondHalf = false;
|
||||
js::ResetStopwatches(Get()->Runtime());
|
||||
}
|
||||
void OnAfterProcessNextEvent() {
|
||||
mSlowScriptCheckpoint = mozilla::TimeStamp();
|
||||
mSlowScriptSecondHalf = false;
|
||||
}
|
||||
|
||||
nsTArray<nsXPCWrappedJS*>& WrappedJSToReleaseArray() { return mWrappedJSToReleaseArray; }
|
||||
|
||||
private:
|
||||
|
@ -431,9 +431,9 @@ SheetLoadData::OnDispatchedEvent(nsIThreadInternal* aThread)
|
||||
|
||||
NS_IMETHODIMP
|
||||
SheetLoadData::OnProcessNextEvent(nsIThreadInternal* aThread,
|
||||
bool aMayWait,
|
||||
uint32_t aRecursionDepth)
|
||||
bool aMayWait)
|
||||
{
|
||||
// XXXkhuey this is insane!
|
||||
// We want to fire our load even before or after event processing,
|
||||
// whichever comes first.
|
||||
FireLoadEvent(aThread);
|
||||
@ -442,9 +442,9 @@ SheetLoadData::OnProcessNextEvent(nsIThreadInternal* aThread,
|
||||
|
||||
NS_IMETHODIMP
|
||||
SheetLoadData::AfterProcessNextEvent(nsIThreadInternal* aThread,
|
||||
uint32_t aRecursionDepth,
|
||||
bool aEventWasProcessed)
|
||||
{
|
||||
// XXXkhuey this too!
|
||||
// We want to fire our load even before or after event processing,
|
||||
// whichever comes first.
|
||||
FireLoadEvent(aThread);
|
||||
|
@ -763,14 +763,13 @@ nsSocketTransportService::OnDispatchedEvent(nsIThreadInternal *thread)
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsSocketTransportService::OnProcessNextEvent(nsIThreadInternal *thread,
|
||||
bool mayWait, uint32_t depth)
|
||||
bool mayWait)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsSocketTransportService::AfterProcessNextEvent(nsIThreadInternal* thread,
|
||||
uint32_t depth,
|
||||
bool eventWasProcessed)
|
||||
{
|
||||
return NS_OK;
|
||||
|
@ -315,12 +315,12 @@ NS_IMETHODIMP CacheIOThread::OnDispatchedEvent(nsIThreadInternal *thread)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP CacheIOThread::OnProcessNextEvent(nsIThreadInternal *thread, bool mayWait, uint32_t recursionDepth)
|
||||
NS_IMETHODIMP CacheIOThread::OnProcessNextEvent(nsIThreadInternal *thread, bool mayWait)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP CacheIOThread::AfterProcessNextEvent(nsIThreadInternal *thread, uint32_t recursionDepth,
|
||||
NS_IMETHODIMP CacheIOThread::AfterProcessNextEvent(nsIThreadInternal *thread,
|
||||
bool eventWasProcessed)
|
||||
{
|
||||
return NS_OK;
|
||||
|
@ -169,16 +169,9 @@ ScreenProxy::InvalidateCacheOnNextTick()
|
||||
|
||||
mCacheWillInvalidate = true;
|
||||
|
||||
nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
|
||||
if (appShell) {
|
||||
nsCOMPtr<nsIRunnable> r =
|
||||
NS_NewRunnableMethod(this, &ScreenProxy::InvalidateCache);
|
||||
appShell->RunInStableState(r.forget());
|
||||
} else {
|
||||
// It's pretty bad news if we can't get the appshell. In that case,
|
||||
// let's just invalidate the cache right away.
|
||||
InvalidateCache();
|
||||
}
|
||||
nsCOMPtr<nsIRunnable> r =
|
||||
NS_NewRunnableMethod(this, &ScreenProxy::InvalidateCache);
|
||||
nsContentUtils::RunInStableState(r.forget());
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -35,10 +35,8 @@ public:
|
||||
|
||||
NS_IMETHOD Run(void);
|
||||
NS_IMETHOD Exit(void);
|
||||
NS_IMETHOD OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait,
|
||||
uint32_t aRecursionDepth);
|
||||
NS_IMETHOD OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait);
|
||||
NS_IMETHOD AfterProcessNextEvent(nsIThreadInternal *aThread,
|
||||
uint32_t aRecursionDepth,
|
||||
bool aEventWasProcessed);
|
||||
|
||||
// public only to be visible to Objective-C code that must call it
|
||||
|
@ -735,8 +735,7 @@ nsAppShell::Exit(void)
|
||||
//
|
||||
// public
|
||||
NS_IMETHODIMP
|
||||
nsAppShell::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait,
|
||||
uint32_t aRecursionDepth)
|
||||
nsAppShell::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait)
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
||||
|
||||
@ -746,7 +745,7 @@ nsAppShell::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait,
|
||||
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
|
||||
::CFArrayAppendValue(mAutoreleasePools, pool);
|
||||
|
||||
return nsBaseAppShell::OnProcessNextEvent(aThread, aMayWait, aRecursionDepth);
|
||||
return nsBaseAppShell::OnProcessNextEvent(aThread, aMayWait);
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
|
||||
}
|
||||
@ -760,7 +759,6 @@ nsAppShell::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait,
|
||||
// public
|
||||
NS_IMETHODIMP
|
||||
nsAppShell::AfterProcessNextEvent(nsIThreadInternal *aThread,
|
||||
uint32_t aRecursionDepth,
|
||||
bool aEventWasProcessed)
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
||||
@ -775,8 +773,7 @@ nsAppShell::AfterProcessNextEvent(nsIThreadInternal *aThread,
|
||||
::CFArrayRemoveValueAtIndex(mAutoreleasePools, count - 1);
|
||||
[pool release];
|
||||
|
||||
return nsBaseAppShell::AfterProcessNextEvent(aThread, aRecursionDepth,
|
||||
aEventWasProcessed);
|
||||
return nsBaseAppShell::AfterProcessNextEvent(aThread, aEventWasProcessed);
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
|
||||
}
|
||||
|
@ -31,7 +31,6 @@ nsBaseAppShell::nsBaseAppShell()
|
||||
, mSwitchTime(0)
|
||||
, mLastNativeEventTime(0)
|
||||
, mEventloopNestingState(eEventloopNone)
|
||||
, mRunningSyncSections(false)
|
||||
, mRunning(false)
|
||||
, mExiting(false)
|
||||
, mBlockNativeEvent(false)
|
||||
@ -40,7 +39,6 @@ nsBaseAppShell::nsBaseAppShell()
|
||||
|
||||
nsBaseAppShell::~nsBaseAppShell()
|
||||
{
|
||||
NS_ASSERTION(mSyncSections.IsEmpty(), "Must have run all sync sections");
|
||||
}
|
||||
|
||||
nsresult
|
||||
@ -120,7 +118,7 @@ nsBaseAppShell::DoProcessMoreGeckoEvents()
|
||||
|
||||
// Main thread via OnProcessNextEvent below
|
||||
bool
|
||||
nsBaseAppShell::DoProcessNextNativeEvent(bool mayWait, uint32_t recursionDepth)
|
||||
nsBaseAppShell::DoProcessNextNativeEvent(bool mayWait)
|
||||
{
|
||||
// The next native event to be processed may trigger our NativeEventCallback,
|
||||
// in which case we do not want it to process any thread events since we'll
|
||||
@ -137,14 +135,7 @@ nsBaseAppShell::DoProcessNextNativeEvent(bool mayWait, uint32_t recursionDepth)
|
||||
mEventloopNestingState = eEventloopXPCOM;
|
||||
|
||||
IncrementEventloopNestingLevel();
|
||||
|
||||
bool result = ProcessNextNativeEvent(mayWait);
|
||||
|
||||
// Make sure that any sync sections registered during this most recent event
|
||||
// are run now. This is not considered a stable state because we're not back
|
||||
// to the event loop yet.
|
||||
RunSyncSections(false, recursionDepth);
|
||||
|
||||
DecrementEventloopNestingLevel();
|
||||
|
||||
mEventloopNestingState = prevVal;
|
||||
@ -239,8 +230,7 @@ nsBaseAppShell::OnDispatchedEvent(nsIThreadInternal *thr)
|
||||
|
||||
// Called from the main thread
|
||||
NS_IMETHODIMP
|
||||
nsBaseAppShell::OnProcessNextEvent(nsIThreadInternal *thr, bool mayWait,
|
||||
uint32_t recursionDepth)
|
||||
nsBaseAppShell::OnProcessNextEvent(nsIThreadInternal *thr, bool mayWait)
|
||||
{
|
||||
if (mBlockNativeEvent) {
|
||||
if (!mayWait)
|
||||
@ -278,13 +268,13 @@ nsBaseAppShell::OnProcessNextEvent(nsIThreadInternal *thr, bool mayWait,
|
||||
bool keepGoing;
|
||||
do {
|
||||
mLastNativeEventTime = now;
|
||||
keepGoing = DoProcessNextNativeEvent(false, recursionDepth);
|
||||
keepGoing = DoProcessNextNativeEvent(false);
|
||||
} while (keepGoing && ((now = PR_IntervalNow()) - start) < limit);
|
||||
} else {
|
||||
// Avoid starving native events completely when in performance mode
|
||||
if (start - mLastNativeEventTime > limit) {
|
||||
mLastNativeEventTime = start;
|
||||
DoProcessNextNativeEvent(false, recursionDepth);
|
||||
DoProcessNextNativeEvent(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -296,7 +286,7 @@ nsBaseAppShell::OnProcessNextEvent(nsIThreadInternal *thr, bool mayWait,
|
||||
mayWait = false;
|
||||
|
||||
mLastNativeEventTime = PR_IntervalNow();
|
||||
if (!DoProcessNextNativeEvent(mayWait, recursionDepth) || !mayWait)
|
||||
if (!DoProcessNextNativeEvent(mayWait) || !mayWait)
|
||||
break;
|
||||
}
|
||||
|
||||
@ -309,9 +299,6 @@ nsBaseAppShell::OnProcessNextEvent(nsIThreadInternal *thr, bool mayWait,
|
||||
DispatchDummyEvent(thr);
|
||||
}
|
||||
|
||||
// We're about to run an event, so we're in a stable state.
|
||||
RunSyncSections(true, recursionDepth);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -344,95 +331,11 @@ nsBaseAppShell::DecrementEventloopNestingLevel()
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
nsBaseAppShell::RunSyncSectionsInternal(bool aStable,
|
||||
uint32_t aThreadRecursionLevel)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
NS_ASSERTION(!mSyncSections.IsEmpty(), "Nothing to do!");
|
||||
|
||||
// We don't support re-entering sync sections. This effectively means that
|
||||
// sync sections may not spin the event loop.
|
||||
MOZ_RELEASE_ASSERT(!mRunningSyncSections);
|
||||
mRunningSyncSections = true;
|
||||
|
||||
// We've got synchronous sections. Run all of them that are are awaiting a
|
||||
// stable state if aStable is true (i.e. we really are in a stable state).
|
||||
// Also run the synchronous sections that are simply waiting for the right
|
||||
// combination of event loop nesting level and thread recursion level.
|
||||
// Note that a synchronous section could add another synchronous section, so
|
||||
// we don't remove elements from mSyncSections until all sections have been
|
||||
// run, or else we'll screw up our iteration. Any sync sections that are not
|
||||
// ready to be run are saved for later.
|
||||
|
||||
nsTArray<SyncSection> pendingSyncSections;
|
||||
|
||||
for (uint32_t i = 0; i < mSyncSections.Length(); i++) {
|
||||
SyncSection& section = mSyncSections[i];
|
||||
if ((aStable && section.mStable) ||
|
||||
(!section.mStable &&
|
||||
section.mEventloopNestingLevel == mEventloopNestingLevel &&
|
||||
section.mThreadRecursionLevel == aThreadRecursionLevel)) {
|
||||
section.mRunnable->Run();
|
||||
}
|
||||
else {
|
||||
// Add to pending list.
|
||||
SyncSection* pending = pendingSyncSections.AppendElement();
|
||||
section.Forget(pending);
|
||||
}
|
||||
}
|
||||
|
||||
mSyncSections.SwapElements(pendingSyncSections);
|
||||
mRunningSyncSections = false;
|
||||
}
|
||||
|
||||
void
|
||||
nsBaseAppShell::ScheduleSyncSection(already_AddRefed<nsIRunnable> aRunnable,
|
||||
bool aStable)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
|
||||
nsIThread* thread = NS_GetCurrentThread();
|
||||
|
||||
// Add this runnable to our list of synchronous sections.
|
||||
SyncSection* section = mSyncSections.AppendElement();
|
||||
section->mStable = aStable;
|
||||
section->mRunnable = aRunnable;
|
||||
|
||||
// If aStable is false then this synchronous section is supposed to run before
|
||||
// the next event at the current nesting level. Record the event loop nesting
|
||||
// level and the thread recursion level so that the synchronous section will
|
||||
// run at the proper time.
|
||||
if (!aStable) {
|
||||
section->mEventloopNestingLevel = mEventloopNestingLevel;
|
||||
|
||||
nsCOMPtr<nsIThreadInternal> threadInternal = do_QueryInterface(thread);
|
||||
NS_ASSERTION(threadInternal, "This should never fail!");
|
||||
|
||||
uint32_t recursionLevel;
|
||||
if (NS_FAILED(threadInternal->GetRecursionDepth(&recursionLevel))) {
|
||||
NS_ERROR("This should never fail!");
|
||||
}
|
||||
|
||||
// Due to the weird way that the thread recursion counter is implemented we
|
||||
// subtract one from the recursion level if we have one.
|
||||
section->mThreadRecursionLevel = recursionLevel ? recursionLevel - 1 : 0;
|
||||
}
|
||||
|
||||
// Ensure we've got a pending event, else the callbacks will never run.
|
||||
if (!NS_HasPendingEvents(thread) && !DispatchDummyEvent(thread)) {
|
||||
RunSyncSections(true, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Called from the main thread
|
||||
NS_IMETHODIMP
|
||||
nsBaseAppShell::AfterProcessNextEvent(nsIThreadInternal *thr,
|
||||
uint32_t recursionDepth,
|
||||
bool eventWasProcessed)
|
||||
{
|
||||
// We've just finished running an event, so we're in a stable state.
|
||||
RunSyncSections(true, recursionDepth);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -444,17 +347,3 @@ nsBaseAppShell::Observe(nsISupports *subject, const char *topic,
|
||||
Exit();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsBaseAppShell::RunInStableState(already_AddRefed<nsIRunnable> aRunnable)
|
||||
{
|
||||
ScheduleSyncSection(mozilla::Move(aRunnable), true);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsBaseAppShell::RunBeforeNextEvent(nsIRunnable* aRunnable)
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> runnable = aRunnable;
|
||||
ScheduleSyncSection(runnable.forget(), false);
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ class nsBaseAppShell : public nsIAppShell, public nsIThreadObserver,
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIAPPSHELL
|
||||
void RunInStableState(already_AddRefed<nsIRunnable> runnable) override;
|
||||
|
||||
NS_DECL_NSITHREADOBSERVER
|
||||
NS_DECL_NSIOBSERVER
|
||||
@ -77,45 +76,13 @@ protected:
|
||||
uint32_t mEventloopNestingLevel;
|
||||
|
||||
private:
|
||||
bool DoProcessNextNativeEvent(bool mayWait, uint32_t recursionDepth);
|
||||
bool DoProcessNextNativeEvent(bool mayWait);
|
||||
|
||||
bool DispatchDummyEvent(nsIThread* target);
|
||||
|
||||
void IncrementEventloopNestingLevel();
|
||||
void DecrementEventloopNestingLevel();
|
||||
|
||||
/**
|
||||
* Runs all synchronous sections which are queued up in mSyncSections.
|
||||
*/
|
||||
void RunSyncSectionsInternal(bool stable, uint32_t threadRecursionLevel);
|
||||
|
||||
void RunSyncSections(bool stable, uint32_t threadRecursionLevel)
|
||||
{
|
||||
if (!mSyncSections.IsEmpty()) {
|
||||
RunSyncSectionsInternal(stable, threadRecursionLevel);
|
||||
}
|
||||
}
|
||||
|
||||
void ScheduleSyncSection(already_AddRefed<nsIRunnable> runnable, bool stable);
|
||||
|
||||
struct SyncSection {
|
||||
SyncSection()
|
||||
: mStable(false), mEventloopNestingLevel(0), mThreadRecursionLevel(0)
|
||||
{ }
|
||||
|
||||
void Forget(SyncSection* other) {
|
||||
other->mStable = mStable;
|
||||
other->mEventloopNestingLevel = mEventloopNestingLevel;
|
||||
other->mThreadRecursionLevel = mThreadRecursionLevel;
|
||||
other->mRunnable = mRunnable.forget();
|
||||
}
|
||||
|
||||
bool mStable;
|
||||
uint32_t mEventloopNestingLevel;
|
||||
uint32_t mThreadRecursionLevel;
|
||||
nsCOMPtr<nsIRunnable> mRunnable;
|
||||
};
|
||||
|
||||
nsCOMPtr<nsIRunnable> mDummyEvent;
|
||||
/**
|
||||
* mBlockedWait points back to a slot that controls the wait loop in
|
||||
@ -135,8 +102,6 @@ private:
|
||||
eEventloopOther // innermost native event loop is a native library/plugin etc
|
||||
};
|
||||
EventloopNestingState mEventloopNestingState;
|
||||
nsTArray<SyncSection> mSyncSections;
|
||||
bool mRunningSyncSections;
|
||||
bool mRunning;
|
||||
bool mExiting;
|
||||
/**
|
||||
|
@ -15,7 +15,7 @@ template <class T> struct already_AddRefed;
|
||||
* Interface for the native event system layer. This interface is designed
|
||||
* to be used on the main application thread only.
|
||||
*/
|
||||
[uuid(3d09973e-3975-4fd4-b103-276300cc8437)]
|
||||
[uuid(7cd5c71d-223b-4afe-931d-5eedb1f2b01f)]
|
||||
interface nsIAppShell : nsISupports
|
||||
{
|
||||
/**
|
||||
@ -73,26 +73,4 @@ interface nsIAppShell : nsISupports
|
||||
* The current event loop nesting level.
|
||||
*/
|
||||
readonly attribute unsigned long eventloopNestingLevel;
|
||||
|
||||
%{ C++
|
||||
/**
|
||||
* Add a "synchronous section", in the form of an nsIRunnable run once the
|
||||
* event loop has reached a "stable state". |runnable| must not cause any
|
||||
* queued events to be processed (i.e. must not spin the event loop). We've
|
||||
* reached a stable state when the currently executing task/event has
|
||||
* finished, see:
|
||||
* http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#synchronous-section
|
||||
* In practice this runs aRunnable once the currently executing event
|
||||
* finishes. If called multiple times per task/event, all the runnables will
|
||||
* be executed, in the order in which runInStableState() was called.
|
||||
*/
|
||||
virtual void RunInStableState(already_AddRefed<nsIRunnable> runnable) = 0;
|
||||
%}
|
||||
|
||||
/**
|
||||
* Run the given runnable before the next iteration of the event loop (this
|
||||
* includes native events too). If a nested loop is spawned within the current
|
||||
* event then the runnable will not be run until that loop has terminated.
|
||||
*/
|
||||
void runBeforeNextEvent(in nsIRunnable runnable);
|
||||
};
|
||||
|
@ -198,16 +198,9 @@ nsScreenManagerProxy::InvalidateCacheOnNextTick()
|
||||
|
||||
mCacheWillInvalidate = true;
|
||||
|
||||
nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
|
||||
if (appShell) {
|
||||
nsCOMPtr<nsIRunnable> r =
|
||||
NS_NewRunnableMethod(this, &nsScreenManagerProxy::InvalidateCache);
|
||||
appShell->RunInStableState(r.forget());
|
||||
} else {
|
||||
// It's pretty bad news if we can't get the appshell. In that case,
|
||||
// let's just invalidate the cache right away.
|
||||
InvalidateCache();
|
||||
}
|
||||
nsCOMPtr<nsIRunnable> r =
|
||||
NS_NewRunnableMethod(this, &nsScreenManagerProxy::InvalidateCache);
|
||||
nsContentUtils::RunInStableState(r.forget());
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -8,10 +8,6 @@ XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']
|
||||
MOCHITEST_MANIFESTS += ['mochitest.ini']
|
||||
MOCHITEST_CHROME_MANIFESTS += ['chrome.ini']
|
||||
|
||||
GeckoCppUnitTests([
|
||||
'TestAppShellSteadyState',
|
||||
])
|
||||
|
||||
FAIL_ON_WARNINGS = True
|
||||
|
||||
# if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
|
||||
|
@ -63,6 +63,7 @@
|
||||
#include "mozilla/dom/BindingUtils.h"
|
||||
#include "mozilla/DebuggerOnGCRunnable.h"
|
||||
#include "mozilla/dom/DOMJSClass.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
#include "jsprf.h"
|
||||
#include "js/Debug.h"
|
||||
@ -77,6 +78,7 @@
|
||||
#endif
|
||||
|
||||
#include "nsIException.h"
|
||||
#include "nsThread.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "xpcpublic.h"
|
||||
|
||||
@ -402,9 +404,18 @@ CycleCollectedJSRuntime::CycleCollectedJSRuntime(JSRuntime* aParentRuntime,
|
||||
, mJSRuntime(nullptr)
|
||||
, mPrevGCSliceCallback(nullptr)
|
||||
, mJSHolders(256)
|
||||
, mDoingStableStates(false)
|
||||
, mOutOfMemoryState(OOMState::OK)
|
||||
, mLargeAllocationFailureState(OOMState::OK)
|
||||
{
|
||||
nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
|
||||
mOwningThread = thread.forget().downcast<nsThread>().take();
|
||||
MOZ_RELEASE_ASSERT(mOwningThread);
|
||||
|
||||
mOwningThread->SetScriptObserver(this);
|
||||
// The main thread has a base recursion depth of 0, workers of 1.
|
||||
mBaseRecursionDepth = RecursionDepth();
|
||||
|
||||
mozilla::dom::InitScriptSettings();
|
||||
|
||||
mJSRuntime = JS_NewRuntime(aMaxBytes, aMaxNurseryBytes, aParentRuntime);
|
||||
@ -440,6 +451,13 @@ CycleCollectedJSRuntime::~CycleCollectedJSRuntime()
|
||||
MOZ_ASSERT(mJSRuntime);
|
||||
MOZ_ASSERT(!mDeferredFinalizerTable.Count());
|
||||
|
||||
// Last chance to process any events.
|
||||
ProcessMetastableStateQueue(mBaseRecursionDepth);
|
||||
MOZ_ASSERT(mMetastableStateEvents.IsEmpty());
|
||||
|
||||
ProcessStableStateQueue();
|
||||
MOZ_ASSERT(mStableStateEvents.IsEmpty());
|
||||
|
||||
// Clear mPendingException first, since it might be cycle collected.
|
||||
mPendingException = nullptr;
|
||||
|
||||
@ -448,6 +466,9 @@ CycleCollectedJSRuntime::~CycleCollectedJSRuntime()
|
||||
nsCycleCollector_forgetJSRuntime();
|
||||
|
||||
mozilla::dom::DestroyScriptSettings();
|
||||
|
||||
mOwningThread->SetScriptObserver(nullptr);
|
||||
NS_RELEASE(mOwningThread);
|
||||
}
|
||||
|
||||
size_t
|
||||
@ -1015,6 +1036,112 @@ CycleCollectedJSRuntime::DumpJSHeap(FILE* aFile)
|
||||
js::DumpHeap(Runtime(), aFile, js::CollectNurseryBeforeDump);
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSRuntime::ProcessStableStateQueue()
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(!mDoingStableStates);
|
||||
mDoingStableStates = true;
|
||||
|
||||
for (uint32_t i = 0; i < mStableStateEvents.Length(); ++i) {
|
||||
nsCOMPtr<nsIRunnable> event = mStableStateEvents[i].forget();
|
||||
event->Run();
|
||||
}
|
||||
|
||||
mStableStateEvents.Clear();
|
||||
mDoingStableStates = false;
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSRuntime::ProcessMetastableStateQueue(uint32_t aRecursionDepth)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(!mDoingStableStates);
|
||||
mDoingStableStates = true;
|
||||
|
||||
nsTArray<RunInMetastableStateData> localQueue = Move(mMetastableStateEvents);
|
||||
|
||||
for (uint32_t i = 0; i < localQueue.Length(); ++i)
|
||||
{
|
||||
RunInMetastableStateData& data = localQueue[i];
|
||||
if (data.mRecursionDepth != aRecursionDepth) {
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> runnable = data.mRunnable.forget();
|
||||
runnable->Run();
|
||||
}
|
||||
|
||||
localQueue.RemoveElementAt(i--);
|
||||
}
|
||||
|
||||
// If the queue has events in it now, they were added from something we called,
|
||||
// so they belong at the end of the queue.
|
||||
localQueue.AppendElements(mMetastableStateEvents);
|
||||
localQueue.SwapElements(mMetastableStateEvents);
|
||||
mDoingStableStates = false;
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSRuntime::AfterProcessTask(uint32_t aRecursionDepth)
|
||||
{
|
||||
// See HTML 6.1.4.2 Processing model
|
||||
|
||||
// Execute any events that were waiting for a microtask to complete.
|
||||
// This is not (yet) in the spec.
|
||||
ProcessMetastableStateQueue(aRecursionDepth);
|
||||
|
||||
// Step 4.1: Execute microtasks.
|
||||
if (NS_IsMainThread()) {
|
||||
nsContentUtils::PerformMainThreadMicroTaskCheckpoint();
|
||||
}
|
||||
|
||||
Promise::PerformMicroTaskCheckpoint();
|
||||
|
||||
// Step 4.2 Execute any events that were waiting for a stable state.
|
||||
ProcessStableStateQueue();
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSRuntime::AfterProcessMicrotask()
|
||||
{
|
||||
AfterProcessMicrotask(RecursionDepth());
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSRuntime::AfterProcessMicrotask(uint32_t aRecursionDepth)
|
||||
{
|
||||
// Between microtasks, execute any events that were waiting for a microtask
|
||||
// to complete.
|
||||
ProcessMetastableStateQueue(aRecursionDepth);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
CycleCollectedJSRuntime::RecursionDepth()
|
||||
{
|
||||
return mOwningThread->RecursionDepth();
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSRuntime::RunInStableState(already_AddRefed<nsIRunnable>&& aRunnable)
|
||||
{
|
||||
MOZ_ASSERT(mJSRuntime);
|
||||
mStableStateEvents.AppendElement(Move(aRunnable));
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSRuntime::RunInMetastableState(already_AddRefed<nsIRunnable>&& aRunnable)
|
||||
{
|
||||
RunInMetastableStateData data;
|
||||
data.mRunnable = aRunnable;
|
||||
|
||||
MOZ_ASSERT(mOwningThread);
|
||||
data.mRecursionDepth = RecursionDepth();
|
||||
|
||||
// There must be an event running to get here.
|
||||
MOZ_ASSERT(data.mRecursionDepth > mBaseRecursionDepth);
|
||||
|
||||
mMetastableStateEvents.AppendElement(Move(data));
|
||||
}
|
||||
|
||||
IncrementalFinalizeRunnable::IncrementalFinalizeRunnable(CycleCollectedJSRuntime* aRt,
|
||||
DeferredFinalizerTable& aFinalizers)
|
||||
|
@ -21,6 +21,7 @@
|
||||
class nsCycleCollectionNoteRootCallback;
|
||||
class nsIException;
|
||||
class nsIRunnable;
|
||||
class nsThread;
|
||||
|
||||
namespace js {
|
||||
struct Class;
|
||||
@ -151,7 +152,6 @@ protected:
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void
|
||||
DescribeGCThing(bool aIsMarked, JS::GCCellPtr aThing,
|
||||
nsCycleCollectionTraversalCallback& aCb) const;
|
||||
@ -208,6 +208,10 @@ private:
|
||||
virtual void TraceNativeBlackRoots(JSTracer* aTracer) { };
|
||||
void TraceNativeGrayRoots(JSTracer* aTracer);
|
||||
|
||||
void AfterProcessMicrotask(uint32_t aRecursionDepth);
|
||||
void ProcessStableStateQueue();
|
||||
void ProcessMetastableStateQueue(uint32_t aRecursionDepth);
|
||||
|
||||
public:
|
||||
enum DeferredFinalizeType {
|
||||
FinalizeIncrementally,
|
||||
@ -294,6 +298,21 @@ public:
|
||||
return mJSRuntime;
|
||||
}
|
||||
|
||||
// nsThread entrypoints
|
||||
virtual void BeforeProcessTask(bool aMightBlock) { };
|
||||
virtual void AfterProcessTask(uint32_t aRecursionDepth);
|
||||
|
||||
// microtask processor entry point
|
||||
void AfterProcessMicrotask();
|
||||
|
||||
uint32_t RecursionDepth();
|
||||
|
||||
// Run in stable state (call through nsContentUtils)
|
||||
void RunInStableState(already_AddRefed<nsIRunnable>&& aRunnable);
|
||||
// This isn't in the spec at all yet, but this gets the behavior we want for IDB.
|
||||
// Runs after the current microtask completes.
|
||||
void RunInMetastableState(already_AddRefed<nsIRunnable>&& aRunnable);
|
||||
|
||||
// Get the current thread's CycleCollectedJSRuntime. Returns null if there
|
||||
// isn't one.
|
||||
static CycleCollectedJSRuntime* Get();
|
||||
@ -325,9 +344,21 @@ private:
|
||||
nsRefPtr<IncrementalFinalizeRunnable> mFinalizeRunnable;
|
||||
|
||||
nsCOMPtr<nsIException> mPendingException;
|
||||
nsThread* mOwningThread; // Manual refcounting to avoid include hell.
|
||||
|
||||
std::queue<nsCOMPtr<nsIRunnable>> mPromiseMicroTaskQueue;
|
||||
|
||||
struct RunInMetastableStateData
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> mRunnable;
|
||||
uint32_t mRecursionDepth;
|
||||
};
|
||||
|
||||
nsTArray<nsCOMPtr<nsIRunnable>> mStableStateEvents;
|
||||
nsTArray<RunInMetastableStateData> mMetastableStateEvents;
|
||||
uint32_t mBaseRecursionDepth;
|
||||
bool mDoingStableStates;
|
||||
|
||||
OOMState mOutOfMemoryState;
|
||||
OOMState mLargeAllocationFailureState;
|
||||
};
|
||||
|
@ -526,15 +526,13 @@ LazyIdleThread::OnDispatchedEvent(nsIThreadInternal* /*aThread */)
|
||||
|
||||
NS_IMETHODIMP
|
||||
LazyIdleThread::OnProcessNextEvent(nsIThreadInternal* /* aThread */,
|
||||
bool /* aMayWait */,
|
||||
uint32_t /* aRecursionDepth */)
|
||||
bool /* aMayWait */)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LazyIdleThread::AfterProcessNextEvent(nsIThreadInternal* /* aThread */,
|
||||
uint32_t /* aRecursionDepth */,
|
||||
bool aEventWasProcessed)
|
||||
{
|
||||
bool shouldNotifyIdle;
|
||||
|
@ -13,7 +13,7 @@ interface nsIThreadObserver;
|
||||
* The XPCOM thread object implements this interface, which allows a consumer
|
||||
* to observe dispatch activity on the thread.
|
||||
*/
|
||||
[scriptable, uuid(b24c5af3-43c2-4d17-be14-94d6648a305f)]
|
||||
[scriptable, uuid(9cc51754-2eb3-4b46-ae99-38a61881c622)]
|
||||
interface nsIThreadInternal : nsIThread
|
||||
{
|
||||
/**
|
||||
@ -25,13 +25,6 @@ interface nsIThreadInternal : nsIThread
|
||||
*/
|
||||
attribute nsIThreadObserver observer;
|
||||
|
||||
/**
|
||||
* The current recursion depth, 0 when no events are running, 1 when a single
|
||||
* event is running, and higher when nested events are running. Must only be
|
||||
* called on the target thread.
|
||||
*/
|
||||
readonly attribute unsigned long recursionDepth;
|
||||
|
||||
/**
|
||||
* Add an observer that will *only* receive onProcessNextEvent,
|
||||
* beforeProcessNextEvent. and afterProcessNextEvent callbacks. Always called
|
||||
@ -81,7 +74,7 @@ interface nsIThreadInternal : nsIThread
|
||||
* onDispatchedEvent(thread) {
|
||||
* NativeQueue.signal();
|
||||
* }
|
||||
* onProcessNextEvent(thread, mayWait, recursionDepth) {
|
||||
* onProcessNextEvent(thread, mayWait) {
|
||||
* if (NativeQueue.hasNextEvent())
|
||||
* NativeQueue.processNextEvent();
|
||||
* while (mayWait && !thread.hasPendingEvent()) {
|
||||
@ -100,7 +93,7 @@ interface nsIThreadInternal : nsIThread
|
||||
* afterProcessNextEvent, then another that inherits the first and adds
|
||||
* onDispatchedEvent.
|
||||
*/
|
||||
[scriptable, uuid(09b424c3-26b0-4128-9039-d66f85b02c63)]
|
||||
[uuid(cc8da053-1776-44c2-9199-b5a629d0a19d)]
|
||||
interface nsIThreadObserver : nsISupports
|
||||
{
|
||||
/**
|
||||
@ -122,29 +115,21 @@ interface nsIThreadObserver : nsISupports
|
||||
* @param mayWait
|
||||
* Indicates whether or not the method is allowed to block the calling
|
||||
* thread. For example, this parameter is false during thread shutdown.
|
||||
* @param recursionDepth
|
||||
* Indicates the number of calls to ProcessNextEvent on the call stack in
|
||||
* addition to the current call.
|
||||
*/
|
||||
void onProcessNextEvent(in nsIThreadInternal thread, in boolean mayWait,
|
||||
in unsigned long recursionDepth);
|
||||
void onProcessNextEvent(in nsIThreadInternal thread, in boolean mayWait);
|
||||
|
||||
/**
|
||||
* This method is called (from nsIThread::ProcessNextEvent) after an event
|
||||
* is processed. It does not guarantee that an event was actually processed
|
||||
* (depends on the value of |eventWasProcessed|. This method is only called
|
||||
* on the target thread.
|
||||
* on the target thread. DO NOT EVER RUN SCRIPT FROM THIS CALLBACK!!!
|
||||
*
|
||||
* @param thread
|
||||
* The thread that processed another event.
|
||||
* @param recursionDepth
|
||||
* Indicates the number of calls to ProcessNextEvent on the call stack in
|
||||
* addition to the current call.
|
||||
* @param eventWasProcessed
|
||||
* Indicates whether an event was actually processed. May be false if the
|
||||
* |mayWait| flag was false when calling nsIThread::ProcessNextEvent().
|
||||
*/
|
||||
void afterProcessNextEvent(in nsIThreadInternal thread,
|
||||
in unsigned long recursionDepth,
|
||||
in bool eventWasProcessed);
|
||||
};
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "pratom.h"
|
||||
#include "mozilla/CycleCollectedJSRuntime.h"
|
||||
#include "mozilla/Logging.h"
|
||||
#include "nsIObserverService.h"
|
||||
#if !defined(MOZILLA_XPCOMRT_API)
|
||||
@ -94,8 +95,6 @@ GetThreadLog()
|
||||
|
||||
NS_DECL_CI_INTERFACE_GETTER(nsThread)
|
||||
|
||||
nsIThreadObserver* nsThread::sMainThreadObserver = nullptr;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Because we do not have our own nsIFactory, we have to implement nsIClassInfo
|
||||
// somewhat manually.
|
||||
@ -440,6 +439,7 @@ int sCanaryOutputFD = -1;
|
||||
|
||||
nsThread::nsThread(MainThreadFlag aMainThread, uint32_t aStackSize)
|
||||
: mLock("nsThread.mLock")
|
||||
, mScriptObserver(nullptr)
|
||||
, mEvents(&mEventsRoot)
|
||||
, mPriority(PRIORITY_NORMAL)
|
||||
, mThread(nullptr)
|
||||
@ -824,22 +824,19 @@ nsThread::ProcessNextEvent(bool aMayWait, bool* aResult)
|
||||
}
|
||||
#endif
|
||||
|
||||
bool notifyMainThreadObserver =
|
||||
(MAIN_THREAD == mIsMainThread) && sMainThreadObserver;
|
||||
if (notifyMainThreadObserver) {
|
||||
sMainThreadObserver->OnProcessNextEvent(this, reallyWait,
|
||||
mNestedEventLoopDepth);
|
||||
++mNestedEventLoopDepth;
|
||||
|
||||
bool callScriptObserver = !!mScriptObserver;
|
||||
if (callScriptObserver) {
|
||||
mScriptObserver->BeforeProcessTask(reallyWait);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIThreadObserver> obs = mObserver;
|
||||
if (obs) {
|
||||
obs->OnProcessNextEvent(this, reallyWait, mNestedEventLoopDepth);
|
||||
obs->OnProcessNextEvent(this, reallyWait);
|
||||
}
|
||||
|
||||
NOTIFY_EVENT_OBSERVERS(OnProcessNextEvent,
|
||||
(this, reallyWait, mNestedEventLoopDepth));
|
||||
|
||||
++mNestedEventLoopDepth;
|
||||
NOTIFY_EVENT_OBSERVERS(OnProcessNextEvent, (this, reallyWait));
|
||||
|
||||
#ifdef MOZ_CANARY
|
||||
Canary canary;
|
||||
@ -872,20 +869,18 @@ nsThread::ProcessNextEvent(bool aMayWait, bool* aResult)
|
||||
}
|
||||
}
|
||||
|
||||
--mNestedEventLoopDepth;
|
||||
|
||||
NOTIFY_EVENT_OBSERVERS(AfterProcessNextEvent,
|
||||
(this, mNestedEventLoopDepth, *aResult));
|
||||
NOTIFY_EVENT_OBSERVERS(AfterProcessNextEvent, (this, *aResult));
|
||||
|
||||
if (obs) {
|
||||
obs->AfterProcessNextEvent(this, mNestedEventLoopDepth, *aResult);
|
||||
obs->AfterProcessNextEvent(this, *aResult);
|
||||
}
|
||||
|
||||
if (notifyMainThreadObserver && sMainThreadObserver) {
|
||||
sMainThreadObserver->AfterProcessNextEvent(this, mNestedEventLoopDepth,
|
||||
*aResult);
|
||||
if (callScriptObserver && mScriptObserver) {
|
||||
mScriptObserver->AfterProcessTask(mNestedEventLoopDepth);
|
||||
}
|
||||
|
||||
--mNestedEventLoopDepth;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
@ -962,15 +957,11 @@ nsThread::SetObserver(nsIThreadObserver* aObs)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsThread::GetRecursionDepth(uint32_t* aDepth)
|
||||
uint32_t
|
||||
nsThread::RecursionDepth() const
|
||||
{
|
||||
if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
|
||||
return NS_ERROR_NOT_SAME_THREAD;
|
||||
}
|
||||
|
||||
*aDepth = mNestedEventLoopDepth;
|
||||
return NS_OK;
|
||||
MOZ_ASSERT(PR_GetCurrentThread() == mThread);
|
||||
return mNestedEventLoopDepth;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
@ -1069,19 +1060,16 @@ nsThread::PopEventQueue(nsIEventTarget* aInnermostTarget)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsThread::SetMainThreadObserver(nsIThreadObserver* aObserver)
|
||||
void
|
||||
nsThread::SetScriptObserver(mozilla::CycleCollectedJSRuntime* aScriptObserver)
|
||||
{
|
||||
if (aObserver && nsThread::sMainThreadObserver) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
if (!aScriptObserver) {
|
||||
mScriptObserver = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NS_IsMainThread()) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
nsThread::sMainThreadObserver = aObserver;
|
||||
return NS_OK;
|
||||
MOZ_ASSERT(!mScriptObserver);
|
||||
mScriptObserver = aScriptObserver;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -18,6 +18,10 @@
|
||||
#include "nsAutoPtr.h"
|
||||
#include "mozilla/AlreadyAddRefed.h"
|
||||
|
||||
namespace mozilla {
|
||||
class CycleCollectedJSRuntime;
|
||||
}
|
||||
|
||||
// A native thread
|
||||
class nsThread
|
||||
: public nsIThreadInternal
|
||||
@ -67,12 +71,13 @@ public:
|
||||
mEventObservers.Clear();
|
||||
}
|
||||
|
||||
static nsresult
|
||||
SetMainThreadObserver(nsIThreadObserver* aObserver);
|
||||
void
|
||||
SetScriptObserver(mozilla::CycleCollectedJSRuntime* aScriptObserver);
|
||||
|
||||
uint32_t
|
||||
RecursionDepth() const;
|
||||
|
||||
protected:
|
||||
static nsIThreadObserver* sMainThreadObserver;
|
||||
|
||||
class nsChainedEventQueue;
|
||||
|
||||
class nsNestedEventTarget;
|
||||
@ -175,6 +180,7 @@ protected:
|
||||
mozilla::Mutex mLock;
|
||||
|
||||
nsCOMPtr<nsIThreadObserver> mObserver;
|
||||
mozilla::CycleCollectedJSRuntime* mScriptObserver;
|
||||
|
||||
// Only accessed on the target thread.
|
||||
nsAutoTObserverArray<nsCOMPtr<nsIThreadObserver>, 2> mEventObservers;
|
||||
|
Loading…
Reference in New Issue
Block a user