From bbcd8bd97ca5b3bbf93a6c5183b89bd887c67e54 Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Sat, 22 Feb 2025 07:15:40 +0000 Subject: [PATCH] LibWeb: Implement `FileReaderSync` interface This interface allows the user to read File or Blob objects synchronously from inside workers. --- Libraries/LibWeb/CMakeLists.txt | 1 + Libraries/LibWeb/FileAPI/FileReader.h | 17 ++-- Libraries/LibWeb/FileAPI/FileReaderSync.cpp | 106 ++++++++++++++++++++ Libraries/LibWeb/FileAPI/FileReaderSync.h | 41 ++++++++ Libraries/LibWeb/FileAPI/FileReaderSync.idl | 13 +++ Libraries/LibWeb/idl_files.cmake | 1 + 6 files changed, 170 insertions(+), 9 deletions(-) create mode 100644 Libraries/LibWeb/FileAPI/FileReaderSync.cpp create mode 100644 Libraries/LibWeb/FileAPI/FileReaderSync.h create mode 100644 Libraries/LibWeb/FileAPI/FileReaderSync.idl diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index d50d0fe08a1..d740c8b0635 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -284,6 +284,7 @@ set(SOURCES FileAPI/File.cpp FileAPI/FileList.cpp FileAPI/FileReader.cpp + FileAPI/FileReaderSync.cpp Geometry/DOMMatrix.cpp Geometry/DOMMatrixReadOnly.cpp Geometry/DOMPoint.cpp diff --git a/Libraries/LibWeb/FileAPI/FileReader.h b/Libraries/LibWeb/FileAPI/FileReader.h index 39e7b742c80..b5813cca545 100644 --- a/Libraries/LibWeb/FileAPI/FileReader.h +++ b/Libraries/LibWeb/FileAPI/FileReader.h @@ -81,6 +81,14 @@ public: void set_onloadend(WebIDL::CallbackType*); WebIDL::CallbackType* onloadend(); + enum class Type { + ArrayBuffer, + BinaryString, + Text, + DataURL, + }; + static WebIDL::ExceptionOr blob_package_data(JS::Realm& realm, ByteBuffer, FileReader::Type type, Optional const&, Optional const& encoding_name = {}); + protected: FileReader(JS::Realm&, ByteBuffer); @@ -91,17 +99,8 @@ protected: private: explicit FileReader(JS::Realm&); - enum class Type { - ArrayBuffer, - BinaryString, - Text, - DataURL, - }; - WebIDL::ExceptionOr read_operation(Blob&, Type, Optional const& encoding_name = {}); - static WebIDL::ExceptionOr blob_package_data(JS::Realm& realm, ByteBuffer, FileReader::Type type, Optional const&, Optional const& encoding_name); - void queue_a_task(GC::Ref>); // Internal state to handle aborting the FileReader. diff --git a/Libraries/LibWeb/FileAPI/FileReaderSync.cpp b/Libraries/LibWeb/FileAPI/FileReaderSync.cpp new file mode 100644 index 00000000000..dce69c81df6 --- /dev/null +++ b/Libraries/LibWeb/FileAPI/FileReaderSync.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2025, Tim Ledbetter + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Web::FileAPI { + +GC_DEFINE_ALLOCATOR(FileReaderSync); + +FileReaderSync::~FileReaderSync() = default; + +FileReaderSync::FileReaderSync(JS::Realm& realm) + : PlatformObject(realm) +{ +} + +void FileReaderSync::initialize(JS::Realm& realm) +{ + Base::initialize(realm); + WEB_SET_PROTOTYPE_FOR_INTERFACE(FileReaderSync); +} + +GC::Ref FileReaderSync::create(JS::Realm& realm) +{ + return realm.create(realm); +} + +GC::Ref FileReaderSync::construct_impl(JS::Realm& realm) +{ + return FileReaderSync::create(realm); +} + +// https://w3c.github.io/FileAPI/#dfn-readAsArrayBufferSync +WebIDL::ExceptionOr> FileReaderSync::read_as_array_buffer(Blob& blob) +{ + return read_as>(blob, FileReader::Type::ArrayBuffer); +} + +// https://w3c.github.io/FileAPI/#dfn-readAsBinaryStringSync +WebIDL::ExceptionOr FileReaderSync::read_as_binary_string(Blob& blob) +{ + return read_as(blob, FileReader::Type::BinaryString); +} + +// https://w3c.github.io/FileAPI/#dfn-readAsTextSync +WebIDL::ExceptionOr FileReaderSync::read_as_text(Blob& blob, Optional const& encoding) +{ + return read_as(blob, FileReader::Type::Text, encoding); +} + +// https://w3c.github.io/FileAPI/#dfn-readAsDataURLSync +WebIDL::ExceptionOr FileReaderSync::read_as_data_url(Blob& blob) +{ + return read_as(blob, FileReader::Type::DataURL); +} + +template +WebIDL::ExceptionOr FileReaderSync::read_as(Blob& blob, FileReader::Type type, Optional const& encoding) +{ + // 1. Let stream be the result of calling get stream on blob. + auto stream = blob.get_stream(); + + // 2. Let reader be the result of getting a reader from stream. + auto reader = TRY(stream->get_a_reader()); + + // 3. Let promise be the result of reading all bytes from stream with reader. + auto promise_capability = reader->read_all_bytes_deprecated(); + + // FIXME: Try harder to not reach into promise's [[Promise]] slot + auto promise = GC::Ref { as(*promise_capability->promise()) }; + + // 4. Wait for promise to be fulfilled or rejected. + // FIXME: Create spec issue to use WebIDL react to promise steps here instead of this custom logic + HTML::main_thread_event_loop().spin_until(GC::create_function(heap(), [promise]() { + return promise->state() == JS::Promise::State::Fulfilled || promise->state() == JS::Promise::State::Rejected; + })); + + // 5. If promise fulfilled with a byte sequence bytes: + auto result = promise->result(); + auto* array_buffer = result.extract_pointer(); + if (promise->state() == JS::Promise::State::Fulfilled && array_buffer) { + // AD-HOC: This diverges from the spec as wrritten, where the type argument is specified explicitly for each caller. + // 1. Return the result of package data given bytes, type, blob’s type, and encoding. + auto result = TRY(FileReader::blob_package_data(realm(), array_buffer->buffer(), type, blob.type(), encoding)); + return result.get(); + } + + // 6. Throw promise’s rejection reason. + VERIFY(promise->state() == JS::Promise::State::Rejected); + return JS::throw_completion(result); +} + +} diff --git a/Libraries/LibWeb/FileAPI/FileReaderSync.h b/Libraries/LibWeb/FileAPI/FileReaderSync.h new file mode 100644 index 00000000000..79bb7744282 --- /dev/null +++ b/Libraries/LibWeb/FileAPI/FileReaderSync.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025, Tim Ledbetter + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Web::FileAPI { + +// https://w3c.github.io/FileAPI/#FileReaderSync +class FileReaderSync : public Bindings::PlatformObject { + WEB_PLATFORM_OBJECT(FileReaderSync, Bindings::PlatformObject); + GC_DECLARE_ALLOCATOR(FileReaderSync); + +public: + virtual ~FileReaderSync() override; + + [[nodiscard]] static GC::Ref create(JS::Realm&); + static GC::Ref construct_impl(JS::Realm&); + + WebIDL::ExceptionOr> read_as_array_buffer(Blob&); + WebIDL::ExceptionOr read_as_binary_string(Blob&); + WebIDL::ExceptionOr read_as_text(Blob&, Optional const& encoding = {}); + WebIDL::ExceptionOr read_as_data_url(Blob&); + +private: + explicit FileReaderSync(JS::Realm&); + + template + WebIDL::ExceptionOr read_as(Blob&, FileReader::Type, Optional const& encoding = {}); + + virtual void initialize(JS::Realm&) override; +}; + +} diff --git a/Libraries/LibWeb/FileAPI/FileReaderSync.idl b/Libraries/LibWeb/FileAPI/FileReaderSync.idl new file mode 100644 index 00000000000..3308b9d436a --- /dev/null +++ b/Libraries/LibWeb/FileAPI/FileReaderSync.idl @@ -0,0 +1,13 @@ +#import + +// https://w3c.github.io/FileAPI/#FileReaderSync +[Exposed=(DedicatedWorker,SharedWorker)] +interface FileReaderSync { + constructor(); + // Synchronously return strings + + ArrayBuffer readAsArrayBuffer(Blob blob); + DOMString readAsText(Blob blob, optional DOMString encoding); + DOMString readAsBinaryString(Blob blob); + DOMString readAsDataURL(Blob blob); +}; diff --git a/Libraries/LibWeb/idl_files.cmake b/Libraries/LibWeb/idl_files.cmake index 6cc31f28aa7..65c596f03ad 100644 --- a/Libraries/LibWeb/idl_files.cmake +++ b/Libraries/LibWeb/idl_files.cmake @@ -98,6 +98,7 @@ libweb_js_bindings(FileAPI/Blob) libweb_js_bindings(FileAPI/File) libweb_js_bindings(FileAPI/FileList) libweb_js_bindings(FileAPI/FileReader) +libweb_js_bindings(FileAPI/FileReaderSync) libweb_js_bindings(Geometry/DOMMatrix) libweb_js_bindings(Geometry/DOMMatrixReadOnly) libweb_js_bindings(Geometry/DOMPoint)