BrowserWindow.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  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 "ConsoleWidget.h"
  11. #include "Settings.h"
  12. #include "SettingsDialog.h"
  13. #include "StringUtils.h"
  14. #include "WebContentView.h"
  15. #include <AK/TypeCasts.h>
  16. #include <Browser/CookieJar.h>
  17. #include <Ladybird/Utilities.h>
  18. #include <LibWeb/CSS/PreferredColorScheme.h>
  19. #include <LibWeb/Loader/ResourceLoader.h>
  20. #include <QAction>
  21. #include <QActionGroup>
  22. #include <QClipboard>
  23. #include <QGuiApplication>
  24. #include <QInputDialog>
  25. #include <QPlainTextEdit>
  26. #include <QTabBar>
  27. namespace Ladybird {
  28. static QIcon const& app_icon()
  29. {
  30. static QIcon icon;
  31. if (icon.isNull()) {
  32. QPixmap pixmap;
  33. pixmap.load(":/Icons/ladybird.png");
  34. icon = QIcon(pixmap);
  35. }
  36. return icon;
  37. }
  38. BrowserWindow::BrowserWindow(Optional<URL> const& initial_url, Browser::CookieJar& cookie_jar, StringView webdriver_content_ipc_path, WebView::EnableCallgrindProfiling enable_callgrind_profiling, UseLagomNetworking use_lagom_networking)
  39. : m_cookie_jar(cookie_jar)
  40. , m_webdriver_content_ipc_path(webdriver_content_ipc_path)
  41. , m_enable_callgrind_profiling(enable_callgrind_profiling)
  42. , m_use_lagom_networking(use_lagom_networking)
  43. {
  44. setWindowIcon(app_icon());
  45. m_tabs_container = new QTabWidget(this);
  46. m_tabs_container->installEventFilter(this);
  47. m_tabs_container->setElideMode(Qt::TextElideMode::ElideRight);
  48. m_tabs_container->setMovable(true);
  49. m_tabs_container->setTabsClosable(true);
  50. m_tabs_container->setDocumentMode(true);
  51. m_tabs_container->setTabBarAutoHide(true);
  52. auto* menu = menuBar()->addMenu("&File");
  53. auto* new_tab_action = new QAction("New &Tab", this);
  54. new_tab_action->setIcon(QIcon(QString("%1/res/icons/16x16/new-tab.png").arg(s_serenity_resource_root.characters())));
  55. new_tab_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::AddTab));
  56. menu->addAction(new_tab_action);
  57. auto* close_current_tab_action = new QAction("&Close Current Tab", this);
  58. close_current_tab_action->setIcon(QIcon(QString("%1/res/icons/16x16/close-tab.png").arg(s_serenity_resource_root.characters())));
  59. close_current_tab_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Close));
  60. menu->addAction(close_current_tab_action);
  61. auto* open_file_action = new QAction("&Open File...", this);
  62. open_file_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-folder-open.png").arg(s_serenity_resource_root.characters())));
  63. open_file_action->setShortcut(QKeySequence(QKeySequence::StandardKey::Open));
  64. menu->addAction(open_file_action);
  65. menu->addSeparator();
  66. auto* quit_action = new QAction("&Quit", this);
  67. quit_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Quit));
  68. menu->addAction(quit_action);
  69. auto* edit_menu = menuBar()->addMenu("&Edit");
  70. m_copy_selection_action = make<QAction>("&Copy", this);
  71. m_copy_selection_action->setIcon(QIcon(QString("%1/res/icons/16x16/edit-copy.png").arg(s_serenity_resource_root.characters())));
  72. m_copy_selection_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Copy));
  73. edit_menu->addAction(m_copy_selection_action);
  74. QObject::connect(m_copy_selection_action, &QAction::triggered, this, &BrowserWindow::copy_selected_text);
  75. m_select_all_action = make<QAction>("Select &All", this);
  76. m_select_all_action->setIcon(QIcon(QString("%1/res/icons/16x16/select-all.png").arg(s_serenity_resource_root.characters())));
  77. m_select_all_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::SelectAll));
  78. edit_menu->addAction(m_select_all_action);
  79. QObject::connect(m_select_all_action, &QAction::triggered, this, &BrowserWindow::select_all);
  80. edit_menu->addSeparator();
  81. auto* settings_action = new QAction("&Settings", this);
  82. settings_action->setIcon(QIcon(QString("%1/res/icons/16x16/settings.png").arg(s_serenity_resource_root.characters())));
  83. settings_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Preferences));
  84. edit_menu->addAction(settings_action);
  85. auto* view_menu = menuBar()->addMenu("&View");
  86. auto* open_next_tab_action = new QAction("Open &Next Tab", this);
  87. open_next_tab_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_PageDown));
  88. view_menu->addAction(open_next_tab_action);
  89. QObject::connect(open_next_tab_action, &QAction::triggered, this, &BrowserWindow::open_next_tab);
  90. auto* open_previous_tab_action = new QAction("Open &Previous Tab", this);
  91. open_previous_tab_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_PageUp));
  92. view_menu->addAction(open_previous_tab_action);
  93. QObject::connect(open_previous_tab_action, &QAction::triggered, this, &BrowserWindow::open_previous_tab);
  94. view_menu->addSeparator();
  95. m_zoom_menu = view_menu->addMenu("&Zoom");
  96. auto* zoom_in_action = new QAction("Zoom &In", this);
  97. zoom_in_action->setIcon(QIcon(QString("%1/res/icons/16x16/zoom-in.png").arg(s_serenity_resource_root.characters())));
  98. auto zoom_in_shortcuts = QKeySequence::keyBindings(QKeySequence::StandardKey::ZoomIn);
  99. zoom_in_shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_Equal));
  100. zoom_in_action->setShortcuts(zoom_in_shortcuts);
  101. m_zoom_menu->addAction(zoom_in_action);
  102. QObject::connect(zoom_in_action, &QAction::triggered, this, &BrowserWindow::zoom_in);
  103. auto* zoom_out_action = new QAction("Zoom &Out", this);
  104. zoom_out_action->setIcon(QIcon(QString("%1/res/icons/16x16/zoom-out.png").arg(s_serenity_resource_root.characters())));
  105. zoom_out_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::ZoomOut));
  106. m_zoom_menu->addAction(zoom_out_action);
  107. QObject::connect(zoom_out_action, &QAction::triggered, this, &BrowserWindow::zoom_out);
  108. auto* reset_zoom_action = new QAction("&Reset Zoom", this);
  109. reset_zoom_action->setIcon(QIcon(QString("%1/res/icons/16x16/zoom-reset.png").arg(s_serenity_resource_root.characters())));
  110. reset_zoom_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_0));
  111. m_zoom_menu->addAction(reset_zoom_action);
  112. QObject::connect(reset_zoom_action, &QAction::triggered, this, &BrowserWindow::reset_zoom);
  113. view_menu->addSeparator();
  114. auto* color_scheme_menu = view_menu->addMenu("&Color Scheme");
  115. auto* color_scheme_group = new QActionGroup(this);
  116. auto* auto_color_scheme = new QAction("&Auto", this);
  117. auto_color_scheme->setCheckable(true);
  118. color_scheme_group->addAction(auto_color_scheme);
  119. color_scheme_menu->addAction(auto_color_scheme);
  120. QObject::connect(auto_color_scheme, &QAction::triggered, this, &BrowserWindow::enable_auto_color_scheme);
  121. auto* light_color_scheme = new QAction("&Light", this);
  122. light_color_scheme->setCheckable(true);
  123. color_scheme_group->addAction(light_color_scheme);
  124. color_scheme_menu->addAction(light_color_scheme);
  125. QObject::connect(light_color_scheme, &QAction::triggered, this, &BrowserWindow::enable_light_color_scheme);
  126. auto* dark_color_scheme = new QAction("&Dark", this);
  127. dark_color_scheme->setCheckable(true);
  128. color_scheme_group->addAction(dark_color_scheme);
  129. color_scheme_menu->addAction(dark_color_scheme);
  130. QObject::connect(dark_color_scheme, &QAction::triggered, this, &BrowserWindow::enable_dark_color_scheme);
  131. auto_color_scheme->setChecked(true);
  132. auto* inspect_menu = menuBar()->addMenu("&Inspect");
  133. m_view_source_action = make<QAction>("View &Source", this);
  134. m_view_source_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-html.png").arg(s_serenity_resource_root.characters())));
  135. m_view_source_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_U));
  136. inspect_menu->addAction(m_view_source_action);
  137. QObject::connect(m_view_source_action, &QAction::triggered, this, [this] {
  138. if (m_current_tab) {
  139. m_current_tab->view().get_source();
  140. }
  141. });
  142. auto* js_console_action = new QAction("Show &JS Console", this);
  143. js_console_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-javascript.png").arg(s_serenity_resource_root.characters())));
  144. js_console_action->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_J));
  145. inspect_menu->addAction(js_console_action);
  146. QObject::connect(js_console_action, &QAction::triggered, this, [this] {
  147. if (m_current_tab) {
  148. m_current_tab->show_console_window();
  149. }
  150. });
  151. auto* inspector_action = new QAction("Open &Inspector", this);
  152. inspector_action->setIcon(QIcon(QString("%1/res/icons/browser/dom-tree.png").arg(s_serenity_resource_root.characters())));
  153. inspector_action->setShortcut(QKeySequence("Ctrl+Shift+I"));
  154. inspect_menu->addAction(inspector_action);
  155. QObject::connect(inspector_action, &QAction::triggered, this, [this] {
  156. if (m_current_tab) {
  157. m_current_tab->show_inspector_window();
  158. }
  159. });
  160. auto* debug_menu = menuBar()->addMenu("&Debug");
  161. auto* dump_dom_tree_action = new QAction("Dump &DOM Tree", this);
  162. dump_dom_tree_action->setIcon(QIcon(QString("%1/res/icons/browser/dom-tree.png").arg(s_serenity_resource_root.characters())));
  163. debug_menu->addAction(dump_dom_tree_action);
  164. QObject::connect(dump_dom_tree_action, &QAction::triggered, this, [this] {
  165. debug_request("dump-dom-tree");
  166. });
  167. auto* dump_layout_tree_action = new QAction("Dump &Layout Tree", this);
  168. dump_layout_tree_action->setIcon(QIcon(QString("%1/res/icons/16x16/layout.png").arg(s_serenity_resource_root.characters())));
  169. debug_menu->addAction(dump_layout_tree_action);
  170. QObject::connect(dump_layout_tree_action, &QAction::triggered, this, [this] {
  171. debug_request("dump-layout-tree");
  172. });
  173. auto* dump_paint_tree_action = new QAction("Dump &Paint Tree", this);
  174. dump_paint_tree_action->setIcon(QIcon(QString("%1/res/icons/16x16/layout.png").arg(s_serenity_resource_root.characters())));
  175. debug_menu->addAction(dump_paint_tree_action);
  176. QObject::connect(dump_paint_tree_action, &QAction::triggered, this, [this] {
  177. debug_request("dump-paint-tree");
  178. });
  179. auto* dump_stacking_context_tree_action = new QAction("Dump S&tacking Context Tree", this);
  180. dump_stacking_context_tree_action->setIcon(QIcon(QString("%1/res/icons/16x16/layers.png").arg(s_serenity_resource_root.characters())));
  181. debug_menu->addAction(dump_stacking_context_tree_action);
  182. QObject::connect(dump_stacking_context_tree_action, &QAction::triggered, this, [this] {
  183. debug_request("dump-stacking-context-tree");
  184. });
  185. auto* dump_style_sheets_action = new QAction("Dump &Style Sheets", this);
  186. dump_style_sheets_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-css.png").arg(s_serenity_resource_root.characters())));
  187. debug_menu->addAction(dump_style_sheets_action);
  188. QObject::connect(dump_style_sheets_action, &QAction::triggered, this, [this] {
  189. debug_request("dump-style-sheets");
  190. });
  191. auto* dump_styles_action = new QAction("Dump &All Resolved Styles", this);
  192. dump_styles_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-css.png").arg(s_serenity_resource_root.characters())));
  193. debug_menu->addAction(dump_styles_action);
  194. QObject::connect(dump_styles_action, &QAction::triggered, this, [this] {
  195. debug_request("dump-all-resolved-styles");
  196. });
  197. auto* dump_history_action = new QAction("Dump &History", this);
  198. dump_history_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_H));
  199. dump_history_action->setIcon(QIcon(QString("%1/res/icons/16x16/history.png").arg(s_serenity_resource_root.characters())));
  200. debug_menu->addAction(dump_history_action);
  201. QObject::connect(dump_history_action, &QAction::triggered, this, [this] {
  202. debug_request("dump-history");
  203. });
  204. auto* dump_cookies_action = new QAction("Dump C&ookies", this);
  205. dump_cookies_action->setIcon(QIcon(QString("%1/res/icons/browser/cookie.png").arg(s_serenity_resource_root.characters())));
  206. debug_menu->addAction(dump_cookies_action);
  207. QObject::connect(dump_cookies_action, &QAction::triggered, this, [this] {
  208. m_cookie_jar.dump_cookies();
  209. });
  210. auto* dump_local_storage_action = new QAction("Dump Loc&al Storage", this);
  211. dump_local_storage_action->setIcon(QIcon(QString("%1/res/icons/browser/local-storage.png").arg(s_serenity_resource_root.characters())));
  212. debug_menu->addAction(dump_local_storage_action);
  213. QObject::connect(dump_local_storage_action, &QAction::triggered, this, [this] {
  214. debug_request("dump-local-storage");
  215. });
  216. debug_menu->addSeparator();
  217. auto* show_line_box_borders_action = new QAction("Show Line Box Borders", this);
  218. show_line_box_borders_action->setCheckable(true);
  219. debug_menu->addAction(show_line_box_borders_action);
  220. QObject::connect(show_line_box_borders_action, &QAction::triggered, this, [this, show_line_box_borders_action] {
  221. bool state = show_line_box_borders_action->isChecked();
  222. debug_request("set-line-box-borders", state ? "on" : "off");
  223. });
  224. debug_menu->addSeparator();
  225. auto* collect_garbage_action = new QAction("Collect &Garbage", this);
  226. collect_garbage_action->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_G));
  227. collect_garbage_action->setIcon(QIcon(QString("%1/res/icons/16x16/trash-can.png").arg(s_serenity_resource_root.characters())));
  228. debug_menu->addAction(collect_garbage_action);
  229. QObject::connect(collect_garbage_action, &QAction::triggered, this, [this] {
  230. debug_request("collect-garbage");
  231. });
  232. auto* clear_cache_action = new QAction("Clear &Cache", this);
  233. clear_cache_action->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_C));
  234. clear_cache_action->setIcon(QIcon(QString("%1/res/icons/browser/clear-cache.png").arg(s_serenity_resource_root.characters())));
  235. debug_menu->addAction(clear_cache_action);
  236. QObject::connect(clear_cache_action, &QAction::triggered, this, [this] {
  237. debug_request("clear-cache");
  238. });
  239. auto* spoof_user_agent_menu = debug_menu->addMenu("Spoof &User Agent");
  240. spoof_user_agent_menu->setIcon(QIcon(QString("%1/res/icons/16x16/spoof.png").arg(s_serenity_resource_root.characters())));
  241. auto* user_agent_group = new QActionGroup(this);
  242. auto add_user_agent = [this, &user_agent_group, &spoof_user_agent_menu](auto& name, auto& user_agent) {
  243. auto* action = new QAction(name, this);
  244. action->setCheckable(true);
  245. user_agent_group->addAction(action);
  246. spoof_user_agent_menu->addAction(action);
  247. QObject::connect(action, &QAction::triggered, this, [this, user_agent] {
  248. debug_request("spoof-user-agent", user_agent);
  249. debug_request("clear-cache"); // clear the cache to ensure requests are re-done with the new user agent
  250. });
  251. return action;
  252. };
  253. auto* disable_spoofing = add_user_agent("Disabled", Web::default_user_agent);
  254. disable_spoofing->setChecked(true);
  255. add_user_agent("Chrome Linux Desktop", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36");
  256. add_user_agent("Firefox Linux Desktop", "Mozilla/5.0 (X11; Linux i686; rv:87.0) Gecko/20100101 Firefox/87.0");
  257. add_user_agent("Safari macOS Desktop", "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15");
  258. add_user_agent("Chrome Android Mobile", "Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.66 Mobile Safari/537.36");
  259. add_user_agent("Firefox Android Mobile", "Mozilla/5.0 (Android 11; Mobile; rv:68.0) Gecko/68.0 Firefox/86.0");
  260. add_user_agent("Safari iOS Mobile", "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1");
  261. auto* custom_user_agent_action = new QAction("Custom...", this);
  262. custom_user_agent_action->setCheckable(true);
  263. user_agent_group->addAction(custom_user_agent_action);
  264. spoof_user_agent_menu->addAction(custom_user_agent_action);
  265. QObject::connect(custom_user_agent_action, &QAction::triggered, this, [this, disable_spoofing] {
  266. auto user_agent = QInputDialog::getText(this, "Custom User Agent", "Enter User Agent:");
  267. if (!user_agent.isEmpty()) {
  268. debug_request("spoof-user-agent", ak_deprecated_string_from_qstring(user_agent));
  269. debug_request("clear-cache"); // clear the cache to ensure requests are re-done with the new user agent
  270. } else {
  271. disable_spoofing->activate(QAction::Trigger);
  272. }
  273. });
  274. debug_menu->addSeparator();
  275. auto* enable_scripting_action = new QAction("Enable Scripting", this);
  276. enable_scripting_action->setCheckable(true);
  277. enable_scripting_action->setChecked(true);
  278. debug_menu->addAction(enable_scripting_action);
  279. QObject::connect(enable_scripting_action, &QAction::triggered, this, [this, enable_scripting_action] {
  280. bool state = enable_scripting_action->isChecked();
  281. debug_request("scripting", state ? "on" : "off");
  282. });
  283. auto* block_pop_ups_action = new QAction("Block Pop-ups", this);
  284. block_pop_ups_action->setCheckable(true);
  285. block_pop_ups_action->setChecked(true);
  286. debug_menu->addAction(block_pop_ups_action);
  287. QObject::connect(block_pop_ups_action, &QAction::triggered, this, [this, block_pop_ups_action] {
  288. bool state = block_pop_ups_action->isChecked();
  289. debug_request("block-pop-ups", state ? "on" : "off");
  290. });
  291. auto* enable_same_origin_policy_action = new QAction("Enable Same-Origin Policy", this);
  292. enable_same_origin_policy_action->setCheckable(true);
  293. debug_menu->addAction(enable_same_origin_policy_action);
  294. QObject::connect(enable_same_origin_policy_action, &QAction::triggered, this, [this, enable_same_origin_policy_action] {
  295. bool state = enable_same_origin_policy_action->isChecked();
  296. debug_request("same-origin-policy", state ? "on" : "off");
  297. });
  298. QObject::connect(new_tab_action, &QAction::triggered, this, [this] {
  299. new_tab(Settings::the()->new_tab_page(), Web::HTML::ActivateTab::Yes);
  300. });
  301. QObject::connect(open_file_action, &QAction::triggered, this, &BrowserWindow::open_file);
  302. QObject::connect(settings_action, &QAction::triggered, this, [this] {
  303. new SettingsDialog(this);
  304. });
  305. QObject::connect(quit_action, &QAction::triggered, this, &QMainWindow::close);
  306. QObject::connect(m_tabs_container, &QTabWidget::currentChanged, [this](int index) {
  307. setWindowTitle(QString("%1 - Ladybird").arg(m_tabs_container->tabText(index)));
  308. set_current_tab(verify_cast<Tab>(m_tabs_container->widget(index)));
  309. });
  310. QObject::connect(m_tabs_container, &QTabWidget::tabCloseRequested, this, &BrowserWindow::close_tab);
  311. QObject::connect(close_current_tab_action, &QAction::triggered, this, &BrowserWindow::close_current_tab);
  312. m_inspect_dom_node_action = make<QAction>("&Inspect Element", this);
  313. connect(m_inspect_dom_node_action, &QAction::triggered, this, [this] {
  314. if (m_current_tab)
  315. m_current_tab->show_inspector_window(Tab::InspectorTarget::HoveredElement);
  316. });
  317. m_go_back_action = make<QAction>("Go Back");
  318. connect(m_go_back_action, &QAction::triggered, this, [this] {
  319. if (m_current_tab)
  320. m_current_tab->back();
  321. });
  322. m_go_forward_action = make<QAction>("Go Forward");
  323. connect(m_go_forward_action, &QAction::triggered, this, [this] {
  324. if (m_current_tab)
  325. m_current_tab->forward();
  326. });
  327. m_reload_action = make<QAction>("&Reload");
  328. connect(m_reload_action, &QAction::triggered, this, [this] {
  329. if (m_current_tab)
  330. m_current_tab->reload();
  331. });
  332. m_go_back_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Back));
  333. m_go_forward_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Forward));
  334. m_reload_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Refresh));
  335. m_go_back_action->setEnabled(false);
  336. m_go_forward_action->setEnabled(false);
  337. if (initial_url.has_value()) {
  338. auto initial_url_string = qstring_from_ak_deprecated_string(initial_url->serialize());
  339. new_tab(initial_url_string, Web::HTML::ActivateTab::Yes);
  340. } else {
  341. new_tab(Settings::the()->new_tab_page(), Web::HTML::ActivateTab::Yes);
  342. }
  343. setCentralWidget(m_tabs_container);
  344. setContextMenuPolicy(Qt::PreventContextMenu);
  345. }
  346. void BrowserWindow::set_current_tab(Tab* tab)
  347. {
  348. m_current_tab = tab;
  349. if (tab)
  350. update_displayed_zoom_level();
  351. }
  352. void BrowserWindow::debug_request(DeprecatedString const& request, DeprecatedString const& argument)
  353. {
  354. if (!m_current_tab)
  355. return;
  356. m_current_tab->debug_request(request, argument);
  357. }
  358. Tab& BrowserWindow::new_tab(QString const& url, Web::HTML::ActivateTab activate_tab)
  359. {
  360. auto tab = make<Tab>(this, m_webdriver_content_ipc_path, m_enable_callgrind_profiling, m_use_lagom_networking);
  361. auto tab_ptr = tab.ptr();
  362. m_tabs.append(std::move(tab));
  363. if (m_current_tab == nullptr) {
  364. set_current_tab(tab_ptr);
  365. }
  366. m_tabs_container->addTab(tab_ptr, "New Tab");
  367. if (activate_tab == Web::HTML::ActivateTab::Yes)
  368. m_tabs_container->setCurrentWidget(tab_ptr);
  369. QObject::connect(tab_ptr, &Tab::title_changed, this, &BrowserWindow::tab_title_changed);
  370. QObject::connect(tab_ptr, &Tab::favicon_changed, this, &BrowserWindow::tab_favicon_changed);
  371. QObject::connect(&tab_ptr->view(), &WebContentView::urls_dropped, this, [this](auto& urls) {
  372. VERIFY(urls.size());
  373. m_current_tab->navigate(urls[0].toString());
  374. for (qsizetype i = 1; i < urls.size(); ++i)
  375. new_tab(urls[i].toString(), Web::HTML::ActivateTab::No);
  376. });
  377. tab_ptr->view().on_new_tab = [this](auto activate_tab) {
  378. auto& tab = new_tab("about:blank", activate_tab);
  379. return tab.view().handle();
  380. };
  381. tab_ptr->view().on_tab_open_request = [this](auto url, auto activate_tab) {
  382. auto& tab = new_tab(qstring_from_ak_deprecated_string(url.to_deprecated_string()), activate_tab);
  383. return tab.view().handle();
  384. };
  385. tab_ptr->view().on_link_click = [this](auto url, auto target, unsigned modifiers) {
  386. // TODO: maybe activate tabs according to some configuration, this is just normal current browser behavior
  387. if (modifiers == Mod_Ctrl) {
  388. m_current_tab->view().on_tab_open_request(url, Web::HTML::ActivateTab::No);
  389. } else if (target == "_blank") {
  390. m_current_tab->view().on_tab_open_request(url, Web::HTML::ActivateTab::Yes);
  391. } else {
  392. m_current_tab->view().load(url);
  393. }
  394. };
  395. tab_ptr->view().on_link_middle_click = [this](auto url, auto target, unsigned modifiers) {
  396. m_current_tab->view().on_link_click(url, target, Mod_Ctrl);
  397. (void)modifiers;
  398. };
  399. tab_ptr->view().on_get_all_cookies = [this](auto const& url) {
  400. return m_cookie_jar.get_all_cookies(url);
  401. };
  402. tab_ptr->view().on_get_named_cookie = [this](auto const& url, auto const& name) {
  403. return m_cookie_jar.get_named_cookie(url, name);
  404. };
  405. tab_ptr->view().on_get_cookie = [this](auto& url, auto source) -> DeprecatedString {
  406. return m_cookie_jar.get_cookie(url, source);
  407. };
  408. tab_ptr->view().on_set_cookie = [this](auto& url, auto& cookie, auto source) {
  409. m_cookie_jar.set_cookie(url, cookie, source);
  410. };
  411. tab_ptr->view().on_update_cookie = [this](auto const& cookie) {
  412. m_cookie_jar.update_cookie(cookie);
  413. };
  414. tab_ptr->focus_location_editor();
  415. tab_ptr->navigate(url);
  416. return *tab_ptr;
  417. }
  418. void BrowserWindow::activate_tab(int index)
  419. {
  420. m_tabs_container->setCurrentIndex(index);
  421. }
  422. void BrowserWindow::close_tab(int index)
  423. {
  424. auto* tab = m_tabs_container->widget(index);
  425. m_tabs_container->removeTab(index);
  426. m_tabs.remove_first_matching([&](auto& entry) {
  427. return entry == tab;
  428. });
  429. }
  430. void BrowserWindow::open_file()
  431. {
  432. m_current_tab->open_file();
  433. }
  434. void BrowserWindow::close_current_tab()
  435. {
  436. close_tab(m_tabs_container->currentIndex());
  437. if (m_tabs_container->count() == 0)
  438. close();
  439. }
  440. int BrowserWindow::tab_index(Tab* tab)
  441. {
  442. return m_tabs_container->indexOf(tab);
  443. }
  444. void BrowserWindow::tab_title_changed(int index, QString const& title)
  445. {
  446. m_tabs_container->setTabText(index, title);
  447. if (m_tabs_container->currentIndex() == index)
  448. setWindowTitle(QString("%1 - Ladybird").arg(title));
  449. }
  450. void BrowserWindow::tab_favicon_changed(int index, QIcon icon)
  451. {
  452. m_tabs_container->setTabIcon(index, icon);
  453. }
  454. void BrowserWindow::open_next_tab()
  455. {
  456. if (m_tabs_container->count() <= 1)
  457. return;
  458. auto next_index = m_tabs_container->currentIndex() + 1;
  459. if (next_index >= m_tabs_container->count())
  460. next_index = 0;
  461. m_tabs_container->setCurrentIndex(next_index);
  462. }
  463. void BrowserWindow::open_previous_tab()
  464. {
  465. if (m_tabs_container->count() <= 1)
  466. return;
  467. auto next_index = m_tabs_container->currentIndex() - 1;
  468. if (next_index < 0)
  469. next_index = m_tabs_container->count() - 1;
  470. m_tabs_container->setCurrentIndex(next_index);
  471. }
  472. void BrowserWindow::enable_auto_color_scheme()
  473. {
  474. for (auto& tab : m_tabs) {
  475. tab->view().set_preferred_color_scheme(Web::CSS::PreferredColorScheme::Auto);
  476. }
  477. }
  478. void BrowserWindow::enable_light_color_scheme()
  479. {
  480. for (auto& tab : m_tabs) {
  481. tab->view().set_preferred_color_scheme(Web::CSS::PreferredColorScheme::Light);
  482. }
  483. }
  484. void BrowserWindow::enable_dark_color_scheme()
  485. {
  486. for (auto& tab : m_tabs) {
  487. tab->view().set_preferred_color_scheme(Web::CSS::PreferredColorScheme::Dark);
  488. }
  489. }
  490. void BrowserWindow::zoom_in()
  491. {
  492. if (!m_current_tab)
  493. return;
  494. m_current_tab->view().zoom_in();
  495. update_displayed_zoom_level();
  496. }
  497. void BrowserWindow::zoom_out()
  498. {
  499. if (!m_current_tab)
  500. return;
  501. m_current_tab->view().zoom_out();
  502. update_displayed_zoom_level();
  503. }
  504. void BrowserWindow::reset_zoom()
  505. {
  506. if (!m_current_tab)
  507. return;
  508. m_current_tab->view().reset_zoom();
  509. update_displayed_zoom_level();
  510. }
  511. void BrowserWindow::update_zoom_menu()
  512. {
  513. VERIFY(m_zoom_menu);
  514. auto zoom_level_text = MUST(String::formatted("&Zoom ({}%)", round_to<int>(m_current_tab->view().zoom_level() * 100)));
  515. m_zoom_menu->setTitle(qstring_from_ak_string(zoom_level_text));
  516. }
  517. void BrowserWindow::select_all()
  518. {
  519. if (!m_current_tab)
  520. return;
  521. if (auto* console = m_current_tab->console(); console && console->isActiveWindow())
  522. console->view().select_all();
  523. else
  524. m_current_tab->view().select_all();
  525. }
  526. void BrowserWindow::update_displayed_zoom_level()
  527. {
  528. VERIFY(m_current_tab);
  529. update_zoom_menu();
  530. m_current_tab->update_reset_zoom_button();
  531. }
  532. void BrowserWindow::copy_selected_text()
  533. {
  534. if (!m_current_tab)
  535. return;
  536. DeprecatedString text;
  537. if (auto* console = m_current_tab->console(); console && console->isActiveWindow())
  538. text = console->view().selected_text();
  539. else
  540. text = m_current_tab->view().selected_text();
  541. auto* clipboard = QGuiApplication::clipboard();
  542. clipboard->setText(qstring_from_ak_deprecated_string(text));
  543. }
  544. void BrowserWindow::resizeEvent(QResizeEvent* event)
  545. {
  546. QWidget::resizeEvent(event);
  547. for (auto& tab : m_tabs) {
  548. tab->view().set_window_size({ frameSize().width(), frameSize().height() });
  549. }
  550. }
  551. void BrowserWindow::moveEvent(QMoveEvent* event)
  552. {
  553. QWidget::moveEvent(event);
  554. for (auto& tab : m_tabs) {
  555. tab->view().set_window_position({ event->pos().x(), event->pos().y() });
  556. }
  557. }
  558. void BrowserWindow::wheelEvent(QWheelEvent* event)
  559. {
  560. if ((event->modifiers() & Qt::ControlModifier) != 0) {
  561. if (event->angleDelta().y() > 0)
  562. zoom_in();
  563. else if (event->angleDelta().y() < 0)
  564. zoom_out();
  565. }
  566. }
  567. bool BrowserWindow::eventFilter(QObject* obj, QEvent* event)
  568. {
  569. if (event->type() == QEvent::MouseButtonRelease) {
  570. auto const* const mouse_event = static_cast<QMouseEvent*>(event);
  571. if (mouse_event->button() == Qt::MouseButton::MiddleButton) {
  572. if (obj == m_tabs_container) {
  573. auto const tab_index = m_tabs_container->tabBar()->tabAt(mouse_event->pos());
  574. close_tab(tab_index);
  575. return true;
  576. }
  577. }
  578. }
  579. return QMainWindow::eventFilter(obj, event);
  580. }
  581. void BrowserWindow::closeEvent(QCloseEvent* event)
  582. {
  583. Settings::the()->set_last_position(pos());
  584. Settings::the()->set_last_size(size());
  585. Settings::the()->set_is_maximized(isMaximized());
  586. QMainWindow::closeEvent(event);
  587. }
  588. }