1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 10:18:11 +00:00

LibWeb: Move viewport subscriptions from BrowsingContext to Document

With this change, elements that want to receive viewport rect updates
will need to register on document instead of the browsing context.

This change solves the problem where a browsing context for a document
is guaranteed to exist only while the document is active so browsing
context might not exit by the time DOM node that want to register is
constructed.

This is a part of preparation work before switching to navigables where
this issue becomes more visible.
This commit is contained in:
Aliaksandr Kalenik 2023-08-23 18:58:42 +02:00 committed by Andreas Kling
parent 48e9097aa4
commit 5ff7448fee
10 changed files with 66 additions and 61 deletions

View file

@ -1007,7 +1007,7 @@ void Document::update_layout()
layout_state.commit(*m_layout_root); layout_state.commit(*m_layout_root);
// 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.
browsing_context()->inform_all_viewport_clients_about_the_current_viewport_rect(); inform_all_viewport_clients_about_the_current_viewport_rect();
browsing_context()->set_needs_display(); browsing_context()->set_needs_display();
@ -3023,6 +3023,24 @@ JS::NonnullGCPtr<CSS::VisualViewport> Document::visual_viewport()
return *m_visual_viewport; return *m_visual_viewport;
} }
void Document::register_viewport_client(ViewportClient& client)
{
auto result = m_viewport_clients.set(&client);
VERIFY(result == AK::HashSetResult::InsertedNewEntry);
}
void Document::unregister_viewport_client(ViewportClient& client)
{
bool was_removed = m_viewport_clients.remove(&client);
VERIFY(was_removed);
}
void Document::inform_all_viewport_clients_about_the_current_viewport_rect()
{
for (auto* client : m_viewport_clients)
client->did_set_viewport_rect(viewport_rect());
}
void Document::register_intersection_observer(Badge<IntersectionObserver::IntersectionObserver>, IntersectionObserver::IntersectionObserver& observer) void Document::register_intersection_observer(Badge<IntersectionObserver::IntersectionObserver>, IntersectionObserver::IntersectionObserver& observer)
{ {
auto result = m_intersection_observers.set(observer); auto result = m_intersection_observers.set(observer);

View file

@ -392,6 +392,15 @@ public:
JS::NonnullGCPtr<CSS::VisualViewport> visual_viewport(); JS::NonnullGCPtr<CSS::VisualViewport> visual_viewport();
[[nodiscard]] CSSPixelRect viewport_rect() const; [[nodiscard]] CSSPixelRect viewport_rect() const;
class ViewportClient {
public:
virtual ~ViewportClient() = default;
virtual void did_set_viewport_rect(CSSPixelRect const&) = 0;
};
void register_viewport_client(ViewportClient&);
void unregister_viewport_client(ViewportClient&);
void inform_all_viewport_clients_about_the_current_viewport_rect();
bool has_focus() const; bool has_focus() const;
void set_parser(Badge<HTML::HTMLParser>, HTML::HTMLParser&); void set_parser(Badge<HTML::HTMLParser>, HTML::HTMLParser&);
@ -624,6 +633,8 @@ private:
// Used by run_the_resize_steps(). // Used by run_the_resize_steps().
Gfx::IntSize m_last_viewport_size; Gfx::IntSize m_last_viewport_size;
HashTable<ViewportClient*> m_viewport_clients;
// https://w3c.github.io/csswg-drafts/cssom-view-1/#document-pending-scroll-event-targets // https://w3c.github.io/csswg-drafts/cssom-view-1/#document-pending-scroll-event-targets
Vector<JS::NonnullGCPtr<EventTarget>> m_pending_scroll_event_targets; Vector<JS::NonnullGCPtr<EventTarget>> m_pending_scroll_event_targets;

View file

@ -539,12 +539,6 @@ void BrowsingContext::set_active_document(JS::NonnullGCPtr<DOM::Document> docume
previously_active_document->did_stop_being_active_document_in_browsing_context({}); previously_active_document->did_stop_being_active_document_in_browsing_context({});
} }
void BrowsingContext::inform_all_viewport_clients_about_the_current_viewport_rect()
{
for (auto* client : m_viewport_clients)
client->browsing_context_did_set_viewport_rect(viewport_rect());
}
void BrowsingContext::set_viewport_rect(CSSPixelRect const& rect) void BrowsingContext::set_viewport_rect(CSSPixelRect const& rect)
{ {
bool did_change = false; bool did_change = false;
@ -565,9 +559,8 @@ void BrowsingContext::set_viewport_rect(CSSPixelRect const& rect)
did_change = true; did_change = true;
} }
if (did_change) { if (did_change && active_document()) {
for (auto* client : m_viewport_clients) active_document()->inform_all_viewport_clients_about_the_current_viewport_rect();
client->browsing_context_did_set_viewport_rect(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.
@ -585,8 +578,9 @@ void BrowsingContext::set_size(CSSPixelSize size)
document->set_needs_layout(); document->set_needs_layout();
} }
for (auto* client : m_viewport_clients) if (auto* document = active_document()) {
client->browsing_context_did_set_viewport_rect(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.
HTML::main_thread_event_loop().schedule(); HTML::main_thread_event_loop().schedule();
@ -752,18 +746,6 @@ void BrowsingContext::select_all()
(void)selection->select_all_children(*document->body()); (void)selection->select_all_children(*document->body());
} }
void BrowsingContext::register_viewport_client(ViewportClient& client)
{
auto result = m_viewport_clients.set(&client);
VERIFY(result == AK::HashSetResult::InsertedNewEntry);
}
void BrowsingContext::unregister_viewport_client(ViewportClient& client)
{
bool was_removed = m_viewport_clients.remove(&client);
VERIFY(was_removed);
}
void BrowsingContext::register_frame_nesting(AK::URL const& url) void BrowsingContext::register_frame_nesting(AK::URL const& url)
{ {
m_frame_nesting_levels.ensure(url)++; m_frame_nesting_levels.ensure(url)++;

View file

@ -118,14 +118,6 @@ public:
return IterationDecision::Continue; return IterationDecision::Continue;
} }
class ViewportClient {
public:
virtual ~ViewportClient() = default;
virtual void browsing_context_did_set_viewport_rect(CSSPixelRect const&) = 0;
};
void register_viewport_client(ViewportClient&);
void unregister_viewport_client(ViewportClient&);
bool is_top_level() const; bool is_top_level() const;
bool is_focused_context() const; bool is_focused_context() const;
@ -277,8 +269,6 @@ public:
virtual String const& window_handle() const override { return m_window_handle; } virtual String const& window_handle() const override { return m_window_handle; }
virtual void set_window_handle(String handle) override { m_window_handle = move(handle); } virtual void set_window_handle(String handle) override { m_window_handle = move(handle); }
void inform_all_viewport_clients_about_the_current_viewport_rect();
private: private:
explicit BrowsingContext(Page&, HTML::NavigableContainer*); explicit BrowsingContext(Page&, HTML::NavigableContainer*);
@ -324,8 +314,6 @@ private:
RefPtr<Core::Timer> m_cursor_blink_timer; RefPtr<Core::Timer> m_cursor_blink_timer;
bool m_cursor_blink_state { false }; bool m_cursor_blink_state { false };
HashTable<ViewportClient*> m_viewport_clients;
HashMap<AK::URL, size_t> m_frame_nesting_levels; HashMap<AK::URL, size_t> m_frame_nesting_levels;
DeprecatedString m_name; DeprecatedString m_name;

View file

@ -39,8 +39,7 @@ HTMLImageElement::HTMLImageElement(DOM::Document& document, DOM::QualifiedName q
m_animation_timer = Core::Timer::try_create().release_value_but_fixme_should_propagate_errors(); m_animation_timer = Core::Timer::try_create().release_value_but_fixme_should_propagate_errors();
m_animation_timer->on_timeout = [this] { animate(); }; m_animation_timer->on_timeout = [this] { animate(); };
if (auto* browsing_context = document.browsing_context()) document.register_viewport_client(*this);
browsing_context->register_viewport_client(*this);
} }
HTMLImageElement::~HTMLImageElement() = default; HTMLImageElement::~HTMLImageElement() = default;
@ -48,8 +47,7 @@ HTMLImageElement::~HTMLImageElement() = default;
void HTMLImageElement::finalize() void HTMLImageElement::finalize()
{ {
Base::finalize(); Base::finalize();
if (auto* browsing_context = document().browsing_context()) document().unregister_viewport_client(*this);
browsing_context->unregister_viewport_client(*this);
} }
void HTMLImageElement::initialize(JS::Realm& realm) void HTMLImageElement::initialize(JS::Realm& realm)
@ -60,6 +58,12 @@ void HTMLImageElement::initialize(JS::Realm& realm)
m_current_request = ImageRequest::create(realm, *document().page()); m_current_request = ImageRequest::create(realm, *document().page());
} }
void HTMLImageElement::adopted_from(DOM::Document& old_document)
{
old_document.unregister_viewport_client(*this);
document().register_viewport_client(*this);
}
void HTMLImageElement::visit_edges(Cell::Visitor& visitor) void HTMLImageElement::visit_edges(Cell::Visitor& visitor)
{ {
Base::visit_edges(visitor); Base::visit_edges(visitor);
@ -630,7 +634,7 @@ void HTMLImageElement::add_callbacks_to_image_request(JS::NonnullGCPtr<ImageRequ
}); });
} }
void HTMLImageElement::browsing_context_did_set_viewport_rect(CSSPixelRect const& viewport_rect) void HTMLImageElement::did_set_viewport_rect(CSSPixelRect const& viewport_rect)
{ {
if (viewport_rect.size() == m_last_seen_viewport_size) if (viewport_rect.size() == m_last_seen_viewport_size)
return; return;

View file

@ -9,6 +9,7 @@
#include <AK/ByteBuffer.h> #include <AK/ByteBuffer.h>
#include <AK/OwnPtr.h> #include <AK/OwnPtr.h>
#include <LibGfx/Forward.h> #include <LibGfx/Forward.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/DocumentLoadEventDelayer.h> #include <LibWeb/DOM/DocumentLoadEventDelayer.h>
#include <LibWeb/HTML/BrowsingContext.h> #include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/HTML/CORSSettingAttribute.h> #include <LibWeb/HTML/CORSSettingAttribute.h>
@ -23,7 +24,7 @@ class HTMLImageElement final
: public HTMLElement : public HTMLElement
, public FormAssociatedElement , public FormAssociatedElement
, public Layout::ImageProvider , public Layout::ImageProvider
, public BrowsingContext::ViewportClient { , public DOM::Document::ViewportClient {
WEB_PLATFORM_OBJECT(HTMLImageElement, HTMLElement); WEB_PLATFORM_OBJECT(HTMLImageElement, HTMLElement);
FORM_ASSOCIATED_ELEMENT(HTMLElement, HTMLImageElement) FORM_ASSOCIATED_ELEMENT(HTMLElement, HTMLImageElement)
@ -99,11 +100,13 @@ private:
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual void finalize() override; virtual void finalize() override;
virtual void adopted_from(DOM::Document&) override;
virtual void apply_presentational_hints(CSS::StyleProperties&) const override; virtual void apply_presentational_hints(CSS::StyleProperties&) const override;
virtual JS::GCPtr<Layout::Node> create_layout_node(NonnullRefPtr<CSS::StyleProperties>) override; virtual JS::GCPtr<Layout::Node> create_layout_node(NonnullRefPtr<CSS::StyleProperties>) override;
virtual void browsing_context_did_set_viewport_rect(CSSPixelRect const&) override; virtual void did_set_viewport_rect(CSSPixelRect const&) override;
void handle_successful_fetch(AK::URL const&, StringView mime_type, ImageRequest&, ByteBuffer, bool maybe_omit_events, AK::URL const& previous_url); void handle_successful_fetch(AK::URL const&, StringView mime_type, ImageRequest&, ByteBuffer, bool maybe_omit_events, AK::URL const& previous_url);
void handle_failed_fetch(); void handle_failed_fetch();

View file

@ -13,16 +13,16 @@ namespace Web::Layout {
VideoBox::VideoBox(DOM::Document& document, DOM::Element& element, NonnullRefPtr<CSS::StyleProperties> style) VideoBox::VideoBox(DOM::Document& document, DOM::Element& element, NonnullRefPtr<CSS::StyleProperties> style)
: ReplacedBox(document, element, move(style)) : ReplacedBox(document, element, move(style))
{ {
browsing_context().register_viewport_client(*this); document.register_viewport_client(*this);
} }
void VideoBox::finalize() void VideoBox::finalize()
{ {
Base::finalize(); Base::finalize();
// NOTE: We unregister from the browsing context in finalize() to avoid trouble // NOTE: We unregister from the document in finalize() to avoid trouble
// in the scenario where our BrowsingContext has already been swept by GC. // in the scenario where our Document has already been swept by GC.
browsing_context().unregister_viewport_client(*this); document().unregister_viewport_client(*this);
} }
HTML::HTMLVideoElement& VideoBox::dom_node() HTML::HTMLVideoElement& VideoBox::dom_node()
@ -49,7 +49,7 @@ void VideoBox::prepare_for_replaced_layout()
set_natural_aspect_ratio({}); set_natural_aspect_ratio({});
} }
void VideoBox::browsing_context_did_set_viewport_rect(CSSPixelRect const&) void VideoBox::did_set_viewport_rect(CSSPixelRect const&)
{ {
// FIXME: Several steps in HTMLMediaElement indicate we may optionally handle whether the media object // FIXME: Several steps in HTMLMediaElement indicate we may optionally handle whether the media object
// is in view. Implement those steps. // is in view. Implement those steps.

View file

@ -6,15 +6,15 @@
#pragma once #pragma once
#include <LibWeb/DOM/Document.h>
#include <LibWeb/Forward.h> #include <LibWeb/Forward.h>
#include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/Layout/ReplacedBox.h> #include <LibWeb/Layout/ReplacedBox.h>
namespace Web::Layout { namespace Web::Layout {
class VideoBox final class VideoBox final
: public ReplacedBox : public ReplacedBox
, public HTML::BrowsingContext::ViewportClient { , public DOM::Document::ViewportClient {
JS_CELL(VideoBox, ReplacedBox); JS_CELL(VideoBox, ReplacedBox);
public: public:
@ -28,8 +28,8 @@ public:
private: private:
VideoBox(DOM::Document&, DOM::Element&, NonnullRefPtr<CSS::StyleProperties>); VideoBox(DOM::Document&, DOM::Element&, NonnullRefPtr<CSS::StyleProperties>);
// ^BrowsingContext::ViewportClient // ^Document::ViewportClient
virtual void browsing_context_did_set_viewport_rect(CSSPixelRect const&) final; virtual void did_set_viewport_rect(CSSPixelRect const&) final;
// ^JS::Cell // ^JS::Cell
virtual void finalize() override; virtual void finalize() override;

View file

@ -23,16 +23,16 @@ JS::NonnullGCPtr<ImagePaintable> ImagePaintable::create(Layout::ImageBox const&
ImagePaintable::ImagePaintable(Layout::ImageBox const& layout_box) ImagePaintable::ImagePaintable(Layout::ImageBox const& layout_box)
: PaintableBox(layout_box) : PaintableBox(layout_box)
{ {
browsing_context().register_viewport_client(*this); const_cast<DOM::Document&>(layout_box.document()).register_viewport_client(*this);
} }
void ImagePaintable::finalize() void ImagePaintable::finalize()
{ {
Base::finalize(); Base::finalize();
// NOTE: We unregister from the browsing context in finalize() to avoid trouble // NOTE: We unregister from the document in finalize() to avoid trouble
// in the scenario where our BrowsingContext has already been swept by GC. // in the scenario where our Document has already been swept by GC.
browsing_context().unregister_viewport_client(*this); document().unregister_viewport_client(*this);
} }
Layout::ImageBox const& ImagePaintable::layout_box() const Layout::ImageBox const& ImagePaintable::layout_box() const
@ -133,7 +133,7 @@ void ImagePaintable::paint(PaintContext& context, PaintPhase phase) const
} }
} }
void ImagePaintable::browsing_context_did_set_viewport_rect(CSSPixelRect const& viewport_rect) void ImagePaintable::did_set_viewport_rect(CSSPixelRect const& viewport_rect)
{ {
const_cast<Layout::ImageProvider&>(layout_box().image_provider()).set_visible_in_viewport(viewport_rect.intersects(absolute_rect())); const_cast<Layout::ImageProvider&>(layout_box().image_provider()).set_visible_in_viewport(viewport_rect.intersects(absolute_rect()));
} }

View file

@ -6,7 +6,6 @@
#pragma once #pragma once
#include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/Layout/ImageBox.h> #include <LibWeb/Layout/ImageBox.h>
#include <LibWeb/Painting/PaintableBox.h> #include <LibWeb/Painting/PaintableBox.h>
@ -14,7 +13,7 @@ namespace Web::Painting {
class ImagePaintable final class ImagePaintable final
: public PaintableBox : public PaintableBox
, public HTML::BrowsingContext::ViewportClient { , public DOM::Document::ViewportClient {
JS_CELL(ImagePaintable, PaintableBox); JS_CELL(ImagePaintable, PaintableBox);
public: public:
@ -28,8 +27,8 @@ private:
// ^JS::Cell // ^JS::Cell
virtual void finalize() override; virtual void finalize() override;
// ^BrowsingContext::ViewportClient // ^Document::ViewportClient
virtual void browsing_context_did_set_viewport_rect(CSSPixelRect const&) final; virtual void did_set_viewport_rect(CSSPixelRect const&) final;
ImagePaintable(Layout::ImageBox const&); ImagePaintable(Layout::ImageBox const&);
}; };