BrowserWindow.cpp 29 KB

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