mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 21:01:08 +00:00
Bug 1753309 - Implement AbortSignal.timeout() r=smaug
Differential Revision: https://phabricator.services.mozilla.com/D137900
This commit is contained in:
parent
7743d4fd62
commit
2e72af38c0
@ -6,13 +6,17 @@
|
||||
|
||||
#include "AbortSignal.h"
|
||||
|
||||
#include "mozilla/dom/AbortSignalBinding.h"
|
||||
#include "mozilla/dom/DOMException.h"
|
||||
#include "mozilla/dom/Event.h"
|
||||
#include "mozilla/dom/EventBinding.h"
|
||||
#include "mozilla/dom/AbortSignalBinding.h"
|
||||
#include "mozilla/dom/TimeoutHandler.h"
|
||||
#include "mozilla/dom/TimeoutManager.h"
|
||||
#include "mozilla/dom/ToJSValue.h"
|
||||
#include "mozilla/dom/WorkerPrivate.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
@ -143,6 +147,110 @@ already_AddRefed<AbortSignal> AbortSignal::Abort(GlobalObject& aGlobal,
|
||||
return abortSignal.forget();
|
||||
}
|
||||
|
||||
class AbortSignalTimeoutHandler final : public TimeoutHandler {
|
||||
public:
|
||||
AbortSignalTimeoutHandler(JSContext* aCx, AbortSignal* aSignal)
|
||||
: TimeoutHandler(aCx), mSignal(aSignal) {}
|
||||
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS(AbortSignalTimeoutHandler)
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-abortsignal-timeout
|
||||
// Step 3
|
||||
MOZ_CAN_RUN_SCRIPT bool Call(const char* /* unused */) override {
|
||||
AutoJSAPI jsapi;
|
||||
if (NS_WARN_IF(!jsapi.Init(mSignal->GetParentObject()))) {
|
||||
// (false is only for setInterval, see
|
||||
// nsGlobalWindowInner::RunTimeoutHandler)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 1. Queue a global task on the timer task source given global to
|
||||
// signal abort given signal and a new "TimeoutError" DOMException.
|
||||
JS::Rooted<JS::Value> exception(jsapi.cx());
|
||||
RefPtr<DOMException> dom = DOMException::Create(NS_ERROR_DOM_TIMEOUT_ERR);
|
||||
if (NS_WARN_IF(!ToJSValue(jsapi.cx(), dom, &exception))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
mSignal->SignalAbort(exception);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
~AbortSignalTimeoutHandler() override = default;
|
||||
|
||||
RefPtr<AbortSignal> mSignal;
|
||||
};
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION(AbortSignalTimeoutHandler, mSignal)
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(AbortSignalTimeoutHandler)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(AbortSignalTimeoutHandler)
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbortSignalTimeoutHandler)
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
static void SetTimeoutForGlobal(GlobalObject& aGlobal, TimeoutHandler& aHandler,
|
||||
int32_t timeout, ErrorResult& aRv) {
|
||||
if (NS_IsMainThread()) {
|
||||
nsCOMPtr<nsPIDOMWindowInner> innerWindow =
|
||||
do_QueryInterface(aGlobal.GetAsSupports());
|
||||
if (!innerWindow) {
|
||||
aRv.ThrowInvalidStateError("Could not find window.");
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t handle;
|
||||
nsresult rv = innerWindow->TimeoutManager().SetTimeout(
|
||||
&aHandler, timeout, /* aIsInterval */ false,
|
||||
Timeout::Reason::eAbortSignalTimeout, &handle);
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(rv);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
WorkerPrivate* workerPrivate =
|
||||
GetWorkerPrivateFromContext(aGlobal.Context());
|
||||
workerPrivate->SetTimeout(aGlobal.Context(), &aHandler, timeout,
|
||||
/* aIsInterval */ false,
|
||||
Timeout::Reason::eAbortSignalTimeout, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-abortsignal-timeout
|
||||
already_AddRefed<AbortSignal> AbortSignal::Timeout(GlobalObject& aGlobal,
|
||||
uint64_t aMilliseconds,
|
||||
ErrorResult& aRv) {
|
||||
// Step 2. Let global be signal’s relevant global object.
|
||||
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
|
||||
|
||||
// Step 1. Let signal be a new AbortSignal object.
|
||||
RefPtr<AbortSignal> signal =
|
||||
new AbortSignal(global, false, JS::UndefinedHandleValue);
|
||||
|
||||
// Step 3. Run steps after a timeout given global, "AbortSignal-timeout",
|
||||
// milliseconds, and the following step: ...
|
||||
RefPtr<TimeoutHandler> handler =
|
||||
new AbortSignalTimeoutHandler(aGlobal.Context(), signal);
|
||||
|
||||
// Note: We only supports int32_t range intervals
|
||||
int32_t timeout =
|
||||
aMilliseconds > uint64_t(std::numeric_limits<int32_t>::max())
|
||||
? std::numeric_limits<int32_t>::max()
|
||||
: static_cast<int32_t>(aMilliseconds);
|
||||
|
||||
SetTimeoutForGlobal(aGlobal, *handler, timeout, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 4. Return signal.
|
||||
return signal.forget();
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-abortsignal-throwifaborted
|
||||
void AbortSignal::ThrowIfAborted(JSContext* aCx, ErrorResult& aRv) {
|
||||
aRv.MightThrowJSException();
|
||||
|
@ -45,6 +45,10 @@ class AbortSignal final : public DOMEventTargetHelper,
|
||||
JS::Handle<JS::Value> aReason,
|
||||
ErrorResult& aRv);
|
||||
|
||||
static already_AddRefed<AbortSignal> Timeout(GlobalObject& aGlobal,
|
||||
uint64_t aMilliseconds,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void ThrowIfAborted(JSContext* aCx, ErrorResult& aRv);
|
||||
|
||||
// AbortSignalImpl
|
||||
|
@ -34,6 +34,7 @@ class Timeout final : protected LinkedListElement<RefPtr<Timeout>> {
|
||||
enum class Reason : uint8_t {
|
||||
eTimeoutOrInterval,
|
||||
eIdleCallbackTimeout,
|
||||
eAbortSignalTimeout,
|
||||
};
|
||||
|
||||
struct TimeoutIdAndReason {
|
||||
|
@ -450,8 +450,9 @@ uint32_t TimeoutManager::GetTimeoutId(Timeout::Reason aReason) {
|
||||
case Timeout::Reason::eIdleCallbackTimeout:
|
||||
return ++mIdleCallbackTimeoutCounter;
|
||||
case Timeout::Reason::eTimeoutOrInterval:
|
||||
default:
|
||||
return ++mTimeoutIdCounter;
|
||||
case Timeout::Reason::eAbortSignalTimeout:
|
||||
return std::numeric_limits<uint32_t>::max(); // no cancellation support
|
||||
}
|
||||
}
|
||||
|
||||
@ -491,9 +492,13 @@ nsresult TimeoutManager::SetTimeout(TimeoutHandler* aHandler, int32_t interval,
|
||||
// No popups from timeouts by default
|
||||
timeout->mPopupState = PopupBlocker::openAbused;
|
||||
|
||||
timeout->mNestingLevel = sNestingLevel < DOM_CLAMP_TIMEOUT_NESTING_LEVEL
|
||||
? sNestingLevel + 1
|
||||
: sNestingLevel;
|
||||
// XXX: Does eIdleCallbackTimeout need clamping?
|
||||
if (aReason == Timeout::Reason::eTimeoutOrInterval ||
|
||||
aReason == Timeout::Reason::eIdleCallbackTimeout) {
|
||||
timeout->mNestingLevel = sNestingLevel < DOM_CLAMP_TIMEOUT_NESTING_LEVEL
|
||||
? sNestingLevel + 1
|
||||
: sNestingLevel;
|
||||
}
|
||||
|
||||
// Now clamp the actual interval we will use for the timer based on
|
||||
TimeDuration realInterval = CalculateDelay(timeout);
|
||||
@ -559,6 +564,10 @@ void TimeoutManager::ClearTimeout(int32_t aTimerId, Timeout::Reason aReason) {
|
||||
bool TimeoutManager::ClearTimeoutInternal(int32_t aTimerId,
|
||||
Timeout::Reason aReason,
|
||||
bool aIsIdle) {
|
||||
MOZ_ASSERT(aReason == Timeout::Reason::eTimeoutOrInterval ||
|
||||
aReason == Timeout::Reason::eIdleCallbackTimeout,
|
||||
"This timeout reason doesn't support cancellation.");
|
||||
|
||||
uint32_t timerId = (uint32_t)aTimerId;
|
||||
Timeouts& timeouts = aIsIdle ? mIdleTimeouts : mTimeouts;
|
||||
RefPtr<TimeoutExecutor>& executor = aIsIdle ? mIdleExecutor : mExecutor;
|
||||
|
@ -6330,9 +6330,8 @@ static const char* GetTimeoutReasonString(Timeout* aTimeout) {
|
||||
return "setTimeout handler";
|
||||
case Timeout::Reason::eIdleCallbackTimeout:
|
||||
return "setIdleCallback handler (timed out)";
|
||||
default:
|
||||
MOZ_CRASH("Unexpected enum value");
|
||||
return "";
|
||||
case Timeout::Reason::eAbortSignalTimeout:
|
||||
return "AbortSignal timeout";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,8 @@
|
||||
[Exposed=(Window,Worker)]
|
||||
interface AbortSignal : EventTarget {
|
||||
[NewObject, Throws] static AbortSignal abort(optional any reason);
|
||||
[Exposed=(Window,Worker), NewObject, Throws]
|
||||
static AbortSignal timeout([EnforceRange] unsigned long long milliseconds);
|
||||
|
||||
readonly attribute boolean aborted;
|
||||
readonly attribute any reason;
|
||||
|
@ -986,6 +986,7 @@ struct WorkerPrivate::TimeoutInfo {
|
||||
TimeoutInfo()
|
||||
: mId(0),
|
||||
mNestingLevel(0),
|
||||
mReason(Timeout::Reason::eTimeoutOrInterval),
|
||||
mIsInterval(false),
|
||||
mCanceled(false),
|
||||
mOnChromeWorker(false) {
|
||||
@ -1026,6 +1027,7 @@ struct WorkerPrivate::TimeoutInfo {
|
||||
mozilla::TimeDuration mInterval;
|
||||
int32_t mId;
|
||||
uint32_t mNestingLevel;
|
||||
Timeout::Reason mReason;
|
||||
bool mIsInterval;
|
||||
bool mCanceled;
|
||||
bool mOnChromeWorker;
|
||||
@ -4699,12 +4701,16 @@ void WorkerPrivate::ReportErrorToConsole(const char* aMessage,
|
||||
|
||||
int32_t WorkerPrivate::SetTimeout(JSContext* aCx, TimeoutHandler* aHandler,
|
||||
int32_t aTimeout, bool aIsInterval,
|
||||
ErrorResult& aRv) {
|
||||
Timeout::Reason aReason, ErrorResult& aRv) {
|
||||
auto data = mWorkerThreadAccessible.Access();
|
||||
MOZ_ASSERT(aHandler);
|
||||
|
||||
const int32_t timerId = data->mNextTimeoutId;
|
||||
data->mNextTimeoutId += 1;
|
||||
// Reasons that doesn't support cancellation will get -1 as their ids.
|
||||
int32_t timerId = -1;
|
||||
if (aReason == Timeout::Reason::eTimeoutOrInterval) {
|
||||
timerId = data->mNextTimeoutId;
|
||||
data->mNextTimeoutId += 1;
|
||||
}
|
||||
|
||||
WorkerStatus currentStatus;
|
||||
{
|
||||
@ -4719,6 +4725,7 @@ int32_t WorkerPrivate::SetTimeout(JSContext* aCx, TimeoutHandler* aHandler,
|
||||
}
|
||||
|
||||
auto newInfo = MakeUnique<TimeoutInfo>();
|
||||
newInfo->mReason = aReason;
|
||||
newInfo->mOnChromeWorker = mIsChromeWorker;
|
||||
newInfo->mIsInterval = aIsInterval;
|
||||
newInfo->mId = timerId;
|
||||
@ -4726,7 +4733,9 @@ int32_t WorkerPrivate::SetTimeout(JSContext* aCx, TimeoutHandler* aHandler,
|
||||
|
||||
if (MOZ_UNLIKELY(timerId == INT32_MAX)) {
|
||||
NS_WARNING("Timeout ids overflowed!");
|
||||
data->mNextTimeoutId = 1;
|
||||
if (aReason == Timeout::Reason::eTimeoutOrInterval) {
|
||||
data->mNextTimeoutId = 1;
|
||||
}
|
||||
}
|
||||
|
||||
newInfo->mHandler = aHandler;
|
||||
@ -4773,7 +4782,10 @@ int32_t WorkerPrivate::SetTimeout(JSContext* aCx, TimeoutHandler* aHandler,
|
||||
return timerId;
|
||||
}
|
||||
|
||||
void WorkerPrivate::ClearTimeout(int32_t aId) {
|
||||
void WorkerPrivate::ClearTimeout(int32_t aId, Timeout::Reason aReason) {
|
||||
MOZ_ASSERT(aReason == Timeout::Reason::eTimeoutOrInterval,
|
||||
"This timeout reason doesn't support cancellation.");
|
||||
|
||||
auto data = mWorkerThreadAccessible.Access();
|
||||
|
||||
if (!data->mTimeouts.IsEmpty()) {
|
||||
@ -4781,7 +4793,7 @@ void WorkerPrivate::ClearTimeout(int32_t aId) {
|
||||
|
||||
for (uint32_t index = 0; index < data->mTimeouts.Length(); index++) {
|
||||
const auto& info = data->mTimeouts[index];
|
||||
if (info->mId == aId) {
|
||||
if (info->mId == aId && info->mReason == aReason) {
|
||||
info->mCanceled = true;
|
||||
break;
|
||||
}
|
||||
@ -4858,23 +4870,29 @@ bool WorkerPrivate::RunExpiredTimeouts(JSContext* aCx) {
|
||||
// Always check JS_IsExceptionPending if something fails, and if
|
||||
// JS_IsExceptionPending returns false (i.e. uncatchable exception) then
|
||||
// break out of the loop.
|
||||
const char* reason;
|
||||
if (info->mIsInterval) {
|
||||
reason = "setInterval handler";
|
||||
} else {
|
||||
reason = "setTimeout handler";
|
||||
}
|
||||
|
||||
RefPtr<TimeoutHandler> handler(info->mHandler);
|
||||
if (info->mReason == Timeout::Reason::eTimeoutOrInterval) {
|
||||
const char* reason;
|
||||
if (info->mIsInterval) {
|
||||
reason = "setInterval handler";
|
||||
} else {
|
||||
reason = "setTimeout handler";
|
||||
}
|
||||
|
||||
RefPtr<WorkerGlobalScope> scope(this->GlobalScope());
|
||||
CallbackDebuggerNotificationGuard guard(
|
||||
scope, info->mIsInterval
|
||||
? DebuggerNotificationType::SetIntervalCallback
|
||||
: DebuggerNotificationType::SetTimeoutCallback);
|
||||
if (!handler->Call(reason)) {
|
||||
retval = false;
|
||||
break;
|
||||
RefPtr<WorkerGlobalScope> scope(this->GlobalScope());
|
||||
CallbackDebuggerNotificationGuard guard(
|
||||
scope, info->mIsInterval
|
||||
? DebuggerNotificationType::SetIntervalCallback
|
||||
: DebuggerNotificationType::SetTimeoutCallback);
|
||||
|
||||
if (!handler->Call(reason)) {
|
||||
retval = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
MOZ_ASSERT(info->mReason == Timeout::Reason::eAbortSignalTimeout);
|
||||
MOZ_ALWAYS_TRUE(handler->Call("AbortSignal timeout"));
|
||||
}
|
||||
|
||||
NS_ASSERTION(data->mRunningExpiredTimeouts, "Someone changed this!");
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "mozilla/UseCounter.h"
|
||||
#include "mozilla/dom/ClientSource.h"
|
||||
#include "mozilla/dom/FlippedOnce.h"
|
||||
#include "mozilla/dom/Timeout.h"
|
||||
#include "mozilla/dom/quota/CheckedUnsafePtr.h"
|
||||
#include "mozilla/dom/Worker.h"
|
||||
#include "mozilla/dom/WorkerCommon.h"
|
||||
@ -317,9 +318,10 @@ class WorkerPrivate final
|
||||
const nsTArray<nsString>& aParams);
|
||||
|
||||
int32_t SetTimeout(JSContext* aCx, TimeoutHandler* aHandler, int32_t aTimeout,
|
||||
bool aIsInterval, ErrorResult& aRv);
|
||||
bool aIsInterval, Timeout::Reason aReason,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void ClearTimeout(int32_t aId);
|
||||
void ClearTimeout(int32_t aId, Timeout::Reason aReason);
|
||||
|
||||
MOZ_CAN_RUN_SCRIPT bool RunExpiredTimeouts(JSContext* aCx);
|
||||
|
||||
|
@ -521,7 +521,7 @@ void WorkerGlobalScope::ClearTimeout(int32_t aHandle) {
|
||||
|
||||
DebuggerNotificationDispatch(this, DebuggerNotificationType::ClearTimeout);
|
||||
|
||||
mWorkerPrivate->ClearTimeout(aHandle);
|
||||
mWorkerPrivate->ClearTimeout(aHandle, Timeout::Reason::eTimeoutOrInterval);
|
||||
}
|
||||
|
||||
int32_t WorkerGlobalScope::SetInterval(JSContext* aCx, Function& aHandler,
|
||||
@ -544,7 +544,7 @@ void WorkerGlobalScope::ClearInterval(int32_t aHandle) {
|
||||
|
||||
DebuggerNotificationDispatch(this, DebuggerNotificationType::ClearInterval);
|
||||
|
||||
mWorkerPrivate->ClearTimeout(aHandle);
|
||||
mWorkerPrivate->ClearTimeout(aHandle, Timeout::Reason::eTimeoutOrInterval);
|
||||
}
|
||||
|
||||
int32_t WorkerGlobalScope::SetTimeoutOrInterval(
|
||||
@ -565,7 +565,8 @@ int32_t WorkerGlobalScope::SetTimeoutOrInterval(
|
||||
RefPtr<TimeoutHandler> handler =
|
||||
new CallbackTimeoutHandler(aCx, this, &aHandler, std::move(args));
|
||||
|
||||
return mWorkerPrivate->SetTimeout(aCx, handler, aTimeout, aIsInterval, aRv);
|
||||
return mWorkerPrivate->SetTimeout(aCx, handler, aTimeout, aIsInterval,
|
||||
Timeout::Reason::eTimeoutOrInterval, aRv);
|
||||
}
|
||||
|
||||
int32_t WorkerGlobalScope::SetTimeoutOrInterval(JSContext* aCx,
|
||||
@ -589,7 +590,8 @@ int32_t WorkerGlobalScope::SetTimeoutOrInterval(JSContext* aCx,
|
||||
RefPtr<TimeoutHandler> handler =
|
||||
new WorkerScriptTimeoutHandler(aCx, this, aHandler);
|
||||
|
||||
return mWorkerPrivate->SetTimeout(aCx, handler, aTimeout, aIsInterval, aRv);
|
||||
return mWorkerPrivate->SetTimeout(aCx, handler, aTimeout, aIsInterval,
|
||||
Timeout::Reason::eTimeoutOrInterval, aRv);
|
||||
}
|
||||
|
||||
void WorkerGlobalScope::GetOrigin(nsAString& aOrigin) const {
|
||||
|
@ -1,20 +0,0 @@
|
||||
[AbortSignal.any.html]
|
||||
[AbortSignal.timeout() returns a non-aborted signal]
|
||||
expected: FAIL
|
||||
|
||||
[Signal returned by AbortSignal.timeout() times out]
|
||||
expected: FAIL
|
||||
|
||||
[AbortSignal timeouts fire in order]
|
||||
expected: FAIL
|
||||
|
||||
|
||||
[AbortSignal.any.worker.html]
|
||||
[AbortSignal.timeout() returns a non-aborted signal]
|
||||
expected: FAIL
|
||||
|
||||
[Signal returned by AbortSignal.timeout() times out]
|
||||
expected: FAIL
|
||||
|
||||
[AbortSignal timeouts fire in order]
|
||||
expected: FAIL
|
@ -1,3 +0,0 @@
|
||||
[abort-signal-timeout.html]
|
||||
[Signal returned by AbortSignal.timeout() is not aborted after frame detach]
|
||||
expected: FAIL
|
@ -1,22 +0,0 @@
|
||||
[idlharness.any.sharedworker.html]
|
||||
[AbortSignal interface: operation timeout(unsigned long long)]
|
||||
expected: FAIL
|
||||
|
||||
[AbortSignal interface: calling timeout(unsigned long long) on new AbortController().signal with too few arguments must throw TypeError]
|
||||
expected: FAIL
|
||||
|
||||
|
||||
[idlharness.any.worker.html]
|
||||
[AbortSignal interface: operation timeout(unsigned long long)]
|
||||
expected: FAIL
|
||||
|
||||
[AbortSignal interface: calling timeout(unsigned long long) on new AbortController().signal with too few arguments must throw TypeError]
|
||||
expected: FAIL
|
||||
|
||||
|
||||
[idlharness.any.serviceworker.html]
|
||||
[AbortSignal interface: operation timeout(unsigned long long)]
|
||||
expected: FAIL
|
||||
|
||||
[AbortSignal interface: calling timeout(unsigned long long) on new AbortController().signal with too few arguments must throw TypeError]
|
||||
expected: FAIL
|
@ -6,9 +6,3 @@
|
||||
|
||||
[Stringification of document.createNSResolver(document.body)]
|
||||
expected: FAIL
|
||||
|
||||
[AbortSignal interface: operation timeout(unsigned long long)]
|
||||
expected: FAIL
|
||||
|
||||
[AbortSignal interface: calling timeout(unsigned long long) on new AbortController().signal with too few arguments must throw TypeError]
|
||||
expected: FAIL
|
||||
|
Loading…
Reference in New Issue
Block a user