LibWeb+IDLGenerators: Support nullable union types

This commit is contained in:
Luke Wilde 2026-02-17 14:57:03 +00:00 committed by Sam Atkins
parent 500ca417ce
commit cfd795f907
39 changed files with 146 additions and 128 deletions

View file

@ -211,7 +211,7 @@ void Animation::set_timeline(GC::Ptr<AnimationTimeline> new_timeline)
}
// https://drafts.csswg.org/web-animations-2/#validating-a-css-numberish-time
WebIDL::ExceptionOr<Optional<TimeValue>> Animation::validate_a_css_numberish_time(Optional<CSS::CSSNumberish> const& time) const
WebIDL::ExceptionOr<Optional<TimeValue>> Animation::validate_a_css_numberish_time(NullableCSSNumberish const& time) const
{
// The procedure to validate a CSSNumberish time for an input value of time is based on the first condition that matches:
@ -221,7 +221,7 @@ WebIDL::ExceptionOr<Optional<TimeValue>> Animation::validate_a_css_numberish_tim
m_timeline && m_timeline->is_progress_based() &&
// time is not a CSSNumeric value with percent units:
(!time.has_value() || !time->has<GC::Root<CSS::CSSNumericValue>>() || !time->get<GC::Root<CSS::CSSNumericValue>>()->type().matches_percentage())) {
(!time.has<GC::Root<CSS::CSSNumericValue>>() || !time.get<GC::Root<CSS::CSSNumericValue>>()->type().matches_percentage())) {
// throw a TypeError.
// return false;
return WebIDL::SimpleException {
@ -236,14 +236,14 @@ WebIDL::ExceptionOr<Optional<TimeValue>> Animation::validate_a_css_numberish_tim
(!m_timeline || !m_timeline->is_progress_based()) &&
// time is a CSSNumericValue, and
time.has_value() && time->has<GC::Root<CSS::CSSNumericValue>>() &&
time.has<GC::Root<CSS::CSSNumericValue>>() &&
// the units of time are not duration units:
!time->get<GC::Root<CSS::CSSNumericValue>>()->type().matches_time({}) &&
!time.get<GC::Root<CSS::CSSNumericValue>>()->type().matches_time({}) &&
// AD-HOC: While it's not mentioned in the spec WPT also expects us to support CSSNumericValue number value, see
// https://github.com/w3c/csswg-drafts/issues/13196
!time->get<GC::Root<CSS::CSSNumericValue>>()->type().matches_number({})) {
!time.get<GC::Root<CSS::CSSNumericValue>>()->type().matches_number({})) {
// throw a TypeError.
// return false.
return WebIDL::SimpleException {
@ -257,19 +257,19 @@ WebIDL::ExceptionOr<Optional<TimeValue>> Animation::validate_a_css_numberish_tim
// AD-HOC: The spec doesn't say when we should absolutize the validated value so we do it here and return the
// absolutized value instead of a boolean
if (!time.has_value())
if (time.has<Empty>())
return OptionalNone {};
// FIXME: Figure out which element we should use for this, for now we just use the document element of the current
// window
return TimeValue::from_css_numberish(time.value(), DOM::AbstractElement { *as<HTML::Window>(realm().global_object()).associated_document().document_element() });
return TimeValue::from_css_numberish(time.downcast<double, GC::Root<CSS::CSSNumericValue>>(), DOM::AbstractElement { *as<HTML::Window>(realm().global_object()).associated_document().document_element() });
VERIFY_NOT_REACHED();
}
// https://www.w3.org/TR/web-animations-1/#dom-animation-starttime
// https://www.w3.org/TR/web-animations-1/#set-the-start-time
WebIDL::ExceptionOr<void> Animation::set_start_time_for_bindings(Optional<CSS::CSSNumberish> const& raw_new_start_time)
WebIDL::ExceptionOr<void> Animation::set_start_time_for_bindings(NullableCSSNumberish const& raw_new_start_time)
{
// Setting this attribute updates the start time using the procedure to set the start time of this object to the new
// value.
@ -400,7 +400,7 @@ Optional<TimeValue> Animation::current_time() const
}
// https://www.w3.org/TR/web-animations-1/#animation-set-the-current-time
WebIDL::ExceptionOr<void> Animation::set_current_time_for_bindings(Optional<CSS::CSSNumberish> const& raw_seek_time)
WebIDL::ExceptionOr<void> Animation::set_current_time_for_bindings(NullableCSSNumberish const& raw_seek_time)
{
// AD-HOC: We validate here instead of within silently_set_current_time so we have access to the `TimeValue`
// value within this function.
@ -655,7 +655,7 @@ void Animation::cancel(ShouldInvalidate should_invalidate)
// not associated with an active timeline, let timeline time be an unresolved time value.
// 9. Set cancelEvents timelineTime to timeline time. If timeline time is unresolved, set it to null.
AnimationPlaybackEventInit init;
init.timeline_time = m_timeline && !m_timeline->is_inactive() ? m_timeline->current_time().map([&](auto const& value) { return value.as_css_numberish(realm); }) : Optional<CSS::CSSNumberish> {};
init.timeline_time = m_timeline && !m_timeline->is_inactive() ? NullableCSSNumberish { m_timeline->current_time()->as_css_numberish(realm) } : NullableCSSNumberish { Empty {} };
auto cancel_event = AnimationPlaybackEvent::create(realm, HTML::EventNames::cancel, init);
// 10. If animation has a document for timing, then append cancelEvent to its document for timing's pending
@ -1329,7 +1329,7 @@ void Animation::update_finished_state(DidSeek did_seek, SynchronouslyNotify sync
AnimationPlaybackEventInit init;
init.current_time = current_time()->as_css_numberish(realm);
if (m_timeline && !m_timeline->is_inactive())
init.timeline_time = m_timeline->current_time().map([&](auto const& value) { return value.as_css_numberish(realm); });
init.timeline_time = m_timeline->current_time()->as_css_numberish(realm);
auto finish_event = AnimationPlaybackEvent::create(realm, HTML::EventNames::finish, init);

View file

@ -52,7 +52,7 @@ public:
return NullableCSSNumberish::from_optional_css_numberish_time(realm(), start_time());
}
Optional<TimeValue> start_time() const { return m_start_time; }
WebIDL::ExceptionOr<void> set_start_time_for_bindings(Optional<CSS::CSSNumberish> const&);
WebIDL::ExceptionOr<void> set_start_time_for_bindings(NullableCSSNumberish const&);
void calculate_auto_aligned_start_time();
@ -62,7 +62,7 @@ public:
return NullableCSSNumberish::from_optional_css_numberish_time(realm(), current_time());
}
Optional<TimeValue> current_time() const;
WebIDL::ExceptionOr<void> set_current_time_for_bindings(Optional<CSS::CSSNumberish> const&);
WebIDL::ExceptionOr<void> set_current_time_for_bindings(NullableCSSNumberish const&);
double playback_rate() const { return m_playback_rate; }
WebIDL::ExceptionOr<void> set_playback_rate(double value);
@ -163,7 +163,7 @@ private:
double effective_playback_rate() const;
WebIDL::ExceptionOr<Optional<TimeValue>> validate_a_css_numberish_time(Optional<CSS::CSSNumberish> const&) const;
WebIDL::ExceptionOr<Optional<TimeValue>> validate_a_css_numberish_time(NullableCSSNumberish const&) const;
void apply_any_pending_playback_rate();
WebIDL::ExceptionOr<void> silently_set_current_time(Optional<TimeValue>);

View file

@ -25,11 +25,10 @@ WebIDL::ExceptionOr<GC::Ref<AnimationPlaybackEvent>> AnimationPlaybackEvent::con
return create(realm, type, event_init);
}
AnimationPlaybackEvent::CSSNumberishInternal AnimationPlaybackEvent::to_numberish_internal(Optional<CSS::CSSNumberish> const& numberish_root)
AnimationPlaybackEvent::CSSNumberishInternal AnimationPlaybackEvent::to_numberish_internal(NullableCSSNumberish const& numberish_root)
{
if (!numberish_root.has_value())
return Empty {};
return numberish_root->visit(
return numberish_root.visit(
[](Empty) -> CSSNumberishInternal { return Empty {}; },
[](GC::Root<CSS::CSSNumericValue> const& root) -> CSSNumberishInternal { return GC::Ref { *root }; },
[](auto const& other) -> CSSNumberishInternal { return other; });
}

View file

@ -15,8 +15,8 @@ namespace Web::Animations {
// https://www.w3.org/TR/web-animations-1/#dictdef-animationplaybackeventinit
struct AnimationPlaybackEventInit : public DOM::EventInit {
Optional<CSS::CSSNumberish> current_time;
Optional<CSS::CSSNumberish> timeline_time;
NullableCSSNumberish current_time { Empty {} };
NullableCSSNumberish timeline_time { Empty {} };
};
// https://www.w3.org/TR/web-animations-1/#animationplaybackevent
@ -40,7 +40,7 @@ private:
virtual void visit_edges(Visitor&) override;
using CSSNumberishInternal = Variant<Empty, double, GC::Ref<CSS::CSSNumericValue>>;
static CSSNumberishInternal to_numberish_internal(Optional<CSS::CSSNumberish> const&);
static CSSNumberishInternal to_numberish_internal(NullableCSSNumberish const&);
static NullableCSSNumberish to_nullable_numberish(CSSNumberishInternal const&);
// https://drafts.csswg.org/web-animations-2/#dom-animationplaybackevent-currenttime

View file

@ -74,9 +74,7 @@ struct TimeValue {
CSS::CSSNumberish as_css_numberish(JS::Realm& realm) const;
};
// FIXME: This struct is required since our IDL generator requires us to return nullable union types as
// Variant<Empty, Ts...> rather than Optional<Variant<Ts...>> (although setters are forced to be
// Optional<Variant<Ts...>>)
// Nullable CSSNumberish is Variant<double, GC::Root<CSSNumericValue>, Empty> where Empty represents null.
struct NullableCSSNumberish : FlattenVariant<Variant<Empty>, CSS::CSSNumberish> {
using Variant::Variant;

View file

@ -5944,8 +5944,8 @@ void Document::remove_replaced_animations()
// - Set removeEvents timelineTime attribute to the current time of the timeline with which animation is
// associated.
Animations::AnimationPlaybackEventInit init;
init.current_time = animation->current_time().map([&](auto const& value) { return value.as_css_numberish(realm()); });
init.timeline_time = animation->timeline()->current_time().map([&](auto const& value) { return value.as_css_numberish(realm()); });
init.current_time = animation->current_time().has_value() ? Animations::NullableCSSNumberish { animation->current_time()->as_css_numberish(realm()) } : Animations::NullableCSSNumberish { Empty {} };
init.timeline_time = animation->timeline()->current_time().has_value() ? Animations::NullableCSSNumberish { animation->timeline()->current_time()->as_css_numberish(realm()) } : Animations::NullableCSSNumberish { Empty {} };
auto remove_event = Animations::AnimationPlaybackEvent::create(realm(), HTML::EventNames::remove, init);
// - If animation has a document for timing, then append removeEvent to its document for timing's pending

View file

@ -16,6 +16,7 @@ namespace Web::Fetch {
// https://fetch.spec.whatwg.org/#bodyinit
using BodyInit = Variant<GC::Root<Streams::ReadableStream>, GC::Root<FileAPI::Blob>, GC::Root<WebIDL::BufferSource>, GC::Root<XHR::FormData>, GC::Root<DOMURL::URLSearchParams>, String>;
using NullableBodyInit = Variant<GC::Root<Streams::ReadableStream>, GC::Root<FileAPI::Blob>, GC::Root<WebIDL::BufferSource>, GC::Root<XHR::FormData>, GC::Root<DOMURL::URLSearchParams>, String, Empty>;
using BodyInitOrReadableBytes = Variant<GC::Root<Streams::ReadableStream>, GC::Root<FileAPI::Blob>, GC::Root<WebIDL::BufferSource>, GC::Root<XHR::FormData>, GC::Root<DOMURL::URLSearchParams>, String, ReadonlyBytes>;
WEB_API Infrastructure::BodyWithType safely_extract_body(JS::Realm&, BodyInitOrReadableBytes const&);

View file

@ -449,16 +449,16 @@ WebIDL::ExceptionOr<GC::Ref<Request>> Request::construct_impl(JS::Realm& realm,
input_body = input.get<GC::Root<Request>>()->request()->body();
// 35. If either init["body"] exists and is non-null or inputBody is non-null, and requests method is `GET` or `HEAD`, then throw a TypeError.
if (((init.body.has_value() && (*init.body).has_value()) || (input_body.has_value() && !input_body.value().has<Empty>())) && request->method().is_one_of("GET"sv, "HEAD"sv))
if (((init.body.has_value() && !init.body->has<Empty>()) || (input_body.has_value() && !input_body.value().has<Empty>())) && request->method().is_one_of("GET"sv, "HEAD"sv))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Method must not be GET or HEAD when body is provided"sv };
// 36. Let initBody be null.
Optional<Infrastructure::Request::BodyType> init_body;
// 37. If init["body"] exists and is non-null, then:
if (init.body.has_value() && (*init.body).has_value()) {
// 1. Let bodyWithType be the result of extracting init["body"], with keepalive set to requests keepalive.
auto body_with_type = TRY(extract_body(realm, (*init.body).value(), request->keepalive()));
if (init.body.has_value() && !init.body->has<Empty>()) {
// 1. Let bodyWithType be the result of extracting init["body"], with keepalive set to request's keepalive.
auto body_with_type = TRY(extract_body(realm, init.body->downcast<GC::Root<Streams::ReadableStream>, GC::Root<FileAPI::Blob>, GC::Root<WebIDL::BufferSource>, GC::Root<XHR::FormData>, GC::Root<DOMURL::URLSearchParams>, String>(), request->keepalive()));
// 2. Set initBody to bodyWithTypes body.
init_body = body_with_type.body;

View file

@ -26,7 +26,7 @@ using RequestInfo = Variant<GC::Root<Request>, String>;
struct RequestInit {
Optional<String> method;
Optional<HeadersInit> headers;
Optional<Optional<BodyInit>> body;
Optional<NullableBodyInit> body;
Optional<String> referrer;
Optional<Bindings::ReferrerPolicy> referrer_policy;
Optional<Bindings::RequestMode> mode;

View file

@ -136,7 +136,7 @@ WebIDL::ExceptionOr<void> Response::initialize_response(ResponseInit const& init
}
// https://fetch.spec.whatwg.org/#dom-response
WebIDL::ExceptionOr<GC::Ref<Response>> Response::construct_impl(JS::Realm& realm, Optional<BodyInit> const& body, ResponseInit const& init)
WebIDL::ExceptionOr<GC::Ref<Response>> Response::construct_impl(JS::Realm& realm, NullableBodyInit const& body, ResponseInit const& init)
{
auto& vm = realm.vm();
@ -155,8 +155,8 @@ WebIDL::ExceptionOr<GC::Ref<Response>> Response::construct_impl(JS::Realm& realm
Optional<Infrastructure::BodyWithType> body_with_type;
// 4. If body is non-null, then set bodyWithType to the result of extracting body.
if (body.has_value())
body_with_type = TRY(extract_body(realm, *body));
if (!body.has<Empty>())
body_with_type = TRY(extract_body(realm, body.downcast<GC::Root<Streams::ReadableStream>, GC::Root<FileAPI::Blob>, GC::Root<WebIDL::BufferSource>, GC::Root<XHR::FormData>, GC::Root<DOMURL::URLSearchParams>, String>()));
// 5. Perform initialize a response given this, init, and bodyWithType.
TRY(response_object->initialize_response(init, body_with_type));

View file

@ -35,7 +35,7 @@ class Response final
public:
[[nodiscard]] static GC::Ref<Response> create(JS::Realm&, GC::Ref<Infrastructure::Response>, Headers::Guard);
static WebIDL::ExceptionOr<GC::Ref<Response>> construct_impl(JS::Realm&, Optional<BodyInit> const& body = {}, ResponseInit const& init = {});
static WebIDL::ExceptionOr<GC::Ref<Response>> construct_impl(JS::Realm&, NullableBodyInit const& body = { Empty {} }, ResponseInit const& init = {});
virtual ~Response() override;

View file

@ -47,7 +47,7 @@ GC::Ptr<DOM::ShadowRoot> ElementInternals::shadow_root() const
}
// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-elementinternals-setformvalue
WebIDL::ExceptionOr<void> ElementInternals::set_form_value(Variant<GC::Root<FileAPI::File>, String, GC::Root<XHR::FormData>> value, Optional<Variant<GC::Root<FileAPI::File>, String, GC::Root<XHR::FormData>>> state)
WebIDL::ExceptionOr<void> ElementInternals::set_form_value(Variant<GC::Root<FileAPI::File>, String, GC::Root<XHR::FormData>, Empty> value, Optional<Variant<GC::Root<FileAPI::File>, String, GC::Root<XHR::FormData>, Empty>> state)
{
// 1. Let element be this's target element.
auto element = m_target_element;

View file

@ -45,7 +45,7 @@ public:
GC::Ptr<DOM::ShadowRoot> shadow_root() const;
WebIDL::ExceptionOr<void> set_form_value(Variant<GC::Root<FileAPI::File>, String, GC::Root<XHR::FormData>> value, Optional<Variant<GC::Root<FileAPI::File>, String, GC::Root<XHR::FormData>>> state);
WebIDL::ExceptionOr<void> set_form_value(Variant<GC::Root<FileAPI::File>, String, GC::Root<XHR::FormData>, Empty> value, Optional<Variant<GC::Root<FileAPI::File>, String, GC::Root<XHR::FormData>, Empty>> state);
WebIDL::ExceptionOr<GC::Ptr<HTMLFormElement>> form() const;

View file

@ -849,7 +849,7 @@ GC::Ptr<DOM::NodeList> HTMLElement::labels()
}
// https://html.spec.whatwg.org/multipage/interaction.html#dom-hidden
Variant<bool, double, String> HTMLElement::hidden() const
Variant<bool, double, String, Empty> HTMLElement::hidden() const
{
// 1. If the hidden attribute is in the hidden until found state, then return "until-found".
auto const& hidden = get_attribute(HTML::AttributeNames::hidden);
@ -862,7 +862,7 @@ Variant<bool, double, String> HTMLElement::hidden() const
return false;
}
void HTMLElement::set_hidden(Variant<bool, double, String> const& given_value)
void HTMLElement::set_hidden(Variant<bool, double, String, Empty> const& given_value)
{
// 1. If the given value is a string that is an ASCII case-insensitive match for "until-found", then set the hidden attribute to "until-found".
if (given_value.has<String>()) {
@ -876,11 +876,6 @@ void HTMLElement::set_hidden(Variant<bool, double, String> const& given_value)
remove_attribute(HTML::AttributeNames::hidden);
return;
}
// 4. Otherwise, if the given value is null, then remove the hidden attribute.
if (string.equals_ignoring_ascii_case("null"sv) || string.equals_ignoring_ascii_case("undefined"sv)) {
remove_attribute(HTML::AttributeNames::hidden);
return;
}
}
// 2. Otherwise, if the given value is false, then remove the hidden attribute.
else if (given_value.has<bool>()) {
@ -898,6 +893,11 @@ void HTMLElement::set_hidden(Variant<bool, double, String> const& given_value)
return;
}
}
// 4. Otherwise, if the given value is null, then remove the hidden attribute.
else if (given_value.has<Empty>()) {
remove_attribute(HTML::AttributeNames::hidden);
return;
}
// 7. Otherwise, set the hidden attribute to the empty string.
set_attribute_value(HTML::AttributeNames::hidden, ""_string);
}

View file

@ -108,8 +108,8 @@ public:
GC::Ptr<Element> offset_parent() const;
GC::Ptr<Element> scroll_parent() const;
Variant<bool, double, String> hidden() const;
void set_hidden(Variant<bool, double, String> const&);
Variant<bool, double, String, Empty> hidden() const;
void set_hidden(Variant<bool, double, String, Empty> const&);
void click();

View file

@ -119,7 +119,7 @@ WebIDL::ExceptionOr<void> HTMLOptionsCollection::set_value_of_indexed_property(u
}
// https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#dom-htmloptionscollection-add
WebIDL::ExceptionOr<void> HTMLOptionsCollection::add(HTMLOptionOrOptGroupElement element, Optional<HTMLElementOrElementIndex> before)
WebIDL::ExceptionOr<void> HTMLOptionsCollection::add(HTMLOptionOrOptGroupElement element, NullableHTMLElementOrElementIndex before)
{
auto resolved_element = element.visit(
[](auto& e) -> GC::Root<HTMLElement> {
@ -127,8 +127,8 @@ WebIDL::ExceptionOr<void> HTMLOptionsCollection::add(HTMLOptionOrOptGroupElement
});
GC::Ptr<DOM::Node> before_element;
if (before.has_value() && before->has<GC::Root<HTMLElement>>())
before_element = before->get<GC::Root<HTMLElement>>().ptr();
if (before.has<GC::Root<HTMLElement>>())
before_element = before.get<GC::Root<HTMLElement>>().ptr();
// 1. If element is an ancestor of the select element on which the HTMLOptionsCollection is rooted, then throw a "HierarchyRequestError" DOMException.
if (resolved_element->is_ancestor_of(root()))
@ -147,8 +147,8 @@ WebIDL::ExceptionOr<void> HTMLOptionsCollection::add(HTMLOptionOrOptGroupElement
if (before_element)
reference = move(before_element);
else if (before.has_value() && before->has<i32>())
reference = item(before->get<i32>());
else if (before.has<i32>())
reference = item(before.get<i32>());
// 5. If reference is not null, let parent be the parent node of reference. Otherwise, let parent be the select element on which the HTMLOptionsCollection is rooted.
DOM::Node* parent = reference ? reference->parent() : root().ptr();

View file

@ -15,6 +15,7 @@ namespace Web::HTML {
using HTMLOptionOrOptGroupElement = Variant<GC::Root<HTMLOptionElement>, GC::Root<HTMLOptGroupElement>>;
using HTMLElementOrElementIndex = Variant<GC::Root<HTMLElement>, i32>;
using NullableHTMLElementOrElementIndex = Variant<GC::Root<HTMLElement>, i32, Empty>;
class HTMLOptionsCollection final : public DOM::HTMLCollection {
WEB_PLATFORM_OBJECT(HTMLOptionsCollection, DOM::HTMLCollection);
@ -28,7 +29,7 @@ public:
WebIDL::ExceptionOr<void> set_length(WebIDL::UnsignedLong);
WebIDL::ExceptionOr<void> add(HTMLOptionOrOptGroupElement element, Optional<HTMLElementOrElementIndex> before = {});
WebIDL::ExceptionOr<void> add(HTMLOptionOrOptGroupElement element, NullableHTMLElementOrElementIndex before = { Empty {} });
void remove(WebIDL::Long);

View file

@ -770,14 +770,21 @@ Variant<GC::Root<TrustedTypes::TrustedScript>, Utf16String, Empty> HTMLScriptEle
}
// https://w3c.github.io/trusted-types/dist/spec/#the-textContent-idl-attribute
WebIDL::ExceptionOr<void> HTMLScriptElement::set_text_content(TrustedTypes::TrustedScriptOrString text)
WebIDL::ExceptionOr<void> HTMLScriptElement::set_text_content(TrustedTypes::NullableTrustedScriptOrString text)
{
// NOTE: We still require this from the base implementation.
// https://dom.spec.whatwg.org/#dom-node-textcontent
// The textContent setter steps are to, if the given value is null, act as if it was the empty string instead, and then run set text content with this and the given value.
TrustedTypes::TrustedScriptOrString non_null_text = text.has<Empty>()
? ""_utf16
: text.downcast<TrustedTypes::TrustedScriptOrString>();
// 1. Let value be the result of calling Get Trusted Type compliant string with
// TrustedScript, thiss relevant global object, the given value, HTMLScriptElement textContent, and script.
// TrustedScript, this's relevant global object, the given value, HTMLScriptElement textContent, and script.
auto const value = TRY(TrustedTypes::get_trusted_type_compliant_string(
TrustedTypes::TrustedTypeName::TrustedScript,
HTML::relevant_global_object(*this),
text,
non_null_text,
TrustedTypes::InjectionSink::HTMLScriptElement_textContent,
TrustedTypes::Script.to_string()));

View file

@ -66,7 +66,7 @@ public:
WebIDL::ExceptionOr<void> set_src(TrustedTypes::TrustedScriptURLOrString);
Variant<GC::Root<TrustedTypes::TrustedScript>, Utf16String, Empty> text_content() const;
WebIDL::ExceptionOr<void> set_text_content(TrustedTypes::TrustedScriptOrString);
WebIDL::ExceptionOr<void> set_text_content(TrustedTypes::NullableTrustedScriptOrString);
TrustedTypes::TrustedScriptOrString inner_text();
WebIDL::ExceptionOr<void> set_inner_text(TrustedTypes::TrustedScriptOrString);

View file

@ -179,7 +179,7 @@ HTMLOptionElement* HTMLSelectElement::named_item(FlyString const& name)
}
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-select-add
WebIDL::ExceptionOr<void> HTMLSelectElement::add(HTMLOptionOrOptGroupElement element, Optional<HTMLElementOrElementIndex> before)
WebIDL::ExceptionOr<void> HTMLSelectElement::add(HTMLOptionOrOptGroupElement element, NullableHTMLElementOrElementIndex before)
{
// Similarly, the add(element, before) method must act like its namesake method on that same options collection.
TRY(const_cast<HTMLOptionsCollection&>(*options()).add(move(element), move(before)));

View file

@ -45,7 +45,7 @@ public:
HTMLOptionElement* item(WebIDL::UnsignedLong index);
virtual Optional<JS::Value> item_value(size_t index) const override;
HTMLOptionElement* named_item(FlyString const& name);
WebIDL::ExceptionOr<void> add(HTMLOptionOrOptGroupElement element, Optional<HTMLElementOrElementIndex> before = {});
WebIDL::ExceptionOr<void> add(HTMLOptionOrOptGroupElement element, NullableHTMLElementOrElementIndex before = { Empty {} });
virtual WebIDL::ExceptionOr<void> set_value_of_indexed_property(u32, JS::Value) override;
void remove();
void remove(WebIDL::Long);

View file

@ -27,12 +27,11 @@ WebIDL::ExceptionOr<GC::Ref<MessageEvent>> MessageEvent::construct_impl(JS::Real
return create(realm, event_name, event_init);
}
MessageEvent::MessageEventSourceInternal MessageEvent::to_message_event_source_internal(Optional<MessageEventSource> const& source)
MessageEvent::MessageEventSourceInternal MessageEvent::to_message_event_source_internal(NullableMessageEventSource const& source)
{
if (!source.has_value())
return Empty {};
return source->visit([](auto const& root) -> MessageEventSourceInternal { return GC::Ref { *root }; });
return source.visit(
[](Empty) -> MessageEventSourceInternal { return Empty {}; },
[](auto const& root) -> MessageEventSourceInternal { return GC::Ref { *root }; });
}
MessageEvent::MessageEvent(JS::Realm& realm, FlyString const& event_name, MessageEventInit const& event_init)
@ -86,11 +85,11 @@ String MessageEvent::origin() const
});
}
MessageEvent::SourceResult MessageEvent::source() const
NullableMessageEventSource MessageEvent::source() const
{
return m_source.visit(
[](Empty) -> SourceResult { return Empty {}; },
[](auto const& ref) -> SourceResult { return GC::Root { *ref }; });
[](Empty) -> NullableMessageEventSource { return Empty {}; },
[](auto const& ref) -> NullableMessageEventSource { return GC::Root { *ref }; });
}
GC::Ref<JS::Object> MessageEvent::ports() const
@ -107,7 +106,7 @@ GC::Ref<JS::Object> MessageEvent::ports() const
}
// https://html.spec.whatwg.org/multipage/comms.html#dom-messageevent-initmessageevent
void MessageEvent::init_message_event(String const& type, bool bubbles, bool cancelable, JS::Value data, String const& origin, String const& last_event_id, Optional<MessageEventSource> source, Vector<GC::Root<MessagePort>> const& ports)
void MessageEvent::init_message_event(String const& type, bool bubbles, bool cancelable, JS::Value data, String const& origin, String const& last_event_id, NullableMessageEventSource source, Vector<GC::Root<MessagePort>> const& ports)
{
// The initMessageEvent(type, bubbles, cancelable, data, origin, lastEventId, source, ports) method must initialize the event in a
// manner analogous to the similarly-named initEvent() method.

View file

@ -17,13 +17,14 @@ namespace Web::HTML {
// FIXME: Include ServiceWorker
// https://html.spec.whatwg.org/multipage/comms.html#messageeventsource
using MessageEventSource = Variant<GC::Root<WindowProxy>, GC::Root<MessagePort>>;
using NullableMessageEventSource = Variant<GC::Root<WindowProxy>, GC::Root<MessagePort>, Empty>;
// https://html.spec.whatwg.org/multipage/comms.html#messageeventinit
struct MessageEventInit : public DOM::EventInit {
JS::Value data { JS::js_null() };
Variant<URL::Origin, String, Empty> origin {};
String last_event_id {};
Optional<MessageEventSource> source;
NullableMessageEventSource source { Empty {} };
Vector<GC::Root<MessagePort>> ports;
};
@ -44,19 +45,18 @@ public:
String const& last_event_id() const { return m_last_event_id; }
GC::Ref<JS::Object> ports() const;
using SourceResult = Variant<Empty, GC::Root<WindowProxy>, GC::Root<MessagePort>>;
SourceResult source() const;
NullableMessageEventSource source() const;
virtual Optional<URL::Origin> extract_an_origin() const override;
void init_message_event(String const& type, bool bubbles, bool cancelable, JS::Value data, String const& origin, String const& last_event_id, Optional<MessageEventSource> source, Vector<GC::Root<MessagePort>> const& ports);
void init_message_event(String const& type, bool bubbles, bool cancelable, JS::Value data, String const& origin, String const& last_event_id, NullableMessageEventSource source, Vector<GC::Root<MessagePort>> const& ports);
private:
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
using MessageEventSourceInternal = Variant<Empty, GC::Ref<WindowProxy>, GC::Ref<MessagePort>>;
static MessageEventSourceInternal to_message_event_source_internal(Optional<MessageEventSource> const&);
static MessageEventSourceInternal to_message_event_source_internal(NullableMessageEventSource const&);
JS::Value m_data;

View file

@ -18,7 +18,7 @@
namespace Web::HTML {
// https://w3c.github.io/beacon/#sendbeacon-method
WebIDL::ExceptionOr<bool> NavigatorBeaconPartial::send_beacon(String const& url, Optional<Fetch::BodyInit> const& data)
WebIDL::ExceptionOr<bool> NavigatorBeaconPartial::send_beacon(String const& url, Fetch::NullableBodyInit const& data)
{
auto& navigator = as<Navigator>(*this);
auto& realm = navigator.realm();
@ -46,9 +46,9 @@ WebIDL::ExceptionOr<bool> NavigatorBeaconPartial::send_beacon(String const& url,
// 6. If data is not null:
GC::Ptr<Fetch::Infrastructure::Body> transmitted_data;
if (data.has_value()) {
if (!data.has<Empty>()) {
// 6.1 Set transmittedData and contentType to the result of extracting data's byte stream with the keepalive flag set.
auto body_with_type = TRY(Fetch::extract_body(realm, data.value(), true));
auto body_with_type = TRY(Fetch::extract_body(realm, data.downcast<GC::Root<Streams::ReadableStream>, GC::Root<FileAPI::Blob>, GC::Root<WebIDL::BufferSource>, GC::Root<XHR::FormData>, GC::Root<DOMURL::URLSearchParams>, String>(), true));
transmitted_data = body_with_type.body;
auto& content_type = body_with_type.type;

View file

@ -13,7 +13,7 @@ namespace Web::HTML {
class NavigatorBeaconPartial {
public:
WebIDL::ExceptionOr<bool> send_beacon(String const& url, Optional<Fetch::BodyInit> const& data = {});
WebIDL::ExceptionOr<bool> send_beacon(String const& url, Fetch::NullableBodyInit const& data = { Empty {} });
private:
virtual ~NavigatorBeaconPartial() = default;

View file

@ -151,7 +151,7 @@ WebIDL::ExceptionOr<GC::Ref<SharedWorker>> SharedWorker::construct_impl(JS::Real
MessageEventInit init;
init.data = JS::PrimitiveString::create(realm.vm(), String {});
init.ports.append(inside_port);
init.source = inside_port;
init.source = NullableMessageEventSource { inside_port };
worker_global_scope->dispatch_event(MessageEvent::create(realm, EventNames::connect, init));
}));

View file

@ -26,15 +26,11 @@ WebIDL::ExceptionOr<GC::Ref<TrackEvent>> TrackEvent::construct_impl(JS::Realm& r
return create(realm, event_name, move(event_init));
}
TrackEvent::TrackTypeInternal TrackEvent::to_track_type_internal(TrackEventInit::TrackType const& track_type)
TrackEvent::TrackTypeInternal TrackEvent::to_track_type_internal(NullableTrackType const& track_type)
{
if (!track_type.has_value())
return Empty {};
return track_type->visit(
[](GC::Root<VideoTrack> const& root) -> TrackTypeInternal { return GC::Ref { *root }; },
[](GC::Root<AudioTrack> const& root) -> TrackTypeInternal { return GC::Ref { *root }; },
[](GC::Root<TextTrack> const& root) -> TrackTypeInternal { return GC::Ref { *root }; });
return track_type.visit(
[](Empty) -> TrackTypeInternal { return Empty {}; },
[](auto const& root) -> TrackTypeInternal { return GC::Ref { *root }; });
}
TrackEvent::TrackEvent(JS::Realm& realm, FlyString const& event_name, TrackEventInit event_init)
@ -57,11 +53,11 @@ void TrackEvent::visit_edges(Visitor& visitor)
[&](auto const& ref) { visitor.visit(ref); });
}
TrackEvent::TrackReturnType TrackEvent::track() const
NullableTrackType TrackEvent::track() const
{
return m_track.visit(
[](Empty) -> TrackReturnType { return Empty {}; },
[](auto const& ref) -> TrackReturnType { return GC::Root { *ref }; });
[](Empty) -> NullableTrackType { return Empty {}; },
[](auto const& ref) -> NullableTrackType { return GC::Root { *ref }; });
}
}

View file

@ -15,9 +15,10 @@
namespace Web::HTML {
using NullableTrackType = Variant<GC::Root<VideoTrack>, GC::Root<AudioTrack>, GC::Root<TextTrack>, Empty>;
struct TrackEventInit : public DOM::EventInit {
using TrackType = Optional<Variant<GC::Root<VideoTrack>, GC::Root<AudioTrack>, GC::Root<TextTrack>>>;
TrackType track;
NullableTrackType track { Empty {} };
};
class TrackEvent : public DOM::Event {
@ -29,8 +30,7 @@ public:
static WebIDL::ExceptionOr<GC::Ref<TrackEvent>> construct_impl(JS::Realm&, FlyString const& event_name, TrackEventInit);
// https://html.spec.whatwg.org/multipage/media.html#dom-trackevent-track
using TrackReturnType = Variant<Empty, GC::Root<VideoTrack>, GC::Root<AudioTrack>, GC::Root<TextTrack>>;
TrackReturnType track() const;
NullableTrackType track() const;
private:
TrackEvent(JS::Realm&, FlyString const& event_name, TrackEventInit event_init);
@ -39,7 +39,7 @@ private:
virtual void visit_edges(Visitor&) override;
using TrackTypeInternal = Variant<Empty, GC::Ref<VideoTrack>, GC::Ref<AudioTrack>, GC::Ref<TextTrack>>;
static TrackTypeInternal to_track_type_internal(TrackEventInit::TrackType const&);
static TrackTypeInternal to_track_type_internal(NullableTrackType const&);
TrackTypeInternal m_track;
};

View file

@ -118,7 +118,10 @@ WebIDL::ExceptionOr<GC::Ref<IDBObjectStore>> IDBDatabase::create_object_store(St
return WebIDL::TransactionInactiveError::create(realm, "Transaction is not active while creating object store"_utf16);
// 4. Let keyPath be optionss keyPath member if it is not undefined or null, or null otherwise.
auto key_path = options.key_path;
auto const& nullable_key_path = options.key_path;
Optional<KeyPath> key_path;
if (!nullable_key_path.has<Empty>())
key_path = nullable_key_path.downcast<String, Vector<String>>();
// 5. If keyPath is not null and is not a valid key path, throw a "SyntaxError" DOMException.
if (key_path.has_value() && !is_valid_key_path(key_path.value()))

View file

@ -20,10 +20,11 @@
namespace Web::IndexedDB {
using KeyPath = Variant<String, Vector<String>>;
using NullableKeyPath = Variant<String, Vector<String>, Empty>;
// https://w3c.github.io/IndexedDB/#dictdef-idbobjectstoreparameters
struct IDBObjectStoreParameters {
Optional<KeyPath> key_path;
NullableKeyPath key_path { Empty {} };
bool auto_increment { false };
};

View file

@ -84,7 +84,7 @@ WebIDL::ExceptionOr<GC::Ref<IntersectionObserver>> IntersectionObserver::constru
return realm.create<IntersectionObserver>(realm, callback, options.root, move(root_margin.value()), move(scroll_margin.value()), move(thresholds), move(delay), move(options.track_visibility));
}
IntersectionObserver::IntersectionObserver(JS::Realm& realm, GC::Ptr<WebIDL::CallbackType> callback, Optional<Variant<GC::Root<DOM::Element>, GC::Root<DOM::Document>>> const& root, Vector<CSS::LengthPercentage> root_margin, Vector<CSS::LengthPercentage> scroll_margin, Vector<double>&& thresholds, double delay, bool track_visibility)
IntersectionObserver::IntersectionObserver(JS::Realm& realm, GC::Ptr<WebIDL::CallbackType> callback, NullableIntersectionObserverRoot const& root, Vector<CSS::LengthPercentage> root_margin, Vector<CSS::LengthPercentage> scroll_margin, Vector<double>&& thresholds, double delay, bool track_visibility)
: PlatformObject(realm)
, m_callback(callback)
, m_root_margin(root_margin)
@ -93,7 +93,7 @@ IntersectionObserver::IntersectionObserver(JS::Realm& realm, GC::Ptr<WebIDL::Cal
, m_delay(delay)
, m_track_visibility(track_visibility)
{
m_root = root.has_value() ? root->visit([](auto& value) -> GC::Ptr<DOM::Node> { return *value; }) : nullptr;
m_root = root.has<Empty>() ? nullptr : root.visit([](GC::Root<DOM::Element> const& value) -> GC::Ptr<DOM::Node> { return *value; }, [](GC::Root<DOM::Document> const& value) -> GC::Ptr<DOM::Node> { return *value; }, [](Empty) -> GC::Ptr<DOM::Node> { return nullptr; });
intersection_root().visit([this](auto& node) {
m_document = node->document();
});
@ -191,7 +191,7 @@ Vector<GC::Root<IntersectionObserverEntry>> IntersectionObserver::take_records()
return queue;
}
Variant<GC::Root<DOM::Element>, GC::Root<DOM::Document>, Empty> IntersectionObserver::root() const
NullableIntersectionObserverRoot IntersectionObserver::root() const
{
if (!m_root)
return Empty {};

View file

@ -14,8 +14,10 @@
namespace Web::IntersectionObserver {
using NullableIntersectionObserverRoot = Variant<GC::Root<DOM::Element>, GC::Root<DOM::Document>, Empty>;
struct IntersectionObserverInit {
Optional<Variant<GC::Root<DOM::Element>, GC::Root<DOM::Document>>> root;
NullableIntersectionObserverRoot root { Empty {} };
String root_margin { "0px"_string };
String scroll_margin { "0px"_string };
Variant<double, Vector<double>> threshold { 0 };
@ -42,7 +44,7 @@ public:
Vector<GC::Ref<DOM::Element>> const& observation_targets() const { return m_observation_targets; }
Variant<GC::Root<DOM::Element>, GC::Root<DOM::Document>, Empty> root() const;
NullableIntersectionObserverRoot root() const;
String root_margin() const;
String scroll_margin() const;
Vector<CSS::LengthPercentage> const& scroll_margin_values() const { return m_scroll_margin; }
@ -60,7 +62,7 @@ public:
WebIDL::CallbackType& callback() { return *m_callback; }
private:
explicit IntersectionObserver(JS::Realm&, GC::Ptr<WebIDL::CallbackType> callback, Optional<Variant<GC::Root<DOM::Element>, GC::Root<DOM::Document>>> const& root, Vector<CSS::LengthPercentage> root_margin, Vector<CSS::LengthPercentage> scroll_margin, Vector<double>&& thresholds, double debug, bool track_visibility);
explicit IntersectionObserver(JS::Realm&, GC::Ptr<WebIDL::CallbackType> callback, NullableIntersectionObserverRoot const& root, Vector<CSS::LengthPercentage> root_margin, Vector<CSS::LengthPercentage> scroll_margin, Vector<double>&& thresholds, double debug, bool track_visibility);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(JS::Cell::Visitor&) override;

View file

@ -13,6 +13,7 @@
namespace Web::TrustedTypes {
using TrustedScriptOrString = Variant<GC::Root<TrustedScript>, Utf16String>;
using NullableTrustedScriptOrString = Variant<GC::Root<TrustedScript>, Utf16String, Empty>;
class TrustedScript final : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(TrustedScript, Bindings::PlatformObject);

View file

@ -535,7 +535,7 @@ WebIDL::ExceptionOr<void> XMLHttpRequest::open(String const& method, String cons
}
// https://xhr.spec.whatwg.org/#dom-xmlhttprequest-send
WebIDL::ExceptionOr<void> XMLHttpRequest::send(Optional<DocumentOrXMLHttpRequestBodyInit> body)
WebIDL::ExceptionOr<void> XMLHttpRequest::send(NullableDocumentOrXMLHttpRequestBodyInit body)
{
auto& vm = this->vm();
auto& realm = *vm.current_realm();
@ -550,23 +550,23 @@ WebIDL::ExceptionOr<void> XMLHttpRequest::send(Optional<DocumentOrXMLHttpRequest
// 3. If thiss request method is `GET` or `HEAD`, then set body to null.
if (m_request_method.is_one_of("GET"sv, "HEAD"sv))
body = {};
body = Empty {};
// 4. If body is not null, then:
if (body.has_value()) {
if (!body.has<Empty>()) {
// 1. Let extractedContentType be null.
Optional<ByteString> extracted_content_type;
// 2. If body is a Document, then set thiss request body to body, serialized, converted, and UTF-8 encoded.
if (body->has<GC::Root<DOM::Document>>()) {
auto string_serialized_document = TRY(body->get<GC::Root<DOM::Document>>().cell()->serialize_fragment(HTML::RequireWellFormed::No));
if (body.has<GC::Root<DOM::Document>>()) {
auto string_serialized_document = TRY(body.get<GC::Root<DOM::Document>>().cell()->serialize_fragment(HTML::RequireWellFormed::No));
auto string_serialized_document_utf8 = string_serialized_document.to_utf8();
m_request_body = Fetch::Infrastructure::byte_sequence_as_body(realm, string_serialized_document_utf8.bytes());
}
// 3. Otherwise:
else {
// 1. Let bodyWithType be the result of safely extracting body.
auto body_with_type = Fetch::safely_extract_body(realm, body->downcast<Fetch::BodyInitOrReadableBytes>());
auto body_with_type = Fetch::safely_extract_body(realm, body.downcast<Fetch::BodyInitOrReadableBytes>());
// 2. Set thiss request body to bodyWithTypes body.
m_request_body = body_with_type.body;
@ -581,7 +581,7 @@ WebIDL::ExceptionOr<void> XMLHttpRequest::send(Optional<DocumentOrXMLHttpRequest
// 5. If originalAuthorContentType is non-null, then:
if (original_author_content_type.has_value()) {
// 1. If body is a Document or a USVString, then:
if (body->has<GC::Root<DOM::Document>>() || body->has<String>()) {
if (body.has<GC::Root<DOM::Document>>() || body.has<String>()) {
// 1. Let contentTypeRecord be the result of parsing originalAuthorContentType.
auto content_type_record = MimeSniff::MimeType::parse(original_author_content_type.value());
@ -604,8 +604,8 @@ WebIDL::ExceptionOr<void> XMLHttpRequest::send(Optional<DocumentOrXMLHttpRequest
}
// 6. Otherwise:
else {
if (body->has<GC::Root<DOM::Document>>()) {
auto document = body->get<GC::Root<DOM::Document>>();
if (body.has<GC::Root<DOM::Document>>()) {
auto document = body.get<GC::Root<DOM::Document>>();
// NOTE: A document can only be an HTML document or XML document.
// 1. If body is an HTML document, then set (`Content-Type`, `text/html;charset=UTF-8`) in thiss author request headers.

View file

@ -28,6 +28,7 @@ namespace Web::XHR {
// https://fetch.spec.whatwg.org/#typedefdef-xmlhttprequestbodyinit
using DocumentOrXMLHttpRequestBodyInit = Variant<GC::Root<Web::DOM::Document>, GC::Root<Web::FileAPI::Blob>, GC::Root<WebIDL::BufferSource>, GC::Root<XHR::FormData>, GC::Root<Web::DOMURL::URLSearchParams>, AK::String>;
using NullableDocumentOrXMLHttpRequestBodyInit = Variant<GC::Root<Web::DOM::Document>, GC::Root<Web::FileAPI::Blob>, GC::Root<WebIDL::BufferSource>, GC::Root<XHR::FormData>, GC::Root<Web::DOMURL::URLSearchParams>, AK::String, Empty>;
class XMLHttpRequest final : public XMLHttpRequestEventTarget {
WEB_PLATFORM_OBJECT(XMLHttpRequest, XMLHttpRequestEventTarget);
@ -59,7 +60,7 @@ public:
WebIDL::ExceptionOr<void> open(String const& method, String const& url);
WebIDL::ExceptionOr<void> open(String const& method, String const& url, bool async, Optional<String> const& username = Optional<String> {}, Optional<String> const& password = Optional<String> {});
WebIDL::ExceptionOr<void> send(Optional<DocumentOrXMLHttpRequestBodyInit> body);
WebIDL::ExceptionOr<void> send(NullableDocumentOrXMLHttpRequestBodyInit body);
WebIDL::ExceptionOr<void> set_request_header(String const& name, String const& value);
WebIDL::ExceptionOr<void> set_response_type(Bindings::XMLHttpRequestResponseType);

View file

@ -268,7 +268,7 @@ static ByteString union_type_to_variant(UnionType const& union_type, Interface c
builder.append(cpp_type.name);
}
if (union_type.includes_undefined())
if (union_type.includes_undefined() || union_type.includes_nullable_type())
builder.append(", Empty"sv);
builder.append('>');
@ -1369,9 +1369,12 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
)~~~");
}
// FIXME: 2. If the union type includes a nullable type and V is null or undefined, then return the IDL value null.
// 2. If the union type includes a nullable type and V is null or undefined, then return the IDL value null.
if (union_type.includes_nullable_type()) {
// Implement me
union_generator.append(R"~~~(
if (@js_name@@js_suffix@.is_nullish())
return Empty {};
)~~~");
} else if (dictionary_type) {
// 4. If V is null or undefined, then
// 4.1 If types includes a dictionary type, then return the result of converting V to that dictionary type.
@ -1794,7 +1797,7 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
)~~~");
} else {
if (!optional_default_value.has_value()) {
union_generator.set("nullish_or_undefined", union_type.is_nullable() ? "nullish" : "undefined");
union_generator.set("nullish_or_undefined", "undefined");
union_generator.append(R"~~~(
Optional<@union_type@> @cpp_name@;
if (!@js_name@@js_suffix@.is_@nullish_or_undefined@())
@ -1802,11 +1805,17 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
)~~~");
} else {
if (optional_default_value == "null"sv) {
union_generator.append(R"~~~(
if (union_type.includes_nullable_type()) {
union_generator.append(R"~~~(
@union_type@ @cpp_name@ = @js_name@@js_suffix@.is_undefined() ? @union_type@ { Empty {} } : TRY(@js_name@@js_suffix@_to_variant(@js_name@@js_suffix@));
)~~~");
} else {
union_generator.append(R"~~~(
Optional<@union_type@> @cpp_name@;
if (!@js_name@@js_suffix@.is_nullish())
@cpp_name@ = TRY(@js_name@@js_suffix@_to_variant(@js_name@@js_suffix@));
)~~~");
}
} else if (optional_default_value == "\"\"") {
union_generator.append(R"~~~(
@union_type@ @cpp_name@ = @js_name@@js_suffix@.is_undefined() ? TRY(@js_name@@js_suffix@_to_variant(JS::Value(JS::PrimitiveString::create(vm, String {})))) : TRY(@js_name@@js_suffix@_to_variant(@js_name@@js_suffix@));
@ -2182,8 +2191,8 @@ static void generate_wrap_statement(SourceGenerator& generator, ByteString const
generate_wrap_statement(union_generator, ByteString::formatted("visited_union_value{}", recursion_depth), current_union_type, interface, "return"sv, recursion_depth + 1);
// End of current visit lambda.
// The last lambda cannot have a trailing comma on the closing brace, unless the type is nullable, where an extra lambda will be generated for the Empty case.
if (current_union_type_index != union_types.size() - 1 || type.is_nullable()) {
// The last lambda cannot have a trailing comma on the closing brace, unless the type includes a nullable type, where an extra lambda will be generated for the Empty case.
if (current_union_type_index != union_types.size() - 1 || union_type.includes_nullable_type()) {
union_generator.append(R"~~~(
},
)~~~");
@ -2194,7 +2203,7 @@ static void generate_wrap_statement(SourceGenerator& generator, ByteString const
}
}
if (type.is_nullable()) {
if (union_type.includes_nullable_type()) {
union_generator.append(R"~~~(
[](Empty) -> JS::Value {
return JS::js_null();

View file

@ -276,7 +276,7 @@ void WorkerHost::run(GC::Ref<Web::Page> page, Web::HTML::TransferDataEncoder mes
Web::HTML::MessageEventInit event_init {};
event_init.data = GC::Ref { vm.empty_string() };
event_init.ports = { inside_port };
event_init.source = inside_port;
event_init.source = Web::HTML::NullableMessageEventSource { inside_port };
auto message_event = Web::HTML::MessageEvent::create(realm, Web::HTML::EventNames::connect, event_init);
worker_global_scope->dispatch_event(message_event);

View file

@ -2,10 +2,10 @@ Harness status: OK
Found 5 tests
2 Pass
3 Fail
3 Pass
2 Fail
Pass currentTime can be used to seek a CSS transition
Fail Skipping forwards through transition
Pass Skipping backwards through transition
Fail Setting currentTime to null on a CSS transition throws
Pass Setting currentTime to null on a CSS transition throws
Fail Transition reversing behavior respects currentTime and uses the transition's current position.

View file

@ -2,16 +2,16 @@ Harness status: OK
Found 13 tests
9 Pass
4 Fail
12 Pass
1 Fail
Pass Validate different value types that can be used to set start time
Fail Setting the start time of an animation without an active timeline
Fail Setting an unresolved start time an animation without an active timeline does not clear the current time
Pass Setting an unresolved start time an animation without an active timeline does not clear the current time
Pass Setting the start time clears the hold time
Fail Setting an unresolved start time sets the hold time
Pass Setting an unresolved start time sets the hold time
Pass Setting the start time resolves a pending ready promise
Pass Setting the start time resolves a pending pause task
Fail Setting an unresolved start time on a play-pending animation makes it paused
Pass Setting an unresolved start time on a play-pending animation makes it paused
Pass Setting the start time updates the finished state
Pass Setting the start time of a play-pending animation applies a pending playback rate
Pass Setting the start time of a playing animation applies a pending playback rate