LibWeb: Move m_needs_repaint and record_display_list() in Document

Let's make document responsible for display list invalidation,
considering it already takes care of style and layout.
This commit is contained in:
Aliaksandr Kalenik 2024-08-19 02:06:52 +02:00 committed by Andreas Kling
commit 69c6e07139
Notes: github-actions[bot] 2024-08-19 16:58:13 +00:00
16 changed files with 117 additions and 109 deletions

View file

@ -48,7 +48,7 @@ void ImageStyleValue::load_any_resources(DOM::Document& document)
m_document->set_needs_to_resolve_paint_only_properties(); m_document->set_needs_to_resolve_paint_only_properties();
// FIXME: Do less than a full repaint if possible? // FIXME: Do less than a full repaint if possible?
navigable->set_needs_display(); m_document->set_needs_display();
} }
auto image_data = m_resource_request->image_data(); auto image_data = m_resource_request->image_data();

View file

@ -1153,7 +1153,7 @@ void Document::update_layout()
// Broadcast the current viewport rect to any new paintables, so they know whether they're visible or not. // Broadcast the current viewport rect to any new paintables, so they know whether they're visible or not.
inform_all_viewport_clients_about_the_current_viewport_rect(); inform_all_viewport_clients_about_the_current_viewport_rect();
navigable->set_needs_display(); m_document->set_needs_display();
set_needs_to_resolve_paint_only_properties(); set_needs_to_resolve_paint_only_properties();
paintable()->assign_scroll_frames(); paintable()->assign_scroll_frames();
@ -5367,4 +5367,76 @@ void Document::set_cached_navigable(JS::GCPtr<HTML::Navigable> navigable)
m_cached_navigable = navigable.ptr(); m_cached_navigable = navigable.ptr();
} }
void Document::set_needs_display()
{
set_needs_display(viewport_rect());
}
void Document::set_needs_display(CSSPixelRect const&)
{
// FIXME: Ignore updates outside the visible viewport rect.
// This requires accounting for fixed-position elements in the input rect, which we don't do yet.
m_needs_repaint = true;
auto navigable = this->navigable();
if (!navigable)
return;
if (navigable->is_traversable()) {
Web::HTML::main_thread_event_loop().schedule();
return;
}
if (navigable->container()) {
navigable->container()->document().set_needs_display();
}
}
RefPtr<Painting::DisplayList> Document::record_display_list(PaintConfig config)
{
auto display_list = Painting::DisplayList::create();
Painting::DisplayListRecorder display_list_recorder(display_list);
if (config.canvas_fill_rect.has_value()) {
display_list_recorder.fill_rect(config.canvas_fill_rect.value(), CSS::SystemColor::canvas());
}
auto viewport_rect = page().css_to_device_rect(this->viewport_rect());
Gfx::IntRect bitmap_rect { {}, viewport_rect.size().to_type<int>() };
display_list_recorder.fill_rect(bitmap_rect, background_color());
if (!paintable()) {
VERIFY_NOT_REACHED();
}
Web::PaintContext context(display_list_recorder, page().palette(), page().client().device_pixels_per_css_pixel());
context.set_device_viewport_rect(viewport_rect);
context.set_should_show_line_box_borders(config.should_show_line_box_borders);
context.set_should_paint_overlay(config.paint_overlay);
context.set_has_focus(config.has_focus);
update_paint_and_hit_testing_properties_if_needed();
auto& viewport_paintable = *paintable();
viewport_paintable.refresh_scroll_state();
viewport_paintable.paint_all_phases(context);
display_list->set_device_pixels_per_css_pixel(page().client().device_pixels_per_css_pixel());
Vector<RefPtr<Painting::ScrollFrame>> scroll_state;
scroll_state.resize(viewport_paintable.scroll_state.size());
for (auto& [_, scrollable_frame] : viewport_paintable.scroll_state) {
scroll_state[scrollable_frame->id] = scrollable_frame;
}
display_list->set_scroll_state(move(scroll_state));
m_needs_repaint = false;
return display_list;
}
} }

View file

@ -699,6 +699,18 @@ public:
JS::GCPtr<HTML::Navigable> cached_navigable(); JS::GCPtr<HTML::Navigable> cached_navigable();
void set_cached_navigable(JS::GCPtr<HTML::Navigable>); void set_cached_navigable(JS::GCPtr<HTML::Navigable>);
[[nodiscard]] bool needs_repaint() const { return m_needs_repaint; }
void set_needs_display();
void set_needs_display(CSSPixelRect const&);
struct PaintConfig {
bool paint_overlay { false };
bool should_show_line_box_borders { false };
bool has_focus { false };
Optional<Gfx::IntRect> canvas_fill_rect {};
};
RefPtr<Painting::DisplayList> record_display_list(PaintConfig);
protected: protected:
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override; virtual void visit_edges(Cell::Visitor&) override;
@ -972,6 +984,8 @@ private:
// NOTE: This is WeakPtr, not GCPtr, on purpose. We don't want the document to keep some old detached navigable alive. // NOTE: This is WeakPtr, not GCPtr, on purpose. We don't want the document to keep some old detached navigable alive.
WeakPtr<HTML::Navigable> m_cached_navigable; WeakPtr<HTML::Navigable> m_cached_navigable;
bool m_needs_repaint { false };
}; };
template<> template<>

View file

@ -31,7 +31,7 @@ AudioTrack::AudioTrack(JS::Realm& realm, JS::NonnullGCPtr<HTMLMediaElement> medi
, m_audio_plugin(Platform::AudioCodecPlugin::create(move(loader)).release_value_but_fixme_should_propagate_errors()) , m_audio_plugin(Platform::AudioCodecPlugin::create(move(loader)).release_value_but_fixme_should_propagate_errors())
{ {
m_audio_plugin->on_playback_position_updated = [this](auto position) { m_audio_plugin->on_playback_position_updated = [this](auto position) {
if (auto const* paintable = m_media_element->paintable()) if (auto* paintable = m_media_element->paintable())
paintable->set_needs_display(); paintable->set_needs_display();
auto playback_position = static_cast<double>(position.to_milliseconds()) / 1000.0; auto playback_position = static_cast<double>(position.to_milliseconds()) / 1000.0;

View file

@ -209,7 +209,7 @@ void EventLoop::process()
// loop processing. // loop processing.
for_each_fully_active_document_in_docs([&](DOM::Document& document) { for_each_fully_active_document_in_docs([&](DOM::Document& document) {
auto navigable = document.navigable(); auto navigable = document.navigable();
if (navigable && !navigable->has_a_rendering_opportunity() && navigable->needs_repaint()) if (navigable && !navigable->has_a_rendering_opportunity() && document.needs_repaint())
schedule(); schedule();
if (navigable && navigable->has_a_rendering_opportunity()) if (navigable && navigable->has_a_rendering_opportunity())
return; return;
@ -328,7 +328,7 @@ void EventLoop::process()
// 16. For each fully active Document in docs, update the rendering or user interface of that Document and its browsing context to reflect the current state. // 16. For each fully active Document in docs, update the rendering or user interface of that Document and its browsing context to reflect the current state.
for_each_fully_active_document_in_docs([&](DOM::Document& document) { for_each_fully_active_document_in_docs([&](DOM::Document& document) {
auto navigable = document.navigable(); auto navigable = document.navigable();
if (navigable && navigable->needs_repaint()) { if (navigable && document.needs_repaint()) {
auto* browsing_context = document.browsing_context(); auto* browsing_context = document.browsing_context();
auto& page = browsing_context->page(); auto& page = browsing_context->page();
if (navigable->is_traversable()) { if (navigable->is_traversable()) {
@ -341,7 +341,7 @@ void EventLoop::process()
// FIXME: Not in the spec: If there is a screenshot request queued, process it now. // FIXME: Not in the spec: If there is a screenshot request queued, process it now.
// This prevents tests deadlocking on screenshot requests on macOS. // This prevents tests deadlocking on screenshot requests on macOS.
for (auto& document : docs) { for (auto& document : docs) {
if (document->page().top_level_traversable()->needs_repaint()) if (document->needs_repaint())
document->page().client().process_screenshot_requests(); document->page().client().process_screenshot_requests();
} }

View file

@ -2000,9 +2000,10 @@ void Navigable::set_viewport_size(CSSPixelSize size)
document->invalidate_style(); document->invalidate_style();
document->set_needs_layout(); document->set_needs_layout();
} }
set_needs_display();
if (auto document = active_document()) { if (auto document = active_document()) {
document->set_needs_display();
document->inform_all_viewport_clients_about_the_current_viewport_rect(); document->inform_all_viewport_clients_about_the_current_viewport_rect();
// Schedule the HTML event loop to ensure that a `resize` event gets fired. // Schedule the HTML event loop to ensure that a `resize` event gets fired.
@ -2015,9 +2016,9 @@ void Navigable::perform_scroll_of_viewport(CSSPixelPoint new_position)
if (m_viewport_scroll_offset != new_position) { if (m_viewport_scroll_offset != new_position) {
m_viewport_scroll_offset = new_position; m_viewport_scroll_offset = new_position;
scroll_offset_did_change(); scroll_offset_did_change();
set_needs_display();
if (auto document = active_document()) { if (auto document = active_document()) {
document->set_needs_display();
document->set_needs_to_refresh_scroll_state(true); document->set_needs_to_refresh_scroll_state(true);
document->inform_all_viewport_clients_about_the_current_viewport_rect(); document->inform_all_viewport_clients_about_the_current_viewport_rect();
} }
@ -2029,24 +2030,9 @@ void Navigable::perform_scroll_of_viewport(CSSPixelPoint new_position)
void Navigable::set_needs_display() void Navigable::set_needs_display()
{ {
set_needs_display(viewport_rect()); if (auto document = active_document(); document) {
} document->set_needs_display();
void Navigable::set_needs_display(CSSPixelRect const&)
{
// FIXME: Ignore updates outside the visible viewport rect.
// This requires accounting for fixed-position elements in the input rect, which we don't do yet.
m_needs_repaint = true;
if (is<TraversableNavigable>(*this)) {
// Schedule the main thread event loop, which will, in turn, schedule a repaint.
Web::HTML::main_thread_event_loop().schedule();
return;
} }
if (container() && container()->paintable())
container()->paintable()->set_needs_display();
} }
// https://html.spec.whatwg.org/#rendering-opportunity // https://html.spec.whatwg.org/#rendering-opportunity
@ -2090,59 +2076,6 @@ void Navigable::inform_the_navigation_api_about_aborting_navigation()
})); }));
} }
RefPtr<Painting::DisplayList> Navigable::record_display_list(PaintConfig config)
{
auto document = active_document();
if (!document)
return {};
auto display_list = Painting::DisplayList::create();
Painting::DisplayListRecorder display_list_recorder(display_list);
if (config.canvas_fill_rect.has_value()) {
display_list_recorder.fill_rect(config.canvas_fill_rect.value(), CSS::SystemColor::canvas());
}
auto const& page = traversable_navigable()->page();
auto viewport_rect = page.css_to_device_rect(this->viewport_rect());
Gfx::IntRect bitmap_rect { {}, viewport_rect.size().to_type<int>() };
auto background_color = document->background_color();
display_list_recorder.fill_rect(bitmap_rect, background_color);
if (!document->paintable()) {
VERIFY_NOT_REACHED();
}
Web::PaintContext context(display_list_recorder, page.palette(), page.client().device_pixels_per_css_pixel());
context.set_device_viewport_rect(viewport_rect);
context.set_should_show_line_box_borders(config.should_show_line_box_borders);
context.set_should_paint_overlay(config.paint_overlay);
context.set_has_focus(config.has_focus);
document->update_paint_and_hit_testing_properties_if_needed();
auto& viewport_paintable = *document->paintable();
viewport_paintable.refresh_scroll_state();
viewport_paintable.paint_all_phases(context);
display_list->set_device_pixels_per_css_pixel(page.client().device_pixels_per_css_pixel());
Vector<RefPtr<Painting::ScrollFrame>> scroll_state;
scroll_state.resize(viewport_paintable.scroll_state.size());
for (auto& [_, scrollable_frame] : viewport_paintable.scroll_state) {
scroll_state[scrollable_frame->id] = scrollable_frame;
}
display_list->set_scroll_state(move(scroll_state));
m_needs_repaint = false;
return display_list;
}
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#event-uni // https://html.spec.whatwg.org/multipage/browsing-the-web.html#event-uni
UserNavigationInvolvement user_navigation_involvement(DOM::Event const& event) UserNavigationInvolvement user_navigation_involvement(DOM::Event const& event)
{ {

View file

@ -170,7 +170,6 @@ public:
void perform_scroll_of_viewport(CSSPixelPoint position); void perform_scroll_of_viewport(CSSPixelPoint position);
void set_needs_display(); void set_needs_display();
void set_needs_display(CSSPixelRect const&);
void set_is_popup(TokenizedFeature::Popup is_popup) { m_is_popup = is_popup; } void set_is_popup(TokenizedFeature::Popup is_popup) { m_is_popup = is_popup; }
@ -179,16 +178,6 @@ public:
[[nodiscard]] TargetSnapshotParams snapshot_target_snapshot_params(); [[nodiscard]] TargetSnapshotParams snapshot_target_snapshot_params();
[[nodiscard]] bool needs_repaint() const { return m_needs_repaint; }
struct PaintConfig {
bool paint_overlay { false };
bool should_show_line_box_borders { false };
bool has_focus { false };
Optional<Gfx::IntRect> canvas_fill_rect {};
};
RefPtr<Painting::DisplayList> record_display_list(PaintConfig);
Page& page() { return m_page; } Page& page() { return m_page; }
Page const& page() const { return m_page; } Page const& page() const { return m_page; }
@ -245,8 +234,6 @@ private:
CSSPixelSize m_size; CSSPixelSize m_size;
CSSPixelPoint m_viewport_scroll_offset; CSSPixelPoint m_viewport_scroll_offset;
bool m_needs_repaint { false };
Web::EventHandler m_event_handler; Web::EventHandler m_event_handler;
}; };

View file

@ -1189,12 +1189,16 @@ JS::GCPtr<DOM::Node> TraversableNavigable::currently_focused_area()
void TraversableNavigable::paint(DevicePixelRect const& content_rect, Painting::BackingStore& target, PaintOptions paint_options) void TraversableNavigable::paint(DevicePixelRect const& content_rect, Painting::BackingStore& target, PaintOptions paint_options)
{ {
HTML::Navigable::PaintConfig paint_config; auto document = active_document();
if (!document)
return;
DOM::Document::PaintConfig paint_config;
paint_config.paint_overlay = paint_options.paint_overlay == PaintOptions::PaintOverlay::Yes; paint_config.paint_overlay = paint_options.paint_overlay == PaintOptions::PaintOverlay::Yes;
paint_config.should_show_line_box_borders = paint_options.should_show_line_box_borders; paint_config.should_show_line_box_borders = paint_options.should_show_line_box_borders;
paint_config.has_focus = paint_options.has_focus; paint_config.has_focus = paint_options.has_focus;
paint_config.canvas_fill_rect = Gfx::IntRect { {}, content_rect.size() }; paint_config.canvas_fill_rect = Gfx::IntRect { {}, content_rect.size() };
auto display_list = record_display_list(paint_config); auto display_list = document->record_display_list(paint_config);
if (!display_list) { if (!display_list) {
return; return;
} }

View file

@ -582,7 +582,7 @@ bool EventHandler::handle_mousemove(CSSPixelPoint viewport_position, CSSPixelPoi
if (should_set_cursor_position) if (should_set_cursor_position)
document.set_cursor_position(DOM::Position::create(realm, *hit->dom_node(), *start_index)); document.set_cursor_position(DOM::Position::create(realm, *hit->dom_node(), *start_index));
document.navigable()->set_needs_display(); document.set_needs_display();
} }
} }
} }

View file

@ -54,11 +54,11 @@ void NestedBrowsingContextPaintable::paint(PaintContext& context, PaintPhase pha
context.display_list_recorder().add_clip_rect(clip_rect.to_type<int>()); context.display_list_recorder().add_clip_rect(clip_rect.to_type<int>());
HTML::Navigable::PaintConfig paint_config; DOM::Document::PaintConfig paint_config;
paint_config.paint_overlay = context.should_paint_overlay(); paint_config.paint_overlay = context.should_paint_overlay();
paint_config.should_show_line_box_borders = context.should_show_line_box_borders(); paint_config.should_show_line_box_borders = context.should_show_line_box_borders();
paint_config.has_focus = context.has_focus(); paint_config.has_focus = context.has_focus();
auto display_list = const_cast<DOM::Document*>(hosted_document)->navigable()->record_display_list(paint_config); auto display_list = const_cast<DOM::Document*>(hosted_document)->record_display_list(paint_config);
context.display_list_recorder().paint_nested_display_list(display_list, context.enclosing_device_rect(absolute_rect).to_type<int>()); context.display_list_recorder().paint_nested_display_list(display_list, context.enclosing_device_rect(absolute_rect).to_type<int>());
context.display_list_recorder().restore(); context.display_list_recorder().restore();

View file

@ -127,25 +127,24 @@ void Paintable::invalidate_stacking_context()
m_stacking_context = nullptr; m_stacking_context = nullptr;
} }
void Paintable::set_needs_display() const void Paintable::set_needs_display()
{ {
auto* containing_block = this->containing_block(); auto* containing_block = this->containing_block();
if (!containing_block) if (!containing_block)
return; return;
auto navigable = this->navigable();
if (!navigable) auto& document = const_cast<DOM::Document&>(this->document());
return;
if (is<Painting::InlinePaintable>(*this)) { if (is<Painting::InlinePaintable>(*this)) {
auto const& fragments = static_cast<Painting::InlinePaintable const*>(this)->fragments(); auto const& fragments = static_cast<Painting::InlinePaintable const*>(this)->fragments();
for (auto const& fragment : fragments) for (auto const& fragment : fragments)
navigable->set_needs_display(fragment.absolute_rect()); document.set_needs_display(fragment.absolute_rect());
} }
if (!is<Painting::PaintableWithLines>(*containing_block)) if (!is<Painting::PaintableWithLines>(*containing_block))
return; return;
static_cast<Painting::PaintableWithLines const&>(*containing_block).for_each_fragment([&](auto& fragment) { static_cast<Painting::PaintableWithLines const&>(*containing_block).for_each_fragment([&](auto& fragment) {
navigable->set_needs_display(fragment.absolute_rect()); document.set_needs_display(fragment.absolute_rect());
return IterationDecision::Continue; return IterationDecision::Continue;
}); });
} }

View file

@ -198,7 +198,7 @@ public:
JS::GCPtr<HTML::Navigable> navigable() const; JS::GCPtr<HTML::Navigable> navigable() const;
virtual void set_needs_display() const; virtual void set_needs_display();
PaintableBox* containing_block() const PaintableBox* containing_block() const
{ {

View file

@ -907,10 +907,9 @@ TraversalDecision PaintableWithLines::hit_test(CSSPixelPoint position, HitTestTy
return TraversalDecision::Continue; return TraversalDecision::Continue;
} }
void PaintableBox::set_needs_display() const void PaintableBox::set_needs_display()
{ {
if (auto navigable = this->navigable()) document().set_needs_display(absolute_rect());
navigable->set_needs_display(absolute_rect());
} }
Optional<CSSPixelRect> PaintableBox::get_masking_area() const Optional<CSSPixelRect> PaintableBox::get_masking_area() const

View file

@ -128,7 +128,7 @@ public:
DOM::Node const* dom_node() const { return layout_box().dom_node(); } DOM::Node const* dom_node() const { return layout_box().dom_node(); }
DOM::Node* dom_node() { return layout_box().dom_node(); } DOM::Node* dom_node() { return layout_box().dom_node(); }
virtual void set_needs_display() const override; virtual void set_needs_display() override;
virtual void apply_scroll_offset(PaintContext&, PaintPhase) const override; virtual void apply_scroll_offset(PaintContext&, PaintPhase) const override;
virtual void reset_scroll_offset(PaintContext&, PaintPhase) const override; virtual void reset_scroll_offset(PaintContext&, PaintPhase) const override;

View file

@ -92,7 +92,7 @@ RefPtr<Gfx::Bitmap> SVGDecodedImageData::render(Gfx::IntSize size) const
m_document->navigable()->set_viewport_size(size.to_type<CSSPixels>()); m_document->navigable()->set_viewport_size(size.to_type<CSSPixels>());
m_document->update_layout(); m_document->update_layout();
auto display_list = m_document->navigable()->record_display_list({}); auto display_list = m_document->record_display_list({});
if (!display_list) if (!display_list)
return {}; return {};

View file

@ -359,7 +359,7 @@ void ConnectionFromClient::debug_request(u64 page_id, ByteString const& request,
if (request == "set-line-box-borders") { if (request == "set-line-box-borders") {
bool state = argument == "on"; bool state = argument == "on";
page->set_should_show_line_box_borders(state); page->set_should_show_line_box_borders(state);
page->page().top_level_traversable()->set_needs_display(page->page().top_level_traversable()->viewport_rect()); page->page().top_level_traversable()->set_needs_display();
return; return;
} }