/* * Copyright (c) 2022, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include namespace Web::HTML { NonnullRefPtr SessionHistoryEntry::create() { return adopt_ref(*new SessionHistoryEntry()); } SessionHistoryEntry::~SessionHistoryEntry() = default; RefPtr SessionHistoryEntry::document_state() const { return m_document_state; } void SessionHistoryEntry::set_document_state(RefPtr document_state) { m_document_state = move(document_state); } SessionHistoryEntry::SessionHistoryEntry() : m_classic_history_api_state(MUST(structured_serialize_for_storage(JS::VM::the(), JS::js_null()))) , m_navigation_api_state(MUST(structured_serialize_for_storage(JS::VM::the(), JS::js_undefined()))) , m_navigation_api_key(Crypto::generate_random_uuid()) , m_navigation_api_id(Crypto::generate_random_uuid()) { } static u64 document_state_id_for_descriptor(DocumentState const& document_state, SessionHistoryEntryDescriptorCreationState& creation_state) { if (auto id = creation_state.document_state_ids.get(&document_state); id.has_value()) return *id; auto id = creation_state.next_document_state_id++; VERIFY(id != 0); creation_state.document_state_ids.set(&document_state, id); return id; } static SessionHistoryDocumentStateDescriptor create_session_history_document_state_descriptor(DocumentState const& document_state, SessionHistoryEntryDescriptorCreationState& creation_state) { Vector nested_history_descriptors; nested_history_descriptors.ensure_capacity(document_state.nested_histories().size()); for (auto const& nested_history : document_state.nested_histories()) { Vector nested_entry_descriptors; nested_entry_descriptors.ensure_capacity(nested_history.entries.size()); for (auto const& nested_entry : nested_history.entries) { // NB: UI-process session history mirrors only concrete used history steps. A child entry whose step is // still "pending" has not been attached to the traversable's step graph yet. if (!nested_entry->step_value().has_value()) continue; nested_entry_descriptors.unchecked_append(create_session_history_entry_descriptor(nested_entry, creation_state)); } // NB: Keep the nested-history descriptor even when every entry in it is still pending. The entries are not // used history steps yet, but the descriptor id preserves the live child navigable identity when the UI // process later reseeds an already-loaded document. nested_history_descriptors.unchecked_append({ .id = nested_history.id, .entries = move(nested_entry_descriptors), }); } return { .id = document_state_id_for_descriptor(document_state, creation_state), .history_policy_container = document_state.history_policy_container(), .request_referrer = document_state.request_referrer(), .request_referrer_policy = document_state.request_referrer_policy(), .initiator_origin = document_state.initiator_origin(), .origin = document_state.origin(), .about_base_url = document_state.about_base_url(), .resource = document_state.resource(), .reload_pending = document_state.reload_pending(), .ever_populated = document_state.ever_populated(), .navigable_target_name = document_state.navigable_target_name(), .nested_histories = move(nested_history_descriptors), }; } SessionHistoryEntryDescriptor create_session_history_entry_descriptor(SessionHistoryEntry const& entry, SessionHistoryEntryDescriptorCreationState& creation_state) { auto entry_step = entry.step_value(); VERIFY(entry_step.has_value()); SessionHistoryDocumentStateDescriptor document_state_descriptor; if (auto document_state = entry.document_state()) document_state_descriptor = create_session_history_document_state_descriptor(*document_state, creation_state); return { .step = static_cast(*entry_step), .url = entry.url(), .document_state = move(document_state_descriptor), .classic_history_api_state = entry.classic_history_api_state(), .navigation_api_state = entry.navigation_api_state(), .navigation_api_key = entry.navigation_api_key(), .navigation_api_id = entry.navigation_api_id(), .scroll_restoration_mode = entry.scroll_restoration_mode(), .scroll_position_data = entry.scroll_position_data(), }; } static bool session_history_nested_history_descriptors_match(Vector const& a, Vector const& b); static bool serialized_directives_match(Vector const& a, Vector const& b) { if (a.size() != b.size()) return false; for (size_t i = 0; i < a.size(); ++i) { if (a[i].name != b[i].name || a[i].value != b[i].value) return false; } return true; } static bool serialized_policies_match(Vector const& a, Vector const& b) { if (a.size() != b.size()) return false; for (size_t i = 0; i < a.size(); ++i) { if (!serialized_directives_match(a[i].directives, b[i].directives) || a[i].disposition != b[i].disposition || a[i].source != b[i].source || a[i].self_origin != b[i].self_origin || a[i].pre_parsed_policy_string != b[i].pre_parsed_policy_string) return false; } return true; } static bool embedder_policies_match(EmbedderPolicy const& a, EmbedderPolicy const& b) { return a.value == b.value && a.report_only_value == b.report_only_value && a.reporting_endpoint == b.reporting_endpoint && a.report_only_reporting_endpoint == b.report_only_reporting_endpoint; } static bool serialized_policy_containers_match(SerializedPolicyContainer const& a, SerializedPolicyContainer const& b) { return serialized_policies_match(a.csp_list, b.csp_list) && embedder_policies_match(a.embedder_policy, b.embedder_policy) && a.referrer_policy == b.referrer_policy; } static bool history_policy_containers_match(Variant const& a, Variant const& b) { if (auto const* a_serialized_policy_container = a.get_pointer()) { auto const* b_serialized_policy_container = b.get_pointer(); return b_serialized_policy_container && serialized_policy_containers_match(*a_serialized_policy_container, *b_serialized_policy_container); } return a.has() && b.has(); } static bool session_history_document_state_descriptors_match(SessionHistoryDocumentStateDescriptor const& a, SessionHistoryDocumentStateDescriptor const& b) { return a.id == b.id && history_policy_containers_match(a.history_policy_container, b.history_policy_container) && a.request_referrer == b.request_referrer && a.request_referrer_policy == b.request_referrer_policy && a.initiator_origin == b.initiator_origin && a.origin == b.origin && a.about_base_url == b.about_base_url && a.resource == b.resource && a.reload_pending == b.reload_pending && a.ever_populated == b.ever_populated && a.navigable_target_name == b.navigable_target_name && session_history_nested_history_descriptors_match(a.nested_histories, b.nested_histories); } static bool session_history_nested_history_descriptors_match_ignoring_document_state_ids(Vector const&, Vector const&); static bool session_history_document_state_descriptors_match_ignoring_id(SessionHistoryDocumentStateDescriptor const& a, SessionHistoryDocumentStateDescriptor const& b, MatchNestedHistories match_nested_histories) { if (!(history_policy_containers_match(a.history_policy_container, b.history_policy_container) && a.request_referrer == b.request_referrer && a.request_referrer_policy == b.request_referrer_policy && a.initiator_origin == b.initiator_origin && a.origin == b.origin && a.about_base_url == b.about_base_url && a.resource == b.resource && a.reload_pending == b.reload_pending && a.ever_populated == b.ever_populated && a.navigable_target_name == b.navigable_target_name)) return false; if (match_nested_histories == MatchNestedHistories::No) return true; return session_history_nested_history_descriptors_match_ignoring_document_state_ids(a.nested_histories, b.nested_histories); } bool session_history_entry_descriptors_match(SessionHistoryEntryDescriptor const& a, SessionHistoryEntryDescriptor const& b) { return a.step == b.step && a.url == b.url && session_history_document_state_descriptors_match(a.document_state, b.document_state) && a.classic_history_api_state == b.classic_history_api_state && a.navigation_api_state == b.navigation_api_state && a.navigation_api_key == b.navigation_api_key && a.navigation_api_id == b.navigation_api_id && a.scroll_restoration_mode == b.scroll_restoration_mode && a.scroll_position_data == b.scroll_position_data; } bool session_history_entry_descriptors_match_ignoring_document_state_id(SessionHistoryEntryDescriptor const& a, SessionHistoryEntryDescriptor const& b, MatchNestedHistories match_nested_histories) { if (a.step != b.step) return false; if (a.url != b.url) return false; if (a.document_state.id != 0 && !session_history_document_state_descriptors_match_ignoring_id(a.document_state, b.document_state, match_nested_histories)) return false; if (a.classic_history_api_state != b.classic_history_api_state) return false; if (a.navigation_api_state != b.navigation_api_state || a.navigation_api_key != b.navigation_api_key || a.navigation_api_id != b.navigation_api_id) return false; if (a.scroll_restoration_mode != b.scroll_restoration_mode) return false; if (a.document_state.id != 0 && a.scroll_position_data != b.scroll_position_data) return false; return true; } static bool session_history_entry_descriptors_match(Vector const& a, Vector const& b) { if (a.size() != b.size()) return false; for (size_t i = 0; i < a.size(); ++i) { if (session_history_entry_descriptors_match(a[i], b[i])) continue; return false; } return true; } static bool session_history_entry_descriptors_match_ignoring_document_state_ids(Vector const& a, Vector const& b) { if (a.size() != b.size()) return false; for (size_t i = 0; i < a.size(); ++i) { if (session_history_entry_descriptors_match_ignoring_document_state_id(a[i], b[i])) continue; return false; } return true; } static bool session_history_nested_history_descriptors_match(Vector const& a, Vector const& b) { if (a.size() != b.size()) return false; for (size_t i = 0; i < a.size(); ++i) { if (a[i].id == b[i].id && session_history_entry_descriptors_match(a[i].entries, b[i].entries)) continue; return false; } return true; } static bool session_history_nested_history_descriptors_match_ignoring_document_state_ids(Vector const& a, Vector const& b) { if (a.size() != b.size()) return false; for (size_t i = 0; i < a.size(); ++i) { if (a[i].id == b[i].id && session_history_entry_descriptors_match_ignoring_document_state_ids(a[i].entries, b[i].entries)) continue; return false; } return true; } static bool session_history_nested_history_descriptors_match_document_state_nested_histories(Vector const& descriptors, Vector const& nested_histories) { if (descriptors.size() != nested_histories.size()) return false; for (size_t i = 0; i < descriptors.size(); ++i) { auto const& descriptor = descriptors[i]; auto const& nested_history = nested_histories[i]; if (descriptor.id != nested_history.id || descriptor.entries.size() != nested_history.entries.size()) return false; for (size_t j = 0; j < descriptor.entries.size(); ++j) { if (!session_history_entry_matches_descriptor_ignoring_document_state_id(*nested_history.entries[j], descriptor.entries[j])) return false; } } return true; } static bool session_history_document_state_descriptor_matches_document_state_ignoring_id(SessionHistoryDocumentStateDescriptor const& descriptor, DocumentState const& document_state, MatchNestedHistories match_nested_histories) { if (!(history_policy_containers_match(descriptor.history_policy_container, document_state.history_policy_container()) && descriptor.request_referrer == document_state.request_referrer() && descriptor.request_referrer_policy == document_state.request_referrer_policy() && descriptor.initiator_origin == document_state.initiator_origin() && descriptor.origin == document_state.origin() && descriptor.about_base_url == document_state.about_base_url() && descriptor.resource == document_state.resource() && descriptor.reload_pending == document_state.reload_pending() && descriptor.ever_populated == document_state.ever_populated() && descriptor.navigable_target_name == document_state.navigable_target_name())) return false; if (match_nested_histories == MatchNestedHistories::No) return true; return session_history_nested_history_descriptors_match_document_state_nested_histories(descriptor.nested_histories, document_state.nested_histories()); } bool session_history_entry_matches_descriptor_ignoring_document_state_id(SessionHistoryEntry const& entry, SessionHistoryEntryDescriptor const& descriptor, MatchNestedHistories match_nested_histories) { auto entry_step = entry.step_value(); if (!entry_step.has_value() || *entry_step != descriptor.step) return false; if (entry.url() != descriptor.url) return false; if (descriptor.document_state.id != 0) { auto document_state = entry.document_state(); if (!document_state || !session_history_document_state_descriptor_matches_document_state_ignoring_id(descriptor.document_state, *document_state, match_nested_histories)) return false; } if (entry.classic_history_api_state() != descriptor.classic_history_api_state) return false; if (entry.navigation_api_state() != descriptor.navigation_api_state || entry.navigation_api_key() != descriptor.navigation_api_key || entry.navigation_api_id() != descriptor.navigation_api_id) return false; if (entry.scroll_restoration_mode() != descriptor.scroll_restoration_mode) return false; if (descriptor.document_state.id != 0 && entry.scroll_position_data() != descriptor.scroll_position_data) return false; return true; } } template<> ErrorOr 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 IPC::decode(Decoder& decoder) { auto step = TRY(decoder.decode()); auto url = TRY(decoder.decode()); auto document_state = TRY(decoder.decode()); auto classic_history_api_state = TRY(decoder.decode()); auto navigation_api_state = TRY(decoder.decode()); auto navigation_api_key = TRY(decoder.decode()); auto navigation_api_id = TRY(decoder.decode()); auto scroll_restoration_mode = TRY(decoder.decode()); auto scroll_position_data = TRY(decoder.decode()); 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 IPC::encode(Encoder& encoder, Web::HTML::SessionHistoryEntryScrollPositionData const& scroll_position_data) { TRY(encoder.encode(scroll_position_data.viewport_scroll_position)); return {}; } template<> ErrorOr IPC::decode(Decoder& decoder) { auto viewport_scroll_position = TRY(decoder.decode>()); return Web::HTML::SessionHistoryEntryScrollPositionData { .viewport_scroll_position = move(viewport_scroll_position), }; } template<> ErrorOr 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 IPC::decode(Decoder& decoder) { auto id = TRY(decoder.decode()); auto history_policy_container = TRY(decoder.decode>()); auto request_referrer = TRY(decoder.decode()); auto request_referrer_policy = TRY(decoder.decode()); auto initiator_origin = TRY(decoder.decode>()); auto origin = TRY(decoder.decode>()); auto about_base_url = TRY(decoder.decode>()); auto resource = TRY(decoder.decode>()); auto reload_pending = TRY(decoder.decode()); auto ever_populated = TRY(decoder.decode()); auto navigable_target_name = TRY(decoder.decode()); auto nested_histories = TRY(decoder.decode>()); 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 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 IPC::decode(Decoder& decoder) { auto id = TRY(decoder.decode()); auto entries = TRY(decoder.decode>()); return Web::HTML::SessionHistoryNestedHistoryDescriptor { move(id), move(entries) }; }