diff --git a/Meta/Lagom/Tools/CodeGenerators/IPCCompiler/main.cpp b/Meta/Lagom/Tools/CodeGenerators/IPCCompiler/main.cpp
index 88b8a2003b..b3f6734624 100644
--- a/Meta/Lagom/Tools/CodeGenerators/IPCCompiler/main.cpp
+++ b/Meta/Lagom/Tools/CodeGenerators/IPCCompiler/main.cpp
@@ -70,7 +70,7 @@ static bool is_primitive_type(ByteString const& type)
static bool is_simple_type(ByteString const& type)
{
// Small types that it makes sense just to pass by value.
- return type.is_one_of("Gfx::Color", "Web::DevicePixels", "Gfx::IntPoint", "Gfx::FloatPoint", "Web::DevicePixelPoint", "Gfx::IntSize", "Gfx::FloatSize", "Web::DevicePixelSize", "Core::File::OpenMode", "Web::Cookie::Source");
+ return type.is_one_of("Gfx::Color", "Web::DevicePixels", "Gfx::IntPoint", "Gfx::FloatPoint", "Web::DevicePixelPoint", "Gfx::IntSize", "Gfx::FloatSize", "Web::DevicePixelSize", "Core::File::OpenMode", "Web::Cookie::Source", "Web::HTML::AllowMultipleFiles");
}
static bool is_primitive_or_simple_type(ByteString const& type)
diff --git a/Meta/gn/secondary/Userland/Libraries/LibWeb/HTML/BUILD.gn b/Meta/gn/secondary/Userland/Libraries/LibWeb/HTML/BUILD.gn
index 920adbabcf..84c30ce754 100644
--- a/Meta/gn/secondary/Userland/Libraries/LibWeb/HTML/BUILD.gn
+++ b/Meta/gn/secondary/Userland/Libraries/LibWeb/HTML/BUILD.gn
@@ -140,6 +140,7 @@ source_set("HTML") {
"PotentialCORSRequest.cpp",
"PromiseRejectionEvent.cpp",
"SelectItem.cpp",
+ "SelectedFile.cpp",
"SessionHistoryEntry.cpp",
"SharedImageRequest.cpp",
"SourceSet.cpp",
diff --git a/Tests/LibWeb/Text/expected/input-file.txt b/Tests/LibWeb/Text/expected/input-file.txt
new file mode 100644
index 0000000000..6e3a94c410
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/input-file.txt
@@ -0,0 +1,7 @@
+Select file...file1 Select files...4 files selected. input1:
+file1: Contents for file1
+input2:
+file1: Contents for file1
+file2: Contents for file2
+file3: Contents for file3
+file4: Contents for file4
diff --git a/Tests/LibWeb/Text/input/input-file.html b/Tests/LibWeb/Text/input/input-file.html
new file mode 100644
index 0000000000..0ee499460a
--- /dev/null
+++ b/Tests/LibWeb/Text/input/input-file.html
@@ -0,0 +1,32 @@
+
+
+
+
diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt
index e9b4b480fb..b9d51888b1 100644
--- a/Userland/Libraries/LibWeb/CMakeLists.txt
+++ b/Userland/Libraries/LibWeb/CMakeLists.txt
@@ -389,6 +389,7 @@ set(SOURCES
HTML/Scripting/TemporaryExecutionContext.cpp
HTML/Scripting/WindowEnvironmentSettingsObject.cpp
HTML/Scripting/WorkerEnvironmentSettingsObject.cpp
+ HTML/SelectedFile.cpp
HTML/SelectItem.cpp
HTML/SessionHistoryEntry.cpp
HTML/SharedImageRequest.cpp
diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h
index 4adba1b4ea..0406287bd1 100644
--- a/Userland/Libraries/LibWeb/Forward.h
+++ b/Userland/Libraries/LibWeb/Forward.h
@@ -445,6 +445,7 @@ class Path2D;
class Plugin;
class PluginArray;
class PromiseRejectionEvent;
+class SelectedFile;
class SharedImageRequest;
class Storage;
class SubmitEvent;
@@ -468,6 +469,7 @@ class WorkerGlobalScope;
class WorkerLocation;
class WorkerNavigator;
+enum class AllowMultipleFiles;
enum class MediaSeekMode;
enum class SandboxingFlagSet;
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp
index 51735c72c5..c0902bb29a 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp
+++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp
@@ -28,6 +28,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -37,6 +38,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -213,12 +215,12 @@ static void show_the_picker_if_applicable(HTMLInputElement& element)
// with the bubbles attribute initialized to true.
// 5. Otherwise, update the file selection for element.
- bool const multiple = element.has_attribute(HTML::AttributeNames::multiple);
- auto weak_element = element.make_weak_ptr();
+ auto allow_multiple_files = element.has_attribute(HTML::AttributeNames::multiple) ? AllowMultipleFiles::Yes : AllowMultipleFiles::No;
+ auto weak_element = element.make_weak_ptr();
// FIXME: Pass along accept attribute information https://html.spec.whatwg.org/multipage/input.html#attr-input-accept
// The accept attribute may be specified to provide user agents with a hint of what file types will be accepted.
- element.document().browsing_context()->top_level_browsing_context()->page().client().page_did_request_file_picker(weak_element, multiple);
+ element.document().browsing_context()->top_level_browsing_context()->page().did_request_file_picker(weak_element, allow_multiple_files);
return;
}
@@ -380,6 +382,51 @@ void HTMLInputElement::did_pick_color(Optional picked_color)
}
}
+void HTMLInputElement::did_select_files(Span selected_files)
+{
+ // https://html.spec.whatwg.org/multipage/input.html#show-the-picker,-if-applicable
+ // 4. If the user dismissed the prompt without changing their selection, then queue an element task on the user
+ // interaction task source given element to fire an event named cancel at element, with the bubbles attribute
+ // initialized to true.
+ if (selected_files.is_empty()) {
+ queue_an_element_task(HTML::Task::Source::UserInteraction, [this]() {
+ dispatch_event(DOM::Event::create(realm(), HTML::EventNames::cancel, { .bubbles = true }));
+ });
+
+ return;
+ }
+
+ Vector> files;
+ files.ensure_capacity(selected_files.size());
+
+ for (auto& selected_file : selected_files) {
+ auto contents = selected_file.take_contents();
+
+ auto mime_type = MUST(MimeSniff::Resource::sniff(contents));
+ auto blob = FileAPI::Blob::create(realm(), move(contents), mime_type.essence());
+
+ // FIXME: The FileAPI should use ByteString for file names.
+ auto file_name = MUST(String::from_byte_string(selected_file.name()));
+
+ auto file = MUST(FileAPI::File::create(realm(), { JS::make_handle(blob) }, file_name));
+ files.unchecked_append(file);
+ }
+
+ // https://html.spec.whatwg.org/multipage/input.html#update-the-file-selection
+ // 1. Queue an element task on the user interaction task source given element and the following steps:
+ queue_an_element_task(HTML::Task::Source::UserInteraction, [this, files = move(files)]() mutable {
+ // 1. Update element's selected files so that it represents the user's selection.
+ m_selected_files = FileAPI::FileList::create(realm(), move(files));
+ update_file_input_shadow_tree();
+
+ // 2. Fire an event named input at the input element, with the bubbles and composed attributes initialized to true.
+ dispatch_event(DOM::Event::create(realm(), HTML::EventNames::input, { .bubbles = true, .composed = true }));
+
+ // 3. Fire an event named change at the input element, with the bubbles attribute initialized to true.
+ dispatch_event(DOM::Event::create(realm(), HTML::EventNames::change, { .bubbles = true }));
+ });
+}
+
String HTMLInputElement::value() const
{
switch (value_attribute_mode()) {
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h
index 95c287e7d8..278d5d1532 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h
+++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h
@@ -94,6 +94,8 @@ public:
void did_pick_color(Optional picked_color);
+ void did_select_files(Span selected_files);
+
JS::GCPtr files();
void set_files(JS::GCPtr);
diff --git a/Userland/Libraries/LibWeb/HTML/SelectedFile.cpp b/Userland/Libraries/LibWeb/HTML/SelectedFile.cpp
new file mode 100644
index 0000000000..be31a4e1b3
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/SelectedFile.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2024, Tim Flynn
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+namespace Web::HTML {
+
+ErrorOr SelectedFile::from_file_path(ByteString const& file_path)
+{
+ // https://html.spec.whatwg.org/multipage/input.html#file-upload-state-(type=file):concept-input-file-path
+ // Filenames must not contain path components, even in the case that a user has selected an entire directory
+ // hierarchy or multiple files with the same name from different directories.
+ auto name = LexicalPath::basename(file_path);
+
+ auto file = TRY(Core::File::open(file_path, Core::File::OpenMode::Read));
+ return SelectedFile { move(name), IPC::File { *file } };
+}
+
+SelectedFile::SelectedFile(ByteString name, ByteBuffer contents)
+ : m_name(move(name))
+ , m_file_or_contents(move(contents))
+{
+}
+
+SelectedFile::SelectedFile(ByteString name, IPC::File file)
+ : m_name(move(name))
+ , m_file_or_contents(move(file))
+{
+}
+
+ByteBuffer SelectedFile::take_contents()
+{
+ VERIFY(m_file_or_contents.has());
+ return move(m_file_or_contents.get());
+}
+
+}
+
+template<>
+ErrorOr IPC::encode(Encoder& encoder, Web::HTML::SelectedFile const& file)
+{
+ TRY(encoder.encode(file.name()));
+ TRY(encoder.encode(file.file_or_contents()));
+ return {};
+}
+
+template<>
+ErrorOr IPC::decode(Decoder& decoder)
+{
+ auto name = TRY(decoder.decode());
+ auto file_or_contents = TRY((decoder.decode>()));
+
+ ByteBuffer contents;
+
+ if (file_or_contents.has()) {
+ auto file = TRY(Core::File::adopt_fd(file_or_contents.get().take_fd(), Core::File::OpenMode::Read));
+ contents = TRY(file->read_until_eof());
+ } else {
+ contents = move(file_or_contents.get());
+ }
+
+ return Web::HTML::SelectedFile { move(name), move(contents) };
+}
diff --git a/Userland/Libraries/LibWeb/HTML/SelectedFile.h b/Userland/Libraries/LibWeb/HTML/SelectedFile.h
new file mode 100644
index 0000000000..8d1739231d
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/SelectedFile.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2024, Tim Flynn
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+namespace Web::HTML {
+
+enum class AllowMultipleFiles {
+ No,
+ Yes,
+};
+
+class SelectedFile {
+public:
+ static ErrorOr from_file_path(ByteString const& file_path);
+
+ SelectedFile(ByteString name, ByteBuffer contents);
+ SelectedFile(ByteString name, IPC::File file);
+
+ ByteString const& name() const { return m_name; }
+ auto const& file_or_contents() const { return m_file_or_contents; }
+ ByteBuffer take_contents();
+
+private:
+ ByteString m_name;
+ Variant m_file_or_contents;
+};
+
+}
+
+namespace IPC {
+
+template<>
+ErrorOr encode(Encoder&, Web::HTML::SelectedFile const&);
+
+template<>
+ErrorOr decode(Decoder&);
+
+}
diff --git a/Userland/Libraries/LibWeb/Page/Page.cpp b/Userland/Libraries/LibWeb/Page/Page.cpp
index e6df976060..dfdce259f5 100644
--- a/Userland/Libraries/LibWeb/Page/Page.cpp
+++ b/Userland/Libraries/LibWeb/Page/Page.cpp
@@ -18,6 +18,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -343,6 +344,30 @@ void Page::color_picker_update(Optional picked_color, HTML::ColorPickerUp
}
}
+void Page::did_request_file_picker(WeakPtr target, HTML::AllowMultipleFiles allow_multiple_files)
+{
+ if (m_pending_non_blocking_dialog == PendingNonBlockingDialog::None) {
+ m_pending_non_blocking_dialog = PendingNonBlockingDialog::FilePicker;
+ m_pending_non_blocking_dialog_target = move(target);
+
+ m_client->page_did_request_file_picker(allow_multiple_files);
+ }
+}
+
+void Page::file_picker_closed(Span selected_files)
+{
+ if (m_pending_non_blocking_dialog == PendingNonBlockingDialog::FilePicker) {
+ m_pending_non_blocking_dialog = PendingNonBlockingDialog::None;
+
+ if (m_pending_non_blocking_dialog_target) {
+ auto& input_element = verify_cast(*m_pending_non_blocking_dialog_target);
+ input_element.did_select_files(selected_files);
+
+ m_pending_non_blocking_dialog_target.clear();
+ }
+ }
+}
+
void Page::did_request_select_dropdown(WeakPtr target, Web::CSSPixelPoint content_position, Web::CSSPixels minimum_width, Vector items)
{
if (m_pending_non_blocking_dialog == PendingNonBlockingDialog::None) {
diff --git a/Userland/Libraries/LibWeb/Page/Page.h b/Userland/Libraries/LibWeb/Page/Page.h
index f1e360c70a..595a29ea8e 100644
--- a/Userland/Libraries/LibWeb/Page/Page.h
+++ b/Userland/Libraries/LibWeb/Page/Page.h
@@ -134,12 +134,16 @@ public:
void did_request_color_picker(WeakPtr target, Color current_color);
void color_picker_update(Optional picked_color, HTML::ColorPickerUpdateState state);
+ void did_request_file_picker(WeakPtr target, HTML::AllowMultipleFiles);
+ void file_picker_closed(Span selected_files);
+
void did_request_select_dropdown(WeakPtr target, Web::CSSPixelPoint content_position, Web::CSSPixels minimum_width, Vector items);
void select_dropdown_closed(Optional value);
enum class PendingNonBlockingDialog {
None,
ColorPicker,
+ FilePicker,
Select,
};
@@ -280,8 +284,8 @@ public:
virtual void request_file(FileRequest) = 0;
// https://html.spec.whatwg.org/multipage/input.html#show-the-picker,-if-applicable
- virtual void page_did_request_file_picker(WeakPtr, [[maybe_unused]] bool multiple) {};
virtual void page_did_request_color_picker([[maybe_unused]] Color current_color) {};
+ virtual void page_did_request_file_picker(Web::HTML::AllowMultipleFiles) {};
virtual void page_did_request_select_dropdown([[maybe_unused]] Web::CSSPixelPoint content_position, [[maybe_unused]] Web::CSSPixels minimum_width, [[maybe_unused]] Vector items) {};
virtual void page_did_finish_text_test() {};
diff --git a/Userland/Libraries/LibWebView/ViewImplementation.cpp b/Userland/Libraries/LibWebView/ViewImplementation.cpp
index 2b5d5e61cd..833abb4509 100644
--- a/Userland/Libraries/LibWebView/ViewImplementation.cpp
+++ b/Userland/Libraries/LibWebView/ViewImplementation.cpp
@@ -250,6 +250,11 @@ void ViewImplementation::color_picker_update(Optional picked_color, Web::
client().async_color_picker_update(page_id(), picked_color, state);
}
+void ViewImplementation::file_picker_closed(Vector selected_files)
+{
+ client().async_file_picker_closed(page_id(), move(selected_files));
+}
+
void ViewImplementation::select_dropdown_closed(Optional value)
{
client().async_select_dropdown_closed(page_id(), value);
diff --git a/Userland/Libraries/LibWebView/ViewImplementation.h b/Userland/Libraries/LibWebView/ViewImplementation.h
index f1c107c679..805728ccd9 100644
--- a/Userland/Libraries/LibWebView/ViewImplementation.h
+++ b/Userland/Libraries/LibWebView/ViewImplementation.h
@@ -86,6 +86,7 @@ public:
void confirm_closed(bool accepted);
void prompt_closed(Optional response);
void color_picker_update(Optional picked_color, Web::HTML::ColorPickerUpdateState state);
+ void file_picker_closed(Vector selected_files);
void select_dropdown_closed(Optional value);
void toggle_media_play_state();
@@ -164,6 +165,7 @@ public:
Function on_minimize_window;
Function on_fullscreen_window;
Function on_request_color_picker;
+ Function on_request_file_picker;
Function items)> on_request_select_dropdown;
Function on_finish_handling_input_event;
Function on_text_test_finish;
diff --git a/Userland/Libraries/LibWebView/WebContentClient.cpp b/Userland/Libraries/LibWebView/WebContentClient.cpp
index 21875ce0b0..1f423eb833 100644
--- a/Userland/Libraries/LibWebView/WebContentClient.cpp
+++ b/Userland/Libraries/LibWebView/WebContentClient.cpp
@@ -806,6 +806,19 @@ void WebContentClient::did_request_color_picker(u64 page_id, Color const& curren
view.on_request_color_picker(current_color);
}
+void WebContentClient::did_request_file_picker(u64 page_id, Web::HTML::AllowMultipleFiles allow_multiple_files)
+{
+ auto maybe_view = m_views.get(page_id);
+ if (!maybe_view.has_value()) {
+ dbgln("Received request file picker for unknown page ID {}", page_id);
+ return;
+ }
+ auto& view = *maybe_view.value();
+
+ if (view.on_request_file_picker)
+ view.on_request_file_picker(allow_multiple_files);
+}
+
void WebContentClient::did_request_select_dropdown(u64 page_id, Gfx::IntPoint content_position, i32 minimum_width, Vector const& items)
{
auto maybe_view = m_views.get(page_id);
diff --git a/Userland/Libraries/LibWebView/WebContentClient.h b/Userland/Libraries/LibWebView/WebContentClient.h
index 85af653e7d..f55b1f5124 100644
--- a/Userland/Libraries/LibWebView/WebContentClient.h
+++ b/Userland/Libraries/LibWebView/WebContentClient.h
@@ -89,6 +89,7 @@ private:
virtual Messages::WebContentClient::DidRequestFullscreenWindowResponse did_request_fullscreen_window(u64 page_id) override;
virtual void did_request_file(u64 page_id, ByteString const& path, i32) override;
virtual void did_request_color_picker(u64 page_id, Color const& current_color) override;
+ virtual void did_request_file_picker(u64 page_id, Web::HTML::AllowMultipleFiles) override;
virtual void did_request_select_dropdown(u64 page_id, Gfx::IntPoint content_position, i32 minimum_width, Vector const& items) override;
virtual void did_finish_handling_input_event(u64 page_id, bool event_was_accepted) override;
virtual void did_finish_text_test(u64 page_id) override;
diff --git a/Userland/Services/WebContent/ConnectionFromClient.cpp b/Userland/Services/WebContent/ConnectionFromClient.cpp
index cbcaf800f0..585a58e02c 100644
--- a/Userland/Services/WebContent/ConnectionFromClient.cpp
+++ b/Userland/Services/WebContent/ConnectionFromClient.cpp
@@ -28,6 +28,8 @@
#include
#include
#include
+#include
+#include
#include
#include
#include
@@ -1317,6 +1319,18 @@ void ConnectionFromClient::color_picker_update(u64 page_id, Optional cons
page.page().color_picker_update(picked_color, state);
}
+void ConnectionFromClient::file_picker_closed(u64 page_id, Vector const& selected_files)
+{
+ auto maybe_page = page(page_id);
+ if (!maybe_page.has_value()) {
+ dbgln("ConnectionFromClient::color_picker_update: No page with ID {}", page_id);
+ return;
+ }
+
+ auto& page = maybe_page.release_value();
+ page.page().file_picker_closed(const_cast&>(selected_files));
+}
+
void ConnectionFromClient::select_dropdown_closed(u64 page_id, Optional const& value)
{
auto maybe_page = page(page_id);
diff --git a/Userland/Services/WebContent/ConnectionFromClient.h b/Userland/Services/WebContent/ConnectionFromClient.h
index 5dd491c47f..6e3159c9d8 100644
--- a/Userland/Services/WebContent/ConnectionFromClient.h
+++ b/Userland/Services/WebContent/ConnectionFromClient.h
@@ -106,6 +106,7 @@ private:
virtual void confirm_closed(u64 page_id, bool accepted) override;
virtual void prompt_closed(u64 page_id, Optional const& response) override;
virtual void color_picker_update(u64 page_id, Optional const& picked_color, Web::HTML::ColorPickerUpdateState const& state) override;
+ virtual void file_picker_closed(u64 page_id, Vector const& selected_files) override;
virtual void select_dropdown_closed(u64 page_id, Optional const& value) override;
virtual void toggle_media_play_state(u64 page_id) override;
diff --git a/Userland/Services/WebContent/PageClient.cpp b/Userland/Services/WebContent/PageClient.cpp
index 73dc906753..3d66bf2fdf 100644
--- a/Userland/Services/WebContent/PageClient.cpp
+++ b/Userland/Services/WebContent/PageClient.cpp
@@ -549,6 +549,11 @@ void PageClient::page_did_request_color_picker(Color current_color)
client().async_did_request_color_picker(m_id, current_color);
}
+void PageClient::page_did_request_file_picker(Web::HTML::AllowMultipleFiles allow_multiple_files)
+{
+ client().async_did_request_file_picker(m_id, allow_multiple_files);
+}
+
void PageClient::page_did_request_select_dropdown(Web::CSSPixelPoint content_position, Web::CSSPixels minimum_width, Vector items)
{
client().async_did_request_select_dropdown(m_id, page().css_to_device_point(content_position).to_type(), minimum_width * device_pixels_per_css_pixel(), items);
diff --git a/Userland/Services/WebContent/PageClient.h b/Userland/Services/WebContent/PageClient.h
index fb40371dd3..8936cfdf23 100644
--- a/Userland/Services/WebContent/PageClient.h
+++ b/Userland/Services/WebContent/PageClient.h
@@ -133,6 +133,7 @@ private:
virtual void page_did_close_top_level_traversable() override;
virtual void request_file(Web::FileRequest) override;
virtual void page_did_request_color_picker(Color current_color) override;
+ virtual void page_did_request_file_picker(Web::HTML::AllowMultipleFiles) override;
virtual void page_did_request_select_dropdown(Web::CSSPixelPoint content_position, Web::CSSPixels minimum_width, Vector items) override;
virtual void page_did_finish_text_test() override;
virtual void page_did_change_theme_color(Gfx::Color color) override;
diff --git a/Userland/Services/WebContent/WebContentClient.ipc b/Userland/Services/WebContent/WebContentClient.ipc
index 57b8729a56..0abce74f54 100644
--- a/Userland/Services/WebContent/WebContentClient.ipc
+++ b/Userland/Services/WebContent/WebContentClient.ipc
@@ -6,6 +6,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -70,6 +71,7 @@ endpoint WebContentClient
did_request_fullscreen_window(u64 page_id) => (Gfx::IntRect window_rect)
did_request_file(u64 page_id, ByteString path, i32 request_id) =|
did_request_color_picker(u64 page_id, Color current_color) =|
+ did_request_file_picker(u64 page_id, Web::HTML::AllowMultipleFiles allow_multiple_files) =|
did_request_select_dropdown(u64 page_id, Gfx::IntPoint content_position, i32 minimum_width, Vector items) =|
did_finish_handling_input_event(u64 page_id, bool event_was_accepted) =|
did_change_theme_color(u64 page_id, Gfx::Color color) =|
diff --git a/Userland/Services/WebContent/WebContentServer.ipc b/Userland/Services/WebContent/WebContentServer.ipc
index 7db19cae92..f16e836254 100644
--- a/Userland/Services/WebContent/WebContentServer.ipc
+++ b/Userland/Services/WebContent/WebContentServer.ipc
@@ -6,6 +6,7 @@
#include
#include
#include
+#include
#include
#include
@@ -93,6 +94,7 @@ endpoint WebContentServer
confirm_closed(u64 page_id, bool accepted) =|
prompt_closed(u64 page_id, Optional response) =|
color_picker_update(u64 page_id, Optional picked_color, Web::HTML::ColorPickerUpdateState state) =|
+ file_picker_closed(u64 page_id, Vector selected_files) =|
select_dropdown_closed(u64 page_id, Optional value) =|
toggle_media_play_state(u64 page_id) =|
diff --git a/Userland/Utilities/headless-browser.cpp b/Userland/Utilities/headless-browser.cpp
index 5990263444..433e53aafc 100644
--- a/Userland/Utilities/headless-browser.cpp
+++ b/Userland/Utilities/headless-browser.cpp
@@ -8,6 +8,7 @@
*/
#include
+#include
#include
#include
#include
@@ -44,6 +45,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -433,6 +435,21 @@ static ErrorOr run_test(HeadlessWebContentView& view, StringView inp
promise->resolve({});
};
view.on_text_test_finish = {};
+
+ view.on_request_file_picker = [&](auto allow_multiple_files) {
+ // Create some dummy files for tests.
+ Vector selected_files;
+ selected_files.empend("file1"sv, MUST(ByteBuffer::copy("Contents for file1"sv.bytes())));
+
+ if (allow_multiple_files == Web::HTML::AllowMultipleFiles::Yes) {
+ selected_files.empend("file2"sv, MUST(ByteBuffer::copy("Contents for file2"sv.bytes())));
+ selected_files.empend("file3"sv, MUST(ByteBuffer::copy("Contents for file3"sv.bytes())));
+ selected_files.empend("file4"sv, MUST(ByteBuffer::copy("Contents for file4"sv.bytes())));
+ }
+
+ view.file_picker_closed(move(selected_files));
+ };
+
view.load(URL("about:blank"sv));
MUST(promise->await());