BrowserWindow.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752
  1. /*
  2. * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2021, networkException <networkexception@serenityos.org>
  4. * Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
  5. * Copyright (c) 2022, the SerenityOS developers.
  6. *
  7. * SPDX-License-Identifier: BSD-2-Clause
  8. */
  9. #include "BrowserWindow.h"
  10. #include "BookmarksBarWidget.h"
  11. #include "Browser.h"
  12. #include "ConsoleWidget.h"
  13. #include "CookieJar.h"
  14. #include "InspectorWidget.h"
  15. #include "Tab.h"
  16. #include <AK/LexicalPath.h>
  17. #include <Applications/Browser/BrowserWindowGML.h>
  18. #include <LibConfig/Client.h>
  19. #include <LibCore/DateTime.h>
  20. #include <LibCore/StandardPaths.h>
  21. #include <LibCore/Stream.h>
  22. #include <LibCore/Version.h>
  23. #include <LibGUI/AboutDialog.h>
  24. #include <LibGUI/Application.h>
  25. #include <LibGUI/Clipboard.h>
  26. #include <LibGUI/Icon.h>
  27. #include <LibGUI/InputBox.h>
  28. #include <LibGUI/Menu.h>
  29. #include <LibGUI/Menubar.h>
  30. #include <LibGUI/MessageBox.h>
  31. #include <LibGUI/Process.h>
  32. #include <LibGUI/SeparatorWidget.h>
  33. #include <LibGUI/Statusbar.h>
  34. #include <LibGUI/TabWidget.h>
  35. #include <LibGUI/ToolbarContainer.h>
  36. #include <LibGUI/Widget.h>
  37. #include <LibGfx/PNGWriter.h>
  38. #include <LibJS/Interpreter.h>
  39. #include <LibWeb/CSS/PreferredColorScheme.h>
  40. #include <LibWeb/Dump.h>
  41. #include <LibWeb/Layout/InitialContainingBlock.h>
  42. #include <LibWeb/Loader/ResourceLoader.h>
  43. #include <LibWebView/OutOfProcessWebView.h>
  44. namespace Browser {
  45. static String bookmarks_file_path()
  46. {
  47. StringBuilder builder;
  48. builder.append(Core::StandardPaths::config_directory());
  49. builder.append("/bookmarks.json"sv);
  50. return builder.to_string();
  51. }
  52. static String search_engines_file_path()
  53. {
  54. StringBuilder builder;
  55. builder.append(Core::StandardPaths::config_directory());
  56. builder.append("/SearchEngines.json"sv);
  57. return builder.to_string();
  58. }
  59. BrowserWindow::BrowserWindow(CookieJar& cookie_jar, URL url)
  60. : m_cookie_jar(cookie_jar)
  61. , m_window_actions(*this)
  62. {
  63. auto app_icon = GUI::Icon::default_icon("app-browser"sv);
  64. m_bookmarks_bar = Browser::BookmarksBarWidget::construct(Browser::bookmarks_file_path(), true);
  65. resize(730, 560);
  66. set_icon(app_icon.bitmap_for_size(16));
  67. set_title("Browser");
  68. auto& widget = set_main_widget<GUI::Widget>();
  69. widget.load_from_gml(browser_window_gml);
  70. auto& top_line = *widget.find_descendant_of_type_named<GUI::HorizontalSeparator>("top_line");
  71. m_tab_widget = *widget.find_descendant_of_type_named<GUI::TabWidget>("tab_widget");
  72. m_tab_widget->on_tab_count_change = [&top_line](size_t tab_count) {
  73. top_line.set_visible(tab_count > 1);
  74. };
  75. m_tab_widget->on_change = [this](auto& active_widget) {
  76. auto& tab = static_cast<Browser::Tab&>(active_widget);
  77. set_window_title_for_tab(tab);
  78. tab.did_become_active();
  79. };
  80. m_tab_widget->on_middle_click = [](auto& clicked_widget) {
  81. auto& tab = static_cast<Browser::Tab&>(clicked_widget);
  82. tab.on_tab_close_request(tab);
  83. };
  84. m_tab_widget->on_tab_close_click = [](auto& clicked_widget) {
  85. auto& tab = static_cast<Browser::Tab&>(clicked_widget);
  86. tab.on_tab_close_request(tab);
  87. };
  88. m_tab_widget->on_context_menu_request = [](auto& clicked_widget, const GUI::ContextMenuEvent& context_menu_event) {
  89. auto& tab = static_cast<Browser::Tab&>(clicked_widget);
  90. tab.context_menu_requested(context_menu_event.screen_position());
  91. };
  92. m_window_actions.on_create_new_tab = [this] {
  93. create_new_tab(Browser::url_from_user_input(Browser::g_new_tab_url), true);
  94. };
  95. m_window_actions.on_create_new_window = [this] {
  96. GUI::Process::spawn_or_show_error(this, "/bin/Browser"sv);
  97. };
  98. m_window_actions.on_next_tab = [this] {
  99. m_tab_widget->activate_next_tab();
  100. };
  101. m_window_actions.on_previous_tab = [this] {
  102. m_tab_widget->activate_previous_tab();
  103. };
  104. for (size_t i = 0; i <= 7; ++i) {
  105. m_window_actions.on_tabs.append([this, i] {
  106. if (i >= m_tab_widget->tab_count())
  107. return;
  108. m_tab_widget->set_tab_index(i);
  109. });
  110. }
  111. m_window_actions.on_tabs.append([this] {
  112. m_tab_widget->activate_last_tab();
  113. });
  114. m_window_actions.on_about = [this] {
  115. auto app_icon = GUI::Icon::default_icon("app-browser"sv);
  116. GUI::AboutDialog::show("Browser"sv, Core::Version::read_long_version_string(), app_icon.bitmap_for_size(32), this);
  117. };
  118. m_window_actions.on_show_bookmarks_bar = [](auto& action) {
  119. Browser::BookmarksBarWidget::the().set_visible(action.is_checked());
  120. Config::write_bool("Browser"sv, "Preferences"sv, "ShowBookmarksBar"sv, action.is_checked());
  121. };
  122. bool show_bookmarks_bar = Config::read_bool("Browser"sv, "Preferences"sv, "ShowBookmarksBar"sv, true);
  123. m_window_actions.show_bookmarks_bar_action().set_checked(show_bookmarks_bar);
  124. Browser::BookmarksBarWidget::the().set_visible(show_bookmarks_bar);
  125. m_window_actions.on_vertical_tabs = [this](auto& action) {
  126. m_tab_widget->set_tab_position(action.is_checked() ? GUI::TabWidget::TabPosition::Left : GUI::TabWidget::TabPosition::Top);
  127. Config::write_bool("Browser"sv, "Preferences"sv, "VerticalTabs"sv, action.is_checked());
  128. };
  129. bool vertical_tabs = Config::read_bool("Browser"sv, "Preferences"sv, "VerticalTabs"sv, false);
  130. m_window_actions.vertical_tabs_action().set_checked(vertical_tabs);
  131. m_tab_widget->set_tab_position(vertical_tabs ? GUI::TabWidget::TabPosition::Left : GUI::TabWidget::TabPosition::Top);
  132. build_menus();
  133. create_new_tab(move(url), true);
  134. }
  135. void BrowserWindow::build_menus()
  136. {
  137. auto& file_menu = add_menu("&File");
  138. file_menu.add_action(WindowActions::the().create_new_tab_action());
  139. file_menu.add_action(WindowActions::the().create_new_window_action());
  140. auto close_tab_action = GUI::CommonActions::make_close_tab_action([this](auto&) {
  141. active_tab().on_tab_close_request(active_tab());
  142. },
  143. this);
  144. file_menu.add_action(close_tab_action);
  145. file_menu.add_separator();
  146. file_menu.add_action(GUI::CommonActions::make_quit_action([](auto&) {
  147. GUI::Application::the()->quit();
  148. }));
  149. auto& view_menu = add_menu("&View");
  150. view_menu.add_action(WindowActions::the().show_bookmarks_bar_action());
  151. view_menu.add_action(WindowActions::the().vertical_tabs_action());
  152. view_menu.add_separator();
  153. view_menu.add_action(GUI::CommonActions::make_fullscreen_action(
  154. [this](auto&) {
  155. auto& tab = active_tab();
  156. set_fullscreen(!is_fullscreen());
  157. auto is_fullscreen = this->is_fullscreen();
  158. tab_widget().set_bar_visible(!is_fullscreen && tab_widget().children().size() > 1);
  159. tab.m_toolbar_container->set_visible(!is_fullscreen);
  160. tab.m_statusbar->set_visible(!is_fullscreen);
  161. if (is_fullscreen) {
  162. tab.view().set_frame_thickness(0);
  163. } else {
  164. tab.view().set_frame_thickness(2);
  165. }
  166. },
  167. this));
  168. m_go_back_action = GUI::CommonActions::make_go_back_action([this](auto&) { active_tab().go_back(); }, this);
  169. m_go_forward_action = GUI::CommonActions::make_go_forward_action([this](auto&) { active_tab().go_forward(); }, this);
  170. m_go_home_action = GUI::CommonActions::make_go_home_action([this](auto&) { active_tab().load(Browser::url_from_user_input(g_home_url)); }, this);
  171. m_go_home_action->set_status_tip("Go to home page");
  172. m_reload_action = GUI::CommonActions::make_reload_action([this](auto&) { active_tab().reload(); }, this);
  173. m_reload_action->set_status_tip("Reload current page");
  174. auto& go_menu = add_menu("&Go");
  175. go_menu.add_action(*m_go_back_action);
  176. go_menu.add_action(*m_go_forward_action);
  177. go_menu.add_action(*m_go_home_action);
  178. go_menu.add_separator();
  179. go_menu.add_action(*m_reload_action);
  180. m_copy_selection_action = GUI::CommonActions::make_copy_action([this](auto&) {
  181. auto& tab = active_tab();
  182. auto selected_text = tab.view().selected_text();
  183. if (!selected_text.is_empty())
  184. GUI::Clipboard::the().set_plain_text(selected_text);
  185. });
  186. m_select_all_action = GUI::CommonActions::make_select_all_action([this](auto&) {
  187. active_tab().view().select_all();
  188. });
  189. m_view_source_action = GUI::Action::create(
  190. "View &Source", { Mod_Ctrl, Key_U }, g_icon_bag.code, [this](auto&) {
  191. active_tab().view().get_source();
  192. },
  193. this);
  194. m_view_source_action->set_status_tip("View source code of the current page");
  195. m_inspect_dom_tree_action = GUI::Action::create(
  196. "Inspect &DOM Tree", { Mod_None, Key_F12 }, g_icon_bag.dom_tree, [this](auto&) {
  197. active_tab().show_inspector_window(Tab::InspectorTarget::Document);
  198. },
  199. this);
  200. m_inspect_dom_tree_action->set_status_tip("Open inspector window for this page");
  201. m_inspect_dom_node_action = GUI::Action::create(
  202. "&Inspect Element", g_icon_bag.inspect, [this](auto&) {
  203. active_tab().show_inspector_window(Tab::InspectorTarget::HoveredElement);
  204. },
  205. this);
  206. m_inspect_dom_node_action->set_status_tip("Open inspector for this element");
  207. m_take_screenshot_action = GUI::Action::create(
  208. "&Take Screenshot"sv, g_icon_bag.filetype_image, [this](auto&) {
  209. if (auto result = take_screenshot(); result.is_error())
  210. GUI::MessageBox::show_error(this, String::formatted("{}", result.error()));
  211. },
  212. this);
  213. m_take_screenshot_action->set_status_tip("Save a screenshot of the current tab to the Downloads directory"sv);
  214. auto& inspect_menu = add_menu("&Inspect");
  215. inspect_menu.add_action(*m_view_source_action);
  216. inspect_menu.add_action(*m_inspect_dom_tree_action);
  217. auto js_console_action = GUI::Action::create(
  218. "Open &JS Console", { Mod_Ctrl, Key_I }, g_icon_bag.filetype_javascript, [this](auto&) {
  219. active_tab().show_console_window();
  220. },
  221. this);
  222. js_console_action->set_status_tip("Open JavaScript console for this page");
  223. inspect_menu.add_action(js_console_action);
  224. auto storage_window_action = GUI::Action::create(
  225. "Open S&torage Inspector", g_icon_bag.cookie, [this](auto&) {
  226. active_tab().show_storage_inspector();
  227. },
  228. this);
  229. storage_window_action->set_status_tip("Show Storage inspector for this page");
  230. inspect_menu.add_action(storage_window_action);
  231. auto& settings_menu = add_menu("&Settings");
  232. m_change_homepage_action = GUI::Action::create(
  233. "Set Homepage URL...", g_icon_bag.go_home, [this](auto&) {
  234. auto homepage_url = Config::read_string("Browser"sv, "Preferences"sv, "Home"sv, "about:blank"sv);
  235. if (GUI::InputBox::show(this, homepage_url, "Enter URL"sv, "Change homepage URL"sv) == GUI::InputBox::ExecResult::OK) {
  236. if (URL(homepage_url).is_valid()) {
  237. Config::write_string("Browser"sv, "Preferences"sv, "Home"sv, homepage_url);
  238. Browser::g_home_url = homepage_url;
  239. } else {
  240. GUI::MessageBox::show_error(this, "The URL you have entered is not valid"sv);
  241. }
  242. }
  243. },
  244. this);
  245. settings_menu.add_action(*m_change_homepage_action);
  246. auto load_search_engines_result = load_search_engines(settings_menu);
  247. if (load_search_engines_result.is_error()) {
  248. dbgln("Failed to open search-engines file: {}", load_search_engines_result.error());
  249. }
  250. auto& color_scheme_menu = settings_menu.add_submenu("&Color Scheme");
  251. color_scheme_menu.set_icon(g_icon_bag.color_chooser);
  252. {
  253. auto current_setting = Web::CSS::preferred_color_scheme_from_string(Config::read_string("Browser"sv, "Preferences"sv, "ColorScheme"sv, "auto"sv));
  254. m_color_scheme_actions.set_exclusive(true);
  255. auto add_color_scheme_action = [&](auto& name, Web::CSS::PreferredColorScheme preference_value) {
  256. auto action = GUI::Action::create_checkable(
  257. name, [=, this](auto&) {
  258. Config::write_string("Browser"sv, "Preferences"sv, "ColorScheme"sv, Web::CSS::preferred_color_scheme_to_string(preference_value));
  259. active_tab().view().set_preferred_color_scheme(preference_value);
  260. },
  261. this);
  262. if (current_setting == preference_value)
  263. action->set_checked(true);
  264. color_scheme_menu.add_action(action);
  265. m_color_scheme_actions.add_action(action);
  266. };
  267. add_color_scheme_action("Follow system theme", Web::CSS::PreferredColorScheme::Auto);
  268. add_color_scheme_action("Light", Web::CSS::PreferredColorScheme::Light);
  269. add_color_scheme_action("Dark", Web::CSS::PreferredColorScheme::Dark);
  270. }
  271. settings_menu.add_separator();
  272. auto open_settings_action = GUI::Action::create("Browser &Settings", Gfx::Bitmap::try_load_from_file("/res/icons/16x16/settings.png"sv).release_value_but_fixme_should_propagate_errors(),
  273. [this](auto&) {
  274. GUI::Process::spawn_or_show_error(this, "/bin/BrowserSettings"sv);
  275. });
  276. settings_menu.add_action(move(open_settings_action));
  277. auto& debug_menu = add_menu("&Debug");
  278. debug_menu.add_action(GUI::Action::create(
  279. "Dump &DOM Tree", g_icon_bag.dom_tree, [this](auto&) {
  280. active_tab().view().debug_request("dump-dom-tree");
  281. },
  282. this));
  283. debug_menu.add_action(GUI::Action::create(
  284. "Dump &Layout Tree", g_icon_bag.layout, [this](auto&) {
  285. active_tab().view().debug_request("dump-layout-tree");
  286. },
  287. this));
  288. debug_menu.add_action(GUI::Action::create(
  289. "Dump S&tacking Context Tree", g_icon_bag.layers, [this](auto&) {
  290. active_tab().view().debug_request("dump-stacking-context-tree");
  291. },
  292. this));
  293. debug_menu.add_action(GUI::Action::create(
  294. "Dump &Style Sheets", g_icon_bag.filetype_css, [this](auto&) {
  295. active_tab().view().debug_request("dump-style-sheets");
  296. },
  297. this));
  298. debug_menu.add_action(GUI::Action::create("Dump &History", { Mod_Ctrl, Key_H }, g_icon_bag.history, [this](auto&) {
  299. active_tab().m_history.dump();
  300. }));
  301. debug_menu.add_action(GUI::Action::create("Dump C&ookies", g_icon_bag.cookie, [this](auto&) {
  302. auto& tab = active_tab();
  303. if (tab.on_dump_cookies)
  304. tab.on_dump_cookies();
  305. }));
  306. debug_menu.add_action(GUI::Action::create("Dump Loc&al Storage", g_icon_bag.local_storage, [this](auto&) {
  307. active_tab().view().debug_request("dump-local-storage");
  308. }));
  309. debug_menu.add_separator();
  310. auto line_box_borders_action = GUI::Action::create_checkable(
  311. "Line &Box Borders", [this](auto& action) {
  312. active_tab().view().debug_request("set-line-box-borders", action.is_checked() ? "on" : "off");
  313. },
  314. this);
  315. line_box_borders_action->set_checked(false);
  316. debug_menu.add_action(line_box_borders_action);
  317. debug_menu.add_separator();
  318. debug_menu.add_action(GUI::Action::create("Collect &Garbage", { Mod_Ctrl | Mod_Shift, Key_G }, g_icon_bag.trash_can, [this](auto&) {
  319. active_tab().view().debug_request("collect-garbage");
  320. }));
  321. debug_menu.add_action(GUI::Action::create("Clear &Cache", { Mod_Ctrl | Mod_Shift, Key_C }, g_icon_bag.clear_cache, [this](auto&) {
  322. active_tab().view().debug_request("clear-cache");
  323. }));
  324. m_user_agent_spoof_actions.set_exclusive(true);
  325. auto& spoof_user_agent_menu = debug_menu.add_submenu("Spoof &User Agent");
  326. m_disable_user_agent_spoofing = GUI::Action::create_checkable("Disabled", [this](auto&) {
  327. active_tab().view().debug_request("spoof-user-agent", Web::default_user_agent);
  328. });
  329. m_disable_user_agent_spoofing->set_status_tip(Web::default_user_agent);
  330. spoof_user_agent_menu.add_action(*m_disable_user_agent_spoofing);
  331. spoof_user_agent_menu.set_icon(g_icon_bag.spoof);
  332. m_user_agent_spoof_actions.add_action(*m_disable_user_agent_spoofing);
  333. m_disable_user_agent_spoofing->set_checked(true);
  334. auto add_user_agent = [this, &spoof_user_agent_menu](auto& name, auto& user_agent) {
  335. auto action = GUI::Action::create_checkable(name, [this, user_agent](auto&) {
  336. active_tab().view().debug_request("spoof-user-agent", user_agent);
  337. });
  338. action->set_status_tip(user_agent);
  339. spoof_user_agent_menu.add_action(action);
  340. m_user_agent_spoof_actions.add_action(action);
  341. };
  342. 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");
  343. add_user_agent("Firefox Linux Desktop", "Mozilla/5.0 (X11; Linux i686; rv:87.0) Gecko/20100101 Firefox/87.0");
  344. 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");
  345. 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");
  346. add_user_agent("Firefox Android Mobile", "Mozilla/5.0 (Android 11; Mobile; rv:68.0) Gecko/68.0 Firefox/86.0");
  347. 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");
  348. auto custom_user_agent = GUI::Action::create_checkable("Custom...", [this](auto& action) {
  349. String user_agent;
  350. if (GUI::InputBox::show(this, user_agent, "Enter User Agent:"sv, "Custom User Agent"sv) != GUI::InputBox::ExecResult::OK || user_agent.is_empty() || user_agent.is_null()) {
  351. m_disable_user_agent_spoofing->activate();
  352. return;
  353. }
  354. active_tab().view().debug_request("spoof-user-agent", user_agent);
  355. action.set_status_tip(user_agent);
  356. });
  357. spoof_user_agent_menu.add_action(custom_user_agent);
  358. m_user_agent_spoof_actions.add_action(custom_user_agent);
  359. debug_menu.add_separator();
  360. auto scripting_enabled_action = GUI::Action::create_checkable(
  361. "Enable Scripting", [this](auto& action) {
  362. active_tab().view().debug_request("scripting", action.is_checked() ? "on" : "off");
  363. },
  364. this);
  365. scripting_enabled_action->set_checked(true);
  366. debug_menu.add_action(scripting_enabled_action);
  367. auto same_origin_policy_action = GUI::Action::create_checkable(
  368. "Enable Same Origin &Policy", [this](auto& action) {
  369. active_tab().view().debug_request("same-origin-policy", action.is_checked() ? "on" : "off");
  370. },
  371. this);
  372. same_origin_policy_action->set_checked(false);
  373. debug_menu.add_action(same_origin_policy_action);
  374. auto& help_menu = add_menu("&Help");
  375. help_menu.add_action(GUI::CommonActions::make_command_palette_action(this));
  376. help_menu.add_action(WindowActions::the().about_action());
  377. }
  378. ErrorOr<void> BrowserWindow::load_search_engines(GUI::Menu& settings_menu)
  379. {
  380. m_search_engine_actions.set_exclusive(true);
  381. auto& search_engine_menu = settings_menu.add_submenu("&Search Engine");
  382. search_engine_menu.set_icon(g_icon_bag.find);
  383. bool search_engine_set = false;
  384. m_disable_search_engine_action = GUI::Action::create_checkable(
  385. "Disable", [](auto&) {
  386. g_search_engine = {};
  387. Config::write_string("Browser"sv, "Preferences"sv, "SearchEngine"sv, g_search_engine);
  388. },
  389. this);
  390. search_engine_menu.add_action(*m_disable_search_engine_action);
  391. m_search_engine_actions.add_action(*m_disable_search_engine_action);
  392. m_disable_search_engine_action->set_checked(true);
  393. auto search_engines_file = TRY(Core::Stream::File::open(Browser::search_engines_file_path(), Core::Stream::OpenMode::Read));
  394. auto file_size = TRY(search_engines_file->size());
  395. auto buffer = TRY(ByteBuffer::create_uninitialized(file_size));
  396. if (search_engines_file->read_or_error(buffer)) {
  397. StringView buffer_contents { buffer.bytes() };
  398. if (auto json = TRY(JsonValue::from_string(buffer_contents)); json.is_array()) {
  399. auto json_array = json.as_array();
  400. for (auto& json_item : json_array.values()) {
  401. if (!json_item.is_object())
  402. continue;
  403. auto search_engine = json_item.as_object();
  404. auto name = search_engine.get("title"sv).to_string();
  405. auto url_format = search_engine.get("url_format"sv).to_string();
  406. auto action = GUI::Action::create_checkable(
  407. name, [&, url_format](auto&) {
  408. g_search_engine = url_format;
  409. Config::write_string("Browser"sv, "Preferences"sv, "SearchEngine"sv, g_search_engine);
  410. },
  411. this);
  412. search_engine_menu.add_action(action);
  413. m_search_engine_actions.add_action(action);
  414. if (g_search_engine == url_format) {
  415. action->set_checked(true);
  416. search_engine_set = true;
  417. }
  418. action->set_status_tip(url_format);
  419. }
  420. }
  421. }
  422. auto custom_search_engine_action = GUI::Action::create_checkable("Custom...", [&](auto& action) {
  423. String search_engine;
  424. if (GUI::InputBox::show(this, search_engine, "Enter URL template:"sv, "Custom Search Engine"sv, "https://host/search?q={}"sv) != GUI::InputBox::ExecResult::OK || search_engine.is_empty()) {
  425. m_disable_search_engine_action->activate();
  426. return;
  427. }
  428. auto argument_count = search_engine.count("{}"sv);
  429. if (argument_count != 1) {
  430. GUI::MessageBox::show(this, "Invalid format, must contain '{}' once!"sv, "Error"sv, GUI::MessageBox::Type::Error);
  431. m_disable_search_engine_action->activate();
  432. return;
  433. }
  434. g_search_engine = search_engine;
  435. Config::write_string("Browser"sv, "Preferences"sv, "SearchEngine"sv, g_search_engine);
  436. action.set_status_tip(search_engine);
  437. });
  438. search_engine_menu.add_action(custom_search_engine_action);
  439. m_search_engine_actions.add_action(custom_search_engine_action);
  440. if (!search_engine_set && !g_search_engine.is_empty()) {
  441. custom_search_engine_action->set_checked(true);
  442. custom_search_engine_action->set_status_tip(g_search_engine);
  443. }
  444. return {};
  445. }
  446. GUI::TabWidget& BrowserWindow::tab_widget()
  447. {
  448. return *m_tab_widget;
  449. }
  450. Tab& BrowserWindow::active_tab()
  451. {
  452. return verify_cast<Tab>(*tab_widget().active_widget());
  453. }
  454. void BrowserWindow::set_window_title_for_tab(Tab const& tab)
  455. {
  456. auto& title = tab.title();
  457. auto url = tab.url();
  458. set_title(String::formatted("{} - Browser", title.is_empty() ? url.to_string() : title));
  459. }
  460. void BrowserWindow::create_new_tab(URL url, bool activate)
  461. {
  462. auto& new_tab = m_tab_widget->add_tab<Browser::Tab>("New tab", *this);
  463. m_tab_widget->set_bar_visible(!is_fullscreen() && m_tab_widget->children().size() > 1);
  464. new_tab.on_title_change = [this, &new_tab](auto& title) {
  465. m_tab_widget->set_tab_title(new_tab, title);
  466. if (m_tab_widget->active_widget() == &new_tab)
  467. set_window_title_for_tab(new_tab);
  468. };
  469. new_tab.on_favicon_change = [this, &new_tab](auto& bitmap) {
  470. m_tab_widget->set_tab_icon(new_tab, &bitmap);
  471. };
  472. new_tab.on_tab_open_request = [this](auto& url) {
  473. create_new_tab(url, true);
  474. };
  475. new_tab.on_tab_close_request = [this](auto& tab) {
  476. m_tab_widget->deferred_invoke([this, &tab] {
  477. m_tab_widget->remove_tab(tab);
  478. m_tab_widget->set_bar_visible(!is_fullscreen() && m_tab_widget->children().size() > 1);
  479. if (m_tab_widget->children().is_empty())
  480. close();
  481. });
  482. };
  483. new_tab.on_tab_close_other_request = [this](auto& tab) {
  484. m_tab_widget->deferred_invoke([this, &tab] {
  485. m_tab_widget->remove_all_tabs_except(tab);
  486. VERIFY(m_tab_widget->children().size() == 1);
  487. m_tab_widget->set_bar_visible(false);
  488. });
  489. };
  490. new_tab.on_get_cookie = [this](auto& url, auto source) -> String {
  491. return m_cookie_jar.get_cookie(url, source);
  492. };
  493. new_tab.on_set_cookie = [this](auto& url, auto& cookie, auto source) {
  494. m_cookie_jar.set_cookie(url, cookie, source);
  495. };
  496. new_tab.on_dump_cookies = [this]() {
  497. m_cookie_jar.dump_cookies();
  498. };
  499. new_tab.on_update_cookie = [this](auto const& url, auto cookie) {
  500. m_cookie_jar.update_cookie(url, move(cookie));
  501. };
  502. new_tab.on_get_cookies_entries = [this]() {
  503. return m_cookie_jar.get_all_cookies();
  504. };
  505. new_tab.on_get_local_storage_entries = [this]() {
  506. return active_tab().view().get_local_storage_entries();
  507. };
  508. new_tab.on_get_session_storage_entries = [this]() {
  509. return active_tab().view().get_session_storage_entries();
  510. };
  511. new_tab.on_take_screenshot = [this]() {
  512. return active_tab().view().take_screenshot();
  513. };
  514. new_tab.webdriver_endpoints().on_get_document_element = [this]() {
  515. return active_tab().view().get_document_element();
  516. };
  517. new_tab.webdriver_endpoints().on_query_selector_all = [this](i32 start_node_id, String const& selector) {
  518. return active_tab().view().query_selector_all(start_node_id, selector);
  519. };
  520. new_tab.webdriver_endpoints().on_get_element_attribute = [this](i32 element_id, String const& name) {
  521. return active_tab().view().get_element_attribute(element_id, name);
  522. };
  523. new_tab.webdriver_endpoints().on_get_element_property = [this](i32 element_id, String const& name) {
  524. return active_tab().view().get_element_property(element_id, name);
  525. };
  526. new_tab.webdriver_endpoints().on_get_active_documents_type = [this]() {
  527. return active_tab().view().get_active_documents_type();
  528. };
  529. new_tab.webdriver_endpoints().on_get_computed_value_for_element = [this](i32 element_id, String const& property_name) {
  530. return active_tab().view().get_computed_value_for_element(element_id, property_name);
  531. };
  532. new_tab.webdriver_endpoints().on_get_element_text = [this](i32 element_id) {
  533. return active_tab().view().get_element_text(element_id);
  534. };
  535. new_tab.webdriver_endpoints().on_get_element_tag_name = [this](i32 element_id) {
  536. return active_tab().view().get_element_tag_name(element_id);
  537. };
  538. new_tab.load(url);
  539. dbgln_if(SPAM_DEBUG, "Added new tab {:p}, loading {}", &new_tab, url);
  540. if (activate)
  541. m_tab_widget->set_active_widget(&new_tab);
  542. }
  543. void BrowserWindow::content_filters_changed()
  544. {
  545. tab_widget().for_each_child_of_type<Browser::Tab>([](auto& tab) {
  546. tab.content_filters_changed();
  547. return IterationDecision::Continue;
  548. });
  549. }
  550. void BrowserWindow::proxy_mappings_changed()
  551. {
  552. tab_widget().for_each_child_of_type<Browser::Tab>([](auto& tab) {
  553. tab.proxy_mappings_changed();
  554. return IterationDecision::Continue;
  555. });
  556. }
  557. void BrowserWindow::config_string_did_change(String const& domain, String const& group, String const& key, String const& value)
  558. {
  559. if (domain != "Browser")
  560. return;
  561. if (group == "Preferences") {
  562. if (key == "SearchEngine")
  563. Browser::g_search_engine = value;
  564. else if (key == "Home")
  565. Browser::g_home_url = value;
  566. else if (key == "NewTab")
  567. Browser::g_new_tab_url = value;
  568. } else if (group.starts_with("Proxy:"sv)) {
  569. dbgln("Proxy mapping changed: {}/{} = {}", group, key, value);
  570. auto proxy_spec = group.substring_view(6);
  571. auto existing_proxy = Browser::g_proxies.find(proxy_spec);
  572. if (existing_proxy.is_end())
  573. Browser::g_proxies.append(proxy_spec);
  574. Browser::g_proxy_mappings.set(key, existing_proxy.index());
  575. proxy_mappings_changed();
  576. }
  577. // TODO: ColorScheme
  578. }
  579. void BrowserWindow::config_bool_did_change(String const& domain, String const& group, String const& key, bool value)
  580. {
  581. dbgln("{} {} {} {}", domain, group, key, value);
  582. if (domain != "Browser" || group != "Preferences")
  583. return;
  584. if (key == "ShowBookmarksBar") {
  585. m_window_actions.show_bookmarks_bar_action().set_checked(value);
  586. Browser::BookmarksBarWidget::the().set_visible(value);
  587. } else if (key == "EnableContentFilters") {
  588. Browser::g_content_filters_enabled = value;
  589. content_filters_changed();
  590. }
  591. // NOTE: CloseDownloadWidgetOnFinish is read each time in DownloadWindow
  592. }
  593. void BrowserWindow::broadcast_window_position(Gfx::IntPoint const& position)
  594. {
  595. tab_widget().for_each_child_of_type<Browser::Tab>([&](auto& tab) {
  596. tab.window_position_changed(position);
  597. return IterationDecision::Continue;
  598. });
  599. }
  600. void BrowserWindow::broadcast_window_size(Gfx::IntSize const& size)
  601. {
  602. tab_widget().for_each_child_of_type<Browser::Tab>([&](auto& tab) {
  603. tab.window_size_changed(size);
  604. return IterationDecision::Continue;
  605. });
  606. }
  607. void BrowserWindow::event(Core::Event& event)
  608. {
  609. switch (event.type()) {
  610. case GUI::Event::Move:
  611. broadcast_window_position(static_cast<GUI::MoveEvent&>(event).position());
  612. break;
  613. case GUI::Event::Resize:
  614. broadcast_window_size(static_cast<GUI::ResizeEvent&>(event).size());
  615. break;
  616. default:
  617. break;
  618. }
  619. Window::event(event);
  620. }
  621. ErrorOr<void> BrowserWindow::take_screenshot()
  622. {
  623. if (!active_tab().on_take_screenshot)
  624. return {};
  625. auto bitmap = active_tab().on_take_screenshot();
  626. if (!bitmap.is_valid())
  627. return Error::from_string_view("Failed to take a screenshot of the current tab"sv);
  628. LexicalPath path { Core::StandardPaths::downloads_directory() };
  629. path = path.append(Core::DateTime::now().to_string("screenshot-%Y-%m-%d-%H-%M-%S.png"sv));
  630. auto encoded = Gfx::PNGWriter::encode(*bitmap.bitmap());
  631. auto screenshot_file = TRY(Core::Stream::File::open(path.string(), Core::Stream::OpenMode::Write));
  632. TRY(screenshot_file->write(encoded));
  633. return {};
  634. }
  635. }