Serenity Operating System
1/*
2 * Copyright (c) 2022-2023, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2022, Matthew Costa <ucosty@gmail.com>
4 * Copyright (c) 2022, Filiph Sandström <filiph.sandstrom@filfatstudios.com>
5 * Copyright (c) 2023, Linus Groh <linusg@serenityos.org>
6 *
7 * SPDX-License-Identifier: BSD-2-Clause
8 */
9
10#include "BrowserWindow.h"
11#include "Settings.h"
12#include "SettingsDialog.h"
13#include "Utilities.h"
14#include "WebContentView.h"
15#include <AK/TypeCasts.h>
16#include <Browser/CookieJar.h>
17#include <LibWeb/CSS/PreferredColorScheme.h>
18#include <LibWeb/Loader/ResourceLoader.h>
19#include <QAction>
20#include <QActionGroup>
21#include <QClipboard>
22#include <QGuiApplication>
23#include <QInputDialog>
24#include <QPlainTextEdit>
25#include <QTabBar>
26
27extern DeprecatedString s_serenity_resource_root;
28extern Browser::Settings* s_settings;
29
30BrowserWindow::BrowserWindow(Browser::CookieJar& cookie_jar, StringView webdriver_content_ipc_path)
31 : m_cookie_jar(cookie_jar)
32 , m_webdriver_content_ipc_path(webdriver_content_ipc_path)
33{
34 m_tabs_container = new QTabWidget(this);
35 m_tabs_container->installEventFilter(this);
36 m_tabs_container->setElideMode(Qt::TextElideMode::ElideRight);
37 m_tabs_container->setMovable(true);
38 m_tabs_container->setTabsClosable(true);
39 m_tabs_container->setDocumentMode(true);
40 m_tabs_container->setTabBarAutoHide(true);
41
42 auto* menu = menuBar()->addMenu("&File");
43
44 auto* new_tab_action = new QAction("New &Tab", this);
45 new_tab_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::AddTab));
46 menu->addAction(new_tab_action);
47
48 auto* settings_action = new QAction("&Settings", this);
49 settings_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Preferences));
50 menu->addAction(settings_action);
51
52 auto* close_current_tab_action = new QAction("Close Current Tab", this);
53 close_current_tab_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Close));
54 menu->addAction(close_current_tab_action);
55
56 auto* quit_action = new QAction("&Quit", this);
57 quit_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Quit));
58 menu->addAction(quit_action);
59
60 auto* edit_menu = menuBar()->addMenu("&Edit");
61
62 auto* copy_action = new QAction("&Copy", this);
63 copy_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Copy));
64 edit_menu->addAction(copy_action);
65 QObject::connect(copy_action, &QAction::triggered, this, &BrowserWindow::copy_selected_text);
66
67 auto* select_all_action = new QAction("Select &All", this);
68 select_all_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::SelectAll));
69 edit_menu->addAction(select_all_action);
70 QObject::connect(select_all_action, &QAction::triggered, this, &BrowserWindow::select_all);
71
72 auto* view_menu = menuBar()->addMenu("&View");
73
74 auto* open_next_tab_action = new QAction("Open &Next Tab", this);
75 open_next_tab_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_PageDown));
76 view_menu->addAction(open_next_tab_action);
77 QObject::connect(open_next_tab_action, &QAction::triggered, this, &BrowserWindow::open_next_tab);
78
79 auto* open_previous_tab_action = new QAction("Open &Previous Tab", this);
80 open_previous_tab_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_PageUp));
81 view_menu->addAction(open_previous_tab_action);
82 QObject::connect(open_previous_tab_action, &QAction::triggered, this, &BrowserWindow::open_previous_tab);
83
84 view_menu->addSeparator();
85
86 auto* zoom_menu = view_menu->addMenu("&Zoom");
87
88 auto* zoom_in_action = new QAction("Zoom &In", this);
89 auto zoom_in_shortcuts = QKeySequence::keyBindings(QKeySequence::StandardKey::ZoomIn);
90 zoom_in_shortcuts.append(QKeySequence(Qt::CTRL | Qt::Key_Equal));
91 zoom_in_action->setShortcuts(zoom_in_shortcuts);
92 zoom_menu->addAction(zoom_in_action);
93 QObject::connect(zoom_in_action, &QAction::triggered, this, &BrowserWindow::zoom_in);
94
95 auto* zoom_out_action = new QAction("Zoom &Out", this);
96 zoom_out_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::ZoomOut));
97 zoom_menu->addAction(zoom_out_action);
98 QObject::connect(zoom_out_action, &QAction::triggered, this, &BrowserWindow::zoom_out);
99
100 auto* reset_zoom_action = new QAction("&Reset Zoom", this);
101 reset_zoom_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_0));
102 zoom_menu->addAction(reset_zoom_action);
103 QObject::connect(reset_zoom_action, &QAction::triggered, this, &BrowserWindow::reset_zoom);
104
105 view_menu->addSeparator();
106
107 auto* color_scheme_menu = view_menu->addMenu("&Color Scheme");
108
109 auto* color_scheme_group = new QActionGroup(this);
110
111 auto* auto_color_scheme = new QAction("&Auto", this);
112 auto_color_scheme->setCheckable(true);
113 color_scheme_group->addAction(auto_color_scheme);
114 color_scheme_menu->addAction(auto_color_scheme);
115 QObject::connect(auto_color_scheme, &QAction::triggered, this, &BrowserWindow::enable_auto_color_scheme);
116
117 auto* light_color_scheme = new QAction("&Light", this);
118 light_color_scheme->setCheckable(true);
119 color_scheme_group->addAction(light_color_scheme);
120 color_scheme_menu->addAction(light_color_scheme);
121 QObject::connect(light_color_scheme, &QAction::triggered, this, &BrowserWindow::enable_light_color_scheme);
122
123 auto* dark_color_scheme = new QAction("&Dark", this);
124 dark_color_scheme->setCheckable(true);
125 color_scheme_group->addAction(dark_color_scheme);
126 color_scheme_menu->addAction(dark_color_scheme);
127 QObject::connect(dark_color_scheme, &QAction::triggered, this, &BrowserWindow::enable_dark_color_scheme);
128
129 auto_color_scheme->setChecked(true);
130
131 auto* inspect_menu = menuBar()->addMenu("&Inspect");
132
133 auto* view_source_action = new QAction("View &Source", this);
134 view_source_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-html.png").arg(s_serenity_resource_root.characters())));
135 view_source_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_U));
136 inspect_menu->addAction(view_source_action);
137 QObject::connect(view_source_action, &QAction::triggered, this, [this] {
138 if (m_current_tab) {
139 m_current_tab->view().get_source();
140 }
141 });
142
143 auto* js_console_action = new QAction("Show &JS Console", this);
144 js_console_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-javascript.png").arg(s_serenity_resource_root.characters())));
145 js_console_action->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_J));
146 inspect_menu->addAction(js_console_action);
147 QObject::connect(js_console_action, &QAction::triggered, this, [this] {
148 if (m_current_tab) {
149 m_current_tab->view().show_js_console();
150 }
151 });
152
153 auto* inspector_action = new QAction("Open &Inspector");
154 inspector_action->setIcon(QIcon(QString("%1/res/icons/browser/dom-tree.png").arg(s_serenity_resource_root.characters())));
155 inspector_action->setShortcut(QKeySequence("Ctrl+Shift+I"));
156 inspect_menu->addAction(inspector_action);
157 QObject::connect(inspector_action, &QAction::triggered, this, [this] {
158 if (m_current_tab) {
159 m_current_tab->view().show_inspector();
160 }
161 });
162
163 auto* debug_menu = menuBar()->addMenu("&Debug");
164
165 auto* dump_dom_tree_action = new QAction("Dump DOM Tree", this);
166 dump_dom_tree_action->setIcon(QIcon(QString("%1/res/icons/browser/dom-tree.png").arg(s_serenity_resource_root.characters())));
167 debug_menu->addAction(dump_dom_tree_action);
168 QObject::connect(dump_dom_tree_action, &QAction::triggered, this, [this] {
169 debug_request("dump-dom-tree");
170 });
171
172 auto* dump_layout_tree_action = new QAction("Dump Layout Tree", this);
173 dump_layout_tree_action->setIcon(QIcon(QString("%1/res/icons/16x16/layout.png").arg(s_serenity_resource_root.characters())));
174 debug_menu->addAction(dump_layout_tree_action);
175 QObject::connect(dump_layout_tree_action, &QAction::triggered, this, [this] {
176 debug_request("dump-layout-tree");
177 });
178
179 auto* dump_stacking_context_tree_action = new QAction("Dump Stacking Context Tree", this);
180 dump_stacking_context_tree_action->setIcon(QIcon(QString("%1/res/icons/16x16/layers.png").arg(s_serenity_resource_root.characters())));
181 debug_menu->addAction(dump_stacking_context_tree_action);
182 QObject::connect(dump_stacking_context_tree_action, &QAction::triggered, this, [this] {
183 debug_request("dump-stacking-context-tree");
184 });
185
186 auto* dump_style_sheets_action = new QAction("Dump Style Sheets", this);
187 dump_style_sheets_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-css.png").arg(s_serenity_resource_root.characters())));
188 debug_menu->addAction(dump_style_sheets_action);
189 QObject::connect(dump_style_sheets_action, &QAction::triggered, this, [this] {
190 debug_request("dump-style-sheets");
191 });
192
193 auto* dump_history_action = new QAction("Dump History", this);
194 dump_history_action->setIcon(QIcon(QString("%1/res/icons/16x16/history.png").arg(s_serenity_resource_root.characters())));
195 debug_menu->addAction(dump_history_action);
196 QObject::connect(dump_history_action, &QAction::triggered, this, [this] {
197 debug_request("dump-history");
198 });
199
200 auto* dump_cookies_action = new QAction("Dump Cookies", this);
201 dump_cookies_action->setIcon(QIcon(QString("%1/res/icons/browser/cookie.png").arg(s_serenity_resource_root.characters())));
202 debug_menu->addAction(dump_cookies_action);
203 QObject::connect(dump_cookies_action, &QAction::triggered, this, [this] {
204 m_cookie_jar.dump_cookies();
205 });
206
207 auto* dump_local_storage_action = new QAction("Dump Local Storage", this);
208 dump_local_storage_action->setIcon(QIcon(QString("%1/res/icons/browser/local-storage.png").arg(s_serenity_resource_root.characters())));
209 debug_menu->addAction(dump_local_storage_action);
210 QObject::connect(dump_local_storage_action, &QAction::triggered, this, [this] {
211 debug_request("dump-local-storage");
212 });
213
214 debug_menu->addSeparator();
215
216 auto* show_line_box_borders_action = new QAction("Show Line Box Borders", this);
217 show_line_box_borders_action->setCheckable(true);
218 debug_menu->addAction(show_line_box_borders_action);
219 QObject::connect(show_line_box_borders_action, &QAction::triggered, this, [this, show_line_box_borders_action] {
220 bool state = show_line_box_borders_action->isChecked();
221 debug_request("set-line-box-borders", state ? "on" : "off");
222 });
223
224 debug_menu->addSeparator();
225
226 auto* collect_garbage_action = new QAction("Collect Garbage", this);
227 collect_garbage_action->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_G));
228 collect_garbage_action->setIcon(QIcon(QString("%1/res/icons/16x16/trash-can.png").arg(s_serenity_resource_root.characters())));
229 debug_menu->addAction(collect_garbage_action);
230 QObject::connect(collect_garbage_action, &QAction::triggered, this, [this] {
231 debug_request("collect-garbage");
232 });
233
234 auto* clear_cache_action = new QAction("Clear Cache", this);
235 clear_cache_action->setIcon(QIcon(QString("%1/res/icons/browser/clear-cache.png").arg(s_serenity_resource_root.characters())));
236 debug_menu->addAction(clear_cache_action);
237 QObject::connect(clear_cache_action, &QAction::triggered, this, [this] {
238 debug_request("clear-cache");
239 });
240
241 auto* spoof_user_agent_menu = debug_menu->addMenu("Spoof User Agent");
242 spoof_user_agent_menu->setIcon(QIcon(QString("%1/res/icons/16x16/spoof.png").arg(s_serenity_resource_root.characters())));
243
244 auto* user_agent_group = new QActionGroup(this);
245
246 auto add_user_agent = [this, &user_agent_group, &spoof_user_agent_menu](auto& name, auto& user_agent) {
247 auto* action = new QAction(name);
248 action->setCheckable(true);
249 user_agent_group->addAction(action);
250 spoof_user_agent_menu->addAction(action);
251 QObject::connect(action, &QAction::triggered, this, [this, user_agent] {
252 debug_request("spoof-user-agent", user_agent);
253 debug_request("clear-cache"); // clear the cache to ensure requests are re-done with the new user agent
254 });
255 return action;
256 };
257
258 auto* disable_spoofing = add_user_agent("Disabled", Web::default_user_agent);
259 disable_spoofing->setChecked(true);
260 add_user_agent("Chrome Linux Desktop", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36");
261 add_user_agent("Firefox Linux Desktop", "Mozilla/5.0 (X11; Linux i686; rv:87.0) Gecko/20100101 Firefox/87.0");
262 add_user_agent("Safari macOS Desktop", "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15");
263 add_user_agent("Chrome Android Mobile", "Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.66 Mobile Safari/537.36");
264 add_user_agent("Firefox Android Mobile", "Mozilla/5.0 (Android 11; Mobile; rv:68.0) Gecko/68.0 Firefox/86.0");
265 add_user_agent("Safari iOS Mobile", "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1");
266
267 auto* custom_user_agent_action = new QAction("Custom...");
268 custom_user_agent_action->setCheckable(true);
269 user_agent_group->addAction(custom_user_agent_action);
270 spoof_user_agent_menu->addAction(custom_user_agent_action);
271 QObject::connect(custom_user_agent_action, &QAction::triggered, this, [this, disable_spoofing] {
272 auto user_agent = QInputDialog::getText(this, "Custom User Agent", "Enter User Agent:");
273 if (!user_agent.isEmpty()) {
274 debug_request("spoof-user-agent", ak_deprecated_string_from_qstring(user_agent));
275 debug_request("clear-cache"); // clear the cache to ensure requests are re-done with the new user agent
276 } else {
277 disable_spoofing->activate(QAction::Trigger);
278 }
279 });
280
281 debug_menu->addSeparator();
282
283 auto* enable_scripting_action = new QAction("Enable Scripting", this);
284 enable_scripting_action->setCheckable(true);
285 enable_scripting_action->setChecked(true);
286 debug_menu->addAction(enable_scripting_action);
287 QObject::connect(enable_scripting_action, &QAction::triggered, this, [this, enable_scripting_action] {
288 bool state = enable_scripting_action->isChecked();
289 debug_request("scripting", state ? "on" : "off");
290 });
291
292 auto* block_pop_ups_action = new QAction("Block Pop-ups", this);
293 block_pop_ups_action->setCheckable(true);
294 block_pop_ups_action->setChecked(true);
295 debug_menu->addAction(block_pop_ups_action);
296 QObject::connect(block_pop_ups_action, &QAction::triggered, this, [this, block_pop_ups_action] {
297 bool state = block_pop_ups_action->isChecked();
298 debug_request("block-pop-ups", state ? "on" : "off");
299 });
300
301 auto* enable_same_origin_policy_action = new QAction("Enable Same-Origin Policy", this);
302 enable_same_origin_policy_action->setCheckable(true);
303 debug_menu->addAction(enable_same_origin_policy_action);
304 QObject::connect(enable_same_origin_policy_action, &QAction::triggered, this, [this, enable_same_origin_policy_action] {
305 bool state = enable_same_origin_policy_action->isChecked();
306 debug_request("same-origin-policy", state ? "on" : "off");
307 });
308
309 QObject::connect(new_tab_action, &QAction::triggered, this, [this] {
310 new_tab(s_settings->new_tab_page(), Activate::Yes);
311 });
312 QObject::connect(settings_action, &QAction::triggered, this, [this] {
313 new SettingsDialog(this);
314 });
315 QObject::connect(quit_action, &QAction::triggered, this, &QMainWindow::close);
316 QObject::connect(m_tabs_container, &QTabWidget::currentChanged, [this](int index) {
317 setWindowTitle(QString("%1 - Ladybird").arg(m_tabs_container->tabText(index)));
318 setWindowIcon(m_tabs_container->tabIcon(index));
319 m_current_tab = verify_cast<Tab>(m_tabs_container->widget(index));
320 });
321 QObject::connect(m_tabs_container, &QTabWidget::tabCloseRequested, this, &BrowserWindow::close_tab);
322 QObject::connect(close_current_tab_action, &QAction::triggered, this, &BrowserWindow::close_current_tab);
323
324 new_tab(s_settings->new_tab_page(), Activate::Yes);
325
326 setCentralWidget(m_tabs_container);
327}
328
329void BrowserWindow::debug_request(DeprecatedString const& request, DeprecatedString const& argument)
330{
331 if (!m_current_tab)
332 return;
333 m_current_tab->debug_request(request, argument);
334}
335
336void BrowserWindow::new_tab(QString const& url, Activate activate)
337{
338 auto tab = make<Tab>(this, m_webdriver_content_ipc_path);
339 auto tab_ptr = tab.ptr();
340 m_tabs.append(std::move(tab));
341
342 if (m_current_tab == nullptr) {
343 m_current_tab = tab_ptr;
344 }
345
346 m_tabs_container->addTab(tab_ptr, "New Tab");
347 if (activate == Activate::Yes)
348 m_tabs_container->setCurrentWidget(tab_ptr);
349
350 QObject::connect(tab_ptr, &Tab::title_changed, this, &BrowserWindow::tab_title_changed);
351 QObject::connect(tab_ptr, &Tab::favicon_changed, this, &BrowserWindow::tab_favicon_changed);
352
353 QObject::connect(&tab_ptr->view(), &WebContentView::urls_dropped, this, [this](auto& urls) {
354 VERIFY(urls.size());
355 m_current_tab->navigate(urls[0].toString());
356
357 for (qsizetype i = 1; i < urls.size(); ++i)
358 new_tab(urls[i].toString(), Activate::No);
359 });
360
361 tab_ptr->view().on_get_all_cookies = [this](auto const& url) {
362 return m_cookie_jar.get_all_cookies(url);
363 };
364
365 tab_ptr->view().on_get_named_cookie = [this](auto const& url, auto const& name) {
366 return m_cookie_jar.get_named_cookie(url, name);
367 };
368
369 tab_ptr->view().on_get_cookie = [this](auto& url, auto source) -> DeprecatedString {
370 return m_cookie_jar.get_cookie(url, source);
371 };
372
373 tab_ptr->view().on_set_cookie = [this](auto& url, auto& cookie, auto source) {
374 m_cookie_jar.set_cookie(url, cookie, source);
375 };
376
377 tab_ptr->view().on_update_cookie = [this](auto const& cookie) {
378 m_cookie_jar.update_cookie(cookie);
379 };
380
381 tab_ptr->focus_location_editor();
382
383 // We *don't* load the initial page if we are connected to a WebDriver, as the Set URL command may come in very
384 // quickly, and become replaced by this load.
385 if (m_webdriver_content_ipc_path.is_empty()) {
386 // We make it HistoryNavigation so that the initial page doesn't get added to the history.
387 tab_ptr->navigate(url, Tab::LoadType::HistoryNavigation);
388 }
389}
390
391void BrowserWindow::close_tab(int index)
392{
393 auto* tab = m_tabs_container->widget(index);
394 m_tabs_container->removeTab(index);
395 m_tabs.remove_first_matching([&](auto& entry) {
396 return entry == tab;
397 });
398}
399
400void BrowserWindow::close_current_tab()
401{
402 auto count = m_tabs_container->count() - 1;
403 if (!count)
404 close();
405 else
406 close_tab(m_tabs_container->currentIndex());
407}
408
409int BrowserWindow::tab_index(Tab* tab)
410{
411 return m_tabs_container->indexOf(tab);
412}
413
414void BrowserWindow::tab_title_changed(int index, QString const& title)
415{
416 if (title.isEmpty()) {
417 m_tabs_container->setTabText(index, "...");
418 if (m_tabs_container->currentIndex() == index)
419 setWindowTitle("Ladybird");
420 } else {
421 m_tabs_container->setTabText(index, title);
422 if (m_tabs_container->currentIndex() == index)
423 setWindowTitle(QString("%1 - Ladybird").arg(title));
424 }
425}
426
427void BrowserWindow::tab_favicon_changed(int index, QIcon icon)
428{
429 m_tabs_container->setTabIcon(index, icon);
430 if (m_tabs_container->currentIndex() == index)
431 setWindowIcon(icon);
432}
433
434void BrowserWindow::open_next_tab()
435{
436 if (m_tabs_container->count() <= 1)
437 return;
438
439 auto next_index = m_tabs_container->currentIndex() + 1;
440 if (next_index >= m_tabs_container->count())
441 next_index = 0;
442 m_tabs_container->setCurrentIndex(next_index);
443}
444
445void BrowserWindow::open_previous_tab()
446{
447 if (m_tabs_container->count() <= 1)
448 return;
449
450 auto next_index = m_tabs_container->currentIndex() - 1;
451 if (next_index < 0)
452 next_index = m_tabs_container->count() - 1;
453 m_tabs_container->setCurrentIndex(next_index);
454}
455
456void BrowserWindow::enable_auto_color_scheme()
457{
458 for (auto& tab : m_tabs) {
459 tab->view().set_preferred_color_scheme(Web::CSS::PreferredColorScheme::Auto);
460 }
461}
462
463void BrowserWindow::enable_light_color_scheme()
464{
465 for (auto& tab : m_tabs) {
466 tab->view().set_preferred_color_scheme(Web::CSS::PreferredColorScheme::Light);
467 }
468}
469
470void BrowserWindow::enable_dark_color_scheme()
471{
472 for (auto& tab : m_tabs) {
473 tab->view().set_preferred_color_scheme(Web::CSS::PreferredColorScheme::Dark);
474 }
475}
476
477void BrowserWindow::zoom_in()
478{
479 if (m_current_tab)
480 m_current_tab->view().zoom_in();
481}
482
483void BrowserWindow::zoom_out()
484{
485 if (m_current_tab)
486 m_current_tab->view().zoom_out();
487}
488
489void BrowserWindow::reset_zoom()
490{
491 if (m_current_tab)
492 m_current_tab->view().reset_zoom();
493}
494
495void BrowserWindow::select_all()
496{
497 if (auto* tab = m_current_tab)
498 tab->view().select_all();
499}
500
501void BrowserWindow::copy_selected_text()
502{
503 if (auto* tab = m_current_tab) {
504 auto text = tab->view().selected_text();
505 auto* clipboard = QGuiApplication::clipboard();
506 clipboard->setText(qstring_from_ak_deprecated_string(text));
507 }
508}
509
510void BrowserWindow::resizeEvent(QResizeEvent* event)
511{
512 QWidget::resizeEvent(event);
513 for (auto& tab : m_tabs) {
514 tab->view().set_window_size({ frameSize().width(), frameSize().height() });
515 }
516}
517
518void BrowserWindow::moveEvent(QMoveEvent* event)
519{
520 QWidget::moveEvent(event);
521 for (auto& tab : m_tabs) {
522 tab->view().set_window_position({ event->pos().x(), event->pos().y() });
523 }
524}
525
526bool BrowserWindow::eventFilter(QObject* obj, QEvent* event)
527{
528 if (event->type() == QEvent::MouseButtonRelease) {
529 auto const* const mouse_event = static_cast<QMouseEvent*>(event);
530 if (mouse_event->button() == Qt::MouseButton::MiddleButton) {
531 if (obj == m_tabs_container) {
532 auto const tab_index = m_tabs_container->tabBar()->tabAt(mouse_event->pos());
533 close_tab(tab_index);
534 return true;
535 }
536 }
537 }
538
539 return QMainWindow::eventFilter(obj, event);
540}