Compare commits

..

7 commits

Author SHA1 Message Date
Gingeh
82a286612f
Merge 7fe49734d7 into d6bcd3fb0b 2024-11-21 09:12:03 +01:00
Shannon Booth
d6bcd3fb0b LibWeb: Make CallbackType take a realm instead of settings object
Some checks are pending
CI / Lagom (false, FUZZ, ubuntu-24.04, Linux, Clang) (push) Waiting to run
CI / Lagom (false, NO_FUZZ, macos-15, macOS, Clang) (push) Waiting to run
CI / Lagom (false, NO_FUZZ, ubuntu-24.04, Linux, GNU) (push) Waiting to run
CI / Lagom (true, NO_FUZZ, ubuntu-24.04, Linux, Clang) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (macos-14, macOS, macOS-universal2) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (ubuntu-24.04, Linux, Linux-x86_64) (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Push notes / build (push) Waiting to run
In line with the ShadowRealm proposal changes in the WebIDL spec:
webidl#1437 and supporting changes in HTML spec.

This is required for ShadowRealms as they have no relevant settings
object on the shadow realm, so fixes a crash in the QueueingStrategy
test in this commit.
2024-11-20 18:01:21 -07:00
Shannon Booth
d527c5df5d LibWeb: Allow using queuing strategies on globals other than Window
These interfaces are exposed on *, meaning it should work for workers
and our newly added shadow realm global object by being stored on the
universal global scope mixin.
2024-11-20 18:01:21 -07:00
rmg-x
13f349aea2 LibWeb/Fetch: Implement blob range section of scheme fetch specification 2024-11-21 00:26:58 +00:00
rmg-x
84f673515b LibWeb/XHR: Use normalized header value when validating contents
This was not matching specification and caused valid requests to be
rejected.
2024-11-21 00:26:58 +00:00
rmg-x
4e48298414 LibWeb/Fetch: Implement build_content_range(start, end, full_length) 2024-11-21 00:26:58 +00:00
rmg-x
bf5cf720b5 LibWeb/Fetch: Bring parse_single_range_header_value() up to spec
The previous implementation wasn't using the latest specification steps.
2024-11-21 00:26:58 +00:00
30 changed files with 562 additions and 131 deletions

View file

@ -673,7 +673,7 @@ void queue_mutation_observer_microtask(DOM::Document const& document)
// 4. If records is not empty, then invoke mos callback with « records, mo », and mo. If this throws an exception, catch it, and report the exception.
if (!records.is_empty()) {
auto& callback = mutation_observer->callback();
auto& realm = callback.callback_context->realm();
auto& realm = callback.callback_context;
auto wrapped_records = MUST(JS::Array::create(realm, 0));
for (size_t i = 0; i < records.size(); ++i) {

View file

@ -4203,7 +4203,7 @@ void Document::start_intersection_observing_a_lazy_loading_element(Element& elem
// Spec Note: This allows for fetching the image during scrolling, when it does not yet — but is about to — intersect the viewport.
auto options = IntersectionObserver::IntersectionObserverInit {};
auto wrapped_callback = realm.heap().allocate<WebIDL::CallbackType>(callback, Bindings::principal_host_defined_environment_settings_object(realm));
auto wrapped_callback = realm.heap().allocate<WebIDL::CallbackType>(callback, realm);
m_lazy_load_intersection_observer = IntersectionObserver::IntersectionObserver::construct_impl(realm, wrapped_callback, options).release_value_but_fixme_should_propagate_errors();
}

View file

@ -492,7 +492,8 @@ WebIDL::CallbackType* EventTarget::get_current_value_of_event_handler(FlyString
function->set_script_or_module({});
// 12. Set eventHandler's value to the result of creating a Web IDL EventHandler callback function object whose object reference is function and whose callback context is settings object.
event_handler->value = GC::Ptr(realm.heap().allocate<WebIDL::CallbackType>(*function, settings_object));
// FIXME: Update this comment once the ShadowRealm proposal is merged to pass realm.
event_handler->value = GC::Ptr(realm.heap().allocate<WebIDL::CallbackType>(*function, realm));
}
// 4. Return eventHandler's value.
@ -584,7 +585,7 @@ void EventTarget::activate_event_handler(FlyString const& name, HTML::EventHandl
0, "", &realm);
// NOTE: As per the spec, the callback context is arbitrary.
auto callback = realm.heap().allocate<WebIDL::CallbackType>(*callback_function, Bindings::principal_host_defined_environment_settings_object(realm));
auto callback = realm.heap().allocate<WebIDL::CallbackType>(*callback_function, realm);
// 5. Let listener be a new event listener whose type is the event handler event type corresponding to eventHandler and callback is callback.
auto listener = realm.heap().allocate<DOMEventListener>();

View file

@ -860,33 +860,79 @@ WebIDL::ExceptionOr<GC::Ref<PendingResponse>> scheme_fetch(JS::Realm& realm, Inf
auto content_type_header = Infrastructure::Header::from_string_pair("Content-Type"sv, type);
response->header_list()->append(move(content_type_header));
}
// FIXME: 9. Otherwise:
// 9. Otherwise:
else {
// 1. Set responses range-requested flag.
response->set_range_requested(true);
// 2. Let rangeHeader be the result of getting `Range` from requests header list.
auto const range_header = request->header_list()->get("Range"sv.bytes()).value_or(ByteBuffer {});
// 3. Let rangeValue be the result of parsing a single range header value given rangeHeader and true.
auto maybe_range_value = Infrastructure::parse_single_range_header_value(range_header, true);
// 4. If rangeValue is failure, then return a network error.
if (!maybe_range_value.has_value())
return PendingResponse::create(vm, request, Infrastructure::Response::network_error(vm, "Failed to parse single range header value"sv));
// 5. Let (rangeStart, rangeEnd) be rangeValue.
auto& [range_start, range_end] = maybe_range_value.value();
// 6. If rangeStart is null:
// 1. Set rangeStart to fullLength rangeEnd.
// 2. Set rangeEnd to rangeStart + rangeEnd 1.
if (!range_start.has_value()) {
VERIFY(range_end.has_value());
// 1. Set rangeStart to fullLength rangeEnd.
range_start = full_length - *range_end;
// 2. Set rangeEnd to rangeStart + rangeEnd 1.
range_end = *range_start + *range_end - 1;
}
// 7. Otherwise:
// 1. If rangeStart is greater than or equal to fullLength, then return a network error.
// 2. If rangeEnd is null or rangeEnd is greater than or equal to fullLength, then set rangeEnd to fullLength 1.
else {
// 1. If rangeStart is greater than or equal to fullLength, then return a network error.
if (*range_start >= full_length)
return PendingResponse::create(vm, request, Infrastructure::Response::network_error(vm, "rangeStart is greater than or equal to fullLength"sv));
// 2. If rangeEnd is null or rangeEnd is greater than or equal to fullLength, then set rangeEnd to fullLength 1.
if (!range_end.has_value() || *range_end >= full_length)
range_end = full_length - 1;
}
// 8. Let slicedBlob be the result of invoking slice blob given blob, rangeStart, rangeEnd + 1, and type.
auto sliced_blob = TRY(blob->slice(*range_start, *range_end + 1, type));
// 9. Let slicedBodyWithType be the result of safely extracting slicedBlob.
auto sliced_body_with_type = TRY(safely_extract_body(realm, sliced_blob->raw_bytes()));
// 10. Set responses body to slicedBodyWithTypes body.
response->set_body(sliced_body_with_type.body);
// 11. Let serializedSlicedLength be slicedBlobs size, serialized and isomorphic encoded.
// 12. Let contentRange be `bytes `.
// 13. Append rangeStart, serialized and isomorphic encoded, to contentRange.
// 14. Append 0x2D (-) to contentRange.
// 15. Append rangeEnd, serialized and isomorphic encoded to contentRange.
// 16. Append 0x2F (/) to contentRange.
// 17. Append serializedFullLength to contentRange.
// 18. Set responses status to 206.
// 19. Set responses status message to `Partial Content`.
// 20. Set responses header list to « (`Content-Length`, serializedSlicedLength), (`Content-Type`, type), (`Content-Range`, contentRange) ».
return PendingResponse::create(vm, request, Infrastructure::Response::network_error(vm, "Request has a 'blob:' URL with a Content-Range header, which is currently unsupported"sv));
auto serialized_sliced_length = String::number(sliced_blob->size());
// 12. Let contentRange be the result of invoking build a content range given rangeStart, rangeEnd, and fullLength.
auto content_range = Infrastructure::build_content_range(*range_start, *range_end, full_length);
// 13. Set responses status to 206.
response->set_status(206);
// 14. Set responses status message to `Partial Content`.
response->set_status_message(MUST(ByteBuffer::copy("Partial Content"sv.bytes())));
// 15. Set responses header list to «
// (`Content-Length`, serializedSlicedLength),
auto content_length_header = Infrastructure::Header::from_string_pair("Content-Length"sv, serialized_sliced_length);
response->header_list()->append(move(content_length_header));
// (`Content-Type`, type),
auto content_type_header = Infrastructure::Header::from_string_pair("Content-Type"sv, type);
response->header_list()->append(move(content_type_header));
// (`Content-Range`, contentRange) ».
auto content_range_header = Infrastructure::Header::from_string_pair("Content-Range"sv, content_range);
response->header_list()->append(move(content_range_header));
}
// 10. Return response.

View file

@ -32,6 +32,11 @@ inline constexpr Array HTTP_TAB_OR_SPACE_BYTES = {
0x09, 0x20
};
constexpr bool is_http_tab_or_space(u32 const code_point)
{
return code_point == 0x09 || code_point == 0x20;
}
enum class HttpQuotedStringExtractValue {
No,
Yes,

View file

@ -544,8 +544,8 @@ bool is_cors_safelisted_request_header(Header const& header)
}
// `range`
else if (name.equals_ignoring_ascii_case("range"sv)) {
// 1. Let rangeValue be the result of parsing a single range header value given value.
auto range_value = parse_single_range_header_value(value);
// 1. Let rangeValue be the result of parsing a single range header value given value and false.
auto range_value = parse_single_range_header_value(value, false);
// 2. If rangeValue is failure, then return false.
if (!range_value.has_value())
@ -832,51 +832,84 @@ Variant<Vector<ByteBuffer>, ExtractHeaderParseFailure, Empty> extract_header_lis
return values;
}
// https://fetch.spec.whatwg.org/#build-a-content-range
ByteString build_content_range(u64 const& range_start, u64 const& range_end, u64 const& full_length)
{
// 1. Let contentRange be `bytes `.
// 2. Append rangeStart, serialized and isomorphic encoded, to contentRange.
// 3. Append 0x2D (-) to contentRange.
// 4. Append rangeEnd, serialized and isomorphic encoded to contentRange.
// 5. Append 0x2F (/) to contentRange.
// 6. Append fullLength, serialized and isomorphic encoded to contentRange.
// 7. Return contentRange.
return ByteString::formatted("bytes {}-{}/{}", String::number(range_start), String::number(range_end), String::number(full_length));
}
// https://fetch.spec.whatwg.org/#simple-range-header-value
Optional<RangeHeaderValue> parse_single_range_header_value(ReadonlyBytes value)
Optional<RangeHeaderValue> parse_single_range_header_value(ReadonlyBytes const value, bool const allow_whitespace)
{
// 1. Let data be the isomorphic decoding of value.
auto data = Infra::isomorphic_decode(value);
auto const data = Infra::isomorphic_decode(value);
// 2. If data does not start with "bytes=", then return failure.
if (!data.starts_with_bytes("bytes="sv))
// 2. If data does not start with "bytes", then return failure.
if (!data.starts_with_bytes("bytes"sv))
return {};
// 3. Let position be a position variable for data, initially pointing at the 6th code point of data.
// 3. Let position be a position variable for data, initially pointing at the 5th code point of data.
auto lexer = GenericLexer { data };
lexer.ignore(6);
lexer.ignore(5);
// 4. Let rangeStart be the result of collecting a sequence of code points that are ASCII digits, from data given position.
// 4. If allowWhitespace is true, collect a sequence of code points that are HTTP tab or space, from data given position.
if (allow_whitespace)
lexer.consume_while(is_http_tab_or_space);
// 5. If the code point at position within data is not U+003D (=), then return failure.
// 6. Advance position by 1.
if (!lexer.consume_specific('='))
return {};
// 7. If allowWhitespace is true, collect a sequence of code points that are HTTP tab or space, from data given position.
if (allow_whitespace)
lexer.consume_while(is_http_tab_or_space);
// 8. Let rangeStart be the result of collecting a sequence of code points that are ASCII digits, from data given position.
auto range_start = lexer.consume_while(is_ascii_digit);
// 5. Let rangeStartValue be rangeStart, interpreted as decimal number, if rangeStart is not the empty string; otherwise null.
// 9. Let rangeStartValue be rangeStart, interpreted as decimal number, if rangeStart is not the empty string; otherwise null.
auto range_start_value = range_start.to_number<u64>();
// 6. If the code point at position within data is not U+002D (-), then return failure.
// 7. Advance position by 1.
// 10. If allowWhitespace is true, collect a sequence of code points that are HTTP tab or space, from data given position.
if (allow_whitespace)
lexer.consume_while(is_http_tab_or_space);
// 11. If the code point at position within data is not U+002D (-), then return failure.
// 12. Advance position by 1.
if (!lexer.consume_specific('-'))
return {};
// 8. Let rangeEnd be the result of collecting a sequence of code points that are ASCII digits, from data given position.
// 13. If allowWhitespace is true, collect a sequence of code points that are HTTP tab or space, from data given position.
if (allow_whitespace)
lexer.consume_while(is_http_tab_or_space);
// 14. Let rangeEnd be the result of collecting a sequence of code points that are ASCII digits, from data given position.
auto range_end = lexer.consume_while(is_ascii_digit);
// 9. Let rangeEndValue be rangeEnd, interpreted as decimal number, if rangeEnd is not the empty string; otherwise null.
// 15. Let rangeEndValue be rangeEnd, interpreted as decimal number, if rangeEnd is not the empty string; otherwise null.
auto range_end_value = range_end.to_number<u64>();
// 10. If position is not past the end of data, then return failure.
// 16. If position is not past the end of data, then return failure.
if (!lexer.is_eof())
return {};
// 11. If rangeEndValue and rangeStartValue are null, then return failure.
// 17. If rangeEndValue and rangeStartValue are null, then return failure.
if (!range_end_value.has_value() && !range_start_value.has_value())
return {};
// 12. If rangeStartValue and rangeEndValue are numbers, and rangeStartValue is greater than rangeEndValue, then return failure.
// 18. If rangeStartValue and rangeEndValue are numbers, and rangeStartValue is greater than rangeEndValue, then return failure.
if (range_start_value.has_value() && range_end_value.has_value() && *range_start_value > *range_end_value)
return {};
// 13. Return (rangeStartValue, rangeEndValue).
// NOTE: The range end or start can be omitted, e.g., `bytes=0-` or `bytes=-500` are valid ranges.
// 19. Return (rangeStartValue, rangeEndValue).
return RangeHeaderValue { move(range_start_value), move(range_end_value) };
}

View file

@ -93,7 +93,8 @@ struct ExtractHeaderParseFailure {
[[nodiscard]] bool is_request_body_header_name(ReadonlyBytes);
[[nodiscard]] Optional<Vector<ByteBuffer>> extract_header_values(Header const&);
[[nodiscard]] Variant<Vector<ByteBuffer>, ExtractHeaderParseFailure, Empty> extract_header_list_values(ReadonlyBytes, HeaderList const&);
[[nodiscard]] Optional<RangeHeaderValue> parse_single_range_header_value(ReadonlyBytes);
[[nodiscard]] ByteString build_content_range(u64 const& range_start, u64 const& range_end, u64 const& full_length);
[[nodiscard]] Optional<RangeHeaderValue> parse_single_range_header_value(ReadonlyBytes, bool);
[[nodiscard]] ByteBuffer default_user_agent_value();
}

View file

@ -44,6 +44,7 @@ void CustomElementRegistry::visit_edges(Visitor& visitor)
}
// https://webidl.spec.whatwg.org/#es-callback-function
// https://github.com/whatwg/html/pull/9893
static JS::ThrowCompletionOr<GC::Ref<WebIDL::CallbackType>> convert_value_to_callback_function(JS::VM& vm, JS::Value value)
{
// FIXME: De-duplicate this from the IDL generator.
@ -51,8 +52,8 @@ static JS::ThrowCompletionOr<GC::Ref<WebIDL::CallbackType>> convert_value_to_cal
if (!value.is_function())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAFunction, value.to_string_without_side_effects());
// 2. Return the IDL callback function type value that represents a reference to the same object that V represents, with the incumbent settings object as the callback context.
return vm.heap().allocate<WebIDL::CallbackType>(value.as_object(), HTML::incumbent_settings_object());
// 2. Return the IDL callback function type value that represents a reference to the same object that V represents, with the incumbent realm as the callback context.
return vm.heap().allocate<WebIDL::CallbackType>(value.as_object(), HTML::incumbent_realm());
}
// https://webidl.spec.whatwg.org/#es-sequence

View file

@ -215,7 +215,7 @@ WebIDL::ExceptionOr<void> HTMLDialogElement::show_modal()
return JS::js_undefined();
},
0, "", &realm());
auto cancel_callback = realm().heap().allocate<WebIDL::CallbackType>(*cancel_callback_function, Bindings::principal_host_defined_environment_settings_object(realm()));
auto cancel_callback = realm().heap().allocate<WebIDL::CallbackType>(*cancel_callback_function, realm());
m_close_watcher->add_event_listener_without_options(HTML::EventNames::cancel, DOM::IDLEventListener::create(realm(), cancel_callback));
// - closeAction being to close the dialog given this and null.
auto close_callback_function = JS::NativeFunction::create(
@ -225,7 +225,7 @@ WebIDL::ExceptionOr<void> HTMLDialogElement::show_modal()
return JS::js_undefined();
},
0, "", &realm());
auto close_callback = realm().heap().allocate<WebIDL::CallbackType>(*close_callback_function, Bindings::principal_host_defined_environment_settings_object(realm()));
auto close_callback = realm().heap().allocate<WebIDL::CallbackType>(*close_callback_function, realm());
m_close_watcher->add_event_listener_without_options(HTML::EventNames::close, DOM::IDLEventListener::create(realm(), close_callback));
// FIXME: 16. Set this's previously focused element to the focused element.

View file

@ -899,7 +899,7 @@ void HTMLInputElement::create_text_input_shadow_tree()
return JS::js_undefined();
},
0, "", &realm());
auto mouseup_callback = realm().heap().allocate<WebIDL::CallbackType>(*mouseup_callback_function, Bindings::principal_host_defined_environment_settings_object(realm()));
auto mouseup_callback = realm().heap().allocate<WebIDL::CallbackType>(*mouseup_callback_function, realm());
DOM::AddEventListenerOptions mouseup_listener_options;
mouseup_listener_options.once = true;
@ -912,7 +912,7 @@ void HTMLInputElement::create_text_input_shadow_tree()
return JS::js_undefined();
},
0, "", &realm());
auto step_up_callback = realm().heap().allocate<WebIDL::CallbackType>(*up_callback_function, Bindings::principal_host_defined_environment_settings_object(realm()));
auto step_up_callback = realm().heap().allocate<WebIDL::CallbackType>(*up_callback_function, realm());
up_button->add_event_listener_without_options(UIEvents::EventNames::mousedown, DOM::IDLEventListener::create(realm(), step_up_callback));
up_button->add_event_listener_without_options(UIEvents::EventNames::mouseup, DOM::IDLEventListener::create(realm(), mouseup_callback));
@ -934,7 +934,7 @@ void HTMLInputElement::create_text_input_shadow_tree()
return JS::js_undefined();
},
0, "", &realm());
auto step_down_callback = realm().heap().allocate<WebIDL::CallbackType>(*down_callback_function, Bindings::principal_host_defined_environment_settings_object(realm()));
auto step_down_callback = realm().heap().allocate<WebIDL::CallbackType>(*down_callback_function, realm());
down_button->add_event_listener_without_options(UIEvents::EventNames::mousedown, DOM::IDLEventListener::create(realm(), step_down_callback));
down_button->add_event_listener_without_options(UIEvents::EventNames::mouseup, DOM::IDLEventListener::create(realm(), mouseup_callback));
}
@ -993,7 +993,7 @@ void HTMLInputElement::create_file_input_shadow_tree()
};
auto on_button_click_function = JS::NativeFunction::create(realm, move(on_button_click), 0, "", &realm);
auto on_button_click_callback = realm.heap().allocate<WebIDL::CallbackType>(on_button_click_function, Bindings::principal_host_defined_environment_settings_object(realm));
auto on_button_click_callback = realm.heap().allocate<WebIDL::CallbackType>(on_button_click_function, realm);
m_file_button->add_event_listener_without_options(UIEvents::EventNames::click, DOM::IDLEventListener::create(realm, on_button_click_callback));
update_file_input_shadow_tree();
@ -1065,7 +1065,7 @@ void HTMLInputElement::create_range_input_shadow_tree()
return JS::js_undefined();
},
0, "", &realm());
auto keydown_callback = realm().heap().allocate<WebIDL::CallbackType>(*keydown_callback_function, Bindings::principal_host_defined_environment_settings_object(realm()));
auto keydown_callback = realm().heap().allocate<WebIDL::CallbackType>(*keydown_callback_function, realm());
add_event_listener_without_options(UIEvents::EventNames::keydown, DOM::IDLEventListener::create(realm(), keydown_callback));
auto wheel_callback_function = JS::NativeFunction::create(
@ -1080,7 +1080,7 @@ void HTMLInputElement::create_range_input_shadow_tree()
return JS::js_undefined();
},
0, "", &realm());
auto wheel_callback = realm().heap().allocate<WebIDL::CallbackType>(*wheel_callback_function, Bindings::principal_host_defined_environment_settings_object(realm()));
auto wheel_callback = realm().heap().allocate<WebIDL::CallbackType>(*wheel_callback_function, realm());
add_event_listener_without_options(UIEvents::EventNames::wheel, DOM::IDLEventListener::create(realm(), wheel_callback));
auto update_slider_by_mouse = [this](JS::VM& vm) {
@ -1103,7 +1103,7 @@ void HTMLInputElement::create_range_input_shadow_tree()
return JS::js_undefined();
},
0, "", &realm());
auto mousemove_callback = realm().heap().allocate<WebIDL::CallbackType>(*mousemove_callback_function, Bindings::principal_host_defined_environment_settings_object(realm()));
auto mousemove_callback = realm().heap().allocate<WebIDL::CallbackType>(*mousemove_callback_function, realm());
auto mousemove_listener = DOM::IDLEventListener::create(realm(), mousemove_callback);
auto& window = static_cast<HTML::Window&>(relevant_global_object(*this));
window.add_event_listener_without_options(UIEvents::EventNames::mousemove, mousemove_listener);
@ -1115,7 +1115,7 @@ void HTMLInputElement::create_range_input_shadow_tree()
return JS::js_undefined();
},
0, "", &realm());
auto mouseup_callback = realm().heap().allocate<WebIDL::CallbackType>(*mouseup_callback_function, Bindings::principal_host_defined_environment_settings_object(realm()));
auto mouseup_callback = realm().heap().allocate<WebIDL::CallbackType>(*mouseup_callback_function, realm());
DOM::AddEventListenerOptions mouseup_listener_options;
mouseup_listener_options.once = true;
window.add_event_listener(UIEvents::EventNames::mouseup, DOM::IDLEventListener::create(realm(), mouseup_callback), mouseup_listener_options);
@ -1123,7 +1123,7 @@ void HTMLInputElement::create_range_input_shadow_tree()
return JS::js_undefined();
},
0, "", &realm());
auto mousedown_callback = realm().heap().allocate<WebIDL::CallbackType>(*mousedown_callback_function, Bindings::principal_host_defined_environment_settings_object(realm()));
auto mousedown_callback = realm().heap().allocate<WebIDL::CallbackType>(*mousedown_callback_function, realm());
add_event_listener_without_options(UIEvents::EventNames::mousedown, DOM::IDLEventListener::create(realm(), mousedown_callback));
}

View file

@ -41,6 +41,7 @@ void ShadowRealmGlobalScope::initialize_web_interfaces()
void ShadowRealmGlobalScope::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
UniversalGlobalScopeMixin::visit_edges(visitor);
}
}

View file

@ -12,6 +12,7 @@
#include <AK/Utf8View.h>
#include <AK/Vector.h>
#include <LibGC/Function.h>
#include <LibJS/Runtime/NativeFunction.h>
#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
#include <LibWeb/HTML/StructuredSerialize.h>
#include <LibWeb/HTML/StructuredSerializeOptions.h>
@ -27,6 +28,12 @@ namespace Web::HTML {
UniversalGlobalScopeMixin::~UniversalGlobalScopeMixin() = default;
void UniversalGlobalScopeMixin::visit_edges(GC::Cell::Visitor& visitor)
{
visitor.visit(m_count_queuing_strategy_size_function);
visitor.visit(m_byte_length_queuing_strategy_size_function);
}
// https://html.spec.whatwg.org/multipage/webappapis.html#dom-btoa
WebIDL::ExceptionOr<String> UniversalGlobalScopeMixin::btoa(String const& data) const
{
@ -101,4 +108,52 @@ WebIDL::ExceptionOr<JS::Value> UniversalGlobalScopeMixin::structured_clone(JS::V
return deserialized;
}
// https://streams.spec.whatwg.org/#count-queuing-strategy-size-function
GC::Ref<WebIDL::CallbackType> UniversalGlobalScopeMixin::count_queuing_strategy_size_function()
{
auto& realm = HTML::relevant_realm(this_impl());
if (!m_count_queuing_strategy_size_function) {
// 1. Let steps be the following steps:
auto steps = [](auto const&) {
// 1. Return 1.
return 1.0;
};
// 2. Let F be ! CreateBuiltinFunction(steps, 0, "size", « », globalObjects relevant Realm).
auto function = JS::NativeFunction::create(realm, move(steps), 0, "size", &realm);
// 3. Set globalObjects count queuing strategy size function to a Function that represents a reference to F, with callback context equal to globalObjects relevant settings object.
// FIXME: Update spec comment to pass globalObject's relevant realm once Streams spec is updated for ShadowRealm spec
m_count_queuing_strategy_size_function = realm.create<WebIDL::CallbackType>(*function, realm);
}
return GC::Ref { *m_count_queuing_strategy_size_function };
}
// https://streams.spec.whatwg.org/#byte-length-queuing-strategy-size-function
GC::Ref<WebIDL::CallbackType> UniversalGlobalScopeMixin::byte_length_queuing_strategy_size_function()
{
auto& realm = HTML::relevant_realm(this_impl());
if (!m_byte_length_queuing_strategy_size_function) {
// 1. Let steps be the following steps, given chunk:
auto steps = [](JS::VM& vm) {
auto chunk = vm.argument(0);
// 1. Return ? GetV(chunk, "byteLength").
return chunk.get(vm, vm.names.byteLength);
};
// 2. Let F be ! CreateBuiltinFunction(steps, 1, "size", « », globalObjects relevant Realm).
auto function = JS::NativeFunction::create(realm, move(steps), 1, "size", &realm);
// 3. Set globalObjects byte length queuing strategy size function to a Function that represents a reference to F, with callback context equal to globalObjects relevant settings object.
// FIXME: Update spec comment to pass globalObject's relevant realm once Streams spec is updated for ShadowRealm spec
m_byte_length_queuing_strategy_size_function = realm.create<WebIDL::CallbackType>(*function, realm);
}
return GC::Ref { *m_byte_length_queuing_strategy_size_function };
}
}

View file

@ -28,6 +28,19 @@ public:
WebIDL::ExceptionOr<String> atob(String const& data) const;
void queue_microtask(WebIDL::CallbackType&);
WebIDL::ExceptionOr<JS::Value> structured_clone(JS::Value, StructuredSerializeOptions const&) const;
GC::Ref<WebIDL::CallbackType> count_queuing_strategy_size_function();
GC::Ref<WebIDL::CallbackType> byte_length_queuing_strategy_size_function();
protected:
void visit_edges(GC::Cell::Visitor&);
private:
// https://streams.spec.whatwg.org/#count-queuing-strategy-size-function
GC::Ptr<WebIDL::CallbackType> m_count_queuing_strategy_size_function;
// https://streams.spec.whatwg.org/#byte-length-queuing-strategy-size-function
GC::Ptr<WebIDL::CallbackType> m_byte_length_queuing_strategy_size_function;
};
}

View file

@ -116,6 +116,7 @@ void Window::visit_edges(JS::Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
WindowOrWorkerGlobalScopeMixin::visit_edges(visitor);
UniversalGlobalScopeMixin::visit_edges(visitor);
visitor.visit(m_associated_document);
visitor.visit(m_current_event);
@ -127,8 +128,6 @@ void Window::visit_edges(JS::Cell::Visitor& visitor)
visitor.visit(m_animation_frame_callback_driver);
visitor.visit(m_pdf_viewer_plugin_objects);
visitor.visit(m_pdf_viewer_mime_type_objects);
visitor.visit(m_count_queuing_strategy_size_function);
visitor.visit(m_byte_length_queuing_strategy_size_function);
visitor.visit(m_close_watcher_manager);
}
@ -662,52 +661,6 @@ Vector<GC::Ref<MimeType>> Window::pdf_viewer_mime_type_objects()
return m_pdf_viewer_mime_type_objects;
}
// https://streams.spec.whatwg.org/#count-queuing-strategy-size-function
GC::Ref<WebIDL::CallbackType> Window::count_queuing_strategy_size_function()
{
auto& realm = this->realm();
if (!m_count_queuing_strategy_size_function) {
// 1. Let steps be the following steps:
auto steps = [](auto const&) {
// 1. Return 1.
return 1.0;
};
// 2. Let F be ! CreateBuiltinFunction(steps, 0, "size", « », globalObjects relevant Realm).
auto function = JS::NativeFunction::create(realm, move(steps), 0, "size", &realm);
// 3. Set globalObjects count queuing strategy size function to a Function that represents a reference to F, with callback context equal to globalObjects relevant settings object.
m_count_queuing_strategy_size_function = realm.create<WebIDL::CallbackType>(*function, relevant_settings_object(*this));
}
return GC::Ref { *m_count_queuing_strategy_size_function };
}
// https://streams.spec.whatwg.org/#byte-length-queuing-strategy-size-function
GC::Ref<WebIDL::CallbackType> Window::byte_length_queuing_strategy_size_function()
{
auto& realm = this->realm();
if (!m_byte_length_queuing_strategy_size_function) {
// 1. Let steps be the following steps, given chunk:
auto steps = [](JS::VM& vm) {
auto chunk = vm.argument(0);
// 1. Return ? GetV(chunk, "byteLength").
return chunk.get(vm, vm.names.byteLength);
};
// 2. Let F be ! CreateBuiltinFunction(steps, 1, "size", « », globalObjects relevant Realm).
auto function = JS::NativeFunction::create(realm, move(steps), 1, "size", &realm);
// 3. Set globalObjects byte length queuing strategy size function to a Function that represents a reference to F, with callback context equal to globalObjects relevant settings object.
m_byte_length_queuing_strategy_size_function = realm.create<WebIDL::CallbackType>(*function, relevant_settings_object(*this));
}
return GC::Ref { *m_byte_length_queuing_strategy_size_function };
}
static bool s_inspector_object_exposed = false;
static bool s_internals_object_exposed = false;

View file

@ -141,9 +141,6 @@ public:
CrossOriginPropertyDescriptorMap const& cross_origin_property_descriptor_map() const { return m_cross_origin_property_descriptor_map; }
CrossOriginPropertyDescriptorMap& cross_origin_property_descriptor_map() { return m_cross_origin_property_descriptor_map; }
GC::Ref<WebIDL::CallbackType> count_queuing_strategy_size_function();
GC::Ref<WebIDL::CallbackType> byte_length_queuing_strategy_size_function();
// JS API functions
GC::Ref<WindowProxy> window() const;
GC::Ref<WindowProxy> self() const;
@ -313,12 +310,6 @@ private:
// https://html.spec.whatwg.org/multipage/interaction.html#last-history-action-activation-timestamp
HighResolutionTime::DOMHighResTimeStamp m_last_history_action_activation_timestamp { AK::Infinity<double> };
// https://streams.spec.whatwg.org/#count-queuing-strategy-size-function
GC::Ptr<WebIDL::CallbackType> m_count_queuing_strategy_size_function;
// https://streams.spec.whatwg.org/#byte-length-queuing-strategy-size-function
GC::Ptr<WebIDL::CallbackType> m_byte_length_queuing_strategy_size_function;
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-window-status
// When the Window object is created, the attribute must be set to the empty string. It does not do anything else.
String m_status;

View file

@ -45,6 +45,7 @@ void WorkerGlobalScope::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
WindowOrWorkerGlobalScopeMixin::visit_edges(visitor);
UniversalGlobalScopeMixin::visit_edges(visitor);
visitor.visit(m_location);
visitor.visit(m_navigator);

View file

@ -100,7 +100,7 @@ void ResizeObserver::disconnect()
void ResizeObserver::invoke_callback(ReadonlySpan<GC::Ref<ResizeObserverEntry>> entries) const
{
auto& callback = *m_callback;
auto& realm = callback.callback_context->realm();
auto& realm = callback.callback_context;
auto wrapped_records = MUST(JS::Array::create(realm, 0));
for (size_t i = 0; i < entries.size(); ++i) {

View file

@ -5428,7 +5428,7 @@ JS::ThrowCompletionOr<GC::Root<WebIDL::CallbackType>> property_to_callback(JS::V
if (!property.is_function())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAFunction, property.to_string_without_side_effects());
return vm.heap().allocate<WebIDL::CallbackType>(property.as_object(), HTML::incumbent_settings_object(), operation_returns_promise);
return vm.heap().allocate<WebIDL::CallbackType>(property.as_object(), HTML::incumbent_realm(), operation_returns_promise);
}
// https://streams.spec.whatwg.org/#set-up-readable-byte-stream-controller-from-underlying-source

View file

@ -7,7 +7,7 @@
#include <LibWeb/Bindings/ByteLengthQueuingStrategyPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/HTML/UniversalGlobalScope.h>
#include <LibWeb/Streams/ByteLengthQueuingStrategy.h>
namespace Web::Streams {
@ -34,7 +34,9 @@ ByteLengthQueuingStrategy::~ByteLengthQueuingStrategy() = default;
GC::Ref<WebIDL::CallbackType> ByteLengthQueuingStrategy::size()
{
// 1. Return this's relevant global object's byte length queuing strategy size function.
return verify_cast<HTML::Window>(HTML::relevant_global_object(*this)).byte_length_queuing_strategy_size_function();
auto* global = dynamic_cast<HTML::UniversalGlobalScopeMixin*>(&HTML::relevant_global_object(*this));
VERIFY(global);
return global->byte_length_queuing_strategy_size_function();
}
void ByteLengthQueuingStrategy::initialize(JS::Realm& realm)

View file

@ -7,7 +7,7 @@
#include <LibWeb/Bindings/CountQueuingStrategyPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/HTML/UniversalGlobalScope.h>
#include <LibWeb/Streams/CountQueuingStrategy.h>
namespace Web::Streams {
@ -34,7 +34,9 @@ CountQueuingStrategy::~CountQueuingStrategy() = default;
GC::Ref<WebIDL::CallbackType> CountQueuingStrategy::size()
{
// 1. Return this's relevant global object's count queuing strategy size function.
return verify_cast<HTML::Window>(HTML::relevant_global_object(*this)).count_queuing_strategy_size_function();
auto* global = dynamic_cast<HTML::UniversalGlobalScopeMixin*>(&HTML::relevant_global_object(*this));
VERIFY(global);
return global->count_queuing_strategy_size_function();
}
void CountQueuingStrategy::initialize(JS::Realm& realm)

View file

@ -167,9 +167,8 @@ JS::Completion call_user_object_operation(WebIDL::CallbackType& callback, String
// 4. Let relevant realm be Os associated Realm.
auto& relevant_realm = object->shape().realm();
// FIXME: We should get the realm directly from the callback context.
// 5. Let stored realm be values callback context.
auto& stored_realm = callback.callback_context->realm();
auto& stored_realm = callback.callback_context;
// 6. Prepare to run script with relevant realm.
HTML::prepare_to_run_script(relevant_realm);
@ -252,9 +251,8 @@ JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional<JS::Valu
// 5. Let relevant realm be Fs associated Realm.
auto& relevant_realm = function_object->shape().realm();
// FIXME: We should get the realm directly from the callback context.
// 6. Let stored realm be values callback context.
auto& stored_realm = callback.callback_context->realm();
auto& stored_realm = callback.callback_context;
// 8. Prepare to run script with relevant realm.
HTML::prepare_to_run_script(relevant_realm);
@ -297,9 +295,8 @@ JS::Completion construct(WebIDL::CallbackType& callback, GC::MarkedVector<JS::Va
if (!JS::Value(function_object).is_constructor())
return relevant_realm.vm().template throw_completion<JS::TypeError>(JS::ErrorType::NotAConstructor, JS::Value(function_object).to_string_without_side_effects());
// FIXME: We should get the realm directly from the callback context.
// 4. Let stored realm be callables callback context.
auto& stored_realm = callback.callback_context->realm();
auto& stored_realm = callback.callback_context;
// 5. Prepare to run script with relevant realm.
HTML::prepare_to_run_script(relevant_realm);

View file

@ -12,7 +12,7 @@ namespace Web::WebIDL {
GC_DEFINE_ALLOCATOR(CallbackType);
CallbackType::CallbackType(JS::Object& callback, HTML::EnvironmentSettingsObject& callback_context, OperationReturnsPromise operation_returns_promise)
CallbackType::CallbackType(JS::Object& callback, JS::Realm& callback_context, OperationReturnsPromise operation_returns_promise)
: callback(callback)
, callback_context(callback_context)
, operation_returns_promise(operation_returns_promise)

View file

@ -24,12 +24,13 @@ class CallbackType final : public JS::Cell {
GC_DECLARE_ALLOCATOR(CallbackType);
public:
CallbackType(JS::Object& callback, HTML::EnvironmentSettingsObject& callback_context, OperationReturnsPromise = OperationReturnsPromise::No);
CallbackType(JS::Object& callback, JS::Realm& callback_context, OperationReturnsPromise = OperationReturnsPromise::No);
GC::Ref<JS::Object> callback;
// https://webidl.spec.whatwg.org/#dfn-callback-context
GC::Ref<HTML::EnvironmentSettingsObject> callback_context;
// NOTE: This is a Realm per ShadowRealm proposal https://github.com/whatwg/webidl/pull/1437
GC::Ref<JS::Realm> callback_context;
// Non-standard property used to distinguish Promise-returning callbacks in callback-related AOs
OperationReturnsPromise operation_returns_promise;

View file

@ -433,7 +433,7 @@ WebIDL::ExceptionOr<void> XMLHttpRequest::set_request_header(String const& name_
// 4. If name is not a header name or value is not a header value, then throw a "SyntaxError" DOMException.
if (!Fetch::Infrastructure::is_header_name(name))
return WebIDL::SyntaxError::create(realm, "Header name contains invalid characters."_string);
if (!Fetch::Infrastructure::is_header_value(value))
if (!Fetch::Infrastructure::is_header_value(normalized_value))
return WebIDL::SyntaxError::create(realm, "Header value contains invalid characters."_string);
auto header = Fetch::Infrastructure::Header {

View file

@ -554,7 +554,7 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
if (!@js_name@@js_suffix@.is_object())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObject, @js_name@@js_suffix@.to_string_without_side_effects());
auto callback_type = vm.heap().allocate<WebIDL::CallbackType>(@js_name@@js_suffix@.as_object(), HTML::incumbent_settings_object());
auto callback_type = vm.heap().allocate<WebIDL::CallbackType>(@js_name@@js_suffix@.as_object(), HTML::incumbent_realm());
@cpp_name@ = TRY(throw_dom_exception_if_needed(vm, [&] { return @cpp_type@::create(realm, *callback_type); }));
}
)~~~");
@ -563,7 +563,7 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
if (!@js_name@@js_suffix@.is_object())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObject, @js_name@@js_suffix@.to_string_without_side_effects());
auto callback_type = vm.heap().allocate<WebIDL::CallbackType>(@js_name@@js_suffix@.as_object(), HTML::incumbent_settings_object());
auto callback_type = vm.heap().allocate<WebIDL::CallbackType>(@js_name@@js_suffix@.as_object(), HTML::incumbent_realm());
auto @cpp_name@ = adopt_ref(*new @cpp_type@(callback_type));
)~~~");
}
@ -916,16 +916,16 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAFunction, @js_name@@js_suffix@.to_string_without_side_effects());
)~~~");
}
// 2. Return the IDL callback function type value that represents a reference to the same object that V represents, with the incumbent settings object as the callback context.
// 2. Return the IDL callback function type value that represents a reference to the same object that V represents, with the incumbent realm as the callback context.
if (parameter.type->is_nullable() || callback_function.is_legacy_treat_non_object_as_null) {
callback_function_generator.append(R"~~~(
GC::Ptr<WebIDL::CallbackType> @cpp_name@;
if (@js_name@@js_suffix@.is_object())
@cpp_name@ = vm.heap().allocate<WebIDL::CallbackType>(@js_name@@js_suffix@.as_object(), HTML::incumbent_settings_object(), @operation_returns_promise@);
@cpp_name@ = vm.heap().allocate<WebIDL::CallbackType>(@js_name@@js_suffix@.as_object(), HTML::incumbent_realm(), @operation_returns_promise@);
)~~~");
} else {
callback_function_generator.append(R"~~~(
auto @cpp_name@ = vm.heap().allocate<WebIDL::CallbackType>(@js_name@@js_suffix@.as_object(), HTML::incumbent_settings_object(), @operation_returns_promise@);
auto @cpp_name@ = vm.heap().allocate<WebIDL::CallbackType>(@js_name@@js_suffix@.as_object(), HTML::incumbent_realm(), @operation_returns_promise@);
)~~~");
}
} else if (parameter.type->name() == "sequence") {
@ -1282,7 +1282,7 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
if (includes_callable) {
union_generator.append(R"~~~(
if (@js_name@@js_suffix@_object.is_function())
return vm.heap().allocate<WebIDL::CallbackType>(@js_name@@js_suffix@.as_function(), HTML::incumbent_settings_object());
return vm.heap().allocate<WebIDL::CallbackType>(@js_name@@js_suffix@.as_function(), HTML::incumbent_realm());
)~~~");
}

View file

@ -0,0 +1,5 @@
CountQueuingStrategy | size1 === size2 -> true
ByteLengthQueuingStrategy | size1 === size2 -> true
CountQueuingStrategy | size1 === size2 -> true
ByteLengthQueuingStrategy | size1 === size2 -> true

View file

@ -0,0 +1,37 @@
Summary
Harness status: OK
Rerun
Found 27 tests
27 Pass
Details
Result Test Name MessagePass A simple blob range request.
Pass A blob range request with no type.
Pass A blob range request with no end.
Pass A blob range request with no start.
Pass A simple blob range request with whitespace.
Pass Blob content with short content and a large range end
Pass Blob content with short content and a range end matching content length
Pass Blob range with whitespace before and after hyphen
Pass Blob range with whitespace after hyphen
Pass Blob range with whitespace around equals sign
Pass Blob range with no value
Pass Blob range with incorrect range header
Pass Blob range with incorrect range header #2
Pass Blob range with incorrect range header #3
Pass Blob range request with multiple range values
Pass Blob range request with multiple range values and whitespace
Pass Blob range request with trailing comma
Pass Blob range with no start or end
Pass Blob range request with short range end
Pass Blob range start should be an ASCII digit
Pass Blob range should have a dash
Pass Blob range end should be an ASCII digit
Pass Blob range should include '-'
Pass Blob range should include '='
Pass Blob range should include 'bytes='
Pass Blob content with short content and a large range start
Pass Blob content with short content and a range start matching the content length

View file

@ -0,0 +1,25 @@
<script src="../include.js"></script>
<script>
test(() => {
const realm = new ShadowRealm();
const result = realm.evaluate(`
(() => {
let str = "";
for (const QueuingStrategy of [CountQueuingStrategy, ByteLengthQueuingStrategy]) {
const size1 = (new QueuingStrategy({ highWaterMark: 5 })).size;
const size2 = (new QueuingStrategy({ highWaterMark: 10 })).size;
str += \`\${QueuingStrategy.name} | size1 === size2 -> \${size1 === size2}\n\`;
}
return str;
})()
`);
println(result);
for (const QueuingStrategy of [CountQueuingStrategy, ByteLengthQueuingStrategy]) {
const size1 = (new QueuingStrategy({ highWaterMark: 5 })).size;
const size2 = (new QueuingStrategy({ highWaterMark: 10 })).size;
println(`${QueuingStrategy.name} | size1 === size2 -> ${size1 === size2}`);
}
})
</script>

View file

@ -0,0 +1,15 @@
<!doctype html>
<meta charset=utf-8>
<script>
self.GLOBAL = {
isWindow: function() { return true; },
isWorker: function() { return false; },
isShadowRealm: function() { return false; },
};
</script>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<div id=log></div>
<script src="../xhr/blob-range.any.js"></script>

View file

@ -0,0 +1,246 @@
// See also /fetch/range/blob.any.js
const supportedBlobRange = [
{
name: "A simple blob range request.",
data: ["A simple Hello, World! example"],
type: "text/plain",
range: "bytes=9-21",
content_length: 13,
content_range: "bytes 9-21/30",
result: "Hello, World!",
},
{
name: "A blob range request with no type.",
data: ["A simple Hello, World! example"],
type: undefined,
range: "bytes=9-21",
content_length: 13,
content_range: "bytes 9-21/30",
result: "Hello, World!",
},
{
name: "A blob range request with no end.",
data: ["Range with no end"],
type: "text/plain",
range: "bytes=11-",
content_length: 6,
content_range: "bytes 11-16/17",
result: "no end",
},
{
name: "A blob range request with no start.",
data: ["Range with no start"],
type: "text/plain",
range: "bytes=-8",
content_length: 8,
content_range: "bytes 11-18/19",
result: "no start",
},
{
name: "A simple blob range request with whitespace.",
data: ["A simple Hello, World! example"],
type: "text/plain",
range: "bytes= \t9-21",
content_length: 13,
content_range: "bytes 9-21/30",
result: "Hello, World!",
},
{
name: "Blob content with short content and a large range end",
data: ["Not much here"],
type: "text/plain",
range: "bytes=4-100000000000",
content_length: 9,
content_range: "bytes 4-12/13",
result: "much here",
},
{
name: "Blob content with short content and a range end matching content length",
data: ["Not much here"],
type: "text/plain",
range: "bytes=4-13",
content_length: 9,
content_range: "bytes 4-12/13",
result: "much here",
},
{
name: "Blob range with whitespace before and after hyphen",
data: ["Valid whitespace #1"],
type: "text/plain",
range: "bytes=5 - 10",
content_length: 6,
content_range: "bytes 5-10/19",
result: " white",
},
{
name: "Blob range with whitespace after hyphen",
data: ["Valid whitespace #2"],
type: "text/plain",
range: "bytes=-\t 5",
content_length: 5,
content_range: "bytes 14-18/19",
result: "ce #2",
},
{
name: "Blob range with whitespace around equals sign",
data: ["Valid whitespace #3"],
type: "text/plain",
range: "bytes \t =\t 6-",
content_length: 13,
content_range: "bytes 6-18/19",
result: "whitespace #3",
},
];
const unsupportedBlobRange = [
{
name: "Blob range with no value",
data: ["Blob range should have a value"],
type: "text/plain",
range: "",
},
{
name: "Blob range with incorrect range header",
data: ["A"],
type: "text/plain",
range: "byte=0-"
},
{
name: "Blob range with incorrect range header #2",
data: ["A"],
type: "text/plain",
range: "bytes"
},
{
name: "Blob range with incorrect range header #3",
data: ["A"],
type: "text/plain",
range: "bytes\t \t"
},
{
name: "Blob range request with multiple range values",
data: ["Multiple ranges are not currently supported"],
type: "text/plain",
range: "bytes=0-5,15-",
},
{
name: "Blob range request with multiple range values and whitespace",
data: ["Multiple ranges are not currently supported"],
type: "text/plain",
range: "bytes=0-5, 15-",
},
{
name: "Blob range request with trailing comma",
data: ["Range with invalid trailing comma"],
type: "text/plain",
range: "bytes=0-5,",
},
{
name: "Blob range with no start or end",
data: ["Range with no start or end"],
type: "text/plain",
range: "bytes=-",
},
{
name: "Blob range request with short range end",
data: ["Range end should be greater than range start"],
type: "text/plain",
range: "bytes=10-5",
},
{
name: "Blob range start should be an ASCII digit",
data: ["Range start must be an ASCII digit"],
type: "text/plain",
range: "bytes=x-5",
},
{
name: "Blob range should have a dash",
data: ["Blob range should have a dash"],
type: "text/plain",
range: "bytes=5",
},
{
name: "Blob range end should be an ASCII digit",
data: ["Range end must be an ASCII digit"],
type: "text/plain",
range: "bytes=5-x",
},
{
name: "Blob range should include '-'",
data: ["Range end must include '-'"],
type: "text/plain",
range: "bytes=x",
},
{
name: "Blob range should include '='",
data: ["Range end must include '='"],
type: "text/plain",
range: "bytes 5-",
},
{
name: "Blob range should include 'bytes='",
data: ["Range end must include 'bytes='"],
type: "text/plain",
range: "5-",
},
{
name: "Blob content with short content and a large range start",
data: ["Not much here"],
type: "text/plain",
range: "bytes=100000-",
},
{
name: "Blob content with short content and a range start matching the content length",
data: ["Not much here"],
type: "text/plain",
range: "bytes=13-",
},
];
supportedBlobRange.forEach(({ name, data, type, range, content_length, content_range, result }) => {
promise_test(async t => {
const blob = new Blob(data, { "type" : type });
const blobURL = URL.createObjectURL(blob);
t.add_cleanup(() => URL.revokeObjectURL(blobURL));
const xhr = new XMLHttpRequest();
xhr.open("GET", blobURL);
xhr.responseType = "text";
xhr.setRequestHeader("Range", range);
await new Promise(resolve => {
xhr.onloadend = resolve;
xhr.send();
});
assert_equals(xhr.status, 206, "HTTP status is 206");
assert_equals(xhr.getResponseHeader("Content-Type"), type || "", "Content-Type is " + xhr.getResponseHeader("Content-Type"));
assert_equals(xhr.getResponseHeader("Content-Length"), content_length.toString(), "Content-Length is " + xhr.getResponseHeader("Content-Length"));
assert_equals(xhr.getResponseHeader("Content-Range"), content_range, "Content-Range is " + xhr.getResponseHeader("Content-Range"));
assert_equals(xhr.responseText, result, "Response's body is correct");
const all = xhr.getAllResponseHeaders().toLowerCase();
assert_true(all.includes(`content-type: ${type || ""}`), "Expected Content-Type in getAllResponseHeaders()");
assert_true(all.includes(`content-length: ${content_length}`), "Expected Content-Length in getAllResponseHeaders()");
assert_true(all.includes(`content-range: ${content_range}`), "Expected Content-Range in getAllResponseHeaders()")
}, name);
});
unsupportedBlobRange.forEach(({ name, data, type, range }) => {
promise_test(t => {
const blob = new Blob(data, { "type" : type });
const blobURL = URL.createObjectURL(blob);
t.add_cleanup(() => URL.revokeObjectURL(blobURL));
const xhr = new XMLHttpRequest();
xhr.open("GET", blobURL, false);
xhr.setRequestHeader("Range", range);
assert_throws_dom("NetworkError", () => xhr.send());
xhr.open("GET", blobURL);
xhr.setRequestHeader("Range", range);
xhr.responseType = "text";
return new Promise((resolve, reject) => {
xhr.onload = reject;
xhr.onerror = resolve;
xhr.send();
});
}, name);
});