Serenity Operating System
at master 1035 lines 35 kB view raw
1/* 2 * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2023, Linus Groh <linusg@serenityos.org> 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#define AK_DONT_REPLACE_STD 9 10#include "WebContentView.h" 11#include "ConsoleWidget.h" 12#include "HelperProcess.h" 13#include "InspectorWidget.h" 14#include "Utilities.h" 15#include <AK/Assertions.h> 16#include <AK/ByteBuffer.h> 17#include <AK/Format.h> 18#include <AK/HashTable.h> 19#include <AK/LexicalPath.h> 20#include <AK/NonnullOwnPtr.h> 21#include <AK/StringBuilder.h> 22#include <AK/Types.h> 23#include <Browser/CookieJar.h> 24#include <Kernel/API/KeyCode.h> 25#include <LibCore/ArgsParser.h> 26#include <LibCore/EventLoop.h> 27#include <LibCore/IODevice.h> 28#include <LibCore/System.h> 29#include <LibCore/Timer.h> 30#include <LibGfx/Bitmap.h> 31#include <LibGfx/Font/FontDatabase.h> 32#include <LibGfx/PNGWriter.h> 33#include <LibGfx/Painter.h> 34#include <LibGfx/Rect.h> 35#include <LibGfx/SystemTheme.h> 36#include <LibJS/Runtime/ConsoleObject.h> 37#include <LibMain/Main.h> 38#include <LibWeb/Loader/ContentFilter.h> 39#include <LibWebView/WebContentClient.h> 40#include <QApplication> 41#include <QCursor> 42#include <QIcon> 43#include <QInputDialog> 44#include <QLineEdit> 45#include <QMessageBox> 46#include <QMimeData> 47#include <QMouseEvent> 48#include <QPaintEvent> 49#include <QPainter> 50#include <QScrollBar> 51#include <QTextEdit> 52#include <QTimer> 53#include <QToolTip> 54 55WebContentView::WebContentView(StringView webdriver_content_ipc_path) 56 : m_webdriver_content_ipc_path(webdriver_content_ipc_path) 57{ 58 setMouseTracking(true); 59 setAcceptDrops(true); 60 61 setFocusPolicy(Qt::FocusPolicy::StrongFocus); 62 63 m_device_pixel_ratio = devicePixelRatio(); 64 m_inverse_pixel_scaling_ratio = 1.0 / m_device_pixel_ratio; 65 66 verticalScrollBar()->setSingleStep(24); 67 horizontalScrollBar()->setSingleStep(24); 68 69 QObject::connect(verticalScrollBar(), &QScrollBar::valueChanged, [this](int) { 70 update_viewport_rect(); 71 }); 72 QObject::connect(horizontalScrollBar(), &QScrollBar::valueChanged, [this](int) { 73 update_viewport_rect(); 74 }); 75 76 create_client(); 77} 78 79WebContentView::~WebContentView() 80{ 81 close_sub_widgets(); 82} 83 84unsigned get_button_from_qt_event(QMouseEvent const& event) 85{ 86 if (event.button() == Qt::MouseButton::LeftButton) 87 return 1; 88 if (event.button() == Qt::MouseButton::RightButton) 89 return 2; 90 if (event.button() == Qt::MouseButton::MiddleButton) 91 return 4; 92 if (event.button() == Qt::MouseButton::BackButton) 93 return 8; 94 if (event.buttons() == Qt::MouseButton::ForwardButton) 95 return 16; 96 return 0; 97} 98 99unsigned get_buttons_from_qt_event(QMouseEvent const& event) 100{ 101 unsigned buttons = 0; 102 if (event.buttons() & Qt::MouseButton::LeftButton) 103 buttons |= 1; 104 if (event.buttons() & Qt::MouseButton::RightButton) 105 buttons |= 2; 106 if (event.buttons() & Qt::MouseButton::MiddleButton) 107 buttons |= 4; 108 if (event.buttons() & Qt::MouseButton::BackButton) 109 buttons |= 8; 110 if (event.buttons() & Qt::MouseButton::ForwardButton) 111 buttons |= 16; 112 return buttons; 113} 114 115unsigned get_modifiers_from_qt_mouse_event(QMouseEvent const& event) 116{ 117 unsigned modifiers = 0; 118 if (event.modifiers() & Qt::Modifier::ALT) 119 modifiers |= 1; 120 if (event.modifiers() & Qt::Modifier::CTRL) 121 modifiers |= 2; 122 if (event.modifiers() & Qt::Modifier::SHIFT) 123 modifiers |= 4; 124 return modifiers; 125} 126 127unsigned get_modifiers_from_qt_keyboard_event(QKeyEvent const& event) 128{ 129 auto modifiers = 0; 130 if (event.modifiers().testFlag(Qt::AltModifier)) 131 modifiers |= KeyModifier::Mod_Alt; 132 if (event.modifiers().testFlag(Qt::ControlModifier)) 133 modifiers |= KeyModifier::Mod_Ctrl; 134 if (event.modifiers().testFlag(Qt::MetaModifier)) 135 modifiers |= KeyModifier::Mod_Super; 136 if (event.modifiers().testFlag(Qt::ShiftModifier)) 137 modifiers |= KeyModifier::Mod_Shift; 138 if (event.modifiers().testFlag(Qt::AltModifier)) 139 modifiers |= KeyModifier::Mod_AltGr; 140 return modifiers; 141} 142 143KeyCode get_keycode_from_qt_keyboard_event(QKeyEvent const& event) 144{ 145 struct Mapping { 146 constexpr Mapping(Qt::Key q, KeyCode s) 147 : qt_key(q) 148 , serenity_key(s) 149 { 150 } 151 152 Qt::Key qt_key; 153 KeyCode serenity_key; 154 }; 155 156 constexpr Mapping mappings[] = { 157 { Qt::Key_0, Key_0 }, 158 { Qt::Key_1, Key_1 }, 159 { Qt::Key_2, Key_2 }, 160 { Qt::Key_3, Key_3 }, 161 { Qt::Key_4, Key_4 }, 162 { Qt::Key_5, Key_5 }, 163 { Qt::Key_6, Key_6 }, 164 { Qt::Key_7, Key_7 }, 165 { Qt::Key_8, Key_8 }, 166 { Qt::Key_9, Key_9 }, 167 { Qt::Key_A, Key_A }, 168 { Qt::Key_Alt, Key_Alt }, 169 { Qt::Key_Ampersand, Key_Ampersand }, 170 { Qt::Key_Apostrophe, Key_Apostrophe }, 171 { Qt::Key_AsciiCircum, Key_Circumflex }, 172 { Qt::Key_AsciiTilde, Key_Tilde }, 173 { Qt::Key_Asterisk, Key_Asterisk }, 174 { Qt::Key_At, Key_AtSign }, 175 { Qt::Key_B, Key_B }, 176 { Qt::Key_Backslash, Key_Backslash }, 177 { Qt::Key_Backspace, Key_Backspace }, 178 { Qt::Key_Bar, Key_Pipe }, 179 { Qt::Key_BraceLeft, Key_LeftBrace }, 180 { Qt::Key_BraceRight, Key_RightBrace }, 181 { Qt::Key_BracketLeft, Key_LeftBracket }, 182 { Qt::Key_BracketRight, Key_RightBracket }, 183 { Qt::Key_C, Key_C }, 184 { Qt::Key_CapsLock, Key_CapsLock }, 185 { Qt::Key_Colon, Key_Colon }, 186 { Qt::Key_Comma, Key_Comma }, 187 { Qt::Key_Control, Key_Control }, 188 { Qt::Key_D, Key_D }, 189 { Qt::Key_Delete, Key_Delete }, 190 { Qt::Key_Dollar, Key_Dollar }, 191 { Qt::Key_Down, Key_Down }, 192 { Qt::Key_E, Key_E }, 193 { Qt::Key_End, Key_End }, 194 { Qt::Key_Equal, Key_Equal }, 195 { Qt::Key_Escape, Key_Escape }, 196 { Qt::Key_exclamdown, Key_ExclamationPoint }, 197 { Qt::Key_F, Key_F }, 198 { Qt::Key_F1, Key_F1 }, 199 { Qt::Key_F10, Key_F10 }, 200 { Qt::Key_F11, Key_F11 }, 201 { Qt::Key_F12, Key_F12 }, 202 { Qt::Key_F2, Key_F2 }, 203 { Qt::Key_F3, Key_F3 }, 204 { Qt::Key_F4, Key_F4 }, 205 { Qt::Key_F5, Key_F5 }, 206 { Qt::Key_F6, Key_F6 }, 207 { Qt::Key_F7, Key_F7 }, 208 { Qt::Key_F8, Key_F8 }, 209 { Qt::Key_F9, Key_F9 }, 210 { Qt::Key_G, Key_G }, 211 { Qt::Key_Greater, Key_GreaterThan }, 212 { Qt::Key_H, Key_H }, 213 { Qt::Key_Home, Key_Home }, 214 { Qt::Key_I, Key_I }, 215 { Qt::Key_Insert, Key_Insert }, 216 { Qt::Key_J, Key_J }, 217 { Qt::Key_K, Key_K }, 218 { Qt::Key_L, Key_L }, 219 { Qt::Key_Left, Key_Left }, 220 { Qt::Key_Less, Key_LessThan }, 221 { Qt::Key_M, Key_M }, 222 { Qt::Key_Menu, Key_Menu }, 223 { Qt::Key_Minus, Key_Minus }, 224 { Qt::Key_N, Key_N }, 225 { Qt::Key_NumLock, Key_NumLock }, 226 { Qt::Key_O, Key_O }, 227 { Qt::Key_P, Key_P }, 228 { Qt::Key_PageDown, Key_PageDown }, 229 { Qt::Key_PageUp, Key_PageUp }, 230 { Qt::Key_ParenLeft, Key_LeftParen }, 231 { Qt::Key_ParenRight, Key_RightParen }, 232 { Qt::Key_Percent, Key_Percent }, 233 { Qt::Key_Period, Key_Period }, 234 { Qt::Key_Plus, Key_Plus }, 235 { Qt::Key_Print, Key_PrintScreen }, 236 { Qt::Key_Q, Key_Q }, 237 { Qt::Key_Question, Key_QuestionMark }, 238 { Qt::Key_QuoteDbl, Key_DoubleQuote }, 239 { Qt::Key_R, Key_R }, 240 { Qt::Key_Return, Key_Return }, 241 { Qt::Key_Right, Key_Right }, 242 { Qt::Key_S, Key_S }, 243 { Qt::Key_ScrollLock, Key_ScrollLock }, 244 { Qt::Key_Semicolon, Key_Semicolon }, 245 { Qt::Key_Shift, Key_LeftShift }, 246 { Qt::Key_Slash, Key_Slash }, 247 { Qt::Key_Space, Key_Space }, 248 { Qt::Key_Super_L, Key_Super }, 249 { Qt::Key_SysReq, Key_SysRq }, 250 { Qt::Key_T, Key_T }, 251 { Qt::Key_Tab, Key_Tab }, 252 { Qt::Key_U, Key_U }, 253 { Qt::Key_Underscore, Key_Underscore }, 254 { Qt::Key_Up, Key_Up }, 255 { Qt::Key_V, Key_V }, 256 { Qt::Key_W, Key_W }, 257 { Qt::Key_X, Key_X }, 258 { Qt::Key_Y, Key_Y }, 259 { Qt::Key_Z, Key_Z }, 260 }; 261 262 for (auto const& mapping : mappings) { 263 if (event.key() == mapping.qt_key) 264 return mapping.serenity_key; 265 } 266 return Key_Invalid; 267} 268 269void WebContentView::mouseMoveEvent(QMouseEvent* event) 270{ 271 Gfx::IntPoint position(event->position().x() / m_inverse_pixel_scaling_ratio, event->position().y() / m_inverse_pixel_scaling_ratio); 272 auto buttons = get_buttons_from_qt_event(*event); 273 auto modifiers = get_modifiers_from_qt_mouse_event(*event); 274 client().async_mouse_move(to_content(position), 0, buttons, modifiers); 275} 276 277void WebContentView::mousePressEvent(QMouseEvent* event) 278{ 279 Gfx::IntPoint position(event->position().x() / m_inverse_pixel_scaling_ratio, event->position().y() / m_inverse_pixel_scaling_ratio); 280 auto button = get_button_from_qt_event(*event); 281 if (button == 0) { 282 // We could not convert Qt buttons to something that Lagom can 283 // recognize - don't even bother propagating this to the web engine 284 // as it will not handle it anyway, and it will (currently) assert 285 return; 286 } 287 auto modifiers = get_modifiers_from_qt_mouse_event(*event); 288 auto buttons = get_buttons_from_qt_event(*event); 289 client().async_mouse_down(to_content(position), button, buttons, modifiers); 290} 291 292void WebContentView::mouseReleaseEvent(QMouseEvent* event) 293{ 294 Gfx::IntPoint position(event->position().x() / m_inverse_pixel_scaling_ratio, event->position().y() / m_inverse_pixel_scaling_ratio); 295 auto button = get_button_from_qt_event(*event); 296 297 if (event->button() & Qt::MouseButton::BackButton) { 298 emit back_mouse_button(); 299 } else if (event->button() & Qt::MouseButton::ForwardButton) { 300 emit forward_mouse_button(); 301 } 302 303 if (button == 0) { 304 // We could not convert Qt buttons to something that Lagom can 305 // recognize - don't even bother propagating this to the web engine 306 // as it will not handle it anyway, and it will (currently) assert 307 return; 308 } 309 auto modifiers = get_modifiers_from_qt_mouse_event(*event); 310 auto buttons = get_buttons_from_qt_event(*event); 311 client().async_mouse_up(to_content(position), button, buttons, modifiers); 312} 313 314void WebContentView::dragEnterEvent(QDragEnterEvent* event) 315{ 316 if (event->mimeData()->hasUrls()) 317 event->acceptProposedAction(); 318} 319 320void WebContentView::dropEvent(QDropEvent* event) 321{ 322 VERIFY(event->mimeData()->hasUrls()); 323 emit urls_dropped(event->mimeData()->urls()); 324 event->acceptProposedAction(); 325} 326 327void WebContentView::keyPressEvent(QKeyEvent* event) 328{ 329 switch (event->key()) { 330 case Qt::Key_Left: 331 case Qt::Key_Right: 332 case Qt::Key_Up: 333 case Qt::Key_Down: 334 case Qt::Key_PageUp: 335 case Qt::Key_PageDown: 336 QAbstractScrollArea::keyPressEvent(event); 337 break; 338 default: 339 break; 340 } 341 342 if (event->key() == Qt::Key_Backtab) { 343 // NOTE: Qt transforms Shift+Tab into a "Backtab", so we undo that transformation here. 344 client().async_key_down(KeyCode::Key_Tab, Mod_Shift, '\t'); 345 return; 346 } 347 348 auto text = event->text(); 349 if (text.isEmpty()) { 350 return; 351 } 352 auto point = event->text()[0].unicode(); 353 auto keycode = get_keycode_from_qt_keyboard_event(*event); 354 auto modifiers = get_modifiers_from_qt_keyboard_event(*event); 355 client().async_key_down(keycode, modifiers, point); 356} 357 358void WebContentView::keyReleaseEvent(QKeyEvent* event) 359{ 360 auto text = event->text(); 361 if (text.isEmpty()) { 362 return; 363 } 364 auto point = event->text()[0].unicode(); 365 auto keycode = get_keycode_from_qt_keyboard_event(*event); 366 auto modifiers = get_modifiers_from_qt_keyboard_event(*event); 367 client().async_key_up(keycode, modifiers, point); 368} 369 370void WebContentView::focusInEvent(QFocusEvent*) 371{ 372 client().async_set_has_focus(true); 373} 374 375void WebContentView::focusOutEvent(QFocusEvent*) 376{ 377 client().async_set_has_focus(false); 378} 379 380Gfx::IntPoint WebContentView::to_content(Gfx::IntPoint viewport_position) const 381{ 382 return viewport_position.translated(horizontalScrollBar()->value(), verticalScrollBar()->value()); 383} 384 385Gfx::IntPoint WebContentView::to_widget(Gfx::IntPoint content_position) const 386{ 387 return content_position.translated(-horizontalScrollBar()->value(), -verticalScrollBar()->value()); 388} 389 390void WebContentView::paintEvent(QPaintEvent*) 391{ 392 QPainter painter(viewport()); 393 painter.scale(m_inverse_pixel_scaling_ratio, m_inverse_pixel_scaling_ratio); 394 395 if (auto* bitmap = m_client_state.has_usable_bitmap ? m_client_state.front_bitmap.bitmap.ptr() : m_backup_bitmap.ptr()) { 396 QImage q_image(bitmap->scanline_u8(0), bitmap->width(), bitmap->height(), QImage::Format_RGB32); 397 painter.drawImage(QPoint(0, 0), q_image); 398 return; 399 } 400 401 painter.fillRect(rect(), palette().base()); 402} 403 404void WebContentView::resizeEvent(QResizeEvent* event) 405{ 406 QAbstractScrollArea::resizeEvent(event); 407 handle_resize(); 408} 409 410void WebContentView::handle_resize() 411{ 412 update_viewport_rect(); 413 414 if (m_client_state.has_usable_bitmap) { 415 // NOTE: We keep the outgoing front bitmap as a backup so we have something to paint until we get a new one. 416 m_backup_bitmap = m_client_state.front_bitmap.bitmap; 417 } 418 419 if (m_client_state.front_bitmap.bitmap) 420 client().async_remove_backing_store(m_client_state.front_bitmap.id); 421 422 if (m_client_state.back_bitmap.bitmap) 423 client().async_remove_backing_store(m_client_state.back_bitmap.id); 424 425 m_client_state.front_bitmap = {}; 426 m_client_state.back_bitmap = {}; 427 m_client_state.has_usable_bitmap = false; 428 429 auto available_size = m_viewport_rect.size(); 430 431 if (available_size.is_empty()) 432 return; 433 434 if (auto new_bitmap_or_error = Gfx::Bitmap::create_shareable(Gfx::BitmapFormat::BGRx8888, available_size); !new_bitmap_or_error.is_error()) { 435 m_client_state.front_bitmap.bitmap = new_bitmap_or_error.release_value(); 436 m_client_state.front_bitmap.id = m_client_state.next_bitmap_id++; 437 client().async_add_backing_store(m_client_state.front_bitmap.id, m_client_state.front_bitmap.bitmap->to_shareable_bitmap()); 438 } 439 440 if (auto new_bitmap_or_error = Gfx::Bitmap::create_shareable(Gfx::BitmapFormat::BGRx8888, available_size); !new_bitmap_or_error.is_error()) { 441 m_client_state.back_bitmap.bitmap = new_bitmap_or_error.release_value(); 442 m_client_state.back_bitmap.id = m_client_state.next_bitmap_id++; 443 client().async_add_backing_store(m_client_state.back_bitmap.id, m_client_state.back_bitmap.bitmap->to_shareable_bitmap()); 444 } 445 446 request_repaint(); 447} 448 449void WebContentView::set_viewport_rect(Gfx::IntRect rect) 450{ 451 m_viewport_rect = rect; 452 client().async_set_viewport_rect(rect); 453} 454 455void WebContentView::set_window_size(Gfx::IntSize size) 456{ 457 client().async_set_window_size(size); 458} 459 460void WebContentView::set_window_position(Gfx::IntPoint position) 461{ 462 client().async_set_window_position(position); 463} 464 465void WebContentView::update_viewport_rect() 466{ 467 auto scaled_width = int(viewport()->width() / m_inverse_pixel_scaling_ratio); 468 auto scaled_height = int(viewport()->height() / m_inverse_pixel_scaling_ratio); 469 Gfx::IntRect rect(horizontalScrollBar()->value(), verticalScrollBar()->value(), scaled_width, scaled_height); 470 471 set_viewport_rect(rect); 472 473 request_repaint(); 474} 475 476void WebContentView::did_output_js_console_message(i32 message_index) 477{ 478 if (m_console_widget) 479 m_console_widget->notify_about_new_console_message(message_index); 480} 481 482void WebContentView::did_get_js_console_messages(i32 start_index, Vector<DeprecatedString> message_types, Vector<DeprecatedString> messages) 483{ 484 if (m_console_widget) 485 m_console_widget->handle_console_messages(start_index, message_types, messages); 486} 487 488void WebContentView::ensure_js_console_widget() 489{ 490 if (!m_console_widget) { 491 m_console_widget = new Ladybird::ConsoleWidget; 492 m_console_widget->setWindowTitle("JS Console"); 493 m_console_widget->resize(640, 480); 494 m_console_widget->on_js_input = [this](auto js_source) { 495 client().async_js_console_input(js_source); 496 }; 497 m_console_widget->on_request_messages = [this](i32 start_index) { 498 client().async_js_console_request_messages(start_index); 499 }; 500 } 501} 502 503void WebContentView::show_js_console() 504{ 505 ensure_js_console_widget(); 506 m_console_widget->show(); 507} 508 509void WebContentView::ensure_inspector_widget() 510{ 511 if (m_inspector_widget) 512 return; 513 m_inspector_widget = new Ladybird::InspectorWidget; 514 m_inspector_widget->setWindowTitle("Inspector"); 515 m_inspector_widget->resize(640, 480); 516 m_inspector_widget->on_close = [this] { 517 clear_inspected_dom_node(); 518 }; 519 520 m_inspector_widget->on_dom_node_inspected = [&](auto id, auto pseudo_element) { 521 return inspect_dom_node(id, pseudo_element); 522 }; 523} 524 525void WebContentView::close_sub_widgets() 526{ 527 auto close_widget_window = [](auto* widget) { 528 if (widget) 529 widget->close(); 530 }; 531 close_widget_window(m_console_widget); 532 close_widget_window(m_inspector_widget); 533} 534 535bool WebContentView::is_inspector_open() const 536{ 537 return m_inspector_widget && m_inspector_widget->isVisible(); 538} 539 540void WebContentView::show_inspector() 541{ 542 ensure_inspector_widget(); 543 m_inspector_widget->show(); 544 inspect_dom_tree(); 545 inspect_accessibility_tree(); 546} 547 548void WebContentView::update_zoom() 549{ 550 client().async_set_device_pixels_per_css_pixel(m_device_pixel_ratio * m_zoom_level); 551 update_viewport_rect(); 552 request_repaint(); 553} 554 555void WebContentView::showEvent(QShowEvent* event) 556{ 557 QAbstractScrollArea::showEvent(event); 558 client().async_set_system_visibility_state(true); 559} 560 561void WebContentView::hideEvent(QHideEvent* event) 562{ 563 QAbstractScrollArea::hideEvent(event); 564 client().async_set_system_visibility_state(false); 565} 566 567void WebContentView::create_client() 568{ 569 m_client_state = {}; 570 571 auto candidate_web_content_paths = get_paths_for_helper_process("WebContent"sv).release_value_but_fixme_should_propagate_errors(); 572 auto new_client = launch_web_content_process(candidate_web_content_paths, m_webdriver_content_ipc_path).release_value_but_fixme_should_propagate_errors(); 573 574 m_web_content_notifier.setSocket(new_client->socket().fd().value()); 575 m_web_content_notifier.setEnabled(true); 576 577 QObject::connect(&m_web_content_notifier, &QSocketNotifier::activated, [new_client = new_client.ptr()] { 578 if (auto notifier = new_client->socket().notifier()) 579 notifier->on_ready_to_read(); 580 }); 581 582 struct DeferredInvokerQt final : IPC::DeferredInvoker { 583 virtual ~DeferredInvokerQt() = default; 584 virtual void schedule(Function<void()> callback) override 585 { 586 QTimer::singleShot(0, std::move(callback)); 587 } 588 }; 589 590 new_client->set_deferred_invoker(make<DeferredInvokerQt>()); 591 592 m_client_state.client = new_client; 593 m_client_state.client->on_web_content_process_crash = [this] { 594 QTimer::singleShot(0, [this] { 595 handle_web_content_process_crash(); 596 }); 597 }; 598 599 client().async_set_device_pixels_per_css_pixel(m_device_pixel_ratio); 600 client().async_update_system_theme(MUST(Gfx::load_system_theme(DeprecatedString::formatted("{}/res/themes/Default.ini", s_serenity_resource_root)))); 601 client().async_update_system_fonts(Gfx::FontDatabase::default_font_query(), Gfx::FontDatabase::fixed_width_font_query(), Gfx::FontDatabase::window_title_font_query()); 602 603 // FIXME: Get the screen rect. 604 // client().async_update_screen_rects(GUI::Desktop::the().rects(), GUI::Desktop::the().main_screen_index()); 605} 606 607void WebContentView::handle_web_content_process_crash() 608{ 609 dbgln("WebContent process crashed!"); 610 create_client(); 611 VERIFY(m_client_state.client); 612 613 // Don't keep a stale backup bitmap around. 614 m_backup_bitmap = nullptr; 615 616 handle_resize(); 617 StringBuilder builder; 618 builder.append("<html><head><title>Crashed: "sv); 619 builder.append(escape_html_entities(m_url.to_deprecated_string())); 620 builder.append("</title></head><body>"sv); 621 builder.append("<h1>Web page crashed"sv); 622 if (!m_url.host().is_empty()) { 623 builder.appendff(" on {}", escape_html_entities(m_url.host())); 624 } 625 builder.append("</h1>"sv); 626 auto escaped_url = escape_html_entities(m_url.to_deprecated_string()); 627 builder.appendff("The web page <a href=\"{}\">{}</a> has crashed.<br><br>You can reload the page to try again.", escaped_url, escaped_url); 628 builder.append("</body></html>"sv); 629 load_html(builder.to_deprecated_string(), m_url); 630} 631 632void WebContentView::notify_server_did_paint(Badge<WebContentClient>, i32 bitmap_id) 633{ 634 if (m_client_state.back_bitmap.id == bitmap_id) { 635 m_client_state.has_usable_bitmap = true; 636 m_client_state.back_bitmap.pending_paints--; 637 swap(m_client_state.back_bitmap, m_client_state.front_bitmap); 638 // We don't need the backup bitmap anymore, so drop it. 639 m_backup_bitmap = nullptr; 640 viewport()->update(); 641 642 if (m_client_state.got_repaint_requests_while_painting) { 643 m_client_state.got_repaint_requests_while_painting = false; 644 request_repaint(); 645 } 646 } 647} 648 649void WebContentView::notify_server_did_invalidate_content_rect(Badge<WebContentClient>, [[maybe_unused]] Gfx::IntRect const& content_rect) 650{ 651 request_repaint(); 652} 653 654void WebContentView::notify_server_did_change_selection(Badge<WebContentClient>) 655{ 656 request_repaint(); 657} 658 659void WebContentView::notify_server_did_request_cursor_change(Badge<WebContentClient>, Gfx::StandardCursor cursor) 660{ 661 switch (cursor) { 662 case Gfx::StandardCursor::Hand: 663 setCursor(Qt::PointingHandCursor); 664 break; 665 case Gfx::StandardCursor::IBeam: 666 setCursor(Qt::IBeamCursor); 667 break; 668 case Gfx::StandardCursor::Arrow: 669 default: 670 setCursor(Qt::ArrowCursor); 671 break; 672 } 673} 674 675void WebContentView::notify_server_did_layout(Badge<WebContentClient>, Gfx::IntSize content_size) 676{ 677 verticalScrollBar()->setMinimum(0); 678 verticalScrollBar()->setMaximum(content_size.height() - m_viewport_rect.height()); 679 verticalScrollBar()->setPageStep(m_viewport_rect.height()); 680 horizontalScrollBar()->setMinimum(0); 681 horizontalScrollBar()->setMaximum(content_size.width() - m_viewport_rect.width()); 682 horizontalScrollBar()->setPageStep(m_viewport_rect.width()); 683} 684 685void WebContentView::notify_server_did_change_title(Badge<WebContentClient>, DeprecatedString const& title) 686{ 687 emit title_changed(qstring_from_ak_deprecated_string(title)); 688} 689 690void WebContentView::notify_server_did_request_scroll(Badge<WebContentClient>, i32 x_delta, i32 y_delta) 691{ 692 horizontalScrollBar()->setValue(horizontalScrollBar()->value() + x_delta); 693 verticalScrollBar()->setValue(verticalScrollBar()->value() + y_delta); 694} 695 696void WebContentView::notify_server_did_request_scroll_to(Badge<WebContentClient>, Gfx::IntPoint scroll_position) 697{ 698 horizontalScrollBar()->setValue(scroll_position.x()); 699 verticalScrollBar()->setValue(scroll_position.y()); 700} 701 702void WebContentView::notify_server_did_request_scroll_into_view(Badge<WebContentClient>, Gfx::IntRect const& rect) 703{ 704 if (m_viewport_rect.contains(rect)) 705 return; 706 707 if (rect.top() < m_viewport_rect.top()) { 708 verticalScrollBar()->setValue(rect.top()); 709 } else if (rect.top() > m_viewport_rect.top() && rect.bottom() > m_viewport_rect.bottom()) { 710 verticalScrollBar()->setValue(rect.bottom() - m_viewport_rect.height() + 1); 711 } 712} 713 714void WebContentView::notify_server_did_enter_tooltip_area(Badge<WebContentClient>, Gfx::IntPoint content_position, DeprecatedString const& tooltip) 715{ 716 auto widget_position = to_widget(content_position); 717 QToolTip::showText( 718 mapToGlobal(QPoint(widget_position.x(), widget_position.y())), 719 qstring_from_ak_deprecated_string(tooltip), 720 this); 721} 722 723void WebContentView::notify_server_did_leave_tooltip_area(Badge<WebContentClient>) 724{ 725 QToolTip::hideText(); 726} 727 728void WebContentView::notify_server_did_hover_link(Badge<WebContentClient>, AK::URL const& url) 729{ 730 emit link_hovered(qstring_from_ak_deprecated_string(url.to_deprecated_string())); 731} 732 733void WebContentView::notify_server_did_unhover_link(Badge<WebContentClient>) 734{ 735 emit link_unhovered(); 736} 737 738void WebContentView::notify_server_did_click_link(Badge<WebContentClient>, AK::URL const& url, DeprecatedString const& target, unsigned int modifiers) 739{ 740 // FIXME 741 (void)url; 742 (void)target; 743 (void)modifiers; 744 // if (on_link_click) 745 // on_link_click(url, target, modifiers); 746} 747 748void WebContentView::notify_server_did_middle_click_link(Badge<WebContentClient>, AK::URL const& url, DeprecatedString const& target, unsigned int modifiers) 749{ 750 (void)url; 751 (void)target; 752 (void)modifiers; 753} 754 755void WebContentView::notify_server_did_start_loading(Badge<WebContentClient>, AK::URL const& url, bool is_redirect) 756{ 757 m_url = url; 758 emit load_started(url, is_redirect); 759 if (m_inspector_widget) 760 m_inspector_widget->clear_dom_json(); 761} 762 763void WebContentView::notify_server_did_finish_loading(Badge<WebContentClient>, AK::URL const& url) 764{ 765 m_url = url; 766 if (is_inspector_open()) { 767 inspect_dom_tree(); 768 inspect_accessibility_tree(); 769 } 770 if (on_load_finish) 771 on_load_finish(url); 772} 773 774void WebContentView::notify_server_did_request_navigate_back(Badge<WebContentClient>) 775{ 776 emit navigate_back(); 777} 778 779void WebContentView::notify_server_did_request_navigate_forward(Badge<WebContentClient>) 780{ 781 emit navigate_forward(); 782} 783 784void WebContentView::notify_server_did_request_refresh(Badge<WebContentClient>) 785{ 786 emit refresh(); 787} 788 789void WebContentView::notify_server_did_request_context_menu(Badge<WebContentClient>, Gfx::IntPoint content_position) 790{ 791 // FIXME 792 (void)content_position; 793} 794 795void WebContentView::notify_server_did_request_link_context_menu(Badge<WebContentClient>, Gfx::IntPoint content_position, AK::URL const& url, DeprecatedString const&, unsigned) 796{ 797 // FIXME 798 (void)content_position; 799 (void)url; 800} 801 802void WebContentView::notify_server_did_request_image_context_menu(Badge<WebContentClient>, Gfx::IntPoint content_position, AK::URL const& url, DeprecatedString const&, unsigned, Gfx::ShareableBitmap const& bitmap) 803{ 804 // FIXME 805 (void)content_position; 806 (void)url; 807 (void)bitmap; 808} 809 810void WebContentView::notify_server_did_request_alert(Badge<WebContentClient>, DeprecatedString const& message) 811{ 812 m_dialog = new QMessageBox(QMessageBox::Icon::Warning, "Ladybird", qstring_from_ak_deprecated_string(message), QMessageBox::StandardButton::Ok, this); 813 m_dialog->exec(); 814 815 client().async_alert_closed(); 816 m_dialog = nullptr; 817} 818 819void WebContentView::notify_server_did_request_confirm(Badge<WebContentClient>, DeprecatedString const& message) 820{ 821 m_dialog = new QMessageBox(QMessageBox::Icon::Question, "Ladybird", qstring_from_ak_deprecated_string(message), QMessageBox::StandardButton::Ok | QMessageBox::StandardButton::Cancel, this); 822 auto result = m_dialog->exec(); 823 824 client().async_confirm_closed(result == QMessageBox::StandardButton::Ok || result == QDialog::Accepted); 825 m_dialog = nullptr; 826} 827 828void WebContentView::notify_server_did_request_prompt(Badge<WebContentClient>, DeprecatedString const& message, DeprecatedString const& default_) 829{ 830 m_dialog = new QInputDialog(this); 831 auto& dialog = static_cast<QInputDialog&>(*m_dialog); 832 833 dialog.setWindowTitle("Ladybird"); 834 dialog.setLabelText(qstring_from_ak_deprecated_string(message)); 835 dialog.setTextValue(qstring_from_ak_deprecated_string(default_)); 836 837 if (dialog.exec() == QDialog::Accepted) 838 client().async_prompt_closed(ak_deprecated_string_from_qstring(dialog.textValue())); 839 else 840 client().async_prompt_closed({}); 841 842 m_dialog = nullptr; 843} 844 845void WebContentView::notify_server_did_request_set_prompt_text(Badge<WebContentClient>, DeprecatedString const& message) 846{ 847 if (m_dialog && is<QInputDialog>(*m_dialog)) 848 static_cast<QInputDialog&>(*m_dialog).setTextValue(qstring_from_ak_deprecated_string(message)); 849} 850 851void WebContentView::notify_server_did_request_accept_dialog(Badge<WebContentClient>) 852{ 853 if (m_dialog) 854 m_dialog->accept(); 855} 856 857void WebContentView::notify_server_did_request_dismiss_dialog(Badge<WebContentClient>) 858{ 859 if (m_dialog) 860 m_dialog->reject(); 861} 862 863void WebContentView::notify_server_did_get_source(AK::URL const& url, DeprecatedString const& source) 864{ 865 emit got_source(url, qstring_from_ak_deprecated_string(source)); 866} 867 868void WebContentView::notify_server_did_get_dom_tree(DeprecatedString const& dom_tree) 869{ 870 if (on_get_dom_tree) 871 on_get_dom_tree(dom_tree); 872 if (m_inspector_widget) 873 m_inspector_widget->set_dom_json(dom_tree); 874} 875 876void WebContentView::notify_server_did_get_dom_node_properties(i32 node_id, DeprecatedString const& specified_style, DeprecatedString const& computed_style, DeprecatedString const& custom_properties, DeprecatedString const& node_box_sizing) 877{ 878 if (on_get_dom_node_properties) 879 on_get_dom_node_properties(node_id, specified_style, computed_style, custom_properties, node_box_sizing); 880} 881 882void WebContentView::notify_server_did_output_js_console_message(i32 message_index) 883{ 884 if (m_console_widget) 885 m_console_widget->notify_about_new_console_message(message_index); 886} 887 888void WebContentView::notify_server_did_get_js_console_messages(i32 start_index, Vector<DeprecatedString> const& message_types, Vector<DeprecatedString> const& messages) 889{ 890 if (m_console_widget) 891 m_console_widget->handle_console_messages(start_index, message_types, messages); 892} 893 894void WebContentView::notify_server_did_change_favicon(Gfx::Bitmap const& bitmap) 895{ 896 auto qimage = QImage(bitmap.scanline_u8(0), bitmap.width(), bitmap.height(), QImage::Format_ARGB32); 897 if (qimage.isNull()) 898 return; 899 auto qpixmap = QPixmap::fromImage(qimage); 900 if (qpixmap.isNull()) 901 return; 902 emit favicon_changed(QIcon(qpixmap)); 903} 904 905Vector<Web::Cookie::Cookie> WebContentView::notify_server_did_request_all_cookies(Badge<WebContentClient>, AK::URL const& url) 906{ 907 if (on_get_all_cookies) 908 return on_get_all_cookies(url); 909 return {}; 910} 911 912Optional<Web::Cookie::Cookie> WebContentView::notify_server_did_request_named_cookie(Badge<WebContentClient>, AK::URL const& url, DeprecatedString const& name) 913{ 914 if (on_get_named_cookie) 915 return on_get_named_cookie(url, name); 916 return {}; 917} 918 919DeprecatedString WebContentView::notify_server_did_request_cookie(Badge<WebContentClient>, AK::URL const& url, Web::Cookie::Source source) 920{ 921 if (on_get_cookie) 922 return on_get_cookie(url, source); 923 return {}; 924} 925 926void WebContentView::notify_server_did_set_cookie(Badge<WebContentClient>, AK::URL const& url, Web::Cookie::ParsedCookie const& cookie, Web::Cookie::Source source) 927{ 928 if (on_set_cookie) 929 on_set_cookie(url, cookie, source); 930} 931 932void WebContentView::notify_server_did_close_browsing_context(Badge<WebContentClient>) 933{ 934 emit close(); 935} 936 937void WebContentView::notify_server_did_update_cookie(Badge<WebContentClient>, Web::Cookie::Cookie const& cookie) 938{ 939 if (on_update_cookie) 940 on_update_cookie(cookie); 941} 942 943void WebContentView::notify_server_did_update_resource_count(i32 count_waiting) 944{ 945 // FIXME 946 (void)count_waiting; 947} 948 949void WebContentView::notify_server_did_request_restore_window() 950{ 951 emit restore_window(); 952} 953 954Gfx::IntPoint WebContentView::notify_server_did_request_reposition_window(Gfx::IntPoint position) 955{ 956 return emit reposition_window(position); 957} 958 959Gfx::IntSize WebContentView::notify_server_did_request_resize_window(Gfx::IntSize size) 960{ 961 return emit resize_window(size); 962} 963 964Gfx::IntRect WebContentView::notify_server_did_request_maximize_window() 965{ 966 return emit maximize_window(); 967} 968 969Gfx::IntRect WebContentView::notify_server_did_request_minimize_window() 970{ 971 return emit minimize_window(); 972} 973 974Gfx::IntRect WebContentView::notify_server_did_request_fullscreen_window() 975{ 976 return emit fullscreen_window(); 977} 978 979void WebContentView::notify_server_did_request_file(Badge<WebContentClient>, DeprecatedString const& path, i32 request_id) 980{ 981 auto file = Core::File::open(path, Core::File::OpenMode::Read); 982 if (file.is_error()) 983 client().async_handle_file_return(file.error().code(), {}, request_id); 984 else 985 client().async_handle_file_return(0, IPC::File(*file.value()), request_id); 986} 987 988void WebContentView::request_repaint() 989{ 990 // If this widget was instantiated but not yet added to a window, 991 // it won't have a back bitmap yet, so we can just skip repaint requests. 992 if (!m_client_state.back_bitmap.bitmap) 993 return; 994 // Don't request a repaint until pending paint requests have finished. 995 if (m_client_state.back_bitmap.pending_paints) { 996 m_client_state.got_repaint_requests_while_painting = true; 997 return; 998 } 999 m_client_state.back_bitmap.pending_paints++; 1000 client().async_paint(m_client_state.back_bitmap.bitmap->rect().translated(horizontalScrollBar()->value(), verticalScrollBar()->value()), m_client_state.back_bitmap.id); 1001} 1002 1003bool WebContentView::event(QEvent* event) 1004{ 1005 // NOTE: We have to implement event() manually as Qt's focus navigation mechanism 1006 // eats all the Tab key presses by default. 1007 1008 if (event->type() == QEvent::KeyPress) { 1009 keyPressEvent(static_cast<QKeyEvent*>(event)); 1010 return true; 1011 } 1012 if (event->type() == QEvent::KeyRelease) { 1013 keyReleaseEvent(static_cast<QKeyEvent*>(event)); 1014 return true; 1015 } 1016 return QAbstractScrollArea::event(event); 1017} 1018 1019void WebContentView::notify_server_did_finish_handling_input_event(bool event_was_accepted) 1020{ 1021 // FIXME: Currently Ladybird handles the keyboard shortcuts before passing the event to web content, so 1022 // we don't need to do anything here. But we'll need to once we start asking web content first. 1023 (void)event_was_accepted; 1024} 1025 1026void WebContentView::notify_server_did_get_accessibility_tree(DeprecatedString const& accessibility_json) 1027{ 1028 if (m_inspector_widget) 1029 m_inspector_widget->set_accessibility_json(accessibility_json); 1030} 1031 1032ErrorOr<String> WebContentView::dump_layout_tree() 1033{ 1034 return String::from_deprecated_string(client().dump_layout_tree()); 1035}