mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 15:40:19 +00:00
ecce4ad426
If a tab's title is cut off due to its size, setting a tool tip will allow for viewing the full title.
935 lines
35 KiB
C++
935 lines
35 KiB
C++
/*
|
|
* Copyright (c) 2022-2023, Andreas Kling <kling@serenityos.org>
|
|
* Copyright (c) 2022, Matthew Costa <ucosty@gmail.com>
|
|
* Copyright (c) 2022, Filiph Sandström <filiph.sandstrom@filfatstudios.com>
|
|
* Copyright (c) 2023, Linus Groh <linusg@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include "BrowserWindow.h"
|
|
#include "Icon.h"
|
|
#include "Settings.h"
|
|
#include "SettingsDialog.h"
|
|
#include "StringUtils.h"
|
|
#include "TaskManagerWindow.h"
|
|
#include "WebContentView.h"
|
|
#include <AK/TypeCasts.h>
|
|
#include <Ladybird/Qt/TabBar.h>
|
|
#include <Ladybird/Utilities.h>
|
|
#include <LibWeb/CSS/PreferredColorScheme.h>
|
|
#include <LibWeb/Loader/ResourceLoader.h>
|
|
#include <LibWebView/CookieJar.h>
|
|
#include <LibWebView/UserAgent.h>
|
|
#include <QAction>
|
|
#include <QActionGroup>
|
|
#include <QApplication>
|
|
#include <QClipboard>
|
|
#include <QGuiApplication>
|
|
#include <QInputDialog>
|
|
#include <QPlainTextEdit>
|
|
#include <QShortcut>
|
|
#include <QTabBar>
|
|
#include <QWindow>
|
|
|
|
namespace Ladybird {
|
|
|
|
static QIcon const& app_icon()
|
|
{
|
|
static QIcon icon;
|
|
if (icon.isNull()) {
|
|
QPixmap pixmap;
|
|
pixmap.load(":/Icons/ladybird.png");
|
|
icon = QIcon(pixmap);
|
|
}
|
|
return icon;
|
|
}
|
|
|
|
BrowserWindow::BrowserWindow(Vector<URL::URL> const& initial_urls, WebView::CookieJar& cookie_jar, WebContentOptions const& web_content_options, StringView webdriver_content_ipc_path)
|
|
: m_tabs_container(new TabWidget(this))
|
|
, m_cookie_jar(cookie_jar)
|
|
, m_web_content_options(web_content_options)
|
|
, m_webdriver_content_ipc_path(webdriver_content_ipc_path)
|
|
{
|
|
setWindowIcon(app_icon());
|
|
|
|
// Listen for DPI changes
|
|
m_device_pixel_ratio = devicePixelRatio();
|
|
m_current_screen = screen();
|
|
if (QT_VERSION < QT_VERSION_CHECK(6, 6, 0) || QGuiApplication::platformName() != "wayland") {
|
|
setAttribute(Qt::WA_NativeWindow);
|
|
setAttribute(Qt::WA_DontCreateNativeAncestors);
|
|
QObject::connect(m_current_screen, &QScreen::logicalDotsPerInchChanged, this, &BrowserWindow::device_pixel_ratio_changed);
|
|
QObject::connect(windowHandle(), &QWindow::screenChanged, this, [this](QScreen* screen) {
|
|
if (m_device_pixel_ratio != devicePixelRatio())
|
|
device_pixel_ratio_changed(devicePixelRatio());
|
|
|
|
// Listen for logicalDotsPerInchChanged signals on new screen
|
|
QObject::disconnect(m_current_screen, &QScreen::logicalDotsPerInchChanged, nullptr, nullptr);
|
|
m_current_screen = screen;
|
|
QObject::connect(m_current_screen, &QScreen::logicalDotsPerInchChanged, this, &BrowserWindow::device_pixel_ratio_changed);
|
|
});
|
|
}
|
|
|
|
auto* menu = menuBar()->addMenu("&File");
|
|
|
|
auto* about_action = new QAction("&About Ladybird", this);
|
|
menu->addAction(about_action);
|
|
|
|
menu->addSeparator();
|
|
|
|
auto* new_tab_action = new QAction("New &Tab", this);
|
|
new_tab_action->setIcon(load_icon_from_uri("resource://icons/16x16/new-tab.png"sv));
|
|
new_tab_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::AddTab));
|
|
menu->addAction(new_tab_action);
|
|
|
|
auto* close_current_tab_action = new QAction("&Close Current Tab", this);
|
|
close_current_tab_action->setIcon(load_icon_from_uri("resource://icons/16x16/close-tab.png"sv));
|
|
close_current_tab_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Close));
|
|
menu->addAction(close_current_tab_action);
|
|
|
|
auto* open_file_action = new QAction("&Open File...", this);
|
|
open_file_action->setIcon(load_icon_from_uri("resource://icons/16x16/filetype-folder-open.png"sv));
|
|
open_file_action->setShortcut(QKeySequence(QKeySequence::StandardKey::Open));
|
|
menu->addAction(open_file_action);
|
|
|
|
menu->addSeparator();
|
|
|
|
auto* quit_action = new QAction("&Quit", this);
|
|
quit_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Quit));
|
|
menu->addAction(quit_action);
|
|
|
|
auto* edit_menu = menuBar()->addMenu("&Edit");
|
|
|
|
m_copy_selection_action = new QAction("&Copy", this);
|
|
m_copy_selection_action->setIcon(load_icon_from_uri("resource://icons/16x16/edit-copy.png"sv));
|
|
m_copy_selection_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Copy));
|
|
edit_menu->addAction(m_copy_selection_action);
|
|
QObject::connect(m_copy_selection_action, &QAction::triggered, this, &BrowserWindow::copy_selected_text);
|
|
|
|
m_paste_action = new QAction("&Paste", this);
|
|
m_paste_action->setIcon(load_icon_from_uri("resource://icons/16x16/paste.png"sv));
|
|
m_paste_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Paste));
|
|
edit_menu->addAction(m_paste_action);
|
|
QObject::connect(m_paste_action, &QAction::triggered, this, &BrowserWindow::paste);
|
|
|
|
m_select_all_action = new QAction("Select &All", this);
|
|
m_select_all_action->setIcon(load_icon_from_uri("resource://icons/16x16/select-all.png"sv));
|
|
m_select_all_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::SelectAll));
|
|
edit_menu->addAction(m_select_all_action);
|
|
QObject::connect(m_select_all_action, &QAction::triggered, this, &BrowserWindow::select_all);
|
|
|
|
edit_menu->addSeparator();
|
|
|
|
auto* settings_action = new QAction("&Settings", this);
|
|
settings_action->setIcon(load_icon_from_uri("resource://icons/16x16/settings.png"sv));
|
|
settings_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Preferences));
|
|
edit_menu->addAction(settings_action);
|
|
|
|
auto* view_menu = menuBar()->addMenu("&View");
|
|
|
|
auto* open_next_tab_action = new QAction("Open &Next Tab", this);
|
|
open_next_tab_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_PageDown));
|
|
view_menu->addAction(open_next_tab_action);
|
|
QObject::connect(open_next_tab_action, &QAction::triggered, this, &BrowserWindow::open_next_tab);
|
|
|
|
auto* open_previous_tab_action = new QAction("Open &Previous Tab", this);
|
|
open_previous_tab_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_PageUp));
|
|
view_menu->addAction(open_previous_tab_action);
|
|
QObject::connect(open_previous_tab_action, &QAction::triggered, this, &BrowserWindow::open_previous_tab);
|
|
|
|
view_menu->addSeparator();
|
|
|
|
m_zoom_menu = view_menu->addMenu("&Zoom");
|
|
|
|
auto* zoom_in_action = new QAction("Zoom &In", this);
|
|
zoom_in_action->setIcon(load_icon_from_uri("resource://icons/16x16/zoom-in.png"sv));
|
|
auto zoom_in_shortcuts = QKeySequence::keyBindings(QKeySequence::StandardKey::ZoomIn);
|
|
zoom_in_shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_Equal));
|
|
zoom_in_action->setShortcuts(zoom_in_shortcuts);
|
|
m_zoom_menu->addAction(zoom_in_action);
|
|
QObject::connect(zoom_in_action, &QAction::triggered, this, &BrowserWindow::zoom_in);
|
|
|
|
auto* zoom_out_action = new QAction("Zoom &Out", this);
|
|
zoom_out_action->setIcon(load_icon_from_uri("resource://icons/16x16/zoom-out.png"sv));
|
|
zoom_out_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::ZoomOut));
|
|
m_zoom_menu->addAction(zoom_out_action);
|
|
QObject::connect(zoom_out_action, &QAction::triggered, this, &BrowserWindow::zoom_out);
|
|
|
|
auto* reset_zoom_action = new QAction("&Reset Zoom", this);
|
|
reset_zoom_action->setIcon(load_icon_from_uri("resource://icons/16x16/zoom-reset.png"sv));
|
|
reset_zoom_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_0));
|
|
m_zoom_menu->addAction(reset_zoom_action);
|
|
QObject::connect(reset_zoom_action, &QAction::triggered, this, &BrowserWindow::reset_zoom);
|
|
|
|
view_menu->addSeparator();
|
|
|
|
auto* color_scheme_menu = view_menu->addMenu("&Color Scheme");
|
|
|
|
auto* color_scheme_group = new QActionGroup(this);
|
|
|
|
auto* auto_color_scheme = new QAction("&Auto", this);
|
|
auto_color_scheme->setCheckable(true);
|
|
color_scheme_group->addAction(auto_color_scheme);
|
|
color_scheme_menu->addAction(auto_color_scheme);
|
|
QObject::connect(auto_color_scheme, &QAction::triggered, this, &BrowserWindow::enable_auto_color_scheme);
|
|
|
|
auto* light_color_scheme = new QAction("&Light", this);
|
|
light_color_scheme->setCheckable(true);
|
|
color_scheme_group->addAction(light_color_scheme);
|
|
color_scheme_menu->addAction(light_color_scheme);
|
|
QObject::connect(light_color_scheme, &QAction::triggered, this, &BrowserWindow::enable_light_color_scheme);
|
|
|
|
auto* dark_color_scheme = new QAction("&Dark", this);
|
|
dark_color_scheme->setCheckable(true);
|
|
color_scheme_group->addAction(dark_color_scheme);
|
|
color_scheme_menu->addAction(dark_color_scheme);
|
|
QObject::connect(dark_color_scheme, &QAction::triggered, this, &BrowserWindow::enable_dark_color_scheme);
|
|
|
|
auto_color_scheme->setChecked(true);
|
|
|
|
auto* inspect_menu = menuBar()->addMenu("&Inspect");
|
|
|
|
m_view_source_action = new QAction("View &Source", this);
|
|
m_view_source_action->setIcon(load_icon_from_uri("resource://icons/16x16/filetype-html.png"sv));
|
|
m_view_source_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_U));
|
|
inspect_menu->addAction(m_view_source_action);
|
|
QObject::connect(m_view_source_action, &QAction::triggered, this, [this] {
|
|
if (m_current_tab) {
|
|
m_current_tab->view().get_source();
|
|
}
|
|
});
|
|
|
|
auto* inspector_action = new QAction("Open &Inspector", this);
|
|
inspector_action->setIcon(load_icon_from_uri("resource://icons/browser/dom-tree.png"sv));
|
|
inspector_action->setShortcuts({ QKeySequence("Ctrl+Shift+I"), QKeySequence("F12") });
|
|
inspect_menu->addAction(inspector_action);
|
|
QObject::connect(inspector_action, &QAction::triggered, this, [this] {
|
|
if (m_current_tab) {
|
|
m_current_tab->show_inspector_window();
|
|
}
|
|
});
|
|
|
|
auto* task_manager_action = new QAction("Open Task &Manager", this);
|
|
task_manager_action->setIcon(load_icon_from_uri("resource://icons/16x16/app-system-monitor.png"sv));
|
|
task_manager_action->setShortcuts({ QKeySequence("Ctrl+Shift+M") });
|
|
inspect_menu->addAction(task_manager_action);
|
|
QObject::connect(task_manager_action, &QAction::triggered, this, [this] {
|
|
show_task_manager_window();
|
|
});
|
|
|
|
auto* debug_menu = menuBar()->addMenu("&Debug");
|
|
|
|
auto* dump_session_history_tree_action = new QAction("Dump Session History Tree", this);
|
|
debug_menu->addAction(dump_session_history_tree_action);
|
|
QObject::connect(dump_session_history_tree_action, &QAction::triggered, this, [this] {
|
|
debug_request("dump-session-history");
|
|
});
|
|
|
|
auto* dump_dom_tree_action = new QAction("Dump &DOM Tree", this);
|
|
dump_dom_tree_action->setIcon(load_icon_from_uri("resource://icons/browser/dom-tree.png"sv));
|
|
debug_menu->addAction(dump_dom_tree_action);
|
|
QObject::connect(dump_dom_tree_action, &QAction::triggered, this, [this] {
|
|
debug_request("dump-dom-tree");
|
|
});
|
|
|
|
auto* dump_layout_tree_action = new QAction("Dump &Layout Tree", this);
|
|
dump_layout_tree_action->setIcon(load_icon_from_uri("resource://icons/16x16/layout.png"sv));
|
|
debug_menu->addAction(dump_layout_tree_action);
|
|
QObject::connect(dump_layout_tree_action, &QAction::triggered, this, [this] {
|
|
debug_request("dump-layout-tree");
|
|
});
|
|
|
|
auto* dump_paint_tree_action = new QAction("Dump &Paint Tree", this);
|
|
dump_paint_tree_action->setIcon(load_icon_from_uri("resource://icons/16x16/layout.png"sv));
|
|
debug_menu->addAction(dump_paint_tree_action);
|
|
QObject::connect(dump_paint_tree_action, &QAction::triggered, this, [this] {
|
|
debug_request("dump-paint-tree");
|
|
});
|
|
|
|
auto* dump_stacking_context_tree_action = new QAction("Dump S&tacking Context Tree", this);
|
|
dump_stacking_context_tree_action->setIcon(load_icon_from_uri("resource://icons/16x16/layers.png"sv));
|
|
debug_menu->addAction(dump_stacking_context_tree_action);
|
|
QObject::connect(dump_stacking_context_tree_action, &QAction::triggered, this, [this] {
|
|
debug_request("dump-stacking-context-tree");
|
|
});
|
|
|
|
auto* dump_style_sheets_action = new QAction("Dump &Style Sheets", this);
|
|
dump_style_sheets_action->setIcon(load_icon_from_uri("resource://icons/16x16/filetype-css.png"sv));
|
|
debug_menu->addAction(dump_style_sheets_action);
|
|
QObject::connect(dump_style_sheets_action, &QAction::triggered, this, [this] {
|
|
debug_request("dump-style-sheets");
|
|
});
|
|
|
|
auto* dump_styles_action = new QAction("Dump &All Resolved Styles", this);
|
|
dump_styles_action->setIcon(load_icon_from_uri("resource://icons/16x16/filetype-css.png"sv));
|
|
debug_menu->addAction(dump_styles_action);
|
|
QObject::connect(dump_styles_action, &QAction::triggered, this, [this] {
|
|
debug_request("dump-all-resolved-styles");
|
|
});
|
|
|
|
auto* dump_history_action = new QAction("Dump &History", this);
|
|
dump_history_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_H));
|
|
dump_history_action->setIcon(load_icon_from_uri("resource://icons/16x16/history.png"sv));
|
|
debug_menu->addAction(dump_history_action);
|
|
QObject::connect(dump_history_action, &QAction::triggered, this, [this] {
|
|
debug_request("dump-history");
|
|
});
|
|
|
|
auto* dump_cookies_action = new QAction("Dump C&ookies", this);
|
|
dump_cookies_action->setIcon(load_icon_from_uri("resource://icons/browser/cookie.png"sv));
|
|
debug_menu->addAction(dump_cookies_action);
|
|
QObject::connect(dump_cookies_action, &QAction::triggered, this, [this] {
|
|
m_cookie_jar.dump_cookies();
|
|
});
|
|
|
|
auto* dump_local_storage_action = new QAction("Dump Loc&al Storage", this);
|
|
dump_local_storage_action->setIcon(load_icon_from_uri("resource://icons/browser/local-storage.png"sv));
|
|
debug_menu->addAction(dump_local_storage_action);
|
|
QObject::connect(dump_local_storage_action, &QAction::triggered, this, [this] {
|
|
debug_request("dump-local-storage");
|
|
});
|
|
|
|
debug_menu->addSeparator();
|
|
|
|
auto* show_line_box_borders_action = new QAction("Show Line Box Borders", this);
|
|
show_line_box_borders_action->setCheckable(true);
|
|
debug_menu->addAction(show_line_box_borders_action);
|
|
QObject::connect(show_line_box_borders_action, &QAction::triggered, this, [this, show_line_box_borders_action] {
|
|
bool state = show_line_box_borders_action->isChecked();
|
|
debug_request("set-line-box-borders", state ? "on" : "off");
|
|
});
|
|
|
|
debug_menu->addSeparator();
|
|
|
|
auto* collect_garbage_action = new QAction("Collect &Garbage", this);
|
|
collect_garbage_action->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_G));
|
|
collect_garbage_action->setIcon(load_icon_from_uri("resource://icons/16x16/trash-can.png"sv));
|
|
debug_menu->addAction(collect_garbage_action);
|
|
QObject::connect(collect_garbage_action, &QAction::triggered, this, [this] {
|
|
debug_request("collect-garbage");
|
|
});
|
|
|
|
auto* dump_gc_graph_action = new QAction("Dump GC graph", this);
|
|
debug_menu->addAction(dump_gc_graph_action);
|
|
QObject::connect(dump_gc_graph_action, &QAction::triggered, this, [this] {
|
|
if (m_current_tab) {
|
|
auto gc_graph_path = m_current_tab->view().dump_gc_graph();
|
|
warnln("\033[33;1mDumped GC-graph into {}"
|
|
"\033[0m",
|
|
gc_graph_path);
|
|
}
|
|
});
|
|
|
|
auto* clear_cache_action = new QAction("Clear &Cache", this);
|
|
clear_cache_action->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_C));
|
|
clear_cache_action->setIcon(load_icon_from_uri("resource://icons/browser/clear-cache.png"sv));
|
|
debug_menu->addAction(clear_cache_action);
|
|
QObject::connect(clear_cache_action, &QAction::triggered, this, [this] {
|
|
debug_request("clear-cache");
|
|
});
|
|
|
|
auto* spoof_user_agent_menu = debug_menu->addMenu("Spoof &User Agent");
|
|
spoof_user_agent_menu->setIcon(load_icon_from_uri("resource://icons/16x16/spoof.png"sv));
|
|
|
|
auto* user_agent_group = new QActionGroup(this);
|
|
|
|
auto add_user_agent = [this, &user_agent_group, &spoof_user_agent_menu](auto name, auto const& user_agent) {
|
|
auto* action = new QAction(qstring_from_ak_string(name), this);
|
|
action->setCheckable(true);
|
|
user_agent_group->addAction(action);
|
|
spoof_user_agent_menu->addAction(action);
|
|
QObject::connect(action, &QAction::triggered, this, [this, user_agent] {
|
|
debug_request("spoof-user-agent", user_agent);
|
|
debug_request("clear-cache"); // clear the cache to ensure requests are re-done with the new user agent
|
|
});
|
|
return action;
|
|
};
|
|
|
|
auto* disable_spoofing = add_user_agent("Disabled"sv, Web::default_user_agent);
|
|
disable_spoofing->setChecked(true);
|
|
for (auto const& user_agent : WebView::user_agents)
|
|
add_user_agent(user_agent.key, user_agent.value.to_byte_string());
|
|
|
|
auto* custom_user_agent_action = new QAction("Custom...", this);
|
|
custom_user_agent_action->setCheckable(true);
|
|
user_agent_group->addAction(custom_user_agent_action);
|
|
spoof_user_agent_menu->addAction(custom_user_agent_action);
|
|
QObject::connect(custom_user_agent_action, &QAction::triggered, this, [this, disable_spoofing] {
|
|
auto user_agent = QInputDialog::getText(this, "Custom User Agent", "Enter User Agent:");
|
|
if (!user_agent.isEmpty()) {
|
|
debug_request("spoof-user-agent", ak_byte_string_from_qstring(user_agent));
|
|
debug_request("clear-cache"); // clear the cache to ensure requests are re-done with the new user agent
|
|
} else {
|
|
disable_spoofing->activate(QAction::Trigger);
|
|
}
|
|
});
|
|
|
|
debug_menu->addSeparator();
|
|
|
|
auto* enable_scripting_action = new QAction("Enable Scripting", this);
|
|
enable_scripting_action->setCheckable(true);
|
|
enable_scripting_action->setChecked(true);
|
|
debug_menu->addAction(enable_scripting_action);
|
|
QObject::connect(enable_scripting_action, &QAction::triggered, this, [this, enable_scripting_action] {
|
|
bool state = enable_scripting_action->isChecked();
|
|
debug_request("scripting", state ? "on" : "off");
|
|
});
|
|
|
|
auto* block_pop_ups_action = new QAction("Block Pop-ups", this);
|
|
block_pop_ups_action->setCheckable(true);
|
|
block_pop_ups_action->setChecked(true);
|
|
debug_menu->addAction(block_pop_ups_action);
|
|
QObject::connect(block_pop_ups_action, &QAction::triggered, this, [this, block_pop_ups_action] {
|
|
bool state = block_pop_ups_action->isChecked();
|
|
debug_request("block-pop-ups", state ? "on" : "off");
|
|
});
|
|
|
|
auto* enable_same_origin_policy_action = new QAction("Enable Same-Origin Policy", this);
|
|
enable_same_origin_policy_action->setCheckable(true);
|
|
debug_menu->addAction(enable_same_origin_policy_action);
|
|
QObject::connect(enable_same_origin_policy_action, &QAction::triggered, this, [this, enable_same_origin_policy_action] {
|
|
bool state = enable_same_origin_policy_action->isChecked();
|
|
debug_request("same-origin-policy", state ? "on" : "off");
|
|
});
|
|
|
|
QObject::connect(about_action, &QAction::triggered, this, [this] {
|
|
new_tab_from_url("about:version"sv, Web::HTML::ActivateTab::Yes);
|
|
});
|
|
QObject::connect(new_tab_action, &QAction::triggered, this, [this] {
|
|
new_tab_from_url(ak_url_from_qstring(Settings::the()->new_tab_page()), Web::HTML::ActivateTab::Yes);
|
|
});
|
|
QObject::connect(open_file_action, &QAction::triggered, this, &BrowserWindow::open_file);
|
|
QObject::connect(settings_action, &QAction::triggered, this, [this] {
|
|
if (!m_settings_dialog) {
|
|
m_settings_dialog = new SettingsDialog(this);
|
|
}
|
|
|
|
m_settings_dialog->show();
|
|
m_settings_dialog->setFocus();
|
|
});
|
|
QObject::connect(quit_action, &QAction::triggered, this, &QMainWindow::close);
|
|
QObject::connect(m_tabs_container, &QTabWidget::currentChanged, [this](int index) {
|
|
setWindowTitle(QString("%1 - Ladybird").arg(m_tabs_container->tabText(index)));
|
|
set_current_tab(verify_cast<Tab>(m_tabs_container->widget(index)));
|
|
});
|
|
QObject::connect(m_tabs_container, &QTabWidget::tabCloseRequested, this, &BrowserWindow::close_tab);
|
|
QObject::connect(close_current_tab_action, &QAction::triggered, this, &BrowserWindow::close_current_tab);
|
|
|
|
m_inspect_dom_node_action = new QAction("&Inspect Element", this);
|
|
connect(m_inspect_dom_node_action, &QAction::triggered, this, [this] {
|
|
if (m_current_tab)
|
|
m_current_tab->show_inspector_window(Tab::InspectorTarget::HoveredElement);
|
|
});
|
|
m_go_back_action = new QAction("Go Back", this);
|
|
connect(m_go_back_action, &QAction::triggered, this, [this] {
|
|
if (m_current_tab)
|
|
m_current_tab->back();
|
|
});
|
|
m_go_forward_action = new QAction("Go Forward", this);
|
|
connect(m_go_forward_action, &QAction::triggered, this, [this] {
|
|
if (m_current_tab)
|
|
m_current_tab->forward();
|
|
});
|
|
m_reload_action = new QAction("&Reload", this);
|
|
connect(m_reload_action, &QAction::triggered, this, [this] {
|
|
if (m_current_tab)
|
|
m_current_tab->reload();
|
|
});
|
|
|
|
m_go_back_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Back));
|
|
m_go_forward_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Forward));
|
|
m_reload_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Refresh));
|
|
m_go_back_action->setEnabled(false);
|
|
m_go_forward_action->setEnabled(false);
|
|
m_reload_action->setEnabled(false);
|
|
|
|
for (int i = 0; i <= 7; ++i) {
|
|
new QShortcut(QKeySequence(Qt::CTRL | static_cast<Qt::Key>(Qt::Key_1 + i)), this, [this, i] {
|
|
if (m_tabs_container->count() <= 1)
|
|
return;
|
|
|
|
m_tabs_container->setCurrentIndex(min(i, m_tabs_container->count() - 1));
|
|
});
|
|
}
|
|
|
|
new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_9), this, [this] {
|
|
if (m_tabs_container->count() <= 1)
|
|
return;
|
|
|
|
m_tabs_container->setCurrentIndex(m_tabs_container->count() - 1);
|
|
});
|
|
|
|
for (size_t i = 0; i < initial_urls.size(); ++i) {
|
|
new_tab_from_url(initial_urls[i], (i == 0) ? Web::HTML::ActivateTab::Yes : Web::HTML::ActivateTab::No);
|
|
}
|
|
|
|
setCentralWidget(m_tabs_container);
|
|
setContextMenuPolicy(Qt::PreventContextMenu);
|
|
}
|
|
|
|
void BrowserWindow::set_current_tab(Tab* tab)
|
|
{
|
|
m_current_tab = tab;
|
|
if (tab)
|
|
update_displayed_zoom_level();
|
|
}
|
|
|
|
void BrowserWindow::debug_request(ByteString const& request, ByteString const& argument)
|
|
{
|
|
if (!m_current_tab)
|
|
return;
|
|
m_current_tab->debug_request(request, argument);
|
|
}
|
|
|
|
Tab& BrowserWindow::new_tab_from_url(URL::URL const& url, Web::HTML::ActivateTab activate_tab)
|
|
{
|
|
auto& tab = create_new_tab(activate_tab);
|
|
tab.navigate(url);
|
|
return tab;
|
|
}
|
|
|
|
Tab& BrowserWindow::new_tab_from_content(StringView html, Web::HTML::ActivateTab activate_tab)
|
|
{
|
|
auto& tab = create_new_tab(activate_tab);
|
|
tab.load_html(html);
|
|
return tab;
|
|
}
|
|
|
|
Tab& BrowserWindow::new_child_tab(Web::HTML::ActivateTab activate_tab, Tab& parent, Web::HTML::WebViewHints hints, Optional<u64> page_index)
|
|
{
|
|
return create_new_tab(activate_tab, parent, hints, page_index);
|
|
}
|
|
|
|
Tab& BrowserWindow::create_new_tab(Web::HTML::ActivateTab activate_tab, Tab& parent, Web::HTML::WebViewHints, Optional<u64> page_index)
|
|
{
|
|
if (!page_index.has_value())
|
|
return create_new_tab(activate_tab);
|
|
|
|
// FIXME: Respect hints for:
|
|
// popup: Create new window
|
|
// width, height: size of window
|
|
// screen_x, screen_y: positioning of window on the screen
|
|
auto* tab = new Tab(this, m_web_content_options, m_webdriver_content_ipc_path, parent.view().client(), page_index.value());
|
|
|
|
VERIFY(m_current_tab != nullptr);
|
|
|
|
m_tabs_container->addTab(tab, "New Tab");
|
|
if (activate_tab == Web::HTML::ActivateTab::Yes)
|
|
m_tabs_container->setCurrentWidget(tab);
|
|
initialize_tab(tab);
|
|
return *tab;
|
|
}
|
|
|
|
Tab& BrowserWindow::create_new_tab(Web::HTML::ActivateTab activate_tab)
|
|
{
|
|
auto* tab = new Tab(this, m_web_content_options, m_webdriver_content_ipc_path);
|
|
|
|
if (m_current_tab == nullptr) {
|
|
set_current_tab(tab);
|
|
}
|
|
|
|
m_tabs_container->addTab(tab, "New Tab");
|
|
if (activate_tab == Web::HTML::ActivateTab::Yes)
|
|
m_tabs_container->setCurrentWidget(tab);
|
|
initialize_tab(tab);
|
|
|
|
return *tab;
|
|
}
|
|
|
|
void BrowserWindow::initialize_tab(Tab* tab)
|
|
{
|
|
QObject::connect(tab, &Tab::title_changed, this, &BrowserWindow::tab_title_changed);
|
|
QObject::connect(tab, &Tab::favicon_changed, this, &BrowserWindow::tab_favicon_changed);
|
|
QObject::connect(tab, &Tab::audio_play_state_changed, this, &BrowserWindow::tab_audio_play_state_changed);
|
|
|
|
QObject::connect(&tab->view(), &WebContentView::urls_dropped, this, [this](auto& urls) {
|
|
VERIFY(urls.size());
|
|
m_current_tab->navigate(ak_url_from_qurl(urls[0]));
|
|
|
|
for (qsizetype i = 1; i < urls.size(); ++i)
|
|
new_tab_from_url(ak_url_from_qurl(urls[i]), Web::HTML::ActivateTab::No);
|
|
});
|
|
|
|
tab->view().on_new_web_view = [this, tab](auto activate_tab, Web::HTML::WebViewHints hints, Optional<u64> page_index) {
|
|
auto& new_tab = new_child_tab(activate_tab, *tab, hints, page_index);
|
|
return new_tab.view().handle();
|
|
};
|
|
|
|
tab->view().on_tab_open_request = [this](auto url, auto activate_tab) {
|
|
auto& tab = new_tab_from_url(url, activate_tab);
|
|
return tab.view().handle();
|
|
};
|
|
|
|
tab->view().on_link_click = [this](auto url, auto target, unsigned modifiers) {
|
|
// TODO: maybe activate tabs according to some configuration, this is just normal current browser behavior
|
|
if (modifiers == Mod_Ctrl) {
|
|
m_current_tab->view().on_tab_open_request(url, Web::HTML::ActivateTab::No);
|
|
} else if (target == "_blank") {
|
|
m_current_tab->view().on_tab_open_request(url, Web::HTML::ActivateTab::Yes);
|
|
} else {
|
|
m_current_tab->view().load(url);
|
|
}
|
|
};
|
|
|
|
tab->view().on_link_middle_click = [this](auto url, auto target, unsigned modifiers) {
|
|
m_current_tab->view().on_link_click(url, target, Mod_Ctrl);
|
|
(void)modifiers;
|
|
};
|
|
|
|
tab->view().on_get_all_cookies = [this](auto const& url) {
|
|
return m_cookie_jar.get_all_cookies(url);
|
|
};
|
|
|
|
tab->view().on_get_named_cookie = [this](auto const& url, auto const& name) {
|
|
return m_cookie_jar.get_named_cookie(url, name);
|
|
};
|
|
|
|
tab->view().on_get_cookie = [this](auto& url, auto source) {
|
|
return m_cookie_jar.get_cookie(url, source);
|
|
};
|
|
|
|
tab->view().on_set_cookie = [this](auto& url, auto& cookie, auto source) {
|
|
m_cookie_jar.set_cookie(url, cookie, source);
|
|
};
|
|
|
|
tab->view().on_update_cookie = [this](auto const& cookie) {
|
|
m_cookie_jar.update_cookie(cookie);
|
|
};
|
|
|
|
m_tabs_container->setTabIcon(m_tabs_container->indexOf(tab), tab->favicon());
|
|
create_close_button_for_tab(tab);
|
|
|
|
tab->focus_location_editor();
|
|
}
|
|
|
|
void BrowserWindow::activate_tab(int index)
|
|
{
|
|
m_tabs_container->setCurrentIndex(index);
|
|
}
|
|
|
|
void BrowserWindow::close_tab(int index)
|
|
{
|
|
auto* tab = m_tabs_container->widget(index);
|
|
m_tabs_container->removeTab(index);
|
|
tab->deleteLater();
|
|
}
|
|
|
|
void BrowserWindow::open_file()
|
|
{
|
|
m_current_tab->open_file();
|
|
}
|
|
|
|
void BrowserWindow::close_current_tab()
|
|
{
|
|
close_tab(m_tabs_container->currentIndex());
|
|
|
|
if (m_tabs_container->count() == 0)
|
|
close();
|
|
}
|
|
|
|
int BrowserWindow::tab_index(Tab* tab)
|
|
{
|
|
return m_tabs_container->indexOf(tab);
|
|
}
|
|
|
|
void BrowserWindow::device_pixel_ratio_changed(qreal dpi)
|
|
{
|
|
m_device_pixel_ratio = dpi;
|
|
for_each_tab([this](auto& tab) {
|
|
tab.view().set_device_pixel_ratio(m_device_pixel_ratio);
|
|
});
|
|
}
|
|
|
|
void BrowserWindow::tab_title_changed(int index, QString const& title)
|
|
{
|
|
m_tabs_container->setTabText(index, title);
|
|
m_tabs_container->setTabToolTip(index, title);
|
|
|
|
if (m_tabs_container->currentIndex() == index)
|
|
setWindowTitle(QString("%1 - Ladybird").arg(title));
|
|
}
|
|
|
|
void BrowserWindow::tab_favicon_changed(int index, QIcon const& icon)
|
|
{
|
|
m_tabs_container->setTabIcon(index, icon);
|
|
}
|
|
|
|
void BrowserWindow::create_close_button_for_tab(Tab* tab)
|
|
{
|
|
auto index = m_tabs_container->indexOf(tab);
|
|
m_tabs_container->setTabIcon(index, tab->favicon());
|
|
|
|
auto* button = new TabBarButton(create_tvg_icon_with_theme_colors("close", palette()));
|
|
auto position = audio_button_position_for_tab(index) == QTabBar::LeftSide ? QTabBar::RightSide : QTabBar::LeftSide;
|
|
|
|
connect(button, &QPushButton::clicked, this, [this, tab]() {
|
|
auto index = m_tabs_container->indexOf(tab);
|
|
close_tab(index);
|
|
});
|
|
|
|
m_tabs_container->tabBar()->setTabButton(index, position, button);
|
|
}
|
|
|
|
void BrowserWindow::tab_audio_play_state_changed(int index, Web::HTML::AudioPlayState play_state)
|
|
{
|
|
auto* tab = verify_cast<Tab>(m_tabs_container->widget(index));
|
|
auto position = audio_button_position_for_tab(index);
|
|
|
|
switch (play_state) {
|
|
case Web::HTML::AudioPlayState::Paused:
|
|
if (tab->view().page_mute_state() == Web::HTML::MuteState::Unmuted)
|
|
m_tabs_container->tabBar()->setTabButton(index, position, nullptr);
|
|
break;
|
|
|
|
case Web::HTML::AudioPlayState::Playing:
|
|
auto* button = new TabBarButton(icon_for_page_mute_state(*tab));
|
|
button->setToolTip(tool_tip_for_page_mute_state(*tab));
|
|
button->setObjectName("LadybirdAudioState");
|
|
|
|
connect(button, &QPushButton::clicked, this, [this, tab, position]() {
|
|
tab->view().toggle_page_mute_state();
|
|
auto index = tab_index(tab);
|
|
|
|
switch (tab->view().audio_play_state()) {
|
|
case Web::HTML::AudioPlayState::Paused:
|
|
m_tabs_container->tabBar()->setTabButton(index, position, nullptr);
|
|
break;
|
|
case Web::HTML::AudioPlayState::Playing:
|
|
auto* button = m_tabs_container->tabBar()->tabButton(index, position);
|
|
verify_cast<TabBarButton>(button)->setIcon(icon_for_page_mute_state(*tab));
|
|
button->setToolTip(tool_tip_for_page_mute_state(*tab));
|
|
break;
|
|
}
|
|
});
|
|
|
|
m_tabs_container->tabBar()->setTabButton(index, position, button);
|
|
break;
|
|
}
|
|
}
|
|
|
|
QIcon BrowserWindow::icon_for_page_mute_state(Tab& tab) const
|
|
{
|
|
switch (tab.view().page_mute_state()) {
|
|
case Web::HTML::MuteState::Muted:
|
|
return style()->standardIcon(QStyle::SP_MediaVolumeMuted);
|
|
case Web::HTML::MuteState::Unmuted:
|
|
return style()->standardIcon(QStyle::SP_MediaVolume);
|
|
}
|
|
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
QString BrowserWindow::tool_tip_for_page_mute_state(Tab& tab) const
|
|
{
|
|
switch (tab.view().page_mute_state()) {
|
|
case Web::HTML::MuteState::Muted:
|
|
return "Unmute tab";
|
|
case Web::HTML::MuteState::Unmuted:
|
|
return "Mute tab";
|
|
}
|
|
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
QTabBar::ButtonPosition BrowserWindow::audio_button_position_for_tab(int tab_index) const
|
|
{
|
|
if (auto* button = m_tabs_container->tabBar()->tabButton(tab_index, QTabBar::LeftSide)) {
|
|
if (button->objectName() != "LadybirdAudioState")
|
|
return QTabBar::RightSide;
|
|
}
|
|
|
|
return QTabBar::LeftSide;
|
|
}
|
|
|
|
void BrowserWindow::open_next_tab()
|
|
{
|
|
if (m_tabs_container->count() <= 1)
|
|
return;
|
|
|
|
auto next_index = m_tabs_container->currentIndex() + 1;
|
|
if (next_index >= m_tabs_container->count())
|
|
next_index = 0;
|
|
m_tabs_container->setCurrentIndex(next_index);
|
|
}
|
|
|
|
void BrowserWindow::open_previous_tab()
|
|
{
|
|
if (m_tabs_container->count() <= 1)
|
|
return;
|
|
|
|
auto next_index = m_tabs_container->currentIndex() - 1;
|
|
if (next_index < 0)
|
|
next_index = m_tabs_container->count() - 1;
|
|
m_tabs_container->setCurrentIndex(next_index);
|
|
}
|
|
|
|
void BrowserWindow::enable_auto_color_scheme()
|
|
{
|
|
for_each_tab([](auto& tab) {
|
|
tab.view().set_preferred_color_scheme(Web::CSS::PreferredColorScheme::Auto);
|
|
});
|
|
}
|
|
|
|
void BrowserWindow::enable_light_color_scheme()
|
|
{
|
|
for_each_tab([](auto& tab) {
|
|
tab.view().set_preferred_color_scheme(Web::CSS::PreferredColorScheme::Light);
|
|
});
|
|
}
|
|
|
|
void BrowserWindow::enable_dark_color_scheme()
|
|
{
|
|
for_each_tab([](auto& tab) {
|
|
tab.view().set_preferred_color_scheme(Web::CSS::PreferredColorScheme::Dark);
|
|
});
|
|
}
|
|
|
|
void BrowserWindow::zoom_in()
|
|
{
|
|
if (!m_current_tab)
|
|
return;
|
|
m_current_tab->view().zoom_in();
|
|
update_displayed_zoom_level();
|
|
}
|
|
|
|
void BrowserWindow::zoom_out()
|
|
{
|
|
if (!m_current_tab)
|
|
return;
|
|
m_current_tab->view().zoom_out();
|
|
update_displayed_zoom_level();
|
|
}
|
|
|
|
void BrowserWindow::reset_zoom()
|
|
{
|
|
if (!m_current_tab)
|
|
return;
|
|
m_current_tab->view().reset_zoom();
|
|
update_displayed_zoom_level();
|
|
}
|
|
|
|
void BrowserWindow::update_zoom_menu()
|
|
{
|
|
VERIFY(m_zoom_menu);
|
|
auto zoom_level_text = MUST(String::formatted("&Zoom ({}%)", round_to<int>(m_current_tab->view().zoom_level() * 100)));
|
|
m_zoom_menu->setTitle(qstring_from_ak_string(zoom_level_text));
|
|
}
|
|
|
|
void BrowserWindow::select_all()
|
|
{
|
|
if (!m_current_tab)
|
|
return;
|
|
|
|
m_current_tab->view().select_all();
|
|
}
|
|
|
|
void BrowserWindow::paste()
|
|
{
|
|
if (!m_current_tab)
|
|
return;
|
|
|
|
auto* clipboard = QGuiApplication::clipboard();
|
|
m_current_tab->view().paste(ak_string_from_qstring(clipboard->text()));
|
|
}
|
|
|
|
void BrowserWindow::update_displayed_zoom_level()
|
|
{
|
|
VERIFY(m_current_tab);
|
|
update_zoom_menu();
|
|
m_current_tab->update_reset_zoom_button();
|
|
}
|
|
|
|
void BrowserWindow::copy_selected_text()
|
|
{
|
|
if (!m_current_tab)
|
|
return;
|
|
|
|
auto text = m_current_tab->view().selected_text();
|
|
|
|
auto* clipboard = QGuiApplication::clipboard();
|
|
clipboard->setText(qstring_from_ak_string(text));
|
|
}
|
|
|
|
bool BrowserWindow::event(QEvent* event)
|
|
{
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
|
if (event->type() == QEvent::DevicePixelRatioChange) {
|
|
if (m_device_pixel_ratio != devicePixelRatio())
|
|
device_pixel_ratio_changed(devicePixelRatio());
|
|
}
|
|
#endif
|
|
|
|
return QMainWindow::event(event);
|
|
}
|
|
|
|
void BrowserWindow::resizeEvent(QResizeEvent* event)
|
|
{
|
|
QWidget::resizeEvent(event);
|
|
|
|
for_each_tab([&](auto& tab) {
|
|
tab.view().set_window_size({ frameSize().width() * m_device_pixel_ratio, frameSize().height() * m_device_pixel_ratio });
|
|
});
|
|
}
|
|
|
|
void BrowserWindow::moveEvent(QMoveEvent* event)
|
|
{
|
|
QWidget::moveEvent(event);
|
|
|
|
for_each_tab([&](auto& tab) {
|
|
tab.view().set_window_position({ event->pos().x() * m_device_pixel_ratio, event->pos().y() * m_device_pixel_ratio });
|
|
});
|
|
}
|
|
|
|
void BrowserWindow::wheelEvent(QWheelEvent* event)
|
|
{
|
|
if ((event->modifiers() & Qt::ControlModifier) != 0) {
|
|
if (event->angleDelta().y() > 0)
|
|
zoom_in();
|
|
else if (event->angleDelta().y() < 0)
|
|
zoom_out();
|
|
}
|
|
}
|
|
|
|
bool BrowserWindow::eventFilter(QObject* obj, QEvent* event)
|
|
{
|
|
if (event->type() == QEvent::MouseButtonRelease) {
|
|
auto const* const mouse_event = static_cast<QMouseEvent*>(event);
|
|
if (mouse_event->button() == Qt::MouseButton::MiddleButton) {
|
|
if (obj == m_tabs_container) {
|
|
auto const tab_index = m_tabs_container->tabBar()->tabAt(mouse_event->pos());
|
|
close_tab(tab_index);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return QMainWindow::eventFilter(obj, event);
|
|
}
|
|
|
|
void BrowserWindow::closeEvent(QCloseEvent* event)
|
|
{
|
|
Settings::the()->set_last_position(pos());
|
|
Settings::the()->set_last_size(size());
|
|
Settings::the()->set_is_maximized(isMaximized());
|
|
|
|
QMainWindow::closeEvent(event);
|
|
}
|
|
|
|
void BrowserWindow::show_task_manager_window()
|
|
{
|
|
if (!m_task_manager_window) {
|
|
m_task_manager_window = new TaskManagerWindow(this);
|
|
}
|
|
m_task_manager_window->show();
|
|
m_task_manager_window->activateWindow();
|
|
m_task_manager_window->raise();
|
|
}
|
|
|
|
void BrowserWindow::close_task_manager_window()
|
|
{
|
|
if (m_task_manager_window)
|
|
m_task_manager_window->close();
|
|
}
|
|
|
|
}
|