LibWeb: Add session history serialization support
Add structured helpers for the history data mirrored by LibWebView. Cover POST resources, history state, navigation API state, scroll positions, and entry metadata. Keep this below the UI model so browser-side history code can move data through typed objects instead of ad-hoc strings.
This commit is contained in:
parent
6c13a3f37d
commit
9e2ec2dc5c
7 changed files with 311 additions and 3 deletions
|
|
@ -656,6 +656,7 @@ set(SOURCES
|
|||
HTML/Plugin.cpp
|
||||
HTML/PluginArray.cpp
|
||||
HTML/PolicyContainers.cpp
|
||||
HTML/POSTResource.cpp
|
||||
HTML/PopoverTargetAttributes.cpp
|
||||
HTML/PopStateEvent.cpp
|
||||
HTML/PotentialCORSRequest.cpp
|
||||
|
|
|
|||
49
Libraries/LibWeb/HTML/POSTResource.cpp
Normal file
49
Libraries/LibWeb/HTML/POSTResource.cpp
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present, the Ladybird developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibIPC/Decoder.h>
|
||||
#include <LibIPC/Encoder.h>
|
||||
#include <LibWeb/HTML/POSTResource.h>
|
||||
|
||||
namespace IPC {
|
||||
|
||||
template<>
|
||||
ErrorOr<void> encode(Encoder& encoder, Web::HTML::POSTResource::Directive const& directive)
|
||||
{
|
||||
TRY(encoder.encode(directive.type));
|
||||
TRY(encoder.encode(directive.value));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<Web::HTML::POSTResource::Directive> decode(Decoder& decoder)
|
||||
{
|
||||
return Web::HTML::POSTResource::Directive {
|
||||
.type = TRY(decoder.decode<String>()),
|
||||
.value = TRY(decoder.decode<String>()),
|
||||
};
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<void> encode(Encoder& encoder, Web::HTML::POSTResource const& post_resource)
|
||||
{
|
||||
TRY(encoder.encode(post_resource.request_body));
|
||||
TRY(encoder.encode(post_resource.request_content_type));
|
||||
TRY(encoder.encode(post_resource.request_content_type_directives));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<Web::HTML::POSTResource> decode(Decoder& decoder)
|
||||
{
|
||||
return Web::HTML::POSTResource {
|
||||
.request_body = TRY(decoder.decode<Optional<ByteBuffer>>()),
|
||||
.request_content_type = TRY(decoder.decode<Web::HTML::POSTResource::RequestContentType>()),
|
||||
.request_content_type_directives = TRY(decoder.decode<Vector<Web::HTML::POSTResource::Directive>>()),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -8,8 +8,9 @@
|
|||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibIPC/Forward.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
|
|
@ -31,10 +32,30 @@ struct POSTResource {
|
|||
RequestContentType request_content_type {};
|
||||
|
||||
struct Directive {
|
||||
StringView type;
|
||||
String type;
|
||||
String value;
|
||||
|
||||
bool operator==(Directive const&) const = default;
|
||||
};
|
||||
Vector<Directive> request_content_type_directives {};
|
||||
|
||||
bool operator==(POSTResource const&) const = default;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace IPC {
|
||||
|
||||
template<>
|
||||
WEB_API ErrorOr<void> encode(Encoder&, Web::HTML::POSTResource::Directive const&);
|
||||
|
||||
template<>
|
||||
WEB_API ErrorOr<Web::HTML::POSTResource::Directive> decode(Decoder&);
|
||||
|
||||
template<>
|
||||
WEB_API ErrorOr<void> encode(Encoder&, Web::HTML::POSTResource const&);
|
||||
|
||||
template<>
|
||||
WEB_API ErrorOr<Web::HTML::POSTResource> decode(Decoder&);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibIPC/Decoder.h>
|
||||
#include <LibIPC/Encoder.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibWeb/Crypto/Crypto.h>
|
||||
#include <LibWeb/HTML/DocumentState.h>
|
||||
|
|
@ -31,3 +33,127 @@ SessionHistoryEntry::SessionHistoryEntry()
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<void> IPC::encode(Encoder& encoder, Web::HTML::SessionHistoryEntryDescriptor const& entry)
|
||||
{
|
||||
TRY(encoder.encode(entry.step));
|
||||
TRY(encoder.encode(entry.url));
|
||||
TRY(encoder.encode(entry.document_state));
|
||||
TRY(encoder.encode(entry.classic_history_api_state));
|
||||
TRY(encoder.encode(entry.navigation_api_state));
|
||||
TRY(encoder.encode(entry.navigation_api_key));
|
||||
TRY(encoder.encode(entry.navigation_api_id));
|
||||
TRY(encoder.encode(entry.scroll_restoration_mode));
|
||||
TRY(encoder.encode(entry.scroll_position_data));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<Web::HTML::SessionHistoryEntryDescriptor> IPC::decode(Decoder& decoder)
|
||||
{
|
||||
auto step = TRY(decoder.decode<i32>());
|
||||
auto url = TRY(decoder.decode<URL::URL>());
|
||||
auto document_state = TRY(decoder.decode<Web::HTML::SessionHistoryDocumentStateDescriptor>());
|
||||
auto classic_history_api_state = TRY(decoder.decode<Web::HTML::SerializationRecord>());
|
||||
auto navigation_api_state = TRY(decoder.decode<Web::HTML::SerializationRecord>());
|
||||
auto navigation_api_key = TRY(decoder.decode<String>());
|
||||
auto navigation_api_id = TRY(decoder.decode<String>());
|
||||
auto scroll_restoration_mode = TRY(decoder.decode<Web::HTML::ScrollRestorationMode>());
|
||||
auto scroll_position_data = TRY(decoder.decode<Web::HTML::SessionHistoryEntryScrollPositionData>());
|
||||
|
||||
return Web::HTML::SessionHistoryEntryDescriptor {
|
||||
.step = step,
|
||||
.url = move(url),
|
||||
.document_state = move(document_state),
|
||||
.classic_history_api_state = move(classic_history_api_state),
|
||||
.navigation_api_state = move(navigation_api_state),
|
||||
.navigation_api_key = move(navigation_api_key),
|
||||
.navigation_api_id = move(navigation_api_id),
|
||||
.scroll_restoration_mode = scroll_restoration_mode,
|
||||
.scroll_position_data = move(scroll_position_data),
|
||||
};
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<void> IPC::encode(Encoder& encoder, Web::HTML::SessionHistoryEntryScrollPositionData const& scroll_position_data)
|
||||
{
|
||||
TRY(encoder.encode(scroll_position_data.viewport_scroll_position));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<Web::HTML::SessionHistoryEntryScrollPositionData> IPC::decode(Decoder& decoder)
|
||||
{
|
||||
auto viewport_scroll_position = TRY(decoder.decode<Optional<Web::CSSPixelPoint>>());
|
||||
return Web::HTML::SessionHistoryEntryScrollPositionData {
|
||||
.viewport_scroll_position = move(viewport_scroll_position),
|
||||
};
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<void> IPC::encode(Encoder& encoder, Web::HTML::SessionHistoryDocumentStateDescriptor const& document_state)
|
||||
{
|
||||
TRY(encoder.encode(document_state.id));
|
||||
TRY(encoder.encode(document_state.history_policy_container));
|
||||
TRY(encoder.encode(document_state.request_referrer));
|
||||
TRY(encoder.encode(document_state.request_referrer_policy));
|
||||
TRY(encoder.encode(document_state.initiator_origin));
|
||||
TRY(encoder.encode(document_state.origin));
|
||||
TRY(encoder.encode(document_state.about_base_url));
|
||||
TRY(encoder.encode(document_state.resource));
|
||||
TRY(encoder.encode(document_state.reload_pending));
|
||||
TRY(encoder.encode(document_state.ever_populated));
|
||||
TRY(encoder.encode(document_state.navigable_target_name));
|
||||
TRY(encoder.encode(document_state.nested_histories));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<Web::HTML::SessionHistoryDocumentStateDescriptor> IPC::decode(Decoder& decoder)
|
||||
{
|
||||
auto id = TRY(decoder.decode<u64>());
|
||||
auto history_policy_container = TRY(decoder.decode<Variant<Web::HTML::SerializedPolicyContainer, Web::HTML::DocumentState::Client>>());
|
||||
auto request_referrer = TRY(decoder.decode<Web::Fetch::Infrastructure::Request::ReferrerType>());
|
||||
auto request_referrer_policy = TRY(decoder.decode<Web::ReferrerPolicy::ReferrerPolicy>());
|
||||
auto initiator_origin = TRY(decoder.decode<Optional<URL::Origin>>());
|
||||
auto origin = TRY(decoder.decode<Optional<URL::Origin>>());
|
||||
auto about_base_url = TRY(decoder.decode<Optional<URL::URL>>());
|
||||
auto resource = TRY(decoder.decode<Variant<Empty, String, Web::HTML::POSTResource>>());
|
||||
auto reload_pending = TRY(decoder.decode<bool>());
|
||||
auto ever_populated = TRY(decoder.decode<bool>());
|
||||
auto navigable_target_name = TRY(decoder.decode<String>());
|
||||
auto nested_histories = TRY(decoder.decode<Vector<Web::HTML::SessionHistoryNestedHistoryDescriptor>>());
|
||||
|
||||
return Web::HTML::SessionHistoryDocumentStateDescriptor {
|
||||
.id = id,
|
||||
.history_policy_container = move(history_policy_container),
|
||||
.request_referrer = move(request_referrer),
|
||||
.request_referrer_policy = request_referrer_policy,
|
||||
.initiator_origin = move(initiator_origin),
|
||||
.origin = move(origin),
|
||||
.about_base_url = move(about_base_url),
|
||||
.resource = move(resource),
|
||||
.reload_pending = reload_pending,
|
||||
.ever_populated = ever_populated,
|
||||
.navigable_target_name = move(navigable_target_name),
|
||||
.nested_histories = move(nested_histories),
|
||||
};
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<void> IPC::encode(Encoder& encoder, Web::HTML::SessionHistoryNestedHistoryDescriptor const& nested_history)
|
||||
{
|
||||
TRY(encoder.encode(nested_history.id));
|
||||
TRY(encoder.encode(nested_history.entries));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<Web::HTML::SessionHistoryNestedHistoryDescriptor> IPC::decode(Decoder& decoder)
|
||||
{
|
||||
auto id = TRY(decoder.decode<String>());
|
||||
auto entries = TRY(decoder.decode<Vector<Web::HTML::SessionHistoryEntryDescriptor>>());
|
||||
|
||||
return Web::HTML::SessionHistoryNestedHistoryDescriptor { move(id), move(entries) };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,16 @@
|
|||
|
||||
#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/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 {
|
||||
|
||||
|
|
@ -25,6 +32,57 @@ enum class ScrollRestorationMode {
|
|||
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 SessionHistoryEntry final : public RefCounted<SessionHistoryEntry> {
|
||||
public:
|
||||
|
|
@ -61,6 +119,9 @@ public:
|
|||
[[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".
|
||||
|
|
@ -94,7 +155,8 @@ private:
|
|||
ScrollRestorationMode m_scroll_restoration_mode { ScrollRestorationMode::Auto };
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#she-scroll-position
|
||||
// FIXME: scroll position data, which is scroll position data for the document's restorable scrollable regions
|
||||
// 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
|
||||
|
|
@ -102,3 +164,31 @@ private:
|
|||
};
|
||||
|
||||
}
|
||||
|
||||
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&);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,22 @@ namespace Web {
|
|||
|
||||
namespace IPC {
|
||||
|
||||
template<>
|
||||
ErrorOr<void> encode(Encoder& encoder, Web::CSSPixelPoint const& value)
|
||||
{
|
||||
TRY(encoder.encode(value.x().raw_value()));
|
||||
TRY(encoder.encode(value.y().raw_value()));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<Web::CSSPixelPoint> decode(Decoder& decoder)
|
||||
{
|
||||
auto x = TRY(decoder.decode<i32>());
|
||||
auto y = TRY(decoder.decode<i32>());
|
||||
return Web::CSSPixelPoint { Web::CSSPixels::from_raw(x), Web::CSSPixels::from_raw(y) };
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<void> encode(Encoder& encoder, Web::DevicePixelPoint const& value)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -536,6 +536,11 @@ struct Formatter<Web::DevicePixels> : Formatter<Web::DevicePixels::Type> {
|
|||
|
||||
namespace IPC {
|
||||
|
||||
template<>
|
||||
WEB_API ErrorOr<void> encode(Encoder& encoder, Web::CSSPixelPoint const& value);
|
||||
template<>
|
||||
WEB_API ErrorOr<Web::CSSPixelPoint> decode(Decoder& decoder);
|
||||
|
||||
template<>
|
||||
WEB_API ErrorOr<void> encode(Encoder& encoder, Web::DevicePixelPoint const& value);
|
||||
template<>
|
||||
|
|
|
|||
Loading…
Reference in a new issue