/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// JS_MaybeGC will run once every second during normal execution. #define PERIODIC_GC_TIMER_DELAY_SEC 1
// A shrinking GC will run five seconds after the last event is processed. #define IDLE_GC_TIMER_DELAY_SEC 5
// Arbitrary short grace period for the currently running task to finish. // There isn't an advantage for us to immediately interrupt JS in the middle of // execution that might yield soon, especially when there is so much async // variability in the data flow prior to us deciding to trigger the interrupt. #define DEBUGGER_RUNNABLE_INTERRUPT_AFTER_MS 250
// This class is used to wrap any runnables that the worker receives via the // nsIEventTarget::Dispatch() method (either from NS_DispatchToCurrentThread or // from the worker's EventTarget). class ExternalRunnableWrapper final : public WorkerThreadRunnable {
nsCOMPtr<nsIRunnable> mWrappedRunnable;
virtualbool WorkerRun(JSContext* aCx,
WorkerPrivate* aWorkerPrivate) override { // This may block on the main thread.
AutoYieldJSThreadExecution yield;
class CompileScriptRunnable final : public WorkerDebuggeeRunnable {
nsString mScriptURL; const mozilla::Encoding* mDocumentEncoding;
UniquePtr<SerializedStackHolder> mOriginStack;
private: // We can't implement PreRun effectively, because at the point when that would // run we have not yet done our load so don't know things like our final // principal and whatnot.
if (aWorkerPrivate->ExtensionAPIAllowed()) {
MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
RefPtr<Runnable> extWorkerRunnable =
extensions::CreateWorkerLoadedRunnable(
aWorkerPrivate->ServiceWorkerID(), aWorkerPrivate->GetBaseURI()); // Dispatch as a low priority runnable. if (NS_FAILED(aWorkerPrivate->DispatchToMainThreadForMessaging(
extWorkerRunnable.forget()))) {
NS_WARNING( "Failed to dispatch runnable to notify extensions worker loaded");
}
}
rv.WouldReportJSException(); // Explicitly ignore NS_BINDING_ABORTED on rv. Or more precisely, still // return false and don't SetWorkerScriptExecutedSuccessfully() in that // case, but don't throw anything on aCx. The idea is to not dispatch error // events if our load is canceled with that error code. if (rv.ErrorCodeIs(NS_BINDING_ABORTED)) {
rv.SuppressException(); returnfalse;
}
// Make sure to propagate exceptions from rv onto aCx, so that they will get // reported after we return. We want to propagate just JS exceptions, // because all the other errors are handled when the script is loaded. // See: https://dom.spec.whatwg.org/#concept-event-fire if (rv.Failed() && !rv.IsJSException()) {
WorkerErrorReport::CreateAndDispatchGenericErrorRunnableToParent(
aWorkerPrivate);
rv.SuppressException(); returnfalse;
}
// This is a little dumb, but aCx is in the null realm here because we // set it up that way in our Run(), since we had not created the global at // that point yet. So we need to enter the realm of our global, // because setting a pending exception on aCx involves wrapping into its // current compartment. Luckily we have a global now.
JSAutoRealm ar(aCx, globalScope->GetGlobalJSObject()); if (rv.MaybeSetPendingException(aCx)) { // In the event of an uncaught exception, the worker should still keep // running (return true) but should not be marked as having executed // successfully (which will cause ServiceWorker installation to fail). // In previous error handling cases in this method, we return false (to // trigger CloseInternal) because the global is not in an operable // state at all. // // For ServiceWorkers, this would correspond to the "Run Service Worker" // algorithm returning an "abrupt completion" and _not_ failure. // // For DedicatedWorkers and SharedWorkers, this would correspond to the // "run a worker" algorithm disregarding the return value of "run the // classic script"/"run the module script" in step 24: // // "If script is a classic script, then run the classic script script. // Otherwise, it is a module script; run the module script script." returntrue;
}
class FreezeRunnable final : public WorkerControlRunnable { public: explicit FreezeRunnable(WorkerPrivate* aWorkerPrivate)
: WorkerControlRunnable("FreezeRunnable") {}
class ThawRunnable final : public WorkerControlRunnable { public: explicit ThawRunnable(WorkerPrivate* aWorkerPrivate)
: WorkerControlRunnable("ThawRunnable") {}
class ReportErrorToConsoleRunnable final : public WorkerParentThreadRunnable { public: // aWorkerPrivate is the worker thread we're on (or the main thread, if null) staticvoid Report(WorkerPrivate* aWorkerPrivate, uint32_t aErrorFlags, const nsCString& aCategory,
nsContentUtils::PropertiesFile aFile, const nsCString& aMessageName, const nsTArray<nsString>& aParams, const mozilla::SourceLocation& aLocation) { if (aWorkerPrivate) {
aWorkerPrivate->AssertIsOnWorkerThread();
} else {
AssertIsOnMainThread();
}
// Now fire a runnable to do the same on the parent's thread if we can. if (aWorkerPrivate) {
RefPtr<ReportErrorToConsoleRunnable> runnable = new ReportErrorToConsoleRunnable(aWorkerPrivate, aErrorFlags,
aCategory, aFile, aMessageName,
aParams, aLocation);
runnable->Dispatch(aWorkerPrivate); return;
}
// Log a warning to the console.
nsContentUtils::ReportToConsole(aErrorFlags, aCategory, nullptr, aFile,
aMessageName.get(), aParams, aLocation);
}
// Make as MOZ_CAN_RUN_SCRIPT_BOUNDARY for calling mHandler->Call(); // Since WorkerRunnable::WorkerRun has not to be MOZ_CAN_RUN_SCRIPT, but // DebuggerImmediateRunnable is a special case that must to call the function // defined in the debugger script.
MOZ_CAN_RUN_SCRIPT_BOUNDARY virtualbool WorkerRun(JSContext* aCx,
WorkerPrivate* aWorkerPrivate) override {
JS::Rooted<JS::Value> rval(aCx);
IgnoredErrorResult rv;
MOZ_KnownLive(mHandler)->Call({}, &rval, rv);
return !rv.Failed();
}
};
// GetJSContext() is safe on the worker thread void PeriodicGCTimerCallback(nsITimer* aTimer, void* aClosure) MOZ_NO_THREAD_SAFETY_ANALYSIS { auto* workerPrivate = static_cast<WorkerPrivate*>(aClosure);
MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
workerPrivate->AssertIsOnWorkerThread();
workerPrivate->GarbageCollectInternal(workerPrivate->GetJSContext(), false/* shrinking */, false/* collect children */);
LOG(WorkerLog(), ("Worker %p run periodic GC\n", workerPrivate));
}
private: virtualbool PreDispatch(WorkerPrivate* aWorkerPrivate) override { // Silence bad assertions, this can be dispatched from either the main // thread or the timer thread.. returntrue;
}
virtualvoid PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override { // Silence bad assertions, this can be dispatched from either the main // thread or the timer thread..
}
virtualbool WorkerRun(JSContext* aCx,
WorkerPrivate* aWorkerPrivate) override {
aWorkerPrivate->GarbageCollectInternal(aCx, mShrinking, mCollectChildren); if (mShrinking) { // Either we've run the idle GC or explicit GC call from the parent, // we can cancel the current timers.
aWorkerPrivate->CancelGCTimers();
} returntrue;
}
};
class CycleCollectRunnable final : public WorkerControlRunnable { bool mCollectChildren;
class MemoryPressureRunnable final : public WorkerControlRunnable { public: explicit MemoryPressureRunnable(WorkerPrivate* aWorkerPrivate)
: WorkerControlRunnable("MemoryPressureRunnable") {}
// A runnable to cancel the worker from the parent thread when self.close() is // called. This runnable is executed on the parent process in order to cancel // the current runnable. It uses a normal WorkerDebuggeeRunnable in order to be // sure that all the pending WorkerDebuggeeRunnables are executed before this. class CancelingOnParentRunnable final : public WorkerParentDebuggeeRunnable { public: explicit CancelingOnParentRunnable(WorkerPrivate* aWorkerPrivate)
: WorkerParentDebuggeeRunnable("CancelingOnParentRunnable") {}
// A runnable to cancel the worker from the parent process. class CancelingWithTimeoutOnParentRunnable final
: public WorkerParentControlRunnable { public: explicit CancelingWithTimeoutOnParentRunnable(WorkerPrivate* aWorkerPrivate)
: WorkerParentControlRunnable("CancelingWithTimeoutOnParentRunnable") {}
// This runnable starts the canceling of a worker after a self.close(). class CancelingRunnable final : public Runnable { public:
CancelingRunnable() : Runnable("CancelingRunnable") {}
// Now we can cancel the this worker from the parent process.
RefPtr<CancelingOnParentRunnable> r = new CancelingOnParentRunnable(workerPrivate);
r->Dispatch(workerPrivate);
class WorkerPrivate::EventTarget final : public nsISerialEventTarget { // This mutex protects mWorkerPrivate and must be acquired *before* the // WorkerPrivate's mutex whenever they must both be held.
mozilla::Mutex mMutex;
WorkerPrivate* mWorkerPrivate MOZ_GUARDED_BY(mMutex);
nsCOMPtr<nsIEventTarget> mNestedEventTarget MOZ_GUARDED_BY(mMutex); bool mDisabled MOZ_GUARDED_BY(mMutex); bool mShutdown MOZ_GUARDED_BY(mMutex);
// ReportJSRuntimeExplicitTreeStats expects that // aRealmStats->extra is a xpc::RealmStatsExtras pointer.
xpc::RealmStatsExtras* extras = new xpc::RealmStatsExtras;
// This is the |jsPathPrefix|. Each worker has exactly one realm.
extras->jsPathPrefix.Assign(mRtPath);
extras->jsPathPrefix +=
nsPrintfCString("zone(0x%p)/", (void*)js::GetRealmZone(aRealm));
extras->jsPathPrefix += "realm(web-worker)/"_ns;
// This should never be used when reporting with workers (hence the "?!").
extras->domPathPrefix.AssignLiteral("explicit/workers/?!/");
class CollectReportsRunnable final : public MainThreadWorkerControlRunnable {
RefPtr<FinishCollectRunnable> mFinishCollectRunnable; constbool mAnonymize;
// We should query the window from the top level worker in case of a nested // worker, as only the top level one can have a window.
WorkerPrivate* top = GetTopLevelWorker(); return top->GetWindow();
}
class EvictFromBFCacheRunnable final : public WorkerProxyToMainThreadRunnable { public: void RunOnMainThread(WorkerPrivate* aWorkerPrivate) override {
MOZ_ASSERT(aWorkerPrivate);
AssertIsOnMainThread(); if (nsCOMPtr<nsPIDOMWindowInner> win =
aWorkerPrivate->GetAncestorWindow()) {
win->RemoveFromBFCacheSync();
}
}
nsresult rv;
nsCOMPtr<nsIContentSecurityPolicy> csp = new nsCSPContext();
// First, we try to query the URI from the Principal, but // in case selfURI remains empty (e.g in case the Principal // is a SystemPrincipal) then we fall back and use the // base URI as selfURI for CSP.
nsCOMPtr<nsIURI> selfURI; // Its not recommended to use the BasePrincipal to get the URI // but in this case we need to make an exception auto* basePrin = BasePrincipal::Cast(mLoadInfo.mPrincipal); if (basePrin) {
basePrin->GetURI(getter_AddRefs(selfURI));
} if (!selfURI) {
selfURI = mLoadInfo.mBaseURI;
}
MOZ_ASSERT(selfURI, "need a self URI for CSP");
// If there's a CSP header, apply it. if (!cspHeaderValue.IsEmpty()) {
rv = CSP_AppendCSPFromHeader(csp, cspHeaderValue, false);
NS_ENSURE_SUCCESS(rv, rv);
} // If there's a report-only CSP header, apply it. if (!cspROHeaderValue.IsEmpty()) {
rv = CSP_AppendCSPFromHeader(csp, cspROHeaderValue, true);
NS_ENSURE_SUCCESS(rv, rv);
}
if (basePrin) {
addonPolicy = basePrin->AddonPolicy();
}
// For extension workers there aren't any csp header values, // instead it will inherit the Extension CSP. if (addonPolicy) {
csp->AppendPolicy(addonPolicy->BaseCSP(), false, false);
csp->AppendPolicy(addonPolicy->ExtensionPageCSP(), false, false);
}
mLoadInfo.mCSP = csp;
// Set evalAllowed, default value is set in GetAllowsEval bool evalAllowed = false; bool reportEvalViolations = false;
rv = csp->GetAllowsEval(&reportEvalViolations, &evalAllowed);
NS_ENSURE_SUCCESS(rv, rv);
// As for nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction, // for MV2 extensions we have to allow wasm by default and report violations // for historical reasons. // TODO bug 1770909: remove this exception. if (!wasmEvalAllowed && addonPolicy && addonPolicy->ManifestVersion() == 2) {
wasmEvalAllowed = true;
reportWasmEvalViolations = true;
}
void WorkerPrivate::StoreCSPOnClient() { auto data = mWorkerThreadAccessible.Access();
MOZ_ASSERT(data->mScope); if (mLoadInfo.mCSPInfo) {
data->mScope->MutableClientSourceRef().SetCspInfo(*mLoadInfo.mCSPInfo);
}
}
// The WorkerPrivate::mParentEventTargetRef has a reference to the exposed // Worker object, which is really held by the worker thread. We traverse this // reference if and only if all main thread event queues are empty, no // shutdown tasks, no StrongWorkerRefs, no child workers, no timeouts, no // blocking background actors, and we have not released the main thread // reference. We do not unlink it. This allows the CC to break cycles // involving the Worker and begin shutting it down (which does happen in // unlink) but ensures that the WorkerPrivate won't be deleted before we're // done shutting down the thread. if (IsEligibleForCC() && !mMainThreadObjectsForgotten) {
nsCycleCollectionTraversalCallback& cb = aCb;
WorkerPrivate* tmp = this;
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentEventTargetRef);
}
}
nsresult WorkerPrivate::Dispatch(already_AddRefed<WorkerRunnable> aRunnable,
nsIEventTarget* aSyncLoopTarget) { // May be called on any thread!
RefPtr<WorkerRunnable> runnable(aRunnable);
LOGV(("WorkerPrivate::Dispatch [%p] runnable %p", this, runnable.get())); if (!aSyncLoopTarget) { // Dispatch control runnable if (runnable->IsControlRunnable()) { return DispatchControlRunnable(runnable.forget());
}
WorkerPrivate* parent = GetParent(); // Dispatch to parent worker if (parent) { if (runnable->IsControlRunnable()) { return parent->DispatchControlRunnable(runnable.forget());
} return parent->Dispatch(runnable.forget());
}
// Dispatch to main thread if (runnable->IsDebuggeeRunnable()) {
RefPtr<WorkerParentDebuggeeRunnable> debuggeeRunnable =
runnable.forget().downcast<WorkerParentDebuggeeRunnable>(); return DispatchDebuggeeToMainThread(debuggeeRunnable.forget(),
NS_DISPATCH_NORMAL);
} return DispatchToMainThread(runnable.forget());
}
nsresult WorkerPrivate::DispatchLockHeld(
already_AddRefed<WorkerRunnable> aRunnable, nsIEventTarget* aSyncLoopTarget, const MutexAutoLock& aProofOfLock) { // May be called on any thread!
RefPtr<WorkerRunnable> runnable(aRunnable);
LOGV(("WorkerPrivate::DispatchLockHeld [%p] runnable: %p", this,
runnable.get()));
MOZ_ASSERT_IF(aSyncLoopTarget, mThread);
// Dispatch normal worker runnable if (mStatus == Dead || (!aSyncLoopTarget && ParentStatus() > Canceling)) {
NS_WARNING( "A runnable was posted to a worker that is already shutting " "down!"); return NS_ERROR_UNEXPECTED;
}
if (runnable->IsDebuggeeRunnable() && !mDebuggerReady) {
MOZ_RELEASE_ASSERT(!aSyncLoopTarget);
mDelayedDebuggeeRunnables.AppendElement(runnable); return NS_OK;
}
if (!mThread) { if (ParentStatus() == Pending || mStatus == Pending) {
LOGV(
("WorkerPrivate::DispatchLockHeld [%p] runnable %p is queued in " "mPreStartRunnables", this, runnable.get()));
RefPtr<WorkerThreadRunnable> workerThreadRunnable = static_cast<WorkerThreadRunnable*>(runnable.get());
mPreStartRunnables.AppendElement(workerThreadRunnable); return NS_OK;
}
NS_WARNING( "Using a worker event target after the thread has already" "been released!"); return NS_ERROR_UNEXPECTED;
}
nsresult rv; if (aSyncLoopTarget) {
LOGV(
("WorkerPrivate::DispatchLockHeld [%p] runnable %p dispatch to a " "SyncLoop(%p)", this, runnable.get(), aSyncLoopTarget));
rv = aSyncLoopTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
} else { // If mStatus is Pending, the WorkerPrivate initialization still can fail. // Append this WorkerThreadRunnable to WorkerPrivate::mPreStartRunnables, // such that this WorkerThreadRunnable can get the correct value of // mCleanPreStartDispatching in WorkerPrivate::RunLoopNeverRan(). if (mStatus == Pending) {
LOGV(
("WorkerPrivate::DispatchLockHeld [%p] runnable %p is append in " "mPreStartRunnables", this, runnable.get()));
RefPtr<WorkerThreadRunnable> workerThreadRunnable = static_cast<WorkerThreadRunnable*>(runnable.get());
mPreStartRunnables.AppendElement(workerThreadRunnable);
}
// WorkerDebuggeeRunnables don't need any special treatment here. True, // they should not be delivered to a frozen worker. But frozen workers // aren't drawing from the thread's main event queue anyway, only from // mControlQueue.
LOGV(
("WorkerPrivate::DispatchLockHeld [%p] runnable %p dispatch to the " "main event queue", this, runnable.get()));
rv = mThread->DispatchAnyThread(WorkerThreadFriendKey(), runnable.forget());
}
if (NS_WARN_IF(NS_FAILED(rv))) {
LOGV(("WorkerPrivate::Dispatch Failed [%p]", this)); return rv;
}
// RegisterDebuggerMainThreadRunnable might be dispatched but not executed. // Wait for its execution before unregistraion. if (!NS_IsMainThread()) {
WaitForIsDebuggerRegistered(true);
}
if (NS_FAILED(UnregisterWorkerDebugger(this))) {
NS_WARNING("Failed to unregister worker debugger!");
}
}
nsresult WorkerPrivate::DispatchControlRunnable(
already_AddRefed<WorkerRunnable> aWorkerRunnable) { // May be called on any thread!
RefPtr<WorkerRunnable> runnable(aWorkerRunnable);
MOZ_ASSERT_DEBUG_OR_FUZZING(runnable && runnable->IsControlRunnable());
MutexAutoLock lock(mMutex); if (!mDebuggerInterruptTimer) { // There is no timer, so we need to create one. For locking discipline // purposes we can't manipulate the timer while our mutex is held so // drop the mutex while we build and configure the timer. Only this // function here on the main thread will create a timer, so we're not // racing anyone to create or assign the timer.
nsCOMPtr<nsITimer> timer;
{
MutexAutoUnlock unlock(mMutex);
timer = NS_NewTimer();
MOZ_ALWAYS_SUCCEEDS(timer->SetTarget(mWorkerControlEventTarget));
// Whenever an event is scheduled on the WorkerControlEventTarget an // interrupt is automatically requested which causes us to yield JS // execution and the next JS execution in the queue to execute. This // allows for simple code reuse of the existing interrupt callback code // used for control events.
MOZ_ALWAYS_SUCCEEDS(timer->InitWithNamedFuncCallback(
DebuggerInterruptTimerCallback, nullptr,
DEBUGGER_RUNNABLE_INTERRUPT_AFTER_MS, nsITimer::TYPE_ONE_SHOT, "dom:DebuggerInterruptTimer"));
}
// okay, we have our mutex back now, put the timer in place.
mDebuggerInterruptTimer.swap(timer);
}
if (mStatus == Dead) {
NS_WARNING( "A debugger runnable was posted to a worker that is already " "shutting down!"); return NS_ERROR_UNEXPECTED;
}
// Transfer ownership to the debugger queue.
mDebuggerQueue.Push(runnable.forget().take());
RefPtr<WorkerRunnable> workerRunnable =
WorkerRunnable::FromRunnable(runnable); if (workerRunnable) { return workerRunnable.forget();
}
workerRunnable = new ExternalRunnableWrapper(this, runnable); return workerRunnable.forget();
}
bool WorkerPrivate::Start() { // May be called on any thread!
LOG(WorkerLog(), ("WorkerPrivate::Start [%p]", this));
{
MutexAutoLock lock(mMutex);
NS_ASSERTION(mParentStatus != Running, "How can this be?!");
// aCx is null when called from the finalizer bool WorkerPrivate::Notify(WorkerStatus aStatus) {
AssertIsOnParentThread(); // This method is only called for Canceling or later.
MOZ_DIAGNOSTIC_ASSERT(aStatus >= Canceling);
if (mCancellationCallback) {
mCancellationCallback(!pending);
mCancellationCallback = nullptr;
}
mParentRef->DropWorkerPrivate();
if (pending) { #ifdef DEBUG
{ // Fake a thread here just so that our assertions don't go off for no // reason.
nsIThread* currentThread = NS_GetCurrentThread();
MOZ_ASSERT(currentThread);
// Worker never got a chance to run, go ahead and delete it.
ScheduleDeletion(WorkerPrivate::WorkerNeverRan); returntrue;
}
// No Canceling timeout is needed. if (mCancelingTimer) {
mCancelingTimer->Cancel();
mCancelingTimer = nullptr;
}
// The NotifyRunnable kicks off a series of events that need the // CancelingOnParentRunnable to be executed always. // Note that we already advanced mParentStatus above and we check that // status in all other (asynchronous) call sites of SetIsPaused. if (!mParent) {
MOZ_ALWAYS_SUCCEEDS(mMainThreadDebuggeeEventTarget->SetIsPaused(false));
}
RefPtr<NotifyRunnable> runnable = new NotifyRunnable(this, aStatus); return runnable->Dispatch(this);
}
// WorkerDebuggeeRunnables sent from a worker to content must not be // delivered while the worker is frozen. // // Since a top-level worker and all its children share the same // mMainThreadDebuggeeEventTarget, it's sufficient to do this only in the // top-level worker. if (aWindow) { // This is called from WorkerPrivate construction, and We may not have // allocated mMainThreadDebuggeeEventTarget yet. if (mMainThreadDebuggeeEventTarget) { // Pausing a ThrottledEventQueue is infallible.
MOZ_ALWAYS_SUCCEEDS(
mMainThreadDebuggeeEventTarget->SetIsPaused(!isCanceling));
}
}
if (isCanceling) { returntrue;
}
DisableDebugger();
RefPtr<FreezeRunnable> runnable = new FreezeRunnable(this); return runnable->Dispatch(this);
}
// Delivery of WorkerDebuggeeRunnables to the window may resume. // // Since a top-level worker and all its children share the same // mMainThreadDebuggeeEventTarget, it's sufficient to do this only in the // top-level worker. if (aWindow) { // Since the worker is no longer frozen, only a paused parent window // should require the queue to remain paused. // // This can only fail if the ThrottledEventQueue cannot dispatch its // executor to the main thread, in which case the main thread was never // going to draw runnables from it anyway, so the failure doesn't matter.
Unused << mMainThreadDebuggeeEventTarget->SetIsPaused(
IsParentWindowPaused() && !isCanceling);
}
if (isCanceling) { returntrue;
}
}
EnableDebugger();
RefPtr<ThawRunnable> runnable = new ThawRunnable(this); return runnable->Dispatch(this);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.