UI/AppKit: Process drag-and-drop events through the web view

This forwards all drag-and-drop events from the UI to the WebContent
process. If the page accepts the events, the UI does not handle them.
Otherwise, we will open the dropped files as file:// URLs.
This commit is contained in:
Timothy Flynn 2024-08-17 16:23:13 -04:00 committed by Andreas Kling
commit ac062d0c97
Notes: github-actions[bot] 2024-08-19 11:30:14 +00:00
5 changed files with 138 additions and 3 deletions

View file

@ -1,11 +1,13 @@
/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Vector.h>
#include <LibURL/Forward.h>
#include <LibWeb/Page/InputEvent.h>
#import <Cocoa/Cocoa.h>
@ -13,6 +15,10 @@
namespace Ladybird {
Web::MouseEvent ns_event_to_mouse_event(Web::MouseEvent::Type, NSEvent*, NSView*, NSScrollView*, Web::UIEvents::MouseButton);
Web::DragEvent ns_event_to_drag_event(Web::DragEvent::Type, id<NSDraggingInfo>, NSView*);
Vector<URL::URL> drag_event_url_list(Web::DragEvent const&);
Web::KeyEvent ns_event_to_key_event(Web::KeyEvent::Type, NSEvent*);
NSEvent* key_event_to_ns_event(Web::KeyEvent const&);

View file

@ -1,11 +1,14 @@
/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/TypeCasts.h>
#include <AK/Utf8View.h>
#include <LibURL/URL.h>
#include <LibWeb/HTML/SelectedFile.h>
#include <LibWeb/UIEvents/KeyCode.h>
#import <Carbon/Carbon.h>
#import <UI/Event.h>
@ -70,6 +73,66 @@ Web::MouseEvent ns_event_to_mouse_event(Web::MouseEvent::Type type, NSEvent* eve
return { type, device_position, device_screen_position, button, button, modifiers, wheel_delta_x, wheel_delta_y, nullptr };
}
struct DragData : public Web::ChromeInputData {
explicit DragData(Vector<URL::URL> urls)
: urls(move(urls))
{
}
Vector<URL::URL> urls;
};
Web::DragEvent ns_event_to_drag_event(Web::DragEvent::Type type, id<NSDraggingInfo> event, NSView* view)
{
auto position = [view convertPoint:event.draggingLocation fromView:nil];
auto device_position = ns_point_to_gfx_point(position).to_type<Web::DevicePixels>();
auto screen_position = [NSEvent mouseLocation];
auto device_screen_position = ns_point_to_gfx_point(screen_position).to_type<Web::DevicePixels>();
auto button = Web::UIEvents::MouseButton::Primary;
auto modifiers = ns_modifiers_to_key_modifiers([NSEvent modifierFlags], button);
Vector<Web::HTML::SelectedFile> files;
OwnPtr<DragData> chrome_data;
auto for_each_file = [&](auto callback) {
NSArray* file_list = [[event draggingPasteboard] readObjectsForClasses:@[ [NSURL class] ]
options:nil];
for (NSURL* file in file_list) {
auto file_path = Ladybird::ns_string_to_byte_string([file path]);
callback(file_path);
}
};
if (type == Web::DragEvent::Type::DragStart) {
for_each_file([&](ByteString const& file_path) {
if (auto file = Web::HTML::SelectedFile::from_file_path(file_path); file.is_error())
warnln("Unable to open file {}: {}", file_path, file.error());
else
files.append(file.release_value());
});
} else if (type == Web::DragEvent::Type::Drop) {
Vector<URL::URL> urls;
for_each_file([&](ByteString const& file_path) {
if (auto url = URL::create_with_url_or_path(file_path); url.is_valid())
urls.append(move(url));
});
chrome_data = make<DragData>(move(urls));
}
return { type, device_position, device_screen_position, button, button, modifiers, move(files), move(chrome_data) };
}
Vector<URL::URL> drag_event_url_list(Web::DragEvent const& event)
{
auto& chrome_data = verify_cast<DragData>(*event.chrome_data);
return move(chrome_data.urls);
}
NSEvent* create_context_menu_mouse_event(NSView* view, Gfx::IntPoint position)
{
return create_context_menu_mouse_event(view, gfx_point_to_ns_point(position));

View file

@ -48,7 +48,7 @@ struct HideCursor {
}
};
@interface LadybirdWebView ()
@interface LadybirdWebView () <NSDraggingDestination>
{
OwnPtr<Ladybird::WebViewBridge> m_web_view_bridge;
@ -114,6 +114,8 @@ struct HideCursor {
owner:self
userInfo:nil];
[self addTrackingArea:area];
[self registerForDraggedTypes:[NSArray arrayWithObjects:NSPasteboardTypeFileURL, nil]];
}
return self;
@ -417,6 +419,25 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_
self.event_being_redispatched = nil;
};
m_web_view_bridge->on_finish_handling_drag_event = [weak_self](auto const& event) {
LadybirdWebView* self = weak_self;
if (self == nil) {
return;
}
if (event.type != Web::DragEvent::Type::Drop) {
return;
}
if (auto urls = Ladybird::drag_event_url_list(event); !urls.is_empty()) {
[self.observer loadURL:urls[0]];
for (size_t i = 1; i < urls.size(); ++i) {
[self.observer onCreateNewTab:urls[i] activateTab:Web::HTML::ActivateTab::No];
}
}
};
m_web_view_bridge->on_cursor_change = [weak_self](auto cursor) {
LadybirdWebView* self = weak_self;
if (self == nil) {
@ -1669,4 +1690,41 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_
m_web_view_bridge->enqueue_input_event(move(key_event));
}
#pragma mark - NSDraggingDestination
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)event
{
auto drag_event = Ladybird::ns_event_to_drag_event(Web::DragEvent::Type::DragStart, event, self);
m_web_view_bridge->enqueue_input_event(move(drag_event));
return NSDragOperationCopy;
}
- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)event
{
auto drag_event = Ladybird::ns_event_to_drag_event(Web::DragEvent::Type::DragMove, event, self);
m_web_view_bridge->enqueue_input_event(move(drag_event));
return NSDragOperationCopy;
}
- (void)draggingExited:(id<NSDraggingInfo>)event
{
auto drag_event = Ladybird::ns_event_to_drag_event(Web::DragEvent::Type::DragEnd, event, self);
m_web_view_bridge->enqueue_input_event(move(drag_event));
}
- (BOOL)performDragOperation:(id<NSDraggingInfo>)event
{
auto drag_event = Ladybird::ns_event_to_drag_event(Web::DragEvent::Type::Drop, event, self);
m_web_view_bridge->enqueue_input_event(move(drag_event));
return YES;
}
- (BOOL)wantsPeriodicDraggingUpdates
{
return NO;
}
@end

View file

@ -93,6 +93,13 @@ void WebViewBridge::enqueue_input_event(Web::MouseEvent event)
ViewImplementation::enqueue_input_event(move(event));
}
void WebViewBridge::enqueue_input_event(Web::DragEvent event)
{
event.position = to_content_position(event.position.to_type<int>()).to_type<Web::DevicePixels>();
event.screen_position = to_content_position(event.screen_position.to_type<int>()).to_type<Web::DevicePixels>();
ViewImplementation::enqueue_input_event(move(event));
}
void WebViewBridge::enqueue_input_event(Web::KeyEvent event)
{
ViewImplementation::enqueue_input_event(move(event));

View file

@ -44,6 +44,7 @@ public:
void set_preferred_motion(Web::CSS::PreferredMotion);
void enqueue_input_event(Web::MouseEvent);
void enqueue_input_event(Web::DragEvent);
void enqueue_input_event(Web::KeyEvent);
struct Paintable {