BrowserWindow.cpp 40 KB

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