ladybird/Libraries/LibWeb/HTML/NavigationHistoryEntry.cpp
Aliaksandr Kalenik 2645695fdd LibWeb: Make Navigable directly own its active document
Previously, the active document's lifecycle was bound to
SessionHistoryEntry via DocumentState. The ownership chain was:
  Navigable → SessionHistoryEntry → DocumentState → Document

This made it impossible to move SessionHistoryEntry to the UI process
(which cannot own DOM::Document). This commit decouples the two by
giving Navigable a direct m_active_document field that serves as the
authoritative source for active_document().

- Navigable owns m_active_document directly; active_document() reads
  from it instead of going through the active session history entry.

- DocumentState no longer holds a Document pointer. Instead, it stores
  a document_id for "same document?" checks. Same-document navigations
  share a DocumentState and thus the same document_id, while
  cross-document navigations create a new DocumentState with a new ID.

- A pending_document parameter is threaded through
  finalize_a_cross_document_navigation → apply_the_push_or_replace →
  apply_the_history_step so the newly created document reaches
  activation without being stored on DocumentState.

- For traversal, the population output delivers the document.
  A resolved_document is computed per continuation from either the
  pending document, the population output, or the current active
  document (for same-document traversals).
2026-04-01 11:51:43 +02:00

154 lines
6.4 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGC/Heap.h>
#include <LibJS/Runtime/Realm.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/NavigationHistoryEntryPrototype.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/HTML/DocumentState.h>
#include <LibWeb/HTML/Navigable.h>
#include <LibWeb/HTML/Navigation.h>
#include <LibWeb/HTML/NavigationHistoryEntry.h>
#include <LibWeb/HTML/SessionHistoryEntry.h>
#include <LibWeb/HTML/StructuredSerialize.h>
#include <LibWeb/HTML/Window.h>
namespace Web::HTML {
GC_DEFINE_ALLOCATOR(NavigationHistoryEntry);
GC::Ref<NavigationHistoryEntry> NavigationHistoryEntry::create(JS::Realm& realm, GC::Ref<SessionHistoryEntry> she)
{
return realm.create<NavigationHistoryEntry>(realm, she);
}
NavigationHistoryEntry::NavigationHistoryEntry(JS::Realm& realm, GC::Ref<SessionHistoryEntry> she)
: DOM::EventTarget(realm)
, m_session_history_entry(she)
{
}
NavigationHistoryEntry::~NavigationHistoryEntry() = default;
void NavigationHistoryEntry::initialize(JS::Realm& realm)
{
WEB_SET_PROTOTYPE_FOR_INTERFACE(NavigationHistoryEntry);
Base::initialize(realm);
}
void NavigationHistoryEntry::visit_edges(JS::Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_session_history_entry);
}
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigationhistoryentry-url
Optional<String> NavigationHistoryEntry::url() const
{
// The url getter steps are:
// 1. Let document be this's relevant global object's associated Document.
auto& document = as<HTML::Window>(relevant_global_object(*this)).associated_document();
// 2. If document is not fully active, then return the empty string.
if (!document.is_fully_active())
return String {};
// 3. Let she be this's session history entry.
auto const& she = this->m_session_history_entry;
// 4. If she's document does not equal document, and she's document state's request referrer policy
// is "no-referrer" or "origin", then return null.
if ((she->document_state()->document_id() != document.unique_id())
&& (she->document_state()->request_referrer_policy() == ReferrerPolicy::ReferrerPolicy::NoReferrer
|| she->document_state()->request_referrer_policy() == ReferrerPolicy::ReferrerPolicy::Origin))
return OptionalNone {};
// 5. Return she's URL, serialized.
return she->url().serialize();
}
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#concept-navigationhistoryentry-key
String NavigationHistoryEntry::key() const
{
// The key of a NavigationHistoryEntry nhe is given by the return value of the following algorithm:
// 1. If nhe's relevant global object's associated Document is not fully active, then return the empty string.
auto& associated_document = as<HTML::Window>(relevant_global_object(*this)).associated_document();
if (!associated_document.is_fully_active())
return {};
// 2. Return nhe's session history entry's navigation API key.
return m_session_history_entry->navigation_api_key();
}
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#concept-navigationhistoryentry-id
String NavigationHistoryEntry::id() const
{
// The ID of a NavigationHistoryEntry nhe is given by the return value of the following algorithm:
// 1. If nhe's relevant global object's associated Document is not fully active, then return the empty string.
auto& associated_document = as<HTML::Window>(relevant_global_object(*this)).associated_document();
if (!associated_document.is_fully_active())
return {};
// 2. Return nhe's session history entry's navigation API ID.
return m_session_history_entry->navigation_api_id();
}
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#concept-navigationhistoryentry-index
i64 NavigationHistoryEntry::index() const
{
// The index of a NavigationHistoryEntry nhe is given by the return value of the following algorithm:
// 1. If nhe's relevant global object's associated Document is not fully active, then return 1.
auto& this_relevant_global_object = as<HTML::Window>(relevant_global_object(*this));
if (!this_relevant_global_object.associated_document().is_fully_active())
return -1;
// 2. Return the result of getting the navigation API entry index of this's session history entry
// within this's relevant global object's navigation API.
return this_relevant_global_object.navigation()->get_the_navigation_api_entry_index(*m_session_history_entry);
}
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigationhistoryentry-samedocument
bool NavigationHistoryEntry::same_document() const
{
// The sameDocument getter steps are:
// 1. Let document be this's relevant global object's associated Document.
auto& document = as<HTML::Window>(relevant_global_object(*this)).associated_document();
// 2. If document is not fully active, then return false.
if (!document.is_fully_active())
return false;
// 3. Return true if this's session history entry's document equals document, and false otherwise.
return m_session_history_entry->document_state()->document_id() == document.unique_id();
}
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigationhistoryentry-getstate
WebIDL::ExceptionOr<JS::Value> NavigationHistoryEntry::get_state()
{
// The getState() method steps are:
// 1. If this's relevant global object's associated Document is not fully active, then return undefined.
auto& associated_document = as<HTML::Window>(relevant_global_object(*this)).associated_document();
if (!associated_document.is_fully_active())
return JS::js_undefined();
// 2. Return StructuredDeserialize(this's session history entry's navigation API state). Rethrow any exceptions.
// NOTE: This can in theory throw an exception, if attempting to deserialize a large ArrayBuffer
// when not enough memory is available.
return structured_deserialize(vm(), m_session_history_entry->navigation_api_state(), realm());
}
void NavigationHistoryEntry::set_ondispose(WebIDL::CallbackType* event_handler)
{
set_event_handler_attribute(HTML::EventNames::dispose, event_handler);
}
WebIDL::CallbackType* NavigationHistoryEntry::ondispose()
{
return event_handler_attribute(HTML::EventNames::dispose);
}
}