mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-09-11 05:25:32 +03:00
LibWeb+WebContent: Move scrollbar painting into WebContent
The main intention of this change is to have a consistent look and behavior across all scrollbars, including elements with `overflow: scroll` and `overflow: auto`, iframes, and a page. Before: - Page's scrollbar is painted by Browser (Qt/AppKit) using the corresponding UI framework style, - Both WebContent and Browser know the scroll position offset. - WebContent uses did_request_scroll_to() IPC call to send updates. - Browser uses set_viewport_rect() to send updates. After: - Page's scrollbar is painted on WebContent side using the same style as currently used for elements with `overflow: scroll` and `overflow: auto`. A nice side effects: scrollbars are now painted for iframes, and page's scrollbar respects scrollbar-width CSS property. - Only WebContent knows scroll position offset. - did_request_scroll_to() is no longer used. - set_viewport_rect() is changed to set_viewport_size().
This commit is contained in:
parent
eb909118bf
commit
5285e22f2a
Notes:
sideshowbarker
2024-07-17 01:28:15 +09:00
Author: https://github.com/kalenikaliaksandr Commit: https://github.com/LadybirdBrowser/ladybird/commit/5285e22f2a Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/21
@ -351,22 +351,6 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_
|
||||
self.event_being_redispatched = nil;
|
||||
};
|
||||
|
||||
m_web_view_bridge->on_scroll = [self](auto position) {
|
||||
auto content_rect = [self frame];
|
||||
auto document_rect = [[self documentView] frame];
|
||||
auto ns_position = Ladybird::gfx_point_to_ns_point(position);
|
||||
|
||||
ns_position.x = max(ns_position.x, document_rect.origin.x);
|
||||
ns_position.x = min(ns_position.x, document_rect.size.width - content_rect.size.width);
|
||||
|
||||
ns_position.y = max(ns_position.y, document_rect.origin.y);
|
||||
ns_position.y = min(ns_position.y, document_rect.size.height - content_rect.size.height);
|
||||
|
||||
[self scrollToPoint:ns_position];
|
||||
[[self scrollView] reflectScrolledClipView:self];
|
||||
[self updateViewportRect:Ladybird::WebViewBridge::ForResize::No];
|
||||
};
|
||||
|
||||
m_web_view_bridge->on_cursor_change = [self](auto cursor) {
|
||||
if (cursor == Gfx::StandardCursor::Hidden) {
|
||||
if (!m_hidden_cursor.has_value()) {
|
||||
|
@ -35,20 +35,6 @@ WebViewBridge::WebViewBridge(Vector<Web::DevicePixelRect> screen_rects, float de
|
||||
, m_preferred_color_scheme(preferred_color_scheme)
|
||||
{
|
||||
m_device_pixel_ratio = device_pixel_ratio;
|
||||
|
||||
on_scroll_by_delta = [this](auto x_delta, auto y_delta) {
|
||||
auto position = m_viewport_rect.location();
|
||||
position.set_x(position.x() + x_delta);
|
||||
position.set_y(position.y() + y_delta);
|
||||
|
||||
if (on_scroll_to_point)
|
||||
on_scroll_to_point(position);
|
||||
};
|
||||
|
||||
on_scroll_to_point = [this](auto position) {
|
||||
if (on_scroll)
|
||||
on_scroll(to_widget_position(position));
|
||||
};
|
||||
}
|
||||
|
||||
WebViewBridge::~WebViewBridge() = default;
|
||||
@ -67,9 +53,9 @@ void WebViewBridge::set_system_visibility_state(bool is_visible)
|
||||
void WebViewBridge::set_viewport_rect(Gfx::IntRect viewport_rect, ForResize for_resize)
|
||||
{
|
||||
viewport_rect.set_size(scale_for_device(viewport_rect.size(), m_device_pixel_ratio));
|
||||
m_viewport_rect = viewport_rect;
|
||||
m_viewport_size = viewport_rect.size();
|
||||
|
||||
client().async_set_viewport_rect(m_client_state.page_index, m_viewport_rect.to_type<Web::DevicePixels>());
|
||||
client().async_set_viewport_size(m_client_state.page_index, m_viewport_size.to_type<Web::DevicePixels>());
|
||||
|
||||
if (for_resize == ForResize::Yes) {
|
||||
handle_resize();
|
||||
@ -126,9 +112,9 @@ void WebViewBridge::update_zoom()
|
||||
on_zoom_level_changed();
|
||||
}
|
||||
|
||||
Web::DevicePixelRect WebViewBridge::viewport_rect() const
|
||||
Web::DevicePixelSize WebViewBridge::viewport_size() const
|
||||
{
|
||||
return m_viewport_rect.to_type<Web::DevicePixels>();
|
||||
return m_viewport_size.to_type<Web::DevicePixels>();
|
||||
}
|
||||
|
||||
Gfx::IntPoint WebViewBridge::to_content_position(Gfx::IntPoint widget_position) const
|
||||
|
@ -53,18 +53,17 @@ public:
|
||||
|
||||
Function<NonnullRefPtr<WebView::WebContentClient>()> on_request_web_content;
|
||||
Function<void()> on_zoom_level_changed;
|
||||
Function<void(Gfx::IntPoint)> on_scroll;
|
||||
|
||||
private:
|
||||
WebViewBridge(Vector<Web::DevicePixelRect> screen_rects, float device_pixel_ratio, WebContentOptions const&, Optional<StringView> webdriver_content_ipc_path, Web::CSS::PreferredColorScheme);
|
||||
|
||||
virtual void update_zoom() override;
|
||||
virtual Web::DevicePixelRect viewport_rect() const override;
|
||||
virtual Web::DevicePixelSize viewport_size() const override;
|
||||
virtual Gfx::IntPoint to_content_position(Gfx::IntPoint widget_position) const override;
|
||||
virtual Gfx::IntPoint to_widget_position(Gfx::IntPoint content_position) const override;
|
||||
|
||||
Vector<Web::DevicePixelRect> m_screen_rects;
|
||||
Gfx::IntRect m_viewport_rect;
|
||||
Gfx::IntSize m_viewport_size;
|
||||
|
||||
WebContentOptions m_web_content_options;
|
||||
Optional<StringView> m_webdriver_content_ipc_path;
|
||||
|
@ -91,8 +91,8 @@ static constexpr CGFloat const WINDOW_HEIGHT = 800;
|
||||
[self.search_panel setHidden:YES];
|
||||
|
||||
auto* scroll_view = [[NSScrollView alloc] init];
|
||||
[scroll_view setHasVerticalScroller:YES];
|
||||
[scroll_view setHasHorizontalScroller:YES];
|
||||
[scroll_view setHasVerticalScroller:NO];
|
||||
[scroll_view setHasHorizontalScroller:NO];
|
||||
[scroll_view setLineScroll:24];
|
||||
|
||||
[scroll_view setContentView:self.web_view];
|
||||
|
@ -55,6 +55,9 @@ WebContentView::WebContentView(QWidget* window, WebContentOptions const& web_con
|
||||
, m_web_content_options(web_content_options)
|
||||
, m_webdriver_content_ipc_path(webdriver_content_ipc_path)
|
||||
{
|
||||
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
|
||||
m_client_state.client = parent_client;
|
||||
m_client_state.page_index = page_index;
|
||||
|
||||
@ -68,13 +71,6 @@ WebContentView::WebContentView(QWidget* window, WebContentOptions const& web_con
|
||||
verticalScrollBar()->setSingleStep(24);
|
||||
horizontalScrollBar()->setSingleStep(24);
|
||||
|
||||
QObject::connect(verticalScrollBar(), &QScrollBar::valueChanged, [this](int) {
|
||||
update_viewport_rect();
|
||||
});
|
||||
QObject::connect(horizontalScrollBar(), &QScrollBar::valueChanged, [this](int) {
|
||||
update_viewport_rect();
|
||||
});
|
||||
|
||||
QObject::connect(qGuiApp, &QGuiApplication::screenRemoved, [this](QScreen*) {
|
||||
update_screen_rects();
|
||||
});
|
||||
@ -85,29 +81,10 @@ WebContentView::WebContentView(QWidget* window, WebContentOptions const& web_con
|
||||
|
||||
initialize_client((parent_client == nullptr) ? CreateNewClient::Yes : CreateNewClient::No);
|
||||
|
||||
on_did_layout = [this](auto content_size) {
|
||||
verticalScrollBar()->setMinimum(0);
|
||||
verticalScrollBar()->setMaximum(content_size.height() - m_viewport_rect.height());
|
||||
verticalScrollBar()->setPageStep(m_viewport_rect.height());
|
||||
horizontalScrollBar()->setMinimum(0);
|
||||
horizontalScrollBar()->setMaximum(content_size.width() - m_viewport_rect.width());
|
||||
horizontalScrollBar()->setPageStep(m_viewport_rect.width());
|
||||
};
|
||||
|
||||
on_ready_to_paint = [this]() {
|
||||
viewport()->update();
|
||||
};
|
||||
|
||||
on_scroll_by_delta = [this](auto x_delta, auto y_delta) {
|
||||
horizontalScrollBar()->setValue(max(0, horizontalScrollBar()->value() + x_delta));
|
||||
verticalScrollBar()->setValue(max(0, verticalScrollBar()->value() + y_delta));
|
||||
};
|
||||
|
||||
on_scroll_to_point = [this](auto position) {
|
||||
horizontalScrollBar()->setValue(position.x());
|
||||
verticalScrollBar()->setValue(position.y());
|
||||
};
|
||||
|
||||
on_cursor_change = [this](auto cursor) {
|
||||
update_cursor(cursor);
|
||||
};
|
||||
@ -445,14 +422,14 @@ void WebContentView::paintEvent(QPaintEvent*)
|
||||
void WebContentView::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
QAbstractScrollArea::resizeEvent(event);
|
||||
update_viewport_rect();
|
||||
update_viewport_size();
|
||||
handle_resize();
|
||||
}
|
||||
|
||||
void WebContentView::set_viewport_rect(Gfx::IntRect rect)
|
||||
{
|
||||
m_viewport_rect = rect;
|
||||
client().async_set_viewport_rect(m_client_state.page_index, rect.to_type<Web::DevicePixels>());
|
||||
m_viewport_size = rect.size();
|
||||
client().async_set_viewport_size(m_client_state.page_index, rect.size().to_type<Web::DevicePixels>());
|
||||
}
|
||||
|
||||
void WebContentView::set_window_size(Gfx::IntSize size)
|
||||
@ -469,15 +446,15 @@ void WebContentView::set_device_pixel_ratio(double device_pixel_ratio)
|
||||
{
|
||||
m_device_pixel_ratio = device_pixel_ratio;
|
||||
client().async_set_device_pixels_per_css_pixel(m_client_state.page_index, m_device_pixel_ratio * m_zoom_level);
|
||||
update_viewport_rect();
|
||||
update_viewport_size();
|
||||
handle_resize();
|
||||
}
|
||||
|
||||
void WebContentView::update_viewport_rect()
|
||||
void WebContentView::update_viewport_size()
|
||||
{
|
||||
auto scaled_width = int(viewport()->width() * m_device_pixel_ratio);
|
||||
auto scaled_height = int(viewport()->height() * m_device_pixel_ratio);
|
||||
Gfx::IntRect rect(max(0, horizontalScrollBar()->value()), max(0, verticalScrollBar()->value()), scaled_width, scaled_height);
|
||||
Gfx::IntRect rect(0, 0, scaled_width, scaled_height);
|
||||
|
||||
set_viewport_rect(rect);
|
||||
}
|
||||
@ -485,7 +462,7 @@ void WebContentView::update_viewport_rect()
|
||||
void WebContentView::update_zoom()
|
||||
{
|
||||
client().async_set_device_pixels_per_css_pixel(m_client_state.page_index, m_device_pixel_ratio * m_zoom_level);
|
||||
update_viewport_rect();
|
||||
update_viewport_size();
|
||||
}
|
||||
|
||||
void WebContentView::showEvent(QShowEvent* event)
|
||||
@ -661,9 +638,9 @@ void WebContentView::update_cursor(Gfx::StandardCursor cursor)
|
||||
}
|
||||
}
|
||||
|
||||
Web::DevicePixelRect WebContentView::viewport_rect() const
|
||||
Web::DevicePixelSize WebContentView::viewport_size() const
|
||||
{
|
||||
return m_viewport_rect.to_type<Web::DevicePixels>();
|
||||
return m_viewport_size.to_type<Web::DevicePixels>();
|
||||
}
|
||||
|
||||
QPoint WebContentView::map_point_to_global_position(Gfx::IntPoint position) const
|
||||
@ -673,12 +650,12 @@ QPoint WebContentView::map_point_to_global_position(Gfx::IntPoint position) cons
|
||||
|
||||
Gfx::IntPoint WebContentView::to_content_position(Gfx::IntPoint widget_position) const
|
||||
{
|
||||
return widget_position.translated(max(0, horizontalScrollBar()->value()), max(0, verticalScrollBar()->value()));
|
||||
return widget_position;
|
||||
}
|
||||
|
||||
Gfx::IntPoint WebContentView::to_widget_position(Gfx::IntPoint content_position) const
|
||||
{
|
||||
return content_position.translated(-(max(0, horizontalScrollBar()->value())), -(max(0, verticalScrollBar()->value())));
|
||||
return content_position;
|
||||
}
|
||||
|
||||
bool WebContentView::event(QEvent* event)
|
||||
@ -715,7 +692,7 @@ ErrorOr<String> WebContentView::dump_layout_tree()
|
||||
|
||||
void WebContentView::enqueue_native_event(Web::MouseEvent::Type type, QSinglePointEvent const& event)
|
||||
{
|
||||
auto position = to_content_position({ event.position().x() * m_device_pixel_ratio, event.position().y() * m_device_pixel_ratio });
|
||||
Web::DevicePixelPoint position = { event.position().x() * m_device_pixel_ratio, event.position().y() * m_device_pixel_ratio };
|
||||
auto screen_position = Gfx::IntPoint { event.globalPosition().x() * m_device_pixel_ratio, event.globalPosition().y() * m_device_pixel_ratio };
|
||||
|
||||
auto button = get_button_from_qt_event(event);
|
||||
@ -751,7 +728,7 @@ void WebContentView::enqueue_native_event(Web::MouseEvent::Type type, QSinglePoi
|
||||
}
|
||||
}
|
||||
|
||||
enqueue_input_event(Web::MouseEvent { type, position.to_type<Web::DevicePixels>(), screen_position.to_type<Web::DevicePixels>(), button, buttons, modifiers, wheel_delta_x, wheel_delta_y, nullptr });
|
||||
enqueue_input_event(Web::MouseEvent { type, position, screen_position.to_type<Web::DevicePixels>(), button, buttons, modifiers, wheel_delta_x, wheel_delta_y, nullptr });
|
||||
}
|
||||
|
||||
struct KeyData : Web::ChromeInputData {
|
||||
|
@ -90,11 +90,11 @@ private:
|
||||
// ^WebView::ViewImplementation
|
||||
virtual void initialize_client(CreateNewClient) override;
|
||||
virtual void update_zoom() override;
|
||||
virtual Web::DevicePixelRect viewport_rect() const override;
|
||||
virtual Web::DevicePixelSize viewport_size() const override;
|
||||
virtual Gfx::IntPoint to_content_position(Gfx::IntPoint widget_position) const override;
|
||||
virtual Gfx::IntPoint to_widget_position(Gfx::IntPoint content_position) const override;
|
||||
|
||||
void update_viewport_rect();
|
||||
void update_viewport_size();
|
||||
void update_cursor(Gfx::StandardCursor cursor);
|
||||
|
||||
void enqueue_native_event(Web::MouseEvent::Type, QSinglePointEvent const& event);
|
||||
@ -104,7 +104,7 @@ private:
|
||||
|
||||
bool m_should_show_line_box_borders { false };
|
||||
|
||||
Gfx::IntRect m_viewport_rect;
|
||||
Gfx::IntSize m_viewport_size;
|
||||
|
||||
WebContentOptions m_web_content_options;
|
||||
StringView m_webdriver_content_ipc_path;
|
||||
|
@ -7,6 +7,10 @@
|
||||
<title>Document</title>
|
||||
|
||||
<style>
|
||||
html {
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
p, .container {
|
||||
border: 0.8em darkviolet;
|
||||
border-style: dotted double;
|
||||
|
@ -5,6 +5,9 @@
|
||||
body {
|
||||
background-color: white;
|
||||
}
|
||||
html {
|
||||
scrollbar-width: none;
|
||||
}
|
||||
</style>
|
||||
<!-- To rebase:
|
||||
1. Open background-clip-text.html in Ladybird
|
||||
|
@ -11,7 +11,10 @@
|
||||
<script>
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.srcdoc = `
|
||||
<style>body { margin: 0 }</style>
|
||||
<style>
|
||||
body { margin: 0 }
|
||||
html { scrollbar-width: none; }
|
||||
</style>
|
||||
<div style="width: 200px; height: 200px; background-color: darkblue"></div>
|
||||
<div style="width: 200px; height: 200px; background-color: blue"></div>
|
||||
<div style="width: 200px; height: 200px; background-color: magenta"></div>
|
||||
|
@ -1146,6 +1146,11 @@ void Document::update_layout()
|
||||
paintable()->recompute_selection_states();
|
||||
|
||||
m_needs_layout = false;
|
||||
|
||||
// Scrolling by zero offset will clamp scroll offset back to valid range if it was out of bounds
|
||||
// after the viewport size change.
|
||||
if (auto window = this->window())
|
||||
window->scroll_by(0, 0);
|
||||
}
|
||||
|
||||
[[nodiscard]] static CSS::RequiredInvalidationAfterStyleChange update_style_recursively(Node& node, CSS::StyleComputer& style_computer)
|
||||
|
@ -890,7 +890,7 @@ JS::NonnullGCPtr<Geometry::DOMRectList> Element::get_client_rects() const
|
||||
|
||||
if (auto const* paintable_box = this->paintable_box()) {
|
||||
transform = Gfx::extract_2d_affine_transform(paintable_box->transform());
|
||||
for (auto const* containing_block = paintable->containing_block(); containing_block; containing_block = containing_block->containing_block()) {
|
||||
for (auto const* containing_block = paintable->containing_block(); !containing_block->is_viewport(); containing_block = containing_block->containing_block()) {
|
||||
transform = Gfx::extract_2d_affine_transform(containing_block->transform()).multiply(transform);
|
||||
scroll_offset.translate_by(containing_block->scroll_offset());
|
||||
}
|
||||
|
@ -1993,59 +1993,35 @@ CSSPixelPoint Navigable::to_top_level_position(CSSPixelPoint a_position)
|
||||
return position;
|
||||
}
|
||||
|
||||
void Navigable::set_viewport_rect(CSSPixelRect const& rect)
|
||||
void Navigable::set_viewport_size(CSSPixelSize size)
|
||||
{
|
||||
bool did_change = false;
|
||||
if (m_size == size)
|
||||
return;
|
||||
|
||||
if (m_size != rect.size()) {
|
||||
m_size = rect.size();
|
||||
m_size = size;
|
||||
if (auto document = active_document()) {
|
||||
// NOTE: Resizing the viewport changes the reference value for viewport-relative CSS lengths.
|
||||
document->invalidate_style();
|
||||
document->set_needs_layout();
|
||||
}
|
||||
did_change = true;
|
||||
m_needs_repaint = true;
|
||||
}
|
||||
|
||||
if (m_viewport_scroll_offset != rect.location()) {
|
||||
m_viewport_scroll_offset = rect.location();
|
||||
scroll_offset_did_change();
|
||||
did_change = true;
|
||||
m_needs_repaint = true;
|
||||
}
|
||||
|
||||
if (did_change && active_document()) {
|
||||
active_document()->inform_all_viewport_clients_about_the_current_viewport_rect();
|
||||
}
|
||||
if (auto document = active_document()) {
|
||||
document->inform_all_viewport_clients_about_the_current_viewport_rect();
|
||||
|
||||
// Schedule the HTML event loop to ensure that a `resize` event gets fired.
|
||||
HTML::main_thread_event_loop().schedule();
|
||||
}
|
||||
|
||||
void Navigable::perform_scroll_of_viewport(CSSPixelPoint position)
|
||||
{
|
||||
auto viewport_rect = this->viewport_rect();
|
||||
viewport_rect.set_location(position);
|
||||
set_viewport_rect(viewport_rect);
|
||||
set_needs_display();
|
||||
|
||||
if (is_traversable() && active_browsing_context())
|
||||
active_browsing_context()->page().client().page_did_request_scroll_to(position);
|
||||
}
|
||||
|
||||
void Navigable::set_size(CSSPixelSize size)
|
||||
{
|
||||
if (m_size == size)
|
||||
return;
|
||||
m_size = size;
|
||||
|
||||
if (auto document = active_document()) {
|
||||
document->invalidate_style();
|
||||
document->set_needs_layout();
|
||||
}
|
||||
}
|
||||
|
||||
if (auto document = active_document()) {
|
||||
void Navigable::perform_scroll_of_viewport(CSSPixelPoint new_position)
|
||||
{
|
||||
if (m_viewport_scroll_offset != new_position) {
|
||||
m_viewport_scroll_offset = new_position;
|
||||
scroll_offset_did_change();
|
||||
m_needs_repaint = true;
|
||||
|
||||
if (auto document = active_document())
|
||||
document->inform_all_viewport_clients_about_the_current_viewport_rect();
|
||||
}
|
||||
|
||||
|
@ -160,11 +160,10 @@ public:
|
||||
CSSPixelRect to_top_level_rect(CSSPixelRect const&);
|
||||
|
||||
CSSPixelSize size() const { return m_size; }
|
||||
void set_size(CSSPixelSize);
|
||||
|
||||
CSSPixelPoint viewport_scroll_offset() const { return m_viewport_scroll_offset; }
|
||||
CSSPixelRect viewport_rect() const { return { m_viewport_scroll_offset, m_size }; }
|
||||
void set_viewport_rect(CSSPixelRect const&);
|
||||
void set_viewport_size(CSSPixelSize);
|
||||
void perform_scroll_of_viewport(CSSPixelPoint position);
|
||||
|
||||
void set_needs_display();
|
||||
|
@ -32,7 +32,7 @@ void FrameBox::did_set_content_size()
|
||||
ReplacedBox::did_set_content_size();
|
||||
|
||||
if (dom_node().content_navigable())
|
||||
dom_node().content_navigable()->set_size(paintable_box()->content_size());
|
||||
dom_node().content_navigable()->set_viewport_size(paintable_box()->content_size());
|
||||
}
|
||||
|
||||
JS::GCPtr<Painting::Paintable> FrameBox::create_paintable() const
|
||||
|
@ -151,13 +151,16 @@ Painting::PaintableBox const* EventHandler::paint_root() const
|
||||
return m_navigable->active_document()->paintable_box();
|
||||
}
|
||||
|
||||
bool EventHandler::handle_mousewheel(CSSPixelPoint position, CSSPixelPoint screen_position, u32 button, u32 buttons, u32 modifiers, int wheel_delta_x, int wheel_delta_y)
|
||||
bool EventHandler::handle_mousewheel(CSSPixelPoint viewport_position, CSSPixelPoint screen_position, u32 button, u32 buttons, u32 modifiers, int wheel_delta_x, int wheel_delta_y)
|
||||
{
|
||||
if (!m_navigable->active_document())
|
||||
return false;
|
||||
if (!m_navigable->active_document()->is_fully_active())
|
||||
return false;
|
||||
|
||||
auto scroll_offset = m_navigable->active_document()->navigable()->viewport_scroll_offset();
|
||||
auto position = viewport_position.translated(scroll_offset);
|
||||
|
||||
m_navigable->active_document()->update_layout();
|
||||
|
||||
if (!paint_root())
|
||||
@ -214,13 +217,16 @@ bool EventHandler::handle_mousewheel(CSSPixelPoint position, CSSPixelPoint scree
|
||||
return handled_event;
|
||||
}
|
||||
|
||||
bool EventHandler::handle_mouseup(CSSPixelPoint position, CSSPixelPoint screen_position, u32 button, u32 buttons, u32 modifiers)
|
||||
bool EventHandler::handle_mouseup(CSSPixelPoint viewport_position, CSSPixelPoint screen_position, u32 button, u32 buttons, u32 modifiers)
|
||||
{
|
||||
if (!m_navigable->active_document())
|
||||
return false;
|
||||
if (!m_navigable->active_document()->is_fully_active())
|
||||
return false;
|
||||
|
||||
auto scroll_offset = m_navigable->active_document()->navigable()->viewport_scroll_offset();
|
||||
auto position = viewport_position.translated(scroll_offset);
|
||||
|
||||
m_navigable->active_document()->update_layout();
|
||||
|
||||
if (!paint_root())
|
||||
@ -292,8 +298,6 @@ bool EventHandler::handle_mouseup(CSSPixelPoint position, CSSPixelPoint screen_p
|
||||
//
|
||||
// https://html.spec.whatwg.org/multipage/document-sequences.html#the-rules-for-choosing-a-navigable
|
||||
|
||||
auto top_level_position = m_navigable->active_document()->navigable()->to_top_level_position(position);
|
||||
|
||||
if (JS::GCPtr<HTML::HTMLAnchorElement const> link = node->enclosing_link_element()) {
|
||||
JS::NonnullGCPtr<DOM::Document> document = *m_navigable->active_document();
|
||||
auto href = link->href();
|
||||
@ -302,13 +306,13 @@ bool EventHandler::handle_mouseup(CSSPixelPoint position, CSSPixelPoint screen_p
|
||||
if (button == UIEvents::MouseButton::Middle) {
|
||||
m_navigable->page().client().page_did_middle_click_link(url, link->target().to_byte_string(), modifiers);
|
||||
} else if (button == UIEvents::MouseButton::Secondary) {
|
||||
m_navigable->page().client().page_did_request_link_context_menu(top_level_position, url, link->target().to_byte_string(), modifiers);
|
||||
m_navigable->page().client().page_did_request_link_context_menu(viewport_position, url, link->target().to_byte_string(), modifiers);
|
||||
}
|
||||
} else if (button == UIEvents::MouseButton::Secondary) {
|
||||
if (is<HTML::HTMLImageElement>(*node)) {
|
||||
auto& image_element = verify_cast<HTML::HTMLImageElement>(*node);
|
||||
auto image_url = image_element.document().parse_url(image_element.src());
|
||||
m_navigable->page().client().page_did_request_image_context_menu(top_level_position, image_url, "", modifiers, image_element.bitmap());
|
||||
m_navigable->page().client().page_did_request_image_context_menu(viewport_position, image_url, "", modifiers, image_element.bitmap());
|
||||
} else if (is<HTML::HTMLMediaElement>(*node)) {
|
||||
auto& media_element = verify_cast<HTML::HTMLMediaElement>(*node);
|
||||
|
||||
@ -321,9 +325,9 @@ bool EventHandler::handle_mouseup(CSSPixelPoint position, CSSPixelPoint screen_p
|
||||
.is_looping = media_element.has_attribute(HTML::AttributeNames::loop),
|
||||
};
|
||||
|
||||
m_navigable->page().did_request_media_context_menu(media_element.unique_id(), top_level_position, "", modifiers, move(menu));
|
||||
m_navigable->page().did_request_media_context_menu(media_element.unique_id(), viewport_position, "", modifiers, move(menu));
|
||||
} else {
|
||||
m_navigable->page().client().page_did_request_context_menu(top_level_position);
|
||||
m_navigable->page().client().page_did_request_context_menu(viewport_position);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -336,13 +340,16 @@ after_node_use:
|
||||
return handled_event;
|
||||
}
|
||||
|
||||
bool EventHandler::handle_mousedown(CSSPixelPoint position, CSSPixelPoint screen_position, u32 button, u32 buttons, u32 modifiers)
|
||||
bool EventHandler::handle_mousedown(CSSPixelPoint viewport_position, CSSPixelPoint screen_position, u32 button, u32 buttons, u32 modifiers)
|
||||
{
|
||||
if (!m_navigable->active_document())
|
||||
return false;
|
||||
if (!m_navigable->active_document()->is_fully_active())
|
||||
return false;
|
||||
|
||||
auto scroll_offset = m_navigable->active_document()->navigable()->viewport_scroll_offset();
|
||||
auto position = viewport_position.translated(scroll_offset);
|
||||
|
||||
m_navigable->active_document()->update_layout();
|
||||
|
||||
if (!paint_root())
|
||||
@ -436,13 +443,16 @@ bool EventHandler::handle_mousedown(CSSPixelPoint position, CSSPixelPoint screen
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EventHandler::handle_mousemove(CSSPixelPoint position, CSSPixelPoint screen_position, u32 buttons, u32 modifiers)
|
||||
bool EventHandler::handle_mousemove(CSSPixelPoint viewport_position, CSSPixelPoint screen_position, u32 buttons, u32 modifiers)
|
||||
{
|
||||
if (!m_navigable->active_document())
|
||||
return false;
|
||||
if (!m_navigable->active_document()->is_fully_active())
|
||||
return false;
|
||||
|
||||
auto scroll_offset = m_navigable->active_document()->navigable()->viewport_scroll_offset();
|
||||
auto position = viewport_position.translated(scroll_offset);
|
||||
|
||||
m_navigable->active_document()->update_layout();
|
||||
|
||||
if (!paint_root())
|
||||
@ -562,13 +572,16 @@ bool EventHandler::handle_mousemove(CSSPixelPoint position, CSSPixelPoint screen
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EventHandler::handle_doubleclick(CSSPixelPoint position, CSSPixelPoint screen_position, u32 button, u32 buttons, u32 modifiers)
|
||||
bool EventHandler::handle_doubleclick(CSSPixelPoint viewport_position, CSSPixelPoint screen_position, u32 button, u32 buttons, u32 modifiers)
|
||||
{
|
||||
if (!m_navigable->active_document())
|
||||
return false;
|
||||
if (!m_navigable->active_document()->is_fully_active())
|
||||
return false;
|
||||
|
||||
auto scroll_offset = m_navigable->active_document()->navigable()->viewport_scroll_offset();
|
||||
auto position = viewport_position.translated(scroll_offset);
|
||||
|
||||
m_navigable->active_document()->update_layout();
|
||||
|
||||
if (!paint_root())
|
||||
|
@ -147,6 +147,15 @@ CSSPixelRect Page::device_to_css_rect(DevicePixelRect rect) const
|
||||
};
|
||||
}
|
||||
|
||||
CSSPixelSize Page::device_to_css_size(DevicePixelSize size) const
|
||||
{
|
||||
auto scale = client().device_pixels_per_css_pixel();
|
||||
return {
|
||||
CSSPixels::floored_value_for(size.width().value() / scale),
|
||||
CSSPixels::floored_value_for(size.height().value() / scale),
|
||||
};
|
||||
}
|
||||
|
||||
DevicePixelRect Page::enclosing_device_rect(CSSPixelRect rect) const
|
||||
{
|
||||
auto scale = client().device_pixels_per_css_pixel();
|
||||
|
@ -82,6 +82,7 @@ public:
|
||||
DevicePixelPoint css_to_device_point(CSSPixelPoint) const;
|
||||
DevicePixelRect css_to_device_rect(CSSPixelRect) const;
|
||||
CSSPixelRect device_to_css_rect(DevicePixelRect) const;
|
||||
CSSPixelSize device_to_css_size(DevicePixelSize) const;
|
||||
DevicePixelRect enclosing_device_rect(CSSPixelRect) const;
|
||||
DevicePixelRect rounded_device_rect(CSSPixelRect) const;
|
||||
|
||||
|
@ -245,7 +245,7 @@ void paint_background(PaintContext& context, Layout::NodeWithStyleAndBoxModelMet
|
||||
background_positioning_area = get_box(layer.origin).rect;
|
||||
if (is<Layout::Box>(layout_node)) {
|
||||
auto* paintable_box = static_cast<Layout::Box const&>(layout_node).paintable_box();
|
||||
if (paintable_box) {
|
||||
if (paintable_box && !paintable_box->is_viewport()) {
|
||||
auto scroll_offset = paintable_box->scroll_offset();
|
||||
background_positioning_area.translate_by(-scroll_offset.x(), -scroll_offset.y());
|
||||
}
|
||||
|
@ -53,6 +53,12 @@ PaintableWithLines::~PaintableWithLines()
|
||||
|
||||
CSSPixelPoint PaintableBox::scroll_offset() const
|
||||
{
|
||||
if (is_viewport()) {
|
||||
auto navigable = document().navigable();
|
||||
VERIFY(navigable);
|
||||
return navigable->viewport_scroll_offset();
|
||||
}
|
||||
|
||||
auto const& node = layout_node();
|
||||
if (node.is_generated_for_before_pseudo_element())
|
||||
return node.pseudo_element_generator()->scroll_offset(DOM::Element::ScrollOffsetFor::PseudoBefore);
|
||||
@ -230,7 +236,7 @@ bool PaintableBox::is_scrollable(ScrollDirection direction) const
|
||||
auto overflow = direction == ScrollDirection::Horizontal ? computed_values().overflow_x() : computed_values().overflow_y();
|
||||
auto scrollable_overflow_size = direction == ScrollDirection::Horizontal ? scrollable_overflow_rect()->width() : scrollable_overflow_rect()->height();
|
||||
auto scrollport_size = direction == ScrollDirection::Horizontal ? absolute_padding_box_rect().width() : absolute_padding_box_rect().height();
|
||||
if (overflow == CSS::Overflow::Auto)
|
||||
if (is_viewport() || overflow == CSS::Overflow::Auto)
|
||||
return scrollable_overflow_size > scrollport_size;
|
||||
return overflow == CSS::Overflow::Scroll;
|
||||
}
|
||||
@ -255,20 +261,27 @@ Optional<CSSPixelRect> PaintableBox::scroll_thumb_rect(ScrollDirection direction
|
||||
if (scroll_overflow_size > scrollport_size)
|
||||
thumb_position = scroll_offset * (scrollport_size - thumb_size) / (scroll_overflow_size - scrollport_size);
|
||||
|
||||
CSSPixelRect thumb_rect;
|
||||
if (direction == ScrollDirection::Horizontal) {
|
||||
return CSSPixelRect {
|
||||
thumb_rect = {
|
||||
padding_rect.left() + thumb_position,
|
||||
padding_rect.bottom() - scrollbar_thumb_thickness,
|
||||
thumb_size,
|
||||
scrollbar_thumb_thickness
|
||||
};
|
||||
}
|
||||
return CSSPixelRect {
|
||||
} else {
|
||||
thumb_rect = {
|
||||
padding_rect.right() - scrollbar_thumb_thickness,
|
||||
padding_rect.top() + thumb_position,
|
||||
scrollbar_thumb_thickness,
|
||||
thumb_size
|
||||
};
|
||||
}
|
||||
|
||||
if (is_viewport())
|
||||
thumb_rect.translate_by(this->scroll_offset());
|
||||
|
||||
return thumb_rect;
|
||||
}
|
||||
|
||||
void PaintableBox::paint(PaintContext& context, PaintPhase phase) const
|
||||
@ -314,7 +327,7 @@ void PaintableBox::paint(PaintContext& context, PaintPhase phase) const
|
||||
}
|
||||
|
||||
auto scrollbar_width = computed_values().scrollbar_width();
|
||||
if (!layout_box().is_viewport() && phase == PaintPhase::Overlay && scrollbar_width != CSS::ScrollbarWidth::None) {
|
||||
if (phase == PaintPhase::Overlay && scrollbar_width != CSS::ScrollbarWidth::None) {
|
||||
auto color = Color(Color::NamedColor::DarkGray).with_alpha(128);
|
||||
int thumb_corner_radius = static_cast<int>(context.rounded_device_pixels(scrollbar_thumb_thickness / 2));
|
||||
if (auto thumb_rect = scroll_thumb_rect(ScrollDirection::Horizontal); thumb_rect.has_value()) {
|
||||
|
@ -203,6 +203,8 @@ public:
|
||||
|
||||
Optional<CSSPixelRect> get_clip_rect() const;
|
||||
|
||||
bool is_viewport() const { return layout_box().is_viewport(); }
|
||||
|
||||
protected:
|
||||
explicit PaintableBox(Layout::Box const&);
|
||||
|
||||
|
@ -80,7 +80,7 @@ void ViewportPaintable::assign_scroll_frames()
|
||||
});
|
||||
|
||||
for_each_in_subtree([&](auto const& paintable) {
|
||||
for (auto block = paintable.containing_block(); block; block = block->containing_block()) {
|
||||
for (auto block = paintable.containing_block(); !block->is_viewport(); block = block->containing_block()) {
|
||||
if (auto scroll_frame = scroll_state.get(block); scroll_frame.has_value()) {
|
||||
if (paintable.is_paintable_box()) {
|
||||
auto const& paintable_box = static_cast<PaintableBox const&>(paintable);
|
||||
@ -110,7 +110,7 @@ void ViewportPaintable::assign_clip_frames()
|
||||
});
|
||||
|
||||
for_each_in_subtree([&](auto const& paintable) {
|
||||
for (auto block = paintable.containing_block(); block; block = block->containing_block()) {
|
||||
for (auto block = paintable.containing_block(); !block->is_viewport(); block = block->containing_block()) {
|
||||
if (auto clip_frame = clip_state.get(block); clip_frame.has_value()) {
|
||||
if (paintable.is_paintable_box()) {
|
||||
auto const& paintable_box = static_cast<PaintableBox const&>(paintable);
|
||||
@ -136,7 +136,7 @@ void ViewportPaintable::refresh_scroll_state()
|
||||
auto const& paintable_box = *it.key;
|
||||
auto& scroll_frame = *it.value;
|
||||
CSSPixelPoint offset;
|
||||
for (auto const* block = &paintable_box.layout_box(); block; block = block->containing_block()) {
|
||||
for (auto const* block = &paintable_box.layout_box(); !block->is_viewport(); block = block->containing_block()) {
|
||||
auto const& block_paintable_box = *block->paintable_box();
|
||||
offset.translate_by(block_paintable_box.scroll_offset());
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ RefPtr<Gfx::Bitmap> SVGDecodedImageData::render(Gfx::IntSize size) const
|
||||
{
|
||||
auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, size).release_value_but_fixme_should_propagate_errors();
|
||||
VERIFY(m_document->navigable());
|
||||
m_document->navigable()->set_viewport_rect({ 0, 0, size.width(), size.height() });
|
||||
m_document->navigable()->set_viewport_size(size.to_type<CSSPixels>());
|
||||
m_document->update_layout();
|
||||
|
||||
Painting::CommandList painting_commands;
|
||||
|
@ -392,18 +392,18 @@ void ViewImplementation::resize_backing_stores_if_needed(WindowResizeInProgress
|
||||
|
||||
m_client_state.has_usable_bitmap = false;
|
||||
|
||||
auto viewport_rect = this->viewport_rect();
|
||||
if (viewport_rect.is_empty())
|
||||
auto viewport_size = this->viewport_size();
|
||||
if (viewport_size.is_empty())
|
||||
return;
|
||||
|
||||
Web::DevicePixelSize minimum_needed_size;
|
||||
|
||||
if (window_resize_in_progress == WindowResizeInProgress::Yes) {
|
||||
// Pad the minimum needed size so that we don't have to keep reallocating backing stores while the window is being resized.
|
||||
minimum_needed_size = { viewport_rect.width() + 256, viewport_rect.height() + 256 };
|
||||
minimum_needed_size = { viewport_size.width() + 256, viewport_size.height() + 256 };
|
||||
} else {
|
||||
// If we're not in the middle of a resize, we can shrink the backing store size to match the viewport size.
|
||||
minimum_needed_size = viewport_rect.size();
|
||||
minimum_needed_size = viewport_size;
|
||||
m_client_state.front_bitmap = {};
|
||||
m_client_state.back_bitmap = {};
|
||||
}
|
||||
@ -417,7 +417,7 @@ void ViewImplementation::resize_backing_stores_if_needed(WindowResizeInProgress
|
||||
backing_store.bitmap = new_bitmap_or_error.release_value();
|
||||
backing_store.id = m_client_state.next_bitmap_id++;
|
||||
}
|
||||
backing_store.last_painted_size = viewport_rect.size();
|
||||
backing_store.last_painted_size = viewport_size;
|
||||
}
|
||||
};
|
||||
|
||||
@ -430,7 +430,7 @@ void ViewImplementation::resize_backing_stores_if_needed(WindowResizeInProgress
|
||||
if (front_bitmap.id != old_front_bitmap_id || back_bitmap.id != old_back_bitmap_id) {
|
||||
client().async_add_backing_store(page_id(), front_bitmap.id, front_bitmap.bitmap->to_shareable_bitmap(), back_bitmap.id,
|
||||
back_bitmap.bitmap->to_shareable_bitmap());
|
||||
client().async_set_viewport_rect(page_id(), viewport_rect);
|
||||
client().async_set_viewport_size(page_id(), viewport_size);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,7 +205,7 @@ public:
|
||||
Function<void(String const&)> on_inspector_executed_console_script;
|
||||
Function<IPC::File()> on_request_worker_agent;
|
||||
|
||||
virtual Web::DevicePixelRect viewport_rect() const = 0;
|
||||
virtual Web::DevicePixelSize viewport_size() const = 0;
|
||||
virtual Gfx::IntPoint to_content_position(Gfx::IntPoint widget_position) const = 0;
|
||||
virtual Gfx::IntPoint to_widget_position(Gfx::IntPoint content_position) const = 0;
|
||||
|
||||
|
@ -161,10 +161,10 @@ void ConnectionFromClient::traverse_the_history_by_delta(u64 page_id, i32 delta)
|
||||
page->page().traverse_the_history_by_delta(delta);
|
||||
}
|
||||
|
||||
void ConnectionFromClient::set_viewport_rect(u64 page_id, Web::DevicePixelRect const& rect)
|
||||
void ConnectionFromClient::set_viewport_size(u64 page_id, Web::DevicePixelSize const size)
|
||||
{
|
||||
if (auto page = this->page(page_id); page.has_value())
|
||||
page->set_viewport_rect(rect);
|
||||
page->set_viewport_size(size);
|
||||
}
|
||||
|
||||
void ConnectionFromClient::add_backing_store(u64 page_id, i32 front_bitmap_id, Gfx::ShareableBitmap const& front_bitmap, i32 back_bitmap_id, Gfx::ShareableBitmap const& back_bitmap)
|
||||
|
@ -58,7 +58,7 @@ private:
|
||||
virtual void load_html(u64 page_id, ByteString const&) override;
|
||||
virtual void reload(u64 page_id) override;
|
||||
virtual void traverse_the_history_by_delta(u64 page_id, i32 delta) override;
|
||||
virtual void set_viewport_rect(u64 page_id, Web::DevicePixelRect const&) override;
|
||||
virtual void set_viewport_size(u64 page_id, Web::DevicePixelSize const) override;
|
||||
virtual void key_event(u64 page_id, Web::KeyEvent const&) override;
|
||||
virtual void mouse_event(u64 page_id, Web::MouseEvent const&) override;
|
||||
virtual void add_backing_store(u64 page_id, i32 front_bitmap_id, Gfx::ShareableBitmap const& front_bitmap, i32 back_bitmap_id, Gfx::ShareableBitmap const& back_bitmap) override;
|
||||
|
@ -233,9 +233,9 @@ void PageClient::paint(Web::DevicePixelRect const& content_rect, Gfx::Bitmap& ta
|
||||
}
|
||||
}
|
||||
|
||||
void PageClient::set_viewport_rect(Web::DevicePixelRect const& rect)
|
||||
void PageClient::set_viewport_size(Web::DevicePixelSize const& size)
|
||||
{
|
||||
page().top_level_traversable()->set_viewport_rect(page().device_to_css_rect(rect));
|
||||
page().top_level_traversable()->set_viewport_size(page().device_to_css_size(size));
|
||||
}
|
||||
|
||||
void PageClient::page_did_request_cursor_change(Gfx::StandardCursor cursor)
|
||||
@ -321,7 +321,7 @@ void PageClient::page_did_request_scroll_to(Web::CSSPixelPoint scroll_position)
|
||||
// scroll on browser side happens.
|
||||
auto viewport = page().top_level_traversable()->viewport_rect();
|
||||
viewport.set_location(scroll_position);
|
||||
page().top_level_traversable()->set_viewport_rect(viewport);
|
||||
page().top_level_traversable()->set_viewport_size(viewport.size());
|
||||
|
||||
auto device_scroll_position = page().css_to_device_point(scroll_position);
|
||||
client().async_did_request_scroll_to(m_id, device_scroll_position.to_type<int>());
|
||||
|
@ -44,7 +44,7 @@ public:
|
||||
virtual void paint(Web::DevicePixelRect const& content_rect, Gfx::Bitmap&, Web::PaintOptions = {}) override;
|
||||
|
||||
void set_palette_impl(Gfx::PaletteImpl&);
|
||||
void set_viewport_rect(Web::DevicePixelRect const&);
|
||||
void set_viewport_size(Web::DevicePixelSize const&);
|
||||
void set_screen_rects(Vector<Web::DevicePixelRect, 4> const& rects, size_t main_screen_index) { m_screen_rect = rects[main_screen_index]; }
|
||||
void set_device_pixels_per_css_pixel(float device_pixels_per_css_pixel) { m_device_pixels_per_css_pixel = device_pixels_per_css_pixel; }
|
||||
void set_preferred_color_scheme(Web::CSS::PreferredColorScheme);
|
||||
|
@ -29,7 +29,7 @@ endpoint WebContentServer
|
||||
add_backing_store(u64 page_id, i32 front_bitmap_id, Gfx::ShareableBitmap front_bitmap, i32 back_bitmap_id, Gfx::ShareableBitmap back_bitmap) =|
|
||||
ready_to_paint(u64 page_id) =|
|
||||
|
||||
set_viewport_rect(u64 page_id, Web::DevicePixelRect rect) =|
|
||||
set_viewport_size(u64 page_id, Web::DevicePixelSize size) =|
|
||||
|
||||
key_event(u64 page_id, Web::KeyEvent event) =|
|
||||
mouse_event(u64 page_id, Web::MouseEvent event) =|
|
||||
|
@ -105,8 +105,8 @@ public:
|
||||
|
||||
view->client().async_update_system_theme(0, move(theme));
|
||||
|
||||
view->m_viewport_rect = { { 0, 0 }, window_size };
|
||||
view->client().async_set_viewport_rect(0, view->m_viewport_rect.to_type<Web::DevicePixels>());
|
||||
view->m_viewport_size = window_size;
|
||||
view->client().async_set_viewport_size(0, view->m_viewport_size.to_type<Web::DevicePixels>());
|
||||
view->client().async_set_window_size(0, window_size.to_type<Web::DevicePixels>());
|
||||
|
||||
if (!web_driver_ipc_path.is_empty())
|
||||
@ -168,19 +168,6 @@ private:
|
||||
, m_cookie_jar(move(cookie_jar))
|
||||
, m_request_client(move(request_client))
|
||||
{
|
||||
on_scroll_to_point = [this](auto position) {
|
||||
m_viewport_rect.set_location(position);
|
||||
client().async_set_viewport_rect(0, m_viewport_rect.to_type<Web::DevicePixels>());
|
||||
};
|
||||
|
||||
on_scroll_by_delta = [this](auto x_delta, auto y_delta) {
|
||||
auto position = m_viewport_rect.location();
|
||||
position.set_x(position.x() + x_delta);
|
||||
position.set_y(position.y() + y_delta);
|
||||
if (on_scroll_to_point)
|
||||
on_scroll_to_point(position);
|
||||
};
|
||||
|
||||
on_get_cookie = [this](auto const& url, auto source) {
|
||||
return m_cookie_jar->get_cookie(url, source);
|
||||
};
|
||||
@ -203,12 +190,12 @@ private:
|
||||
void update_zoom() override { }
|
||||
void initialize_client(CreateNewClient) override { }
|
||||
|
||||
virtual Web::DevicePixelRect viewport_rect() const override { return m_viewport_rect.to_type<Web::DevicePixels>(); }
|
||||
virtual Web::DevicePixelSize viewport_size() const override { return m_viewport_size.to_type<Web::DevicePixels>(); }
|
||||
virtual Gfx::IntPoint to_content_position(Gfx::IntPoint widget_position) const override { return widget_position; }
|
||||
virtual Gfx::IntPoint to_widget_position(Gfx::IntPoint content_position) const override { return content_position; }
|
||||
|
||||
private:
|
||||
Gfx::IntRect m_viewport_rect;
|
||||
Gfx::IntSize m_viewport_size;
|
||||
RefPtr<Core::Promise<RefPtr<Gfx::Bitmap>>> m_pending_screenshot;
|
||||
|
||||
NonnullRefPtr<WebView::Database> m_database;
|
||||
|
Loading…
Reference in New Issue
Block a user