LibWebView+UI: Add a context menu item to download images
This introduces a simple FileDownloader to download files in the UI process from RequestServer. We use this to download the context menu image - this download is likely to hit the disk cache.
This commit is contained in:
parent
70482687a2
commit
f322e8a29c
9 changed files with 130 additions and 3 deletions
|
|
@ -24,6 +24,7 @@
|
|||
#include <LibWeb/CSS/PreferredMotion.h>
|
||||
#include <LibWeb/Clipboard/SystemClipboard.h>
|
||||
#include <LibWeb/HTML/ActivateTab.h>
|
||||
#include <LibWebView/FileDownloader.h>
|
||||
#include <LibWebView/Forward.h>
|
||||
#include <LibWebView/Options.h>
|
||||
#include <LibWebView/Process.h>
|
||||
|
|
@ -126,6 +127,8 @@ public:
|
|||
|
||||
Menu& debug_menu() { return *m_debug_menu; }
|
||||
|
||||
FileDownloader& file_downloader() { return m_file_downloader; }
|
||||
|
||||
void apply_view_options(Badge<ViewImplementation>, ViewImplementation&);
|
||||
|
||||
ErrorOr<void> toggle_devtools_enabled();
|
||||
|
|
@ -256,6 +259,8 @@ private:
|
|||
|
||||
Optional<Web::Clipboard::SystemClipboardRepresentation> m_clipboard;
|
||||
|
||||
FileDownloader m_file_downloader;
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
OwnPtr<MachPortServer> m_mach_port_server;
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ set(SOURCES
|
|||
ConsoleOutput.cpp
|
||||
CookieJar.cpp
|
||||
DOMNodeProperties.cpp
|
||||
FileDownloader.cpp
|
||||
HeadlessWebView.cpp
|
||||
HelperProcess.cpp
|
||||
Menu.cpp
|
||||
|
|
|
|||
75
Libraries/LibWebView/FileDownloader.cpp
Normal file
75
Libraries/LibWebView/FileDownloader.cpp
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (c) 2026, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibCore/File.h>
|
||||
#include <LibHTTP/HeaderList.h>
|
||||
#include <LibRequests/Request.h>
|
||||
#include <LibRequests/RequestClient.h>
|
||||
#include <LibTextCodec/Encoder.h>
|
||||
#include <LibWeb/Loader/UserAgent.h>
|
||||
#include <LibWebView/Application.h>
|
||||
#include <LibWebView/CookieJar.h>
|
||||
#include <LibWebView/FileDownloader.h>
|
||||
|
||||
namespace WebView {
|
||||
|
||||
FileDownloader::FileDownloader() = default;
|
||||
FileDownloader::~FileDownloader() = default;
|
||||
|
||||
static ErrorOr<void> save_file(LexicalPath const& destination, ReadonlyBytes data)
|
||||
{
|
||||
auto file = TRY(Core::File::open(destination.string(), Core::File::OpenMode::Write));
|
||||
TRY(file->write_until_depleted(data));
|
||||
return {};
|
||||
}
|
||||
|
||||
void FileDownloader::download_file(URL::URL const& url, LexicalPath destination)
|
||||
{
|
||||
static u64 next_request_id = 0;
|
||||
|
||||
// FIXME: What other request headers should be set? Perhaps we want to use exactly the same request headers used to
|
||||
// originally fetch the image in WebContent.
|
||||
auto request_headers = HTTP::HeaderList::create();
|
||||
request_headers->set({ "Cookie"sv, TextCodec::isomorphic_encode(Application::cookie_jar().get_cookie(url, Web::Cookie::Source::Http)) });
|
||||
request_headers->set({ "User-Agent"sv, Web::default_user_agent });
|
||||
|
||||
auto request = Application::request_server_client().start_request("GET"sv, url, *request_headers);
|
||||
if (!request) {
|
||||
Application::the().display_error_dialog("Unable to start request to download file"sv);
|
||||
return;
|
||||
}
|
||||
|
||||
auto request_id = next_request_id++;
|
||||
|
||||
request->set_buffered_request_finished_callback(
|
||||
[this, request_id, destination = move(destination)](u64, Requests::RequestTimingInfo const&, Optional<Requests::NetworkError> const& network_error, HTTP::HeaderList const&, Optional<u32> response_code, Optional<String> const& reason_phrase, ReadonlyBytes payload) {
|
||||
Core::deferred_invoke([this, request_id]() { m_requests.remove(request_id); });
|
||||
|
||||
if (network_error.has_value()) {
|
||||
auto error = MUST(String::formatted("Unable to download file: {}", Requests::network_error_to_string(*network_error)));
|
||||
Application::the().display_error_dialog(error);
|
||||
return;
|
||||
}
|
||||
if (response_code.has_value() && *response_code >= 400) {
|
||||
auto error = reason_phrase.has_value()
|
||||
? MUST(String::formatted("Received error response code {} while downloading file: {}", *response_code, reason_phrase))
|
||||
: MUST(String::formatted("Received error response code {} while downloading file", *response_code));
|
||||
Application::the().display_error_dialog(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto result = save_file(destination, payload); result.is_error()) {
|
||||
auto error = MUST(String::formatted("Unable to save downloaded file file: {}", result.error()));
|
||||
Application::the().display_error_dialog(error);
|
||||
}
|
||||
|
||||
// FIXME: Add a UI element (i.e. a download manager) to indicate download completion.
|
||||
});
|
||||
|
||||
m_requests.set(request_id, request.release_nonnull());
|
||||
}
|
||||
|
||||
}
|
||||
29
Libraries/LibWebView/FileDownloader.h
Normal file
29
Libraries/LibWebView/FileDownloader.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2026, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/LexicalPath.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <LibRequests/Forward.h>
|
||||
#include <LibURL/Forward.h>
|
||||
#include <LibWebView/Forward.h>
|
||||
|
||||
namespace WebView {
|
||||
|
||||
class WEBVIEW_API FileDownloader {
|
||||
public:
|
||||
FileDownloader();
|
||||
~FileDownloader();
|
||||
|
||||
void download_file(URL::URL const&, LexicalPath);
|
||||
|
||||
private:
|
||||
HashMap<u64, NonnullRefPtr<Requests::Request>> m_requests;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
* Copyright (c) 2025-2026, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
|
@ -46,6 +46,7 @@ enum class ActionID {
|
|||
CopyURL,
|
||||
|
||||
OpenImage,
|
||||
SaveImage,
|
||||
CopyImage,
|
||||
|
||||
OpenAudio,
|
||||
|
|
|
|||
|
|
@ -891,6 +891,13 @@ void ViewImplementation::initialize_context_menus()
|
|||
m_open_image_action = Action::create("Open Image"sv, ActionID::OpenImage, [this]() {
|
||||
load(m_context_menu_url);
|
||||
});
|
||||
m_save_image_action = Action::create("Save Image As..."sv, ActionID::SaveImage, [this]() {
|
||||
auto download_path = Application::the().path_for_downloaded_file(m_context_menu_url.basename());
|
||||
if (download_path.is_error())
|
||||
return;
|
||||
|
||||
Application::the().file_downloader().download_file(m_context_menu_url, download_path.release_value());
|
||||
});
|
||||
m_copy_image_action = Action::create("Copy Image"sv, ActionID::CopyImage, [this]() {
|
||||
if (!m_image_context_menu_bitmap.has_value())
|
||||
return;
|
||||
|
|
@ -958,6 +965,8 @@ void ViewImplementation::initialize_context_menus()
|
|||
m_image_context_menu->add_action(*m_open_image_action);
|
||||
m_image_context_menu->add_action(*m_open_in_new_tab_action);
|
||||
m_image_context_menu->add_separator();
|
||||
m_image_context_menu->add_action(*m_save_image_action);
|
||||
m_image_context_menu->add_separator();
|
||||
m_image_context_menu->add_action(*m_copy_image_action);
|
||||
m_image_context_menu->add_action(*m_copy_url_action);
|
||||
|
||||
|
|
|
|||
|
|
@ -334,6 +334,7 @@ protected:
|
|||
URL::URL m_context_menu_url;
|
||||
|
||||
RefPtr<Action> m_open_image_action;
|
||||
RefPtr<Action> m_save_image_action;
|
||||
RefPtr<Action> m_copy_image_action;
|
||||
Optional<Gfx::ShareableBitmap> m_image_context_menu_bitmap;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
* Copyright (c) 2025-2026, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
|
@ -179,6 +179,9 @@ static void initialize_native_control(WebView::Action& action, id control)
|
|||
case WebView::ActionID::OpenImage:
|
||||
set_control_image(control, @"photo");
|
||||
break;
|
||||
case WebView::ActionID::SaveImage:
|
||||
set_control_image(control, @"square.and.arrow.down");
|
||||
break;
|
||||
case WebView::ActionID::CopyImage:
|
||||
set_control_image(control, @"document.on.document");
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
* Copyright (c) 2025-2026, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
|
@ -142,6 +142,9 @@ static void initialize_native_control(WebView::Action& action, QAction& qaction,
|
|||
case WebView::ActionID::OpenImage:
|
||||
qaction.setIcon(load_icon_from_uri("resource://icons/16x16/filetype-image.png"sv));
|
||||
break;
|
||||
case WebView::ActionID::SaveImage:
|
||||
qaction.setIcon(load_icon_from_uri("resource://icons/16x16/download.png"sv));
|
||||
break;
|
||||
case WebView::ActionID::CopyImage:
|
||||
qaction.setIcon(load_icon_from_uri("resource://icons/16x16/edit-copy.png"sv));
|
||||
break;
|
||||
|
|
|
|||
Loading…
Reference in a new issue