ladybird/Libraries/LibWeb/HTML/Navigable.h
Aliaksandr Kalenik d96dc6535f LibWeb+Compositor+WebContent: Simplify nested context composition
Nested navigables were represented through compositor surface ids owned
by the parent context. That forced CompositorState and ContextState to
maintain bidirectional attach/detach bookkeeping, publish child
snapshots into a surface map, and keep presentation mode variants just
to distinguish UI presentation from parent composition.

Record the child compositor context id directly in the display list and
let the compositor resolve it against the painting parent at playback
time. Child contexts now keep their parent context id and latest
rendered surface, while parents no longer track child maps or compositor
surface ids. UI presentation is represented separately from parent
composition, so closing a page only stops client presentation and nested
contexts keep using set_parent_context.
2026-06-18 07:08:34 +02:00

432 lines
19 KiB
C++

/*
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2023-2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Assertions.h>
#include <AK/HashTable.h>
#include <AK/OwnPtr.h>
#include <AK/String.h>
#include <AK/Tuple.h>
#include <LibCore/Forward.h>
#include <LibJS/Heap/Cell.h>
#include <LibWeb/Bindings/Navigation.h>
#include <LibWeb/Compositor/CompositorHost.h>
#include <LibWeb/DOM/DocumentLoadEventDelayer.h>
#include <LibWeb/Export.h>
#include <LibWeb/Forward.h>
#include <LibWeb/HTML/ActivateTab.h>
#include <LibWeb/HTML/DocumentState.h>
#include <LibWeb/HTML/HistoryHandlingBehavior.h>
#include <LibWeb/HTML/InitialInsertion.h>
#include <LibWeb/HTML/NavigationObserver.h>
#include <LibWeb/HTML/NavigationParams.h>
#include <LibWeb/HTML/POSTResource.h>
#include <LibWeb/HTML/PaintConfig.h>
#include <LibWeb/HTML/SandboxingFlagSet.h>
#include <LibWeb/HTML/SourceSnapshotParams.h>
#include <LibWeb/HTML/StructuredSerializeTypes.h>
#include <LibWeb/HTML/TokenizedFeatures.h>
#include <LibWeb/HTML/WindowType.h>
#include <LibWeb/InvalidateDisplayList.h>
#include <LibWeb/Page/EventHandler.h>
#include <LibWeb/Painting/DisplayListResourceStorage.h>
#include <LibWeb/PixelUnits.h>
#include <LibWeb/XHR/FormDataEntry.h>
namespace Web::HTML {
struct PopulateSessionHistoryEntryDocumentOutput;
enum class HistoryStepResult {
InitiatorDisallowed,
CanceledByBeforeUnload,
CanceledByNavigate,
Applied,
};
using OnApplyHistoryStepComplete = GC::Function<void(HistoryStepResult)>;
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#target-snapshot-params
struct TargetSnapshotParams {
// sandboxing flags: a sandboxing flag set
SandboxingFlagSet sandboxing_flags {};
// iframe element referrer policy: a referrer policy
ReferrerPolicy::ReferrerPolicy iframe_element_referrer_policy { ReferrerPolicy::ReferrerPolicy::EmptyString };
};
// https://html.spec.whatwg.org/multipage/document-sequences.html#navigable
class WEB_API Navigable : public JS::Cell {
GC_CELL(Navigable, JS::Cell);
GC_DECLARE_ALLOCATOR(Navigable);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
virtual ~Navigable() override;
using NullOrError = Optional<String>;
using NavigationParamsVariant = Variant<NullOrError, GC::Ref<NavigationParams>, GC::Ref<NonFetchSchemeNavigationParams>>;
void initialize_navigable(NonnullRefPtr<DocumentState> document_state, GC::Ptr<Navigable> parent, GC::Ref<DOM::Document> document);
void register_navigation_observer(Badge<NavigationObserver>, NavigationObserver&);
void unregister_navigation_observer(Badge<NavigationObserver>, NavigationObserver&);
Vector<GC::Root<Navigable>> child_navigables() const;
virtual bool is_traversable() const { return false; }
String const& id() const { return m_id; }
GC::Ptr<Navigable> parent() const { return m_parent; }
bool is_ancestor_of(GC::Ref<Navigable>) const;
bool is_closing() const { return m_closing; }
void set_closing(bool value) { m_closing = value; }
bool is_script_closable();
void stop_loading();
void set_delaying_load_events(bool value);
bool is_delaying_load_events() const { return m_delaying_the_load_event.has_value(); }
void set_navigation_load_event_guard(DOM::Document& parent_doc);
void clear_navigation_load_event_guard();
RefPtr<SessionHistoryEntry> active_session_history_entry() const;
void set_active_session_history_entry(RefPtr<SessionHistoryEntry>);
RefPtr<SessionHistoryEntry> current_session_history_entry() const;
void set_current_session_history_entry(RefPtr<SessionHistoryEntry>);
Vector<NonnullRefPtr<SessionHistoryEntry>>& get_session_history_entries() const;
void activate_history_entry(RefPtr<SessionHistoryEntry>, GC::Ref<DOM::Document>);
GC::Ptr<DOM::Document> active_document() const;
Optional<UniqueNodeID> active_document_id() const;
void set_active_document(GC::Ptr<DOM::Document>);
GC::Ptr<BrowsingContext> active_browsing_context();
GC::Ptr<WindowProxy> active_window_proxy();
GC::Ptr<Window> active_window();
RefPtr<SessionHistoryEntry> get_the_target_history_entry(int target_step) const;
RefPtr<SessionHistoryEntry> get_the_target_history_entry_if_present(int target_step) const;
void save_persisted_state_to_active_session_history_entry();
void restore_persisted_state_from_session_history_entry(SessionHistoryEntry const&);
void restore_scroll_position_data(SessionHistoryEntry const&);
String target_name() const;
GC::Ptr<NavigableContainer> container() const;
GC::Ptr<DOM::Document> container_document() const;
GC::Ptr<TraversableNavigable> traversable_navigable() const;
GC::Ptr<TraversableNavigable> top_level_traversable();
virtual bool is_top_level_traversable() const { return false; }
[[nodiscard]] bool is_focused() const;
struct ChosenNavigable {
GC::Ptr<Navigable> navigable;
WindowType window_type;
};
ChosenNavigable choose_a_navigable(StringView name, TokenizedFeature::NoOpener no_opener, ActivateTab = ActivateTab::Yes, Optional<TokenizedFeature::Map const&> window_features = {});
GC::Ptr<Navigable> find_a_navigable_by_target_name(StringView name);
enum class Traversal {
Tag
};
enum class NavigationAPIAbortBehavior {
Abort,
Preserve
};
Variant<Empty, Traversal, String> ongoing_navigation() const { return m_ongoing_navigation; }
void set_ongoing_navigation(Variant<Empty, Traversal, String> ongoing_navigation, NavigationAPIAbortBehavior = NavigationAPIAbortBehavior::Abort);
// Test-only (Internals.clobberNextNavigationWithATraversal): make the next navigation's unload check be interrupted
// by a synthetic session-history traversal that re-stamps the ongoing navigation.
static void clobber_next_navigation_with_a_traversal_for_testing();
void populate_session_history_entry_document(
URL::URL url,
Variant<Empty, String, POSTResource> document_resource,
Fetch::Infrastructure::Request::ReferrerType request_referrer,
ReferrerPolicy::ReferrerPolicy request_referrer_policy,
Optional<URL::Origin> initiator_origin,
Optional<URL::Origin> origin,
Variant<SerializedPolicyContainer, DocumentState::Client> history_policy_container,
Optional<URL::URL> about_base_url,
String navigable_target_name,
bool reload_pending,
bool ever_populated,
GC::Ref<SourceSnapshotParams> source_snapshot_params,
TargetSnapshotParams const& target_snapshot_params,
UserNavigationInvolvement user_involvement,
Optional<String> navigation_id,
NavigationParamsVariant navigation_params,
ContentSecurityPolicy::Directives::Directive::NavigationType csp_navigation_type,
bool allow_POST,
GC::Ptr<GC::Function<void(GC::Ptr<PopulateSessionHistoryEntryDocumentOutput>)>> completion_steps);
struct NavigateParams {
URL::URL url;
// FIXME: source_document should now be nullable, and default to nullptr.
GC::Ref<DOM::Document> source_document;
Variant<Empty, String, POSTResource> document_resource = Empty {};
GC::Ptr<Fetch::Infrastructure::Response> response = nullptr;
bool exceptions_enabled = false;
Bindings::NavigationHistoryBehavior history_handling = Bindings::NavigationHistoryBehavior::Auto;
Optional<SerializationRecord> navigation_api_state = {};
Optional<Vector<XHR::FormDataEntry>> form_data_entry_list = {};
ReferrerPolicy::ReferrerPolicy referrer_policy = ReferrerPolicy::ReferrerPolicy::EmptyString;
UserNavigationInvolvement user_involvement = UserNavigationInvolvement::None;
GC::Ptr<DOM::Element> source_element = nullptr;
InitialInsertion initial_insertion = InitialInsertion::No;
void visit_edges(Cell::Visitor& visitor);
};
WebIDL::ExceptionOr<void> navigate(NavigateParams);
GC::Ptr<DOM::Document> evaluate_javascript_url(URL::URL const&, URL::Origin const& new_document_origin, UserNavigationInvolvement, String navigation_id);
bool allowed_by_sandboxing_to_navigate(Navigable const& target, SourceSnapshotParams const&);
void reload(Optional<SerializationRecord> navigation_api_state = {}, UserNavigationInvolvement = UserNavigationInvolvement::None);
// https://github.com/whatwg/html/issues/9690
[[nodiscard]] bool has_been_destroyed() const { return m_has_been_destroyed; }
void set_has_been_destroyed();
void remove_from_all_navigables();
CSSPixelPoint to_top_level_position(CSSPixelPoint);
CSSPixelRect to_top_level_rect(CSSPixelRect const&);
CSSPixelPoint viewport_scroll_offset() const { return m_viewport_scroll_offset; }
CSSPixelRect viewport_rect() const { return { m_viewport_scroll_offset, m_viewport_size }; }
CSSPixelSize viewport_size() const { return m_viewport_size; }
void set_viewport_size(CSSPixelSize, InvalidateDisplayList = InvalidateDisplayList::No);
void perform_scroll_of_viewport_scrolling_box(CSSPixelPoint position);
void adopt_pending_async_scroll_offsets();
void wait_for_async_scroll_operation(Compositor::AsyncScrollOperationID, GC::Ref<WebIDL::Promise>);
void clamp_viewport_scroll_offset();
// https://html.spec.whatwg.org/multipage/webappapis.html#rendering-opportunity
[[nodiscard]] bool has_a_rendering_opportunity() const;
[[nodiscard]] TargetSnapshotParams snapshot_target_snapshot_params();
Page& page() { return m_page; }
Page const& page() const { return m_page; }
String selected_text() const;
String cut_selected_text() const;
void select_all();
void paste(Utf16String const&);
void set_marked_text_from_input_method(Utf16String const& text);
void commit_text_from_input_method(Utf16String const& text);
void unmark_text_from_input_method();
Web::EventHandler& event_handler() { return m_event_handler; }
Web::EventHandler const& event_handler() const { return m_event_handler; }
// https://drafts.csswg.org/css-view-transitions-1/#snapshot-containing-block
CSSPixelRect snapshot_containing_block();
// https://drafts.csswg.org/css-view-transitions-1/#snapshot-containing-block-size
CSSPixelSize snapshot_containing_block_size();
bool has_session_history_entry_and_ready_for_navigation() const { return m_has_session_history_entry_and_ready_for_navigation; }
void set_has_session_history_entry_and_ready_for_navigation();
void inform_the_navigation_api_about_child_navigable_destruction();
bool has_pending_navigations() const { return !m_pending_navigations.is_empty(); }
void clear_pending_navigations() { m_pending_navigations.clear(); }
bool record_display_list_and_scroll_state(PaintConfig);
void paint_next_frame();
void render_screenshot(Gfx::PaintingSurface&, PaintConfig, Function<void()>&& callback);
Painting::DisplayListResourceStorage& display_list_resource_storage() { return m_display_list_resource_storage; }
Painting::DisplayListResourceStorage const& display_list_resource_storage() const { return m_display_list_resource_storage; }
bool needs_repaint() const { return m_needs_repaint; }
void set_needs_repaint() { m_needs_repaint = true; }
void set_needs_to_record_display_list() { m_needs_to_record_display_list = true; }
void repaint_after_compositor_process_reconnect();
[[nodiscard]] bool has_inclusive_ancestor_with_visibility_hidden() const;
Compositor::CompositorContextHandle& compositor_context()
{
VERIFY(m_compositor_context);
return *m_compositor_context;
}
Compositor::CompositorContextHandle const& compositor_context() const
{
VERIFY(m_compositor_context);
return *m_compositor_context;
}
bool has_compositor_context() const { return m_compositor_context; }
void set_pending_set_browser_zoom_request(bool value) { m_pending_set_browser_zoom_request = value; }
bool pending_set_browser_zoom_request() const { return m_pending_set_browser_zoom_request; }
void set_should_show_line_box_borders(bool);
void set_should_show_caret_hit_test_debug_overlay(bool);
bool should_show_caret_hit_test_debug_overlay() const { return m_should_show_caret_hit_test_debug_overlay; }
bool is_svg_page() const { return m_is_svg_page; }
template<typename T>
bool fast_is() const = delete;
GC::Ref<WebIDL::Promise> scroll_viewport_by_delta(CSSPixelPoint delta);
GC::Ref<WebIDL::Promise> perform_a_scroll_of_the_viewport(CSSPixelPoint position);
void reset_zoom();
protected:
explicit Navigable(
GC::Ref<Page>,
bool is_svg_page,
Compositor::PagePresentationRegistration = Compositor::PagePresentationRegistration::No);
virtual void visit_edges(Cell::Visitor&) override;
virtual void finalize() override;
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#ongoing-navigation
Variant<Empty, Traversal, String> m_ongoing_navigation;
private:
enum class PendingNavigationBehavior {
Append,
Replace
};
void begin_navigation(NavigateParams);
void queue_pending_navigation(NavigateParams, PendingNavigationBehavior);
void process_pending_navigations();
void navigate_to_a_fragment(URL::URL const&, HistoryHandlingBehavior, UserNavigationInvolvement, GC::Ptr<DOM::Element> source_element, Optional<SerializationRecord> navigation_api_state, String navigation_id);
void navigate_to_a_javascript_url(URL::URL const&, HistoryHandlingBehavior, GC::Ref<SourceSnapshotParams>, URL::Origin const& initiator_origin, UserNavigationInvolvement, ContentSecurityPolicy::Directives::Directive::NavigationType csp_navigation_type, InitialInsertion, String navigation_id);
void reset_cursor_blink_cycle();
void scroll_offset_did_change();
void clear_parent_compositor_context();
void destroy_compositor_context();
void inform_the_navigation_api_about_aborting_navigation();
void resolve_async_scroll_operation(Compositor::AsyncScrollOperationID);
void resolve_all_pending_async_scroll_operations();
void schedule_hover_update_after_async_scroll();
void update_hover_after_async_scroll_stops();
void cancel_hover_update_after_async_scroll();
// https://html.spec.whatwg.org/multipage/document-sequences.html#nav-id
String m_id;
// https://html.spec.whatwg.org/multipage/document-sequences.html#nav-parent
GC::Ptr<Navigable> m_parent;
// https://html.spec.whatwg.org/multipage/document-sequences.html#nav-current-history-entry
RefPtr<SessionHistoryEntry> m_current_session_history_entry;
// https://html.spec.whatwg.org/multipage/document-sequences.html#nav-active-history-entry
RefPtr<SessionHistoryEntry> m_active_session_history_entry;
// AD-HOC: Direct reference to the active document, decoupled from session history.
// This is the authoritative source for active_document().
GC::Ptr<DOM::Document> m_active_document;
// AD-HOC: Active IME composition state. While a composition is in progress, m_input_method_composition_node and
// m_input_method_composition_offset record the start of the marked (preedit) text; the marked text spans
// from there to the caret. A null node means no composition is in progress.
void replace_input_method_marked_text(Utf16String const& text);
GC::Ptr<DOM::Node> m_input_method_composition_node;
size_t m_input_method_composition_offset { 0 };
// https://html.spec.whatwg.org/multipage/document-sequences.html#is-closing
bool m_closing { false };
// https://html.spec.whatwg.org/multipage/document-sequences.html#delaying-load-events-mode
Optional<DOM::DocumentLoadEventDelayer> m_delaying_the_load_event;
// AD-HOC: Guards the parent document's load event delay count during cross-document navigation.
Optional<DOM::DocumentLoadEventDelayer> m_navigation_load_event_guard;
// Implied link between navigable and its container.
GC::Ptr<NavigableContainer> m_container;
GC::Ref<Page> m_page;
NavigationObserver::NavigationObserversList m_navigation_observers;
bool m_has_been_destroyed { false };
CSSPixelSize m_viewport_size;
CSSPixelPoint m_viewport_scroll_offset;
Web::EventHandler m_event_handler;
bool m_has_session_history_entry_and_ready_for_navigation { false };
Vector<NavigateParams> m_pending_navigations;
bool m_is_svg_page { false };
bool m_needs_repaint { true };
bool m_needs_to_record_display_list { true };
bool m_pending_set_browser_zoom_request { false };
bool m_should_show_line_box_borders { false };
bool m_should_show_caret_hit_test_debug_overlay { false };
Optional<PaintConfig> m_compositor_display_list_paint_config;
Painting::DisplayListResourceStorage m_display_list_resource_storage;
Painting::DisplayListResourceSet m_compositor_display_list_resources;
OwnPtr<Compositor::CompositorContextHandle> m_compositor_context;
RefPtr<Core::Timer> m_async_scroll_hover_update_timer;
struct PendingAsyncScrollOperation {
Compositor::AsyncScrollOperationID operation_id { 0 };
GC::Ref<WebIDL::Promise> promise;
};
Vector<PendingAsyncScrollOperation> m_pending_async_scroll_operations;
};
struct PopulateSessionHistoryEntryDocumentOutput final : public JS::Cell {
GC_CELL(PopulateSessionHistoryEntryDocumentOutput, JS::Cell);
GC_DECLARE_ALLOCATOR(PopulateSessionHistoryEntryDocumentOutput);
public:
GC::Ptr<DOM::Document> document;
Navigable::NavigationParamsVariant navigation_params { Navigable::NullOrError {} };
bool save_extra_document_state = true;
Optional<URL::URL> redirected_url;
Optional<SerializationRecord> classic_history_api_state;
RefPtr<DocumentState> replacement_document_state;
bool resource_cleared = false;
void apply_to(NonnullRefPtr<SessionHistoryEntry> entry);
private:
virtual void visit_edges(Cell::Visitor&) override;
};
WEB_API HashTable<GC::RawRef<Navigable>>& all_navigables();
Vector<NonnullRefPtr<SessionHistoryEntry>>* append_nested_history_for_child_navigable(
Navigable& parent_navigable, Navigable& child_navigable, SessionHistoryEntry& history_entry);
bool navigation_must_be_a_replace(URL::URL const& url, DOM::Document const& document);
void finalize_a_cross_document_navigation(GC::Ref<Navigable>, HistoryHandlingBehavior, UserNavigationInvolvement, NonnullRefPtr<SessionHistoryEntry>, GC::Ptr<DOM::Document> pending_document, Optional<String> expected_ongoing_navigation_id, GC::Ref<OnApplyHistoryStepComplete> on_complete);
void perform_url_and_history_update_steps(DOM::Document& document, URL::URL new_url, Optional<SerializationRecord> = {}, HistoryHandlingBehavior history_handling = HistoryHandlingBehavior::Replace);
}