BrowserWindow.cpp 35 KB

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