Tab.cpp 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061
  1. /*
  2. * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2021, Maciej Zygmanowski <sppmacd@pm.me>
  4. * Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
  5. * Copyright (c) 2022, the SerenityOS developers.
  6. * Copyright (c) 2022, networkException <networkexception@serenityos.org>
  7. * Copyright (c) 2023, Cameron Youell <cameronyouell@gmail.com>
  8. *
  9. * SPDX-License-Identifier: BSD-2-Clause
  10. */
  11. #include "Tab.h"
  12. #include "BookmarksBarWidget.h"
  13. #include "Browser.h"
  14. #include "BrowserWindow.h"
  15. #include "DownloadWidget.h"
  16. #include "History/HistoryWidget.h"
  17. #include "InspectorWidget.h"
  18. #include "StorageWidget.h"
  19. #include <AK/StringBuilder.h>
  20. #include <Applications/Browser/TabGML.h>
  21. #include <Applications/Browser/URLBox.h>
  22. #include <Applications/BrowserSettings/Defaults.h>
  23. #include <LibConfig/Client.h>
  24. #include <LibCore/MimeData.h>
  25. #include <LibDesktop/Launcher.h>
  26. #include <LibGUI/Action.h>
  27. #include <LibGUI/Application.h>
  28. #include <LibGUI/BoxLayout.h>
  29. #include <LibGUI/Button.h>
  30. #include <LibGUI/Clipboard.h>
  31. #include <LibGUI/ColorPicker.h>
  32. #include <LibGUI/Dialog.h>
  33. #include <LibGUI/FilePicker.h>
  34. #include <LibGUI/FileTypeFilter.h>
  35. #include <LibGUI/InputBox.h>
  36. #include <LibGUI/Menu.h>
  37. #include <LibGUI/MessageBox.h>
  38. #include <LibGUI/Statusbar.h>
  39. #include <LibGUI/TextBox.h>
  40. #include <LibGUI/Toolbar.h>
  41. #include <LibGUI/ToolbarContainer.h>
  42. #include <LibGUI/Window.h>
  43. #include <LibURL/URL.h>
  44. #include <LibWeb/HTML/BrowsingContext.h>
  45. #include <LibWeb/HTML/SelectedFile.h>
  46. #include <LibWeb/HTML/SyntaxHighlighter/SyntaxHighlighter.h>
  47. #include <LibWeb/Layout/BlockContainer.h>
  48. #include <LibWeb/Layout/Viewport.h>
  49. #include <LibWeb/Loader/ResourceLoader.h>
  50. #include <LibWebView/OutOfProcessWebView.h>
  51. #include <LibWebView/SearchEngine.h>
  52. #include <LibWebView/URL.h>
  53. namespace Browser {
  54. Tab::~Tab()
  55. {
  56. close_sub_widgets();
  57. }
  58. void Tab::start_download(const URL::URL& url)
  59. {
  60. auto window = GUI::Window::construct(&this->window());
  61. window->resize(300, 170);
  62. window->set_title(ByteString::formatted("0% of {}", url.basename()));
  63. window->set_resizable(false);
  64. (void)window->set_main_widget<DownloadWidget>(url);
  65. window->show();
  66. }
  67. void Tab::view_source(const URL::URL& url, ByteString const& source)
  68. {
  69. auto window = GUI::Window::construct(&this->window());
  70. auto editor = window->set_main_widget<GUI::TextEditor>();
  71. editor->set_text(source);
  72. editor->set_mode(GUI::TextEditor::ReadOnly);
  73. editor->set_syntax_highlighter(make<Web::HTML::SyntaxHighlighter>());
  74. editor->set_ruler_visible(true);
  75. editor->set_visualize_trailing_whitespace(false);
  76. window->resize(640, 480);
  77. window->set_title(url.to_byte_string());
  78. window->set_icon(g_icon_bag.filetype_text);
  79. window->set_window_mode(GUI::WindowMode::Modeless);
  80. window->show();
  81. }
  82. void Tab::update_status(Optional<String> text_override, i32 count_waiting)
  83. {
  84. if (text_override.has_value()) {
  85. m_statusbar->set_text(*text_override);
  86. return;
  87. }
  88. if (m_loaded) {
  89. m_statusbar->set_text({});
  90. return;
  91. }
  92. VERIFY(m_navigating_url.has_value());
  93. if (count_waiting == 0) {
  94. // ex: "Loading google.com"
  95. m_statusbar->set_text(String::formatted("Loading {}", m_navigating_url->serialized_host().release_value_but_fixme_should_propagate_errors()).release_value_but_fixme_should_propagate_errors());
  96. } else {
  97. // ex: "google.com is waiting on 5 resources"
  98. m_statusbar->set_text(String::formatted("{} is waiting on {} resource{}", m_navigating_url->serialized_host().release_value_but_fixme_should_propagate_errors(), count_waiting, count_waiting == 1 ? ""sv : "s"sv).release_value_but_fixme_should_propagate_errors());
  99. }
  100. }
  101. Tab::Tab(BrowserWindow& window)
  102. {
  103. load_from_gml(tab_gml).release_value_but_fixme_should_propagate_errors();
  104. m_icon = g_icon_bag.default_favicon;
  105. m_toolbar_container = *find_descendant_of_type_named<GUI::ToolbarContainer>("toolbar_container");
  106. auto& toolbar = *find_descendant_of_type_named<GUI::Toolbar>("toolbar");
  107. auto& webview_container = *find_descendant_of_type_named<GUI::Widget>("webview_container");
  108. m_web_content_view = webview_container.add<WebView::OutOfProcessWebView>();
  109. auto preferred_color_scheme = Web::CSS::preferred_color_scheme_from_string(Config::read_string("Browser"sv, "Preferences"sv, "ColorScheme"sv, Browser::default_color_scheme));
  110. m_web_content_view->set_preferred_color_scheme(preferred_color_scheme);
  111. content_filters_changed();
  112. autoplay_allowlist_changed();
  113. m_web_content_view->set_proxy_mappings(g_proxies, g_proxy_mappings);
  114. if (!g_webdriver_content_ipc_path.is_empty())
  115. enable_webdriver_mode();
  116. auto& go_back_button = toolbar.add_action(window.go_back_action());
  117. go_back_button.on_context_menu_request = [&](auto&) {
  118. if (!m_can_navigate_back)
  119. return;
  120. // FIXME: Reimplement selecting a specific entry using WebContent's history.
  121. };
  122. auto& go_forward_button = toolbar.add_action(window.go_forward_action());
  123. go_forward_button.on_context_menu_request = [&](auto&) {
  124. if (!m_can_navigate_forward)
  125. return;
  126. // FIXME: Reimplement selecting a specific entry using WebContent's history.
  127. };
  128. auto& go_home_button = toolbar.add_action(window.go_home_action());
  129. go_home_button.set_allowed_mouse_buttons_for_pressing(GUI::MouseButton::Primary | GUI::MouseButton::Middle);
  130. go_home_button.on_middle_mouse_click = [&](auto) {
  131. on_tab_open_request(g_home_url);
  132. };
  133. toolbar.add_action(window.reload_action());
  134. m_location_box = toolbar.add<URLBox>();
  135. m_location_box->set_placeholder("Search or enter address"sv);
  136. m_location_box->on_return_pressed = [this] {
  137. if (auto url = WebView::sanitize_url(m_location_box->text(), g_search_engine, WebView::AppendTLD::No); url.has_value())
  138. load(*url);
  139. };
  140. m_location_box->on_ctrl_return_pressed = [this] {
  141. if (auto url = WebView::sanitize_url(m_location_box->text(), g_search_engine, WebView::AppendTLD::Yes); url.has_value())
  142. load(*url);
  143. };
  144. m_location_box->add_custom_context_menu_action(GUI::Action::create("Paste && Go", [this](auto&) {
  145. auto [data, mime_type, _] = GUI::Clipboard::the().fetch_data_and_type();
  146. if (!mime_type.starts_with("text/"sv))
  147. return;
  148. auto const& paste_text = data;
  149. if (paste_text.is_empty())
  150. return;
  151. m_location_box->set_text(paste_text);
  152. m_location_box->on_return_pressed();
  153. }));
  154. auto bookmark_action = GUI::Action::create(
  155. "Bookmark current URL", { Mod_Ctrl, Key_D }, [this](auto&) {
  156. if (auto result = bookmark_current_url(); result.is_error())
  157. GUI::MessageBox::show_error(this->window().main_widget()->window(), MUST(String::formatted("Failed to bookmark URL: {}", result.error())));
  158. },
  159. this);
  160. m_reset_zoom_button = toolbar.add<GUI::Button>();
  161. m_reset_zoom_button->set_tooltip("Reset zoom level"_string);
  162. m_reset_zoom_button->on_click = [&](auto) {
  163. view().reset_zoom();
  164. update_reset_zoom_button();
  165. window.update_zoom_menu();
  166. };
  167. m_reset_zoom_button->set_button_style(Gfx::ButtonStyle::Coolbar);
  168. m_reset_zoom_button->set_visible(false);
  169. m_reset_zoom_button->set_preferred_width(GUI::SpecialDimension::Shrink);
  170. m_bookmark_button = toolbar.add<GUI::Button>();
  171. m_bookmark_button->set_action(bookmark_action);
  172. m_bookmark_button->set_button_style(Gfx::ButtonStyle::Coolbar);
  173. m_bookmark_button->set_focus_policy(GUI::FocusPolicy::TabFocus);
  174. m_bookmark_button->set_icon(g_icon_bag.bookmark_contour);
  175. m_bookmark_button->set_fixed_size(22, 22);
  176. view().on_load_start = [this](auto& url, bool) {
  177. m_navigating_url = url;
  178. m_loaded = false;
  179. auto url_serialized = url.serialize();
  180. m_title = url_serialized;
  181. if (on_title_change)
  182. on_title_change(m_title);
  183. m_icon = g_icon_bag.default_favicon;
  184. if (on_favicon_change)
  185. on_favicon_change(*m_icon);
  186. m_location_box->set_icon(nullptr);
  187. m_location_box->set_text(url_serialized);
  188. update_status();
  189. update_bookmark_button(url_serialized);
  190. if (m_dom_inspector_widget)
  191. m_dom_inspector_widget->reset();
  192. };
  193. view().on_load_finish = [this](auto&) {
  194. m_navigating_url = {};
  195. m_loaded = true;
  196. update_status();
  197. if (m_dom_inspector_widget)
  198. m_dom_inspector_widget->inspect();
  199. };
  200. view().on_url_change = [this](auto const& url) {
  201. auto url_serialized = url.serialize();
  202. m_location_box->set_text(url_serialized);
  203. update_bookmark_button(url_serialized);
  204. };
  205. view().on_navigation_buttons_state_changed = [this](auto back_enabled, auto forward_enabled) {
  206. m_can_navigate_back = back_enabled;
  207. m_can_navigate_forward = forward_enabled;
  208. update_actions();
  209. };
  210. view().on_navigate_back = [this]() {
  211. go_back();
  212. };
  213. view().on_navigate_forward = [this]() {
  214. go_forward();
  215. };
  216. view().on_refresh = [this]() {
  217. reload();
  218. };
  219. view().on_link_click = [this](auto& url, auto& target, unsigned modifiers) {
  220. if (target == "_blank" || modifiers == Mod_Ctrl) {
  221. on_tab_open_request(url);
  222. } else {
  223. load(url);
  224. }
  225. };
  226. view().on_resource_status_change = [this](auto count_waiting) {
  227. update_status({}, count_waiting);
  228. };
  229. view().on_restore_window = [this]() {
  230. this->window().show();
  231. this->window().move_to_front();
  232. m_web_content_view->set_system_visibility_state(true);
  233. };
  234. view().on_reposition_window = [this](Gfx::IntPoint position) {
  235. this->window().move_to(position);
  236. return this->window().position();
  237. };
  238. view().on_resize_window = [this](Gfx::IntSize size) {
  239. this->window().resize(size);
  240. return this->window().size();
  241. };
  242. view().on_maximize_window = [this]() {
  243. this->window().set_maximized(true);
  244. return this->window().rect();
  245. };
  246. view().on_minimize_window = [this]() {
  247. this->window().set_minimized(true);
  248. m_web_content_view->set_system_visibility_state(false);
  249. return this->window().rect();
  250. };
  251. view().on_fullscreen_window = [this]() {
  252. this->window().set_fullscreen(true);
  253. return this->window().rect();
  254. };
  255. m_link_context_menu = GUI::Menu::construct();
  256. auto link_default_action = GUI::Action::create("&Open", g_icon_bag.go_to, [this](auto&) {
  257. view().on_link_click(m_link_context_menu_url, "", 0);
  258. });
  259. m_link_context_menu->add_action(link_default_action);
  260. m_link_context_menu_default_action = link_default_action;
  261. m_link_context_menu->add_action(GUI::Action::create("Open in New &Tab", g_icon_bag.new_tab, [this](auto&) {
  262. view().on_link_click(m_link_context_menu_url, "_blank", 0);
  263. }));
  264. m_link_context_menu->add_separator();
  265. m_link_copy_action = GUI::Action::create("Copy &URL", g_icon_bag.copy, [this](auto&) {
  266. GUI::Clipboard::the().set_plain_text(WebView::url_text_to_copy(m_link_context_menu_url));
  267. });
  268. m_link_context_menu->add_action(*m_link_copy_action);
  269. m_link_context_menu->add_separator();
  270. m_link_context_menu->add_action(GUI::Action::create("&Download", g_icon_bag.download, [this](auto&) {
  271. start_download(m_link_context_menu_url);
  272. }));
  273. m_link_context_menu->add_separator();
  274. m_link_context_menu->add_action(window.inspect_dom_node_action());
  275. view().on_link_context_menu_request = [this](auto& url, auto widget_position) {
  276. m_link_context_menu_url = url;
  277. switch (WebView::url_type(url)) {
  278. case WebView::URLType::Email:
  279. m_link_copy_action->set_text("Copy &Email Address");
  280. break;
  281. case WebView::URLType::Telephone:
  282. m_link_copy_action->set_text("Copy &Phone Number");
  283. break;
  284. case WebView::URLType::Other:
  285. m_link_copy_action->set_text("Copy &URL");
  286. break;
  287. }
  288. auto screen_position = view().screen_relative_rect().location().translated(widget_position);
  289. m_link_context_menu->popup(screen_position, m_link_context_menu_default_action);
  290. };
  291. m_image_context_menu = GUI::Menu::construct();
  292. m_image_context_menu->add_action(GUI::Action::create("&Open Image", g_icon_bag.filetype_image, [this](auto&) {
  293. view().on_link_click(m_image_context_menu_url, "", 0);
  294. }));
  295. m_image_context_menu->add_action(GUI::Action::create("Open Image in New &Tab", g_icon_bag.new_tab, [this](auto&) {
  296. view().on_link_click(m_image_context_menu_url, "_blank", 0);
  297. }));
  298. m_image_context_menu->add_separator();
  299. m_image_context_menu->add_action(GUI::Action::create("&Copy Image", g_icon_bag.copy, [this](auto&) {
  300. if (m_image_context_menu_bitmap.is_valid())
  301. GUI::Clipboard::the().set_bitmap(*m_image_context_menu_bitmap.bitmap());
  302. }));
  303. m_image_context_menu->add_action(GUI::Action::create("Copy Image &URL", g_icon_bag.copy, [this](auto&) {
  304. GUI::Clipboard::the().set_plain_text(m_image_context_menu_url.to_byte_string());
  305. }));
  306. m_image_context_menu->add_separator();
  307. m_image_context_menu->add_action(GUI::Action::create("&Download", g_icon_bag.download, [this](auto&) {
  308. start_download(m_image_context_menu_url);
  309. }));
  310. m_image_context_menu->add_separator();
  311. m_image_context_menu->add_action(window.inspect_dom_node_action());
  312. view().on_image_context_menu_request = [this](auto& image_url, auto widget_position, Gfx::ShareableBitmap const& shareable_bitmap) {
  313. m_image_context_menu_url = image_url;
  314. m_image_context_menu_bitmap = shareable_bitmap;
  315. auto screen_position = view().screen_relative_rect().location().translated(widget_position);
  316. m_image_context_menu->popup(screen_position);
  317. };
  318. m_media_context_menu_play_pause_action = GUI::Action::create("&Play", g_icon_bag.play, [this](auto&) {
  319. view().toggle_media_play_state();
  320. });
  321. m_media_context_menu_mute_unmute_action = GUI::Action::create("&Mute", g_icon_bag.mute, [this](auto&) {
  322. view().toggle_media_mute_state();
  323. });
  324. m_media_context_menu_controls_action = GUI::Action::create_checkable("Show &Controls", [this](auto&) {
  325. view().toggle_media_controls_state();
  326. });
  327. m_media_context_menu_loop_action = GUI::Action::create_checkable("&Loop", [this](auto&) {
  328. view().toggle_media_loop_state();
  329. });
  330. m_audio_context_menu = GUI::Menu::construct();
  331. m_audio_context_menu->add_action(*m_media_context_menu_play_pause_action);
  332. m_audio_context_menu->add_action(*m_media_context_menu_mute_unmute_action);
  333. m_audio_context_menu->add_action(*m_media_context_menu_controls_action);
  334. m_audio_context_menu->add_action(*m_media_context_menu_loop_action);
  335. m_audio_context_menu->add_separator();
  336. m_audio_context_menu->add_action(GUI::Action::create("&Open Audio", g_icon_bag.filetype_audio, [this](auto&) {
  337. view().on_link_click(m_media_context_menu_url, "", 0);
  338. }));
  339. m_audio_context_menu->add_action(GUI::Action::create("Open Audio in New &Tab", g_icon_bag.new_tab, [this](auto&) {
  340. view().on_link_click(m_media_context_menu_url, "_blank", 0);
  341. }));
  342. m_audio_context_menu->add_separator();
  343. m_audio_context_menu->add_action(GUI::Action::create("Copy Audio &URL", g_icon_bag.copy, [this](auto&) {
  344. GUI::Clipboard::the().set_plain_text(m_media_context_menu_url.to_byte_string());
  345. }));
  346. m_audio_context_menu->add_separator();
  347. m_audio_context_menu->add_action(GUI::Action::create("&Download", g_icon_bag.download, [this](auto&) {
  348. start_download(m_media_context_menu_url);
  349. }));
  350. m_audio_context_menu->add_separator();
  351. m_audio_context_menu->add_action(window.inspect_dom_node_action());
  352. m_video_context_menu = GUI::Menu::construct();
  353. m_video_context_menu->add_action(*m_media_context_menu_play_pause_action);
  354. m_video_context_menu->add_action(*m_media_context_menu_mute_unmute_action);
  355. m_video_context_menu->add_action(*m_media_context_menu_controls_action);
  356. m_video_context_menu->add_action(*m_media_context_menu_loop_action);
  357. m_video_context_menu->add_separator();
  358. m_video_context_menu->add_action(GUI::Action::create("&Open Video", g_icon_bag.filetype_video, [this](auto&) {
  359. view().on_link_click(m_media_context_menu_url, "", 0);
  360. }));
  361. m_video_context_menu->add_action(GUI::Action::create("Open Video in New &Tab", g_icon_bag.new_tab, [this](auto&) {
  362. view().on_link_click(m_media_context_menu_url, "_blank", 0);
  363. }));
  364. m_video_context_menu->add_separator();
  365. m_video_context_menu->add_action(GUI::Action::create("Copy Video &URL", g_icon_bag.copy, [this](auto&) {
  366. GUI::Clipboard::the().set_plain_text(m_media_context_menu_url.to_byte_string());
  367. }));
  368. m_video_context_menu->add_separator();
  369. m_video_context_menu->add_action(GUI::Action::create("&Download", g_icon_bag.download, [this](auto&) {
  370. start_download(m_media_context_menu_url);
  371. }));
  372. m_video_context_menu->add_separator();
  373. m_video_context_menu->add_action(window.inspect_dom_node_action());
  374. view().on_media_context_menu_request = [this](auto widget_position, Web::Page::MediaContextMenu const& menu) {
  375. m_media_context_menu_url = menu.media_url;
  376. if (menu.is_playing) {
  377. m_media_context_menu_play_pause_action->set_icon(g_icon_bag.pause);
  378. m_media_context_menu_play_pause_action->set_text("&Pause"sv);
  379. } else {
  380. m_media_context_menu_play_pause_action->set_icon(g_icon_bag.play);
  381. m_media_context_menu_play_pause_action->set_text("&Play"sv);
  382. }
  383. if (menu.is_muted) {
  384. m_media_context_menu_mute_unmute_action->set_icon(g_icon_bag.unmute);
  385. m_media_context_menu_mute_unmute_action->set_text("Un&mute"sv);
  386. } else {
  387. m_media_context_menu_mute_unmute_action->set_icon(g_icon_bag.mute);
  388. m_media_context_menu_mute_unmute_action->set_text("&Mute"sv);
  389. }
  390. m_media_context_menu_controls_action->set_checked(menu.has_user_agent_controls);
  391. m_media_context_menu_loop_action->set_checked(menu.is_looping);
  392. auto screen_position = view().screen_relative_rect().location().translated(widget_position);
  393. if (menu.is_video)
  394. m_video_context_menu->popup(screen_position);
  395. else
  396. m_audio_context_menu->popup(screen_position);
  397. };
  398. view().on_link_middle_click = [this](auto& href, auto&, auto) {
  399. view().on_link_click(href, "_blank", 0);
  400. };
  401. view().on_title_change = [this](auto const& title) {
  402. m_title = title;
  403. if (on_title_change)
  404. on_title_change(m_title);
  405. };
  406. view().on_favicon_change = [this](auto& icon) {
  407. m_icon = icon;
  408. m_location_box->set_icon(&icon);
  409. if (on_favicon_change)
  410. on_favicon_change(icon);
  411. };
  412. view().on_request_alert = [this](String const& message) {
  413. auto& window = this->window();
  414. m_dialog = GUI::MessageBox::create(&window, message, "Alert"sv, GUI::MessageBox::Type::Information, GUI::MessageBox::InputType::OK).release_value_but_fixme_should_propagate_errors();
  415. m_dialog->set_icon(window.icon());
  416. m_dialog->exec();
  417. view().alert_closed();
  418. m_dialog = nullptr;
  419. };
  420. view().on_request_confirm = [this](String const& message) {
  421. auto& window = this->window();
  422. m_dialog = GUI::MessageBox::create(&window, message, "Confirm"sv, GUI::MessageBox::Type::Warning, GUI::MessageBox::InputType::OKCancel).release_value_but_fixme_should_propagate_errors();
  423. m_dialog->set_icon(window.icon());
  424. view().confirm_closed(m_dialog->exec() == GUI::Dialog::ExecResult::OK);
  425. m_dialog = nullptr;
  426. };
  427. view().on_request_prompt = [this](String const& message, String const& default_) {
  428. auto& window = this->window();
  429. String mutable_value = default_;
  430. m_dialog = GUI::InputBox::create(&window, mutable_value, message, "Prompt"sv, GUI::InputType::Text).release_value_but_fixme_should_propagate_errors();
  431. m_dialog->set_icon(window.icon());
  432. if (m_dialog->exec() == GUI::InputBox::ExecResult::OK) {
  433. auto const& dialog = static_cast<GUI::InputBox const&>(*m_dialog);
  434. auto response = dialog.text_value();
  435. view().prompt_closed(move(response));
  436. } else {
  437. view().prompt_closed({});
  438. }
  439. m_dialog = nullptr;
  440. };
  441. view().on_request_set_prompt_text = [this](String const& message) {
  442. if (m_dialog && is<GUI::InputBox>(*m_dialog))
  443. static_cast<GUI::InputBox&>(*m_dialog).set_text_value(message);
  444. };
  445. view().on_request_accept_dialog = [this]() {
  446. if (m_dialog)
  447. m_dialog->done(GUI::Dialog::ExecResult::OK);
  448. };
  449. view().on_request_dismiss_dialog = [this]() {
  450. if (m_dialog)
  451. m_dialog->done(GUI::Dialog::ExecResult::Cancel);
  452. };
  453. view().on_request_color_picker = [this](Color current_color) {
  454. auto& window = this->window();
  455. m_dialog = GUI::ColorPicker::construct(current_color, &window);
  456. auto& dialog = static_cast<GUI::ColorPicker&>(*m_dialog);
  457. dialog.set_icon(window.icon());
  458. dialog.set_color_has_alpha_channel(false);
  459. dialog.on_color_changed = [this](Color color) {
  460. view().color_picker_update(color, Web::HTML::ColorPickerUpdateState::Update);
  461. };
  462. if (dialog.exec() == GUI::ColorPicker::ExecResult::OK)
  463. view().color_picker_update(dialog.color(), Web::HTML::ColorPickerUpdateState::Closed);
  464. else
  465. view().color_picker_update({}, Web::HTML::ColorPickerUpdateState::Closed);
  466. m_dialog = nullptr;
  467. };
  468. view().on_request_file_picker = [this](auto const& accepted_file_types, auto allow_multiple_files) {
  469. // FIXME: GUI::FilePicker does not allow selecting multiple files at once.
  470. (void)allow_multiple_files;
  471. Vector<Web::HTML::SelectedFile> selected_files;
  472. auto& window = this->window();
  473. auto create_selected_file = [&](auto file_path) {
  474. if (auto file = Web::HTML::SelectedFile::from_file_path(file_path); file.is_error())
  475. warnln("Unable to open file {}: {}", file_path, file.error());
  476. else
  477. selected_files.append(file.release_value());
  478. };
  479. Vector<GUI::FileTypeFilter> accepted_file_filters;
  480. for (auto const& filter : accepted_file_types.filters) {
  481. filter.visit(
  482. [&](Web::HTML::FileFilter::FileType type) {
  483. switch (type) {
  484. case Web::HTML::FileFilter::FileType::Audio:
  485. accepted_file_filters.append(GUI::FileTypeFilter::audio_files());
  486. break;
  487. case Web::HTML::FileFilter::FileType::Image:
  488. accepted_file_filters.append(GUI::FileTypeFilter::image_files());
  489. break;
  490. case Web::HTML::FileFilter::FileType::Video:
  491. accepted_file_filters.append(GUI::FileTypeFilter::video_files());
  492. break;
  493. }
  494. },
  495. [&](Web::HTML::FileFilter::MimeType const& filter) {
  496. if (auto mime_type = Core::get_mime_type_data(filter.value); mime_type.has_value()) {
  497. Vector<ByteString> extensions;
  498. extensions.ensure_capacity(mime_type->common_extensions.size());
  499. for (auto extension : mime_type->common_extensions)
  500. extensions.append(extension);
  501. accepted_file_filters.append({ mime_type->description, move(extensions) });
  502. }
  503. },
  504. [&](Web::HTML::FileFilter::Extension const& filter) {
  505. accepted_file_filters.empend(ByteString {}, Vector<ByteString> { filter.value.to_byte_string() });
  506. });
  507. }
  508. accepted_file_filters.size() > 1 ? accepted_file_filters.prepend(GUI::FileTypeFilter::all_files()) : accepted_file_filters.append(GUI::FileTypeFilter::all_files());
  509. if (auto path = GUI::FilePicker::get_open_filepath(&window, "Select file", Core::StandardPaths::home_directory(), false, GUI::Dialog::ScreenPosition::CenterWithinParent, move(accepted_file_filters)); path.has_value())
  510. create_selected_file(path.release_value());
  511. view().file_picker_closed(std::move(selected_files));
  512. };
  513. m_select_dropdown = GUI::Menu::construct();
  514. m_select_dropdown->on_visibility_change = [this](bool visible) {
  515. if (!visible && !m_select_dropdown_closed_by_action)
  516. view().select_dropdown_closed({});
  517. };
  518. view().on_request_select_dropdown = [this](Gfx::IntPoint content_position, i32 minimum_width, Vector<Web::HTML::SelectItem> items) {
  519. m_select_dropdown_closed_by_action = false;
  520. m_select_dropdown->remove_all_actions();
  521. m_select_dropdown->set_minimum_width(minimum_width);
  522. auto add_menu_item = [this](Web::HTML::SelectItemOption const& item_option, bool in_option_group) {
  523. auto action = GUI::Action::create(in_option_group ? ByteString::formatted(" {}", item_option.label) : item_option.label.to_byte_string(), [this, item_option](GUI::Action&) {
  524. m_select_dropdown_closed_by_action = true;
  525. view().select_dropdown_closed(item_option.id);
  526. });
  527. action->set_checkable(true);
  528. action->set_checked(item_option.selected);
  529. action->set_enabled(!item_option.disabled);
  530. m_select_dropdown->add_action(action);
  531. };
  532. for (auto const& item : items) {
  533. if (item.has<Web::HTML::SelectItemOptionGroup>()) {
  534. auto const& item_option_group = item.get<Web::HTML::SelectItemOptionGroup>();
  535. auto subtitle = GUI::Action::create(item_option_group.label.to_byte_string(), nullptr);
  536. subtitle->set_enabled(false);
  537. m_select_dropdown->add_action(subtitle);
  538. for (auto const& item_option : item_option_group.items)
  539. add_menu_item(item_option, true);
  540. }
  541. if (item.has<Web::HTML::SelectItemOption>())
  542. add_menu_item(item.get<Web::HTML::SelectItemOption>(), false);
  543. if (item.has<Web::HTML::SelectItemSeparator>())
  544. m_select_dropdown->add_separator();
  545. }
  546. m_select_dropdown->popup(view().screen_relative_rect().location().translated(content_position));
  547. };
  548. view().on_received_source = [this](auto& url, auto& source) {
  549. view_source(url, source);
  550. };
  551. auto focus_location_box_action = GUI::Action::create(
  552. "Focus location box", { Mod_Ctrl, Key_L }, Key_F6, [this](auto&) {
  553. m_location_box->set_focus(true);
  554. m_location_box->select_current_line();
  555. },
  556. this);
  557. m_statusbar = *find_descendant_of_type_named<GUI::Statusbar>("statusbar");
  558. view().on_link_hover = [this](auto& url) {
  559. update_status(String::from_byte_string(url.to_byte_string()).release_value_but_fixme_should_propagate_errors());
  560. };
  561. view().on_link_unhover = [this]() {
  562. view().set_override_cursor(Gfx::StandardCursor::None);
  563. update_status();
  564. };
  565. view().on_new_web_view = [this](auto activate_tab, auto, auto) {
  566. // FIXME: Create a child tab that re-uses the ConnectionFromClient of the parent tab
  567. auto& tab = this->window().create_new_tab(URL::URL("about:blank"), activate_tab);
  568. return tab.view().handle();
  569. };
  570. view().on_activate_tab = [this]() {
  571. on_activate_tab_request(*this);
  572. };
  573. view().on_close = [this] {
  574. on_tab_close_request(*this);
  575. };
  576. view().on_insert_clipboard_entry = [](auto const& data, auto const&, auto const& mime_type) {
  577. GUI::Clipboard::the().set_data(data.bytes(), mime_type.to_byte_string());
  578. };
  579. m_tab_context_menu = GUI::Menu::construct();
  580. m_tab_context_menu->add_action(GUI::CommonActions::make_reload_action([this](auto&) {
  581. reload();
  582. }));
  583. m_tab_context_menu->add_action(GUI::CommonActions::make_close_tab_action([this](auto&) {
  584. on_tab_close_request(*this);
  585. }));
  586. m_tab_context_menu->add_action(GUI::Action::create("&Duplicate Tab", g_icon_bag.duplicate_tab, [this](auto&) {
  587. on_tab_open_request(url());
  588. }));
  589. m_tab_context_menu->add_action(GUI::Action::create("Close &Other Tabs", g_icon_bag.close_other_tabs, [this](auto&) {
  590. on_tab_close_other_request(*this);
  591. }));
  592. auto search_selected_text_action = GUI::Action::create(
  593. "&Search for <query>"sv, g_icon_bag.search, [this](auto&) {
  594. auto url = MUST(String::formatted(g_search_engine, URL::percent_encode(*m_page_context_menu_search_text)));
  595. this->window().create_new_tab(url, Web::HTML::ActivateTab::Yes);
  596. },
  597. this);
  598. auto take_screenshot = [this](auto type) {
  599. auto& view = this->view();
  600. view.take_screenshot(type)
  601. ->when_resolved([this](auto const& path) {
  602. auto message = MUST(String::formatted("Screenshot saved to: {}", path));
  603. auto response = GUI::MessageBox::show(&this->window(), message, "Ladybird"sv, GUI::MessageBox::Type::Information, GUI::MessageBox::InputType::OKReveal);
  604. if (response == GUI::MessageBox::ExecResult::Reveal)
  605. Desktop::Launcher::open(URL::create_with_file_scheme(path.dirname()));
  606. })
  607. .when_rejected([this](auto const& error) {
  608. auto error_message = MUST(String::formatted("{}", error));
  609. GUI::MessageBox::show_error(&this->window(), error_message);
  610. });
  611. };
  612. auto take_visible_screenshot_action = GUI::Action::create(
  613. "Take &Visible Screenshot"sv, g_icon_bag.filetype_image, [take_screenshot](auto&) {
  614. take_screenshot(WebView::ViewImplementation::ScreenshotType::Visible);
  615. },
  616. this);
  617. take_visible_screenshot_action->set_status_tip("Save a screenshot of the visible portion of the current tab to the Downloads directory"_string);
  618. auto take_full_screenshot_action = GUI::Action::create(
  619. "Take &Full Screenshot"sv, g_icon_bag.filetype_image, [take_screenshot](auto&) {
  620. take_screenshot(WebView::ViewImplementation::ScreenshotType::Full);
  621. },
  622. this);
  623. take_full_screenshot_action->set_status_tip("Save a screenshot of the entirety of the current tab to the Downloads directory"_string);
  624. m_page_context_menu = GUI::Menu::construct();
  625. m_page_context_menu->add_action(window.go_back_action());
  626. m_page_context_menu->add_action(window.go_forward_action());
  627. m_page_context_menu->add_action(window.reload_action());
  628. m_page_context_menu->add_separator();
  629. m_page_context_menu->add_action(window.copy_selection_action());
  630. m_page_context_menu->add_action(window.paste_action());
  631. m_page_context_menu->add_action(window.select_all_action());
  632. // FIXME: It would be nice to have a separator here, but the below action is sometimes hidden, and WindowServer
  633. // does not hide successive separators like other toolkits.
  634. m_page_context_menu->add_action(search_selected_text_action);
  635. m_page_context_menu->add_separator();
  636. m_page_context_menu->add_action(move(take_visible_screenshot_action));
  637. m_page_context_menu->add_action(move(take_full_screenshot_action));
  638. m_page_context_menu->add_separator();
  639. m_page_context_menu->add_action(window.view_source_action());
  640. m_page_context_menu->add_action(window.inspect_dom_tree_action());
  641. m_page_context_menu->add_action(window.inspect_dom_node_action());
  642. m_page_context_menu->on_visibility_change = [this](auto visible) {
  643. if (!visible)
  644. m_page_context_menu_search_text = {};
  645. };
  646. view().on_context_menu_request = [&, search_selected_text_action = move(search_selected_text_action)](auto widget_position) {
  647. m_page_context_menu_search_text = g_search_engine.is_empty()
  648. ? OptionalNone {}
  649. : view().selected_text_with_whitespace_collapsed();
  650. if (m_page_context_menu_search_text.has_value()) {
  651. auto action_text = WebView::format_search_query_for_display(g_search_engine, *m_page_context_menu_search_text);
  652. search_selected_text_action->set_text(action_text.to_byte_string());
  653. search_selected_text_action->set_visible(true);
  654. } else {
  655. search_selected_text_action->set_visible(false);
  656. }
  657. auto screen_position = view().screen_relative_rect().location().translated(widget_position);
  658. m_page_context_menu->popup(screen_position);
  659. };
  660. }
  661. void Tab::update_reset_zoom_button()
  662. {
  663. auto zoom_level = view().zoom_level();
  664. if (zoom_level != 1.0f) {
  665. m_reset_zoom_button->set_text(MUST(String::formatted("{}%", round_to<int>(zoom_level * 100))));
  666. m_reset_zoom_button->set_visible(true);
  667. } else {
  668. m_reset_zoom_button->set_visible(false);
  669. }
  670. }
  671. void Tab::load(URL::URL const& url)
  672. {
  673. m_web_content_view->load(url);
  674. m_location_box->set_focus(false);
  675. }
  676. URL::URL Tab::url() const
  677. {
  678. return m_web_content_view->url();
  679. }
  680. void Tab::reload()
  681. {
  682. view().reload();
  683. }
  684. void Tab::go_back()
  685. {
  686. view().traverse_the_history_by_delta(-1);
  687. }
  688. void Tab::go_forward()
  689. {
  690. view().traverse_the_history_by_delta(1);
  691. }
  692. void Tab::update_actions()
  693. {
  694. auto& window = this->window();
  695. if (this != &window.active_tab())
  696. return;
  697. window.go_back_action().set_enabled(m_can_navigate_back);
  698. window.go_forward_action().set_enabled(m_can_navigate_forward);
  699. }
  700. ErrorOr<void> Tab::bookmark_current_url()
  701. {
  702. auto url = this->url().to_byte_string();
  703. if (BookmarksBarWidget::the().contains_bookmark(url)) {
  704. TRY(BookmarksBarWidget::the().remove_bookmark(url));
  705. } else {
  706. TRY(BookmarksBarWidget::the().add_bookmark(url, m_title));
  707. }
  708. return {};
  709. }
  710. void Tab::update_bookmark_button(StringView url)
  711. {
  712. if (BookmarksBarWidget::the().contains_bookmark(url)) {
  713. m_bookmark_button->set_icon(g_icon_bag.bookmark_filled);
  714. m_bookmark_button->set_tooltip("Remove Bookmark"_string);
  715. } else {
  716. m_bookmark_button->set_icon(g_icon_bag.bookmark_contour);
  717. m_bookmark_button->set_tooltip("Add Bookmark"_string);
  718. }
  719. }
  720. void Tab::did_become_active()
  721. {
  722. BookmarksBarWidget::the().on_bookmark_click = [this](auto& url, auto open) {
  723. if (open == BookmarksBarWidget::Open::InNewTab)
  724. on_tab_open_request(url);
  725. else if (open == BookmarksBarWidget::Open::InNewWindow)
  726. on_window_open_request(url);
  727. else
  728. load(url);
  729. };
  730. BookmarksBarWidget::the().on_bookmark_hover = [this](auto&, auto& url) {
  731. m_statusbar->set_text(String::from_byte_string(url).release_value_but_fixme_should_propagate_errors());
  732. };
  733. BookmarksBarWidget::the().on_bookmark_change = [this]() {
  734. update_bookmark_button(url().to_byte_string());
  735. };
  736. BookmarksBarWidget::the().remove_from_parent();
  737. m_toolbar_container->add_child(BookmarksBarWidget::the());
  738. auto is_fullscreen = window().is_fullscreen();
  739. m_toolbar_container->set_visible(!is_fullscreen);
  740. m_statusbar->set_visible(!is_fullscreen);
  741. update_actions();
  742. }
  743. void Tab::context_menu_requested(Gfx::IntPoint screen_position)
  744. {
  745. m_tab_context_menu->popup(screen_position);
  746. }
  747. void Tab::content_filters_changed()
  748. {
  749. if (g_content_filters_enabled)
  750. m_web_content_view->set_content_filters(g_content_filters);
  751. else
  752. m_web_content_view->set_content_filters({});
  753. }
  754. void Tab::autoplay_allowlist_changed()
  755. {
  756. if (g_autoplay_allowed_on_all_websites)
  757. m_web_content_view->set_autoplay_allowed_on_all_websites();
  758. else
  759. m_web_content_view->set_autoplay_allowlist(g_autoplay_allowlist);
  760. }
  761. void Tab::proxy_mappings_changed()
  762. {
  763. m_web_content_view->set_proxy_mappings(g_proxies, g_proxy_mappings);
  764. }
  765. void Tab::action_entered(GUI::Action& action)
  766. {
  767. m_statusbar->set_override_text(action.status_tip());
  768. }
  769. void Tab::action_left(GUI::Action&)
  770. {
  771. m_statusbar->set_override_text({});
  772. }
  773. void Tab::window_position_changed(Gfx::IntPoint position)
  774. {
  775. m_web_content_view->set_window_position(position);
  776. }
  777. void Tab::window_size_changed(Gfx::IntSize size)
  778. {
  779. m_web_content_view->set_window_size(size);
  780. }
  781. BrowserWindow const& Tab::window() const
  782. {
  783. return static_cast<BrowserWindow const&>(*Widget::window());
  784. }
  785. BrowserWindow& Tab::window()
  786. {
  787. return static_cast<BrowserWindow&>(*Widget::window());
  788. }
  789. void Tab::show_inspector_window(Browser::Tab::InspectorTarget inspector_target)
  790. {
  791. if (!m_dom_inspector_widget) {
  792. auto window = GUI::Window::construct(&this->window());
  793. window->set_window_mode(GUI::WindowMode::Modeless);
  794. window->resize(750, 500);
  795. window->set_title("Inspector");
  796. window->set_icon(g_icon_bag.inspector_object);
  797. window->on_close = [&]() {
  798. m_web_content_view->clear_inspected_dom_node();
  799. };
  800. m_dom_inspector_widget = window->set_main_widget<InspectorWidget>(*m_web_content_view);
  801. } else {
  802. m_dom_inspector_widget->inspect();
  803. }
  804. if (inspector_target == InspectorTarget::HoveredElement) {
  805. m_dom_inspector_widget->select_hovered_node();
  806. } else {
  807. VERIFY(inspector_target == InspectorTarget::Document);
  808. m_dom_inspector_widget->select_default_node();
  809. }
  810. auto* window = m_dom_inspector_widget->window();
  811. window->show();
  812. window->move_to_front();
  813. }
  814. void Tab::close_sub_widgets()
  815. {
  816. auto close_widget_window = [](auto& widget) {
  817. if (widget) {
  818. auto* window = widget->window();
  819. window->close();
  820. }
  821. };
  822. close_widget_window(m_dom_inspector_widget);
  823. close_widget_window(m_storage_widget);
  824. }
  825. void Tab::show_storage_inspector()
  826. {
  827. if (!m_storage_widget) {
  828. auto storage_window = GUI::Window::construct(&window());
  829. storage_window->resize(500, 300);
  830. storage_window->set_title("Storage Inspector");
  831. storage_window->set_icon(g_icon_bag.cookie);
  832. m_storage_widget = storage_window->set_main_widget<StorageWidget>();
  833. m_storage_widget->on_update_cookie = [this](Web::Cookie::Cookie cookie) {
  834. if (view().on_update_cookie)
  835. view().on_update_cookie(move(cookie));
  836. };
  837. }
  838. if (on_get_cookies_entries) {
  839. auto cookies = on_get_cookies_entries();
  840. m_storage_widget->clear_cookies();
  841. m_storage_widget->set_cookies_entries(cookies);
  842. }
  843. if (on_get_local_storage_entries) {
  844. auto local_storage_entries = on_get_local_storage_entries();
  845. m_storage_widget->clear_local_storage_entries();
  846. m_storage_widget->set_local_storage_entries(move(local_storage_entries));
  847. }
  848. if (on_get_session_storage_entries) {
  849. auto session_storage_entries = on_get_session_storage_entries();
  850. m_storage_widget->clear_session_storage_entries();
  851. m_storage_widget->set_session_storage_entries(move(session_storage_entries));
  852. }
  853. auto* window = m_storage_widget->window();
  854. window->show();
  855. window->move_to_front();
  856. }
  857. void Tab::show_history_inspector()
  858. {
  859. if (!m_history_widget) {
  860. auto history_window = GUI::Window::construct(&window());
  861. history_window->resize(500, 300);
  862. history_window->set_title("History");
  863. history_window->set_icon(g_icon_bag.history);
  864. m_history_widget = history_window->set_main_widget<HistoryWidget>();
  865. }
  866. // FIXME: Reimplement viewing history entries using WebContent's history.
  867. m_history_widget->clear_history_entries();
  868. auto* window = m_history_widget->window();
  869. window->show();
  870. window->move_to_front();
  871. }
  872. void Tab::show_event(GUI::ShowEvent&)
  873. {
  874. m_web_content_view->set_visible(true);
  875. }
  876. void Tab::hide_event(GUI::HideEvent&)
  877. {
  878. m_web_content_view->set_visible(false);
  879. }
  880. void Tab::enable_webdriver_mode()
  881. {
  882. m_web_content_view->connect_to_webdriver(Browser::g_webdriver_content_ipc_path);
  883. auto& webdriver_banner = *find_descendant_of_type_named<GUI::Widget>("webdriver_banner");
  884. webdriver_banner.set_visible(true);
  885. }
  886. }