Treat pending session history entries as absent from the used step graph, and share that through a small step_value() helper so snapshotting, Navigation API entry construction, target-entry lookup, and forward clearing do not drift apart. Keep cross-document history application tied to the navigation id that created it. Queued changing-navigable work now finishes without applying when a later navigation has already replaced its target, and any traversal sentinel is cleared through the shared setter so queued navigations can drain. When navigation arrives while traversal is still ongoing, keep only the newest pending navigation. This matches Chromium, WebKit, and Gecko on sites that click through product or category links while prior loads settle. Revalidate queued same-document child continuations before running them from null-document tasks, so removed frames or frames claimed by newer navigations do not receive stale history state. Preserve nested-history descriptors even when all child entries are pending, keeping live child navigable identity available for later UI process history seeds. Add regression coverage for iframe renavigation during history commit, for pending child history followed by a real navigation, and for removed iframes with queued history updates.
217 lines
9.6 KiB
C++
217 lines
9.6 KiB
C++
/*
|
|
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <AK/HashMap.h>
|
|
#include <AK/Optional.h>
|
|
#include <AK/RefCounted.h>
|
|
#include <AK/RefPtr.h>
|
|
#include <AK/String.h>
|
|
#include <AK/Types.h>
|
|
#include <AK/Vector.h>
|
|
#include <LibURL/URL.h>
|
|
#include <LibWeb/Export.h>
|
|
#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
|
|
#include <LibWeb/Forward.h>
|
|
#include <LibWeb/HTML/DocumentState.h>
|
|
#include <LibWeb/HTML/StructuredSerializeTypes.h>
|
|
#include <LibWeb/PixelUnits.h>
|
|
#include <LibWeb/ReferrerPolicy/ReferrerPolicy.h>
|
|
|
|
namespace Web::HTML {
|
|
|
|
// https://html.spec.whatwg.org/multipage/history.html#scroll-restoration-mode
|
|
enum class ScrollRestorationMode {
|
|
// https://html.spec.whatwg.org/multipage/history.html#dom-scrollrestoration-auto
|
|
// The user agent is responsible for restoring the scroll position upon navigation.
|
|
Auto,
|
|
|
|
// https://html.spec.whatwg.org/multipage/history.html#dom-scrollrestoration-manual
|
|
// The page is responsible for restoring the scroll position and the user agent does not attempt to do so automatically.
|
|
Manual,
|
|
};
|
|
|
|
struct SessionHistoryNestedHistoryDescriptor;
|
|
|
|
// IPC-friendly descriptors for the parts of session history entries and document states that can survive
|
|
// WebContent process swaps.
|
|
//
|
|
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#session-history-entry
|
|
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#document-state
|
|
struct SessionHistoryDocumentStateDescriptor {
|
|
// AD-HOC: The spec models shared document state by object identity. The UI-process mirror uses a stable
|
|
// descriptor ID so entries that share a document state can be reconstructed after IPC.
|
|
u64 id { 0 };
|
|
Variant<SerializedPolicyContainer, DocumentState::Client> history_policy_container { DocumentState::Client::Tag };
|
|
Fetch::Infrastructure::Request::ReferrerType request_referrer { Fetch::Infrastructure::Request::Referrer::Client };
|
|
ReferrerPolicy::ReferrerPolicy request_referrer_policy { ReferrerPolicy::DEFAULT_REFERRER_POLICY };
|
|
Optional<URL::Origin> initiator_origin;
|
|
Optional<URL::Origin> origin;
|
|
Optional<URL::URL> about_base_url;
|
|
Variant<Empty, String, POSTResource> resource;
|
|
bool reload_pending { false };
|
|
bool ever_populated { false };
|
|
String navigable_target_name;
|
|
Vector<SessionHistoryNestedHistoryDescriptor> nested_histories;
|
|
};
|
|
|
|
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#she-scroll-position
|
|
struct SessionHistoryEntryScrollPositionData {
|
|
// FIXME: Track all restorable scrollable regions. Currently only the viewport is persisted.
|
|
Optional<CSSPixelPoint> viewport_scroll_position;
|
|
|
|
bool operator==(SessionHistoryEntryScrollPositionData const&) const = default;
|
|
};
|
|
|
|
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#session-history-entry
|
|
struct SessionHistoryEntryDescriptor {
|
|
i32 step { 0 };
|
|
URL::URL url;
|
|
SessionHistoryDocumentStateDescriptor document_state;
|
|
SerializationRecord classic_history_api_state;
|
|
SerializationRecord navigation_api_state;
|
|
String navigation_api_key;
|
|
String navigation_api_id;
|
|
ScrollRestorationMode scroll_restoration_mode { ScrollRestorationMode::Auto };
|
|
SessionHistoryEntryScrollPositionData scroll_position_data;
|
|
};
|
|
|
|
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#nested-history
|
|
struct SessionHistoryNestedHistoryDescriptor {
|
|
String id;
|
|
Vector<SessionHistoryEntryDescriptor> entries;
|
|
};
|
|
|
|
// https://html.spec.whatwg.org/multipage/history.html#session-history-entry
|
|
class WEB_API SessionHistoryEntry final : public RefCounted<SessionHistoryEntry> {
|
|
public:
|
|
static NonnullRefPtr<SessionHistoryEntry> create();
|
|
|
|
SessionHistoryEntry();
|
|
~SessionHistoryEntry();
|
|
|
|
enum class Pending {
|
|
Tag,
|
|
};
|
|
|
|
[[nodiscard]] Variant<int, Pending> step() const { return m_step; }
|
|
void set_step(Variant<int, Pending> step) { m_step = step; }
|
|
[[nodiscard]] Optional<int> step_value() const
|
|
{
|
|
if (auto const* step = m_step.get_pointer<int>())
|
|
return *step;
|
|
return {};
|
|
}
|
|
|
|
[[nodiscard]] URL::URL const& url() const { return m_url; }
|
|
void set_url(URL::URL url) { m_url = move(url); }
|
|
|
|
[[nodiscard]] RefPtr<HTML::DocumentState> document_state() const;
|
|
void set_document_state(RefPtr<HTML::DocumentState>);
|
|
|
|
[[nodiscard]] SerializationRecord const& classic_history_api_state() const { return m_classic_history_api_state; }
|
|
void set_classic_history_api_state(SerializationRecord classic_history_api_state) { m_classic_history_api_state = move(classic_history_api_state); }
|
|
|
|
[[nodiscard]] SerializationRecord const& navigation_api_state() const { return m_navigation_api_state; }
|
|
void set_navigation_api_state(SerializationRecord navigation_api_state) { m_navigation_api_state = move(navigation_api_state); }
|
|
|
|
[[nodiscard]] String const& navigation_api_key() const { return m_navigation_api_key; }
|
|
void set_navigation_api_key(String navigation_api_key) { m_navigation_api_key = move(navigation_api_key); }
|
|
|
|
[[nodiscard]] String const& navigation_api_id() const { return m_navigation_api_id; }
|
|
void set_navigation_api_id(String navigation_api_id) { m_navigation_api_id = move(navigation_api_id); }
|
|
|
|
[[nodiscard]] ScrollRestorationMode scroll_restoration_mode() const { return m_scroll_restoration_mode; }
|
|
void set_scroll_restoration_mode(ScrollRestorationMode scroll_restoration_mode) { m_scroll_restoration_mode = scroll_restoration_mode; }
|
|
|
|
[[nodiscard]] SessionHistoryEntryScrollPositionData const& scroll_position_data() const { return m_scroll_position_data; }
|
|
void set_scroll_position_data(SessionHistoryEntryScrollPositionData scroll_position_data) { m_scroll_position_data = move(scroll_position_data); }
|
|
|
|
private:
|
|
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#she-step
|
|
// step, a non-negative integer or "pending", initially "pending".
|
|
Variant<int, Pending> m_step { Pending::Tag };
|
|
|
|
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#she-url
|
|
// URL, a URL
|
|
URL::URL m_url;
|
|
|
|
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#she-document-state
|
|
RefPtr<HTML::DocumentState> m_document_state;
|
|
|
|
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#she-classic-history-api-state
|
|
// classic history API state, which is serialized state, initially StructuredSerializeForStorage(null).
|
|
SerializationRecord m_classic_history_api_state;
|
|
|
|
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#she-navigation-api-state
|
|
// navigation API state, which is a serialized state, initially StructuredSerializeForStorage(undefined).
|
|
SerializationRecord m_navigation_api_state;
|
|
|
|
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#she-navigation-api-key
|
|
// navigation API key, which is a string, initially set to the result of generating a random UUID.
|
|
String m_navigation_api_key;
|
|
|
|
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#she-navigation-api-id
|
|
// navigation API ID, which is a string, initially set to the result of generating a random UUID.
|
|
String m_navigation_api_id;
|
|
|
|
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#she-scroll-restoration-mode
|
|
// scroll restoration mode, a scroll restoration mode, initially "auto"
|
|
ScrollRestorationMode m_scroll_restoration_mode { ScrollRestorationMode::Auto };
|
|
|
|
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#she-scroll-position
|
|
// scroll position data, which is scroll position data for the document's restorable scrollable regions
|
|
SessionHistoryEntryScrollPositionData m_scroll_position_data;
|
|
|
|
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#she-other
|
|
// FIXME: persisted user state, which is implementation-defined, initially null
|
|
// NOTE: This is where we could remember the state of form controls, for example.
|
|
};
|
|
|
|
struct SessionHistoryEntryDescriptorCreationState {
|
|
HashMap<DocumentState const*, u64> document_state_ids;
|
|
u64 next_document_state_id { 1 };
|
|
};
|
|
|
|
WEB_API SessionHistoryEntryDescriptor create_session_history_entry_descriptor(SessionHistoryEntry const&, SessionHistoryEntryDescriptorCreationState&);
|
|
WEB_API bool session_history_entry_descriptors_match(SessionHistoryEntryDescriptor const&, SessionHistoryEntryDescriptor const&);
|
|
enum class MatchNestedHistories {
|
|
Yes,
|
|
No,
|
|
};
|
|
WEB_API bool session_history_entry_descriptors_match_ignoring_document_state_id(SessionHistoryEntryDescriptor const&, SessionHistoryEntryDescriptor const&, MatchNestedHistories = MatchNestedHistories::Yes);
|
|
WEB_API bool session_history_entry_matches_descriptor_ignoring_document_state_id(SessionHistoryEntry const&, SessionHistoryEntryDescriptor const&, MatchNestedHistories = MatchNestedHistories::Yes);
|
|
|
|
}
|
|
|
|
namespace IPC {
|
|
|
|
template<>
|
|
WEB_API ErrorOr<void> encode(Encoder&, Web::HTML::SessionHistoryEntryDescriptor const&);
|
|
|
|
template<>
|
|
WEB_API ErrorOr<Web::HTML::SessionHistoryEntryDescriptor> decode(Decoder&);
|
|
|
|
template<>
|
|
WEB_API ErrorOr<void> encode(Encoder&, Web::HTML::SessionHistoryEntryScrollPositionData const&);
|
|
|
|
template<>
|
|
WEB_API ErrorOr<Web::HTML::SessionHistoryEntryScrollPositionData> decode(Decoder&);
|
|
|
|
template<>
|
|
WEB_API ErrorOr<void> encode(Encoder&, Web::HTML::SessionHistoryDocumentStateDescriptor const&);
|
|
|
|
template<>
|
|
WEB_API ErrorOr<Web::HTML::SessionHistoryDocumentStateDescriptor> decode(Decoder&);
|
|
|
|
template<>
|
|
WEB_API ErrorOr<void> encode(Encoder&, Web::HTML::SessionHistoryNestedHistoryDescriptor const&);
|
|
|
|
template<>
|
|
WEB_API ErrorOr<Web::HTML::SessionHistoryNestedHistoryDescriptor> decode(Decoder&);
|
|
|
|
}
|