Tab.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702
  1. /*
  2. * Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2022, Matthew Costa <ucosty@gmail.com>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include "BrowserWindow.h"
  8. #include "ConsoleWidget.h"
  9. #include "InspectorWidget.h"
  10. #include "Settings.h"
  11. #include "StringUtils.h"
  12. #include "TVGIconEngine.h"
  13. #include <Browser/History.h>
  14. #include <LibGfx/ImageFormats/BMPWriter.h>
  15. #include <LibGfx/Painter.h>
  16. #include <QClipboard>
  17. #include <QCoreApplication>
  18. #include <QCursor>
  19. #include <QFileDialog>
  20. #include <QFont>
  21. #include <QFontMetrics>
  22. #include <QGuiApplication>
  23. #include <QImage>
  24. #include <QMenu>
  25. #include <QMessageBox>
  26. #include <QPainter>
  27. #include <QPlainTextEdit>
  28. #include <QPoint>
  29. #include <QResizeEvent>
  30. extern DeprecatedString s_serenity_resource_root;
  31. namespace Ladybird {
  32. extern Settings* s_settings;
  33. static QIcon create_tvg_icon_with_theme_colors(QString name, QPalette const& palette)
  34. {
  35. auto path = QString(":/Icons/%1.tvg").arg(name);
  36. auto icon_engine = TVGIconEngine::from_file(path);
  37. VERIFY(icon_engine);
  38. auto icon_filter = [](QColor color) {
  39. return [color = Color::from_argb(color.rgba64().toArgb32())](Gfx::Color icon_color) {
  40. return color.with_alpha((icon_color.alpha() * color.alpha()) / 255);
  41. };
  42. };
  43. icon_engine->add_filter(QIcon::Mode::Normal, icon_filter(palette.color(QPalette::ColorGroup::Normal, QPalette::ColorRole::ButtonText)));
  44. icon_engine->add_filter(QIcon::Mode::Disabled, icon_filter(palette.color(QPalette::ColorGroup::Disabled, QPalette::ColorRole::ButtonText)));
  45. return QIcon(icon_engine);
  46. }
  47. Tab::Tab(BrowserWindow* window, StringView webdriver_content_ipc_path, WebView::EnableCallgrindProfiling enable_callgrind_profiling, UseLagomNetworking use_lagom_networking)
  48. : QWidget(window)
  49. , m_window(window)
  50. {
  51. m_layout = new QBoxLayout(QBoxLayout::Direction::TopToBottom, this);
  52. m_layout->setSpacing(0);
  53. m_layout->setContentsMargins(0, 0, 0, 0);
  54. m_view = new WebContentView(webdriver_content_ipc_path, enable_callgrind_profiling, use_lagom_networking);
  55. m_toolbar = new QToolBar(this);
  56. m_location_edit = new LocationEdit(this);
  57. m_hover_label = new QLabel(this);
  58. m_hover_label->hide();
  59. m_hover_label->setFrameShape(QFrame::Shape::Box);
  60. m_hover_label->setAutoFillBackground(true);
  61. auto* focus_location_editor_action = new QAction("Edit Location", this);
  62. focus_location_editor_action->setShortcut(QKeySequence("Ctrl+L"));
  63. addAction(focus_location_editor_action);
  64. m_layout->addWidget(m_toolbar);
  65. m_layout->addWidget(m_view);
  66. recreate_toolbar_icons();
  67. m_toolbar->addAction(&m_window->go_back_action());
  68. m_toolbar->addAction(&m_window->go_forward_action());
  69. m_toolbar->addAction(&m_window->reload_action());
  70. m_toolbar->addWidget(m_location_edit);
  71. m_toolbar->setIconSize({ 16, 16 });
  72. // This is a little awkward, but without this Qt shrinks the button to the size of the icon.
  73. // Note: toolButtonStyle="0" -> ToolButtonIconOnly.
  74. m_toolbar->setStyleSheet("QToolButton[toolButtonStyle=\"0\"]{width:24px;height:24px}");
  75. m_reset_zoom_button = new QToolButton(m_toolbar);
  76. m_reset_zoom_button->setToolButtonStyle(Qt::ToolButtonTextOnly);
  77. m_reset_zoom_button->setToolTip("Reset zoom level");
  78. m_reset_zoom_button_action = m_toolbar->addWidget(m_reset_zoom_button);
  79. m_reset_zoom_button_action->setVisible(false);
  80. QObject::connect(m_reset_zoom_button, &QAbstractButton::clicked, [this] {
  81. view().reset_zoom();
  82. update_reset_zoom_button();
  83. m_window->update_zoom_menu();
  84. });
  85. view().on_activate_tab = [this] {
  86. m_window->activate_tab(tab_index());
  87. };
  88. view().on_close = [this] {
  89. m_window->close_tab(tab_index());
  90. };
  91. view().on_link_hover = [this](auto const& url) {
  92. m_hover_label->setText(qstring_from_ak_deprecated_string(url.to_deprecated_string()));
  93. update_hover_label();
  94. m_hover_label->show();
  95. };
  96. view().on_link_unhover = [this]() {
  97. m_hover_label->hide();
  98. };
  99. view().on_back_button = [this] {
  100. back();
  101. };
  102. view().on_forward_button = [this] {
  103. forward();
  104. };
  105. view().on_load_start = [this](const URL& url, bool is_redirect) {
  106. // If we are loading due to a redirect, we replace the current history entry
  107. // with the loaded URL
  108. if (is_redirect) {
  109. m_history.replace_current(url, m_title.toUtf8().data());
  110. }
  111. m_location_edit->setText(url.to_deprecated_string().characters());
  112. m_location_edit->setCursorPosition(0);
  113. // Don't add to history if back or forward is pressed
  114. if (!m_is_history_navigation) {
  115. m_history.push(url, m_title.toUtf8().data());
  116. }
  117. m_is_history_navigation = false;
  118. m_window->go_back_action().setEnabled(m_history.can_go_back());
  119. m_window->go_forward_action().setEnabled(m_history.can_go_forward());
  120. if (m_inspector_widget)
  121. m_inspector_widget->clear_dom_json();
  122. if (m_console_widget)
  123. m_console_widget->reset();
  124. };
  125. view().on_load_finish = [this](auto&) {
  126. if (m_inspector_widget != nullptr && m_inspector_widget->isVisible()) {
  127. view().inspect_dom_tree();
  128. view().inspect_accessibility_tree();
  129. }
  130. };
  131. QObject::connect(m_location_edit, &QLineEdit::returnPressed, this, &Tab::location_edit_return_pressed);
  132. view().on_title_change = [this](auto const& title) {
  133. m_title = qstring_from_ak_deprecated_string(title);
  134. m_history.update_title(title);
  135. emit title_changed(tab_index(), m_title);
  136. };
  137. view().on_favicon_change = [this](auto const& bitmap) {
  138. auto qimage = QImage(bitmap.scanline_u8(0), bitmap.width(), bitmap.height(), QImage::Format_ARGB32);
  139. if (qimage.isNull())
  140. return;
  141. auto qpixmap = QPixmap::fromImage(qimage);
  142. if (qpixmap.isNull())
  143. return;
  144. emit favicon_changed(tab_index(), QIcon(qpixmap));
  145. };
  146. QObject::connect(focus_location_editor_action, &QAction::triggered, this, &Tab::focus_location_editor);
  147. view().on_get_source = [this](auto const& url, auto const& source) {
  148. auto* text_edit = new QPlainTextEdit(this);
  149. text_edit->setWindowFlags(Qt::Window);
  150. text_edit->setFont(QFontDatabase::systemFont(QFontDatabase::SystemFont::FixedFont));
  151. text_edit->resize(800, 600);
  152. text_edit->setWindowTitle(qstring_from_ak_deprecated_string(url.to_deprecated_string()));
  153. text_edit->setPlainText(qstring_from_ak_deprecated_string(source));
  154. text_edit->show();
  155. };
  156. view().on_navigate_back = [this]() {
  157. back();
  158. };
  159. view().on_navigate_forward = [this]() {
  160. forward();
  161. };
  162. view().on_refresh = [this]() {
  163. reload();
  164. };
  165. view().on_restore_window = [this]() {
  166. m_window->showNormal();
  167. };
  168. view().on_reposition_window = [this](auto const& position) {
  169. m_window->move(position.x(), position.y());
  170. return Gfx::IntPoint { m_window->x(), m_window->y() };
  171. };
  172. view().on_resize_window = [this](auto const& size) {
  173. m_window->resize(size.width(), size.height());
  174. return Gfx::IntSize { m_window->width(), m_window->height() };
  175. };
  176. view().on_maximize_window = [this]() {
  177. m_window->showMaximized();
  178. return Gfx::IntRect { m_window->x(), m_window->y(), m_window->width(), m_window->height() };
  179. };
  180. view().on_minimize_window = [this]() {
  181. m_window->showMinimized();
  182. return Gfx::IntRect { m_window->x(), m_window->y(), m_window->width(), m_window->height() };
  183. };
  184. view().on_fullscreen_window = [this]() {
  185. m_window->showFullScreen();
  186. return Gfx::IntRect { m_window->x(), m_window->y(), m_window->width(), m_window->height() };
  187. };
  188. view().on_get_dom_tree = [this](auto& dom_tree) {
  189. if (m_inspector_widget)
  190. m_inspector_widget->set_dom_json(dom_tree);
  191. };
  192. view().on_get_accessibility_tree = [this](auto& accessibility_tree) {
  193. if (m_inspector_widget)
  194. m_inspector_widget->set_accessibility_json(accessibility_tree);
  195. };
  196. view().on_js_console_new_message = [this](auto message_index) {
  197. if (m_console_widget)
  198. m_console_widget->notify_about_new_console_message(message_index);
  199. };
  200. view().on_get_js_console_messages = [this](auto start_index, auto& message_types, auto& messages) {
  201. if (m_console_widget)
  202. m_console_widget->handle_console_messages(start_index, message_types, messages);
  203. };
  204. auto* take_visible_screenshot_action = new QAction("Take &Visible Screenshot", this);
  205. take_visible_screenshot_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-image.png").arg(s_serenity_resource_root.characters())));
  206. QObject::connect(take_visible_screenshot_action, &QAction::triggered, this, [this]() {
  207. if (auto result = view().take_screenshot(WebView::ViewImplementation::ScreenshotType::Visible); result.is_error()) {
  208. auto error = String::formatted("{}", result.error()).release_value_but_fixme_should_propagate_errors();
  209. QMessageBox::warning(this, "Ladybird", qstring_from_ak_string(error));
  210. }
  211. });
  212. auto* take_full_screenshot_action = new QAction("Take &Full Screenshot", this);
  213. take_full_screenshot_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-image.png").arg(s_serenity_resource_root.characters())));
  214. QObject::connect(take_full_screenshot_action, &QAction::triggered, this, [this]() {
  215. if (auto result = view().take_screenshot(WebView::ViewImplementation::ScreenshotType::Full); result.is_error()) {
  216. auto error = String::formatted("{}", result.error()).release_value_but_fixme_should_propagate_errors();
  217. QMessageBox::warning(this, "Ladybird", qstring_from_ak_string(error));
  218. }
  219. });
  220. m_page_context_menu = make<QMenu>("Context menu", this);
  221. m_page_context_menu->addAction(&m_window->go_back_action());
  222. m_page_context_menu->addAction(&m_window->go_forward_action());
  223. m_page_context_menu->addAction(&m_window->reload_action());
  224. m_page_context_menu->addSeparator();
  225. m_page_context_menu->addAction(&m_window->copy_selection_action());
  226. m_page_context_menu->addAction(&m_window->select_all_action());
  227. m_page_context_menu->addSeparator();
  228. m_page_context_menu->addAction(take_visible_screenshot_action);
  229. m_page_context_menu->addAction(take_full_screenshot_action);
  230. m_page_context_menu->addSeparator();
  231. m_page_context_menu->addAction(&m_window->view_source_action());
  232. m_page_context_menu->addAction(&m_window->inspect_dom_node_action());
  233. view().on_context_menu_request = [this](Gfx::IntPoint) {
  234. auto screen_position = QCursor::pos();
  235. m_page_context_menu->exec(screen_position);
  236. };
  237. auto* open_link_action = new QAction("&Open", this);
  238. open_link_action->setIcon(QIcon(QString("%1/res/icons/16x16/go-forward.png").arg(s_serenity_resource_root.characters())));
  239. QObject::connect(open_link_action, &QAction::triggered, this, [this]() {
  240. open_link(m_link_context_menu_url);
  241. });
  242. auto* open_link_in_new_tab_action = new QAction("&Open in New &Tab", this);
  243. open_link_in_new_tab_action->setIcon(QIcon(QString("%1/res/icons/16x16/new-tab.png").arg(s_serenity_resource_root.characters())));
  244. QObject::connect(open_link_in_new_tab_action, &QAction::triggered, this, [this]() {
  245. open_link_in_new_tab(m_link_context_menu_url);
  246. });
  247. auto* copy_url_action = new QAction("Copy &URL", this);
  248. copy_url_action->setIcon(QIcon(QString("%1/res/icons/16x16/edit-copy.png").arg(s_serenity_resource_root.characters())));
  249. QObject::connect(copy_url_action, &QAction::triggered, this, [this]() {
  250. copy_link_url(m_link_context_menu_url);
  251. });
  252. m_link_context_menu = make<QMenu>("Link context menu", this);
  253. m_link_context_menu->addAction(open_link_action);
  254. m_link_context_menu->addAction(open_link_in_new_tab_action);
  255. m_link_context_menu->addSeparator();
  256. m_link_context_menu->addAction(copy_url_action);
  257. m_link_context_menu->addSeparator();
  258. m_link_context_menu->addAction(&m_window->inspect_dom_node_action());
  259. view().on_link_context_menu_request = [this](auto const& url, Gfx::IntPoint) {
  260. m_link_context_menu_url = url;
  261. auto screen_position = QCursor::pos();
  262. m_link_context_menu->exec(screen_position);
  263. };
  264. auto* open_image_action = new QAction("&Open Image", this);
  265. open_image_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-image.png").arg(s_serenity_resource_root.characters())));
  266. QObject::connect(open_image_action, &QAction::triggered, this, [this]() {
  267. open_link(m_image_context_menu_url);
  268. });
  269. auto* open_image_in_new_tab_action = new QAction("&Open Image in New &Tab", this);
  270. open_image_in_new_tab_action->setIcon(QIcon(QString("%1/res/icons/16x16/new-tab.png").arg(s_serenity_resource_root.characters())));
  271. QObject::connect(open_image_in_new_tab_action, &QAction::triggered, this, [this]() {
  272. open_link_in_new_tab(m_image_context_menu_url);
  273. });
  274. auto* copy_image_action = new QAction("&Copy Image", this);
  275. copy_image_action->setIcon(QIcon(QString("%1/res/icons/16x16/edit-copy.png").arg(s_serenity_resource_root.characters())));
  276. QObject::connect(copy_image_action, &QAction::triggered, this, [this]() {
  277. auto* bitmap = m_image_context_menu_bitmap.bitmap();
  278. if (bitmap == nullptr)
  279. return;
  280. auto data = Gfx::BMPWriter::encode(*bitmap);
  281. if (data.is_error())
  282. return;
  283. auto image = QImage::fromData(data.value().data(), data.value().size(), "BMP");
  284. if (image.isNull())
  285. return;
  286. auto* clipboard = QGuiApplication::clipboard();
  287. clipboard->setImage(image);
  288. });
  289. auto* copy_image_url_action = new QAction("Copy Image &URL", this);
  290. copy_image_url_action->setIcon(QIcon(QString("%1/res/icons/16x16/edit-copy.png").arg(s_serenity_resource_root.characters())));
  291. QObject::connect(copy_image_url_action, &QAction::triggered, this, [this]() {
  292. copy_link_url(m_image_context_menu_url);
  293. });
  294. m_image_context_menu = make<QMenu>("Image context menu", this);
  295. m_image_context_menu->addAction(open_image_action);
  296. m_image_context_menu->addAction(open_image_in_new_tab_action);
  297. m_image_context_menu->addSeparator();
  298. m_image_context_menu->addAction(copy_image_action);
  299. m_image_context_menu->addAction(copy_image_url_action);
  300. m_image_context_menu->addSeparator();
  301. m_image_context_menu->addAction(&m_window->inspect_dom_node_action());
  302. view().on_image_context_menu_request = [this](auto& image_url, Gfx::IntPoint, Gfx::ShareableBitmap const& shareable_bitmap) {
  303. m_image_context_menu_url = image_url;
  304. m_image_context_menu_bitmap = shareable_bitmap;
  305. auto screen_position = QCursor::pos();
  306. m_image_context_menu->exec(screen_position);
  307. };
  308. m_media_context_menu_play_icon = make<QIcon>(QString("%1/res/icons/16x16/play.png").arg(s_serenity_resource_root.characters()));
  309. m_media_context_menu_pause_icon = make<QIcon>(QString("%1/res/icons/16x16/pause.png").arg(s_serenity_resource_root.characters()));
  310. m_media_context_menu_mute_icon = make<QIcon>(QString("%1/res/icons/16x16/audio-volume-muted.png").arg(s_serenity_resource_root.characters()));
  311. m_media_context_menu_unmute_icon = make<QIcon>(QString("%1/res/icons/16x16/audio-volume-high.png").arg(s_serenity_resource_root.characters()));
  312. m_media_context_menu_play_pause_action = make<QAction>("&Play", this);
  313. m_media_context_menu_play_pause_action->setIcon(*m_media_context_menu_play_icon);
  314. QObject::connect(m_media_context_menu_play_pause_action, &QAction::triggered, this, [this]() {
  315. view().toggle_media_play_state();
  316. });
  317. m_media_context_menu_mute_unmute_action = make<QAction>("&Mute", this);
  318. m_media_context_menu_mute_unmute_action->setIcon(*m_media_context_menu_mute_icon);
  319. QObject::connect(m_media_context_menu_mute_unmute_action, &QAction::triggered, this, [this]() {
  320. view().toggle_media_mute_state();
  321. });
  322. m_media_context_menu_controls_action = make<QAction>("Show &Controls", this);
  323. m_media_context_menu_controls_action->setCheckable(true);
  324. QObject::connect(m_media_context_menu_controls_action, &QAction::triggered, this, [this]() {
  325. view().toggle_media_controls_state();
  326. });
  327. m_media_context_menu_loop_action = make<QAction>("&Loop", this);
  328. m_media_context_menu_loop_action->setCheckable(true);
  329. QObject::connect(m_media_context_menu_loop_action, &QAction::triggered, this, [this]() {
  330. view().toggle_media_loop_state();
  331. });
  332. auto* open_audio_action = new QAction("&Open Audio", this);
  333. open_audio_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-sound.png").arg(s_serenity_resource_root.characters())));
  334. QObject::connect(open_audio_action, &QAction::triggered, this, [this]() {
  335. open_link(m_media_context_menu_url);
  336. });
  337. auto* open_audio_in_new_tab_action = new QAction("Open Audio in New &Tab", this);
  338. open_audio_in_new_tab_action->setIcon(QIcon(QString("%1/res/icons/16x16/new-tab.png").arg(s_serenity_resource_root.characters())));
  339. QObject::connect(open_audio_in_new_tab_action, &QAction::triggered, this, [this]() {
  340. open_link_in_new_tab(m_media_context_menu_url);
  341. });
  342. auto* copy_audio_url_action = new QAction("Copy Audio &URL", this);
  343. copy_audio_url_action->setIcon(QIcon(QString("%1/res/icons/16x16/edit-copy.png").arg(s_serenity_resource_root.characters())));
  344. QObject::connect(copy_audio_url_action, &QAction::triggered, this, [this]() {
  345. copy_link_url(m_media_context_menu_url);
  346. });
  347. m_audio_context_menu = make<QMenu>("Audio context menu", this);
  348. m_audio_context_menu->addAction(m_media_context_menu_play_pause_action);
  349. m_audio_context_menu->addAction(m_media_context_menu_mute_unmute_action);
  350. m_audio_context_menu->addAction(m_media_context_menu_controls_action);
  351. m_audio_context_menu->addAction(m_media_context_menu_loop_action);
  352. m_audio_context_menu->addSeparator();
  353. m_audio_context_menu->addAction(open_audio_action);
  354. m_audio_context_menu->addAction(open_audio_in_new_tab_action);
  355. m_audio_context_menu->addSeparator();
  356. m_audio_context_menu->addAction(copy_audio_url_action);
  357. m_audio_context_menu->addSeparator();
  358. m_audio_context_menu->addAction(&m_window->inspect_dom_node_action());
  359. auto* open_video_action = new QAction("&Open Video", this);
  360. open_video_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-video.png").arg(s_serenity_resource_root.characters())));
  361. QObject::connect(open_video_action, &QAction::triggered, this, [this]() {
  362. open_link(m_media_context_menu_url);
  363. });
  364. auto* open_video_in_new_tab_action = new QAction("Open Video in New &Tab", this);
  365. open_video_in_new_tab_action->setIcon(QIcon(QString("%1/res/icons/16x16/new-tab.png").arg(s_serenity_resource_root.characters())));
  366. QObject::connect(open_video_in_new_tab_action, &QAction::triggered, this, [this]() {
  367. open_link_in_new_tab(m_media_context_menu_url);
  368. });
  369. auto* copy_video_url_action = new QAction("Copy Video &URL", this);
  370. copy_video_url_action->setIcon(QIcon(QString("%1/res/icons/16x16/edit-copy.png").arg(s_serenity_resource_root.characters())));
  371. QObject::connect(copy_video_url_action, &QAction::triggered, this, [this]() {
  372. copy_link_url(m_media_context_menu_url);
  373. });
  374. m_video_context_menu = make<QMenu>("Video context menu", this);
  375. m_video_context_menu->addAction(m_media_context_menu_play_pause_action);
  376. m_video_context_menu->addAction(m_media_context_menu_mute_unmute_action);
  377. m_video_context_menu->addAction(m_media_context_menu_controls_action);
  378. m_video_context_menu->addAction(m_media_context_menu_loop_action);
  379. m_video_context_menu->addSeparator();
  380. m_video_context_menu->addAction(open_video_action);
  381. m_video_context_menu->addAction(open_video_in_new_tab_action);
  382. m_video_context_menu->addSeparator();
  383. m_video_context_menu->addAction(copy_video_url_action);
  384. m_video_context_menu->addSeparator();
  385. m_video_context_menu->addAction(&m_window->inspect_dom_node_action());
  386. view().on_media_context_menu_request = [this](Gfx::IntPoint, Web::Page::MediaContextMenu const& menu) {
  387. m_media_context_menu_url = menu.media_url;
  388. if (menu.is_playing) {
  389. m_media_context_menu_play_pause_action->setIcon(*m_media_context_menu_pause_icon);
  390. m_media_context_menu_play_pause_action->setText("&Pause");
  391. } else {
  392. m_media_context_menu_play_pause_action->setIcon(*m_media_context_menu_play_icon);
  393. m_media_context_menu_play_pause_action->setText("&Play");
  394. }
  395. if (menu.is_muted) {
  396. m_media_context_menu_mute_unmute_action->setIcon(*m_media_context_menu_unmute_icon);
  397. m_media_context_menu_mute_unmute_action->setText("Un&mute");
  398. } else {
  399. m_media_context_menu_mute_unmute_action->setIcon(*m_media_context_menu_mute_icon);
  400. m_media_context_menu_mute_unmute_action->setText("&Mute");
  401. }
  402. m_media_context_menu_controls_action->setChecked(menu.has_user_agent_controls);
  403. m_media_context_menu_loop_action->setChecked(menu.is_looping);
  404. auto screen_position = QCursor::pos();
  405. if (menu.is_video)
  406. m_video_context_menu->exec(screen_position);
  407. else
  408. m_audio_context_menu->exec(screen_position);
  409. };
  410. }
  411. Tab::~Tab()
  412. {
  413. close_sub_widgets();
  414. }
  415. void Tab::update_reset_zoom_button()
  416. {
  417. auto zoom_level = view().zoom_level();
  418. if (zoom_level != 1.0f) {
  419. auto zoom_level_text = MUST(String::formatted("{}%", round_to<int>(zoom_level * 100)));
  420. m_reset_zoom_button->setText(qstring_from_ak_string(zoom_level_text));
  421. m_reset_zoom_button_action->setVisible(true);
  422. } else {
  423. m_reset_zoom_button_action->setVisible(false);
  424. }
  425. }
  426. void Tab::focus_location_editor()
  427. {
  428. m_location_edit->setFocus();
  429. m_location_edit->selectAll();
  430. }
  431. void Tab::navigate(QString url_qstring)
  432. {
  433. auto url_string = ak_deprecated_string_from_qstring(url_qstring);
  434. if (url_string.starts_with('/'))
  435. url_string = DeprecatedString::formatted("file://{}", url_string);
  436. else if (URL url = url_string; !url.is_valid())
  437. url_string = DeprecatedString::formatted("https://{}", url_string);
  438. view().load(url_string);
  439. }
  440. void Tab::back()
  441. {
  442. if (!m_history.can_go_back())
  443. return;
  444. m_is_history_navigation = true;
  445. m_history.go_back();
  446. view().load(m_history.current().url.to_deprecated_string());
  447. }
  448. void Tab::forward()
  449. {
  450. if (!m_history.can_go_forward())
  451. return;
  452. m_is_history_navigation = true;
  453. m_history.go_forward();
  454. view().load(m_history.current().url.to_deprecated_string());
  455. }
  456. void Tab::reload()
  457. {
  458. m_is_history_navigation = true;
  459. view().load(m_history.current().url.to_deprecated_string());
  460. }
  461. void Tab::open_link(URL const& url)
  462. {
  463. view().on_link_click(url, "", 0);
  464. }
  465. void Tab::open_link_in_new_tab(URL const& url)
  466. {
  467. view().on_link_click(url, "_blank", 0);
  468. }
  469. void Tab::copy_link_url(URL const& url)
  470. {
  471. auto* clipboard = QGuiApplication::clipboard();
  472. clipboard->setText(qstring_from_ak_deprecated_string(url.to_deprecated_string()));
  473. }
  474. void Tab::location_edit_return_pressed()
  475. {
  476. navigate(m_location_edit->text());
  477. }
  478. void Tab::open_file()
  479. {
  480. auto filename = QFileDialog::getOpenFileName(this, "Open file", QDir::homePath(), "All Files (*.*)");
  481. if (!filename.isNull())
  482. navigate("file://" + filename);
  483. }
  484. int Tab::tab_index()
  485. {
  486. return m_window->tab_index(this);
  487. }
  488. void Tab::debug_request(DeprecatedString const& request, DeprecatedString const& argument)
  489. {
  490. if (request == "dump-history")
  491. m_history.dump();
  492. else
  493. m_view->debug_request(request, argument);
  494. }
  495. void Tab::resizeEvent(QResizeEvent* event)
  496. {
  497. QWidget::resizeEvent(event);
  498. if (m_hover_label->isVisible())
  499. update_hover_label();
  500. }
  501. void Tab::update_hover_label()
  502. {
  503. m_hover_label->resize(QFontMetrics(m_hover_label->font()).boundingRect(m_hover_label->text()).adjusted(-4, -2, 4, 2).size());
  504. m_hover_label->move(6, height() - m_hover_label->height() - 8);
  505. m_hover_label->raise();
  506. }
  507. bool Tab::event(QEvent* event)
  508. {
  509. if (event->type() == QEvent::PaletteChange) {
  510. recreate_toolbar_icons();
  511. return QWidget::event(event);
  512. }
  513. return QWidget::event(event);
  514. }
  515. void Tab::recreate_toolbar_icons()
  516. {
  517. m_window->go_back_action().setIcon(create_tvg_icon_with_theme_colors("back", palette()));
  518. m_window->go_forward_action().setIcon(create_tvg_icon_with_theme_colors("forward", palette()));
  519. m_window->reload_action().setIcon(create_tvg_icon_with_theme_colors("reload", palette()));
  520. }
  521. void Tab::show_inspector_window(InspectorTarget inspector_target)
  522. {
  523. bool inspector_previously_loaded = m_inspector_widget != nullptr;
  524. if (!m_inspector_widget) {
  525. m_inspector_widget = new Ladybird::InspectorWidget;
  526. m_inspector_widget->setWindowTitle("Inspector");
  527. m_inspector_widget->resize(640, 480);
  528. m_inspector_widget->on_close = [this] {
  529. view().clear_inspected_dom_node();
  530. };
  531. m_inspector_widget->on_dom_node_inspected = [&](auto id, auto pseudo_element) {
  532. return view().inspect_dom_node(id, pseudo_element);
  533. };
  534. }
  535. if (!inspector_previously_loaded || !m_inspector_widget->dom_loaded()) {
  536. view().inspect_dom_tree();
  537. view().inspect_accessibility_tree();
  538. }
  539. m_inspector_widget->show();
  540. if (inspector_target == InspectorTarget::HoveredElement) {
  541. auto hovered_node = view().get_hovered_node_id();
  542. m_inspector_widget->set_selection({ hovered_node });
  543. } else {
  544. m_inspector_widget->select_default_node();
  545. }
  546. }
  547. void Tab::show_console_window()
  548. {
  549. if (!m_console_widget) {
  550. m_console_widget = new Ladybird::ConsoleWidget;
  551. m_console_widget->setWindowTitle("JS Console");
  552. m_console_widget->resize(640, 480);
  553. // Make these actions available on the window itself. Adding them to the context menu alone
  554. // does not enable activattion via keyboard shortcuts.
  555. m_console_widget->addAction(&m_window->copy_selection_action());
  556. m_console_widget->addAction(&m_window->select_all_action());
  557. m_console_context_menu = make<QMenu>("Context menu", m_console_widget);
  558. m_console_context_menu->addAction(&m_window->copy_selection_action());
  559. m_console_context_menu->addAction(&m_window->select_all_action());
  560. m_console_widget->view().on_context_menu_request = [this](Gfx::IntPoint) {
  561. auto screen_position = QCursor::pos();
  562. m_console_context_menu->exec(screen_position);
  563. };
  564. m_console_widget->on_js_input = [this](auto js_source) {
  565. view().js_console_input(js_source);
  566. };
  567. m_console_widget->on_request_messages = [this](i32 start_index) {
  568. view().js_console_request_messages(start_index);
  569. };
  570. }
  571. m_console_widget->show();
  572. }
  573. void Tab::close_sub_widgets()
  574. {
  575. auto close_widget_window = [](auto* widget) {
  576. if (widget)
  577. widget->close();
  578. };
  579. close_widget_window(m_console_widget);
  580. close_widget_window(m_inspector_widget);
  581. }
  582. }