UI/AppKit: Implement opening child web views from e.g. window.open
Some checks are pending
CI / Lagom (true, NO_FUZZ, ubuntu-22.04, Linux, Clang) (push) Waiting to run
CI / Lagom (false, FUZZ, ubuntu-22.04, Linux, Clang) (push) Waiting to run
CI / Lagom (false, NO_FUZZ, macos-14, macOS, Clang) (push) Waiting to run
CI / Lagom (false, NO_FUZZ, ubuntu-22.04, Linux, GNU) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (macos-14, macOS, macOS-universal2) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (ubuntu-22.04, Linux, Linux-x86_64) (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Push notes / build (push) Waiting to run

This has been implemented in Qt for quite some time. This patch adds the
same feature to AppKit. This is needed to run many WPT subtests with the
AppKit chrome. This is also needed to handle window.open, target=_blank
link clicks, etc.
This commit is contained in:
Timothy Flynn 2024-09-17 15:49:00 -04:00 committed by Andreas Kling
parent e6965b11e4
commit 27776c8854
Notes: github-actions[bot] 2024-09-18 08:11:03 +00:00
10 changed files with 154 additions and 12 deletions

View file

@ -13,6 +13,7 @@
#include <LibWeb/CSS/PreferredContrast.h>
#include <LibWeb/CSS/PreferredMotion.h>
#include <LibWeb/HTML/ActivateTab.h>
#include <LibWebView/Forward.h>
#import <Cocoa/Cocoa.h>
@ -32,6 +33,11 @@
fromTab:(nullable Tab*)tab
activateTab:(Web::HTML::ActivateTab)activate_tab;
- (nonnull TabController*)createChildTab:(Optional<URL::URL> const&)url
fromTab:(nonnull Tab*)tab
activateTab:(Web::HTML::ActivateTab)activate_tab
pageIndex:(u64)page_index;
- (void)setActiveTab:(nonnull Tab*)tab;
- (nullable Tab*)activeTab;

View file

@ -116,6 +116,20 @@
return controller;
}
- (nonnull TabController*)createChildTab:(Optional<URL::URL> const&)url
fromTab:(nonnull Tab*)tab
activateTab:(Web::HTML::ActivateTab)activate_tab
pageIndex:(u64)page_index
{
auto* controller = [self createChildTab:activate_tab fromTab:tab pageIndex:page_index];
if (url.has_value()) {
[controller loadURL:*url];
}
return controller;
}
- (void)setActiveTab:(Tab*)tab
{
self.active_tab = tab;
@ -175,6 +189,29 @@
fromTab:(nullable Tab*)tab
{
auto* controller = [[TabController alloc] init];
[self initializeTabController:controller
activateTab:activate_tab
fromTab:tab];
return controller;
}
- (nonnull TabController*)createChildTab:(Web::HTML::ActivateTab)activate_tab
fromTab:(nonnull Tab*)tab
pageIndex:(u64)page_index
{
auto* controller = [[TabController alloc] initAsChild:tab pageIndex:page_index];
[self initializeTabController:controller
activateTab:activate_tab
fromTab:tab];
return controller;
}
- (void)initializeTabController:(TabController*)controller
activateTab:(Web::HTML::ActivateTab)activate_tab
fromTab:(nullable Tab*)tab
{
[controller showWindow:nil];
if (tab) {
@ -192,7 +229,6 @@
[self.managed_tabs addObject:controller];
[controller onCreateNewTab];
return controller;
}
- (void)closeCurrentTab:(id)sender

View file

@ -27,6 +27,10 @@
url:(URL::URL const&)url
activateTab:(Web::HTML::ActivateTab)activate_tab;
- (String const&)onCreateChildTab:(Optional<URL::URL> const&)url
activateTab:(Web::HTML::ActivateTab)activate_tab
pageIndex:(u64)page_index;
- (void)loadURL:(URL::URL const&)url;
- (void)onLoadStart:(URL::URL const&)url isRedirect:(BOOL)is_redirect;
- (void)onLoadFinish:(URL::URL const&)url;
@ -47,6 +51,9 @@
@interface LadybirdWebView : NSClipView <NSMenuDelegate>
- (instancetype)init:(id<LadybirdWebViewObserver>)observer;
- (instancetype)initAsChild:(id<LadybirdWebViewObserver>)observer
parent:(LadybirdWebView*)parent
pageIndex:(u64)page_index;
- (void)loadURL:(URL::URL const&)url;
- (void)loadHTML:(StringView)html;

View file

@ -88,6 +88,26 @@ struct HideCursor {
@synthesize status_label = _status_label;
- (instancetype)init:(id<LadybirdWebViewObserver>)observer
{
if (self = [self initWebView:observer]) {
m_web_view_bridge->initialize_client();
}
return self;
}
- (instancetype)initAsChild:(id<LadybirdWebViewObserver>)observer
parent:(LadybirdWebView*)parent
pageIndex:(u64)page_index
{
if (self = [self initWebView:observer]) {
m_web_view_bridge->initialize_client_as_child(*parent->m_web_view_bridge, page_index);
}
return self;
}
- (instancetype)initWebView:(id<LadybirdWebViewObserver>)observer
{
if (self = [super init]) {
self.observer = observer;
@ -109,8 +129,6 @@ struct HideCursor {
m_web_view_bridge = MUST(Ladybird::WebViewBridge::create(move(screen_rects), device_pixel_ratio, [delegate preferredColorScheme], [delegate preferredContrast], [delegate preferredMotion]));
[self setWebViewCallbacks];
m_web_view_bridge->initialize_client();
auto* area = [[NSTrackingArea alloc] initWithRect:[self bounds]
options:NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect | NSTrackingMouseMoved
owner:self
@ -317,12 +335,18 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_
[self setNeedsDisplay:YES];
};
m_web_view_bridge->on_new_web_view = [weak_self](auto activate_tab, auto, auto) {
m_web_view_bridge->on_new_web_view = [weak_self](auto activate_tab, auto, auto page_index) {
LadybirdWebView* self = weak_self;
if (self == nil) {
return String {};
}
// FIXME: Create a child tab that re-uses the ConnectionFromClient of the parent tab
if (page_index.has_value()) {
return [self.observer onCreateChildTab:{}
activateTab:activate_tab
pageIndex:*page_index];
}
return [self.observer onCreateNewTab:{} activateTab:activate_tab];
};

View file

@ -147,15 +147,17 @@ Gfx::IntPoint WebViewBridge::to_widget_position(Gfx::IntPoint content_position)
return scale_for_device(content_position, inverse_device_pixel_ratio());
}
void WebViewBridge::initialize_client(CreateNewClient)
void WebViewBridge::initialize_client(CreateNewClient create_new_client)
{
VERIFY(on_request_web_content);
// FIXME: Don't create a new process when CreateNewClient is false
// We should create a new tab/window in the UI instead, and re-use the existing WebContentClient object.
m_client_state = {};
if (create_new_client == CreateNewClient::Yes) {
m_client_state = {};
m_client_state.client = on_request_web_content();
} else {
m_client_state.client->register_view(m_client_state.page_index, *this);
}
m_client_state.client = on_request_web_content();
m_client_state.client->on_web_content_process_crash = [this] {
Core::deferred_invoke([this] {
handle_web_content_process_crash();
@ -184,4 +186,12 @@ void WebViewBridge::initialize_client(CreateNewClient)
}
}
void WebViewBridge::initialize_client_as_child(WebViewBridge const& parent, u64 page_index)
{
m_client_state.client = parent.client();
m_client_state.page_index = page_index;
initialize_client(CreateNewClient::No);
}
}

View file

@ -25,6 +25,7 @@ public:
virtual ~WebViewBridge() override;
virtual void initialize_client(CreateNewClient = CreateNewClient::Yes) override;
void initialize_client_as_child(WebViewBridge const& parent, u64 page_index);
float device_pixel_ratio() const { return m_device_pixel_ratio; }
void set_device_pixel_ratio(float device_pixel_ratio);

View file

@ -6,12 +6,18 @@
#pragma once
#include <AK/Types.h>
#import <Cocoa/Cocoa.h>
@class LadybirdWebView;
@interface Tab : NSWindow
- (instancetype)init;
- (instancetype)initAsChild:(Tab*)parent
pageIndex:(u64)page_index;
- (void)tabWillClose;
- (void)openInspector:(id)sender;

View file

@ -60,6 +60,19 @@ static constexpr CGFloat const WINDOW_HEIGHT = 800;
}
- (instancetype)init
{
auto* web_view = [[LadybirdWebView alloc] init:self];
return [self initWithWebView:web_view];
}
- (instancetype)initAsChild:(Tab*)parent
pageIndex:(u64)page_index
{
auto* web_view = [[LadybirdWebView alloc] initAsChild:self parent:[parent web_view] pageIndex:page_index];
return [self initWithWebView:web_view];
}
- (instancetype)initWithWebView:(LadybirdWebView*)web_view
{
auto screen_rect = [[NSScreen mainScreen] frame];
auto position_x = (NSWidth(screen_rect) - WINDOW_WIDTH) / 2;
@ -77,7 +90,7 @@ static constexpr CGFloat const WINDOW_HEIGHT = 800;
// Remember last window position
self.frameAutosaveName = @"window";
self.web_view = [[LadybirdWebView alloc] init:self];
self.web_view = web_view;
[self.web_view setPostsBoundsChangedNotifications:YES];
self.favicon = [Tab defaultFavicon];
@ -296,6 +309,21 @@ static constexpr CGFloat const WINDOW_HEIGHT = 800;
return [[tab web_view] handle];
}
- (String const&)onCreateChildTab:(Optional<URL::URL> const&)url
activateTab:(Web::HTML::ActivateTab)activate_tab
pageIndex:(u64)page_index
{
auto* delegate = (ApplicationDelegate*)[NSApp delegate];
auto* controller = [delegate createChildTab:url
fromTab:self
activateTab:activate_tab
pageIndex:page_index];
auto* tab = (Tab*)[controller window];
return [[tab web_view] handle];
}
- (void)loadURL:(URL::URL const&)url
{
[[self tabController] loadURL:url];

View file

@ -11,6 +11,8 @@
#import <Cocoa/Cocoa.h>
@class Tab;
struct TabSettings {
BOOL should_show_line_box_borders { NO };
BOOL scripting_enabled { YES };
@ -23,6 +25,8 @@ struct TabSettings {
@interface TabController : NSWindowController <NSWindowDelegate>
- (instancetype)init;
- (instancetype)initAsChild:(Tab*)parent
pageIndex:(u64)page_index;
- (void)loadURL:(URL::URL const&)url;
- (void)loadHTML:(StringView)html url:(URL::URL const&)url;

View file

@ -49,6 +49,8 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde
@interface TabController () <NSToolbarDelegate, NSSearchFieldDelegate>
{
u64 m_page_index;
ByteString m_title;
TabSettings m_settings;
@ -57,6 +59,8 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde
bool m_can_navigate_forward;
}
@property (nonatomic, strong) Tab* parent;
@property (nonatomic, strong) NSToolbar* toolbar;
@property (nonatomic, strong) NSArray* toolbar_identifiers;
@ -92,6 +96,8 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde
[self.toolbar setAllowsUserCustomization:NO];
[self.toolbar setSizeMode:NSToolbarSizeModeRegular];
m_page_index = 0;
m_settings = {
.scripting_enabled = WebView::Application::chrome_options().disable_scripting == WebView::DisableScripting::Yes ? NO : YES,
.block_popups = WebView::Application::chrome_options().allow_popups == WebView::AllowPopups::Yes ? NO : YES,
@ -107,6 +113,17 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde
return self;
}
- (instancetype)initAsChild:(Tab*)parent
pageIndex:(u64)page_index
{
if (self = [self init]) {
self.parent = parent;
m_page_index = page_index;
}
return self;
}
#pragma mark - Public methods
- (void)loadURL:(URL::URL const&)url
@ -544,7 +561,10 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde
- (IBAction)showWindow:(id)sender
{
self.window = [[Tab alloc] init];
self.window = self.parent
? [[Tab alloc] initAsChild:self.parent pageIndex:m_page_index]
: [[Tab alloc] init];
[self.window setDelegate:self];
[self.window setToolbar:self.toolbar];