Serenity Operating System
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
8#include "Tab.h"
9#include "BrowserWindow.h"
10#include "Settings.h"
11#include "Utilities.h"
12#include <Browser/History.h>
13#include <QCoreApplication>
14#include <QFont>
15#include <QFontMetrics>
16#include <QPlainTextEdit>
17#include <QPoint>
18#include <QResizeEvent>
19
20extern DeprecatedString s_serenity_resource_root;
21extern Browser::Settings* s_settings;
22
23Tab::Tab(BrowserWindow* window, StringView webdriver_content_ipc_path)
24 : QWidget(window)
25 , m_window(window)
26{
27 m_layout = new QBoxLayout(QBoxLayout::Direction::TopToBottom, this);
28 m_layout->setSpacing(0);
29 m_layout->setContentsMargins(0, 0, 0, 0);
30
31 m_view = new WebContentView(webdriver_content_ipc_path);
32 m_toolbar = new QToolBar(this);
33 m_location_edit = new LocationEdit(this);
34
35 m_hover_label = new QLabel(this);
36 m_hover_label->hide();
37 m_hover_label->setFrameShape(QFrame::Shape::Box);
38 m_hover_label->setAutoFillBackground(true);
39
40 auto* focus_location_editor_action = new QAction("Edit Location");
41 focus_location_editor_action->setShortcut(QKeySequence("Ctrl+L"));
42 addAction(focus_location_editor_action);
43
44 m_layout->addWidget(m_toolbar);
45 m_layout->addWidget(m_view);
46
47 auto back_icon_path = QString("%1/res/icons/16x16/go-back.png").arg(s_serenity_resource_root.characters());
48 auto forward_icon_path = QString("%1/res/icons/16x16/go-forward.png").arg(s_serenity_resource_root.characters());
49 auto home_icon_path = QString("%1/res/icons/16x16/go-home.png").arg(s_serenity_resource_root.characters());
50 auto reload_icon_path = QString("%1/res/icons/16x16/reload.png").arg(s_serenity_resource_root.characters());
51 m_back_action = make<QAction>(QIcon(back_icon_path), "Back");
52 m_back_action->setEnabled(false);
53 m_back_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Back));
54 m_forward_action = make<QAction>(QIcon(forward_icon_path), "Forward");
55 m_forward_action->setEnabled(false);
56 m_forward_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Forward));
57 m_home_action = make<QAction>(QIcon(home_icon_path), "Home");
58 m_reload_action = make<QAction>(QIcon(reload_icon_path), "Reload");
59 m_reload_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Refresh));
60
61 m_toolbar->addAction(m_back_action);
62 m_toolbar->addAction(m_forward_action);
63 m_toolbar->addAction(m_reload_action);
64 m_toolbar->addAction(m_home_action);
65 m_toolbar->addWidget(m_location_edit);
66
67 QObject::connect(m_view, &WebContentView::close, [this] {
68 m_window->close_tab(tab_index());
69 });
70
71 QObject::connect(m_view, &WebContentView::link_hovered, [this](QString const& title) {
72 m_hover_label->setText(title);
73 update_hover_label();
74 m_hover_label->show();
75 });
76 QObject::connect(m_view, &WebContentView::link_unhovered, [this] {
77 m_hover_label->hide();
78 });
79
80 QObject::connect(m_view, &WebContentView::back_mouse_button, [this] {
81 back();
82 });
83
84 QObject::connect(m_view, &WebContentView::forward_mouse_button, [this] {
85 forward();
86 });
87
88 QObject::connect(m_view, &WebContentView::load_started, [this](const URL& url, bool is_redirect) {
89 // If we are loading due to a redirect, we replace the current history entry
90 // with the loaded URL
91 if (is_redirect) {
92 m_history.replace_current(url, m_title.toUtf8().data());
93 }
94
95 m_location_edit->setText(url.to_deprecated_string().characters());
96 m_location_edit->setCursorPosition(0);
97
98 // Don't add to history if back or forward is pressed
99 if (!m_is_history_navigation) {
100 m_history.push(url, m_title.toUtf8().data());
101 }
102 m_is_history_navigation = false;
103
104 m_back_action->setEnabled(m_history.can_go_back());
105 m_forward_action->setEnabled(m_history.can_go_forward());
106 });
107 QObject::connect(m_location_edit, &QLineEdit::returnPressed, this, &Tab::location_edit_return_pressed);
108 QObject::connect(m_view, &WebContentView::title_changed, this, &Tab::page_title_changed);
109 QObject::connect(m_view, &WebContentView::favicon_changed, this, &Tab::page_favicon_changed);
110
111 QObject::connect(m_back_action, &QAction::triggered, this, &Tab::back);
112 QObject::connect(m_forward_action, &QAction::triggered, this, &Tab::forward);
113 QObject::connect(m_home_action, &QAction::triggered, this, &Tab::home);
114 QObject::connect(m_reload_action, &QAction::triggered, this, &Tab::reload);
115 QObject::connect(focus_location_editor_action, &QAction::triggered, this, &Tab::focus_location_editor);
116
117 QObject::connect(m_view, &WebContentView::got_source, this, [this](AK::URL, QString const& source) {
118 auto* text_edit = new QPlainTextEdit(this);
119 text_edit->setWindowFlags(Qt::Window);
120 text_edit->setFont(QFontDatabase::systemFont(QFontDatabase::SystemFont::FixedFont));
121 text_edit->resize(800, 600);
122 text_edit->setPlainText(source);
123 text_edit->show();
124 });
125
126 QObject::connect(m_view, &WebContentView::navigate_back, this, &Tab::back);
127 QObject::connect(m_view, &WebContentView::navigate_forward, this, &Tab::forward);
128 QObject::connect(m_view, &WebContentView::refresh, this, &Tab::reload);
129 QObject::connect(m_view, &WebContentView::restore_window, this, [this]() {
130 m_window->showNormal();
131 });
132 QObject::connect(m_view, &WebContentView::reposition_window, this, [this](auto const& position) {
133 m_window->move(position.x(), position.y());
134 return Gfx::IntPoint { m_window->x(), m_window->y() };
135 });
136 QObject::connect(m_view, &WebContentView::resize_window, this, [this](auto const& size) {
137 m_window->resize(size.width(), size.height());
138 return Gfx::IntSize { m_window->width(), m_window->height() };
139 });
140 QObject::connect(m_view, &WebContentView::maximize_window, this, [this]() {
141 m_window->showMaximized();
142 return Gfx::IntRect { m_window->x(), m_window->y(), m_window->width(), m_window->height() };
143 });
144 QObject::connect(m_view, &WebContentView::minimize_window, this, [this]() {
145 m_window->showMinimized();
146 return Gfx::IntRect { m_window->x(), m_window->y(), m_window->width(), m_window->height() };
147 });
148 QObject::connect(m_view, &WebContentView::fullscreen_window, this, [this]() {
149 m_window->showFullScreen();
150 return Gfx::IntRect { m_window->x(), m_window->y(), m_window->width(), m_window->height() };
151 });
152}
153
154void Tab::focus_location_editor()
155{
156 m_location_edit->setFocus();
157 m_location_edit->selectAll();
158}
159
160void Tab::navigate(QString url, LoadType load_type)
161{
162 if (!url.startsWith("http://", Qt::CaseInsensitive) && !url.startsWith("https://", Qt::CaseInsensitive) && !url.startsWith("file://", Qt::CaseInsensitive) && !url.startsWith("about:", Qt::CaseInsensitive))
163 url = "http://" + url;
164 m_is_history_navigation = (load_type == LoadType::HistoryNavigation);
165 view().load(ak_deprecated_string_from_qstring(url));
166}
167
168void Tab::back()
169{
170 if (!m_history.can_go_back())
171 return;
172
173 m_is_history_navigation = true;
174 m_history.go_back();
175 view().load(m_history.current().url.to_deprecated_string());
176}
177
178void Tab::forward()
179{
180 if (!m_history.can_go_forward())
181 return;
182
183 m_is_history_navigation = true;
184 m_history.go_forward();
185 view().load(m_history.current().url.to_deprecated_string());
186}
187
188void Tab::home()
189{
190 navigate(s_settings->homepage());
191}
192
193void Tab::reload()
194{
195 m_is_history_navigation = true;
196 view().load(m_history.current().url.to_deprecated_string());
197}
198
199void Tab::location_edit_return_pressed()
200{
201 navigate(m_location_edit->text());
202}
203
204void Tab::page_title_changed(QString title)
205{
206 m_title = title;
207 m_history.update_title(ak_deprecated_string_from_qstring(m_title));
208 emit title_changed(tab_index(), std::move(title));
209}
210
211void Tab::page_favicon_changed(QIcon icon)
212{
213 emit favicon_changed(tab_index(), std::move(icon));
214}
215
216int Tab::tab_index()
217{
218 return m_window->tab_index(this);
219}
220
221void Tab::debug_request(DeprecatedString const& request, DeprecatedString const& argument)
222{
223 if (request == "dump-history")
224 m_history.dump();
225 else
226 m_view->debug_request(request, argument);
227}
228
229void Tab::resizeEvent(QResizeEvent* event)
230{
231 QWidget::resizeEvent(event);
232 if (m_hover_label->isVisible())
233 update_hover_label();
234}
235
236void Tab::update_hover_label()
237{
238 m_hover_label->resize(QFontMetrics(m_hover_label->font()).boundingRect(m_hover_label->text()).adjusted(-4, -2, 4, 2).size());
239 m_hover_label->move(6, height() - m_hover_label->height() - 8);
240 m_hover_label->raise();
241}