BrowserWindow.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. /*
  2. * Copyright (c) 2022, 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. *
  6. * SPDX-License-Identifier: BSD-2-Clause
  7. */
  8. #include "BrowserWindow.h"
  9. #include "Settings.h"
  10. #include "SettingsDialog.h"
  11. #include "Utilities.h"
  12. #include "WebContentView.h"
  13. #include <AK/TypeCasts.h>
  14. #include <LibWeb/Loader/ResourceLoader.h>
  15. #include <QAction>
  16. #include <QActionGroup>
  17. #include <QInputDialog>
  18. #include <QPlainTextEdit>
  19. extern String s_serenity_resource_root;
  20. extern Browser::Settings* s_settings;
  21. BrowserWindow::BrowserWindow(int webdriver_fd_passing_socket)
  22. : m_webdriver_fd_passing_socket(webdriver_fd_passing_socket)
  23. {
  24. m_tabs_container = new QTabWidget(this);
  25. m_tabs_container->setElideMode(Qt::TextElideMode::ElideRight);
  26. m_tabs_container->setMovable(true);
  27. m_tabs_container->setTabsClosable(true);
  28. m_tabs_container->setDocumentMode(true);
  29. m_tabs_container->setTabBarAutoHide(true);
  30. auto* menu = menuBar()->addMenu("&File");
  31. auto* new_tab_action = new QAction("New &Tab", this);
  32. new_tab_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_T));
  33. menu->addAction(new_tab_action);
  34. auto* settings_action = new QAction("&Settings", this);
  35. settings_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Comma));
  36. menu->addAction(settings_action);
  37. auto* close_current_tab_action = new QAction("Close Current Tab", this);
  38. close_current_tab_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_W));
  39. menu->addAction(close_current_tab_action);
  40. auto* quit_action = new QAction("&Quit", this);
  41. quit_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q));
  42. menu->addAction(quit_action);
  43. auto* view_menu = menuBar()->addMenu("&View");
  44. auto* open_next_tab_action = new QAction("Open &Next Tab", this);
  45. open_next_tab_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_PageDown));
  46. view_menu->addAction(open_next_tab_action);
  47. QObject::connect(open_next_tab_action, &QAction::triggered, this, &BrowserWindow::open_next_tab);
  48. auto* open_previous_tab_action = new QAction("Open &Previous Tab", this);
  49. open_previous_tab_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_PageUp));
  50. view_menu->addAction(open_previous_tab_action);
  51. QObject::connect(open_previous_tab_action, &QAction::triggered, this, &BrowserWindow::open_previous_tab);
  52. view_menu->addSeparator();
  53. auto* color_scheme_menu = view_menu->addMenu("&Color Scheme");
  54. auto* color_scheme_group = new QActionGroup(this);
  55. auto* auto_color_scheme = new QAction("&Auto", this);
  56. auto_color_scheme->setCheckable(true);
  57. color_scheme_group->addAction(auto_color_scheme);
  58. color_scheme_menu->addAction(auto_color_scheme);
  59. QObject::connect(auto_color_scheme, &QAction::triggered, this, &BrowserWindow::enable_auto_color_scheme);
  60. auto* light_color_scheme = new QAction("&Light", this);
  61. light_color_scheme->setCheckable(true);
  62. color_scheme_group->addAction(light_color_scheme);
  63. color_scheme_menu->addAction(light_color_scheme);
  64. QObject::connect(light_color_scheme, &QAction::triggered, this, &BrowserWindow::enable_light_color_scheme);
  65. auto* dark_color_scheme = new QAction("&Dark", this);
  66. dark_color_scheme->setCheckable(true);
  67. color_scheme_group->addAction(dark_color_scheme);
  68. color_scheme_menu->addAction(dark_color_scheme);
  69. QObject::connect(dark_color_scheme, &QAction::triggered, this, &BrowserWindow::enable_dark_color_scheme);
  70. auto_color_scheme->setChecked(true);
  71. auto* inspect_menu = menuBar()->addMenu("&Inspect");
  72. auto* view_source_action = new QAction("View &Source", this);
  73. view_source_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-html.png").arg(s_serenity_resource_root.characters())));
  74. view_source_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_U));
  75. inspect_menu->addAction(view_source_action);
  76. QObject::connect(view_source_action, &QAction::triggered, this, [this] {
  77. if (m_current_tab) {
  78. m_current_tab->view().get_source();
  79. }
  80. });
  81. auto* js_console_action = new QAction("Show &JS Console", this);
  82. js_console_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-javascript.png").arg(s_serenity_resource_root.characters())));
  83. js_console_action->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_J));
  84. inspect_menu->addAction(js_console_action);
  85. QObject::connect(js_console_action, &QAction::triggered, this, [this] {
  86. if (m_current_tab) {
  87. m_current_tab->view().show_js_console();
  88. }
  89. });
  90. auto* inspector_action = new QAction("Open &Inspector");
  91. inspector_action->setIcon(QIcon(QString("%1/res/icons/browser/dom-tree.png").arg(s_serenity_resource_root.characters())));
  92. inspector_action->setShortcut(QKeySequence("Ctrl+Shift+I"));
  93. inspect_menu->addAction(inspector_action);
  94. QObject::connect(inspector_action, &QAction::triggered, this, [this] {
  95. if (m_current_tab) {
  96. m_current_tab->view().show_inspector();
  97. }
  98. });
  99. auto* debug_menu = menuBar()->addMenu("&Debug");
  100. auto* dump_dom_tree_action = new QAction("Dump DOM Tree", this);
  101. dump_dom_tree_action->setIcon(QIcon(QString("%1/res/icons/browser/dom-tree.png").arg(s_serenity_resource_root.characters())));
  102. debug_menu->addAction(dump_dom_tree_action);
  103. QObject::connect(dump_dom_tree_action, &QAction::triggered, this, [this] {
  104. debug_request("dump-dom-tree");
  105. });
  106. auto* dump_layout_tree_action = new QAction("Dump Layout Tree", this);
  107. dump_layout_tree_action->setIcon(QIcon(QString("%1/res/icons/16x16/layout.png").arg(s_serenity_resource_root.characters())));
  108. debug_menu->addAction(dump_layout_tree_action);
  109. QObject::connect(dump_layout_tree_action, &QAction::triggered, this, [this] {
  110. debug_request("dump-layout-tree");
  111. });
  112. auto* dump_stacking_context_tree_action = new QAction("Dump Stacking Context Tree", this);
  113. dump_stacking_context_tree_action->setIcon(QIcon(QString("%1/res/icons/16x16/layers.png").arg(s_serenity_resource_root.characters())));
  114. debug_menu->addAction(dump_stacking_context_tree_action);
  115. QObject::connect(dump_stacking_context_tree_action, &QAction::triggered, this, [this] {
  116. debug_request("dump-stacking-context-tree");
  117. });
  118. auto* dump_style_sheets_action = new QAction("Dump Style Sheets", this);
  119. dump_style_sheets_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-css.png").arg(s_serenity_resource_root.characters())));
  120. debug_menu->addAction(dump_style_sheets_action);
  121. QObject::connect(dump_style_sheets_action, &QAction::triggered, this, [this] {
  122. debug_request("dump-style-sheets");
  123. });
  124. auto* dump_history_action = new QAction("Dump History", this);
  125. dump_history_action->setIcon(QIcon(QString("%1/res/icons/16x16/history.png").arg(s_serenity_resource_root.characters())));
  126. debug_menu->addAction(dump_history_action);
  127. QObject::connect(dump_history_action, &QAction::triggered, this, [this] {
  128. debug_request("dump-history");
  129. });
  130. auto* dump_cookies_action = new QAction("Dump Cookies", this);
  131. dump_cookies_action->setIcon(QIcon(QString("%1/res/icons/browser/cookie.png").arg(s_serenity_resource_root.characters())));
  132. debug_menu->addAction(dump_cookies_action);
  133. QObject::connect(dump_cookies_action, &QAction::triggered, this, [this] {
  134. m_cookie_jar.dump_cookies();
  135. });
  136. auto* dump_local_storage_action = new QAction("Dump Local Storage", this);
  137. dump_local_storage_action->setIcon(QIcon(QString("%1/res/icons/browser/local-storage.png").arg(s_serenity_resource_root.characters())));
  138. debug_menu->addAction(dump_local_storage_action);
  139. QObject::connect(dump_local_storage_action, &QAction::triggered, this, [this] {
  140. debug_request("dump-local-storage");
  141. });
  142. debug_menu->addSeparator();
  143. auto* show_line_box_borders_action = new QAction("Show Line Box Borders", this);
  144. show_line_box_borders_action->setCheckable(true);
  145. debug_menu->addAction(show_line_box_borders_action);
  146. QObject::connect(show_line_box_borders_action, &QAction::triggered, this, [this, show_line_box_borders_action] {
  147. bool state = show_line_box_borders_action->isChecked();
  148. debug_request("set-line-box-borders", state ? "on" : "off");
  149. });
  150. debug_menu->addSeparator();
  151. auto* collect_garbage_action = new QAction("Collect Garbage", this);
  152. collect_garbage_action->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_G));
  153. collect_garbage_action->setIcon(QIcon(QString("%1/res/icons/16x16/trash-can.png").arg(s_serenity_resource_root.characters())));
  154. debug_menu->addAction(collect_garbage_action);
  155. QObject::connect(collect_garbage_action, &QAction::triggered, this, [this] {
  156. debug_request("collect-garbage");
  157. });
  158. auto* clear_cache_action = new QAction("Clear Cache", this);
  159. clear_cache_action->setIcon(QIcon(QString("%1/res/icons/browser/clear-cache.png").arg(s_serenity_resource_root.characters())));
  160. debug_menu->addAction(clear_cache_action);
  161. QObject::connect(clear_cache_action, &QAction::triggered, this, [this] {
  162. debug_request("clear-cache");
  163. });
  164. auto* spoof_user_agent_menu = debug_menu->addMenu("Spoof User Agent");
  165. spoof_user_agent_menu->setIcon(QIcon(QString("%1/res/icons/16x16/spoof.png").arg(s_serenity_resource_root.characters())));
  166. auto* user_agent_group = new QActionGroup(this);
  167. auto add_user_agent = [this, &user_agent_group, &spoof_user_agent_menu](auto& name, auto& user_agent) {
  168. auto* action = new QAction(name);
  169. action->setCheckable(true);
  170. user_agent_group->addAction(action);
  171. spoof_user_agent_menu->addAction(action);
  172. QObject::connect(action, &QAction::triggered, this, [this, user_agent] {
  173. debug_request("spoof-user-agent", user_agent);
  174. debug_request("clear-cache"); // clear the cache to ensure requests are re-done with the new user agent
  175. });
  176. return action;
  177. };
  178. auto* disable_spoofing = add_user_agent("Disabled", Web::default_user_agent);
  179. disable_spoofing->setChecked(true);
  180. 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");
  181. add_user_agent("Firefox Linux Desktop", "Mozilla/5.0 (X11; Linux i686; rv:87.0) Gecko/20100101 Firefox/87.0");
  182. 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");
  183. 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");
  184. add_user_agent("Firefox Android Mobile", "Mozilla/5.0 (Android 11; Mobile; rv:68.0) Gecko/68.0 Firefox/86.0");
  185. 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");
  186. auto* custom_user_agent_action = new QAction("Custom...");
  187. custom_user_agent_action->setCheckable(true);
  188. user_agent_group->addAction(custom_user_agent_action);
  189. spoof_user_agent_menu->addAction(custom_user_agent_action);
  190. QObject::connect(custom_user_agent_action, &QAction::triggered, this, [this, disable_spoofing] {
  191. auto user_agent = QInputDialog::getText(this, "Custom User Agent", "Enter User Agent:");
  192. if (!user_agent.isEmpty()) {
  193. debug_request("spoof-user-agent", akstring_from_qstring(user_agent));
  194. debug_request("clear-cache"); // clear the cache to ensure requests are re-done with the new user agent
  195. } else {
  196. disable_spoofing->activate(QAction::Trigger);
  197. }
  198. });
  199. debug_menu->addSeparator();
  200. auto* enable_scripting_action = new QAction("Enable Scripting", this);
  201. enable_scripting_action->setCheckable(true);
  202. enable_scripting_action->setChecked(true);
  203. debug_menu->addAction(enable_scripting_action);
  204. QObject::connect(enable_scripting_action, &QAction::triggered, this, [this, enable_scripting_action] {
  205. bool state = enable_scripting_action->isChecked();
  206. debug_request("scripting", state ? "on" : "off");
  207. });
  208. auto* enable_same_origin_policy_action = new QAction("Enable Same-Origin Policy", this);
  209. enable_same_origin_policy_action->setCheckable(true);
  210. debug_menu->addAction(enable_same_origin_policy_action);
  211. QObject::connect(enable_same_origin_policy_action, &QAction::triggered, this, [this, enable_same_origin_policy_action] {
  212. bool state = enable_same_origin_policy_action->isChecked();
  213. debug_request("same-origin-policy", state ? "on" : "off");
  214. });
  215. QObject::connect(new_tab_action, &QAction::triggered, this, &BrowserWindow::new_tab);
  216. QObject::connect(settings_action, &QAction::triggered, this, [this] {
  217. new SettingsDialog(this);
  218. });
  219. QObject::connect(quit_action, &QAction::triggered, this, &QMainWindow::close);
  220. QObject::connect(m_tabs_container, &QTabWidget::currentChanged, [this](int index) {
  221. setWindowTitle(QString("%1 - Ladybird").arg(m_tabs_container->tabText(index)));
  222. setWindowIcon(m_tabs_container->tabIcon(index));
  223. m_current_tab = verify_cast<Tab>(m_tabs_container->widget(index));
  224. });
  225. QObject::connect(m_tabs_container, &QTabWidget::tabCloseRequested, this, &BrowserWindow::close_tab);
  226. QObject::connect(close_current_tab_action, &QAction::triggered, this, &BrowserWindow::close_current_tab);
  227. new_tab();
  228. setCentralWidget(m_tabs_container);
  229. }
  230. void BrowserWindow::debug_request(String const& request, String const& argument)
  231. {
  232. if (!m_current_tab)
  233. return;
  234. m_current_tab->debug_request(request, argument);
  235. }
  236. void BrowserWindow::new_tab()
  237. {
  238. auto tab = make<Tab>(this, m_webdriver_fd_passing_socket);
  239. auto tab_ptr = tab.ptr();
  240. m_tabs.append(std::move(tab));
  241. if (m_current_tab == nullptr) {
  242. m_current_tab = tab_ptr;
  243. }
  244. m_tabs_container->addTab(tab_ptr, "New Tab");
  245. m_tabs_container->setCurrentWidget(tab_ptr);
  246. QObject::connect(tab_ptr, &Tab::title_changed, this, &BrowserWindow::tab_title_changed);
  247. QObject::connect(tab_ptr, &Tab::favicon_changed, this, &BrowserWindow::tab_favicon_changed);
  248. tab_ptr->view().on_get_cookie = [this](auto& url, auto source) -> String {
  249. return m_cookie_jar.get_cookie(url, source);
  250. };
  251. tab_ptr->view().on_set_cookie = [this](auto& url, auto& cookie, auto source) {
  252. m_cookie_jar.set_cookie(url, cookie, source);
  253. };
  254. tab_ptr->focus_location_editor();
  255. }
  256. void BrowserWindow::close_tab(int index)
  257. {
  258. auto* tab = m_tabs_container->widget(index);
  259. m_tabs_container->removeTab(index);
  260. m_tabs.remove_first_matching([&](auto& entry) {
  261. return entry == tab;
  262. });
  263. }
  264. void BrowserWindow::close_current_tab()
  265. {
  266. auto count = m_tabs_container->count() - 1;
  267. if (!count)
  268. close();
  269. else
  270. close_tab(m_tabs_container->currentIndex());
  271. }
  272. int BrowserWindow::tab_index(Tab* tab)
  273. {
  274. return m_tabs_container->indexOf(tab);
  275. }
  276. void BrowserWindow::tab_title_changed(int index, QString const& title)
  277. {
  278. if (title.isEmpty()) {
  279. m_tabs_container->setTabText(index, "...");
  280. if (m_tabs_container->currentIndex() == index)
  281. setWindowTitle("Ladybird");
  282. } else {
  283. m_tabs_container->setTabText(index, title);
  284. if (m_tabs_container->currentIndex() == index)
  285. setWindowTitle(QString("%1 - Ladybird").arg(title));
  286. }
  287. }
  288. void BrowserWindow::tab_favicon_changed(int index, QIcon icon)
  289. {
  290. m_tabs_container->setTabIcon(index, icon);
  291. if (m_tabs_container->currentIndex() == index)
  292. setWindowIcon(icon);
  293. }
  294. void BrowserWindow::open_next_tab()
  295. {
  296. if (m_tabs_container->count() <= 1)
  297. return;
  298. auto next_index = m_tabs_container->currentIndex() + 1;
  299. if (next_index >= m_tabs_container->count())
  300. next_index = 0;
  301. m_tabs_container->setCurrentIndex(next_index);
  302. }
  303. void BrowserWindow::open_previous_tab()
  304. {
  305. if (m_tabs_container->count() <= 1)
  306. return;
  307. auto next_index = m_tabs_container->currentIndex() - 1;
  308. if (next_index < 0)
  309. next_index = m_tabs_container->count() - 1;
  310. m_tabs_container->setCurrentIndex(next_index);
  311. }
  312. void BrowserWindow::enable_auto_color_scheme()
  313. {
  314. for (auto& tab : m_tabs) {
  315. tab.view().set_color_scheme(ColorScheme::Auto);
  316. }
  317. }
  318. void BrowserWindow::enable_light_color_scheme()
  319. {
  320. for (auto& tab : m_tabs) {
  321. tab.view().set_color_scheme(ColorScheme::Light);
  322. }
  323. }
  324. void BrowserWindow::enable_dark_color_scheme()
  325. {
  326. for (auto& tab : m_tabs) {
  327. tab.view().set_color_scheme(ColorScheme::Dark);
  328. }
  329. }