Compare commits

...

9 commits

Author SHA1 Message Date
Ashwin Paudel
8071afd7f7
Merge 719a69215d into 99073c0561 2024-12-11 06:13:29 -08:00
Shannon Booth
99073c0561 LibWeb: Move ad hoc CallbackType helper method to CallbackType header
Abstract operations of a stream does not seem like the correct home for
this function.
2024-12-11 15:11:21 +01:00
Shannon Booth
93f258deb7 LibWeb/Streams: Do not expose some non-standard functions in header
These are non-standard and only needed internally as implementation
details in the implementation of AbstractOperations, so let's keep them
at a file-local level.
2024-12-11 15:11:21 +01:00
Shannon Booth
19bbfb023a LibWeb/Streams: Move "set up transform stream" to TransformStream
This is not marked as an AO in the spec, and is a publically exported
API exposed on TransformStream.
2024-12-11 15:11:21 +01:00
Shannon Booth
3f572d9ab7 LibWeb/Streams: Move ReadableStream functions out of AbstractOperations
These are not defined in the abstract operations section of the spec and
are the publically exported Stream APIs exposed on ReadableStream.
2024-12-11 15:11:21 +01:00
Shannon Booth
c6d0f87bb7 LibWeb/Streams: Put algorithm definitions in a separate header file
To solve a future cyclic dependency problem.
2024-12-11 15:11:21 +01:00
Shannon Booth
f110edebd1 LibWeb/HTML: Encoding parse a URL when setting a href URL
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
Fixes many WPT encoding regression tests which regressed in
fe891727dc.
2024-12-11 09:48:17 +01:00
Ashwin Paudel
719a69215d
LibWebView: Updated URL.cpp as requested by rmg-x 2024-12-08 15:34:11 -05:00
Ashwin Paudel
2b5235b6de LibWebView: Fixed url sanitizing logic 2024-12-08 09:06:51 -05:00
22 changed files with 357 additions and 313 deletions

View file

@ -76,7 +76,7 @@ WebIDL::ExceptionOr<GC::Ref<CompressionStream>> CompressionStream::construct_imp
});
// 6. Set up this's transform with transformAlgorithm set to transformAlgorithm and flushAlgorithm set to flushAlgorithm.
Streams::transform_stream_set_up(stream->m_transform, transform_algorithm, flush_algorithm);
stream->m_transform->set_up(transform_algorithm, flush_algorithm);
return stream;
}

View file

@ -77,7 +77,7 @@ WebIDL::ExceptionOr<GC::Ref<DecompressionStream>> DecompressionStream::construct
});
// 6. Set up this's transform with transformAlgorithm set to transformAlgorithm and flushAlgorithm set to flushAlgorithm.
Streams::transform_stream_set_up(stream->m_transform, transform_algorithm, flush_algorithm);
stream->m_transform->set_up(transform_algorithm, flush_algorithm);
return stream;
}

View file

@ -56,7 +56,7 @@ WebIDL::ExceptionOr<Infrastructure::BodyWithType> extract_body(JS::Realm& realm,
// 4. Otherwise, set stream to a new ReadableStream object, and set up stream with byte reading support.
else {
stream = realm.create<Streams::ReadableStream>(realm);
Streams::set_up_readable_stream_controller_with_byte_reading_support(*stream);
stream->set_up_with_byte_reading_support();
}
// 5. Assert: stream is a ReadableStream object.
@ -156,7 +156,7 @@ WebIDL::ExceptionOr<Infrastructure::BodyWithType> extract_body(JS::Realm& realm,
auto array_buffer = JS::ArrayBuffer::create(stream->realm(), move(bytes));
auto chunk = JS::Uint8Array::create(stream->realm(), array_buffer->byte_length(), *array_buffer);
Streams::readable_stream_enqueue(*stream->controller(), chunk).release_value_but_fixme_should_propagate_errors();
stream->enqueue(chunk).release_value_but_fixme_should_propagate_errors();
}
// When running action is done, close stream.

View file

@ -68,7 +68,7 @@ void FetchedDataReceiver::on_data_received(ReadonlyBytes bytes)
HTML::TemporaryExecutionContext execution_context { m_stream->realm(), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes };
// 1. Pull from bytes buffer into stream.
if (auto result = Streams::readable_stream_pull_from_bytes(m_stream, move(bytes)); result.is_error()) {
if (auto result = m_stream->pull_from_bytes(move(bytes)); result.is_error()) {
auto throw_completion = Bindings::exception_to_throw_completion(m_stream->vm(), result.release_error());
dbgln("FetchedDataReceiver: Stream error pulling bytes");

View file

@ -741,7 +741,7 @@ void fetch_response_handover(JS::Realm& realm, Infrastructure::FetchParams const
process_response_end_of_body();
return WebIDL::create_resolved_promise(realm, JS::js_undefined());
});
Streams::transform_stream_set_up(transform_stream, identity_transform_algorithm, flush_algorithm);
transform_stream->set_up(identity_transform_algorithm, flush_algorithm);
// 4. Set internalResponses bodys stream to the result of internalResponses bodys stream piped through transformStream.
auto promise = Streams::readable_stream_pipe_to(internal_response->body()->stream(), transform_stream->writable(), false, false, false, {});
@ -2305,7 +2305,7 @@ WebIDL::ExceptionOr<GC::Ref<PendingResponse>> nonstandard_resource_loader_file_o
});
// 13. Set up stream with byte reading support with pullAlgorithm set to pullAlgorithm, cancelAlgorithm set to cancelAlgorithm.
Streams::set_up_readable_stream_controller_with_byte_reading_support(stream, pull_algorithm, cancel_algorithm);
stream->set_up_with_byte_reading_support(pull_algorithm, cancel_algorithm);
auto on_headers_received = GC::create_function(vm.heap(), [&vm, request, pending_response, stream](HTTP::HeaderMap const& response_headers, Optional<u32> status_code, Optional<String> const& reason_phrase) {
(void)request;

View file

@ -324,7 +324,7 @@ GC::Ref<Streams::ReadableStream> Blob::get_stream()
auto stream = realm.create<Streams::ReadableStream>(realm);
// 2. Set up stream with byte reading support.
set_up_readable_stream_controller_with_byte_reading_support(stream);
stream->set_up_with_byte_reading_support();
// FIXME: 3. Run the following steps in parallel:
{
@ -346,7 +346,7 @@ GC::Ref<Streams::ReadableStream> Blob::get_stream()
// 3. Enqueue chunk in stream.
auto maybe_error = Bindings::throw_dom_exception_if_needed(realm.vm(), [&]() {
return readable_stream_enqueue(*stream->controller(), chunk);
return stream->enqueue(chunk);
});
if (maybe_error.is_error()) {

View file

@ -1,5 +1,6 @@
/*
* Copyright (c) 2021, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2024, Shannon Booth <shannon@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -27,17 +28,23 @@ void HTMLHyperlinkElementUtils::reinitialize_url() const
// https://html.spec.whatwg.org/multipage/links.html#concept-hyperlink-url-set
void HTMLHyperlinkElementUtils::set_the_url()
{
// 1. If this element's href content attribute is absent, set this element's url to null.
// 1. Set this element's url to null.
m_url = {};
// 2. If this element's href content attribute is absent, then return.
auto href_content_attribute = hyperlink_element_utils_href();
if (!href_content_attribute.has_value()) {
m_url = {};
hyperlink_element_utils_element().invalidate_style(DOM::StyleInvalidationReason::HTMLHyperlinkElementHrefChange);
return;
}
// 2. Otherwise, parse this element's href content attribute value relative to this element's node document.
// If parsing is successful, set this element's url to the result; otherwise, set this element's url to null.
m_url = hyperlink_element_utils_document().parse_url(*href_content_attribute);
// 3. Let url be the result of encoding-parsing a URL given this element's href content attribute's value, relative to this element's node document.
auto url = hyperlink_element_utils_document().encoding_parse_url(*href_content_attribute);
// 4. If url is not failure, then set this element's url to url.
if (url.is_valid())
m_url = move(url);
hyperlink_element_utils_element().invalidate_style(DOM::StyleInvalidationReason::HTMLHyperlinkElementHrefChange);
}

View file

@ -45,6 +45,53 @@
namespace Web::Streams {
// https://streams.spec.whatwg.org/#close-sentinel
// Non-standard function that implements the "close sentinel" value.
static JS::Value create_close_sentinel()
{
// The close sentinel is a unique value enqueued into [[queue]], in lieu of a chunk, to signal that the stream is closed. It is only used internally, and is never exposed to web developers.
// Note: We use the empty Value to signal this as, similarly to the note above, the empty value is not exposed to nor creatable by web developers.
return {};
}
// https://streams.spec.whatwg.org/#close-sentinel
// Non-standard function that implements the "If value is a close sentinel" check.
static bool is_close_sentinel(JS::Value value)
{
return value.is_empty();
}
// NON-STANDARD: Can be used instead of CreateReadableStream in cases where we need to set up a newly allocated
// ReadableStream before initialization of said ReadableStream, i.e. ReadableStream is captured by lambdas in an uninitialized state.
// Spec steps are taken from: https://streams.spec.whatwg.org/#create-readable-stream
static WebIDL::ExceptionOr<void> set_up_readable_stream(JS::Realm& realm, ReadableStream& stream, GC::Ref<StartAlgorithm> start_algorithm, GC::Ref<PullAlgorithm> pull_algorithm, GC::Ref<CancelAlgorithm> cancel_algorithm, Optional<double> high_water_mark = {}, GC::Ptr<SizeAlgorithm> size_algorithm = {})
{
// 1. If highWaterMark was not passed, set it to 1.
if (!high_water_mark.has_value())
high_water_mark = 1.0;
// 2. If sizeAlgorithm was not passed, set it to an algorithm that returns 1.
if (!size_algorithm)
size_algorithm = GC::create_function(realm.heap(), [](JS::Value) { return JS::normal_completion(JS::Value(1)); });
// 3. Assert: ! IsNonNegativeNumber(highWaterMark) is true.
VERIFY(is_non_negative_number(JS::Value { *high_water_mark }));
// 4. Let stream be a new ReadableStream.
// NOTE: The ReadableStream is allocated outside the scope of this method.
// 5. Perform ! InitializeReadableStream(stream).
initialize_readable_stream(stream);
// 6. Let controller be a new ReadableStreamDefaultController.
auto controller = realm.create<ReadableStreamDefaultController>(realm);
// 7. Perform ? SetUpReadableStreamDefaultController(stream, controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark, sizeAlgorithm).
TRY(set_up_readable_stream_default_controller(stream, *controller, start_algorithm, pull_algorithm, cancel_algorithm, *high_water_mark, *size_algorithm));
return {};
}
// https://streams.spec.whatwg.org/#acquire-readable-stream-reader
WebIDL::ExceptionOr<GC::Ref<ReadableStreamDefaultReader>> acquire_readable_stream_default_reader(ReadableStream& stream)
{
@ -2932,37 +2979,6 @@ bool readable_byte_stream_controller_should_call_pull(ReadableByteStreamControll
return false;
}
// NON-STANDARD: Can be used instead of CreateReadableStream in cases where we need to set up a newly allocated
// ReadableStream before initialization of said ReadableStream, i.e. ReadableStream is captured by lambdas in an uninitialized state.
// Spec steps are taken from: https://streams.spec.whatwg.org/#create-readable-stream
WebIDL::ExceptionOr<void> set_up_readable_stream(JS::Realm& realm, ReadableStream& stream, GC::Ref<StartAlgorithm> start_algorithm, GC::Ref<PullAlgorithm> pull_algorithm, GC::Ref<CancelAlgorithm> cancel_algorithm, Optional<double> high_water_mark, GC::Ptr<SizeAlgorithm> size_algorithm)
{
// 1. If highWaterMark was not passed, set it to 1.
if (!high_water_mark.has_value())
high_water_mark = 1.0;
// 2. If sizeAlgorithm was not passed, set it to an algorithm that returns 1.
if (!size_algorithm)
size_algorithm = GC::create_function(realm.heap(), [](JS::Value) { return JS::normal_completion(JS::Value(1)); });
// 3. Assert: ! IsNonNegativeNumber(highWaterMark) is true.
VERIFY(is_non_negative_number(JS::Value { *high_water_mark }));
// 4. Let stream be a new ReadableStream.
// NOTE: The ReadableStream is allocated outside the scope of this method.
// 5. Perform ! InitializeReadableStream(stream).
initialize_readable_stream(stream);
// 6. Let controller be a new ReadableStreamDefaultController.
auto controller = realm.create<ReadableStreamDefaultController>(realm);
// 7. Perform ? SetUpReadableStreamDefaultController(stream, controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark, sizeAlgorithm).
TRY(set_up_readable_stream_default_controller(stream, *controller, start_algorithm, pull_algorithm, cancel_algorithm, *high_water_mark, *size_algorithm));
return {};
}
// https://streams.spec.whatwg.org/#create-readable-stream
WebIDL::ExceptionOr<GC::Ref<ReadableStream>> create_readable_stream(JS::Realm& realm, GC::Ref<StartAlgorithm> start_algorithm, GC::Ref<PullAlgorithm> pull_algorithm, GC::Ref<CancelAlgorithm> cancel_algorithm, Optional<double> high_water_mark, GC::Ptr<SizeAlgorithm> size_algorithm)
{
@ -3259,39 +3275,6 @@ WebIDL::ExceptionOr<void> set_up_readable_byte_stream_controller(ReadableStream&
return {};
}
// https://streams.spec.whatwg.org/#readablestream-enqueue
WebIDL::ExceptionOr<void> readable_stream_enqueue(ReadableStreamController& controller, JS::Value chunk)
{
// 1. If stream.[[controller]] implements ReadableStreamDefaultController,
if (controller.has<GC::Ref<ReadableStreamDefaultController>>()) {
// 1. Perform ! ReadableStreamDefaultControllerEnqueue(stream.[[controller]], chunk).
return readable_stream_default_controller_enqueue(controller.get<GC::Ref<ReadableStreamDefaultController>>(), chunk);
}
// 2. Otherwise,
else {
// 1. Assert: stream.[[controller]] implements ReadableByteStreamController.
VERIFY(controller.has<GC::Ref<ReadableByteStreamController>>());
auto readable_byte_controller = controller.get<GC::Ref<ReadableByteStreamController>>();
// FIXME: 2. Assert: chunk is an ArrayBufferView.
// 3. Let byobView be the current BYOB request view for stream.
// FIXME: This is not what the spec means by 'current BYOB request view'
auto byob_view = readable_byte_controller->raw_byob_request();
// 4. If byobView is non-null, and chunk.[[ViewedArrayBuffer]] is byobView.[[ViewedArrayBuffer]], then:
if (byob_view) {
// FIXME: 1. Assert: chunk.[[ByteOffset]] is byobView.[[ByteOffset]].
// FIXME: 2. Assert: chunk.[[ByteLength]] ≤ byobView.[[ByteLength]].
// FIXME: 3. Perform ? ReadableByteStreamControllerRespond(stream.[[controller]], chunk.[[ByteLength]]).
TODO();
}
// 5. Otherwise, perform ? ReadableByteStreamControllerEnqueue(stream.[[controller]], chunk).
return readable_byte_stream_controller_enqueue(readable_byte_controller, chunk);
}
}
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-enqueue
WebIDL::ExceptionOr<void> readable_byte_stream_controller_enqueue(ReadableByteStreamController& controller, JS::Value chunk)
{
@ -3412,49 +3395,6 @@ WebIDL::ExceptionOr<void> readable_byte_stream_controller_enqueue(ReadableByteSt
return {};
}
// https://streams.spec.whatwg.org/#readablestream-pull-from-bytes
WebIDL::ExceptionOr<void> readable_stream_pull_from_bytes(ReadableStream& stream, ByteBuffer bytes)
{
// 1. Assert: stream.[[controller]] implements ReadableByteStreamController.
auto controller = stream.controller()->get<GC::Ref<ReadableByteStreamController>>();
// 2. Let available be bytess length.
auto available = bytes.size();
// 3. Let desiredSize be available.
auto desired_size = available;
// FIXME: 4. If streams current BYOB request view is non-null, then set desiredSize to streams current BYOB request
// view's byte length.
// 5. Let pullSize be the smaller value of available and desiredSize.
auto pull_size = min(available, desired_size);
// 6. Let pulled be the first pullSize bytes of bytes.
auto pulled = pull_size == available ? move(bytes) : MUST(bytes.slice(0, pull_size));
// 7. Remove the first pullSize bytes from bytes.
if (pull_size != available)
bytes = MUST(bytes.slice(pull_size, available - pull_size));
// FIXME: 8. If streams current BYOB request view is non-null, then:
// 1. Write pulled into streams current BYOB request view.
// 2. Perform ? ReadableByteStreamControllerRespond(stream.[[controller]], pullSize).
// 9. Otherwise,
{
auto& realm = HTML::relevant_realm(stream);
// 1. Set view to the result of creating a Uint8Array from pulled in streams relevant Realm.
auto array_buffer = JS::ArrayBuffer::create(realm, move(pulled));
auto view = JS::Uint8Array::create(realm, array_buffer->byte_length(), *array_buffer);
// 2. Perform ? ReadableByteStreamControllerEnqueue(stream.[[controller]], view).
TRY(readable_byte_stream_controller_enqueue(controller, view));
}
return {};
}
// https://streams.spec.whatwg.org/#transfer-array-buffer
WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> transfer_array_buffer(JS::Realm& realm, JS::ArrayBuffer& buffer)
{
@ -3642,52 +3582,6 @@ PullIntoDescriptor readable_byte_stream_controller_shift_pending_pull_into(Reada
return descriptor;
}
// https://streams.spec.whatwg.org/#readablestream-set-up-with-byte-reading-support
void set_up_readable_stream_controller_with_byte_reading_support(ReadableStream& stream, GC::Ptr<PullAlgorithm> pull_algorithm, GC::Ptr<CancelAlgorithm> cancel_algorithm, double high_water_mark)
{
auto& realm = stream.realm();
// 1. Let startAlgorithm be an algorithm that returns undefined.
auto start_algorithm = GC::create_function(realm.heap(), []() -> WebIDL::ExceptionOr<JS::Value> { return JS::js_undefined(); });
// 2. Let pullAlgorithmWrapper be an algorithm that runs these steps:
auto pull_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, pull_algorithm]() {
// 1. Let result be the result of running pullAlgorithm, if pullAlgorithm was given, or null otherwise. If this throws an exception e, return a promise rejected with e.
GC::Ptr<JS::PromiseCapability> result = nullptr;
if (pull_algorithm)
result = pull_algorithm->function()();
// 2. If result is a Promise, then return result.
if (result != nullptr)
return GC::Ref(*result);
// 3. Return a promise resolved with undefined.
return WebIDL::create_resolved_promise(realm, JS::js_undefined());
});
// 3. Let cancelAlgorithmWrapper be an algorithm that runs these steps:
auto cancel_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, cancel_algorithm](JS::Value c) {
// 1. Let result be the result of running cancelAlgorithm, if cancelAlgorithm was given, or null otherwise. If this throws an exception e, return a promise rejected with e.
GC::Ptr<JS::PromiseCapability> result = nullptr;
if (cancel_algorithm)
result = cancel_algorithm->function()(c);
// 2. If result is a Promise, then return result.
if (result != nullptr)
return GC::Ref(*result);
// 3. Return a promise resolved with undefined.
return WebIDL::create_resolved_promise(realm, JS::js_undefined());
});
// 4. Perform ! InitializeReadableStream(stream).
// 5. Let controller be a new ReadableByteStreamController.
auto controller = realm.create<ReadableByteStreamController>(realm);
// 6. Perform ! SetUpReadableByteStreamController(stream, controller, startAlgorithm, pullAlgorithmWrapper, cancelAlgorithmWrapper, highWaterMark, undefined).
MUST(set_up_readable_byte_stream_controller(stream, controller, start_algorithm, pull_algorithm_wrapper, cancel_algorithm_wrapper, high_water_mark, JS::js_undefined()));
}
// https://streams.spec.whatwg.org/#writable-stream-abort
GC::Ref<WebIDL::Promise> writable_stream_abort(WritableStream& stream, JS::Value reason)
{
@ -5298,84 +5192,6 @@ void transform_stream_set_backpressure(TransformStream& stream, bool backpressur
stream.set_backpressure(backpressure);
}
// https://streams.spec.whatwg.org/#transformstream-set-up
void transform_stream_set_up(TransformStream& stream, GC::Ref<TransformAlgorithm> transform_algorithm, GC::Ptr<FlushAlgorithm> flush_algorithm, GC::Ptr<CancelAlgorithm> cancel_algorithm)
{
auto& realm = stream.realm();
// 1. Let writableHighWaterMark be 1.
auto writable_high_water_mark = 1.0;
// 2. Let writableSizeAlgorithm be an algorithm that returns 1.
auto writable_size_algorithm = GC::create_function(realm.heap(), [](JS::Value) {
return JS::normal_completion(JS::Value { 1 });
});
// 3. Let readableHighWaterMark be 0.
auto readable_high_water_mark = 0.0;
// 4. Let readableSizeAlgorithm be an algorithm that returns 1.
auto readable_size_algorithm = GC::create_function(realm.heap(), [](JS::Value) {
return JS::normal_completion(JS::Value { 1 });
});
// 5. Let transformAlgorithmWrapper be an algorithm that runs these steps given a value chunk:
auto transform_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, transform_algorithm](JS::Value chunk) -> GC::Ref<WebIDL::Promise> {
// 1. Let result be the result of running transformAlgorithm given chunk. If this throws an exception e, return a promise rejected with e.
GC::Ptr<JS::PromiseCapability> result = nullptr;
result = transform_algorithm->function()(chunk);
// 2. If result is a Promise, then return result.
if (result)
return GC::Ref { *result };
// 3. Return a promise resolved with undefined.
return WebIDL::create_resolved_promise(realm, JS::js_undefined());
});
// 6. Let flushAlgorithmWrapper be an algorithm that runs these steps:
auto flush_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, flush_algorithm]() -> GC::Ref<WebIDL::Promise> {
// 1. Let result be the result of running flushAlgorithm, if flushAlgorithm was given, or null otherwise. If this throws an exception e, return a promise rejected with e.
GC::Ptr<JS::PromiseCapability> result = nullptr;
if (flush_algorithm)
result = flush_algorithm->function()();
// 2. If result is a Promise, then return result.
if (result)
return GC::Ref { *result };
// 3. Return a promise resolved with undefined.
return WebIDL::create_resolved_promise(realm, JS::js_undefined());
});
// 7. Let cancelAlgorithmWrapper be an algorithm that runs these steps given a value reason:
auto cancel_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, cancel_algorithm](JS::Value reason) -> GC::Ref<WebIDL::Promise> {
// 1. Let result be the result of running cancelAlgorithm given reason, if cancelAlgorithm was given, or null otherwise. If this throws an exception e, return a promise rejected with e.
GC::Ptr<JS::PromiseCapability> result = nullptr;
if (cancel_algorithm)
result = cancel_algorithm->function()(reason);
// 2. If result is a Promise, then return result.
if (result)
return GC::Ref { *result };
// 3. Return a promise resolved with undefined.
return WebIDL::create_resolved_promise(realm, JS::js_undefined());
});
// 8. Let startPromise be a promise resolved with undefined.
auto start_promise = WebIDL::create_resolved_promise(realm, JS::js_undefined());
// 9. Perform ! InitializeTransformStream(stream, startPromise, writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm).
initialize_transform_stream(stream, start_promise, writable_high_water_mark, writable_size_algorithm, readable_high_water_mark, readable_size_algorithm);
// 10. Let controller be a new TransformStreamDefaultController.
auto controller = realm.create<TransformStreamDefaultController>(realm);
// 11. Perform ! SetUpTransformStreamDefaultController(stream, controller, transformAlgorithmWrapper, flushAlgorithmWrapper, cancelAlgorithmWrapper).
set_up_transform_stream_default_controller(stream, controller, transform_algorithm_wrapper, flush_algorithm_wrapper, cancel_algorithm_wrapper);
}
// https://streams.spec.whatwg.org/#transform-stream-unblock-write
void transform_stream_unblock_write(TransformStream& stream)
{
@ -5486,37 +5302,6 @@ WebIDL::ExceptionOr<JS::Value> structured_clone(JS::Realm& realm, JS::Value valu
return TRY(HTML::structured_deserialize(vm, serialized, realm));
}
// https://streams.spec.whatwg.org/#close-sentinel
// Non-standard function that implements the "close sentinel" value.
JS::Value create_close_sentinel()
{
// The close sentinel is a unique value enqueued into [[queue]], in lieu of a chunk, to signal that the stream is closed. It is only used internally, and is never exposed to web developers.
// Note: We use the empty Value to signal this as, similarly to the note above, the empty value is not exposed to nor creatable by web developers.
return {};
}
// https://streams.spec.whatwg.org/#close-sentinel
// Non-standard function that implements the "If value is a close sentinel" check.
bool is_close_sentinel(JS::Value value)
{
return value.is_empty();
}
// Non-standard function to aid in converting a user-provided function into a WebIDL::Callback. This is essentially
// what the Bindings generator would do at compile time, but at runtime instead.
JS::ThrowCompletionOr<GC::Root<WebIDL::CallbackType>> property_to_callback(JS::VM& vm, JS::Value value, JS::PropertyKey const& property_key, WebIDL::OperationReturnsPromise operation_returns_promise)
{
auto property = TRY(value.get(vm, property_key));
if (property.is_undefined())
return GC::Root<WebIDL::CallbackType> {};
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_realm(), operation_returns_promise);
}
// https://streams.spec.whatwg.org/#set-up-readable-byte-stream-controller-from-underlying-source
WebIDL::ExceptionOr<void> set_up_readable_byte_stream_controller_from_underlying_source(ReadableStream& stream, JS::Value underlying_source, UnderlyingSource const& underlying_source_dict, double high_water_mark)
{

View file

@ -11,6 +11,7 @@
#include <LibGC/Ptr.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Streams/Algorithms.h>
#include <LibWeb/Streams/ReadableStream.h>
#include <LibWeb/WebIDL/CallbackType.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
@ -19,16 +20,6 @@
namespace Web::Streams {
using SizeAlgorithm = GC::Function<JS::Completion(JS::Value)>;
using PullAlgorithm = GC::Function<GC::Ref<WebIDL::Promise>()>;
using CancelAlgorithm = GC::Function<GC::Ref<WebIDL::Promise>(JS::Value)>;
using StartAlgorithm = GC::Function<WebIDL::ExceptionOr<JS::Value>()>;
using AbortAlgorithm = GC::Function<GC::Ref<WebIDL::Promise>(JS::Value)>;
using CloseAlgorithm = GC::Function<GC::Ref<WebIDL::Promise>()>;
using WriteAlgorithm = GC::Function<GC::Ref<WebIDL::Promise>(JS::Value)>;
using FlushAlgorithm = GC::Function<GC::Ref<WebIDL::Promise>()>;
using TransformAlgorithm = GC::Function<GC::Ref<WebIDL::Promise>(JS::Value)>;
WebIDL::ExceptionOr<GC::Ref<ReadableStreamDefaultReader>> acquire_readable_stream_default_reader(ReadableStream&);
WebIDL::ExceptionOr<GC::Ref<ReadableStreamBYOBReader>> acquire_readable_stream_byob_reader(ReadableStream&);
bool is_readable_stream_locked(ReadableStream const&);
@ -83,7 +74,6 @@ Optional<double> readable_stream_default_controller_get_desired_size(ReadableStr
bool readable_stream_default_controller_can_close_or_enqueue(ReadableStreamDefaultController&);
WebIDL::ExceptionOr<void> set_up_readable_stream_default_controller(ReadableStream&, ReadableStreamDefaultController&, GC::Ref<StartAlgorithm>, GC::Ref<PullAlgorithm>, GC::Ref<CancelAlgorithm>, double high_water_mark, GC::Ref<SizeAlgorithm>);
WebIDL::ExceptionOr<void> set_up_readable_stream_default_controller_from_underlying_source(ReadableStream&, JS::Value underlying_source_value, UnderlyingSource, double high_water_mark, GC::Ref<SizeAlgorithm>);
void set_up_readable_stream_controller_with_byte_reading_support(ReadableStream&, GC::Ptr<PullAlgorithm> = {}, GC::Ptr<CancelAlgorithm> = {}, double high_water_mark = 0);
WebIDL::ExceptionOr<void> set_up_readable_byte_stream_controller(ReadableStream&, ReadableByteStreamController&, GC::Ref<StartAlgorithm>, GC::Ref<PullAlgorithm>, GC::Ref<CancelAlgorithm>, double high_water_mark, JS::Value auto_allocate_chunk_size);
WebIDL::ExceptionOr<void> set_up_readable_byte_stream_controller_from_underlying_source(ReadableStream&, JS::Value underlying_source, UnderlyingSource const& underlying_source_dict, double high_water_mark);
GC::Ptr<ReadableStreamBYOBRequest> readable_byte_stream_controller_get_byob_request(GC::Ref<ReadableByteStreamController>);
@ -94,9 +84,7 @@ WebIDL::ExceptionOr<void> readable_byte_stream_controller_respond_internal(Reada
WebIDL::ExceptionOr<void> readable_byte_stream_controller_respond(ReadableByteStreamController&, u64 bytes_written);
WebIDL::ExceptionOr<void> readable_byte_stream_controller_respond_with_new_view(JS::Realm&, ReadableByteStreamController&, WebIDL::ArrayBufferView&);
WebIDL::ExceptionOr<void> readable_stream_enqueue(ReadableStreamController& controller, JS::Value chunk);
WebIDL::ExceptionOr<void> readable_byte_stream_controller_enqueue(ReadableByteStreamController& controller, JS::Value chunk);
WebIDL::ExceptionOr<void> readable_stream_pull_from_bytes(ReadableStream&, ByteBuffer bytes);
WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> transfer_array_buffer(JS::Realm& realm, JS::ArrayBuffer& buffer);
WebIDL::ExceptionOr<void> readable_byte_stream_controller_enqueue_detached_pull_into_queue(ReadableByteStreamController& controller, PullIntoDescriptor& pull_into_descriptor);
void readable_byte_stream_controller_commit_pull_into_descriptor(ReadableStream&, PullIntoDescriptor const&);
@ -118,7 +106,6 @@ void readable_byte_stream_controller_handle_queue_drain(ReadableByteStreamContro
void readable_byte_stream_controller_invalidate_byob_request(ReadableByteStreamController&);
bool readable_byte_stream_controller_should_call_pull(ReadableByteStreamController const&);
WebIDL::ExceptionOr<void> set_up_readable_stream(JS::Realm& realm, ReadableStream& stream, GC::Ref<StartAlgorithm> start_algorithm, GC::Ref<PullAlgorithm> pull_algorithm, GC::Ref<CancelAlgorithm> cancel_algorithm, Optional<double> high_water_mark = {}, GC::Ptr<SizeAlgorithm> size_algorithm = {});
WebIDL::ExceptionOr<GC::Ref<ReadableStream>> create_readable_stream(JS::Realm& realm, GC::Ref<StartAlgorithm> start_algorithm, GC::Ref<PullAlgorithm> pull_algorithm, GC::Ref<CancelAlgorithm> cancel_algorithm, Optional<double> high_water_mark = {}, GC::Ptr<SizeAlgorithm> size_algorithm = {});
WebIDL::ExceptionOr<GC::Ref<ReadableStream>> create_readable_byte_stream(JS::Realm& realm, GC::Ref<StartAlgorithm> start_algorithm, GC::Ref<PullAlgorithm> pull_algorithm, GC::Ref<CancelAlgorithm> cancel_algorithm);
WebIDL::ExceptionOr<GC::Ref<WritableStream>> create_writable_stream(JS::Realm& realm, GC::Ref<StartAlgorithm> start_algorithm, GC::Ref<WriteAlgorithm> write_algorithm, GC::Ref<CloseAlgorithm> close_algorithm, GC::Ref<AbortAlgorithm> abort_algorithm, double high_water_mark, GC::Ref<SizeAlgorithm> size_algorithm);
@ -184,7 +171,6 @@ GC::Ref<WebIDL::Promise> transform_stream_default_source_cancel_algorithm(Transf
void transform_stream_error(TransformStream&, JS::Value error);
void transform_stream_error_writable_and_unblock_write(TransformStream&, JS::Value error);
void transform_stream_set_backpressure(TransformStream&, bool backpressure);
void transform_stream_set_up(TransformStream&, GC::Ref<TransformAlgorithm>, GC::Ptr<FlushAlgorithm> = {}, GC::Ptr<CancelAlgorithm> = {});
void transform_stream_unblock_write(TransformStream&);
bool is_non_negative_number(JS::Value);
@ -193,10 +179,6 @@ bool can_transfer_array_buffer(JS::ArrayBuffer const& array_buffer);
WebIDL::ExceptionOr<JS::Value> clone_as_uint8_array(JS::Realm&, WebIDL::ArrayBufferView&);
WebIDL::ExceptionOr<JS::Value> structured_clone(JS::Realm&, JS::Value value);
JS::Value create_close_sentinel();
bool is_close_sentinel(JS::Value);
JS::ThrowCompletionOr<GC::Root<WebIDL::CallbackType>> property_to_callback(JS::VM& vm, JS::Value value, JS::PropertyKey const& property_key, WebIDL::OperationReturnsPromise);
// https://streams.spec.whatwg.org/#value-with-size
struct ValueWithSize {
JS::Value value;

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibGC/Function.h>
#include <LibGC/Ptr.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
#include <LibWeb/WebIDL/Promise.h>
namespace Web::Streams {
using SizeAlgorithm = GC::Function<JS::Completion(JS::Value)>;
using PullAlgorithm = GC::Function<GC::Ref<WebIDL::Promise>()>;
using CancelAlgorithm = GC::Function<GC::Ref<WebIDL::Promise>(JS::Value)>;
using StartAlgorithm = GC::Function<WebIDL::ExceptionOr<JS::Value>()>;
using AbortAlgorithm = GC::Function<GC::Ref<WebIDL::Promise>(JS::Value)>;
using CloseAlgorithm = GC::Function<GC::Ref<WebIDL::Promise>()>;
using WriteAlgorithm = GC::Function<GC::Ref<WebIDL::Promise>(JS::Value)>;
using FlushAlgorithm = GC::Function<GC::Ref<WebIDL::Promise>()>;
using TransformAlgorithm = GC::Function<GC::Ref<WebIDL::Promise>(JS::Value)>;
}

View file

@ -7,6 +7,7 @@
*/
#include <LibJS/Runtime/PromiseCapability.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/ReadableStreamPrototype.h>
#include <LibWeb/DOM/AbortSignal.h>
@ -258,4 +259,128 @@ bool ReadableStream::is_disturbed() const
return m_disturbed;
}
// https://streams.spec.whatwg.org/#readablestream-pull-from-bytes
WebIDL::ExceptionOr<void> ReadableStream::pull_from_bytes(ByteBuffer bytes)
{
auto& realm = this->realm();
// 1. Assert: stream.[[controller]] implements ReadableByteStreamController.
auto& controller = this->controller()->get<GC::Ref<ReadableByteStreamController>>();
// 2. Let available be bytess length.
auto available = bytes.size();
// 3. Let desiredSize be available.
auto desired_size = available;
// FIXME: 4. If streams current BYOB request view is non-null, then set desiredSize to streams current BYOB request
// view's byte length.
// 5. Let pullSize be the smaller value of available and desiredSize.
auto pull_size = min(available, desired_size);
// 6. Let pulled be the first pullSize bytes of bytes.
auto pulled = pull_size == available ? move(bytes) : MUST(bytes.slice(0, pull_size));
// 7. Remove the first pullSize bytes from bytes.
if (pull_size != available)
bytes = MUST(bytes.slice(pull_size, available - pull_size));
// FIXME: 8. If streams current BYOB request view is non-null, then:
// 1. Write pulled into streams current BYOB request view.
// 2. Perform ? ReadableByteStreamControllerRespond(stream.[[controller]], pullSize).
// 9. Otherwise,
{
// 1. Set view to the result of creating a Uint8Array from pulled in streams relevant Realm.
auto array_buffer = JS::ArrayBuffer::create(realm, move(pulled));
auto view = JS::Uint8Array::create(realm, array_buffer->byte_length(), *array_buffer);
// 2. Perform ? ReadableByteStreamControllerEnqueue(stream.[[controller]], view).
TRY(readable_byte_stream_controller_enqueue(controller, view));
}
return {};
}
// https://streams.spec.whatwg.org/#readablestream-enqueue
WebIDL::ExceptionOr<void> ReadableStream::enqueue(JS::Value chunk)
{
VERIFY(m_controller.has_value());
// 1. If stream.[[controller]] implements ReadableStreamDefaultController,
if (m_controller->has<GC::Ref<ReadableStreamDefaultController>>()) {
// 1. Perform ! ReadableStreamDefaultControllerEnqueue(stream.[[controller]], chunk).
return readable_stream_default_controller_enqueue(m_controller->get<GC::Ref<ReadableStreamDefaultController>>(), chunk);
}
// 2. Otherwise,
else {
// 1. Assert: stream.[[controller]] implements ReadableByteStreamController.
VERIFY(m_controller->has<GC::Ref<ReadableByteStreamController>>());
auto readable_byte_controller = m_controller->get<GC::Ref<ReadableByteStreamController>>();
// FIXME: 2. Assert: chunk is an ArrayBufferView.
// 3. Let byobView be the current BYOB request view for stream.
// FIXME: This is not what the spec means by 'current BYOB request view'
auto byob_view = readable_byte_controller->raw_byob_request();
// 4. If byobView is non-null, and chunk.[[ViewedArrayBuffer]] is byobView.[[ViewedArrayBuffer]], then:
if (byob_view) {
// FIXME: 1. Assert: chunk.[[ByteOffset]] is byobView.[[ByteOffset]].
// FIXME: 2. Assert: chunk.[[ByteLength]] ≤ byobView.[[ByteLength]].
// FIXME: 3. Perform ? ReadableByteStreamControllerRespond(stream.[[controller]], chunk.[[ByteLength]]).
TODO();
}
// 5. Otherwise, perform ? ReadableByteStreamControllerEnqueue(stream.[[controller]], chunk).
return readable_byte_stream_controller_enqueue(readable_byte_controller, chunk);
}
}
// https://streams.spec.whatwg.org/#readablestream-set-up-with-byte-reading-support
void ReadableStream::set_up_with_byte_reading_support(GC::Ptr<PullAlgorithm> pull_algorithm, GC::Ptr<CancelAlgorithm> cancel_algorithm, double high_water_mark)
{
auto& realm = this->realm();
// 1. Let startAlgorithm be an algorithm that returns undefined.
auto start_algorithm = GC::create_function(realm.heap(), []() -> WebIDL::ExceptionOr<JS::Value> { return JS::js_undefined(); });
// 2. Let pullAlgorithmWrapper be an algorithm that runs these steps:
auto pull_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, pull_algorithm]() {
// 1. Let result be the result of running pullAlgorithm, if pullAlgorithm was given, or null otherwise. If this throws an exception e, return a promise rejected with e.
GC::Ptr<JS::PromiseCapability> result = nullptr;
if (pull_algorithm)
result = pull_algorithm->function()();
// 2. If result is a Promise, then return result.
if (result != nullptr)
return GC::Ref(*result);
// 3. Return a promise resolved with undefined.
return WebIDL::create_resolved_promise(realm, JS::js_undefined());
});
// 3. Let cancelAlgorithmWrapper be an algorithm that runs these steps:
auto cancel_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, cancel_algorithm](JS::Value c) {
// 1. Let result be the result of running cancelAlgorithm, if cancelAlgorithm was given, or null otherwise. If this throws an exception e, return a promise rejected with e.
GC::Ptr<JS::PromiseCapability> result = nullptr;
if (cancel_algorithm)
result = cancel_algorithm->function()(c);
// 2. If result is a Promise, then return result.
if (result != nullptr)
return GC::Ref(*result);
// 3. Return a promise resolved with undefined.
return WebIDL::create_resolved_promise(realm, JS::js_undefined());
});
// 4. Perform ! InitializeReadableStream(stream).
// 5. Let controller be a new ReadableByteStreamController.
auto controller = realm.create<ReadableByteStreamController>(realm);
// 6. Perform ! SetUpReadableByteStreamController(stream, controller, startAlgorithm, pullAlgorithmWrapper, cancelAlgorithmWrapper, highWaterMark, undefined).
MUST(set_up_readable_byte_stream_controller(*this, controller, start_algorithm, pull_algorithm_wrapper, cancel_algorithm_wrapper, high_water_mark, JS::js_undefined()));
}
}

View file

@ -12,6 +12,7 @@
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Bindings/ReadableStreamPrototype.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Streams/Algorithms.h>
#include <LibWeb/Streams/QueuingStrategy.h>
namespace Web::Streams {
@ -104,6 +105,10 @@ public:
State state() const { return m_state; }
void set_state(State value) { m_state = value; }
WebIDL::ExceptionOr<void> pull_from_bytes(ByteBuffer);
WebIDL::ExceptionOr<void> enqueue(JS::Value chunk);
void set_up_with_byte_reading_support(GC::Ptr<PullAlgorithm> = {}, GC::Ptr<CancelAlgorithm> = {}, double high_water_mark = 0);
private:
explicit ReadableStream(JS::Realm&);

View file

@ -74,6 +74,84 @@ WebIDL::ExceptionOr<GC::Ref<TransformStream>> TransformStream::construct_impl(JS
return stream;
}
// https://streams.spec.whatwg.org/#transformstream-set-up
void TransformStream::set_up(GC::Ref<TransformAlgorithm> transform_algorithm, GC::Ptr<FlushAlgorithm> flush_algorithm, GC::Ptr<CancelAlgorithm> cancel_algorithm)
{
auto& realm = this->realm();
// 1. Let writableHighWaterMark be 1.
auto writable_high_water_mark = 1.0;
// 2. Let writableSizeAlgorithm be an algorithm that returns 1.
auto writable_size_algorithm = GC::create_function(realm.heap(), [](JS::Value) {
return JS::normal_completion(JS::Value { 1 });
});
// 3. Let readableHighWaterMark be 0.
auto readable_high_water_mark = 0.0;
// 4. Let readableSizeAlgorithm be an algorithm that returns 1.
auto readable_size_algorithm = GC::create_function(realm.heap(), [](JS::Value) {
return JS::normal_completion(JS::Value { 1 });
});
// 5. Let transformAlgorithmWrapper be an algorithm that runs these steps given a value chunk:
auto transform_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, transform_algorithm](JS::Value chunk) -> GC::Ref<WebIDL::Promise> {
// 1. Let result be the result of running transformAlgorithm given chunk. If this throws an exception e, return a promise rejected with e.
GC::Ptr<JS::PromiseCapability> result = nullptr;
result = transform_algorithm->function()(chunk);
// 2. If result is a Promise, then return result.
if (result)
return GC::Ref { *result };
// 3. Return a promise resolved with undefined.
return WebIDL::create_resolved_promise(realm, JS::js_undefined());
});
// 6. Let flushAlgorithmWrapper be an algorithm that runs these steps:
auto flush_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, flush_algorithm]() -> GC::Ref<WebIDL::Promise> {
// 1. Let result be the result of running flushAlgorithm, if flushAlgorithm was given, or null otherwise. If this throws an exception e, return a promise rejected with e.
GC::Ptr<JS::PromiseCapability> result = nullptr;
if (flush_algorithm)
result = flush_algorithm->function()();
// 2. If result is a Promise, then return result.
if (result)
return GC::Ref { *result };
// 3. Return a promise resolved with undefined.
return WebIDL::create_resolved_promise(realm, JS::js_undefined());
});
// 7. Let cancelAlgorithmWrapper be an algorithm that runs these steps given a value reason:
auto cancel_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, cancel_algorithm](JS::Value reason) -> GC::Ref<WebIDL::Promise> {
// 1. Let result be the result of running cancelAlgorithm given reason, if cancelAlgorithm was given, or null otherwise. If this throws an exception e, return a promise rejected with e.
GC::Ptr<JS::PromiseCapability> result = nullptr;
if (cancel_algorithm)
result = cancel_algorithm->function()(reason);
// 2. If result is a Promise, then return result.
if (result)
return GC::Ref { *result };
// 3. Return a promise resolved with undefined.
return WebIDL::create_resolved_promise(realm, JS::js_undefined());
});
// 8. Let startPromise be a promise resolved with undefined.
auto start_promise = WebIDL::create_resolved_promise(realm, JS::js_undefined());
// 9. Perform ! InitializeTransformStream(stream, startPromise, writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm).
initialize_transform_stream(*this, start_promise, writable_high_water_mark, writable_size_algorithm, readable_high_water_mark, readable_size_algorithm);
// 10. Let controller be a new TransformStreamDefaultController.
auto controller = realm.create<TransformStreamDefaultController>(realm);
// 11. Perform ! SetUpTransformStreamDefaultController(stream, controller, transformAlgorithmWrapper, flushAlgorithmWrapper, cancelAlgorithmWrapper).
set_up_transform_stream_default_controller(*this, controller, transform_algorithm_wrapper, flush_algorithm_wrapper, cancel_algorithm_wrapper);
}
TransformStream::TransformStream(JS::Realm& realm)
: Bindings::PlatformObject(realm)
{

View file

@ -9,6 +9,7 @@
#include <LibJS/Forward.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Streams/Algorithms.h>
#include <LibWeb/Streams/QueuingStrategy.h>
#include <LibWeb/WebIDL/Promise.h>
@ -40,6 +41,8 @@ public:
GC::Ptr<TransformStreamDefaultController> controller() const { return m_controller; }
void set_controller(GC::Ptr<TransformStreamDefaultController> value) { m_controller = value; }
void set_up(GC::Ref<TransformAlgorithm>, GC::Ptr<FlushAlgorithm> = {}, GC::Ptr<CancelAlgorithm> = {});
private:
explicit TransformStream(JS::Realm& realm);

View file

@ -19,10 +19,10 @@ JS::ThrowCompletionOr<Transformer> Transformer::from_value(JS::VM& vm, JS::Value
auto& object = value.as_object();
Transformer transformer {
.start = TRY(property_to_callback(vm, value, "start", WebIDL::OperationReturnsPromise::No)),
.transform = TRY(property_to_callback(vm, value, "transform", WebIDL::OperationReturnsPromise::Yes)),
.flush = TRY(property_to_callback(vm, value, "flush", WebIDL::OperationReturnsPromise::Yes)),
.cancel = TRY(property_to_callback(vm, value, "cancel", WebIDL::OperationReturnsPromise::Yes)),
.start = TRY(WebIDL::property_to_callback(vm, value, "start", WebIDL::OperationReturnsPromise::No)),
.transform = TRY(WebIDL::property_to_callback(vm, value, "transform", WebIDL::OperationReturnsPromise::Yes)),
.flush = TRY(WebIDL::property_to_callback(vm, value, "flush", WebIDL::OperationReturnsPromise::Yes)),
.cancel = TRY(WebIDL::property_to_callback(vm, value, "cancel", WebIDL::OperationReturnsPromise::Yes)),
.readable_type = {},
.writable_type = {},
};

View file

@ -19,10 +19,10 @@ JS::ThrowCompletionOr<UnderlyingSink> UnderlyingSink::from_value(JS::VM& vm, JS:
auto& object = value.as_object();
UnderlyingSink underlying_sink {
.start = TRY(property_to_callback(vm, value, "start", WebIDL::OperationReturnsPromise::No)),
.write = TRY(property_to_callback(vm, value, "write", WebIDL::OperationReturnsPromise::Yes)),
.close = TRY(property_to_callback(vm, value, "close", WebIDL::OperationReturnsPromise::Yes)),
.abort = TRY(property_to_callback(vm, value, "abort", WebIDL::OperationReturnsPromise::Yes)),
.start = TRY(WebIDL::property_to_callback(vm, value, "start", WebIDL::OperationReturnsPromise::No)),
.write = TRY(WebIDL::property_to_callback(vm, value, "write", WebIDL::OperationReturnsPromise::Yes)),
.close = TRY(WebIDL::property_to_callback(vm, value, "close", WebIDL::OperationReturnsPromise::Yes)),
.abort = TRY(WebIDL::property_to_callback(vm, value, "abort", WebIDL::OperationReturnsPromise::Yes)),
.type = {},
};

View file

@ -22,9 +22,9 @@ JS::ThrowCompletionOr<UnderlyingSource> UnderlyingSource::from_value(JS::VM& vm,
auto& object = value.as_object();
UnderlyingSource underlying_source {
.start = TRY(property_to_callback(vm, value, "start", WebIDL::OperationReturnsPromise::No)),
.pull = TRY(property_to_callback(vm, value, "pull", WebIDL::OperationReturnsPromise::Yes)),
.cancel = TRY(property_to_callback(vm, value, "cancel", WebIDL::OperationReturnsPromise::Yes)),
.start = TRY(WebIDL::property_to_callback(vm, value, "start", WebIDL::OperationReturnsPromise::No)),
.pull = TRY(WebIDL::property_to_callback(vm, value, "pull", WebIDL::OperationReturnsPromise::Yes)),
.cancel = TRY(WebIDL::property_to_callback(vm, value, "cancel", WebIDL::OperationReturnsPromise::Yes)),
.type = {},
.auto_allocate_chunk_size = {},
};

View file

@ -5,6 +5,7 @@
*/
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/VM.h>
#include <LibWeb/HTML/Scripting/Environments.h>
#include <LibWeb/WebIDL/CallbackType.h>
@ -26,4 +27,19 @@ void CallbackType::visit_edges(Cell::Visitor& visitor)
visitor.visit(callback_context);
}
// Non-standard function to aid in converting a user-provided function into a WebIDL::Callback. This is essentially
// what the Bindings generator would do at compile time, but at runtime instead.
JS::ThrowCompletionOr<GC::Root<CallbackType>> property_to_callback(JS::VM& vm, JS::Value value, JS::PropertyKey const& property_key, OperationReturnsPromise operation_returns_promise)
{
auto property = TRY(value.get(vm, property_key));
if (property.is_undefined())
return GC::Root<CallbackType> {};
if (!property.is_function())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAFunction, property.to_string_without_side_effects());
return vm.heap().allocate<CallbackType>(property.as_object(), HTML::incumbent_realm(), operation_returns_promise);
}
}

View file

@ -39,4 +39,6 @@ private:
virtual void visit_edges(Cell::Visitor&) override;
};
JS::ThrowCompletionOr<GC::Root<WebIDL::CallbackType>> property_to_callback(JS::VM& vm, JS::Value value, JS::PropertyKey const& property_key, WebIDL::OperationReturnsPromise);
}

View file

@ -39,14 +39,12 @@ Optional<URL::URL> sanitize_url(StringView url, Optional<StringView> search_engi
}
ByteString url_with_scheme = url;
if (!(url_with_scheme.starts_with("about:"sv) || url_with_scheme.contains("://"sv) || url_with_scheme.starts_with("data:"sv)))
if (!(url_with_scheme.starts_with("about:"sv) || url_with_scheme.contains("://"sv) || url_with_scheme.starts_with("data:"sv) || url_with_scheme.contains("."sv)) {
url_with_scheme = ByteString::formatted("https://{}"sv, url_with_scheme);
auto result = URL::create_with_url_or_path(url_with_scheme);
if (!result.is_valid())
return format_search_engine();
return result;
return URL::create_with_url_or_path(url_with_scheme);
}
return format_search_engine();;
}
Vector<URL::URL> sanitize_urls(ReadonlySpan<ByteString> raw_urls, URL::URL const& new_tab_page_url)

View file

@ -0,0 +1 @@
%26%2319973%3B

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="iso-2022-jp">
<script src="../include.js"></script>
<script>
test(() => {
const input = "\u4E05"; // 丅
const a = document.createElement("a");
a.href = "https://ladybird.org/?" + input;
println(a.search.substr(1));
})
</script>
</head>
<body></body>
</html>