BrowserWindow.cpp 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128
  1. /*
  2. * Copyright (c) 2022-2023, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2022, Matthew Costa <ucosty@gmail.com>
  4. * Copyright (c) 2022, Filiph Sandström <filiph.sandstrom@filfatstudios.com>
  5. * Copyright (c) 2023, Linus Groh <linusg@serenityos.org>
  6. *
  7. * SPDX-License-Identifier: BSD-2-Clause
  8. */
  9. #include "BrowserWindow.h"
  10. #include "Application.h"
  11. #include "Icon.h"
  12. #include "Settings.h"
  13. #include "SettingsDialog.h"
  14. #include "StringUtils.h"
  15. #include "TaskManagerWindow.h"
  16. #include "WebContentView.h"
  17. #include <AK/TypeCasts.h>
  18. #include <Ladybird/Qt/TabBar.h>
  19. #include <Ladybird/Utilities.h>
  20. #include <LibWeb/CSS/PreferredColorScheme.h>
  21. #include <LibWeb/CSS/PreferredContrast.h>
  22. #include <LibWeb/Loader/UserAgent.h>
  23. #include <LibWebView/CookieJar.h>
  24. #include <LibWebView/UserAgent.h>
  25. #include <QAction>
  26. #include <QActionGroup>
  27. #include <QApplication>
  28. #include <QClipboard>
  29. #include <QGuiApplication>
  30. #include <QInputDialog>
  31. #include <QPlainTextEdit>
  32. #include <QShortcut>
  33. #include <QTabBar>
  34. #include <QWindow>
  35. namespace Ladybird {
  36. static QIcon const& app_icon()
  37. {
  38. static QIcon icon;
  39. if (icon.isNull()) {
  40. QPixmap pixmap;
  41. pixmap.load(":/Icons/ladybird.png");
  42. icon = QIcon(pixmap);
  43. }
  44. return icon;
  45. }
  46. class HamburgerMenu : public QMenu {
  47. public:
  48. using QMenu::QMenu;
  49. virtual ~HamburgerMenu() override = default;
  50. virtual void showEvent(QShowEvent*) override
  51. {
  52. if (!isVisible())
  53. return;
  54. auto* browser_window = verify_cast<BrowserWindow>(parentWidget());
  55. if (!browser_window)
  56. return;
  57. auto* current_tab = browser_window->current_tab();
  58. if (!current_tab)
  59. return;
  60. // Ensure the hamburger menu placed within the browser window.
  61. auto* hamburger_button = current_tab->hamburger_button();
  62. auto button_top_right = hamburger_button->mapToGlobal(hamburger_button->rect().bottomRight());
  63. move(button_top_right - QPoint(rect().width(), 0));
  64. }
  65. };
  66. BrowserWindow::BrowserWindow(Vector<URL::URL> const& initial_urls, WebView::CookieJar& cookie_jar, WebContentOptions const& web_content_options, StringView webdriver_content_ipc_path, bool allow_popups, Tab* parent_tab, Optional<u64> page_index)
  67. : m_tabs_container(new TabWidget(this))
  68. , m_cookie_jar(cookie_jar)
  69. , m_web_content_options(web_content_options)
  70. , m_webdriver_content_ipc_path(webdriver_content_ipc_path)
  71. , m_allow_popups(allow_popups)
  72. {
  73. setWindowIcon(app_icon());
  74. // Listen for DPI changes
  75. m_device_pixel_ratio = devicePixelRatio();
  76. m_current_screen = screen();
  77. if (QT_VERSION < QT_VERSION_CHECK(6, 6, 0) || QGuiApplication::platformName() != "wayland") {
  78. setAttribute(Qt::WA_NativeWindow);
  79. setAttribute(Qt::WA_DontCreateNativeAncestors);
  80. QObject::connect(m_current_screen, &QScreen::logicalDotsPerInchChanged, this, &BrowserWindow::device_pixel_ratio_changed);
  81. QObject::connect(windowHandle(), &QWindow::screenChanged, this, [this](QScreen* screen) {
  82. if (m_device_pixel_ratio != devicePixelRatio())
  83. device_pixel_ratio_changed(devicePixelRatio());
  84. // Listen for logicalDotsPerInchChanged signals on new screen
  85. QObject::disconnect(m_current_screen, &QScreen::logicalDotsPerInchChanged, nullptr, nullptr);
  86. m_current_screen = screen;
  87. QObject::connect(m_current_screen, &QScreen::logicalDotsPerInchChanged, this, &BrowserWindow::device_pixel_ratio_changed);
  88. });
  89. }
  90. m_hamburger_menu = new HamburgerMenu(this);
  91. if (!Settings::the()->show_menubar())
  92. menuBar()->hide();
  93. QObject::connect(Settings::the(), &Settings::show_menubar_changed, this, [this](bool show_menubar) {
  94. menuBar()->setVisible(show_menubar);
  95. });
  96. auto* file_menu = menuBar()->addMenu("&File");
  97. m_new_tab_action = new QAction("New &Tab", this);
  98. m_new_tab_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::AddTab));
  99. m_hamburger_menu->addAction(m_new_tab_action);
  100. file_menu->addAction(m_new_tab_action);
  101. m_new_window_action = new QAction("New &Window", this);
  102. m_new_window_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::New));
  103. m_hamburger_menu->addAction(m_new_window_action);
  104. file_menu->addAction(m_new_window_action);
  105. auto* close_current_tab_action = new QAction("&Close Current Tab", this);
  106. close_current_tab_action->setIcon(load_icon_from_uri("resource://icons/16x16/close-tab.png"sv));
  107. close_current_tab_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Close));
  108. m_hamburger_menu->addAction(close_current_tab_action);
  109. file_menu->addAction(close_current_tab_action);
  110. auto* open_file_action = new QAction("&Open File...", this);
  111. open_file_action->setIcon(load_icon_from_uri("resource://icons/16x16/filetype-folder-open.png"sv));
  112. open_file_action->setShortcut(QKeySequence(QKeySequence::StandardKey::Open));
  113. m_hamburger_menu->addAction(open_file_action);
  114. file_menu->addAction(open_file_action);
  115. m_hamburger_menu->addSeparator();
  116. auto* edit_menu = m_hamburger_menu->addMenu("&Edit");
  117. menuBar()->addMenu(edit_menu);
  118. m_copy_selection_action = new QAction("&Copy", this);
  119. m_copy_selection_action->setIcon(load_icon_from_uri("resource://icons/16x16/edit-copy.png"sv));
  120. m_copy_selection_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Copy));
  121. edit_menu->addAction(m_copy_selection_action);
  122. QObject::connect(m_copy_selection_action, &QAction::triggered, this, &BrowserWindow::copy_selected_text);
  123. m_paste_action = new QAction("&Paste", this);
  124. m_paste_action->setIcon(load_icon_from_uri("resource://icons/16x16/paste.png"sv));
  125. m_paste_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Paste));
  126. edit_menu->addAction(m_paste_action);
  127. QObject::connect(m_paste_action, &QAction::triggered, this, &BrowserWindow::paste);
  128. m_select_all_action = new QAction("Select &All", this);
  129. m_select_all_action->setIcon(load_icon_from_uri("resource://icons/16x16/select-all.png"sv));
  130. m_select_all_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::SelectAll));
  131. edit_menu->addAction(m_select_all_action);
  132. QObject::connect(m_select_all_action, &QAction::triggered, this, &BrowserWindow::select_all);
  133. edit_menu->addSeparator();
  134. m_find_in_page_action = new QAction("&Find in Page...", this);
  135. m_find_in_page_action->setIcon(load_icon_from_uri("resource://icons/16x16/find.png"sv));
  136. m_find_in_page_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Find));
  137. auto find_previous_shortcuts = QKeySequence::keyBindings(QKeySequence::StandardKey::FindPrevious);
  138. for (auto const& shortcut : find_previous_shortcuts)
  139. new QShortcut(shortcut, this, [this] {
  140. if (m_current_tab)
  141. m_current_tab->find_previous();
  142. });
  143. auto find_next_shortcuts = QKeySequence::keyBindings(QKeySequence::StandardKey::FindNext);
  144. for (auto const& shortcut : find_next_shortcuts)
  145. new QShortcut(shortcut, this, [this] {
  146. if (m_current_tab)
  147. m_current_tab->find_next();
  148. });
  149. edit_menu->addAction(m_find_in_page_action);
  150. QObject::connect(m_find_in_page_action, &QAction::triggered, this, &BrowserWindow::show_find_in_page);
  151. edit_menu->addSeparator();
  152. auto* settings_action = new QAction("&Settings", this);
  153. settings_action->setIcon(load_icon_from_uri("resource://icons/16x16/settings.png"sv));
  154. settings_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Preferences));
  155. edit_menu->addAction(settings_action);
  156. auto* view_menu = m_hamburger_menu->addMenu("&View");
  157. menuBar()->addMenu(view_menu);
  158. auto* open_next_tab_action = new QAction("Open &Next Tab", this);
  159. open_next_tab_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_PageDown));
  160. view_menu->addAction(open_next_tab_action);
  161. QObject::connect(open_next_tab_action, &QAction::triggered, this, &BrowserWindow::open_next_tab);
  162. auto* open_previous_tab_action = new QAction("Open &Previous Tab", this);
  163. open_previous_tab_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_PageUp));
  164. view_menu->addAction(open_previous_tab_action);
  165. QObject::connect(open_previous_tab_action, &QAction::triggered, this, &BrowserWindow::open_previous_tab);
  166. view_menu->addSeparator();
  167. m_zoom_menu = view_menu->addMenu("&Zoom");
  168. auto* zoom_in_action = new QAction("Zoom &In", this);
  169. zoom_in_action->setIcon(load_icon_from_uri("resource://icons/16x16/zoom-in.png"sv));
  170. auto zoom_in_shortcuts = QKeySequence::keyBindings(QKeySequence::StandardKey::ZoomIn);
  171. auto secondary_zoom_shortcut = QKeySequence(Qt::CTRL | Qt::Key_Equal);
  172. if (!zoom_in_shortcuts.contains(secondary_zoom_shortcut))
  173. zoom_in_shortcuts.append(AK::move(secondary_zoom_shortcut));
  174. zoom_in_action->setShortcuts(zoom_in_shortcuts);
  175. m_zoom_menu->addAction(zoom_in_action);
  176. QObject::connect(zoom_in_action, &QAction::triggered, this, &BrowserWindow::zoom_in);
  177. auto* zoom_out_action = new QAction("Zoom &Out", this);
  178. zoom_out_action->setIcon(load_icon_from_uri("resource://icons/16x16/zoom-out.png"sv));
  179. zoom_out_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::ZoomOut));
  180. m_zoom_menu->addAction(zoom_out_action);
  181. QObject::connect(zoom_out_action, &QAction::triggered, this, &BrowserWindow::zoom_out);
  182. auto* reset_zoom_action = new QAction("&Reset Zoom", this);
  183. reset_zoom_action->setIcon(load_icon_from_uri("resource://icons/16x16/zoom-reset.png"sv));
  184. reset_zoom_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_0));
  185. m_zoom_menu->addAction(reset_zoom_action);
  186. QObject::connect(reset_zoom_action, &QAction::triggered, this, &BrowserWindow::reset_zoom);
  187. view_menu->addSeparator();
  188. auto* color_scheme_menu = view_menu->addMenu("&Color Scheme");
  189. auto* color_scheme_group = new QActionGroup(this);
  190. auto* auto_color_scheme = new QAction("&Auto", this);
  191. auto_color_scheme->setCheckable(true);
  192. color_scheme_group->addAction(auto_color_scheme);
  193. color_scheme_menu->addAction(auto_color_scheme);
  194. QObject::connect(auto_color_scheme, &QAction::triggered, this, &BrowserWindow::enable_auto_color_scheme);
  195. auto* light_color_scheme = new QAction("&Light", this);
  196. light_color_scheme->setCheckable(true);
  197. color_scheme_group->addAction(light_color_scheme);
  198. color_scheme_menu->addAction(light_color_scheme);
  199. QObject::connect(light_color_scheme, &QAction::triggered, this, &BrowserWindow::enable_light_color_scheme);
  200. auto* dark_color_scheme = new QAction("&Dark", this);
  201. dark_color_scheme->setCheckable(true);
  202. color_scheme_group->addAction(dark_color_scheme);
  203. color_scheme_menu->addAction(dark_color_scheme);
  204. QObject::connect(dark_color_scheme, &QAction::triggered, this, &BrowserWindow::enable_dark_color_scheme);
  205. auto_color_scheme->setChecked(true);
  206. auto* contrast_menu = view_menu->addMenu("&Contrast");
  207. auto* contrast_group = new QActionGroup(this);
  208. auto* auto_contrast = new QAction("&Auto", this);
  209. auto_contrast->setCheckable(true);
  210. contrast_group->addAction(auto_contrast);
  211. contrast_menu->addAction(auto_contrast);
  212. QObject::connect(auto_contrast, &QAction::triggered, this, &BrowserWindow::enable_auto_contrast);
  213. auto* less_contrast = new QAction("&Less", this);
  214. less_contrast->setCheckable(true);
  215. contrast_group->addAction(less_contrast);
  216. contrast_menu->addAction(less_contrast);
  217. QObject::connect(less_contrast, &QAction::triggered, this, &BrowserWindow::enable_less_contrast);
  218. auto* more_contrast = new QAction("&More", this);
  219. more_contrast->setCheckable(true);
  220. contrast_group->addAction(more_contrast);
  221. contrast_menu->addAction(more_contrast);
  222. QObject::connect(more_contrast, &QAction::triggered, this, &BrowserWindow::enable_more_contrast);
  223. auto* no_preference_contrast = new QAction("&No Preference", this);
  224. no_preference_contrast->setCheckable(true);
  225. contrast_group->addAction(no_preference_contrast);
  226. contrast_menu->addAction(no_preference_contrast);
  227. QObject::connect(no_preference_contrast, &QAction::triggered, this, &BrowserWindow::enable_no_preference_contrast);
  228. auto_contrast->setChecked(true);
  229. auto* show_menubar = new QAction("Show &Menubar", this);
  230. show_menubar->setCheckable(true);
  231. show_menubar->setChecked(Settings::the()->show_menubar());
  232. view_menu->addAction(show_menubar);
  233. QObject::connect(show_menubar, &QAction::triggered, this, [](bool checked) {
  234. Settings::the()->set_show_menubar(checked);
  235. });
  236. auto* inspect_menu = m_hamburger_menu->addMenu("&Inspect");
  237. menuBar()->addMenu(inspect_menu);
  238. m_view_source_action = new QAction("View &Source", this);
  239. m_view_source_action->setIcon(load_icon_from_uri("resource://icons/16x16/filetype-html.png"sv));
  240. m_view_source_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_U));
  241. inspect_menu->addAction(m_view_source_action);
  242. QObject::connect(m_view_source_action, &QAction::triggered, this, [this] {
  243. if (m_current_tab) {
  244. m_current_tab->view().get_source();
  245. }
  246. });
  247. auto* inspector_action = new QAction("Open &Inspector", this);
  248. inspector_action->setIcon(load_icon_from_uri("resource://icons/browser/dom-tree.png"sv));
  249. inspector_action->setShortcuts({ QKeySequence("Ctrl+Shift+I"), QKeySequence("F12") });
  250. inspect_menu->addAction(inspector_action);
  251. QObject::connect(inspector_action, &QAction::triggered, this, [this] {
  252. if (m_current_tab) {
  253. m_current_tab->show_inspector_window();
  254. }
  255. });
  256. auto* task_manager_action = new QAction("Open Task &Manager", this);
  257. task_manager_action->setIcon(load_icon_from_uri("resource://icons/16x16/app-system-monitor.png"sv));
  258. task_manager_action->setShortcuts({ QKeySequence("Ctrl+Shift+M") });
  259. inspect_menu->addAction(task_manager_action);
  260. QObject::connect(task_manager_action, &QAction::triggered, this, [] {
  261. static_cast<Ladybird::Application*>(QApplication::instance())->show_task_manager_window();
  262. });
  263. auto* debug_menu = m_hamburger_menu->addMenu("&Debug");
  264. menuBar()->addMenu(debug_menu);
  265. auto* dump_session_history_tree_action = new QAction("Dump Session History Tree", this);
  266. dump_session_history_tree_action->setIcon(load_icon_from_uri("resource://icons/16x16/history.png"sv));
  267. debug_menu->addAction(dump_session_history_tree_action);
  268. QObject::connect(dump_session_history_tree_action, &QAction::triggered, this, [this] {
  269. debug_request("dump-session-history");
  270. });
  271. auto* dump_dom_tree_action = new QAction("Dump &DOM Tree", this);
  272. dump_dom_tree_action->setIcon(load_icon_from_uri("resource://icons/browser/dom-tree.png"sv));
  273. debug_menu->addAction(dump_dom_tree_action);
  274. QObject::connect(dump_dom_tree_action, &QAction::triggered, this, [this] {
  275. debug_request("dump-dom-tree");
  276. });
  277. auto* dump_layout_tree_action = new QAction("Dump &Layout Tree", this);
  278. dump_layout_tree_action->setIcon(load_icon_from_uri("resource://icons/16x16/layout.png"sv));
  279. debug_menu->addAction(dump_layout_tree_action);
  280. QObject::connect(dump_layout_tree_action, &QAction::triggered, this, [this] {
  281. debug_request("dump-layout-tree");
  282. });
  283. auto* dump_paint_tree_action = new QAction("Dump &Paint Tree", this);
  284. dump_paint_tree_action->setIcon(load_icon_from_uri("resource://icons/16x16/layout.png"sv));
  285. debug_menu->addAction(dump_paint_tree_action);
  286. QObject::connect(dump_paint_tree_action, &QAction::triggered, this, [this] {
  287. debug_request("dump-paint-tree");
  288. });
  289. auto* dump_stacking_context_tree_action = new QAction("Dump S&tacking Context Tree", this);
  290. dump_stacking_context_tree_action->setIcon(load_icon_from_uri("resource://icons/16x16/layers.png"sv));
  291. debug_menu->addAction(dump_stacking_context_tree_action);
  292. QObject::connect(dump_stacking_context_tree_action, &QAction::triggered, this, [this] {
  293. debug_request("dump-stacking-context-tree");
  294. });
  295. auto* dump_style_sheets_action = new QAction("Dump &Style Sheets", this);
  296. dump_style_sheets_action->setIcon(load_icon_from_uri("resource://icons/16x16/filetype-css.png"sv));
  297. debug_menu->addAction(dump_style_sheets_action);
  298. QObject::connect(dump_style_sheets_action, &QAction::triggered, this, [this] {
  299. debug_request("dump-style-sheets");
  300. });
  301. auto* dump_styles_action = new QAction("Dump &All Resolved Styles", this);
  302. dump_styles_action->setIcon(load_icon_from_uri("resource://icons/16x16/filetype-css.png"sv));
  303. debug_menu->addAction(dump_styles_action);
  304. QObject::connect(dump_styles_action, &QAction::triggered, this, [this] {
  305. debug_request("dump-all-resolved-styles");
  306. });
  307. auto* dump_cookies_action = new QAction("Dump C&ookies", this);
  308. dump_cookies_action->setIcon(load_icon_from_uri("resource://icons/browser/cookie.png"sv));
  309. debug_menu->addAction(dump_cookies_action);
  310. QObject::connect(dump_cookies_action, &QAction::triggered, this, [this] {
  311. m_cookie_jar.dump_cookies();
  312. });
  313. auto* dump_local_storage_action = new QAction("Dump Loc&al Storage", this);
  314. dump_local_storage_action->setIcon(load_icon_from_uri("resource://icons/browser/local-storage.png"sv));
  315. debug_menu->addAction(dump_local_storage_action);
  316. QObject::connect(dump_local_storage_action, &QAction::triggered, this, [this] {
  317. debug_request("dump-local-storage");
  318. });
  319. debug_menu->addSeparator();
  320. m_show_line_box_borders_action = new QAction("Show Line Box Borders", this);
  321. m_show_line_box_borders_action->setCheckable(true);
  322. debug_menu->addAction(m_show_line_box_borders_action);
  323. QObject::connect(m_show_line_box_borders_action, &QAction::triggered, this, [this] {
  324. bool state = m_show_line_box_borders_action->isChecked();
  325. for_each_tab([state](auto& tab) {
  326. tab.set_line_box_borders(state);
  327. });
  328. });
  329. debug_menu->addSeparator();
  330. auto* collect_garbage_action = new QAction("Collect &Garbage", this);
  331. collect_garbage_action->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_G));
  332. collect_garbage_action->setIcon(load_icon_from_uri("resource://icons/16x16/trash-can.png"sv));
  333. debug_menu->addAction(collect_garbage_action);
  334. QObject::connect(collect_garbage_action, &QAction::triggered, this, [this] {
  335. debug_request("collect-garbage");
  336. });
  337. auto* dump_gc_graph_action = new QAction("Dump GC graph", this);
  338. debug_menu->addAction(dump_gc_graph_action);
  339. QObject::connect(dump_gc_graph_action, &QAction::triggered, this, [this] {
  340. if (m_current_tab) {
  341. auto gc_graph_path = m_current_tab->view().dump_gc_graph();
  342. warnln("\033[33;1mDumped GC-graph into {}"
  343. "\033[0m",
  344. gc_graph_path);
  345. }
  346. });
  347. auto* clear_cache_action = new QAction("Clear &Cache", this);
  348. clear_cache_action->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_C));
  349. clear_cache_action->setIcon(load_icon_from_uri("resource://icons/browser/clear-cache.png"sv));
  350. debug_menu->addAction(clear_cache_action);
  351. QObject::connect(clear_cache_action, &QAction::triggered, this, [this] {
  352. debug_request("clear-cache");
  353. });
  354. auto* spoof_user_agent_menu = debug_menu->addMenu("Spoof &User Agent");
  355. spoof_user_agent_menu->setIcon(load_icon_from_uri("resource://icons/16x16/spoof.png"sv));
  356. auto* user_agent_group = new QActionGroup(this);
  357. auto add_user_agent = [this, &user_agent_group, &spoof_user_agent_menu](auto name, auto const& user_agent) {
  358. auto* action = new QAction(qstring_from_ak_string(name), this);
  359. action->setCheckable(true);
  360. user_agent_group->addAction(action);
  361. spoof_user_agent_menu->addAction(action);
  362. QObject::connect(action, &QAction::triggered, this, [this, user_agent] {
  363. for_each_tab([user_agent](auto& tab) {
  364. tab.set_user_agent_string(user_agent);
  365. });
  366. set_user_agent_string(user_agent);
  367. });
  368. return action;
  369. };
  370. set_user_agent_string(Web::default_user_agent);
  371. auto* disable_spoofing = add_user_agent("Disabled"sv, Web::default_user_agent);
  372. disable_spoofing->setChecked(true);
  373. for (auto const& user_agent : WebView::user_agents)
  374. add_user_agent(user_agent.key, user_agent.value.to_byte_string());
  375. auto* custom_user_agent_action = new QAction("Custom...", this);
  376. custom_user_agent_action->setCheckable(true);
  377. user_agent_group->addAction(custom_user_agent_action);
  378. spoof_user_agent_menu->addAction(custom_user_agent_action);
  379. QObject::connect(custom_user_agent_action, &QAction::triggered, this, [this, disable_spoofing] {
  380. auto user_agent = QInputDialog::getText(this, "Custom User Agent", "Enter User Agent:");
  381. if (!user_agent.isEmpty()) {
  382. auto user_agent_byte_string = ak_byte_string_from_qstring(user_agent);
  383. for_each_tab([&](auto& tab) {
  384. tab.set_user_agent_string(user_agent_byte_string);
  385. });
  386. set_user_agent_string(user_agent_byte_string);
  387. } else {
  388. disable_spoofing->activate(QAction::Trigger);
  389. }
  390. });
  391. debug_menu->addSeparator();
  392. m_enable_scripting_action = new QAction("Enable Scripting", this);
  393. m_enable_scripting_action->setCheckable(true);
  394. m_enable_scripting_action->setChecked(true);
  395. debug_menu->addAction(m_enable_scripting_action);
  396. QObject::connect(m_enable_scripting_action, &QAction::triggered, this, [this] {
  397. bool state = m_enable_scripting_action->isChecked();
  398. for_each_tab([state](auto& tab) {
  399. tab.set_scripting(state);
  400. });
  401. });
  402. m_block_pop_ups_action = new QAction("Block Pop-ups", this);
  403. m_block_pop_ups_action->setCheckable(true);
  404. m_block_pop_ups_action->setChecked(!allow_popups);
  405. debug_menu->addAction(m_block_pop_ups_action);
  406. QObject::connect(m_block_pop_ups_action, &QAction::triggered, this, [this] {
  407. bool state = m_block_pop_ups_action->isChecked();
  408. for_each_tab([state](auto& tab) {
  409. tab.set_block_popups(state);
  410. });
  411. });
  412. m_enable_same_origin_policy_action = new QAction("Enable Same-Origin Policy", this);
  413. m_enable_same_origin_policy_action->setCheckable(true);
  414. debug_menu->addAction(m_enable_same_origin_policy_action);
  415. QObject::connect(m_enable_same_origin_policy_action, &QAction::triggered, this, [this] {
  416. bool state = m_enable_same_origin_policy_action->isChecked();
  417. for_each_tab([state](auto& tab) {
  418. tab.set_same_origin_policy(state);
  419. });
  420. });
  421. auto* help_menu = m_hamburger_menu->addMenu("&Help");
  422. menuBar()->addMenu(help_menu);
  423. auto* about_action = new QAction("&About Ladybird", this);
  424. help_menu->addAction(about_action);
  425. QObject::connect(about_action, &QAction::triggered, this, [this] {
  426. new_tab_from_url("about:version"sv, Web::HTML::ActivateTab::Yes);
  427. });
  428. m_hamburger_menu->addSeparator();
  429. file_menu->addSeparator();
  430. auto* quit_action = new QAction("&Quit", this);
  431. quit_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Quit));
  432. m_hamburger_menu->addAction(quit_action);
  433. file_menu->addAction(quit_action);
  434. QObject::connect(quit_action, &QAction::triggered, this, &QMainWindow::close);
  435. QObject::connect(m_new_tab_action, &QAction::triggered, this, [this] {
  436. new_tab_from_url(ak_url_from_qstring(Settings::the()->new_tab_page()), Web::HTML::ActivateTab::Yes);
  437. });
  438. QObject::connect(m_new_window_action, &QAction::triggered, this, [this] {
  439. auto initial_urls = Vector<URL::URL> { ak_url_from_qstring(Settings::the()->new_tab_page()) };
  440. (void)static_cast<Ladybird::Application*>(QApplication::instance())->new_window(initial_urls, m_cookie_jar, m_web_content_options, m_webdriver_content_ipc_path, m_allow_popups);
  441. });
  442. QObject::connect(open_file_action, &QAction::triggered, this, &BrowserWindow::open_file);
  443. QObject::connect(settings_action, &QAction::triggered, this, [this] {
  444. if (!m_settings_dialog) {
  445. m_settings_dialog = new SettingsDialog(this);
  446. }
  447. m_settings_dialog->show();
  448. m_settings_dialog->setFocus();
  449. });
  450. QObject::connect(m_tabs_container, &QTabWidget::currentChanged, [this](int index) {
  451. auto* tab = verify_cast<Tab>(m_tabs_container->widget(index));
  452. if (tab)
  453. setWindowTitle(QString("%1 - Ladybird").arg(tab->title()));
  454. set_current_tab(tab);
  455. });
  456. QObject::connect(m_tabs_container, &QTabWidget::tabCloseRequested, this, &BrowserWindow::close_tab);
  457. QObject::connect(close_current_tab_action, &QAction::triggered, this, &BrowserWindow::close_current_tab);
  458. m_inspect_dom_node_action = new QAction("&Inspect Element", this);
  459. connect(m_inspect_dom_node_action, &QAction::triggered, this, [this] {
  460. if (m_current_tab)
  461. m_current_tab->show_inspector_window(Tab::InspectorTarget::HoveredElement);
  462. });
  463. m_go_back_action = new QAction("Go Back", this);
  464. connect(m_go_back_action, &QAction::triggered, this, [this] {
  465. if (m_current_tab)
  466. m_current_tab->back();
  467. });
  468. m_go_forward_action = new QAction("Go Forward", this);
  469. connect(m_go_forward_action, &QAction::triggered, this, [this] {
  470. if (m_current_tab)
  471. m_current_tab->forward();
  472. });
  473. m_reload_action = new QAction("&Reload", this);
  474. connect(m_reload_action, &QAction::triggered, this, [this] {
  475. if (m_current_tab)
  476. m_current_tab->reload();
  477. });
  478. m_go_back_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Back));
  479. m_go_forward_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Forward));
  480. m_reload_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Refresh));
  481. m_go_back_action->setEnabled(false);
  482. m_go_forward_action->setEnabled(false);
  483. m_reload_action->setEnabled(true);
  484. for (int i = 0; i <= 7; ++i) {
  485. new QShortcut(QKeySequence(Qt::CTRL | static_cast<Qt::Key>(Qt::Key_1 + i)), this, [this, i] {
  486. if (m_tabs_container->count() <= 1)
  487. return;
  488. m_tabs_container->setCurrentIndex(min(i, m_tabs_container->count() - 1));
  489. });
  490. }
  491. new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_9), this, [this] {
  492. if (m_tabs_container->count() <= 1)
  493. return;
  494. m_tabs_container->setCurrentIndex(m_tabs_container->count() - 1);
  495. });
  496. if (parent_tab) {
  497. new_child_tab(Web::HTML::ActivateTab::Yes, *parent_tab, AK::move(page_index));
  498. } else {
  499. for (size_t i = 0; i < initial_urls.size(); ++i) {
  500. new_tab_from_url(initial_urls[i], (i == 0) ? Web::HTML::ActivateTab::Yes : Web::HTML::ActivateTab::No);
  501. }
  502. }
  503. setCentralWidget(m_tabs_container);
  504. setContextMenuPolicy(Qt::PreventContextMenu);
  505. }
  506. void BrowserWindow::set_current_tab(Tab* tab)
  507. {
  508. m_current_tab = tab;
  509. if (tab) {
  510. update_displayed_zoom_level();
  511. tab->update_navigation_buttons_state();
  512. }
  513. }
  514. void BrowserWindow::debug_request(ByteString const& request, ByteString const& argument)
  515. {
  516. if (!m_current_tab)
  517. return;
  518. m_current_tab->debug_request(request, argument);
  519. }
  520. Tab& BrowserWindow::new_tab_from_url(URL::URL const& url, Web::HTML::ActivateTab activate_tab)
  521. {
  522. auto& tab = create_new_tab(activate_tab);
  523. tab.navigate(url);
  524. return tab;
  525. }
  526. Tab& BrowserWindow::new_tab_from_content(StringView html, Web::HTML::ActivateTab activate_tab)
  527. {
  528. auto& tab = create_new_tab(activate_tab);
  529. tab.load_html(html);
  530. return tab;
  531. }
  532. Tab& BrowserWindow::new_child_tab(Web::HTML::ActivateTab activate_tab, Tab& parent, Optional<u64> page_index)
  533. {
  534. return create_new_tab(activate_tab, parent, page_index);
  535. }
  536. Tab& BrowserWindow::create_new_tab(Web::HTML::ActivateTab activate_tab, Tab& parent, Optional<u64> page_index)
  537. {
  538. if (!page_index.has_value())
  539. return create_new_tab(activate_tab);
  540. auto* tab = new Tab(this, m_web_content_options, m_webdriver_content_ipc_path, parent.view().client(), page_index.value());
  541. // FIXME: Merge with other overload
  542. if (m_current_tab == nullptr) {
  543. set_current_tab(tab);
  544. }
  545. m_tabs_container->addTab(tab, "New Tab");
  546. if (activate_tab == Web::HTML::ActivateTab::Yes)
  547. m_tabs_container->setCurrentWidget(tab);
  548. initialize_tab(tab);
  549. return *tab;
  550. }
  551. Tab& BrowserWindow::create_new_tab(Web::HTML::ActivateTab activate_tab)
  552. {
  553. auto* tab = new Tab(this, m_web_content_options, m_webdriver_content_ipc_path);
  554. if (m_current_tab == nullptr) {
  555. set_current_tab(tab);
  556. }
  557. m_tabs_container->addTab(tab, "New Tab");
  558. if (activate_tab == Web::HTML::ActivateTab::Yes)
  559. m_tabs_container->setCurrentWidget(tab);
  560. initialize_tab(tab);
  561. return *tab;
  562. }
  563. void BrowserWindow::initialize_tab(Tab* tab)
  564. {
  565. QObject::connect(tab, &Tab::title_changed, this, &BrowserWindow::tab_title_changed);
  566. QObject::connect(tab, &Tab::favicon_changed, this, &BrowserWindow::tab_favicon_changed);
  567. QObject::connect(tab, &Tab::audio_play_state_changed, this, &BrowserWindow::tab_audio_play_state_changed);
  568. QObject::connect(tab, &Tab::navigation_buttons_state_changed, this, &BrowserWindow::tab_navigation_buttons_state_changed);
  569. QObject::connect(&tab->view(), &WebContentView::urls_dropped, this, [this](auto& urls) {
  570. VERIFY(urls.size());
  571. m_current_tab->navigate(ak_url_from_qurl(urls[0]));
  572. for (qsizetype i = 1; i < urls.size(); ++i)
  573. new_tab_from_url(ak_url_from_qurl(urls[i]), Web::HTML::ActivateTab::No);
  574. });
  575. tab->view().on_new_web_view = [this, tab](auto activate_tab, Web::HTML::WebViewHints hints, Optional<u64> page_index) {
  576. if (hints.popup) {
  577. auto& window = static_cast<Ladybird::Application*>(QApplication::instance())->new_window({}, m_cookie_jar, m_web_content_options, m_webdriver_content_ipc_path, m_allow_popups, tab, AK::move(page_index));
  578. window.set_window_rect(hints.screen_x, hints.screen_y, hints.width, hints.height);
  579. return window.current_tab()->view().handle();
  580. }
  581. auto& new_tab = new_child_tab(activate_tab, *tab, page_index);
  582. return new_tab.view().handle();
  583. };
  584. tab->view().on_tab_open_request = [this](auto url, auto activate_tab) {
  585. auto& tab = new_tab_from_url(url, activate_tab);
  586. return tab.view().handle();
  587. };
  588. tab->view().on_link_click = [this](auto url, auto target, unsigned modifiers) {
  589. // TODO: maybe activate tabs according to some configuration, this is just normal current browser behavior
  590. if (modifiers == Web::UIEvents::Mod_Ctrl) {
  591. m_current_tab->view().on_tab_open_request(url, Web::HTML::ActivateTab::No);
  592. } else if (target == "_blank") {
  593. m_current_tab->view().on_tab_open_request(url, Web::HTML::ActivateTab::Yes);
  594. } else {
  595. m_current_tab->view().load(url);
  596. }
  597. };
  598. tab->view().on_link_middle_click = [this](auto url, auto target, unsigned modifiers) {
  599. m_current_tab->view().on_link_click(url, target, Web::UIEvents::Mod_Ctrl);
  600. (void)modifiers;
  601. };
  602. tab->view().on_get_all_cookies = [this](auto const& url) {
  603. return m_cookie_jar.get_all_cookies(url);
  604. };
  605. tab->view().on_get_named_cookie = [this](auto const& url, auto const& name) {
  606. return m_cookie_jar.get_named_cookie(url, name);
  607. };
  608. tab->view().on_get_cookie = [this](auto& url, auto source) {
  609. return m_cookie_jar.get_cookie(url, source);
  610. };
  611. tab->view().on_set_cookie = [this](auto& url, auto& cookie, auto source) {
  612. m_cookie_jar.set_cookie(url, cookie, source);
  613. };
  614. tab->view().on_update_cookie = [this](auto const& cookie) {
  615. m_cookie_jar.update_cookie(cookie);
  616. };
  617. m_tabs_container->setTabIcon(m_tabs_container->indexOf(tab), tab->favicon());
  618. create_close_button_for_tab(tab);
  619. tab->focus_location_editor();
  620. tab->set_line_box_borders(m_show_line_box_borders_action->isChecked());
  621. tab->set_scripting(m_enable_scripting_action->isChecked());
  622. tab->set_block_popups(m_block_pop_ups_action->isChecked());
  623. tab->set_same_origin_policy(m_enable_same_origin_policy_action->isChecked());
  624. tab->set_user_agent_string(user_agent_string());
  625. }
  626. void BrowserWindow::activate_tab(int index)
  627. {
  628. m_tabs_container->setCurrentIndex(index);
  629. }
  630. void BrowserWindow::close_tab(int index)
  631. {
  632. auto* tab = m_tabs_container->widget(index);
  633. m_tabs_container->removeTab(index);
  634. tab->deleteLater();
  635. if (m_tabs_container->count() == 0)
  636. close();
  637. }
  638. void BrowserWindow::move_tab(int old_index, int new_index)
  639. {
  640. m_tabs_container->tabBar()->moveTab(old_index, new_index);
  641. }
  642. void BrowserWindow::open_file()
  643. {
  644. m_current_tab->open_file();
  645. }
  646. void BrowserWindow::close_current_tab()
  647. {
  648. close_tab(m_tabs_container->currentIndex());
  649. }
  650. int BrowserWindow::tab_index(Tab* tab)
  651. {
  652. return m_tabs_container->indexOf(tab);
  653. }
  654. void BrowserWindow::device_pixel_ratio_changed(qreal dpi)
  655. {
  656. m_device_pixel_ratio = dpi;
  657. for_each_tab([this](auto& tab) {
  658. tab.view().set_device_pixel_ratio(m_device_pixel_ratio);
  659. });
  660. }
  661. void BrowserWindow::tab_title_changed(int index, QString const& title)
  662. {
  663. m_tabs_container->setTabText(index, title);
  664. m_tabs_container->setTabToolTip(index, title);
  665. if (m_tabs_container->currentIndex() == index)
  666. setWindowTitle(QString("%1 - Ladybird").arg(title));
  667. }
  668. void BrowserWindow::tab_favicon_changed(int index, QIcon const& icon)
  669. {
  670. m_tabs_container->setTabIcon(index, icon);
  671. }
  672. void BrowserWindow::create_close_button_for_tab(Tab* tab)
  673. {
  674. auto index = m_tabs_container->indexOf(tab);
  675. m_tabs_container->setTabIcon(index, tab->favicon());
  676. auto* button = new TabBarButton(create_tvg_icon_with_theme_colors("close", palette()));
  677. auto position = audio_button_position_for_tab(index) == QTabBar::LeftSide ? QTabBar::RightSide : QTabBar::LeftSide;
  678. connect(button, &QPushButton::clicked, this, [this, tab]() {
  679. auto index = m_tabs_container->indexOf(tab);
  680. close_tab(index);
  681. });
  682. m_tabs_container->tabBar()->setTabButton(index, position, button);
  683. }
  684. void BrowserWindow::tab_audio_play_state_changed(int index, Web::HTML::AudioPlayState play_state)
  685. {
  686. auto* tab = verify_cast<Tab>(m_tabs_container->widget(index));
  687. auto position = audio_button_position_for_tab(index);
  688. switch (play_state) {
  689. case Web::HTML::AudioPlayState::Paused:
  690. if (tab->view().page_mute_state() == Web::HTML::MuteState::Unmuted)
  691. m_tabs_container->tabBar()->setTabButton(index, position, nullptr);
  692. break;
  693. case Web::HTML::AudioPlayState::Playing:
  694. auto* button = new TabBarButton(icon_for_page_mute_state(*tab));
  695. button->setToolTip(tool_tip_for_page_mute_state(*tab));
  696. button->setObjectName("LadybirdAudioState");
  697. connect(button, &QPushButton::clicked, this, [this, tab, position]() {
  698. tab->view().toggle_page_mute_state();
  699. auto index = tab_index(tab);
  700. switch (tab->view().audio_play_state()) {
  701. case Web::HTML::AudioPlayState::Paused:
  702. m_tabs_container->tabBar()->setTabButton(index, position, nullptr);
  703. break;
  704. case Web::HTML::AudioPlayState::Playing:
  705. auto* button = m_tabs_container->tabBar()->tabButton(index, position);
  706. verify_cast<TabBarButton>(button)->setIcon(icon_for_page_mute_state(*tab));
  707. button->setToolTip(tool_tip_for_page_mute_state(*tab));
  708. break;
  709. }
  710. });
  711. m_tabs_container->tabBar()->setTabButton(index, position, button);
  712. break;
  713. }
  714. }
  715. void BrowserWindow::tab_navigation_buttons_state_changed(int index)
  716. {
  717. auto* tab = verify_cast<Tab>(m_tabs_container->widget(index));
  718. tab->update_navigation_buttons_state();
  719. }
  720. QIcon BrowserWindow::icon_for_page_mute_state(Tab& tab) const
  721. {
  722. switch (tab.view().page_mute_state()) {
  723. case Web::HTML::MuteState::Muted:
  724. return style()->standardIcon(QStyle::SP_MediaVolumeMuted);
  725. case Web::HTML::MuteState::Unmuted:
  726. return style()->standardIcon(QStyle::SP_MediaVolume);
  727. }
  728. VERIFY_NOT_REACHED();
  729. }
  730. QString BrowserWindow::tool_tip_for_page_mute_state(Tab& tab) const
  731. {
  732. switch (tab.view().page_mute_state()) {
  733. case Web::HTML::MuteState::Muted:
  734. return "Unmute tab";
  735. case Web::HTML::MuteState::Unmuted:
  736. return "Mute tab";
  737. }
  738. VERIFY_NOT_REACHED();
  739. }
  740. QTabBar::ButtonPosition BrowserWindow::audio_button_position_for_tab(int tab_index) const
  741. {
  742. if (auto* button = m_tabs_container->tabBar()->tabButton(tab_index, QTabBar::LeftSide)) {
  743. if (button->objectName() != "LadybirdAudioState")
  744. return QTabBar::RightSide;
  745. }
  746. return QTabBar::LeftSide;
  747. }
  748. void BrowserWindow::open_next_tab()
  749. {
  750. if (m_tabs_container->count() <= 1)
  751. return;
  752. auto next_index = m_tabs_container->currentIndex() + 1;
  753. if (next_index >= m_tabs_container->count())
  754. next_index = 0;
  755. m_tabs_container->setCurrentIndex(next_index);
  756. }
  757. void BrowserWindow::open_previous_tab()
  758. {
  759. if (m_tabs_container->count() <= 1)
  760. return;
  761. auto next_index = m_tabs_container->currentIndex() - 1;
  762. if (next_index < 0)
  763. next_index = m_tabs_container->count() - 1;
  764. m_tabs_container->setCurrentIndex(next_index);
  765. }
  766. void BrowserWindow::enable_auto_color_scheme()
  767. {
  768. for_each_tab([](auto& tab) {
  769. tab.view().set_preferred_color_scheme(Web::CSS::PreferredColorScheme::Auto);
  770. });
  771. }
  772. void BrowserWindow::enable_light_color_scheme()
  773. {
  774. for_each_tab([](auto& tab) {
  775. tab.view().set_preferred_color_scheme(Web::CSS::PreferredColorScheme::Light);
  776. });
  777. }
  778. void BrowserWindow::enable_dark_color_scheme()
  779. {
  780. for_each_tab([](auto& tab) {
  781. tab.view().set_preferred_color_scheme(Web::CSS::PreferredColorScheme::Dark);
  782. });
  783. }
  784. void BrowserWindow::enable_auto_contrast()
  785. {
  786. for_each_tab([](auto& tab) {
  787. tab.view().set_preferred_contrast(Web::CSS::PreferredContrast::Auto);
  788. });
  789. }
  790. void BrowserWindow::enable_less_contrast()
  791. {
  792. for_each_tab([](auto& tab) {
  793. tab.view().set_preferred_contrast(Web::CSS::PreferredContrast::Less);
  794. });
  795. }
  796. void BrowserWindow::enable_more_contrast()
  797. {
  798. for_each_tab([](auto& tab) {
  799. tab.view().set_preferred_contrast(Web::CSS::PreferredContrast::More);
  800. });
  801. }
  802. void BrowserWindow::enable_no_preference_contrast()
  803. {
  804. for_each_tab([](auto& tab) {
  805. tab.view().set_preferred_contrast(Web::CSS::PreferredContrast::NoPreference);
  806. });
  807. }
  808. void BrowserWindow::zoom_in()
  809. {
  810. if (!m_current_tab)
  811. return;
  812. m_current_tab->view().zoom_in();
  813. update_displayed_zoom_level();
  814. }
  815. void BrowserWindow::zoom_out()
  816. {
  817. if (!m_current_tab)
  818. return;
  819. m_current_tab->view().zoom_out();
  820. update_displayed_zoom_level();
  821. }
  822. void BrowserWindow::reset_zoom()
  823. {
  824. if (!m_current_tab)
  825. return;
  826. m_current_tab->view().reset_zoom();
  827. update_displayed_zoom_level();
  828. }
  829. void BrowserWindow::update_zoom_menu()
  830. {
  831. VERIFY(m_zoom_menu);
  832. auto zoom_level_text = MUST(String::formatted("&Zoom ({}%)", round_to<int>(m_current_tab->view().zoom_level() * 100)));
  833. m_zoom_menu->setTitle(qstring_from_ak_string(zoom_level_text));
  834. }
  835. void BrowserWindow::select_all()
  836. {
  837. if (!m_current_tab)
  838. return;
  839. m_current_tab->view().select_all();
  840. }
  841. void BrowserWindow::show_find_in_page()
  842. {
  843. if (!m_current_tab)
  844. return;
  845. m_current_tab->show_find_in_page();
  846. }
  847. void BrowserWindow::paste()
  848. {
  849. if (!m_current_tab)
  850. return;
  851. auto* clipboard = QGuiApplication::clipboard();
  852. m_current_tab->view().paste(ak_string_from_qstring(clipboard->text()));
  853. }
  854. void BrowserWindow::update_displayed_zoom_level()
  855. {
  856. VERIFY(m_current_tab);
  857. update_zoom_menu();
  858. m_current_tab->update_reset_zoom_button();
  859. }
  860. void BrowserWindow::set_window_rect(Optional<Web::DevicePixels> x, Optional<Web::DevicePixels> y, Optional<Web::DevicePixels> width, Optional<Web::DevicePixels> height)
  861. {
  862. x = x.value_or(0);
  863. y = y.value_or(0);
  864. if (!width.has_value() || width.value() == 0)
  865. width = 800;
  866. if (!height.has_value() || height.value() == 0)
  867. height = 600;
  868. setGeometry(x.value().value(), y.value().value(), width.value().value(), height.value().value());
  869. }
  870. void BrowserWindow::copy_selected_text()
  871. {
  872. if (!m_current_tab)
  873. return;
  874. auto text = m_current_tab->view().selected_text();
  875. auto* clipboard = QGuiApplication::clipboard();
  876. clipboard->setText(qstring_from_ak_string(text));
  877. }
  878. bool BrowserWindow::event(QEvent* event)
  879. {
  880. #if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
  881. if (event->type() == QEvent::DevicePixelRatioChange) {
  882. if (m_device_pixel_ratio != devicePixelRatio())
  883. device_pixel_ratio_changed(devicePixelRatio());
  884. }
  885. #endif
  886. if (event->type() == QEvent::WindowActivate)
  887. static_cast<Ladybird::Application*>(QApplication::instance())->set_active_window(*this);
  888. return QMainWindow::event(event);
  889. }
  890. void BrowserWindow::resizeEvent(QResizeEvent* event)
  891. {
  892. QWidget::resizeEvent(event);
  893. for_each_tab([&](auto& tab) {
  894. tab.view().set_window_size({ frameSize().width() * m_device_pixel_ratio, frameSize().height() * m_device_pixel_ratio });
  895. });
  896. }
  897. void BrowserWindow::moveEvent(QMoveEvent* event)
  898. {
  899. QWidget::moveEvent(event);
  900. for_each_tab([&](auto& tab) {
  901. tab.view().set_window_position({ event->pos().x() * m_device_pixel_ratio, event->pos().y() * m_device_pixel_ratio });
  902. });
  903. }
  904. void BrowserWindow::wheelEvent(QWheelEvent* event)
  905. {
  906. if ((event->modifiers() & Qt::ControlModifier) != 0) {
  907. if (event->angleDelta().y() > 0)
  908. zoom_in();
  909. else if (event->angleDelta().y() < 0)
  910. zoom_out();
  911. }
  912. }
  913. bool BrowserWindow::eventFilter(QObject* obj, QEvent* event)
  914. {
  915. if (event->type() == QEvent::MouseButtonRelease) {
  916. auto const* const mouse_event = static_cast<QMouseEvent*>(event);
  917. if (mouse_event->button() == Qt::MouseButton::MiddleButton) {
  918. if (obj == m_tabs_container) {
  919. auto const tab_index = m_tabs_container->tabBar()->tabAt(mouse_event->pos());
  920. close_tab(tab_index);
  921. return true;
  922. }
  923. }
  924. }
  925. return QMainWindow::eventFilter(obj, event);
  926. }
  927. void BrowserWindow::closeEvent(QCloseEvent* event)
  928. {
  929. Settings::the()->set_last_position(pos());
  930. Settings::the()->set_last_size(size());
  931. Settings::the()->set_is_maximized(isMaximized());
  932. QObject::deleteLater();
  933. QMainWindow::closeEvent(event);
  934. }
  935. }