LibWeb/HTML: Use relevant global object's document in History
Some checks are pending
CI / Lagom (arm64, Sanitizer_CI, false, macos-15, macOS, Clang) (push) Waiting to run
CI / Lagom (x86_64, Fuzzers_CI, false, ubuntu-24.04, Linux, Clang) (push) Waiting to run
CI / Lagom (x86_64, Sanitizer_CI, false, ubuntu-24.04, Linux, GNU) (push) Waiting to run
CI / Lagom (x86_64, Sanitizer_CI, true, ubuntu-24.04, Linux, Clang) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (arm64, macos-15, macOS, macOS-universal2) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (x86_64, ubuntu-24.04, Linux, Linux-x86_64) (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run

Corresponds to dba6e7b6c2
and 4c0401186c

The spec algorithms now use "the relevant global object's associated
document", so remove the concept of the History object itself having an
associated document. The spec has also combined the implementations for
forward/back/go so I've matched that too.
This commit is contained in:
Sam Atkins 2025-04-11 17:24:50 +01:00 committed by Andreas Kling
commit 141f6cb392
Notes: github-actions[bot] 2025-04-18 09:08:14 +00:00
3 changed files with 52 additions and 50 deletions

View file

@ -3797,7 +3797,7 @@ CSS::StyleSheetList const& Document::style_sheets() const
GC::Ref<HTML::History> Document::history() GC::Ref<HTML::History> Document::history()
{ {
if (!m_history) if (!m_history)
m_history = HTML::History::create(realm(), *this); m_history = HTML::History::create(realm());
return *m_history; return *m_history;
} }

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org> * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -17,14 +18,13 @@ namespace Web::HTML {
GC_DEFINE_ALLOCATOR(History); GC_DEFINE_ALLOCATOR(History);
GC::Ref<History> History::create(JS::Realm& realm, DOM::Document& document) GC::Ref<History> History::create(JS::Realm& realm)
{ {
return realm.create<History>(realm, document); return realm.create<History>(realm);
} }
History::History(JS::Realm& realm, DOM::Document& document) History::History(JS::Realm& realm)
: PlatformObject(realm) : PlatformObject(realm)
, m_associated_document(document)
{ {
} }
@ -39,7 +39,6 @@ void History::initialize(JS::Realm& realm)
void History::visit_edges(Cell::Visitor& visitor) void History::visit_edges(Cell::Visitor& visitor)
{ {
Base::visit_edges(visitor); Base::visit_edges(visitor);
visitor.visit(m_associated_document);
visitor.visit(m_state); visitor.visit(m_state);
} }
@ -61,7 +60,7 @@ WebIDL::ExceptionOr<void> History::replace_state(JS::Value data, String const&,
WebIDL::ExceptionOr<u64> History::length() const WebIDL::ExceptionOr<u64> History::length() const
{ {
// 1. If this's relevant global object's associated Document is not fully active, then throw a "SecurityError" DOMException. // 1. If this's relevant global object's associated Document is not fully active, then throw a "SecurityError" DOMException.
if (!m_associated_document->is_fully_active()) if (!as<Window>(relevant_global_object(*this)).associated_document().is_fully_active())
return WebIDL::SecurityError::create(realm(), "Cannot perform length on a document that isn't fully active."_string); return WebIDL::SecurityError::create(realm(), "Cannot perform length on a document that isn't fully active."_string);
// 2. Return this's length. // 2. Return this's length.
@ -72,7 +71,7 @@ WebIDL::ExceptionOr<u64> History::length() const
WebIDL::ExceptionOr<JS::Value> History::state() const WebIDL::ExceptionOr<JS::Value> History::state() const
{ {
// 1. If this's relevant global object's associated Document is not fully active, then throw a "SecurityError" DOMException. // 1. If this's relevant global object's associated Document is not fully active, then throw a "SecurityError" DOMException.
if (!m_associated_document->is_fully_active()) if (!as<Window>(relevant_global_object(*this)).associated_document().is_fully_active())
return WebIDL::SecurityError::create(realm(), "Cannot perform state on a document that isn't fully active."_string); return WebIDL::SecurityError::create(realm(), "Cannot perform state on a document that isn't fully active."_string);
// 2. Return this's state. // 2. Return this's state.
@ -84,48 +83,49 @@ JS::Value History::unsafe_state() const
return m_state; return m_state;
} }
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#delta-traverse
WebIDL::ExceptionOr<void> History::delta_traverse(WebIDL::Long delta)
{
// 1. Let document be history's relevant global object's associated Document.
auto& document = as<Window>(relevant_global_object(*this)).associated_document();
// 2. If document is not fully active, then throw a "SecurityError" DOMException.
if (!document.is_fully_active())
return WebIDL::SecurityError::create(realm(), "Cannot perform go on a document that isn't fully active."_string);
// 3. If delta is 0, then reload document's node navigable, and return.
if (delta == 0) {
VERIFY(document.navigable());
document.navigable()->reload();
return {};
}
// 4. Traverse the history by a delta given document's node navigable's traversable navigable, delta, and with
// sourceDocument set to document.
document.navigable()->traversable_navigable()->traverse_the_history_by_delta(delta, document);
return {};
}
// https://html.spec.whatwg.org/multipage/history.html#dom-history-go // https://html.spec.whatwg.org/multipage/history.html#dom-history-go
WebIDL::ExceptionOr<void> History::go(WebIDL::Long delta = 0) WebIDL::ExceptionOr<void> History::go(WebIDL::Long delta = 0)
{ {
// 1. Let document be this's associated Document. // The go(delta) method steps are to delta traverse this given delta.
return delta_traverse(delta);
// 2. If document is not fully active, then throw a "SecurityError" DOMException.
if (!m_associated_document->is_fully_active())
return WebIDL::SecurityError::create(realm(), "Cannot perform go on a document that isn't fully active."_string);
VERIFY(m_associated_document->navigable());
// 3. If delta is 0, then reload document's node navigable.
if (delta == 0)
m_associated_document->navigable()->reload();
// 4. Traverse the history by a delta given document's node navigable's traversable navigable, delta, and with sourceDocument set to document.
auto traversable = m_associated_document->navigable()->traversable_navigable();
traversable->traverse_the_history_by_delta(delta);
return {};
} }
// https://html.spec.whatwg.org/multipage/history.html#dom-history-back // https://html.spec.whatwg.org/multipage/history.html#dom-history-back
WebIDL::ExceptionOr<void> History::back() WebIDL::ExceptionOr<void> History::back()
{ {
// 1. Let document be this's associated Document. // The back() method steps are to delta traverse this given 1.
// 2. If document is not fully active, then throw a "SecurityError" DOMException. return delta_traverse(-1);
// NOTE: We already did this check in `go` method, so skip the fully active check here.
// 3. Traverse the history by a delta with 1 and document's browsing context.
return go(-1);
} }
// https://html.spec.whatwg.org/multipage/history.html#dom-history-forward // https://html.spec.whatwg.org/multipage/history.html#dom-history-forward
WebIDL::ExceptionOr<void> History::forward() WebIDL::ExceptionOr<void> History::forward()
{ {
// 1. Let document be this's associated Document. // The forward() method steps are to delta traverse this given +1.
// 2. If document is not fully active, then throw a "SecurityError" DOMException. return delta_traverse(1);
// NOTE: We already did this check in `go` method, so skip the fully active check here.
// 3. Traverse the history by a delta with +1 and document's browsing context.
return go(1);
} }
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#can-have-its-url-rewritten // https://html.spec.whatwg.org/multipage/nav-history-apis.html#can-have-its-url-rewritten
@ -173,11 +173,11 @@ WebIDL::ExceptionOr<void> History::shared_history_push_replace_state(JS::Value d
{ {
auto& vm = this->vm(); auto& vm = this->vm();
// 1. Let document be history's associated Document. // 1. Let document be history's relevant global object's associated Document.
auto& document = m_associated_document; auto& document = as<Window>(relevant_global_object(*this)).associated_document();
// 2. If document is not fully active, then throw a "SecurityError" DOMException. // 2. If document is not fully active, then throw a "SecurityError" DOMException.
if (!document->is_fully_active()) if (!document.is_fully_active())
return WebIDL::SecurityError::create(realm(), "Cannot perform pushState or replaceState on a document that isn't fully active."_string); return WebIDL::SecurityError::create(realm(), "Cannot perform pushState or replaceState on a document that isn't fully active."_string);
// 3. Optionally, throw a "SecurityError" DOMException. (For example, the user agent might disallow calls to these // 3. Optionally, throw a "SecurityError" DOMException. (For example, the user agent might disallow calls to these
@ -191,7 +191,7 @@ WebIDL::ExceptionOr<void> History::shared_history_push_replace_state(JS::Value d
auto serialized_data = serialized_data_or_error.is_error() ? MUST(structured_serialize_for_storage(vm, JS::js_null())) : serialized_data_or_error.release_value(); auto serialized_data = serialized_data_or_error.is_error() ? MUST(structured_serialize_for_storage(vm, JS::js_null())) : serialized_data_or_error.release_value();
// 5. Let newURL be document's URL. // 5. Let newURL be document's URL.
auto new_url = document->url(); auto new_url = document.url();
// 6. If url is not null or the empty string, then: // 6. If url is not null or the empty string, then:
if (url.has_value() && !url->is_empty()) { if (url.has_value() && !url->is_empty()) {
@ -234,11 +234,12 @@ WebIDL::ExceptionOr<void> History::shared_history_push_replace_state(JS::Value d
WebIDL::ExceptionOr<Bindings::ScrollRestoration> History::scroll_restoration() const WebIDL::ExceptionOr<Bindings::ScrollRestoration> History::scroll_restoration() const
{ {
// 1. If this's relevant global object's associated Document is not fully active, then throw a "SecurityError" DOMException. // 1. If this's relevant global object's associated Document is not fully active, then throw a "SecurityError" DOMException.
if (!m_associated_document->is_fully_active()) auto& this_relevant_global_object = as<Window>(relevant_global_object(*this));
if (!this_relevant_global_object.associated_document().is_fully_active())
return WebIDL::SecurityError::create(realm(), "Cannot obtain scroll restoration mode for a document that isn't fully active."_string); return WebIDL::SecurityError::create(realm(), "Cannot obtain scroll restoration mode for a document that isn't fully active."_string);
// 2. Return this's node navigable's active session history entry's scroll restoration mode. // 2. Return this's relevant global object's navigable's active session history entry's scroll restoration mode.
auto scroll_restoration_mode = m_associated_document->navigable()->active_session_history_entry()->scroll_restoration_mode(); auto scroll_restoration_mode = this_relevant_global_object.navigable()->active_session_history_entry()->scroll_restoration_mode();
switch (scroll_restoration_mode) { switch (scroll_restoration_mode) {
case ScrollRestorationMode::Auto: case ScrollRestorationMode::Auto:
return Bindings::ScrollRestoration::Auto; return Bindings::ScrollRestoration::Auto;
@ -252,11 +253,12 @@ WebIDL::ExceptionOr<Bindings::ScrollRestoration> History::scroll_restoration() c
WebIDL::ExceptionOr<void> History::set_scroll_restoration(Bindings::ScrollRestoration scroll_restoration) WebIDL::ExceptionOr<void> History::set_scroll_restoration(Bindings::ScrollRestoration scroll_restoration)
{ {
// 1. If this's relevant global object's associated Document is not fully active, then throw a "SecurityError" DOMException. // 1. If this's relevant global object's associated Document is not fully active, then throw a "SecurityError" DOMException.
if (!m_associated_document->is_fully_active()) auto& this_relevant_global_object = as<Window>(relevant_global_object(*this));
if (!this_relevant_global_object.associated_document().is_fully_active())
return WebIDL::SecurityError::create(realm(), "Cannot set scroll restoration mode for a document that isn't fully active."_string); return WebIDL::SecurityError::create(realm(), "Cannot set scroll restoration mode for a document that isn't fully active."_string);
// 2. Set this's node navigable's active session history entry's scroll restoration mode to the given value. // 2. Set this's relevant global object's navigable's active session history entry's scroll restoration mode to the given value.
auto active_session_history_entry = m_associated_document->navigable()->active_session_history_entry(); auto active_session_history_entry = this_relevant_global_object.navigable()->active_session_history_entry();
switch (scroll_restoration) { switch (scroll_restoration) {
case Bindings::ScrollRestoration::Auto: case Bindings::ScrollRestoration::Auto:
active_session_history_entry->set_scroll_restoration_mode(ScrollRestorationMode::Auto); active_session_history_entry->set_scroll_restoration_mode(ScrollRestorationMode::Auto);

View file

@ -20,7 +20,7 @@ class History final : public Bindings::PlatformObject {
GC_DECLARE_ALLOCATOR(History); GC_DECLARE_ALLOCATOR(History);
public: public:
[[nodiscard]] static GC::Ref<History> create(JS::Realm&, DOM::Document&); [[nodiscard]] static GC::Ref<History> create(JS::Realm&);
virtual ~History() override; virtual ~History() override;
@ -41,14 +41,14 @@ public:
void set_state(JS::Value s) { m_state = s; } void set_state(JS::Value s) { m_state = s; }
private: private:
History(JS::Realm&, DOM::Document&); History(JS::Realm&);
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;
WebIDL::ExceptionOr<void> delta_traverse(WebIDL::Long delta);
WebIDL::ExceptionOr<void> shared_history_push_replace_state(JS::Value data, Optional<String> const& url, HistoryHandlingBehavior); WebIDL::ExceptionOr<void> shared_history_push_replace_state(JS::Value data, Optional<String> const& url, HistoryHandlingBehavior);
GC::Ref<DOM::Document> m_associated_document;
JS::Value m_state { JS::js_null() }; JS::Value m_state { JS::js_null() };
}; };