2022-07-05 22:18:21 +00:00
|
|
|
/*
|
2022-07-19 10:23:11 +00:00
|
|
|
* Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
|
2022-07-05 22:18:21 +00:00
|
|
|
* Copyright (c) 2022, Matthew Costa <ucosty@gmail.com>
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "Tab.h"
|
|
|
|
#include "BrowserWindow.h"
|
2023-05-17 15:54:36 +00:00
|
|
|
#include "ConsoleWidget.h"
|
|
|
|
#include "InspectorWidget.h"
|
2022-07-14 03:41:13 +00:00
|
|
|
#include "Settings.h"
|
2022-10-03 22:59:08 +00:00
|
|
|
#include "Utilities.h"
|
2022-10-22 21:42:00 +00:00
|
|
|
#include <Browser/History.h>
|
2023-05-15 17:43:58 +00:00
|
|
|
#include <LibGfx/ImageFormats/BMPWriter.h>
|
2023-05-15 16:27:10 +00:00
|
|
|
#include <QClipboard>
|
2022-07-05 22:18:21 +00:00
|
|
|
#include <QCoreApplication>
|
2023-05-21 04:53:38 +00:00
|
|
|
#include <QCursor>
|
2023-05-25 21:47:12 +00:00
|
|
|
#include <QFileDialog>
|
2022-09-09 12:23:36 +00:00
|
|
|
#include <QFont>
|
|
|
|
#include <QFontMetrics>
|
2023-05-15 16:27:10 +00:00
|
|
|
#include <QGuiApplication>
|
2023-05-15 17:43:58 +00:00
|
|
|
#include <QImage>
|
2023-05-15 15:49:44 +00:00
|
|
|
#include <QMenu>
|
2023-05-16 11:56:12 +00:00
|
|
|
#include <QMessageBox>
|
2023-05-05 13:38:46 +00:00
|
|
|
#include <QPainter>
|
2022-10-05 13:23:41 +00:00
|
|
|
#include <QPlainTextEdit>
|
2022-07-13 02:39:38 +00:00
|
|
|
#include <QPoint>
|
2022-09-09 12:23:36 +00:00
|
|
|
#include <QResizeEvent>
|
2023-05-05 13:38:46 +00:00
|
|
|
#include <QSvgRenderer>
|
2022-07-05 22:18:21 +00:00
|
|
|
|
2022-12-04 18:43:54 +00:00
|
|
|
extern DeprecatedString s_serenity_resource_root;
|
2022-07-14 03:41:13 +00:00
|
|
|
extern Browser::Settings* s_settings;
|
2022-07-05 22:18:21 +00:00
|
|
|
|
2023-05-05 13:38:46 +00:00
|
|
|
static QIcon render_svg_icon_with_theme_colors(QString name, QPalette const& palette)
|
|
|
|
{
|
|
|
|
auto path = QString(":/Icons/%1.svg").arg(name);
|
|
|
|
|
|
|
|
QSize icon_size(16, 16);
|
|
|
|
|
|
|
|
QIcon icon;
|
|
|
|
|
|
|
|
auto render = [&](QColor color) -> QPixmap {
|
|
|
|
QImage image(icon_size, QImage::Format_ARGB32);
|
|
|
|
image.fill(Qt::transparent);
|
|
|
|
|
|
|
|
QPainter painter(&image);
|
|
|
|
QSvgRenderer renderer(path);
|
|
|
|
renderer.render(&painter);
|
|
|
|
painter.setBrush(color);
|
|
|
|
painter.setCompositionMode(QPainter::CompositionMode_SourceAtop);
|
|
|
|
painter.fillRect(image.rect(), color);
|
|
|
|
return QPixmap::fromImage(image);
|
|
|
|
};
|
|
|
|
|
|
|
|
icon.addPixmap(render(palette.color(QPalette::ColorGroup::Normal, QPalette::ColorRole::ButtonText)), QIcon::Mode::Normal);
|
|
|
|
icon.addPixmap(render(palette.color(QPalette::ColorGroup::Disabled, QPalette::ColorRole::ButtonText)), QIcon::Mode::Disabled);
|
|
|
|
|
|
|
|
return icon;
|
|
|
|
}
|
|
|
|
|
2023-04-15 00:04:28 +00:00
|
|
|
Tab::Tab(BrowserWindow* window, StringView webdriver_content_ipc_path, WebView::EnableCallgrindProfiling enable_callgrind_profiling)
|
2022-10-01 18:46:24 +00:00
|
|
|
: QWidget(window)
|
|
|
|
, m_window(window)
|
2022-07-05 22:18:21 +00:00
|
|
|
{
|
|
|
|
m_layout = new QBoxLayout(QBoxLayout::Direction::TopToBottom, this);
|
2022-09-09 12:27:59 +00:00
|
|
|
m_layout->setSpacing(0);
|
2022-07-05 22:18:21 +00:00
|
|
|
m_layout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
|
2023-04-15 00:04:28 +00:00
|
|
|
m_view = new WebContentView(webdriver_content_ipc_path, enable_callgrind_profiling);
|
2022-10-01 18:46:24 +00:00
|
|
|
m_toolbar = new QToolBar(this);
|
2023-01-18 14:12:13 +00:00
|
|
|
m_location_edit = new LocationEdit(this);
|
2023-03-28 23:25:42 +00:00
|
|
|
m_reset_zoom_button = new QToolButton(m_toolbar);
|
2022-07-05 22:18:21 +00:00
|
|
|
|
2022-09-09 12:23:36 +00:00
|
|
|
m_hover_label = new QLabel(this);
|
2022-09-09 13:19:18 +00:00
|
|
|
m_hover_label->hide();
|
2022-09-09 12:23:36 +00:00
|
|
|
m_hover_label->setFrameShape(QFrame::Shape::Box);
|
|
|
|
m_hover_label->setAutoFillBackground(true);
|
|
|
|
|
2022-09-12 07:12:04 +00:00
|
|
|
auto* focus_location_editor_action = new QAction("Edit Location");
|
|
|
|
focus_location_editor_action->setShortcut(QKeySequence("Ctrl+L"));
|
|
|
|
addAction(focus_location_editor_action);
|
2022-07-07 00:56:09 +00:00
|
|
|
|
2022-07-05 22:18:21 +00:00
|
|
|
m_layout->addWidget(m_toolbar);
|
|
|
|
m_layout->addWidget(m_view);
|
|
|
|
|
2023-05-05 13:38:46 +00:00
|
|
|
rerender_toolbar_icons();
|
|
|
|
|
2023-05-13 12:07:12 +00:00
|
|
|
m_toolbar->addAction(&m_window->go_back_action());
|
|
|
|
m_toolbar->addAction(&m_window->go_forward_action());
|
|
|
|
m_toolbar->addAction(&m_window->reload_action());
|
2022-07-05 22:18:21 +00:00
|
|
|
m_toolbar->addWidget(m_location_edit);
|
2023-03-31 20:33:42 +00:00
|
|
|
m_reset_zoom_button->setToolTip("Reset zoom level");
|
2023-03-28 23:25:42 +00:00
|
|
|
m_reset_zoom_button_action = m_toolbar->addWidget(m_reset_zoom_button);
|
|
|
|
m_reset_zoom_button_action->setVisible(false);
|
|
|
|
|
|
|
|
QObject::connect(m_reset_zoom_button, &QAbstractButton::clicked, [this] {
|
|
|
|
view().reset_zoom();
|
|
|
|
update_reset_zoom_button();
|
|
|
|
});
|
|
|
|
|
2023-05-17 16:37:31 +00:00
|
|
|
view().on_activate_tab = [this] {
|
2023-03-20 23:52:00 +00:00
|
|
|
m_window->activate_tab(tab_index());
|
2023-05-17 16:37:31 +00:00
|
|
|
};
|
2023-03-20 23:52:00 +00:00
|
|
|
|
2023-05-17 16:37:31 +00:00
|
|
|
view().on_close = [this] {
|
2023-03-07 03:13:08 +00:00
|
|
|
m_window->close_tab(tab_index());
|
2023-05-17 16:37:31 +00:00
|
|
|
};
|
2023-03-07 03:13:08 +00:00
|
|
|
|
2023-05-17 16:37:31 +00:00
|
|
|
view().on_link_hover = [this](auto const& url) {
|
|
|
|
m_hover_label->setText(qstring_from_ak_deprecated_string(url.to_deprecated_string()));
|
2022-09-09 12:23:36 +00:00
|
|
|
update_hover_label();
|
|
|
|
m_hover_label->show();
|
2023-05-17 16:37:31 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
view().on_link_unhover = [this]() {
|
2022-09-09 12:23:36 +00:00
|
|
|
m_hover_label->hide();
|
2023-05-17 16:37:31 +00:00
|
|
|
};
|
2022-07-05 22:18:21 +00:00
|
|
|
|
2023-05-17 16:37:31 +00:00
|
|
|
view().on_back_button = [this] {
|
2022-11-08 01:00:24 +00:00
|
|
|
back();
|
2023-05-17 16:37:31 +00:00
|
|
|
};
|
2022-11-08 01:00:24 +00:00
|
|
|
|
2023-05-17 16:37:31 +00:00
|
|
|
view().on_forward_button = [this] {
|
2022-11-08 01:00:24 +00:00
|
|
|
forward();
|
2023-05-17 16:37:31 +00:00
|
|
|
};
|
2022-11-08 01:00:24 +00:00
|
|
|
|
2023-05-17 16:37:31 +00:00
|
|
|
view().on_load_start = [this](const URL& url, bool is_redirect) {
|
2022-11-24 00:52:16 +00:00
|
|
|
// If we are loading due to a redirect, we replace the current history entry
|
|
|
|
// with the loaded URL
|
|
|
|
if (is_redirect) {
|
|
|
|
m_history.replace_current(url, m_title.toUtf8().data());
|
|
|
|
}
|
|
|
|
|
2022-12-04 18:43:54 +00:00
|
|
|
m_location_edit->setText(url.to_deprecated_string().characters());
|
2023-02-01 18:51:56 +00:00
|
|
|
m_location_edit->setCursorPosition(0);
|
2022-11-08 12:01:20 +00:00
|
|
|
|
|
|
|
// Don't add to history if back or forward is pressed
|
|
|
|
if (!m_is_history_navigation) {
|
|
|
|
m_history.push(url, m_title.toUtf8().data());
|
|
|
|
}
|
|
|
|
m_is_history_navigation = false;
|
|
|
|
|
2023-05-13 12:07:12 +00:00
|
|
|
m_window->go_back_action().setEnabled(m_history.can_go_back());
|
|
|
|
m_window->go_forward_action().setEnabled(m_history.can_go_forward());
|
2023-05-17 15:54:36 +00:00
|
|
|
|
|
|
|
if (m_inspector_widget)
|
|
|
|
m_inspector_widget->clear_dom_json();
|
|
|
|
|
|
|
|
if (m_console_widget)
|
|
|
|
m_console_widget->reset();
|
2023-05-17 16:37:31 +00:00
|
|
|
};
|
2023-05-17 15:54:36 +00:00
|
|
|
|
|
|
|
view().on_load_finish = [this](auto&) {
|
|
|
|
if (m_inspector_widget != nullptr && m_inspector_widget->isVisible()) {
|
|
|
|
view().inspect_dom_tree();
|
|
|
|
view().inspect_accessibility_tree();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-07-05 22:18:21 +00:00
|
|
|
QObject::connect(m_location_edit, &QLineEdit::returnPressed, this, &Tab::location_edit_return_pressed);
|
2023-05-17 16:37:31 +00:00
|
|
|
|
|
|
|
view().on_title_change = [this](auto const& title) {
|
|
|
|
m_title = qstring_from_ak_deprecated_string(title);
|
|
|
|
m_history.update_title(title);
|
|
|
|
|
|
|
|
emit title_changed(tab_index(), m_title);
|
|
|
|
};
|
|
|
|
|
|
|
|
view().on_favicon_change = [this](auto const& bitmap) {
|
|
|
|
auto qimage = QImage(bitmap.scanline_u8(0), bitmap.width(), bitmap.height(), QImage::Format_ARGB32);
|
|
|
|
if (qimage.isNull())
|
|
|
|
return;
|
|
|
|
auto qpixmap = QPixmap::fromImage(qimage);
|
|
|
|
if (qpixmap.isNull())
|
|
|
|
return;
|
|
|
|
emit favicon_changed(tab_index(), QIcon(qpixmap));
|
|
|
|
};
|
|
|
|
|
2022-09-12 07:12:04 +00:00
|
|
|
QObject::connect(focus_location_editor_action, &QAction::triggered, this, &Tab::focus_location_editor);
|
2022-10-05 13:23:41 +00:00
|
|
|
|
2023-05-17 16:37:31 +00:00
|
|
|
view().on_get_source = [this](auto const& url, auto const& source) {
|
2022-10-05 13:23:41 +00:00
|
|
|
auto* text_edit = new QPlainTextEdit(this);
|
|
|
|
text_edit->setWindowFlags(Qt::Window);
|
|
|
|
text_edit->setFont(QFontDatabase::systemFont(QFontDatabase::SystemFont::FixedFont));
|
|
|
|
text_edit->resize(800, 600);
|
2023-05-17 16:37:31 +00:00
|
|
|
text_edit->setWindowTitle(qstring_from_ak_deprecated_string(url.to_deprecated_string()));
|
|
|
|
text_edit->setPlainText(qstring_from_ak_deprecated_string(source));
|
2022-10-05 13:23:41 +00:00
|
|
|
text_edit->show();
|
2023-05-17 16:37:31 +00:00
|
|
|
};
|
2022-10-07 11:35:40 +00:00
|
|
|
|
2023-05-17 16:37:31 +00:00
|
|
|
view().on_navigate_back = [this]() {
|
|
|
|
back();
|
|
|
|
};
|
|
|
|
|
|
|
|
view().on_navigate_forward = [this]() {
|
|
|
|
forward();
|
|
|
|
};
|
|
|
|
|
|
|
|
view().on_refresh = [this]() {
|
|
|
|
reload();
|
|
|
|
};
|
|
|
|
|
|
|
|
view().on_restore_window = [this]() {
|
2022-11-14 16:24:06 +00:00
|
|
|
m_window->showNormal();
|
2023-05-17 16:37:31 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
view().on_reposition_window = [this](auto const& position) {
|
2022-11-14 16:24:06 +00:00
|
|
|
m_window->move(position.x(), position.y());
|
|
|
|
return Gfx::IntPoint { m_window->x(), m_window->y() };
|
2023-05-17 16:37:31 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
view().on_resize_window = [this](auto const& size) {
|
2022-11-14 16:24:06 +00:00
|
|
|
m_window->resize(size.width(), size.height());
|
|
|
|
return Gfx::IntSize { m_window->width(), m_window->height() };
|
2023-05-17 16:37:31 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
view().on_maximize_window = [this]() {
|
2022-11-14 16:24:06 +00:00
|
|
|
m_window->showMaximized();
|
|
|
|
return Gfx::IntRect { m_window->x(), m_window->y(), m_window->width(), m_window->height() };
|
2023-05-17 16:37:31 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
view().on_minimize_window = [this]() {
|
2022-11-14 16:24:06 +00:00
|
|
|
m_window->showMinimized();
|
|
|
|
return Gfx::IntRect { m_window->x(), m_window->y(), m_window->width(), m_window->height() };
|
2023-05-17 16:37:31 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
view().on_fullscreen_window = [this]() {
|
2022-11-14 16:24:06 +00:00
|
|
|
m_window->showFullScreen();
|
|
|
|
return Gfx::IntRect { m_window->x(), m_window->y(), m_window->width(), m_window->height() };
|
2023-05-17 16:37:31 +00:00
|
|
|
};
|
2023-05-15 15:49:44 +00:00
|
|
|
|
2023-05-17 15:54:36 +00:00
|
|
|
view().on_get_dom_tree = [this](auto& dom_tree) {
|
|
|
|
if (m_inspector_widget)
|
|
|
|
m_inspector_widget->set_dom_json(dom_tree);
|
|
|
|
};
|
|
|
|
|
|
|
|
view().on_get_accessibility_tree = [this](auto& accessibility_tree) {
|
|
|
|
if (m_inspector_widget)
|
|
|
|
m_inspector_widget->set_accessibility_json(accessibility_tree);
|
|
|
|
};
|
|
|
|
|
|
|
|
view().on_js_console_new_message = [this](auto message_index) {
|
|
|
|
if (m_console_widget)
|
|
|
|
m_console_widget->notify_about_new_console_message(message_index);
|
|
|
|
};
|
|
|
|
|
|
|
|
view().on_get_js_console_messages = [this](auto start_index, auto& message_types, auto& messages) {
|
|
|
|
if (m_console_widget)
|
|
|
|
m_console_widget->handle_console_messages(start_index, message_types, messages);
|
|
|
|
};
|
|
|
|
|
2023-05-16 11:56:12 +00:00
|
|
|
auto* take_visible_screenshot_action = new QAction("Take &Visible Screenshot", this);
|
|
|
|
take_visible_screenshot_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-image.png").arg(s_serenity_resource_root.characters())));
|
|
|
|
QObject::connect(take_visible_screenshot_action, &QAction::triggered, this, [this]() {
|
2023-05-17 13:53:13 +00:00
|
|
|
if (auto result = view().take_screenshot(WebView::ViewImplementation::ScreenshotType::Visible); result.is_error()) {
|
2023-05-16 11:56:12 +00:00
|
|
|
auto error = String::formatted("{}", result.error()).release_value_but_fixme_should_propagate_errors();
|
|
|
|
QMessageBox::warning(this, "Ladybird", qstring_from_ak_string(error));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
auto* take_full_screenshot_action = new QAction("Take &Full Screenshot", this);
|
|
|
|
take_full_screenshot_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-image.png").arg(s_serenity_resource_root.characters())));
|
|
|
|
QObject::connect(take_full_screenshot_action, &QAction::triggered, this, [this]() {
|
2023-05-17 13:53:13 +00:00
|
|
|
if (auto result = view().take_screenshot(WebView::ViewImplementation::ScreenshotType::Full); result.is_error()) {
|
2023-05-16 11:56:12 +00:00
|
|
|
auto error = String::formatted("{}", result.error()).release_value_but_fixme_should_propagate_errors();
|
|
|
|
QMessageBox::warning(this, "Ladybird", qstring_from_ak_string(error));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-05-15 15:49:44 +00:00
|
|
|
m_page_context_menu = make<QMenu>("Context menu", this);
|
|
|
|
m_page_context_menu->addAction(&m_window->go_back_action());
|
|
|
|
m_page_context_menu->addAction(&m_window->go_forward_action());
|
|
|
|
m_page_context_menu->addAction(&m_window->reload_action());
|
|
|
|
m_page_context_menu->addSeparator();
|
|
|
|
m_page_context_menu->addAction(&m_window->copy_selection_action());
|
|
|
|
m_page_context_menu->addAction(&m_window->select_all_action());
|
|
|
|
m_page_context_menu->addSeparator();
|
2023-05-16 11:56:12 +00:00
|
|
|
m_page_context_menu->addAction(take_visible_screenshot_action);
|
|
|
|
m_page_context_menu->addAction(take_full_screenshot_action);
|
|
|
|
m_page_context_menu->addSeparator();
|
2023-05-15 15:49:44 +00:00
|
|
|
m_page_context_menu->addAction(&m_window->view_source_action());
|
|
|
|
m_page_context_menu->addAction(&m_window->inspect_dom_node_action());
|
|
|
|
|
2023-05-21 04:53:38 +00:00
|
|
|
view().on_context_menu_request = [this](Gfx::IntPoint) {
|
|
|
|
auto screen_position = QCursor::pos();
|
2023-05-15 15:49:44 +00:00
|
|
|
m_page_context_menu->exec(screen_position);
|
|
|
|
};
|
2023-05-15 16:27:10 +00:00
|
|
|
|
2023-05-15 16:48:46 +00:00
|
|
|
auto* open_link_action = new QAction("&Open", this);
|
|
|
|
open_link_action->setIcon(QIcon(QString("%1/res/icons/16x16/go-forward.png").arg(s_serenity_resource_root.characters())));
|
|
|
|
QObject::connect(open_link_action, &QAction::triggered, this, [this]() {
|
|
|
|
open_link(m_link_context_menu_url);
|
|
|
|
});
|
|
|
|
|
|
|
|
auto* open_link_in_new_tab_action = new QAction("&Open in New &Tab", this);
|
|
|
|
open_link_in_new_tab_action->setIcon(QIcon(QString("%1/res/icons/16x16/new-tab.png").arg(s_serenity_resource_root.characters())));
|
|
|
|
QObject::connect(open_link_in_new_tab_action, &QAction::triggered, this, [this]() {
|
|
|
|
open_link_in_new_tab(m_link_context_menu_url);
|
|
|
|
});
|
|
|
|
|
|
|
|
auto* copy_url_action = new QAction("Copy &URL", this);
|
|
|
|
copy_url_action->setIcon(QIcon(QString("%1/res/icons/16x16/edit-copy.png").arg(s_serenity_resource_root.characters())));
|
|
|
|
QObject::connect(copy_url_action, &QAction::triggered, this, [this]() {
|
|
|
|
copy_link_url(m_link_context_menu_url);
|
|
|
|
});
|
|
|
|
|
|
|
|
m_link_context_menu = make<QMenu>("Link context menu", this);
|
|
|
|
m_link_context_menu->addAction(open_link_action);
|
|
|
|
m_link_context_menu->addAction(open_link_in_new_tab_action);
|
|
|
|
m_link_context_menu->addSeparator();
|
|
|
|
m_link_context_menu->addAction(copy_url_action);
|
|
|
|
m_link_context_menu->addSeparator();
|
|
|
|
m_link_context_menu->addAction(&m_window->inspect_dom_node_action());
|
|
|
|
|
2023-05-21 04:53:38 +00:00
|
|
|
view().on_link_context_menu_request = [this](auto const& url, Gfx::IntPoint) {
|
2023-05-15 16:48:46 +00:00
|
|
|
m_link_context_menu_url = url;
|
|
|
|
|
2023-05-21 04:53:38 +00:00
|
|
|
auto screen_position = QCursor::pos();
|
2023-05-15 16:48:46 +00:00
|
|
|
m_link_context_menu->exec(screen_position);
|
|
|
|
};
|
|
|
|
|
2023-05-15 17:43:58 +00:00
|
|
|
auto* open_image_action = new QAction("&Open Image", this);
|
|
|
|
open_image_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-image.png").arg(s_serenity_resource_root.characters())));
|
|
|
|
QObject::connect(open_image_action, &QAction::triggered, this, [this]() {
|
|
|
|
open_link(m_image_context_menu_url);
|
|
|
|
});
|
|
|
|
|
|
|
|
auto* open_image_in_new_tab_action = new QAction("&Open Image in New &Tab", this);
|
|
|
|
open_image_in_new_tab_action->setIcon(QIcon(QString("%1/res/icons/16x16/new-tab.png").arg(s_serenity_resource_root.characters())));
|
|
|
|
QObject::connect(open_image_in_new_tab_action, &QAction::triggered, this, [this]() {
|
|
|
|
open_link_in_new_tab(m_image_context_menu_url);
|
|
|
|
});
|
|
|
|
|
|
|
|
auto* copy_image_action = new QAction("&Copy Image", this);
|
|
|
|
copy_image_action->setIcon(QIcon(QString("%1/res/icons/16x16/edit-copy.png").arg(s_serenity_resource_root.characters())));
|
|
|
|
QObject::connect(copy_image_action, &QAction::triggered, this, [this]() {
|
|
|
|
auto* bitmap = m_image_context_menu_bitmap.bitmap();
|
|
|
|
if (bitmap == nullptr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto data = Gfx::BMPWriter::encode(*bitmap);
|
|
|
|
if (data.is_error())
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto image = QImage::fromData(data.value().data(), data.value().size(), "BMP");
|
|
|
|
if (image.isNull())
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto* clipboard = QGuiApplication::clipboard();
|
|
|
|
clipboard->setImage(image);
|
|
|
|
});
|
|
|
|
|
|
|
|
auto* copy_image_url_action = new QAction("Copy Image &URL", this);
|
|
|
|
copy_image_url_action->setIcon(QIcon(QString("%1/res/icons/16x16/edit-copy.png").arg(s_serenity_resource_root.characters())));
|
|
|
|
QObject::connect(copy_image_url_action, &QAction::triggered, this, [this]() {
|
|
|
|
copy_link_url(m_image_context_menu_url);
|
|
|
|
});
|
|
|
|
|
|
|
|
m_image_context_menu = make<QMenu>("Image context menu", this);
|
|
|
|
m_image_context_menu->addAction(open_image_action);
|
|
|
|
m_image_context_menu->addAction(open_image_in_new_tab_action);
|
|
|
|
m_image_context_menu->addSeparator();
|
|
|
|
m_image_context_menu->addAction(copy_image_action);
|
|
|
|
m_image_context_menu->addAction(copy_image_url_action);
|
|
|
|
m_image_context_menu->addSeparator();
|
|
|
|
m_image_context_menu->addAction(&m_window->inspect_dom_node_action());
|
|
|
|
|
2023-05-21 04:53:38 +00:00
|
|
|
view().on_image_context_menu_request = [this](auto& image_url, Gfx::IntPoint, Gfx::ShareableBitmap const& shareable_bitmap) {
|
2023-05-15 17:43:58 +00:00
|
|
|
m_image_context_menu_url = image_url;
|
|
|
|
m_image_context_menu_bitmap = shareable_bitmap;
|
|
|
|
|
2023-05-21 04:53:38 +00:00
|
|
|
auto screen_position = QCursor::pos();
|
2023-05-15 17:43:58 +00:00
|
|
|
m_image_context_menu->exec(screen_position);
|
|
|
|
};
|
|
|
|
|
2023-06-16 14:51:38 +00:00
|
|
|
m_media_context_menu_play_icon = make<QIcon>(QString("%1/res/icons/16x16/play.png").arg(s_serenity_resource_root.characters()));
|
|
|
|
m_media_context_menu_pause_icon = make<QIcon>(QString("%1/res/icons/16x16/pause.png").arg(s_serenity_resource_root.characters()));
|
2023-05-15 16:27:10 +00:00
|
|
|
|
2023-06-16 14:51:38 +00:00
|
|
|
m_media_context_menu_play_pause_action = make<QAction>("&Play", this);
|
|
|
|
m_media_context_menu_play_pause_action->setIcon(*m_media_context_menu_play_icon);
|
|
|
|
QObject::connect(m_media_context_menu_play_pause_action, &QAction::triggered, this, [this]() {
|
|
|
|
view().toggle_media_play_state();
|
2023-05-15 16:27:10 +00:00
|
|
|
});
|
|
|
|
|
2023-06-16 14:51:38 +00:00
|
|
|
m_media_context_menu_controls_action = make<QAction>("Show &Controls", this);
|
|
|
|
m_media_context_menu_controls_action->setCheckable(true);
|
|
|
|
QObject::connect(m_media_context_menu_controls_action, &QAction::triggered, this, [this]() {
|
|
|
|
view().toggle_media_controls_state();
|
2023-05-15 16:27:10 +00:00
|
|
|
});
|
|
|
|
|
2023-06-16 14:51:38 +00:00
|
|
|
m_media_context_menu_loop_action = make<QAction>("&Loop", this);
|
|
|
|
m_media_context_menu_loop_action->setCheckable(true);
|
|
|
|
QObject::connect(m_media_context_menu_loop_action, &QAction::triggered, this, [this]() {
|
|
|
|
view().toggle_media_loop_state();
|
2023-05-15 16:27:10 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
auto* open_video_action = new QAction("&Open Video", this);
|
|
|
|
open_video_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-video.png").arg(s_serenity_resource_root.characters())));
|
|
|
|
QObject::connect(open_video_action, &QAction::triggered, this, [this]() {
|
2023-06-16 14:51:38 +00:00
|
|
|
open_link(m_media_context_menu_url);
|
2023-05-15 16:27:10 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
auto* open_video_in_new_tab_action = new QAction("Open Video in New &Tab", this);
|
|
|
|
open_video_in_new_tab_action->setIcon(QIcon(QString("%1/res/icons/16x16/new-tab.png").arg(s_serenity_resource_root.characters())));
|
|
|
|
QObject::connect(open_video_in_new_tab_action, &QAction::triggered, this, [this]() {
|
2023-06-16 14:51:38 +00:00
|
|
|
open_link_in_new_tab(m_media_context_menu_url);
|
2023-05-15 16:27:10 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
auto* copy_video_url_action = new QAction("Copy Video &URL", this);
|
|
|
|
copy_video_url_action->setIcon(QIcon(QString("%1/res/icons/16x16/edit-copy.png").arg(s_serenity_resource_root.characters())));
|
|
|
|
QObject::connect(copy_video_url_action, &QAction::triggered, this, [this]() {
|
2023-06-16 14:51:38 +00:00
|
|
|
copy_link_url(m_media_context_menu_url);
|
2023-05-15 16:27:10 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
m_video_context_menu = make<QMenu>("Video context menu", this);
|
2023-06-16 14:51:38 +00:00
|
|
|
m_video_context_menu->addAction(m_media_context_menu_play_pause_action);
|
|
|
|
m_video_context_menu->addAction(m_media_context_menu_controls_action);
|
|
|
|
m_video_context_menu->addAction(m_media_context_menu_loop_action);
|
2023-05-15 16:27:10 +00:00
|
|
|
m_video_context_menu->addSeparator();
|
|
|
|
m_video_context_menu->addAction(open_video_action);
|
|
|
|
m_video_context_menu->addAction(open_video_in_new_tab_action);
|
|
|
|
m_video_context_menu->addSeparator();
|
|
|
|
m_video_context_menu->addAction(copy_video_url_action);
|
|
|
|
m_video_context_menu->addSeparator();
|
|
|
|
m_video_context_menu->addAction(&m_window->inspect_dom_node_action());
|
|
|
|
|
2023-06-16 14:51:38 +00:00
|
|
|
view().on_media_context_menu_request = [this](Gfx::IntPoint, Web::Page::MediaContextMenu const& menu) {
|
|
|
|
m_media_context_menu_url = menu.media_url;
|
2023-05-15 16:27:10 +00:00
|
|
|
|
2023-06-16 14:51:38 +00:00
|
|
|
if (menu.is_playing) {
|
|
|
|
m_media_context_menu_play_pause_action->setIcon(*m_media_context_menu_pause_icon);
|
|
|
|
m_media_context_menu_play_pause_action->setText("&Pause");
|
2023-05-15 16:27:10 +00:00
|
|
|
} else {
|
2023-06-16 14:51:38 +00:00
|
|
|
m_media_context_menu_play_pause_action->setIcon(*m_media_context_menu_play_icon);
|
|
|
|
m_media_context_menu_play_pause_action->setText("&Play");
|
2023-05-15 16:27:10 +00:00
|
|
|
}
|
|
|
|
|
2023-06-16 14:51:38 +00:00
|
|
|
m_media_context_menu_controls_action->setChecked(menu.has_user_agent_controls);
|
|
|
|
m_media_context_menu_loop_action->setChecked(menu.is_looping);
|
2023-05-15 16:27:10 +00:00
|
|
|
|
2023-05-21 04:53:38 +00:00
|
|
|
auto screen_position = QCursor::pos();
|
2023-06-16 14:51:38 +00:00
|
|
|
|
|
|
|
if (menu.is_video)
|
|
|
|
m_video_context_menu->exec(screen_position);
|
2023-05-15 16:27:10 +00:00
|
|
|
};
|
2022-09-12 07:12:04 +00:00
|
|
|
}
|
|
|
|
|
2023-05-17 15:54:36 +00:00
|
|
|
Tab::~Tab()
|
|
|
|
{
|
|
|
|
close_sub_widgets();
|
|
|
|
}
|
|
|
|
|
2023-03-28 23:25:42 +00:00
|
|
|
void Tab::update_reset_zoom_button()
|
|
|
|
{
|
|
|
|
auto zoom_level = view().zoom_level();
|
|
|
|
if (zoom_level != 1.0f) {
|
|
|
|
auto zoom_level_text = MUST(String::formatted("{}%", round_to<int>(zoom_level * 100)));
|
|
|
|
m_reset_zoom_button->setText(qstring_from_ak_string(zoom_level_text));
|
|
|
|
m_reset_zoom_button_action->setVisible(true);
|
|
|
|
} else {
|
|
|
|
m_reset_zoom_button_action->setVisible(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-12 07:12:04 +00:00
|
|
|
void Tab::focus_location_editor()
|
|
|
|
{
|
|
|
|
m_location_edit->setFocus();
|
|
|
|
m_location_edit->selectAll();
|
2022-07-06 13:38:50 +00:00
|
|
|
}
|
|
|
|
|
2023-01-08 14:01:26 +00:00
|
|
|
void Tab::navigate(QString url, LoadType load_type)
|
2022-07-06 13:38:50 +00:00
|
|
|
{
|
2023-06-03 05:41:59 +00:00
|
|
|
if (url.startsWith("/"))
|
|
|
|
url = "file://" + url;
|
|
|
|
else if (!url.startsWith("http://", Qt::CaseInsensitive) && !url.startsWith("https://", Qt::CaseInsensitive) && !url.startsWith("file://", Qt::CaseInsensitive) && !url.startsWith("about:", Qt::CaseInsensitive))
|
2023-05-23 08:19:27 +00:00
|
|
|
url = "https://" + url;
|
2023-01-08 14:01:26 +00:00
|
|
|
m_is_history_navigation = (load_type == LoadType::HistoryNavigation);
|
2022-12-04 18:43:54 +00:00
|
|
|
view().load(ak_deprecated_string_from_qstring(url));
|
2022-07-06 13:38:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Tab::back()
|
|
|
|
{
|
|
|
|
if (!m_history.can_go_back())
|
|
|
|
return;
|
|
|
|
|
2022-11-08 12:01:20 +00:00
|
|
|
m_is_history_navigation = true;
|
2022-07-06 13:38:50 +00:00
|
|
|
m_history.go_back();
|
2022-12-04 18:43:54 +00:00
|
|
|
view().load(m_history.current().url.to_deprecated_string());
|
2022-07-06 13:38:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Tab::forward()
|
|
|
|
{
|
|
|
|
if (!m_history.can_go_forward())
|
|
|
|
return;
|
|
|
|
|
2022-11-08 12:01:20 +00:00
|
|
|
m_is_history_navigation = true;
|
2022-07-06 13:38:50 +00:00
|
|
|
m_history.go_forward();
|
2022-12-04 18:43:54 +00:00
|
|
|
view().load(m_history.current().url.to_deprecated_string());
|
2022-07-06 13:38:50 +00:00
|
|
|
}
|
|
|
|
|
2022-07-05 22:18:21 +00:00
|
|
|
void Tab::reload()
|
|
|
|
{
|
2022-11-08 23:35:55 +00:00
|
|
|
m_is_history_navigation = true;
|
2022-12-04 18:43:54 +00:00
|
|
|
view().load(m_history.current().url.to_deprecated_string());
|
2022-07-05 22:18:21 +00:00
|
|
|
}
|
|
|
|
|
2023-05-15 16:27:10 +00:00
|
|
|
void Tab::open_link(URL const& url)
|
|
|
|
{
|
|
|
|
view().on_link_click(url, "", 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Tab::open_link_in_new_tab(URL const& url)
|
|
|
|
{
|
|
|
|
view().on_link_click(url, "_blank", 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Tab::copy_link_url(URL const& url)
|
|
|
|
{
|
|
|
|
auto* clipboard = QGuiApplication::clipboard();
|
|
|
|
clipboard->setText(qstring_from_ak_deprecated_string(url.to_deprecated_string()));
|
|
|
|
}
|
|
|
|
|
2022-07-05 22:18:21 +00:00
|
|
|
void Tab::location_edit_return_pressed()
|
|
|
|
{
|
2022-07-06 13:38:50 +00:00
|
|
|
navigate(m_location_edit->text());
|
2022-07-05 22:18:21 +00:00
|
|
|
}
|
|
|
|
|
2023-05-25 21:47:12 +00:00
|
|
|
void Tab::open_file()
|
|
|
|
{
|
|
|
|
auto filename = QFileDialog::getOpenFileName(this, "Open file", QDir::homePath(), "All Files (*.*)");
|
|
|
|
if (!filename.isNull())
|
|
|
|
navigate("file://" + filename);
|
|
|
|
}
|
|
|
|
|
2022-07-05 22:18:21 +00:00
|
|
|
int Tab::tab_index()
|
|
|
|
{
|
2022-09-21 19:17:13 +00:00
|
|
|
return m_window->tab_index(this);
|
2022-07-05 22:18:21 +00:00
|
|
|
}
|
2022-07-08 12:14:40 +00:00
|
|
|
|
2022-12-04 18:43:54 +00:00
|
|
|
void Tab::debug_request(DeprecatedString const& request, DeprecatedString const& argument)
|
2022-07-08 12:14:40 +00:00
|
|
|
{
|
2022-10-03 21:53:04 +00:00
|
|
|
if (request == "dump-history")
|
|
|
|
m_history.dump();
|
|
|
|
else
|
|
|
|
m_view->debug_request(request, argument);
|
2022-07-08 12:14:40 +00:00
|
|
|
}
|
2022-09-09 12:23:36 +00:00
|
|
|
|
|
|
|
void Tab::resizeEvent(QResizeEvent* event)
|
|
|
|
{
|
|
|
|
QWidget::resizeEvent(event);
|
|
|
|
if (m_hover_label->isVisible())
|
|
|
|
update_hover_label();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Tab::update_hover_label()
|
|
|
|
{
|
|
|
|
m_hover_label->resize(QFontMetrics(m_hover_label->font()).boundingRect(m_hover_label->text()).adjusted(-4, -2, 4, 2).size());
|
|
|
|
m_hover_label->move(6, height() - m_hover_label->height() - 8);
|
|
|
|
m_hover_label->raise();
|
|
|
|
}
|
2023-05-05 13:38:46 +00:00
|
|
|
|
|
|
|
bool Tab::event(QEvent* event)
|
|
|
|
{
|
|
|
|
if (event->type() == QEvent::PaletteChange) {
|
|
|
|
rerender_toolbar_icons();
|
|
|
|
return QWidget::event(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
return QWidget::event(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Tab::rerender_toolbar_icons()
|
|
|
|
{
|
2023-05-13 12:07:12 +00:00
|
|
|
m_window->go_back_action().setIcon(render_svg_icon_with_theme_colors("back", palette()));
|
|
|
|
m_window->go_forward_action().setIcon(render_svg_icon_with_theme_colors("forward", palette()));
|
|
|
|
m_window->reload_action().setIcon(render_svg_icon_with_theme_colors("reload", palette()));
|
2023-05-05 13:38:46 +00:00
|
|
|
}
|
2023-05-17 15:54:36 +00:00
|
|
|
|
|
|
|
void Tab::show_inspector_window(InspectorTarget inspector_target)
|
|
|
|
{
|
|
|
|
bool inspector_previously_loaded = m_inspector_widget != nullptr;
|
|
|
|
|
|
|
|
if (!m_inspector_widget) {
|
|
|
|
m_inspector_widget = new Ladybird::InspectorWidget;
|
|
|
|
m_inspector_widget->setWindowTitle("Inspector");
|
|
|
|
m_inspector_widget->resize(640, 480);
|
|
|
|
m_inspector_widget->on_close = [this] {
|
|
|
|
view().clear_inspected_dom_node();
|
|
|
|
};
|
|
|
|
|
|
|
|
m_inspector_widget->on_dom_node_inspected = [&](auto id, auto pseudo_element) {
|
|
|
|
return view().inspect_dom_node(id, pseudo_element);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!inspector_previously_loaded || !m_inspector_widget->dom_loaded()) {
|
|
|
|
view().inspect_dom_tree();
|
|
|
|
view().inspect_accessibility_tree();
|
|
|
|
}
|
|
|
|
|
|
|
|
m_inspector_widget->show();
|
|
|
|
|
|
|
|
if (inspector_target == InspectorTarget::HoveredElement) {
|
|
|
|
auto hovered_node = view().get_hovered_node_id();
|
|
|
|
m_inspector_widget->set_selection({ hovered_node });
|
|
|
|
} else {
|
|
|
|
m_inspector_widget->select_default_node();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Tab::show_console_window()
|
|
|
|
{
|
|
|
|
if (!m_console_widget) {
|
|
|
|
m_console_widget = new Ladybird::ConsoleWidget;
|
|
|
|
m_console_widget->setWindowTitle("JS Console");
|
|
|
|
m_console_widget->resize(640, 480);
|
|
|
|
m_console_widget->on_js_input = [this](auto js_source) {
|
|
|
|
view().js_console_input(js_source);
|
|
|
|
};
|
|
|
|
m_console_widget->on_request_messages = [this](i32 start_index) {
|
|
|
|
view().js_console_request_messages(start_index);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
m_console_widget->show();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Tab::close_sub_widgets()
|
|
|
|
{
|
|
|
|
auto close_widget_window = [](auto* widget) {
|
|
|
|
if (widget)
|
|
|
|
widget->close();
|
|
|
|
};
|
|
|
|
|
|
|
|
close_widget_window(m_console_widget);
|
|
|
|
close_widget_window(m_inspector_widget);
|
|
|
|
}
|