gecko-dev/dom/base/TimeoutHandler.cpp
Iain Ireland 5617e628be Bug 1467846: Part 10: Introduce DelayedDispatchToEventLoopCallback r=arai,dom-worker-reviewers,smaug
[Atomics.waitAsync](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/waitAsync) creates a promise that can be resolved via Atomics.notify, or after an optional timeout. The timeout is [implemented](https://html.spec.whatwg.org/multipage/webappapis.html#hostenqueuetimeoutjob) using the [HostEnqueueTimeoutJob hook](https://html.spec.whatwg.org/multipage/webappapis.html#hostenqueuetimeoutjob), which queues a global task (not a microtask).

This patch adds a new API (DelayedDispatchToEventLoopCallback) accessible by the JS engine that utilized the TimeoutManager for executing the steps outlined in the HostEnqueTimeoutJob. Unlike DispatchToEventCallback, this is currently restricted to only threads which support TimeoutManager (the Main thread and the Workers Thread). This should not be enabled on worklets per the discussion [here](https://phabricator.services.mozilla.com/D212876#inline-1206374). The next patch adds an implementation in worker threads. A later patch in the stack adds an implementation for the shell.

Depends on D212875

Differential Revision: https://phabricator.services.mozilla.com/D212876
2025-05-13 10:04:54 +00:00

198 lines
7.3 KiB
C++

/* -*- 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/. */
#include "TimeoutHandler.h"
#include "mozilla/Assertions.h"
#include "mozilla/HoldDropJSObjects.h"
namespace mozilla::dom {
//-----------------------------------------------------------------------------
// TimeoutHandler
//-----------------------------------------------------------------------------
bool TimeoutHandler::Call(const char* /* unused */) { return false; }
void TimeoutHandler::GetDescription(nsACString& aOutString) {
aOutString.AppendPrintf("<generic handler> (%s:%d:%d)",
mCaller.FileName().get(), mCaller.mLine,
mCaller.mColumn);
}
//-----------------------------------------------------------------------------
// ScriptTimeoutHandler
//-----------------------------------------------------------------------------
ScriptTimeoutHandler::ScriptTimeoutHandler(JSContext* aCx,
nsIGlobalObject* aGlobal,
const nsAString& aExpression)
: TimeoutHandler(aCx), mGlobal(aGlobal), mExpr(aExpression) {}
NS_IMPL_CYCLE_COLLECTION_CLASS(ScriptTimeoutHandler)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ScriptTimeoutHandler)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(ScriptTimeoutHandler)
if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
nsAutoCString name("ScriptTimeoutHandler");
name.AppendLiteral(" [");
name.Append(tmp->mCaller.FileName());
name.Append(':');
name.AppendInt(tmp->mCaller.mLine);
name.Append(':');
name.AppendInt(tmp->mCaller.mColumn);
name.Append(']');
cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name.get());
} else {
NS_IMPL_CYCLE_COLLECTION_DESCRIBE(ScriptTimeoutHandler, tmp->mRefCnt.get())
}
// If we need to make TimeoutHandler CCed, don't call its Traverse method
// here, otherwise we ends up report same object twice if logging is on. See
// https://bugzilla.mozilla.org/show_bug.cgi?id=1588208.
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptTimeoutHandler)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptTimeoutHandler)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptTimeoutHandler)
void ScriptTimeoutHandler::GetDescription(nsACString& aOutString) {
if (mExpr.Length() > 15) {
aOutString.AppendPrintf(
"<string handler (truncated): \"%s...\"> (%s:%d:%d)",
NS_ConvertUTF16toUTF8(Substring(mExpr, 0, 13)).get(),
mCaller.FileName().get(), mCaller.mLine, mCaller.mColumn);
} else {
aOutString.AppendPrintf("<string handler: \"%s\"> (%s:%d:%d)",
NS_ConvertUTF16toUTF8(mExpr).get(),
mCaller.FileName().get(), mCaller.mLine,
mCaller.mColumn);
}
}
//-----------------------------------------------------------------------------
// CallbackTimeoutHandler
//-----------------------------------------------------------------------------
CallbackTimeoutHandler::CallbackTimeoutHandler(
JSContext* aCx, nsIGlobalObject* aGlobal, Function* aFunction,
nsTArray<JS::Heap<JS::Value>>&& aArguments)
: TimeoutHandler(aCx), mGlobal(aGlobal), mFunction(aFunction) {
mozilla::HoldJSObjectsWithKey(this);
mArgs = std::move(aArguments);
}
NS_IMPL_CYCLE_COLLECTION_CLASS(CallbackTimeoutHandler)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CallbackTimeoutHandler)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFunction)
tmp->ReleaseJSObjects();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(CallbackTimeoutHandler)
if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
nsAutoCString name("CallbackTimeoutHandler");
JSObject* obj = tmp->mFunction->CallablePreserveColor();
JSFunction* fun =
JS_GetObjectFunction(js::UncheckedUnwrapWithoutExpose(obj));
if (fun && JS_GetMaybePartialFunctionId(fun)) {
JSLinearString* funId =
JS_ASSERT_STRING_IS_LINEAR(JS_GetMaybePartialFunctionId(fun));
size_t size = 1 + JS_PutEscapedLinearString(nullptr, 0, funId, 0);
char* funIdName = new char[size];
if (funIdName) {
JS_PutEscapedLinearString(funIdName, size, funId, 0);
name.AppendLiteral(" [");
name.Append(funIdName);
delete[] funIdName;
name.Append(']');
}
}
cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name.get());
} else {
NS_IMPL_CYCLE_COLLECTION_DESCRIBE(CallbackTimeoutHandler,
tmp->mRefCnt.get())
}
// If we need to make TimeoutHandler CCed, don't call its Traverse method
// here, otherwise we ends up report same object twice if logging is on. See
// https://bugzilla.mozilla.org/show_bug.cgi?id=1588208.
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFunction)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CallbackTimeoutHandler)
for (size_t i = 0; i < tmp->mArgs.Length(); ++i) {
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mArgs[i])
}
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CallbackTimeoutHandler)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(CallbackTimeoutHandler)
NS_IMPL_CYCLE_COLLECTING_RELEASE(CallbackTimeoutHandler)
void CallbackTimeoutHandler::ReleaseJSObjects() {
mArgs.Clear();
mozilla::DropJSObjectsWithKey(this);
}
bool CallbackTimeoutHandler::Call(const char* aExecutionReason) {
IgnoredErrorResult rv;
JS::Rooted<JS::Value> ignoredVal(RootingCx());
MOZ_KnownLive(mFunction)->Call(MOZ_KnownLive(mGlobal), mArgs, &ignoredVal, rv,
aExecutionReason);
return !rv.IsUncatchableException();
}
void CallbackTimeoutHandler::MarkForCC() { mFunction->MarkForCC(); }
void CallbackTimeoutHandler::GetDescription(nsACString& aOutString) {
mFunction->GetDescription(aOutString);
}
//-----------------------------------------------------------------------------
// DelayedJSDispatchableHandler
//-----------------------------------------------------------------------------
MOZ_CAN_RUN_SCRIPT bool DelayedJSDispatchableHandler::Call(
const char* /* unused */) {
MOZ_ASSERT(mDispatchable);
// We get the cx in whatever state, as if we have already shutdown
// then the notify task will already be cleared.
JSContext* cx = danger::GetJSContext();
JS::Dispatchable::Run(cx, std::move(mDispatchable),
JS::Dispatchable::NotShuttingDown);
return true;
}
DelayedJSDispatchableHandler::~DelayedJSDispatchableHandler() {
if (mDispatchable) {
// If we shutdown with the DelayedJSDispatchableHandler still holding
// the reference to mDispatchable, release it to the engine for cleanup.
// In the case of WaitAsyncTimeoutTask, this will clear the task, and
// delete itself.
JS::Dispatchable::ReleaseFailedTask(std::move(mDispatchable));
}
}
NS_IMPL_ISUPPORTS(DelayedJSDispatchableHandler, nsISupports)
} // namespace mozilla::dom