/* * Copyright (c) 2022, Andreas Kling * Copyright (c) 2023-2025, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Web::HTML { struct PopulateSessionHistoryEntryDocumentOutput; enum class HistoryStepResult { InitiatorDisallowed, CanceledByBeforeUnload, CanceledByNavigate, Applied, }; using OnApplyHistoryStepComplete = GC::Function; // 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; using NavigationParamsVariant = Variant, GC::Ref>; void initialize_navigable(NonnullRefPtr document_state, GC::Ptr parent, GC::Ref document); void register_navigation_observer(Badge, NavigationObserver&); void unregister_navigation_observer(Badge, NavigationObserver&); Vector> child_navigables() const; virtual bool is_traversable() const { return false; } String const& id() const { return m_id; } GC::Ptr parent() const { return m_parent; } bool is_ancestor_of(GC::Ref) 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 active_session_history_entry() const; void set_active_session_history_entry(RefPtr); RefPtr current_session_history_entry() const; void set_current_session_history_entry(RefPtr); Vector>& get_session_history_entries() const; void activate_history_entry(RefPtr, GC::Ref); GC::Ptr active_document() const; Optional active_document_id() const; void set_active_document(GC::Ptr); GC::Ptr active_browsing_context(); GC::Ptr active_window_proxy(); GC::Ptr active_window(); RefPtr get_the_target_history_entry(int target_step) const; RefPtr 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 container() const; GC::Ptr container_document() const; GC::Ptr traversable_navigable() const; GC::Ptr top_level_traversable(); virtual bool is_top_level_traversable() const { return false; } [[nodiscard]] bool is_focused() const; struct ChosenNavigable { GC::Ptr navigable; WindowType window_type; }; ChosenNavigable choose_a_navigable(StringView name, TokenizedFeature::NoOpener no_opener, ActivateTab = ActivateTab::Yes, Optional window_features = {}); GC::Ptr find_a_navigable_by_target_name(StringView name); enum class Traversal { Tag }; enum class NavigationAPIAbortBehavior { Abort, Preserve }; Variant ongoing_navigation() const { return m_ongoing_navigation; } void set_ongoing_navigation(Variant 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 document_resource, Fetch::Infrastructure::Request::ReferrerType request_referrer, ReferrerPolicy::ReferrerPolicy request_referrer_policy, Optional initiator_origin, Optional origin, Variant history_policy_container, Optional about_base_url, String navigable_target_name, bool reload_pending, bool ever_populated, GC::Ref source_snapshot_params, TargetSnapshotParams const& target_snapshot_params, UserNavigationInvolvement user_involvement, Optional navigation_id, NavigationParamsVariant navigation_params, ContentSecurityPolicy::Directives::Directive::NavigationType csp_navigation_type, bool allow_POST, GC::Ptr)>> completion_steps); struct NavigateParams { URL::URL url; // FIXME: source_document should now be nullable, and default to nullptr. GC::Ref source_document; Variant document_resource = Empty {}; GC::Ptr response = nullptr; bool exceptions_enabled = false; Bindings::NavigationHistoryBehavior history_handling = Bindings::NavigationHistoryBehavior::Auto; Optional navigation_api_state = {}; Optional> form_data_entry_list = {}; ReferrerPolicy::ReferrerPolicy referrer_policy = ReferrerPolicy::ReferrerPolicy::EmptyString; UserNavigationInvolvement user_involvement = UserNavigationInvolvement::None; GC::Ptr source_element = nullptr; InitialInsertion initial_insertion = InitialInsertion::No; void visit_edges(Cell::Visitor& visitor); }; WebIDL::ExceptionOr navigate(NavigateParams); GC::Ptr 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 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); 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&& 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 bool fast_is() const = delete; GC::Ref scroll_viewport_by_delta(CSSPixelPoint delta); GC::Ref perform_a_scroll_of_the_viewport(CSSPixelPoint position); void reset_zoom(); protected: explicit Navigable( GC::Ref, 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 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 source_element, Optional navigation_api_state, String navigation_id); void navigate_to_a_javascript_url(URL::URL const&, HistoryHandlingBehavior, GC::Ref, 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 m_parent; // https://html.spec.whatwg.org/multipage/document-sequences.html#nav-current-history-entry RefPtr m_current_session_history_entry; // https://html.spec.whatwg.org/multipage/document-sequences.html#nav-active-history-entry RefPtr 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 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 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 m_delaying_the_load_event; // AD-HOC: Guards the parent document's load event delay count during cross-document navigation. Optional m_navigation_load_event_guard; // Implied link between navigable and its container. GC::Ptr m_container; GC::Ref 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 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 m_compositor_display_list_paint_config; Painting::DisplayListResourceStorage m_display_list_resource_storage; Painting::DisplayListResourceSet m_compositor_display_list_resources; OwnPtr m_compositor_context; RefPtr m_async_scroll_hover_update_timer; struct PendingAsyncScrollOperation { Compositor::AsyncScrollOperationID operation_id { 0 }; GC::Ref promise; }; Vector m_pending_async_scroll_operations; }; struct PopulateSessionHistoryEntryDocumentOutput final : public JS::Cell { GC_CELL(PopulateSessionHistoryEntryDocumentOutput, JS::Cell); GC_DECLARE_ALLOCATOR(PopulateSessionHistoryEntryDocumentOutput); public: GC::Ptr document; Navigable::NavigationParamsVariant navigation_params { Navigable::NullOrError {} }; bool save_extra_document_state = true; Optional redirected_url; Optional classic_history_api_state; RefPtr replacement_document_state; bool resource_cleared = false; void apply_to(NonnullRefPtr entry); private: virtual void visit_edges(Cell::Visitor&) override; }; WEB_API HashTable>& all_navigables(); Vector>* 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, HistoryHandlingBehavior, UserNavigationInvolvement, NonnullRefPtr, GC::Ptr pending_document, Optional expected_ongoing_navigation_id, GC::Ref on_complete); void perform_url_and_history_update_steps(DOM::Document& document, URL::URL new_url, Optional = {}, HistoryHandlingBehavior history_handling = HistoryHandlingBehavior::Replace); }