LibWeb: Replace cached navigable with Navigable-maintained back-pointer

Now that Navigable directly owns its active document (m_active_document)
we can have Navigable maintain a back-pointer on Document instead of
using the old cache-with-validation pattern that fell back to a linear
scan of all navigables via navigable_with_active_document().
This commit is contained in:
Aliaksandr Kalenik 2026-04-01 10:18:55 +02:00 committed by Alexander Kalenik
parent 2645695fdd
commit 4985dabf3d
8 changed files with 36 additions and 34 deletions

View file

@ -4608,7 +4608,7 @@ void Document::destroy()
// Not in the spec:
for (auto& navigable_container : HTML::NavigableContainer::all_instances()) {
if (&navigable_container->document() == this && navigable_container->content_navigable())
HTML::all_navigables().remove(*navigable_container->content_navigable());
navigable_container->content_navigable()->remove_from_all_navigables();
}
// 9. Set document's node navigable's active session history entry's document state's document to null.
@ -7323,14 +7323,14 @@ GC::Ptr<DOM::Document> Document::container_document() const
return node_navigable->container_document();
}
GC::Ptr<HTML::Navigable> Document::cached_navigable()
GC::Ptr<HTML::Navigable> Document::navigable() const
{
return m_cached_navigable.ptr();
return m_navigable.ptr();
}
void Document::set_cached_navigable(GC::Ptr<HTML::Navigable> navigable)
void Document::set_navigable(GC::Ptr<HTML::Navigable> navigable)
{
m_cached_navigable = navigable.ptr();
m_navigable = navigable.ptr();
}
void Document::notify_css_background_image_loaded()

View file

@ -901,10 +901,10 @@ public:
bool cursor_blink_state() const { return m_cursor_blink_state; }
// Cached pointer to the last known node navigable.
// If this document is currently the "active document" of the cached navigable, the cache is still valid.
GC::Ptr<HTML::Navigable> cached_navigable();
void set_cached_navigable(GC::Ptr<HTML::Navigable>);
// Back-pointer to the navigable whose active document is this document.
// Maintained by Navigable when it sets/clears its active document.
GC::Ptr<HTML::Navigable> navigable() const;
void set_navigable(GC::Ptr<HTML::Navigable>);
template<OneOf<Painting::Paintable, HTML::Navigable, CSS::VisualViewport, Web::EventHandler> T>
void set_needs_repaint(Badge<T>, InvalidateDisplayList should_invalidate_display_list = InvalidateDisplayList::Yes)
@ -1416,7 +1416,7 @@ private:
bool m_cursor_blink_state { false };
// NOTE: This is GC::Weak, not GC::Ptr, on purpose. We don't want the document to keep some old detached navigable alive.
GC::Weak<HTML::Navigable> m_cached_navigable;
GC::Weak<HTML::Navigable> m_navigable;
Core::SharedVersion m_cookie_version { Core::INVALID_SHARED_VERSION };
Optional<Core::SharedVersionIndex> m_cookie_version_index;

View file

@ -398,17 +398,9 @@ WebIDL::ExceptionOr<void> Node::set_node_value(Optional<String> const& maybe_val
// https://html.spec.whatwg.org/multipage/document-sequences.html#node-navigable
GC::Ptr<HTML::Navigable> Node::navigable() const
{
auto& document = const_cast<Document&>(this->document());
if (auto cached_navigable = document.cached_navigable()) {
if (cached_navigable->active_document() == &document)
return cached_navigable;
}
// To get the node navigable of a node node, return the navigable whose active document is node's node document,
// or null if there is no such navigable.
auto navigable = HTML::Navigable::navigable_with_active_document(document);
document.set_cached_navigable(navigable);
return navigable;
return document().navigable();
}
[[maybe_unused]] static StringView to_string(StyleInvalidationReason reason)

View file

@ -299,6 +299,18 @@ Navigable::Navigable(GC::Ref<Page> page, bool is_svg_page)
Navigable::~Navigable() = default;
void Navigable::set_has_been_destroyed()
{
m_has_been_destroyed = true;
}
void Navigable::remove_from_all_navigables()
{
if (m_active_document)
m_active_document->set_navigable(nullptr);
all_navigables().remove(*this);
}
void Navigable::finalize()
{
all_navigables().remove(*this);
@ -369,15 +381,6 @@ void Navigable::clear_navigation_load_event_guard()
m_navigation_load_event_guard.clear();
}
GC::Ptr<Navigable> Navigable::navigable_with_active_document(GC::Ref<DOM::Document> document)
{
for (auto navigable : all_navigables()) {
if (navigable->active_document() == document)
return navigable;
}
return nullptr;
}
// https://html.spec.whatwg.org/multipage/document-sequences.html#initialize-the-navigable
void Navigable::initialize_navigable(GC::Ref<DocumentState> document_state, GC::Ptr<Navigable> parent, GC::Ref<DOM::Document> document)
{
@ -401,6 +404,7 @@ void Navigable::initialize_navigable(GC::Ref<DocumentState> document_state, GC::
// 4. Set navigable's active session history entry to entry.
m_active_session_history_entry = entry;
m_active_document = document;
document->set_navigable(this);
// 5. Set navigable's parent to parent.
m_parent = parent;
@ -441,7 +445,10 @@ void Navigable::activate_history_entry(GC::Ptr<SessionHistoryEntry> entry, GC::R
// 4. Set navigable's active session history entry to entry.
m_active_session_history_entry = entry;
if (m_active_document && m_active_document != new_document)
m_active_document->set_navigable(nullptr);
m_active_document = new_document;
new_document->set_navigable(this);
// 5. Make active newDocument.
new_document->make_active();
@ -485,7 +492,11 @@ Optional<UniqueNodeID> Navigable::active_document_id() const
void Navigable::set_active_document(GC::Ptr<DOM::Document> document)
{
if (m_active_document && m_active_document != document)
m_active_document->set_navigable(nullptr);
m_active_document = document;
if (document)
document->set_navigable(this);
VERIFY(m_active_session_history_entry);
Optional<UniqueNodeID> document_id;

View file

@ -128,8 +128,6 @@ public:
GC::Ptr<Navigable> find_a_navigable_by_target_name(StringView name);
static GC::Ptr<Navigable> navigable_with_active_document(GC::Ref<DOM::Document>);
enum class Traversal {
Tag
};
@ -186,7 +184,8 @@ public:
// https://github.com/whatwg/html/issues/9690
[[nodiscard]] bool has_been_destroyed() const { return m_has_been_destroyed; }
void set_has_been_destroyed() { m_has_been_destroyed = true; }
void set_has_been_destroyed();
void remove_from_all_navigables();
CSSPixelPoint to_top_level_position(CSSPixelPoint);
CSSPixelRect to_top_level_rect(CSSPixelRect const&);

View file

@ -337,7 +337,7 @@ void NavigableContainer::destroy_the_child_navigable()
document().schedule_html_parser_end_check();
// Not in the spec:
HTML::all_navigables().remove(*navigable);
navigable->remove_from_all_navigables();
// 6. Let parentDocState be container's node navigable's active session history entry's document state.
auto parent_doc_state = this->navigable()->active_session_history_entry()->document_state();

View file

@ -1630,7 +1630,7 @@ void TraversableNavigable::destroy_top_level_traversable()
// FIXME: Figure out why we need to do this... we shouldn't be leaking Navigables for all time.
// However, without this, we can keep stale destroyed traversables around.
set_has_been_destroyed();
all_navigables().remove(*this);
remove_from_all_navigables();
}
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#finalize-a-same-document-navigation

View file

@ -719,7 +719,7 @@ BrowsingContext* Window::browsing_context()
GC::Ptr<Navigable> Window::navigable() const
{
// A Window's navigable is the navigable whose active document is the Window's associated Document's, or null if there is no such navigable.
return Navigable::navigable_with_active_document(*m_associated_document);
return m_associated_document->navigable();
}
// https://html.spec.whatwg.org/multipage/system-state.html#pdf-viewer-plugin-objects