diff --git a/Userland/Libraries/LibWeb/FileAPI/Blob.cpp b/Userland/Libraries/LibWeb/FileAPI/Blob.cpp index f3802584510..e894318370b 100644 --- a/Userland/Libraries/LibWeb/FileAPI/Blob.cpp +++ b/Userland/Libraries/LibWeb/FileAPI/Blob.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2022-2023, Kenneth Myhra + * Copyright (c) 2023, Shannon Booth * * SPDX-License-Identifier: BSD-2-Clause */ @@ -7,11 +8,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include namespace Web::FileAPI { @@ -249,6 +252,63 @@ WebIDL::ExceptionOr> Blob::slice(Optional start, Opt return MUST_OR_THROW_OOM(heap().allocate(realm(), realm(), move(byte_buffer), move(relative_content_type))); } +// https://w3c.github.io/FileAPI/#blob-get-stream +WebIDL::ExceptionOr> Blob::get_stream() +{ + auto& realm = this->realm(); + + // 1. Let stream be a new ReadableStream created in blob’s relevant Realm. + auto stream = MUST_OR_THROW_OOM(realm.heap().allocate(realm, realm)); + + // 2. Set up stream with byte reading support. + TRY(set_up_readable_stream_controller_with_byte_reading_support(stream)); + + // FIXME: 3. Run the following steps in parallel: + { + // 1. While not all bytes of blob have been read: + // NOTE: for simplicity the chunk is the entire buffer for now. + { + // 1. Let bytes be the byte sequence that results from reading a chunk from blob, or failure if a chunk cannot be read. + auto bytes = m_byte_buffer; + + // 2. Queue a global task on the file reading task source given blob’s relevant global object to perform the following steps: + HTML::queue_global_task(HTML::Task::Source::FileReading, realm.global_object(), [stream, bytes = move(bytes)]() { + // NOTE: Not part of the spec, but we need to have an execution context on the stack to call native functions. + auto& environment_settings_object = Bindings::host_defined_environment_settings_object(stream->realm()); + environment_settings_object.prepare_to_run_script(); + + ScopeGuard guard = [&]() { + // See above NOTE. + environment_settings_object.clean_up_after_running_script(); + }; + + // 1. If bytes is failure, then error stream with a failure reason and abort these steps. + // 2. Let chunk be a new Uint8Array wrapping an ArrayBuffer containing bytes. If creating the ArrayBuffer throws an exception, then error stream with that exception and abort these steps. + auto array_buffer = JS::ArrayBuffer::create(stream->realm(), bytes); + auto chunk = JS::Uint8Array::create(stream->realm(), bytes.size(), *array_buffer); + + // 3. Enqueue chunk in stream. + auto maybe_error = Bindings::throw_dom_exception_if_needed(stream->realm().vm(), [&]() { + return readable_stream_enqueue(*stream->controller(), chunk); + }); + + if (maybe_error.is_error()) { + readable_stream_error(*stream, maybe_error.release_error().value().value()); + return; + } + + // FIXME: Close the stream now that we have finished enqueuing all chunks to the stream. Without this, ReadableStream.read will never resolve the second time around with 'done' set. + // Nowhere in the spec seems to mention this - but testing against other implementations the stream does appear to be closed after reading all data (closed callback is fired). + // Probably there is a better way of doing this. + readable_stream_close(*stream); + }); + } + } + + // 4. Return stream. + return stream; +} + // https://w3c.github.io/FileAPI/#dom-blob-text WebIDL::ExceptionOr> Blob::text() { diff --git a/Userland/Libraries/LibWeb/FileAPI/Blob.h b/Userland/Libraries/LibWeb/FileAPI/Blob.h index 0719b87c304..33cadebce8a 100644 --- a/Userland/Libraries/LibWeb/FileAPI/Blob.h +++ b/Userland/Libraries/LibWeb/FileAPI/Blob.h @@ -55,6 +55,8 @@ protected: virtual JS::ThrowCompletionOr initialize(JS::Realm&) override; private: + WebIDL::ExceptionOr> get_stream(); + explicit Blob(JS::Realm&); ByteBuffer m_byte_buffer {};