diff --git a/builtins/web/abort/abort-signal.cpp b/builtins/web/abort/abort-signal.cpp index 2bc6a3a5..b5977870 100644 --- a/builtins/web/abort/abort-signal.cpp +++ b/builtins/web/abort/abort-signal.cpp @@ -5,6 +5,7 @@ #include "../event/event.h" #include "../timers.h" +#include "js/ForOfIterator.h" namespace builtins::web::abort { @@ -102,9 +103,6 @@ bool AbortSignal::timeout(JSContext *cx, unsigned argc, JS::Value *vp) { // https://dom.spec.whatwg.org/#dom-abortsignal-abort bool AbortSignal::abort(JSContext *cx, unsigned argc, JS::Value *vp) { CallArgs args = JS::CallArgsFromVp(argc, vp); - if (!args.requireAtLeast(cx, "abort", 1)) { - return false; - } // The abort() method steps are inlined in the AbortSignal::create_with_reason method. RootedObject self(cx, create_with_reason(cx, args.get(0))); @@ -123,8 +121,35 @@ bool AbortSignal::any(JSContext *cx, unsigned argc, JS::Value *vp) { return false; } - // The any() method steps are inlined in the AbortSignal::create_with_signals method. - RootedObject self(cx, create_with_signals(cx, args)); + RootedValue iterable(cx, args.get(0)); + JS::ForOfIterator it(cx); + if (!it.init(iterable)) { + return false; + } + + JS::RootedValueVector signals(cx); + RootedValue signal(cx); + while (true) { + bool done = false; + if (!it.next(&signal, &done)) { + return false; + } + + if (done) { + break; + } + + if (!is_instance(signal)) { + return api::throw_error(cx, api::Errors::TypeError, "AbortSignal.any", "signals", + "contain only AbortSignal objects"); + } + + if (!signals.append(signal)) { + return false; + } + } + + RootedObject self(cx, create_with_signals(cx, signals)); if (!self) { return false; } @@ -142,6 +167,7 @@ bool AbortSignal::throwIfAborted(JSContext *cx, unsigned argc, JS::Value *vp) { if (is_aborted(self)) { RootedValue reason(cx, JS::GetReservedSlot(self, std::to_underlying(Slots::Reason))); JS_SetPendingException(cx, reason); + return false; } return true; @@ -222,7 +248,10 @@ bool AbortSignal::abort(JSContext *cx, HandleObject self, HandleValue reason) { // 2. Set signal's abort reason to reason if it is given; // otherwise to a new "AbortError" DOMException. - set_reason(cx, self, reason); + if (!set_reason(cx, self, reason)) { + return false; + } + RootedValue abort_reason(cx, AbortSignal::reason(self)); // 3. Let dependentSignalsToAbort be a new list. JS::RootedObjectVector dep_signals_to_abort(cx); @@ -234,7 +263,9 @@ bool AbortSignal::abort(JSContext *cx, HandleObject self, HandleValue reason) { // 1. If dependentSignal is not aborted: if (!is_aborted(signal)) { // 1. Set dependentSignal's abort reason to signal's abort reason. - set_reason(cx, signal, reason); + if (!set_reason(cx, signal, abort_reason)) { + return false; + } // 2. Append dependentSignal to dependentSignalsToAbort. if (!dep_signals_to_abort.append(signal)) { return false; @@ -276,7 +307,7 @@ bool AbortSignal::run_abort_steps(JSContext *cx, HandleObject self) { RootedObject event(cx, Event::create(cx, type_val, JS::NullHandleValue)); RootedValue event_val(cx, JS::ObjectValue(*event)); - return EventTarget::dispatch_event(cx, self, event_val, &res_val); + return EventTarget::dispatch_event(cx, self, event_val, &res_val, true); } // Set signal's abort reason to reason if it is given; otherwise to a new "AbortError" DOMException. @@ -408,7 +439,9 @@ JSObject *AbortSignal::create_with_signals(JSContext *cx, HandleValueArray signa // 2. For each signal of signals: if signal is aborted, then set resultSignal's abort reason to // signal's abort reason and return resultSignal. for (size_t i = 0; i < signals.length(); ++i) { - RootedObject signal(cx, &signals[i].toObject()); + RootedValue signal_val(cx, signals[i]); + MOZ_ASSERT(is_instance(signal_val)); + RootedObject signal(cx, &signal_val.toObject()); if (is_aborted(signal)) { SetReservedSlot(self, std::to_underlying(Slots::Reason), reason(signal)); @@ -422,7 +455,8 @@ JSObject *AbortSignal::create_with_signals(JSContext *cx, HandleValueArray signa // 4. For each signal of signals: for (size_t i = 0; i < signals.length(); ++i) { - RootedObject signal(cx, &signals[i].toObject()); + RootedValue signal_val(cx, signals[i]); + RootedObject signal(cx, &signal_val.toObject()); // 1. If signal's dependent is false: if (!is_dependent(signal)) { @@ -514,5 +548,3 @@ bool install(api::Engine *engine) { JSString *AbortSignal::abort_type_atom = nullptr; } // namespace builtins::web::abort - - diff --git a/builtins/web/event/event-target.cpp b/builtins/web/event/event-target.cpp index b77b1530..55d1ca5a 100644 --- a/builtins/web/event/event-target.cpp +++ b/builtins/web/event/event-target.cpp @@ -361,7 +361,7 @@ bool EventTarget::remove_listener(JSContext *cx, HandleObject self, HandleValue // https://dom.spec.whatwg.org/#dom-eventtarget-dispatchevent bool EventTarget::dispatch_event(JSContext *cx, HandleObject self, HandleValue event_val, - MutableHandleValue rval) { + MutableHandleValue rval, bool trusted) { MOZ_ASSERT(is_instance(self)); if (!Event::is_instance(event_val)) { @@ -379,8 +379,8 @@ bool EventTarget::dispatch_event(JSContext *cx, HandleObject self, HandleValue e "InvalidStateError"); } - // 2. Initialize event's isTrusted attribute to false. - Event::set_flag(event, EventFlag::Trusted, false); + // 2. Initialize event's isTrusted attribute. + Event::set_flag(event, EventFlag::Trusted, trusted); // 3. Return the result of dispatching event to this. return dispatch(cx, self, event, nullptr, rval); @@ -651,4 +651,3 @@ bool EventTarget::init_class(JSContext *cx, JS::HandleObject global) { } // namespace builtins::web::event - diff --git a/builtins/web/event/event-target.h b/builtins/web/event/event-target.h index f780a047..aeeac58c 100644 --- a/builtins/web/event/event-target.h +++ b/builtins/web/event/event-target.h @@ -67,7 +67,7 @@ class EventTarget : public BuiltinImpl { static bool remove_listener(JSContext *cx, HandleObject self, HandleValue type, HandleValue callback, HandleValue opts); static bool dispatch_event(JSContext *cx, HandleObject self, HandleValue event, - MutableHandleValue rval); + MutableHandleValue rval, bool trusted = false); static JSObject *create(JSContext *cx); diff --git a/tests/wpt-harness/expectations/dom/abort/AbortSignal.any.js.json b/tests/wpt-harness/expectations/dom/abort/AbortSignal.any.js.json new file mode 100644 index 00000000..eb9125de --- /dev/null +++ b/tests/wpt-harness/expectations/dom/abort/AbortSignal.any.js.json @@ -0,0 +1,17 @@ +{ + "the AbortSignal.abort() static returns an already aborted signal": { + "status": "PASS" + }, + "signal returned by AbortSignal.abort() should not fire abort event": { + "status": "PASS" + }, + "AbortSignal.timeout() returns a non-aborted signal": { + "status": "PASS" + }, + "Signal returned by AbortSignal.timeout() times out": { + "status": "PASS" + }, + "AbortSignal timeouts fire in order": { + "status": "PASS" + } +} \ No newline at end of file diff --git a/tests/wpt-harness/expectations/dom/abort/abort-signal-any.any.js.json b/tests/wpt-harness/expectations/dom/abort/abort-signal-any.any.js.json new file mode 100644 index 00000000..ce8e6ba7 --- /dev/null +++ b/tests/wpt-harness/expectations/dom/abort/abort-signal-any.any.js.json @@ -0,0 +1,44 @@ +{ + "AbortSignal.any() works with an empty array of signals": { + "status": "PASS" + }, + "AbortSignal.any() follows a single signal (using AbortController)": { + "status": "PASS" + }, + "AbortSignal.any() follows multiple signals (using AbortController)": { + "status": "PASS" + }, + "AbortSignal.any() returns an aborted signal if passed an aborted signal (using AbortController)": { + "status": "PASS" + }, + "AbortSignal.any() can be passed the same signal more than once (using AbortController)": { + "status": "PASS" + }, + "AbortSignal.any() uses the first instance of a duplicate signal (using AbortController)": { + "status": "PASS" + }, + "AbortSignal.any() signals are composable (using AbortController)": { + "status": "PASS" + }, + "AbortSignal.any() works with signals returned by AbortSignal.timeout() (using AbortController)": { + "status": "PASS" + }, + "AbortSignal.any() works with intermediate signals (using AbortController)": { + "status": "PASS" + }, + "Abort events for AbortSignal.any() signals fire in the right order (using AbortController)": { + "status": "PASS" + }, + "Dependent signals for AbortSignal.any() are marked aborted before abort events fire (using AbortController)": { + "status": "PASS" + }, + "Dependent signals for AbortSignal.any() are aborted correctly for reentrant aborts (using AbortController)": { + "status": "PASS" + }, + "Dependent signals for AbortSignal.any() should use the same DOMException instance from the already aborted source signal (using AbortController)": { + "status": "PASS" + }, + "Dependent signals for AbortSignal.any() should use the same DOMException instance from the source signal being aborted later (using AbortController)": { + "status": "PASS" + } +} \ No newline at end of file diff --git a/tests/wpt-harness/expectations/dom/abort/event.any.js.json b/tests/wpt-harness/expectations/dom/abort/event.any.js.json new file mode 100644 index 00000000..cd31fbb2 --- /dev/null +++ b/tests/wpt-harness/expectations/dom/abort/event.any.js.json @@ -0,0 +1,50 @@ +{ + "AbortController abort() should fire event synchronously": { + "status": "PASS" + }, + "controller.signal should always return the same object": { + "status": "PASS" + }, + "controller.abort() should do nothing the second time it is called": { + "status": "PASS" + }, + "event handler should not be called if added after controller.abort()": { + "status": "PASS" + }, + "the abort event should have the right properties": { + "status": "PASS" + }, + "AbortController abort(reason) should set signal.reason": { + "status": "PASS" + }, + "aborting AbortController without reason creates an \"AbortError\" DOMException": { + "status": "PASS" + }, + "AbortController abort(undefined) creates an \"AbortError\" DOMException": { + "status": "PASS" + }, + "AbortController abort(null) should set signal.reason": { + "status": "PASS" + }, + "static aborting signal should have right properties": { + "status": "PASS" + }, + "static aborting signal with reason should set signal.reason": { + "status": "PASS" + }, + "throwIfAborted() should throw abort.reason if signal aborted": { + "status": "PASS" + }, + "throwIfAborted() should throw primitive abort.reason if signal aborted": { + "status": "PASS" + }, + "throwIfAborted() should not throw if signal not aborted": { + "status": "PASS" + }, + "AbortSignal.reason returns the same DOMException": { + "status": "PASS" + }, + "AbortController.signal.reason returns the same DOMException": { + "status": "PASS" + } +} \ No newline at end of file diff --git a/tests/wpt-harness/tests.json b/tests/wpt-harness/tests.json index 6f19dc13..df1e73ce 100644 --- a/tests/wpt-harness/tests.json +++ b/tests/wpt-harness/tests.json @@ -25,6 +25,9 @@ "console/console-namespace-object-class-string.any.js", "console/console-tests-historical.any.js", "console/idlharness.any.js", + "dom/abort/AbortSignal.any.js", + "dom/abort/abort-signal-any.any.js", + "dom/abort/event.any.js", "dom/events/AddEventListenerOptions-once.any.js", "dom/events/AddEventListenerOptions-passive.any.js", "dom/events/Event-constructors.any.js",