| /* |
| * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| * (C) 1999 Antti Koivisto (koivisto@kde.org) |
| * (C) 2001 Dirk Mueller (mueller@kde.org) |
| * (C) 2006 Alexey Proskuryakov (ap@webkit.org) |
| * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2011, 2012 Apple Inc. All |
| * rights reserved. |
| * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. |
| * (http://www.torchmobile.com/) |
| * Copyright (C) 2008, 2009, 2011, 2012 Google Inc. All rights reserved. |
| * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) |
| * Copyright (C) Research In Motion Limited 2010-2011. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "third_party/blink/renderer/core/dom/document.h" |
| |
| #include <memory> |
| #include <optional> |
| #include <utility> |
| |
| #include "base/auto_reset.h" |
| #include "base/containers/adapters.h" |
| #include "base/containers/contains.h" |
| #include "base/debug/dump_without_crashing.h" |
| #include "base/i18n/time_formatting.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/notreached.h" |
| #include "base/ranges/algorithm.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/trace_event.h" |
| #include "cc/animation/animation_host.h" |
| #include "cc/animation/animation_timeline.h" |
| #include "cc/input/overscroll_behavior.h" |
| #include "cc/input/scroll_snap_data.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "mojo/public/mojom/base/text_direction.mojom-blink.h" |
| #include "services/metrics/public/cpp/delegating_ukm_recorder.h" |
| #include "services/metrics/public/cpp/metrics_utils.h" |
| #include "services/metrics/public/cpp/mojo_ukm_recorder.h" |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| #include "services/metrics/public/cpp/ukm_recorder.h" |
| #include "services/metrics/public/cpp/ukm_source_id.h" |
| #include "services/network/public/cpp/web_sandbox_flags.h" |
| #include "services/network/public/mojom/trust_tokens.mojom-blink.h" |
| #include "services/network/public/mojom/web_sandbox_flags.mojom-blink.h" |
| #include "third_party/blink/public/common/browser_interface_broker_proxy.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/permissions_policy/document_policy_features.h" |
| #include "third_party/blink/public/common/privacy_budget/identifiability_sample_collector.h" |
| #include "third_party/blink/public/common/privacy_budget/identifiability_study_settings.h" |
| #include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h" |
| #include "third_party/blink/public/mojom/css/preferred_color_scheme.mojom-blink-forward.h" |
| #include "third_party/blink/public/mojom/frame/frame.mojom-blink.h" |
| #include "third_party/blink/public/mojom/input/focus_type.mojom-blink.h" |
| #include "third_party/blink/public/mojom/manifest/display_mode.mojom-blink-forward.h" |
| #include "third_party/blink/public/mojom/page_state/page_state.mojom-blink.h" |
| #include "third_party/blink/public/mojom/permissions/permission_status.mojom-blink-forward.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/public/platform/task_type.h" |
| #include "third_party/blink/public/platform/web_content_settings_client.h" |
| #include "third_party/blink/public/web/web_link_preview_triggerer.h" |
| #include "third_party/blink/public/web/web_print_page_description.h" |
| #include "third_party/blink/renderer/bindings/core/v8/frozen_array.h" |
| #include "third_party/blink/renderer/bindings/core/v8/isolated_world_csp.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_controller.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_value.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_aria_notification_options.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_element_creation_options.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_element_registration_options.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_observable_array_css_style_sheet.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_throw_dom_exception.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_union_elementcreationoptions_string.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_union_htmlscriptelement_svgscriptelement.h" |
| #include "third_party/blink/renderer/bindings/core/v8/window_proxy.h" |
| #include "third_party/blink/renderer/core/accessibility/ax_context.h" |
| #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h" |
| #include "third_party/blink/renderer/core/animation/document_animations.h" |
| #include "third_party/blink/renderer/core/animation/document_timeline.h" |
| #include "third_party/blink/renderer/core/animation/pending_animations.h" |
| #include "third_party/blink/renderer/core/animation/worklet_animation_controller.h" |
| #include "third_party/blink/renderer/core/annotation/annotation_agent_container_impl.h" |
| #include "third_party/blink/renderer/core/aom/computed_accessible_node.h" |
| #include "third_party/blink/renderer/core/core_export.h" |
| #include "third_party/blink/renderer/core/css/css_font_selector.h" |
| #include "third_party/blink/renderer/core/css/css_property_value_set.h" |
| #include "third_party/blink/renderer/core/css/css_style_declaration.h" |
| #include "third_party/blink/renderer/core/css/css_style_sheet.h" |
| #include "third_party/blink/renderer/core/css/cssom/caret_position.h" |
| #include "third_party/blink/renderer/core/css/cssom/computed_style_property_map.h" |
| #include "third_party/blink/renderer/core/css/element_rule_collector.h" |
| #include "third_party/blink/renderer/core/css/font_face_set_document.h" |
| #include "third_party/blink/renderer/core/css/invalidation/style_invalidator.h" |
| #include "third_party/blink/renderer/core/css/layout_upgrade.h" |
| #include "third_party/blink/renderer/core/css/media_query_list.h" |
| #include "third_party/blink/renderer/core/css/media_query_matcher.h" |
| #include "third_party/blink/renderer/core/css/media_values.h" |
| #include "third_party/blink/renderer/core/css/parser/css_parser.h" |
| #include "third_party/blink/renderer/core/css/post_style_update_scope.h" |
| #include "third_party/blink/renderer/core/css/properties/css_property.h" |
| #include "third_party/blink/renderer/core/css/property_registry.h" |
| #include "third_party/blink/renderer/core/css/resolver/font_builder.h" |
| #include "third_party/blink/renderer/core/css/resolver/style_resolver.h" |
| #include "third_party/blink/renderer/core/css/resolver/style_resolver_stats.h" |
| #include "third_party/blink/renderer/core/css/selector_query.h" |
| #include "third_party/blink/renderer/core/css/style_change_reason.h" |
| #include "third_party/blink/renderer/core/css/style_engine.h" |
| #include "third_party/blink/renderer/core/css/style_sheet_contents.h" |
| #include "third_party/blink/renderer/core/css/style_sheet_list.h" |
| #include "third_party/blink/renderer/core/display_lock/display_lock_context.h" |
| #include "third_party/blink/renderer/core/display_lock/display_lock_document_state.h" |
| #include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h" |
| #include "third_party/blink/renderer/core/dom/attr.h" |
| #include "third_party/blink/renderer/core/dom/beforeunload_event_listener.h" |
| #include "third_party/blink/renderer/core/dom/cdata_section.h" |
| #include "third_party/blink/renderer/core/dom/comment.h" |
| #include "third_party/blink/renderer/core/dom/document_data.h" |
| #include "third_party/blink/renderer/core/dom/document_fragment.h" |
| #include "third_party/blink/renderer/core/dom/document_init.h" |
| #include "third_party/blink/renderer/core/dom/document_lifecycle.h" |
| #include "third_party/blink/renderer/core/dom/document_parser_timing.h" |
| #include "third_party/blink/renderer/core/dom/document_part_root.h" |
| #include "third_party/blink/renderer/core/dom/document_type.h" |
| #include "third_party/blink/renderer/core/dom/dom_implementation.h" |
| #include "third_party/blink/renderer/core/dom/element.h" |
| #include "third_party/blink/renderer/core/dom/element_data_cache.h" |
| #include "third_party/blink/renderer/core/dom/element_traversal.h" |
| #include "third_party/blink/renderer/core/dom/events/event.h" |
| #include "third_party/blink/renderer/core/dom/events/event_dispatch_forbidden_scope.h" |
| #include "third_party/blink/renderer/core/dom/events/event_listener.h" |
| #include "third_party/blink/renderer/core/dom/events/native_event_listener.h" |
| #include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h" |
| #include "third_party/blink/renderer/core/dom/flat_tree_traversal.h" |
| #include "third_party/blink/renderer/core/dom/focus_params.h" |
| #include "third_party/blink/renderer/core/dom/focused_element_change_observer.h" |
| #include "third_party/blink/renderer/core/dom/layout_tree_builder_traversal.h" |
| #include "third_party/blink/renderer/core/dom/live_node_list.h" |
| #include "third_party/blink/renderer/core/dom/mutation_observer.h" |
| #include "third_party/blink/renderer/core/dom/node_child_removal_tracker.h" |
| #include "third_party/blink/renderer/core/dom/node_cloning_data.h" |
| #include "third_party/blink/renderer/core/dom/node_computed_style.h" |
| #include "third_party/blink/renderer/core/dom/node_iterator.h" |
| #include "third_party/blink/renderer/core/dom/node_lists_node_data.h" |
| #include "third_party/blink/renderer/core/dom/node_rare_data.h" |
| #include "third_party/blink/renderer/core/dom/node_traversal.h" |
| #include "third_party/blink/renderer/core/dom/node_with_index.h" |
| #include "third_party/blink/renderer/core/dom/part_root.h" |
| #include "third_party/blink/renderer/core/dom/processing_instruction.h" |
| #include "third_party/blink/renderer/core/dom/scripted_animation_controller.h" |
| #include "third_party/blink/renderer/core/dom/scripted_idle_task_controller.h" |
| #include "third_party/blink/renderer/core/dom/shadow_including_tree_order_traversal.h" |
| #include "third_party/blink/renderer/core/dom/shadow_root.h" |
| #include "third_party/blink/renderer/core/dom/slot_assignment.h" |
| #include "third_party/blink/renderer/core/dom/slot_assignment_engine.h" |
| #include "third_party/blink/renderer/core/dom/slot_assignment_recalc_forbidden_scope.h" |
| #include "third_party/blink/renderer/core/dom/static_node_list.h" |
| #include "third_party/blink/renderer/core/dom/text_diff_range.h" |
| #include "third_party/blink/renderer/core/dom/transform_source.h" |
| #include "third_party/blink/renderer/core/dom/tree_walker.h" |
| #include "third_party/blink/renderer/core/dom/visited_link_state.h" |
| #include "third_party/blink/renderer/core/dom/whitespace_attacher.h" |
| #include "third_party/blink/renderer/core/dom/xml_document.h" |
| #include "third_party/blink/renderer/core/editing/editing_utilities.h" |
| #include "third_party/blink/renderer/core/editing/frame_selection.h" |
| #include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h" |
| #include "third_party/blink/renderer/core/editing/position_with_affinity.h" |
| #include "third_party/blink/renderer/core/editing/serializers/serialization.h" |
| #include "third_party/blink/renderer/core/event_type_names.h" |
| #include "third_party/blink/renderer/core/events/before_unload_event.h" |
| #include "third_party/blink/renderer/core/events/event_factory.h" |
| #include "third_party/blink/renderer/core/events/event_util.h" |
| #include "third_party/blink/renderer/core/events/hash_change_event.h" |
| #include "third_party/blink/renderer/core/events/overscroll_event.h" |
| #include "third_party/blink/renderer/core/events/page_transition_event.h" |
| #include "third_party/blink/renderer/core/events/visual_viewport_resize_event.h" |
| #include "third_party/blink/renderer/core/events/visual_viewport_scroll_event.h" |
| #include "third_party/blink/renderer/core/events/visual_viewport_scrollend_event.h" |
| #include "third_party/blink/renderer/core/execution_context/window_agent.h" |
| #include "third_party/blink/renderer/core/fragment_directive/fragment_directive.h" |
| #include "third_party/blink/renderer/core/fragment_directive/text_fragment_handler.h" |
| #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" |
| #include "third_party/blink/renderer/core/frame/dom_visual_viewport.h" |
| #include "third_party/blink/renderer/core/frame/event_handler_registry.h" |
| #include "third_party/blink/renderer/core/frame/font_matching_metrics.h" |
| #include "third_party/blink/renderer/core/frame/frame_console.h" |
| #include "third_party/blink/renderer/core/frame/history.h" |
| #include "third_party/blink/renderer/core/frame/intervention.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_client.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/page_dismissal_scope.h" |
| #include "third_party/blink/renderer/core/frame/performance_monitor.h" |
| #include "third_party/blink/renderer/core/frame/picture_in_picture_controller.h" |
| #include "third_party/blink/renderer/core/frame/settings.h" |
| #include "third_party/blink/renderer/core/frame/viewport_data.h" |
| #include "third_party/blink/renderer/core/frame/visual_viewport.h" |
| #include "third_party/blink/renderer/core/html/anchor_element_observer_for_service_worker.h" |
| #include "third_party/blink/renderer/core/html/canvas/canvas_font_cache.h" |
| #include "third_party/blink/renderer/core/html/collection_type.h" |
| #include "third_party/blink/renderer/core/html/custom/custom_element.h" |
| #include "third_party/blink/renderer/core/html/custom/custom_element_definition.h" |
| #include "third_party/blink/renderer/core/html/custom/custom_element_descriptor.h" |
| #include "third_party/blink/renderer/core/html/custom/custom_element_registry.h" |
| #include "third_party/blink/renderer/core/html/document_all_name_collection.h" |
| #include "third_party/blink/renderer/core/html/document_name_collection.h" |
| #include "third_party/blink/renderer/core/html/forms/email_input_type.h" |
| #include "third_party/blink/renderer/core/html/forms/form_controller.h" |
| #include "third_party/blink/renderer/core/html/forms/html_form_element.h" |
| #include "third_party/blink/renderer/core/html/forms/html_input_element.h" |
| #include "third_party/blink/renderer/core/html/html_all_collection.h" |
| #include "third_party/blink/renderer/core/html/html_anchor_element.h" |
| #include "third_party/blink/renderer/core/html/html_base_element.h" |
| #include "third_party/blink/renderer/core/html/html_body_element.h" |
| #include "third_party/blink/renderer/core/html/html_collection.h" |
| #include "third_party/blink/renderer/core/html/html_dialog_element.h" |
| #include "third_party/blink/renderer/core/html/html_document.h" |
| #include "third_party/blink/renderer/core/html/html_frame_owner_element.h" |
| #include "third_party/blink/renderer/core/html/html_head_element.h" |
| #include "third_party/blink/renderer/core/html/html_html_element.h" |
| #include "third_party/blink/renderer/core/html/html_image_element.h" |
| #include "third_party/blink/renderer/core/html/html_link_element.h" |
| #include "third_party/blink/renderer/core/html/html_meta_element.h" |
| #include "third_party/blink/renderer/core/html/html_object_element.h" |
| #include "third_party/blink/renderer/core/html/html_plugin_element.h" |
| #include "third_party/blink/renderer/core/html/html_script_element.h" |
| #include "third_party/blink/renderer/core/html/html_title_element.h" |
| #include "third_party/blink/renderer/core/html/html_unknown_element.h" |
| #include "third_party/blink/renderer/core/html/lazy_load_image_observer.h" |
| #include "third_party/blink/renderer/core/html/nesting_level_incrementer.h" |
| #include "third_party/blink/renderer/core/html/parser/html_document_parser.h" |
| #include "third_party/blink/renderer/core/html/parser/html_document_parser_fastpath.h" |
| #include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" |
| #include "third_party/blink/renderer/core/html/parser/text_resource_decoder.h" |
| #include "third_party/blink/renderer/core/html/parser/text_resource_decoder_builder.h" |
| #include "third_party/blink/renderer/core/html/plugin_document.h" |
| #include "third_party/blink/renderer/core/html/window_name_collection.h" |
| #include "third_party/blink/renderer/core/html_element_factory.h" |
| #include "third_party/blink/renderer/core/html_element_type_helpers.h" |
| #include "third_party/blink/renderer/core/html_names.h" |
| #include "third_party/blink/renderer/core/input/event_handler.h" |
| #include "third_party/blink/renderer/core/input/touch_list.h" |
| #include "third_party/blink/renderer/core/inspector/console_message.h" |
| #include "third_party/blink/renderer/core/inspector/inspector_trace_events.h" |
| #include "third_party/blink/renderer/core/intersection_observer/element_intersection_observer_data.h" |
| #include "third_party/blink/renderer/core/intersection_observer/intersection_observer_controller.h" |
| #include "third_party/blink/renderer/core/intersection_observer/intersection_observer_entry.h" |
| #include "third_party/blink/renderer/core/layout/adjust_for_absolute_zoom.h" |
| #include "third_party/blink/renderer/core/layout/hit_test_canvas_result.h" |
| #include "third_party/blink/renderer/core/layout/hit_test_result.h" |
| #include "third_party/blink/renderer/core/layout/layout_embedded_content.h" |
| #include "third_party/blink/renderer/core/layout/layout_view.h" |
| #include "third_party/blink/renderer/core/layout/text_autosizer.h" |
| #include "third_party/blink/renderer/core/lcp_critical_path_predictor/lcp_critical_path_predictor.h" |
| #include "third_party/blink/renderer/core/loader/anchor_element_interaction_tracker.h" |
| #include "third_party/blink/renderer/core/loader/cookie_jar.h" |
| #include "third_party/blink/renderer/core/loader/document_loader.h" |
| #include "third_party/blink/renderer/core/loader/frame_fetch_context.h" |
| #include "third_party/blink/renderer/core/loader/frame_loader.h" |
| #include "third_party/blink/renderer/core/loader/http_refresh_scheduler.h" |
| #include "third_party/blink/renderer/core/loader/idleness_detector.h" |
| #include "third_party/blink/renderer/core/loader/interactive_detector.h" |
| #include "third_party/blink/renderer/core/loader/lazy_image_helper.h" |
| #include "third_party/blink/renderer/core/loader/no_state_prefetch_client.h" |
| #include "third_party/blink/renderer/core/loader/pending_link_preload.h" |
| #include "third_party/blink/renderer/core/loader/progress_tracker.h" |
| #include "third_party/blink/renderer/core/loader/render_blocking_resource_manager.h" |
| #include "third_party/blink/renderer/core/loader/resource/link_dictionary_resource.h" |
| #include "third_party/blink/renderer/core/mathml/mathml_element.h" |
| #include "third_party/blink/renderer/core/mathml/mathml_row_element.h" |
| #include "third_party/blink/renderer/core/mathml_element_factory.h" |
| #include "third_party/blink/renderer/core/mathml_names.h" |
| #include "third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h" |
| #include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h" |
| #include "third_party/blink/renderer/core/page/chrome_client.h" |
| #include "third_party/blink/renderer/core/page/event_with_hit_test_results.h" |
| #include "third_party/blink/renderer/core/page/focus_controller.h" |
| #include "third_party/blink/renderer/core/page/frame_tree.h" |
| #include "third_party/blink/renderer/core/page/page.h" |
| #include "third_party/blink/renderer/core/page/page_animator.h" |
| #include "third_party/blink/renderer/core/page/plugin_script_forbidden_scope.h" |
| #include "third_party/blink/renderer/core/page/pointer_lock_controller.h" |
| #include "third_party/blink/renderer/core/page/scrolling/fragment_anchor.h" |
| #include "third_party/blink/renderer/core/page/scrolling/root_scroller_controller.h" |
| #include "third_party/blink/renderer/core/page/scrolling/snap_coordinator.h" |
| #include "third_party/blink/renderer/core/page/scrolling/top_document_root_scroller_controller.h" |
| #include "third_party/blink/renderer/core/page/spatial_navigation_controller.h" |
| #include "third_party/blink/renderer/core/page/validation_message_client.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" |
| #include "third_party/blink/renderer/core/paint/timing/first_meaningful_paint_detector.h" |
| #include "third_party/blink/renderer/core/paint/timing/paint_timing.h" |
| #include "third_party/blink/renderer/core/permissions_policy/dom_feature_policy.h" |
| #include "third_party/blink/renderer/core/permissions_policy/permissions_policy_parser.h" |
| #include "third_party/blink/renderer/core/probe/core_probes.h" |
| #include "third_party/blink/renderer/core/resize_observer/resize_observer.h" |
| #include "third_party/blink/renderer/core/resize_observer/resize_observer_controller.h" |
| #include "third_party/blink/renderer/core/resize_observer/resize_observer_entry.h" |
| #include "third_party/blink/renderer/core/resize_observer/resize_observer_size.h" |
| #include "third_party/blink/renderer/core/script/detect_javascript_frameworks.h" |
| #include "third_party/blink/renderer/core/script/script_runner.h" |
| #include "third_party/blink/renderer/core/scroll/scrollbar_theme.h" |
| #include "third_party/blink/renderer/core/scroll/snap_event.h" |
| #include "third_party/blink/renderer/core/speculation_rules/document_speculation_rules.h" |
| #include "third_party/blink/renderer/core/svg/svg_document_extensions.h" |
| #include "third_party/blink/renderer/core/svg/svg_script_element.h" |
| #include "third_party/blink/renderer/core/svg/svg_title_element.h" |
| #include "third_party/blink/renderer/core/svg/svg_unknown_element.h" |
| #include "third_party/blink/renderer/core/svg/svg_use_element.h" |
| #include "third_party/blink/renderer/core/svg_element_factory.h" |
| #include "third_party/blink/renderer/core/svg_names.h" |
| #include "third_party/blink/renderer/core/timing/render_blocking_metrics_reporter.h" |
| #include "third_party/blink/renderer/core/timing/soft_navigation_heuristics.h" |
| #include "third_party/blink/renderer/core/trustedtypes/trusted_html.h" |
| #include "third_party/blink/renderer/core/view_transition/page_reveal_event.h" |
| #include "third_party/blink/renderer/core/view_transition/view_transition_supplement.h" |
| #include "third_party/blink/renderer/core/view_transition/view_transition_utils.h" |
| #include "third_party/blink/renderer/core/xml/parser/xml_document_parser.h" |
| #include "third_party/blink/renderer/core/xml_names.h" |
| #include "third_party/blink/renderer/core/xmlns_names.h" |
| #include "third_party/blink/renderer/platform/bindings/dom_data_store.h" |
| #include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h" |
| #include "third_party/blink/renderer/platform/bindings/exception_messages.h" |
| #include "third_party/blink/renderer/platform/bindings/exception_state.h" |
| #include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h" |
| #include "third_party/blink/renderer/platform/bindings/script_state.h" |
| #include "third_party/blink/renderer/platform/bindings/source_location.h" |
| #include "third_party/blink/renderer/platform/bindings/v8_dom_wrapper.h" |
| #include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h" |
| #include "third_party/blink/renderer/platform/fonts/font_performance.h" |
| #include "third_party/blink/renderer/platform/geometry/length_functions.h" |
| #include "third_party/blink/renderer/platform/heap/garbage_collected.h" |
| #include "third_party/blink/renderer/platform/heap/thread_state.h" |
| #include "third_party/blink/renderer/platform/instrumentation/histogram.h" |
| #include "third_party/blink/renderer/platform/instrumentation/instance_counters.h" |
| #include "third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.h" |
| #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" |
| #include "third_party/blink/renderer/platform/instrumentation/use_counter.h" |
| #include "third_party/blink/renderer/platform/language.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/null_resource_fetcher_properties.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" |
| #include "third_party/blink/renderer/platform/network/content_security_policy_parsers.h" |
| #include "third_party/blink/renderer/platform/network/http_parsers.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/event_loop.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/frame_or_worker_scheduler.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h" |
| #include "third_party/blink/renderer/platform/text/platform_locale.h" |
| #include "third_party/blink/renderer/platform/web_test_support.h" |
| #include "third_party/blink/renderer/platform/weborigin/origin_access_entry.h" |
| #include "third_party/blink/renderer/platform/weborigin/scheme_registry.h" |
| #include "third_party/blink/renderer/platform/weborigin/security_origin.h" |
| #include "third_party/blink/renderer/platform/widget/frame_widget.h" |
| #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h" |
| #include "third_party/blink/renderer/platform/wtf/functional.h" |
| #include "third_party/blink/renderer/platform/wtf/hash_functions.h" |
| #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" |
| #include "third_party/blink/renderer/platform/wtf/text/character_names.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_buffer.h" |
| #include "third_party/blink/renderer/platform/wtf/text/text_encoding_registry.h" |
| |
| #ifndef NDEBUG |
| using WeakDocumentSet = blink::HeapHashSet<blink::WeakMember<blink::Document>>; |
| static WeakDocumentSet& LiveDocumentSet(); |
| #endif |
| |
| namespace blink { |
| |
| namespace { |
| |
| constexpr char kTextHtml[] = "text/html"; |
| |
| // This enum must match the numbering for RequestStorageResult in |
| // histograms/enums.xml. Do not reorder or remove items, only add new items |
| // at the end. |
| enum class RequestStorageResult { |
| APPROVED_EXISTING_ACCESS = 0, |
| // APPROVED_NEW_GRANT = 1, |
| REJECTED_NO_USER_GESTURE = 2, |
| REJECTED_NO_ORIGIN = 3, |
| REJECTED_OPAQUE_ORIGIN = 4, |
| REJECTED_EXISTING_DENIAL = 5, |
| REJECTED_SANDBOXED = 6, |
| REJECTED_GRANT_DENIED = 7, |
| REJECTED_INCORRECT_FRAME = 8, |
| REJECTED_INSECURE_CONTEXT = 9, |
| APPROVED_PRIMARY_FRAME = 10, |
| REJECTED_CREDENTIALLESS_IFRAME = 11, |
| APPROVED_NEW_OR_EXISTING_GRANT = 12, |
| REJECTED_FENCED_FRAME = 13, |
| REJECTED_INVALID_ORIGIN = 14, |
| kMaxValue = REJECTED_INVALID_ORIGIN, |
| }; |
| void FireRequestStorageAccessHistogram(RequestStorageResult result) { |
| base::UmaHistogramEnumeration("API.StorageAccess.RequestStorageAccess2", |
| result); |
| } |
| |
| void FireRequestStorageAccessForHistogram(RequestStorageResult result) { |
| base::UmaHistogramEnumeration( |
| "API.TopLevelStorageAccess.RequestStorageAccessFor2", result); |
| } |
| |
| class IntrinsicSizeResizeObserverDelegate : public ResizeObserver::Delegate { |
| public: |
| void OnResize(const HeapVector<Member<ResizeObserverEntry>>& entries) final; |
| ResizeObserver::DeliveryTime Delivery() const final; |
| bool SkipNonAtomicInlineObservations() const final; |
| }; |
| |
| // Returns true if any of <object> ancestors don't start loading or are loading |
| // plugins/frames/images. If there are no <object> ancestors, this function |
| // returns false. |
| bool IsInIndeterminateObjectAncestor(const Element* element) { |
| if (!element->isConnected()) |
| return false; |
| for (; element; element = element->ParentOrShadowHostElement()) { |
| if (const auto* object = DynamicTo<HTMLObjectElement>(element)) { |
| if (!object->DidFinishLoading()) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // Helper function to notify both `first` and `second` that the priority scroll |
| // anchor status changed. This is used when, for example, a focused element |
| // changes from `first` to `second`. |
| void NotifyPriorityScrollAnchorStatusChanged(Node* first, Node* second) { |
| if (first) |
| first->NotifyPriorityScrollAnchorStatusChanged(); |
| if (second) |
| second->NotifyPriorityScrollAnchorStatusChanged(); |
| } |
| |
| // Before fetching the default URL, make sure it won't be blocked by CSP. The |
| // webpage didn't requested "/favicon.ico", it is automatic. Developers |
| // shouldn't suffer from any errors provoked by Chrome. |
| // See https://crbug.com/820846 |
| bool DefaultFaviconAllowedByCSP(const Document* document, const IconURL& icon) { |
| ExecutionContext* context = document->GetExecutionContext(); |
| if (!context) { |
| // LocalFrame::UpdateFaviconURL() is sometimes called after a LocalFrame |
| // swap. When this happens, the document has lost its ExecutionContext and |
| // the favicon won't be loaded anyway. The output of this function doesn't |
| // matter anymore. |
| return false; |
| } |
| |
| return context->GetContentSecurityPolicy()->AllowImageFromSource( |
| icon.icon_url_, icon.icon_url_, RedirectStatus::kNoRedirect, |
| ReportingDisposition::kSuppressReporting, |
| ContentSecurityPolicy::CheckHeaderType::kCheckAll); |
| } |
| |
| } // namespace |
| |
| static const unsigned kCMaxWriteRecursionDepth = 21; |
| |
| // This amount of time must have elapsed before we will even consider scheduling |
| // a layout without a delay. |
| // FIXME: For faster machines this value can really be lowered to 200. 250 is |
| // adequate, but a little high for dual G5s. :) |
| static const base::TimeDelta kCLayoutScheduleThreshold = |
| base::Milliseconds(250); |
| |
| // DOM Level 2 says (letters added): |
| // |
| // a) Name start characters must have one of the categories Ll, Lu, Lo, Lt, Nl. |
| // b) Name characters other than Name-start characters must have one of the |
| // categories Mc, Me, Mn, Lm, or Nd. |
| // c) Characters in the compatibility area (i.e. with character code greater |
| // than #xF900 and less than #xFFFE) are not allowed in XML names. |
| // d) Characters which have a font or compatibility decomposition (i.e. those |
| // with a "compatibility formatting tag" in field 5 of the database -- marked |
| // by field 5 beginning with a "<") are not allowed. |
| // e) The following characters are treated as name-start characters rather than |
| // name characters, because the property file classifies them as Alphabetic: |
| // [#x02BB-#x02C1], #x0559, #x06E5, #x06E6. |
| // f) Characters #x20DD-#x20E0 are excluded (in accordance with Unicode, section |
| // 5.14). |
| // g) Character #x00B7 is classified as an extender, because the property list |
| // so identifies it. |
| // h) Character #x0387 is added as a name character, because #x00B7 is its |
| // canonical equivalent. |
| // i) Characters ':' and '_' are allowed as name-start characters. |
| // j) Characters '-' and '.' are allowed as name characters. |
| // |
| // It also contains complete tables. If we decide it's better, we could include |
| // those instead of the following code. |
| |
| static inline bool IsValidNameStart(UChar32 c) { |
| // rule (e) above |
| if ((c >= 0x02BB && c <= 0x02C1) || c == 0x559 || c == 0x6E5 || c == 0x6E6) |
| return true; |
| |
| // rule (i) above |
| if (c == ':' || c == '_') |
| return true; |
| |
| // rules (a) and (f) above |
| const uint32_t kNameStartMask = |
| WTF::unicode::kLetter_Lowercase | WTF::unicode::kLetter_Uppercase | |
| WTF::unicode::kLetter_Other | WTF::unicode::kLetter_Titlecase | |
| WTF::unicode::kNumber_Letter; |
| if (!(WTF::unicode::Category(c) & kNameStartMask)) |
| return false; |
| |
| // rule (c) above |
| if (c >= 0xF900 && c < 0xFFFE) |
| return false; |
| |
| // rule (d) above |
| WTF::unicode::CharDecompositionType decomp_type = |
| WTF::unicode::DecompositionType(c); |
| if (decomp_type == WTF::unicode::kDecompositionFont || |
| decomp_type == WTF::unicode::kDecompositionCompat) |
| return false; |
| |
| return true; |
| } |
| |
| static inline bool IsValidNamePart(UChar32 c) { |
| // rules (a), (e), and (i) above |
| if (IsValidNameStart(c)) |
| return true; |
| |
| // rules (g) and (h) above |
| if (c == 0x00B7 || c == 0x0387) |
| return true; |
| |
| // rule (j) above |
| if (c == '-' || c == '.') |
| return true; |
| |
| // rules (b) and (f) above |
| const uint32_t kOtherNamePartMask = |
| WTF::unicode::kMark_NonSpacing | WTF::unicode::kMark_Enclosing | |
| WTF::unicode::kMark_SpacingCombining | WTF::unicode::kLetter_Modifier | |
| WTF::unicode::kNumber_DecimalDigit; |
| if (!(WTF::unicode::Category(c) & kOtherNamePartMask)) |
| return false; |
| |
| // rule (c) above |
| if (c >= 0xF900 && c < 0xFFFE) |
| return false; |
| |
| // rule (d) above |
| WTF::unicode::CharDecompositionType decomp_type = |
| WTF::unicode::DecompositionType(c); |
| if (decomp_type == WTF::unicode::kDecompositionFont || |
| decomp_type == WTF::unicode::kDecompositionCompat) |
| return false; |
| |
| return true; |
| } |
| |
| // Tests whether |name| is something the HTML parser would accept as a |
| // tag name. |
| template <typename CharType> |
| static inline bool IsValidElementNamePerHTMLParser(const CharType* characters, |
| unsigned length) { |
| CharType c = characters[0] | 0x20; |
| if (!('a' <= c && c <= 'z')) |
| return false; |
| |
| for (unsigned i = 1; i < length; ++i) { |
| c = characters[i]; |
| if (c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == ' ' || |
| c == '/' || c == '>') |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool IsValidElementNamePerHTMLParser(const String& name) { |
| unsigned length = name.length(); |
| if (!length) |
| return false; |
| |
| if (name.Is8Bit()) { |
| const LChar* characters = name.Characters8(); |
| return IsValidElementNamePerHTMLParser(characters, length); |
| } |
| const UChar* characters = name.Characters16(); |
| return IsValidElementNamePerHTMLParser(characters, length); |
| } |
| |
| // Tests whether |name| is a valid name per DOM spec. Also checks |
| // whether the HTML parser would accept this element name and counts |
| // cases of mismatches. |
| static bool IsValidElementName(Document* document, const String& name) { |
| bool is_valid_dom_name = Document::IsValidName(name); |
| bool is_valid_html_name = IsValidElementNamePerHTMLParser(name); |
| if (UNLIKELY(is_valid_html_name != is_valid_dom_name)) { |
| // This is inaccurate because it will not report activity in |
| // detached documents. However retrieving the frame from the |
| // bindings is too slow. |
| UseCounter::Count(document, |
| is_valid_dom_name |
| ? WebFeature::kElementNameDOMValidHTMLParserInvalid |
| : WebFeature::kElementNameDOMInvalidHTMLParserValid); |
| } |
| return is_valid_dom_name; |
| } |
| |
| static bool AcceptsEditingFocus(const Element& element) { |
| DCHECK(IsEditable(element)); |
| |
| return element.GetDocument().GetFrame() && RootEditableElement(element); |
| } |
| |
| uint64_t Document::global_tree_version_ = 0; |
| |
| static bool g_force_synchronous_parsing_for_testing = false; |
| |
| void IntrinsicSizeResizeObserverDelegate::OnResize( |
| const HeapVector<Member<ResizeObserverEntry>>& entries) { |
| for (const auto& entry : entries) { |
| DCHECK_GT(entry->contentBoxSize().size(), 0u); |
| entry->target()->LastRememberedSizeChanged(entry->contentBoxSize().at(0)); |
| } |
| } |
| |
| ResizeObserver::DeliveryTime IntrinsicSizeResizeObserverDelegate::Delivery() |
| const { |
| return ResizeObserver::DeliveryTime::kBeforeOthers; |
| } |
| |
| bool IntrinsicSizeResizeObserverDelegate::SkipNonAtomicInlineObservations() |
| const { |
| return true; |
| } |
| |
| void Document::UnassociatedListedElementsList::MarkDirty() { |
| dirty_ = true; |
| list_.clear(); |
| } |
| |
| void Document::UnassociatedListedElementsList::Trace(Visitor* visitor) const { |
| visitor->Trace(list_); |
| } |
| |
| const ListedElement::List& Document::UnassociatedListedElementsList::Get( |
| const Document& owner) { |
| if (dirty_) { |
| const Node& root = owner.GetTreeScope().RootNode(); |
| DCHECK(list_.empty()); |
| |
| if (base::FeatureList::IsEnabled( |
| features::kAutofillIncludeShadowDomInUnassociatedListedElements)) { |
| for (Node& current : |
| ShadowIncludingTreeOrderTraversal::DescendantsOf(root)) { |
| if (HTMLElement* element = DynamicTo<HTMLElement>(current)) { |
| if (ListedElement* listed_element = ListedElement::From(*element); |
| listed_element && !listed_element->Form()) { |
| list_.push_back(listed_element); |
| } |
| } |
| } |
| } else { |
| for (HTMLElement& element : Traversal<HTMLElement>::StartsAfter(root)) { |
| ListedElement* listed_element = ListedElement::From(element); |
| if (listed_element && !listed_element->Form()) { |
| list_.push_back(listed_element); |
| } |
| } |
| } |
| dirty_ = false; |
| } |
| return list_; |
| } |
| |
| const ListedElement::List& Document::UnassociatedListedElements() const { |
| return const_cast<Document*>(this)->unassociated_listed_elements_.Get(*this); |
| } |
| |
| void Document::MarkUnassociatedListedElementsDirty() { |
| unassociated_listed_elements_.MarkDirty(); |
| } |
| |
| void Document::TopLevelFormsList::MarkDirty() { |
| dirty_ = true; |
| list_.clear(); |
| } |
| |
| void Document::TopLevelFormsList::Trace(Visitor* visitor) const { |
| visitor->Trace(list_); |
| } |
| |
| const HeapVector<Member<HTMLFormElement>>& Document::TopLevelFormsList::Get( |
| Document& owner) { |
| if (dirty_) { |
| // Use BFS to avoid unnecessarily visiting the descendants of form elements. |
| HeapDeque<Member<Node>> nodes_to_visit; |
| nodes_to_visit.push_back(&owner.GetTreeScope().RootNode()); |
| while (!nodes_to_visit.empty()) { |
| Node* current = nodes_to_visit.TakeFirst(); |
| if (HTMLFormElement* form = DynamicTo<HTMLFormElement>(*current)) { |
| list_.push_back(form); |
| } else { |
| for (Node& child : |
| ShadowIncludingTreeOrderTraversal::ChildrenOf(*current)) { |
| nodes_to_visit.push_back(&child); |
| } |
| } |
| } |
| dirty_ = false; |
| } |
| return list_; |
| } |
| |
| const HeapVector<Member<HTMLFormElement>>& Document::GetTopLevelForms() { |
| return top_level_forms_.Get(*this); |
| } |
| |
| void Document::MarkTopLevelFormsDirty() { |
| top_level_forms_.MarkDirty(); |
| } |
| |
| ExplicitlySetAttrElementsMap* Document::GetExplicitlySetAttrElementsMap( |
| const Element* element) { |
| DCHECK(element); |
| DCHECK(element->GetDocument() == this); |
| auto add_result = |
| element_explicitly_set_attr_elements_map_.insert(element, nullptr); |
| if (add_result.is_new_entry) { |
| add_result.stored_value->value = |
| MakeGarbageCollected<ExplicitlySetAttrElementsMap>(); |
| } |
| return add_result.stored_value->value.Get(); |
| } |
| |
| void Document::MoveElementExplicitlySetAttrElementsMapToNewDocument( |
| const Element* element, |
| Document& new_document) { |
| DCHECK(element); |
| auto it = element_explicitly_set_attr_elements_map_.find(element); |
| if (it != element_explicitly_set_attr_elements_map_.end()) { |
| new_document.element_explicitly_set_attr_elements_map_.insert(element, |
| it->value); |
| element_explicitly_set_attr_elements_map_.erase(it); |
| } |
| } |
| |
| CachedAttrAssociatedElementsMap* Document::GetCachedAttrAssociatedElementsMap( |
| Element* element) { |
| DCHECK(element); |
| DCHECK(element->GetDocument() == this); |
| auto add_result = |
| element_cached_attr_associated_elements_map_.insert(element, nullptr); |
| if (add_result.is_new_entry) { |
| add_result.stored_value->value = |
| MakeGarbageCollected<CachedAttrAssociatedElementsMap>(); |
| } |
| return add_result.stored_value->value.Get(); |
| } |
| |
| void Document::MoveElementCachedAttrAssociatedElementsMapToNewDocument( |
| Element* element, |
| Document& new_document) { |
| DCHECK(element); |
| auto it = element_cached_attr_associated_elements_map_.find(element); |
| if (it != element_cached_attr_associated_elements_map_.end()) { |
| new_document.element_cached_attr_associated_elements_map_.insert(element, |
| it->value); |
| element_cached_attr_associated_elements_map_.erase(it); |
| } |
| } |
| |
| UnloadEventTimingInfo::UnloadEventTimingInfo( |
| scoped_refptr<SecurityOrigin> new_document_origin) |
| : new_document_origin(std::move(new_document_origin)) {} |
| |
| Document* Document::Create(Document& document) { |
| return MakeGarbageCollected<Document>( |
| DocumentInit::Create() |
| .WithExecutionContext(document.GetExecutionContext()) |
| .WithAgent(document.GetAgent()) |
| .WithURL(BlankURL())); |
| } |
| |
| Document* Document::CreateForTest(ExecutionContext& execution_context) { |
| return MakeGarbageCollected<Document>( |
| DocumentInit::Create().ForTest(execution_context)); |
| } |
| |
| Document::Document(const DocumentInit& initializer, |
| DocumentClassFlags document_classes) |
| : ContainerNode(nullptr, kCreateDocument), |
| TreeScope( |
| *this, |
| static_cast<V8ObservableArrayCSSStyleSheet::SetAlgorithmCallback>( |
| &Document::OnAdoptedStyleSheetSet), |
| static_cast<V8ObservableArrayCSSStyleSheet::DeleteAlgorithmCallback>( |
| &Document::OnAdoptedStyleSheetDelete)), |
| token_(initializer.GetToken()), |
| is_initial_empty_document_(initializer.IsInitialEmptyDocument()), |
| is_prerendering_(initializer.IsPrerendering()), |
| evaluate_media_queries_on_style_recalc_(false), |
| pending_sheet_layout_(kNoLayoutWithPendingSheets), |
| dom_window_(initializer.GetWindow()), |
| execution_context_(initializer.GetExecutionContext()), |
| agent_(initializer.GetAgent()), |
| http_refresh_scheduler_(MakeGarbageCollected<HttpRefreshScheduler>(this)), |
| well_formed_(false), |
| fallback_base_url_(initializer.FallbackBaseURL()), |
| cookie_url_(dom_window_ ? initializer.GetCookieUrl() |
| : KURL(g_empty_string)), |
| printing_(kNotPrinting), |
| paint_preview_(kNotPaintingPreview), |
| compatibility_mode_(kNoQuirksMode), |
| compatibility_mode_locked_(false), |
| last_focus_type_(mojom::blink::FocusType::kNone), |
| had_keyboard_event_(false), |
| clear_focused_element_timer_( |
| GetTaskRunner(TaskType::kInternalUserInteraction), |
| this, |
| &Document::ClearFocusedElementTimerFired), |
| dom_tree_version_(++global_tree_version_), |
| style_version_(0), |
| listener_types_(0), |
| mutation_observer_types_(0), |
| visited_link_state_(MakeGarbageCollected<VisitedLinkState>(*this)), |
| visually_ordered_(false), |
| // https://html.spec.whatwg.org/multipage/dom.html#current-document-readiness |
| // says the ready state starts as 'loading' if there's an associated |
| // parser and 'complete' otherwise. We don't know whether there's an |
| // associated parser here (we create the parser in ImplicitOpen). But |
| // waiting to set the ready state to 'loading' in ImplicitOpen fires a |
| // readystatechange event, which can be observed in the case where we |
| // reuse a window. If there's a window being reused, there must be an |
| // associated parser, so setting based on dom_window_ here is sufficient |
| // to ensure that the quirk of when we set the ready state is not |
| // web-observable. |
| ready_state_(dom_window_ ? kLoading : kComplete), |
| parsing_state_(kFinishedParsing), |
| contains_plugins_(false), |
| ignore_destructive_write_count_(0), |
| throw_on_dynamic_markup_insertion_count_(0), |
| ignore_opens_during_unload_count_(0), |
| markers_(MakeGarbageCollected<DocumentMarkerController>(*this)), |
| css_target_(nullptr), |
| was_discarded_(false), |
| load_event_progress_(kLoadEventCompleted), |
| is_freezing_in_progress_(false), |
| script_runner_(MakeGarbageCollected<ScriptRunner>(this)), |
| script_runner_delayer_(MakeGarbageCollected<ScriptRunnerDelayer>( |
| script_runner_, |
| ScriptRunner::DelayReason::kMilestone)), |
| xml_version_("1.0"), |
| xml_standalone_(kStandaloneUnspecified), |
| has_xml_declaration_(0), |
| viewport_unit_flags_(0), |
| design_mode_(false), |
| is_running_exec_command_(false), |
| has_draggable_regions_(false), |
| draggable_regions_dirty_(false), |
| document_classes_(document_classes), |
| is_view_source_(false), |
| saw_elements_in_known_namespaces_(false), |
| is_srcdoc_document_(initializer.IsSrcdocDocument()), |
| is_mobile_document_(false), |
| layout_view_(nullptr), |
| load_event_delay_count_(0), |
| // We already intentionally fire load event asynchronously and here we use |
| // kDOMManipulation to ensure that we run onload() in order with other |
| // callbacks (e.g. onloadstart()) per the spec. |
| // See: https://html.spec.whatwg.org/#delay-the-load-event |
| load_event_delay_timer_(GetTaskRunner(TaskType::kDOMManipulation), |
| this, |
| &Document::LoadEventDelayTimerFired), |
| plugin_loading_timer_(GetTaskRunner(TaskType::kInternalLoading), |
| this, |
| &Document::PluginLoadingTimerFired), |
| document_timing_(*this), |
| write_recursion_is_too_deep_(false), |
| write_recursion_depth_(0), |
| scripted_animation_controller_( |
| MakeGarbageCollected<ScriptedAnimationController>(domWindow())), |
| element_data_cache_clear_timer_( |
| GetTaskRunner(TaskType::kInternalUserInteraction), |
| this, |
| &Document::ElementDataCacheClearTimerFired), |
| document_animations_(MakeGarbageCollected<DocumentAnimations>(this)), |
| timeline_(MakeGarbageCollected<DocumentTimeline>(this)), |
| pending_animations_(MakeGarbageCollected<PendingAnimations>(*this)), |
| worklet_animation_controller_( |
| MakeGarbageCollected<WorkletAnimationController>(this)), |
| template_document_host_(nullptr), |
| parser_sync_policy_(kAllowDeferredParsing), |
| // Use the source id from the document initializer if it is available. |
| // Otherwise, generate a new source id to cover any cases that don't |
| // receive a valid source id, this for example includes but is not limited |
| // to SVGImage which does not have an associated RenderFrameHost. No URLs |
| // will be associated to this source id. No DocumentCreated events will be |
| // created either. |
| ukm_source_id_(initializer.UkmSourceId() == ukm::kInvalidSourceId |
| ? ukm::UkmRecorder::GetNewSourceID() |
| : initializer.UkmSourceId()), |
| viewport_data_(MakeGarbageCollected<ViewportData>(*this)), |
| is_for_external_handler_(initializer.IsForExternalHandler()), |
| base_auction_nonce_(initializer.BaseAuctionNonce()), |
| fragment_directive_(MakeGarbageCollected<FragmentDirective>(*this)), |
| display_lock_document_state_( |
| MakeGarbageCollected<DisplayLockDocumentState>(this)), |
| render_blocking_resource_manager_( |
| initializer.GetType() == DocumentInit::Type::kHTML |
| ? MakeGarbageCollected<RenderBlockingResourceManager>(*this) |
| : nullptr), |
| data_(MakeGarbageCollected<DocumentData>(GetExecutionContext())) { |
| TRACE_EVENT_WITH_FLOW0("blink", "Document::Document", TRACE_ID_LOCAL(this), |
| TRACE_EVENT_FLAG_FLOW_OUT); |
| DCHECK(agent_); |
| if (base::FeatureList::IsEnabled(features::kDelayAsyncScriptExecution) && |
| features::kDelayAsyncScriptExecutionDelayByDefaultParam.Get()) { |
| script_runner_delayer_->Activate(); |
| } |
| |
| if (GetFrame()) { |
| DCHECK(GetFrame()->GetPage()); |
| fetcher_ = FrameFetchContext::CreateFetcherForCommittedDocument( |
| *GetFrame()->Loader().GetDocumentLoader(), *this); |
| cookie_jar_ = MakeGarbageCollected<CookieJar>(this); |
| } else { |
| // We disable fetches for frame-less Documents. |
| // See https://crbug.com/961614 for details. |
| auto& properties = |
| *MakeGarbageCollected<DetachableResourceFetcherProperties>( |
| *MakeGarbageCollected<NullResourceFetcherProperties>()); |
| fetcher_ = MakeGarbageCollected<ResourceFetcher>( |
| ResourceFetcherInit(properties, &FetchContext::NullInstance(), |
| GetTaskRunner(TaskType::kNetworking), |
| GetTaskRunner(TaskType::kNetworkingUnfreezable), |
| nullptr /* loader_factory */, GetExecutionContext(), |
| nullptr /* back_forward_cache_loader_helper */)); |
| } |
| DCHECK(fetcher_); |
| |
| // Since CSSFontSelector requires Document::fetcher_ and StyleEngine owns |
| // CSSFontSelector, need to initialize |style_engine_| after initializing |
| // |fetcher_|. |
| style_engine_ = MakeGarbageCollected<StyleEngine>(*this); |
| |
| root_scroller_controller_ = |
| MakeGarbageCollected<RootScrollerController>(*this); |
| |
| // We depend on the url getting immediately set in subframes, but we |
| // also depend on the url NOT getting immediately set in opened windows. |
| // See fast/dom/early-frame-url.html |
| // and fast/dom/location-new-window-no-crash.html, respectively. |
| // FIXME: Can/should we unify this behavior? |
| if (initializer.ShouldSetURL()) { |
| SetURL(initializer.Url()); |
| } else { |
| // Even if this document has no URL, we need to initialize base URL with |
| // fallback base URL. |
| UpdateBaseURL(); |
| } |
| should_record_sandboxed_srcdoc_baseurl_metrics_ = |
| urlForBinding().IsAboutSrcdocURL() && !fallback_base_url_.IsNull() && |
| dom_window_->IsSandboxed(network::mojom::blink::WebSandboxFlags::kOrigin); |
| |
| is_vertical_scroll_enforced_ = |
| GetFrame() && !GetFrame()->IsOutermostMainFrame() && |
| RuntimeEnabledFeatures::ExperimentalPoliciesEnabled() && |
| !dom_window_->IsFeatureEnabled( |
| mojom::blink::PermissionsPolicyFeature::kVerticalScroll); |
| |
| InitDNSPrefetch(); |
| |
| InstanceCounters::IncrementCounter(InstanceCounters::kDocumentCounter); |
| |
| lifecycle_.AdvanceTo(DocumentLifecycle::kInactive); |
| |
| UpdateThemeColorCache(); |
| |
| // The parent's parser should be suspended together with all the other |
| // objects, else this new Document would have a new ExecutionContext which |
| // suspended state would not match the one from the parent, and could start |
| // loading resources ignoring the defersLoading flag. |
| DCHECK(!ParentDocument() || |
| !ParentDocument()->domWindow()->IsContextPaused()); |
| |
| #ifndef NDEBUG |
| LiveDocumentSet().insert(this); |
| #endif |
| } |
| |
| Document::~Document() { |
| DCHECK(!GetLayoutView()); |
| DCHECK(!ParentTreeScope()); |
| // If a top document with a cache, verify that it was comprehensively |
| // cleared during detach. |
| DCHECK(!ax_object_cache_); |
| |
| InstanceCounters::DecrementCounter(InstanceCounters::kDocumentCounter); |
| if (WebTestSupport::IsRunningWebTest() && ukm_recorder_) { |
| ukm::DelegatingUkmRecorder::Get()->RemoveDelegate(ukm_recorder_.get()); |
| } |
| } |
| |
| Range* Document::CreateRangeAdjustedToTreeScope(const TreeScope& tree_scope, |
| const Position& position) { |
| const Position& adjusted_position = |
| PositionAdjustedToTreeScope(tree_scope, position); |
| return MakeGarbageCollected<Range>(tree_scope.GetDocument(), |
| adjusted_position, adjusted_position); |
| } |
| |
| CaretPosition* Document::CreateCaretPosition(const Position& position) { |
| return MakeGarbageCollected<CaretPosition>( |
| position.AnchorNode(), position.ComputeOffsetInContainerNode()); |
| } |
| |
| const Position Document::PositionAdjustedToTreeScope( |
| const TreeScope& tree_scope, |
| const Position& position) { |
| DCHECK(position.IsNotNull()); |
| // Note: Since |Position::ComputeContainerNode()| returns |nullptr| if |
| // |position| is |BeforeAnchor| or |AfterAnchor|. |
| Node* const anchor_node = position.AnchorNode(); |
| if (anchor_node->GetTreeScope() == tree_scope) { |
| return position; |
| } |
| Node* const shadow_host = tree_scope.AncestorInThisScope(anchor_node); |
| return Position::BeforeNode(*shadow_host); |
| } |
| |
| SelectorQueryCache& Document::GetSelectorQueryCache() { |
| if (!selector_query_cache_) |
| selector_query_cache_ = std::make_unique<SelectorQueryCache>(); |
| return *selector_query_cache_; |
| } |
| |
| MediaQueryMatcher& Document::GetMediaQueryMatcher() { |
| if (!media_query_matcher_) { |
| media_query_matcher_ = MakeGarbageCollected<MediaQueryMatcher>(*this); |
| } |
| return *media_query_matcher_; |
| } |
| |
| void Document::MediaQueryAffectingValueChanged(MediaValueChange change) { |
| GetStyleEngine().MediaQueryAffectingValueChanged(change); |
| if (NeedsLayoutTreeUpdate()) |
| evaluate_media_queries_on_style_recalc_ = true; |
| else |
| EvaluateMediaQueryList(); |
| probe::MediaQueryResultChanged(this); |
| } |
| |
| void Document::SetCompatibilityMode(CompatibilityMode mode) { |
| if (compatibility_mode_locked_ || mode == compatibility_mode_) |
| return; |
| |
| if (mode == kQuirksMode) { |
| UseCounter::Count(*this, WebFeature::kQuirksModeDocument); |
| if (urlForBinding().IsAboutBlankURL()) { |
| UseCounter::Count(*this, WebFeature::kQuirksModeAboutBlankDocument); |
| } |
| } else if (mode == kLimitedQuirksMode) { |
| UseCounter::Count(*this, WebFeature::kLimitedQuirksModeDocument); |
| } |
| |
| compatibility_mode_ = mode; |
| GetSelectorQueryCache().Invalidate(); |
| } |
| |
| String Document::compatMode() const { |
| return InQuirksMode() ? "BackCompat" : "CSS1Compat"; |
| } |
| |
| void Document::SetDoctype(DocumentType* doc_type) { |
| // This should never be called more than once. |
| DCHECK(!doc_type_ || !doc_type); |
| doc_type_ = doc_type; |
| if (doc_type_) { |
| AdoptIfNeeded(*doc_type_); |
| if (doc_type_->publicId().StartsWithIgnoringASCIICase( |
| "-//wapforum//dtd xhtml mobile 1.")) { |
| is_mobile_document_ = true; |
| style_engine_->ViewportStyleSettingChanged(); |
| } |
| } |
| } |
| |
| DOMImplementation& Document::implementation() { |
| if (!implementation_) |
| implementation_ = MakeGarbageCollected<DOMImplementation>(*this); |
| return *implementation_; |
| } |
| |
| Location* Document::location() const { |
| if (!GetFrame()) |
| return nullptr; |
| |
| return domWindow()->location(); |
| } |
| |
| bool Document::DocumentPolicyFeatureObserved( |
| mojom::blink::DocumentPolicyFeature feature) { |
| wtf_size_t feature_index = static_cast<wtf_size_t>(feature); |
| if (parsed_document_policies_.size() == 0) { |
| parsed_document_policies_.resize( |
| static_cast<wtf_size_t>( |
| mojom::blink::DocumentPolicyFeature::kMaxValue) + |
| 1); |
| } else if (parsed_document_policies_[feature_index]) { |
| return true; |
| } |
| parsed_document_policies_[feature_index] = true; |
| return false; |
| } |
| |
| void Document::ChildrenChanged(const ChildrenChange& change) { |
| ContainerNode::ChildrenChanged(change); |
| document_element_ = ElementTraversal::FirstWithin(*this); |
| |
| // For non-HTML documents the willInsertBody notification won't happen |
| // so we resume as soon as we have a document element. Even for XHTML |
| // documents there may never be a <body> (since the parser won't always |
| // insert one), so we resume here too. That does mean XHTML documents make |
| // frames when there's only a <head>, but such documents are pretty rare. |
| if (document_element_ && !IsA<HTMLDocument>(this)) |
| BeginLifecycleUpdatesIfRenderingReady(); |
| } |
| |
| bool Document::IsInMainFrame() const { |
| return GetFrame() && GetFrame()->IsMainFrame(); |
| } |
| |
| bool Document::IsInOutermostMainFrame() const { |
| return GetFrame() && GetFrame()->IsOutermostMainFrame(); |
| } |
| |
| AtomicString Document::ConvertLocalName(const AtomicString& name) { |
| return IsA<HTMLDocument>(this) ? name.LowerASCII() : name; |
| } |
| |
| // Just creates an element with specified qualified name without any |
| // custom element processing. |
| // This is a common code for step 5.2 and 7.2 of "create an element" |
| // <https://dom.spec.whatwg.org/#concept-create-element> |
| // Functions other than this one should not use HTMLElementFactory and |
| // SVGElementFactory because they don't support prefixes correctly. |
| Element* Document::CreateRawElement(const QualifiedName& qname, |
| CreateElementFlags flags) { |
| Element* element = nullptr; |
| if (qname.NamespaceURI() == html_names::xhtmlNamespaceURI) { |
| // https://html.spec.whatwg.org/C/#elements-in-the-dom:element-interface |
| element = HTMLElementFactory::Create(qname.LocalName(), *this, flags); |
| if (!element) { |
| // 6. If name is a valid custom element name, then return |
| // HTMLElement. |
| // 7. Return HTMLUnknownElement. |
| if (CustomElement::IsValidName(qname.LocalName())) |
| element = MakeGarbageCollected<HTMLElement>(qname, *this); |
| else |
| element = MakeGarbageCollected<HTMLUnknownElement>(qname, *this); |
| } |
| saw_elements_in_known_namespaces_ = true; |
| } else if (qname.NamespaceURI() == svg_names::kNamespaceURI) { |
| element = SVGElementFactory::Create(qname.LocalName(), *this, flags); |
| if (!element) |
| element = MakeGarbageCollected<SVGUnknownElement>(qname, *this); |
| saw_elements_in_known_namespaces_ = true; |
| } else if (qname.NamespaceURI() == mathml_names::kNamespaceURI) { |
| element = MathMLElementFactory::Create(qname.LocalName(), *this, flags); |
| // An unknown MathML element is treated like an <mrow> element. |
| // TODO(crbug.com/1021837): Determine if we need to introduce a |
| // MathMLUnknownElement IDL. |
| if (!element) { |
| element = MakeGarbageCollected<MathMLRowElement>(qname, *this); |
| } |
| saw_elements_in_known_namespaces_ = true; |
| } else { |
| element = MakeGarbageCollected<Element>(qname, this); |
| } |
| |
| if (element->prefix() != qname.Prefix()) |
| element->SetTagNameForCreateElementNS(qname); |
| DCHECK(qname == element->TagQName()); |
| |
| return element; |
| } |
| |
| // https://dom.spec.whatwg.org/#dom-document-createelement |
| // TODO(crbug.com/1304439): Move it to `tree_scope.cc` if the feature |
| // `ScopedCustomElementRegistry` can stabilize. |
| Element* TreeScope::CreateElementForBinding(const AtomicString& name, |
| ExceptionState& exception_state) { |
| Document& document = GetDocument(); |
| if (!IsValidElementName(&document, name)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidCharacterError, |
| "The tag name provided ('" + name + "') is not a valid name."); |
| return nullptr; |
| } |
| |
| if (document.IsXHTMLDocument() || IsA<HTMLDocument>(document)) { |
| // 2. If the context object is an HTML document, let localName be |
| // converted to ASCII lowercase. |
| AtomicString local_name = document.ConvertLocalName(name); |
| if (CustomElement::ShouldCreateCustomElement(local_name)) { |
| return CustomElement::CreateCustomElement( |
| *this, |
| QualifiedName(g_null_atom, local_name, html_names::xhtmlNamespaceURI), |
| IsA<ShadowRoot>(this) |
| ? CreateElementFlags::ByShadowRootCreateElement() |
| : CreateElementFlags::ByCreateElement()); |
| } |
| if (auto* element = HTMLElementFactory::Create( |
| local_name, document, CreateElementFlags::ByCreateElement())) { |
| return element; |
| } |
| QualifiedName q_name(g_null_atom, local_name, |
| html_names::xhtmlNamespaceURI); |
| return MakeGarbageCollected<HTMLUnknownElement>(q_name, document); |
| } |
| return MakeGarbageCollected<Element>(QualifiedName(name), &document); |
| } |
| |
| AtomicString GetTypeExtension( |
| Document* document, |
| const V8UnionElementCreationOptionsOrString* string_or_options) { |
| DCHECK(string_or_options); |
| |
| switch (string_or_options->GetContentType()) { |
| case V8UnionElementCreationOptionsOrString::ContentType:: |
| kElementCreationOptions: { |
| const ElementCreationOptions* options = |
| string_or_options->GetAsElementCreationOptions(); |
| if (options->hasIs()) |
| return AtomicString(options->is()); |
| return AtomicString(); |
| } |
| case V8UnionElementCreationOptionsOrString::ContentType::kString: |
| UseCounter::Count(document, |
| WebFeature::kDocumentCreateElement2ndArgStringHandling); |
| return AtomicString(string_or_options->GetAsString()); |
| } |
| NOTREACHED(); |
| return AtomicString(); |
| } |
| |
| // https://dom.spec.whatwg.org/#dom-document-createelement |
| // TODO(crbug.com/1304439): Move it to `tree_scope.cc` if the feature |
| // `ScopedCustomElementRegistry` can stabilize. |
| Element* TreeScope::CreateElementForBinding( |
| const AtomicString& local_name, |
| const V8UnionElementCreationOptionsOrString* string_or_options, |
| ExceptionState& exception_state) { |
| if (!string_or_options) { |
| return CreateElementForBinding(local_name, exception_state); |
| } |
| |
| Document& document = GetDocument(); |
| |
| // 1. If localName does not match Name production, throw InvalidCharacterError |
| if (!IsValidElementName(&document, local_name)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidCharacterError, |
| "The tag name provided ('" + local_name + "') is not a valid name."); |
| return nullptr; |
| } |
| |
| // 2. localName converted to ASCII lowercase |
| const AtomicString& converted_local_name = |
| document.ConvertLocalName(local_name); |
| QualifiedName q_name(g_null_atom, converted_local_name, |
| document.IsXHTMLDocument() || IsA<HTMLDocument>(document) |
| ? html_names::xhtmlNamespaceURI |
| : g_null_atom); |
| |
| // 3. |
| const AtomicString& is = GetTypeExtension(&document, string_or_options); |
| |
| // 5. Let element be the result of creating an element given ... |
| Element* element = |
| CreateElement(q_name, CreateElementFlags::ByCreateElement(), is); |
| |
| return element; |
| } |
| |
| static inline QualifiedName CreateQualifiedName( |
| const AtomicString& namespace_uri, |
| const AtomicString& qualified_name, |
| ExceptionState& exception_state) { |
| AtomicString prefix, local_name; |
| if (!Document::ParseQualifiedName(qualified_name, prefix, local_name, |
| exception_state)) |
| return QualifiedName::Null(); |
| |
| QualifiedName q_name(prefix, local_name, namespace_uri); |
| if (!Document::HasValidNamespaceForElements(q_name)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNamespaceError, |
| "The namespace URI provided ('" + namespace_uri + |
| "') is not valid for the qualified name provided ('" + |
| qualified_name + "')."); |
| return QualifiedName::Null(); |
| } |
| |
| return q_name; |
| } |
| |
| // TODO(crbug.com/1304439): Move it to `tree_scope.cc` if the feature |
| // `ScopedCustomElementRegistry` can stabilize. |
| Element* TreeScope::createElementNS(const AtomicString& namespace_uri, |
| const AtomicString& qualified_name, |
| ExceptionState& exception_state) { |
| QualifiedName q_name( |
| CreateQualifiedName(namespace_uri, qualified_name, exception_state)); |
| if (q_name == QualifiedName::Null()) |
| return nullptr; |
| |
| CreateElementFlags flags = CreateElementFlags::ByCreateElement(); |
| if (CustomElement::ShouldCreateCustomElement(q_name)) { |
| return CustomElement::CreateCustomElement( |
| *this, q_name, |
| IsA<ShadowRoot>(this) ? CreateElementFlags::ByShadowRootCreateElement() |
| : CreateElementFlags::ByCreateElement()); |
| } |
| return GetDocument().CreateRawElement(q_name, flags); |
| } |
| |
| // https://dom.spec.whatwg.org/#internal-createelementns-steps |
| // TODO(crbug.com/1304439): Move it to `tree_scope.cc` if the feature |
| // `ScopedCustomElementRegistry` can stabilize. |
| Element* TreeScope::createElementNS( |
| const AtomicString& namespace_uri, |
| const AtomicString& qualified_name, |
| const V8UnionElementCreationOptionsOrString* string_or_options, |
| ExceptionState& exception_state) { |
| DCHECK(string_or_options); |
| |
| // 1. Validate and extract |
| QualifiedName q_name( |
| CreateQualifiedName(namespace_uri, qualified_name, exception_state)); |
| if (q_name == QualifiedName::Null()) |
| return nullptr; |
| |
| Document& document = GetDocument(); |
| |
| // 2. |
| const AtomicString& is = GetTypeExtension(&document, string_or_options); |
| |
| if (!IsValidElementName(&document, qualified_name)) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidCharacterError, |
| "The tag name provided ('" + |
| qualified_name + |
| "') is not a valid name."); |
| return nullptr; |
| } |
| |
| // 3. Let element be the result of creating an element |
| Element* element = |
| CreateElement(q_name, CreateElementFlags::ByCreateElement(), is); |
| |
| return element; |
| } |
| |
| // Entry point of "create an element". |
| // https://dom.spec.whatwg.org/#concept-create-element |
| // TODO(crbug.com/1304439): Move it to `tree_scope.cc` if the feature |
| // `ScopedCustomElementRegistry` can stabilize. |
| Element* TreeScope::CreateElement(const QualifiedName& q_name, |
| const CreateElementFlags flags, |
| const AtomicString& is) { |
| CustomElementDefinition* definition = nullptr; |
| if (flags.IsCustomElements() && |
| q_name.NamespaceURI() == html_names::xhtmlNamespaceURI) { |
| const CustomElementDescriptor desc(is.IsNull() ? q_name.LocalName() : is, |
| q_name.LocalName()); |
| if (CustomElementRegistry* registry = CustomElement::Registry(*this)) |
| definition = registry->DefinitionFor(desc); |
| } |
| |
| if (definition) |
| return definition->CreateElement(GetDocument(), q_name, flags); |
| |
| return CustomElement::CreateUncustomizedOrUndefinedElement(GetDocument(), |
| q_name, flags, is); |
| } |
| |
| DocumentFragment* Document::createDocumentFragment() { |
| return DocumentFragment::Create(*this); |
| } |
| |
| Text* Document::createTextNode(const String& data) { |
| return Text::Create(*this, data); |
| } |
| |
| Comment* Document::createComment(const String& data) { |
| return Comment::Create(*this, data); |
| } |
| |
| CDATASection* Document::createCDATASection(const String& data, |
| ExceptionState& exception_state) { |
| if (IsA<HTMLDocument>(this)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotSupportedError, |
| "This operation is not supported for HTML documents."); |
| return nullptr; |
| } |
| if (data.Contains("]]>")) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidCharacterError, |
| "String cannot contain ']]>' since that " |
| "is the end delimiter of a CData " |
| "section."); |
| return nullptr; |
| } |
| return CDATASection::Create(*this, data); |
| } |
| |
| ProcessingInstruction* Document::createProcessingInstruction( |
| const String& target, |
| const String& data, |
| ExceptionState& exception_state) { |
| if (!IsValidName(target)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidCharacterError, |
| "The target provided ('" + target + "') is not a valid name."); |
| return nullptr; |
| } |
| if (data.Contains("?>")) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidCharacterError, |
| "The data provided ('" + data + "') contains '?>'."); |
| return nullptr; |
| } |
| if (IsA<HTMLDocument>(this)) { |
| UseCounter::Count(*this, |
| WebFeature::kHTMLDocumentCreateProcessingInstruction); |
| } |
| return MakeGarbageCollected<ProcessingInstruction>(*this, target, data); |
| } |
| |
| Text* Document::CreateEditingTextNode(const String& text) { |
| return Text::CreateEditingText(*this, text); |
| } |
| |
| Node* Document::importNode(Node* imported_node, |
| bool deep, |
| ExceptionState& exception_state) { |
| // https://dom.spec.whatwg.org/#dom-document-importnode |
| |
| // 1. If node is a document or shadow root, then throw a "NotSupportedError" |
| // DOMException. |
| if (imported_node->IsDocumentNode()) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotSupportedError, |
| "The node provided is a document, which may not be imported."); |
| return nullptr; |
| } |
| if (imported_node->IsShadowRoot()) { |
| // ShadowRoot nodes should not be explicitly importable. Either they are |
| // imported along with their host node, or created implicitly. |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotSupportedError, |
| "The node provided is a shadow root, which may not be imported."); |
| return nullptr; |
| } |
| |
| // 2. Return a clone of node, with context object, the clone children flag set |
| // if deep is true, and the clone shadows flag set if this is a |
| // DocumentFragment whose host is an HTML template element. |
| NodeCloningData data; |
| if (deep) { |
| data.Put(CloneOption::kIncludeDescendants); |
| if (!RuntimeEnabledFeatures::ShadowRootClonableEnabled()) { |
| auto* fragment = DynamicTo<DocumentFragment>(imported_node); |
| if (fragment && fragment->IsTemplateContent()) { |
| data.Put(CloneOption::kIncludeAllShadowRoots); |
| } |
| } |
| } |
| return imported_node->Clone(*this, data, /*append_to*/ nullptr); |
| } |
| |
| Node* Document::adoptNode(Node* source, ExceptionState& exception_state) { |
| EventQueueScope scope; |
| |
| switch (source->getNodeType()) { |
| case kDocumentNode: |
| exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError, |
| "The node provided is of type '" + |
| source->nodeName() + |
| "', which may not be adopted."); |
| return nullptr; |
| case kAttributeNode: { |
| auto* attr = To<Attr>(source); |
| if (Element* owner_element = attr->ownerElement()) |
| owner_element->removeAttributeNode(attr, exception_state); |
| break; |
| } |
| default: |
| if (source->IsShadowRoot()) { |
| // ShadowRoot cannot disconnect itself from the host node. |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kHierarchyRequestError, |
| "The node provided is a shadow root, which may not be adopted."); |
| return nullptr; |
| } |
| |
| if (auto* frame_owner_element = |
| DynamicTo<HTMLFrameOwnerElement>(source)) { |
| if (GetFrame() && GetFrame()->Tree().IsDescendantOf( |
| frame_owner_element->ContentFrame())) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kHierarchyRequestError, |
| "The node provided is a frame which contains this document."); |
| return nullptr; |
| } |
| } |
| if (source->parentNode()) { |
| source->parentNode()->RemoveChild(source, exception_state); |
| if (exception_state.HadException()) |
| return nullptr; |
| // The above removeChild() can execute arbitrary JavaScript code. |
| if (source->parentNode()) { |
| AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| ConsoleMessage::Source::kJavaScript, |
| ConsoleMessage::Level::kWarning, |
| ExceptionMessages::FailedToExecute("adoptNode", "Document", |
| "Unable to remove the " |
| "specified node from the " |
| "original parent."))); |
| return nullptr; |
| } |
| } |
| } |
| |
| AdoptIfNeeded(*source); |
| |
| return source; |
| } |
| |
| bool Document::HasValidNamespaceForElements(const QualifiedName& q_name) { |
| // These checks are from DOM Core Level 2, createElementNS |
| // http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-DocCrElNS |
| // createElementNS(null, "html:div") |
| if (!q_name.Prefix().empty() && q_name.NamespaceURI().IsNull()) |
| return false; |
| // createElementNS("http://www.example.com", "xml:lang") |
| if (q_name.Prefix() == g_xml_atom && |
| q_name.NamespaceURI() != xml_names::kNamespaceURI) |
| return false; |
| |
| // Required by DOM Level 3 Core and unspecified by DOM Level 2 Core: |
| // http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#ID-DocCrElNS |
| // createElementNS("http://www.w3.org/2000/xmlns/", "foo:bar"), |
| // createElementNS(null, "xmlns:bar"), createElementNS(null, "xmlns") |
| if (q_name.Prefix() == g_xmlns_atom || |
| (q_name.Prefix().empty() && q_name.LocalName() == g_xmlns_atom)) |
| return q_name.NamespaceURI() == xmlns_names::kNamespaceURI; |
| return q_name.NamespaceURI() != xmlns_names::kNamespaceURI; |
| } |
| |
| bool Document::HasValidNamespaceForAttributes(const QualifiedName& q_name) { |
| return HasValidNamespaceForElements(q_name); |
| } |
| |
| String Document::readyState() const { |
| DEFINE_STATIC_LOCAL(const String, loading, ("loading")); |
| DEFINE_STATIC_LOCAL(const String, interactive, ("interactive")); |
| DEFINE_STATIC_LOCAL(const String, complete, ("complete")); |
| |
| switch (ready_state_) { |
| case kLoading: |
| return loading; |
| case kInteractive: |
| return interactive; |
| case kComplete: |
| return complete; |
| } |
| |
| NOTREACHED(); |
| return String(); |
| } |
| |
| void Document::SetReadyState(DocumentReadyState ready_state) { |
| TRACE_EVENT_WITH_FLOW0("blink", "Document::SetReadyState", |
| TRACE_ID_LOCAL(this), |
| TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); |
| if (ready_state == ready_state_) |
| return; |
| |
| switch (ready_state) { |
| case kLoading: |
| if (document_timing_.DomLoading().is_null()) { |
| document_timing_.MarkDomLoading(); |
| } |
| break; |
| case kInteractive: |
| if (document_timing_.DomInteractive().is_null()) |
| document_timing_.MarkDomInteractive(); |
| break; |
| case kComplete: |
| if (document_timing_.DomComplete().is_null()) |
| document_timing_.MarkDomComplete(); |
| break; |
| } |
| |
| ready_state_ = ready_state; |
| if (GetFrame() && GetFrame()->GetPage() && |
| GetFrame()->GetPage()->GetPageScheduler()->IsInBackForwardCache()) { |
| // Enqueue the event when the page is in back/forward cache, so that it |
| // would not cause JavaScript execution. The event will be dispatched upon |
| // restore. |
| EnqueueEvent(*Event::Create(event_type_names::kReadystatechange), |
| TaskType::kInternalDefault); |
| } else { |
| // Synchronously dispatch event when the page is not in back/forward cache. |
| DispatchEvent(*Event::Create(event_type_names::kReadystatechange)); |
| } |
| } |
| |
| bool Document::IsLoadCompleted() const { |
| return ready_state_ == kComplete; |
| } |
| |
| AtomicString Document::EncodingName() const { |
| // TextEncoding::name() returns a char*, no need to allocate a new |
| // String for it each time. |
| // FIXME: We should fix TextEncoding to speak AtomicString anyway. |
| return AtomicString(Encoding().GetName()); |
| } |
| |
| void Document::SetContentLanguage(const AtomicString& language) { |
| if (content_language_ == language) |
| return; |
| content_language_ = language; |
| |
| // Document's style depends on the content language. |
| GetStyleEngine().MarkViewportStyleDirty(); |
| GetStyleEngine().MarkAllElementsForStyleRecalc( |
| StyleChangeReasonForTracing::Create(style_change_reason::kLanguage)); |
| } |
| |
| void Document::setXMLVersion(const String& version, |
| ExceptionState& exception_state) { |
| if (!XMLDocumentParser::SupportsXMLVersion(version)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotSupportedError, |
| "This document does not support the XML version '" + version + "'."); |
| return; |
| } |
| |
| xml_version_ = version; |
| } |
| |
| void Document::setXMLStandalone(bool standalone, |
| ExceptionState& exception_state) { |
| xml_standalone_ = standalone ? kStandalone : kNotStandalone; |
| } |
| |
| void Document::SetContent(const String& content) { |
| // Only set the content of the document if it is ready to be set. This method |
| // could be called at any time. |
| if (ScriptableDocumentParser* parser = GetScriptableDocumentParser()) { |
| if (parser->IsParsing() && parser->IsExecutingScript()) |
| return; |
| } |
| if (ignore_opens_during_unload_count_) |
| return; |
| |
| open(); |
| parser_->Append(content); |
| close(); |
| } |
| |
| using AllowState = blink::Document::DeclarativeShadowRootAllowState; |
| void Document::SetContentFromDOMParser(const String& content) { |
| if (RuntimeEnabledFeatures::DOMParserUsesHTMLFastPathParserEnabled() && |
| contentType() == "text/html" && IsA<HTMLDocument>(this)) { |
| auto* body = MakeGarbageCollected<HTMLBodyElement>(*this); |
| HTMLFragmentParsingBehaviorSet parser_behavior( |
| {HTMLFragmentParsingBehavior::kStripInitialWhitespaceForBody}); |
| if (declarative_shadow_root_allow_state_ == AllowState::kAllow) { |
| parser_behavior.Put(HTMLFragmentParsingBehavior::kIncludeShadowRoots); |
| } |
| // The default for html parsing is quirks mode. This is normally set during |
| // parsing, but not for the fast path, so it needs to be set here. If the |
| // fast-path parser fails, the full parser will adjust the mode |
| // appropriately. |
| SetCompatibilityMode(kQuirksMode); |
| const bool success = TryParsingHTMLFragment(content, *this, *body, *body, |
| kAllowScriptingContent, |
| parser_behavior, nullptr); |
| if (success) { |
| // When DCHECK is enabled, use SetContent() and verify fast-path |
| // content matches. This effectively means the results of the fast-path |
| // parser aren't used with DCHECK enabled, but it provides a way to |
| // catch problems. |
| #if DCHECK_IS_ON() |
| SetContent(content); |
| DCHECK(this->body()); |
| DCHECK_EQ(CreateMarkup(body), CreateMarkup(this->body())) |
| << " supplied value " << content; |
| DCHECK(body->isEqualNode(this->body())); |
| #else |
| auto* html = MakeGarbageCollected<HTMLHtmlElement>(*this); |
| auto* head = MakeGarbageCollected<HTMLHeadElement>(*this); |
| html->AppendChild(head); |
| AppendChild(html); |
| // Append `body` last so that the newly created children of `body` only |
| // get one InsertedInto(). |
| html->AppendChild(body); |
| #endif |
| return; |
| } |
| } |
| SetContent(content); |
| } |
| |
| String Document::SuggestedMIMEType() const { |
| if (IsA<XMLDocument>(this)) { |
| if (IsXHTMLDocument()) |
| return "application/xhtml+xml"; |
| if (IsSVGDocument()) |
| return "image/svg+xml"; |
| return "application/xml"; |
| } |
| if (xmlStandalone()) |
| return "text/xml"; |
| if (IsA<HTMLDocument>(this)) |
| return kTextHtml; |
| |
| if (DocumentLoader* document_loader = Loader()) |
| return document_loader->MimeType(); |
| return String(); |
| } |
| |
| void Document::SetMimeType(const AtomicString& mime_type) { |
| mime_type_ = mime_type; |
| } |
| |
| AtomicString Document::contentType() const { |
| if (!mime_type_.empty()) |
| return mime_type_; |
| |
| if (DocumentLoader* document_loader = Loader()) |
| return document_loader->MimeType(); |
| |
| String mime_type = SuggestedMIMEType(); |
| if (!mime_type.empty()) |
| return AtomicString(mime_type); |
| |
| return AtomicString("application/xml"); |
| } |
| |
| Range* Document::caretRangeFromPoint(int x, int y) { |
| if (!GetLayoutView()) |
| return nullptr; |
| |
| HitTestResult result = HitTestInDocument(this, x, y); |
| PositionWithAffinity position_with_affinity = result.GetPosition(); |
| if (position_with_affinity.IsNull()) |
| return nullptr; |
| |
| Position range_compliant_position = |
| position_with_affinity.GetPosition().ParentAnchoredEquivalent(); |
| return CreateRangeAdjustedToTreeScope(*this, range_compliant_position); |
| } |
| |
| CaretPosition* Document::caretPositionFromPoint(float x, float y) { |
| if (!GetLayoutView()) { |
| return nullptr; |
| } |
| |
| HitTestResult result = HitTestInDocument(this, x, y); |
| PositionWithAffinity position_with_affinity = result.GetPosition(); |
| if (position_with_affinity.IsNull()) { |
| return nullptr; |
| } |
| |
| return CreateCaretPosition( |
| position_with_affinity.GetPosition().ParentAnchoredEquivalent()); |
| } |
| |
| Element* Document::scrollingElement() { |
| if (RuntimeEnabledFeatures::ScrollTopLeftInteropEnabled() && InQuirksMode()) |
| UpdateStyleAndLayoutTree(); |
| return ScrollingElementNoLayout(); |
| } |
| |
| Element* Document::ScrollingElementNoLayout() { |
| if (RuntimeEnabledFeatures::ScrollTopLeftInteropEnabled()) { |
| if (InQuirksMode()) { |
| HTMLBodyElement* body = FirstBodyElement(); |
| if (body && body->GetLayoutObject() && |
| body->GetLayoutObject()->IsScrollContainer()) |
| return nullptr; |
| |
| return body; |
| } |
| |
| return documentElement(); |
| } |
| |
| return body(); |
| } |
| |
| /* |
| * Performs three operations: |
| * 1. Convert control characters to spaces |
| * 2. Trim leading and trailing spaces |
| * 3. Collapse internal whitespace. |
| */ |
| template <typename CharacterType> |
| static inline String CanonicalizedTitle(Document* document, |
| const String& title) { |
| unsigned length = title.length(); |
| unsigned builder_index = 0; |
| const CharacterType* characters = title.GetCharacters<CharacterType>(); |
| |
| StringBuffer<CharacterType> buffer(length); |
| |
| // Replace control characters with spaces and collapse whitespace. |
| bool pending_whitespace = false; |
| for (unsigned i = 0; i < length; ++i) { |
| UChar32 c = characters[i]; |
| if ((c <= WTF::unicode::kSpaceCharacter && |
| c != WTF::unicode::kLineTabulationCharacter) || |
| c == WTF::unicode::kDeleteCharacter) { |
| if (builder_index != 0) |
| pending_whitespace = true; |
| } else { |
| if (pending_whitespace) { |
| buffer[builder_index++] = ' '; |
| pending_whitespace = false; |
| } |
| buffer[builder_index++] = c; |
| } |
| } |
| buffer.Shrink(builder_index); |
| |
| return String::Adopt(buffer); |
| } |
| |
| void Document::UpdateTitle(const String& title) { |
| if (raw_title_ == title) |
| return; |
| |
| raw_title_ = title; |
| |
| String old_title = title_; |
| if (raw_title_.empty()) |
| title_ = String(); |
| else if (raw_title_.Is8Bit()) |
| title_ = CanonicalizedTitle<LChar>(this, raw_title_); |
| else |
| title_ = CanonicalizedTitle<UChar>(this, raw_title_); |
| |
| if (!dom_window_ || old_title == title_) |
| return; |
| DispatchDidReceiveTitle(); |
| |
| if (AXObjectCache* cache = ExistingAXObjectCache()) |
| cache->DocumentTitleChanged(); |
| } |
| |
| void Document::DispatchDidReceiveTitle() { |
| if (IsInMainFrame()) { |
| String shortened_title = title_.Substring(0, mojom::blink::kMaxTitleChars); |
| GetFrame()->GetLocalFrameHostRemote().UpdateTitle( |
| shortened_title, base::i18n::TextDirection::LEFT_TO_RIGHT); |
| GetFrame()->GetPage()->GetPageScheduler()->OnTitleOrFaviconUpdated(); |
| } |
| GetFrame()->Client()->DispatchDidReceiveTitle(title_); |
| } |
| |
| void Document::setTitle(const String& title) { |
| // Title set by JavaScript -- overrides any title elements. |
| Element* element = documentElement(); |
| if (IsA<SVGSVGElement>(element)) { |
| if (!title_element_) { |
| title_element_ = MakeGarbageCollected<SVGTitleElement>(*this); |
| element->InsertBefore(title_element_.Get(), element->firstChild()); |
| } |
| if (auto* svg_title = DynamicTo<SVGTitleElement>(title_element_.Get())) |
| svg_title->SetText(title); |
| } else if (element && element->IsHTMLElement()) { |
| if (!title_element_) { |
| HTMLElement* head_element = head(); |
| if (!head_element) |
| return; |
| title_element_ = MakeGarbageCollected<HTMLTitleElement>(*this); |
| head_element->AppendChild(title_element_.Get()); |
| } |
| if (auto* html_title = DynamicTo<HTMLTitleElement>(title_element_.Get())) |
| html_title->setText(title); |
| } |
| } |
| |
| void Document::SetTitleElement(Element* title_element) { |
| // If the root element is an svg element in the SVG namespace, then let value |
| // be the child text content of the first title element in the SVG namespace |
| // that is a child of the root element. |
| if (IsA<SVGSVGElement>(documentElement())) { |
| title_element_ = Traversal<SVGTitleElement>::FirstChild(*documentElement()); |
| } else { |
| if (title_element_ && title_element_ != title_element) |
| title_element_ = Traversal<HTMLTitleElement>::FirstWithin(*this); |
| else |
| title_element_ = title_element; |
| |
| // If the root element isn't an svg element in the SVG namespace and the |
| // title element is in the SVG namespace, it is ignored. |
| if (IsA<SVGTitleElement>(*title_element_)) { |
| title_element_ = nullptr; |
| return; |
| } |
| } |
| |
| if (auto* html_title = DynamicTo<HTMLTitleElement>(title_element_.Get())) |
| UpdateTitle(html_title->text()); |
| else if (auto* svg_title = DynamicTo<SVGTitleElement>(title_element_.Get())) |
| UpdateTitle(svg_title->textContent()); |
| } |
| |
| void Document::RemoveTitle(Element* title_element) { |
| if (title_element_ != title_element) |
| return; |
| |
| title_element_ = nullptr; |
| |
| // Update title based on first title element in the document, if one exists. |
| if (IsA<HTMLDocument>(this) || IsXHTMLDocument()) { |
| if (HTMLTitleElement* title = |
| Traversal<HTMLTitleElement>::FirstWithin(*this)) |
| SetTitleElement(title); |
| } else if (IsSVGDocument()) { |
| if (SVGTitleElement* title = Traversal<SVGTitleElement>::FirstWithin(*this)) |
| SetTitleElement(title); |
| } |
| |
| if (!title_element_) |
| UpdateTitle(String()); |
| } |
| |
| const AtomicString& Document::dir() { |
| Element* root_element = documentElement(); |
| if (auto* html = DynamicTo<HTMLHtmlElement>(root_element)) |
| return html->dir(); |
| return g_null_atom; |
| } |
| |
| void Document::setDir(const AtomicString& value) { |
| Element* root_element = documentElement(); |
| if (auto* html = DynamicTo<HTMLHtmlElement>(root_element)) |
| html->setDir(value); |
| } |
| |
| bool Document::IsPageVisible() const { |
| // The visibility of the document is inherited from the visibility of the |
| // page. If there is no page associated with the document, we will assume |
| // that the page is hidden, as specified by the spec: |
| // https://w3c.github.io/page-visibility/#hidden-attribute |
| if (!GetFrame() || !GetFrame()->GetPage()) |
| return false; |
| // While visibilitychange is being dispatched during unloading it is |
| // expected that the visibility is hidden regardless of the page's |
| // visibility. |
| if (load_event_progress_ >= kUnloadVisibilityChangeInProgress) |
| return false; |
| return GetFrame()->GetPage()->IsPageVisible(); |
| } |
| |
| bool Document::IsPrefetchOnly() const { |
| if (!GetFrame() || !GetFrame()->GetPage()) |
| return false; |
| |
| NoStatePrefetchClient* no_state_prefetch_client = |
| NoStatePrefetchClient::From(GetFrame()->GetPage()); |
| return no_state_prefetch_client && no_state_prefetch_client->IsPrefetchOnly(); |
| } |
| |
| AtomicString Document::visibilityState() const { |
| return PageHiddenStateString(hidden()); |
| } |
| |
| bool Document::prerendering() const { |
| return IsPrerendering(); |
| } |
| uint32_t Document::softNavigations() const { |
| LocalDOMWindow* window = domWindow(); |
| if (!window) { |
| return 0; |
| } |
| if (SoftNavigationHeuristics* heuristics = |
| SoftNavigationHeuristics::From(*window)) { |
| return heuristics->SoftNavigationCount(); |
| } |
| return 0; |
| } |
| |
| bool Document::hidden() const { |
| return !IsPageVisible(); |
| } |
| |
| bool Document::wasDiscarded() const { |
| return was_discarded_; |
| } |
| |
| void Document::SetWasDiscarded(bool was_discarded) { |
| was_discarded_ = was_discarded; |
| } |
| |
| void Document::DidChangeVisibilityState() { |
| if (load_event_progress_ >= kUnloadVisibilityChangeInProgress) { |
| // It's possible to get here even after we've started unloading the document |
| // and dispatched the visibilitychange event, e.g. when we're closing a tab, |
| // where we would first try to dispatch unload events, and then close the |
| // tab and update the visibility state. |
| return; |
| } |
| DispatchEvent(*Event::CreateBubble(event_type_names::kVisibilitychange)); |
| // Also send out the deprecated version until it can be removed. |
| DispatchEvent( |
| *Event::CreateBubble(event_type_names::kWebkitvisibilitychange)); |
| |
| if (IsPageVisible()) |
| GetDocumentAnimations().MarkAnimationsCompositorPending(); |
| |
| if (hidden() && canvas_font_cache_) |
| canvas_font_cache_->PruneAll(); |
| |
| InteractiveDetector* interactive_detector = InteractiveDetector::From(*this); |
| if (interactive_detector) { |
| interactive_detector->OnPageHiddenChanged(hidden()); |
| } |
| |
| // Don't create a |ukm_recorder_| and |ukm_source_id_| unless necessary. |
| if (hidden() && IdentifiabilityStudySettings::Get()->IsActive()) { |
| // Flush UKM data here in addition to Document::Shutdown(). We want to flush |
| // the UKM data before this document becomes invisible (e.g. before entering |
| // back/forward cache) because we want to send the UKM data before the |
| // renderer process is killed. |
| IdentifiabilitySampleCollector::Get()->FlushSource(UkmRecorder(), |
| UkmSourceID()); |
| } |
| |
| ViewTransitionSupplement::From(*this)->DidChangeVisibilityState(); |
| } |
| |
| String Document::nodeName() const { |
| return "#document"; |
| } |
| |
| FormController& Document::GetFormController() { |
| if (!form_controller_) { |
| form_controller_ = MakeGarbageCollected<FormController>(*this); |
| HistoryItem* history_item = Loader() ? Loader()->GetHistoryItem() : nullptr; |
| if (history_item) |
| history_item->SetDocumentState(form_controller_->ControlStates()); |
| } |
| return *form_controller_; |
| } |
| |
| DocumentState* Document::GetDocumentState() const { |
| if (!form_controller_) |
| return nullptr; |
| return form_controller_->ControlStates(); |
| } |
| |
| void Document::SetStateForNewControls(const Vector<String>& state_vector) { |
| if (!state_vector.size() && !form_controller_) |
| return; |
| GetFormController().SetStateForNewControls(state_vector); |
| } |
| |
| LocalFrameView* Document::View() const { |
| return GetFrame() ? GetFrame()->View() : nullptr; |
| } |
| |
| LocalFrame* Document::GetFrame() const { |
| return dom_window_ ? dom_window_->GetFrame() : nullptr; |
| } |
| |
| Page* Document::GetPage() const { |
| return GetFrame() ? GetFrame()->GetPage() : nullptr; |
| } |
| |
| Settings* Document::GetSettings() const { |
| return GetFrame() ? GetFrame()->GetSettings() : nullptr; |
| } |
| |
| Range* Document::createRange() { |
| return Range::Create(*this); |
| } |
| |
| NodeIterator* Document::createNodeIterator(Node* root, |
| unsigned what_to_show, |
| V8NodeFilter* filter) { |
| DCHECK(root); |
| return MakeGarbageCollected<NodeIterator>(root, what_to_show, filter); |
| } |
| |
| TreeWalker* Document::createTreeWalker(Node* root, |
| unsigned what_to_show, |
| V8NodeFilter* filter) { |
| DCHECK(root); |
| return MakeGarbageCollected<TreeWalker>(root, what_to_show, filter); |
| } |
| |
| Document::StyleAndLayoutTreeUpdate Document::CalculateStyleAndLayoutTreeUpdate() |
| const { |
| Document::StyleAndLayoutTreeUpdate local = |
| CalculateStyleAndLayoutTreeUpdateForThisDocument(); |
| if (local == StyleAndLayoutTreeUpdate::kFull) |
| return local; |
| Document::StyleAndLayoutTreeUpdate parent = |
| CalculateStyleAndLayoutTreeUpdateForParentFrame(); |
| if (parent != StyleAndLayoutTreeUpdate::kNone) |
| return StyleAndLayoutTreeUpdate::kFull; |
| return local; |
| } |
| |
| Document::StyleAndLayoutTreeUpdate |
| Document::CalculateStyleAndLayoutTreeUpdateForThisDocument() const { |
| if (!IsActive() || !View()) |
| return StyleAndLayoutTreeUpdate::kNone; |
| |
| if (style_engine_->NeedsFullStyleUpdate()) |
| return StyleAndLayoutTreeUpdate::kFull; |
| if (!use_elements_needing_update_.empty()) |
| return StyleAndLayoutTreeUpdate::kFull; |
| // We have scheduled an invalidation set on the document node which means any |
| // element may need a style recalc. |
| if (NeedsStyleInvalidation()) |
| return StyleAndLayoutTreeUpdate::kFull; |
| if (IsSlotAssignmentDirty()) |
| return StyleAndLayoutTreeUpdate::kFull; |
| if (document_animations_->NeedsAnimationTimingUpdate()) |
| return StyleAndLayoutTreeUpdate::kFull; |
| |
| if (style_engine_->NeedsStyleRecalc()) |
| return StyleAndLayoutTreeUpdate::kAnalyzed; |
| if (style_engine_->NeedsStyleInvalidation()) |
| return StyleAndLayoutTreeUpdate::kAnalyzed; |
| if (style_engine_->NeedsLayoutTreeRebuild()) { |
| // TODO(futhark): there a couple of places where call back into the top |
| // frame while recursively doing a lifecycle update. One of them are for the |
| // RootScrollerController. These should probably be post layout tasks and |
| // make this test unnecessary since the layout tree rebuild dirtiness is |
| // internal to StyleEngine::UpdateStyleAndLayoutTree(). |
| DCHECK(InStyleRecalc()); |
| return StyleAndLayoutTreeUpdate::kAnalyzed; |
| } |
| |
| return StyleAndLayoutTreeUpdate::kNone; |
| } |
| |
| Document::StyleAndLayoutTreeUpdate |
| Document::CalculateStyleAndLayoutTreeUpdateForParentFrame() const { |
| if (HTMLFrameOwnerElement* owner = LocalOwner()) |
| return owner->GetDocument().CalculateStyleAndLayoutTreeUpdate(); |
| return StyleAndLayoutTreeUpdate::kNone; |
| } |
| |
| bool Document::ShouldScheduleLayoutTreeUpdate() const { |
| if (!IsActive()) |
| return false; |
| if (InStyleRecalc()) |
| return false; |
| if (lifecycle_.GetState() == DocumentLifecycle::kInPerformLayout) |
| return false; |
| return true; |
| } |
| |
| void Document::ScheduleLayoutTreeUpdate() { |
| DCHECK(!HasPendingVisualUpdate()); |
| DCHECK(ShouldScheduleLayoutTreeUpdate()); |
| DCHECK(NeedsLayoutTreeUpdate()); |
| |
| if (!View()->CanThrottleRendering() && ShouldScheduleLayout()) { |
| GetPage()->Animator().ScheduleVisualUpdate(GetFrame()); |
| } |
| |
| // FrameSelection caches visual selection information, which must be |
| // invalidated on dirty layout tree. |
| GetFrame()->Selection().MarkCacheDirty(); |
| |
| lifecycle_.EnsureStateAtMost(DocumentLifecycle::kVisualUpdatePending); |
| |
| DEVTOOLS_TIMELINE_TRACE_EVENT_INSTANT_WITH_CATEGORIES( |
| TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), |
| "ScheduleStyleRecalculation", inspector_recalculate_styles_event::Data, |
| GetFrame()); |
| ++style_version_; |
| } |
| |
| bool Document::HasPendingForcedStyleRecalc() const { |
| return HasPendingVisualUpdate() && !InStyleRecalc() && |
| GetStyleChangeType() == kSubtreeStyleChange; |
| } |
| |
| void Document::UpdateStyleInvalidationIfNeeded() { |
| DCHECK(IsActive()); |
| ScriptForbiddenScope forbid_script; |
| StyleEngine& style_engine = GetStyleEngine(); |
| if (!style_engine.NeedsStyleInvalidation()) { |
| return; |
| } |
| TRACE_EVENT_WITH_FLOW0("blink", "Document::updateStyleInvalidationIfNeeded", |
| TRACE_ID_LOCAL(this), |
| TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); |
| SCOPED_BLINK_UMA_HISTOGRAM_TIMER_HIGHRES("Style.InvalidationTime"); |
| style_engine.InvalidateStyle(); |
| } |
| |
| #if DCHECK_IS_ON() |
| static void AssertNodeClean(const Node& node) { |
| DCHECK(!node.NeedsStyleRecalc()); |
| DCHECK(!node.ChildNeedsStyleRecalc()); |
| DCHECK(!node.NeedsReattachLayoutTree()); |
| DCHECK(!node.ChildNeedsReattachLayoutTree()); |
| DCHECK(!node.NeedsStyleInvalidation()); |
| DCHECK(!node.ChildNeedsStyleInvalidation()); |
| DCHECK(!node.GetForceReattachLayoutTree()); |
| DCHECK(!node.NeedsLayoutSubtreeUpdate()); |
| } |
| |
| static void AssertLayoutTreeUpdatedForPseudoElements(const Element& element) { |
| WTF::Vector<PseudoId> pseudo_ids = {kPseudoIdFirstLetter, kPseudoIdBefore, |
| kPseudoIdAfter, kPseudoIdMarker, |
| kPseudoIdBackdrop}; |
| for (auto pseudo_id : pseudo_ids) { |
| if (auto* pseudo_element = element.GetPseudoElement(pseudo_id)) |
| AssertNodeClean(*pseudo_element); |
| } |
| } |
| |
| static void AssertLayoutTreeUpdated(Node& root, |
| bool allow_dirty_container_subtrees) { |
| Node* node = &root; |
| while (node) { |
| if (auto* element = DynamicTo<Element>(node)) { |
| if (element->ChildStyleRecalcBlockedByDisplayLock() || |
| (allow_dirty_container_subtrees && element->GetLayoutObject() && |
| element->GetLayoutObject()->StyleRef().CanMatchSizeContainerQueries( |
| *element))) { |
| node = FlatTreeTraversal::NextSkippingChildren(*node); |
| continue; |
| } |
| // Check pseudo elements. |
| AssertLayoutTreeUpdatedForPseudoElements(*element); |
| } |
| |
| AssertNodeClean(*node); |
| |
| // Make sure there is no node which has a LayoutObject, but doesn't have a |
| // parent in a flat tree. If there is such a node, we forgot to detach the |
| // node. DocumentNode is only an exception. |
| DCHECK((node->IsDocumentNode() || !node->GetLayoutObject() || |
| FlatTreeTraversal::Parent(*node))) |
| << *node; |
| |
| node = FlatTreeTraversal::Next(*node); |
| } |
| } |
| |
| #endif |
| |
| #if EXPENSIVE_DCHECKS_ARE_ON() |
| void Document::AssertLayoutTreeUpdatedAfterLayout() { |
| AssertLayoutTreeUpdated(*this, false /* allow_dirty_container_subtrees */); |
| DCHECK(!GetStyleEngine().SkippedContainerRecalc()); |
| } |
| #endif |
| |
| void Document::UpdateStyleAndLayoutTree() { |
| DocumentLayoutUpgrade upgrade(*this); |
| UpdateStyleAndLayoutTree(upgrade); |
| } |
| |
| void Document::UpdateStyleAndLayoutTree(LayoutUpgrade& upgrade) { |
| DCHECK(IsMainThread()); |
| DCHECK(ThreadState::Current()->IsAllocationAllowed()); |
| if (!IsActive() || !View() || View()->ShouldThrottleRendering() || |
| Lifecycle().LifecyclePostponed()) { |
| return; |
| } |
| |
| HTMLFrameOwnerElement::PluginDisposeSuspendScope suspend_plugin_dispose; |
| ScriptForbiddenScope forbid_script; |
| |
| if (HTMLFrameOwnerElement* owner = LocalOwner()) { |
| ParentLayoutUpgrade parent_upgrade(*this, *owner); |
| owner->GetDocument().UpdateStyleAndLayoutTree(parent_upgrade); |
| } |
| |
| PostStyleUpdateScope post_style_update_scope(*this); |
| |
| do { |
| // This call has to happen even if UpdateStyleAndLayout below will be |
| // called. This is because the subsequent call to ShouldUpgrade may depend |
| // on the results produced by UpdateStyleAndLayoutTreeForThisDocument. |
| UpdateStyleAndLayoutTreeForThisDocument(); |
| |
| if (upgrade.ShouldUpgrade()) { |
| GetDisplayLockDocumentState().EnsureMinimumForcedPhase( |
| DisplayLockContext::ForcedPhase::kLayout); |
| |
| // TODO(crbug.com/1145970): Provide a better reason. |
| UpdateStyleAndLayout(DocumentUpdateReason::kUnknown); |
| } |
| |
| } while (post_style_update_scope.Apply()); |
| |
| // If the above call to UpdateStyleAndLayoutTreeForThisDocument caused us to |
| // skip style recalc for some node, we should have upgraded [1] and performed |
| // layout to clear that flag again. |
| // |
| // [1] LayoutUpgrade::ShouldUpgrade |
| DCHECK(!GetStyleEngine().SkippedContainerRecalc()); |
| } |
| |
| void Document::UpdateStyleAndLayoutTreeForThisDocument() { |
| DCHECK(IsMainThread()); |
| DCHECK(ThreadState::Current()->IsAllocationAllowed()); |
| if (!IsActive() || !View() || View()->ShouldThrottleRendering() || |
| Lifecycle().LifecyclePostponed()) { |
| return; |
| } |
| |
| #if EXPENSIVE_DCHECKS_ARE_ON() |
| if (HTMLFrameOwnerElement* owner = LocalOwner()) { |
| DCHECK(!owner->GetDocument() |
| .GetSlotAssignmentEngine() |
| .HasPendingSlotAssignmentRecalc()); |
| DCHECK(!owner->GetDocument().NeedsLayoutTreeUpdate()); |
| AssertLayoutTreeUpdated(owner->GetDocument(), |
| false /* allow_dirty_container_subtrees */); |
| } |
| #endif // EXPENSIVE_DCHECKS_ARE_ON() |
| |
| ProcessScheduledShadowTreeCreationsNow(); |
| |
| auto advance_to_style_clean = [this]() { |
| DocumentLifecycle& lifecycle = Lifecycle(); |
| if (lifecycle.GetState() < DocumentLifecycle::kStyleClean) { |
| // NeedsLayoutTreeUpdateForThisDocument may change to false without any |
| // actual layout tree update. For example, NeedsAnimationTimingUpdate |
| // may change to false when time elapses. Advance lifecycle to |
| // StyleClean because style is actually clean now. |
| lifecycle.AdvanceTo(DocumentLifecycle::kInStyleRecalc); |
| lifecycle.AdvanceTo(DocumentLifecycle::kStyleClean); |
| } |
| // If we insert <object> elements into display:none subtrees, we might not |
| // need a layout tree update, but need to make sure they are not blocking |
| // the load event. |
| UnblockLoadEventAfterLayoutTreeUpdate(); |
| }; |
| |
| bool needs_slot_assignment = IsSlotAssignmentDirty(); |
| bool needs_layout_tree_update = false; |
| |
| if (!needs_slot_assignment) { |
| needs_layout_tree_update = NeedsLayoutTreeUpdateForThisDocument(); |
| if (!needs_layout_tree_update) { |
| // Early out for no-op calls before the UMA/UKM measurement is set up to |
| // avoid a large number of close-to-zero samples. |
| if (AXObjectCache* cache = ExistingAXObjectCache()) { |
| cache->ProcessSubtreeRemovals(); |
| } |
| advance_to_style_clean(); |
| return; |
| } |
| } |
| |
| SCOPED_UMA_AND_UKM_TIMER(View()->GetUkmAggregator(), |
| LocalFrameUkmAggregator::kStyle); |
| FontPerformance::StyleScope font_performance_scope; |
| ENTER_EMBEDDER_STATE(GetAgent().isolate(), GetFrame(), BlinkState::STYLE); |
| |
| if (needs_slot_assignment) { |
| // RecalcSlotAssignments should be done before checking |
| // NeedsLayoutTreeUpdateForThisDocument(). |
| GetSlotAssignmentEngine().RecalcSlotAssignments(); |
| DCHECK(!needs_layout_tree_update) << "Should be postponed above"; |
| needs_layout_tree_update = NeedsLayoutTreeUpdateForThisDocument(); |
| } |
| |
| if (AXObjectCache* cache = ExistingAXObjectCache()) { |
| cache->ProcessSubtreeRemovals(); |
| } |
| |
| if (!needs_layout_tree_update) { |
| advance_to_style_clean(); |
| return; |
| } |
| |
| // We can call FlatTreeTraversal::AssertFlatTreeNodeDataUpdated just after |
| // calling RecalcSlotAssignments(), however, it would be better to call it at |
| // least after InStyleRecalc() check below in order to avoid superfluous |
| // check, which would be the cause of web tests timeout when dcheck is on. |
| |
| SlotAssignmentRecalcForbiddenScope forbid_slot_recalc(*this); |
| |
| if (InStyleRecalc()) { |
| NOTREACHED() << "We should not re-enter style recalc for the same document"; |
| return; |
| } |
| |
| #if DCHECK_IS_ON() |
| int assigned_nodes_in_slot_count = 0; |
| int nodes_which_have_assigned_slot_count = 0; |
| FlatTreeTraversal::AssertFlatTreeNodeDataUpdated( |
| *this, assigned_nodes_in_slot_count, |
| nodes_which_have_assigned_slot_count); |
| DCHECK_EQ(assigned_nodes_in_slot_count, nodes_which_have_assigned_slot_count); |
| #endif |
| |
| // Entering here from inside layout, paint etc. would be catastrophic since |
| // recalcStyle can tear down the layout tree or (unfortunately) run |
| // script. Kill the whole layoutObject if someone managed to get into here in |
| // states not allowing tree mutations. |
| CHECK(Lifecycle().StateAllowsTreeMutations()); |
| |
| // No SVG resources should be scheduled for invalidation outside of |
| // style-recalc and layout tree detach (Node::DetachLayoutTree). |
| DCHECK(svg_resources_needing_invalidation_.empty()); |
| |
| TRACE_EVENT_BEGIN1("blink,devtools.timeline", "UpdateLayoutTree", "beginData", |
| [&](perfetto::TracedValue context) { |
| inspector_recalculate_styles_event::Data( |
| std::move(context), GetFrame()); |
| }); |
| |
| StyleEngine& style_engine = GetStyleEngine(); |
| unsigned start_element_count = style_engine.StyleForElementCount(); |
| |
| probe::RecalculateStyle recalculate_style_scope(this); |
| |
| document_animations_->UpdateAnimationTimingIfNeeded(); |
| EvaluateMediaQueryListIfNeeded(); |
| UpdateUseShadowTreesIfNeeded(); |
| |
| style_engine.UpdateActiveStyle(); |
| style_engine.UpdateCounterStyles(); |
| style_engine.InvalidatePositionTryStyles(); |
| style_engine.InvalidateViewportUnitStylesIfNeeded(); |
| InvalidateStyleAndLayoutForFontUpdates(); |
| UpdateStyleInvalidationIfNeeded(); |
| UpdateStyle(); |
| GetStyleResolver().ClearResizedForViewportUnits(); |
| InvalidatePendingSVGResources(); |
| |
| rendering_had_begun_for_last_style_update_ = RenderingHasBegun(); |
| |
| GetLayoutView()->ClearHitTestCache(); |
| |
| DCHECK(!document_animations_->NeedsAnimationTimingUpdate()); |
| |
| unsigned element_count = |
| GetStyleEngine().StyleForElementCount() - start_element_count; |
| |
| // Make sure that document.fonts.ready fires, if appropriate. |
| FontFaceSetDocument::DidLayout(*this); |
| |
| UnblockLoadEventAfterLayoutTreeUpdate(); |
| |
| if (auto* document_rules = DocumentSpeculationRules::FromIfExists(*this)) { |
| document_rules->DocumentStyleUpdated(); |
| } |
| |
| TRACE_EVENT_END1("blink,devtools.timeline", "UpdateLayoutTree", |
| "elementCount", element_count); |
| |
| ElementRuleCollector::DumpAndClearRulesPerfMap(); |
| |
| #if DCHECK_IS_ON() |
| AssertLayoutTreeUpdated(*this, true /* allow_dirty_container_subtrees */); |
| #endif |
| } |
| |
| void Document::InvalidateStyleAndLayoutForFontUpdates() { |
| DCHECK(IsActive()); |
| DCHECK(IsMainThread()); |
| GetStyleEngine().InvalidateStyleAndLayoutForFontUpdates(); |
| } |
| |
| void Document::UpdateStyle() { |
| DCHECK(!View()->ShouldThrottleRendering()); |
| TRACE_EVENT_BEGIN0("blink,blink_style", "Document::updateStyle"); |
| RUNTIME_CALL_TIMER_SCOPE(GetAgent().isolate(), |
| RuntimeCallStats::CounterId::kUpdateStyle); |
| |
| StyleEngine& style_engine = GetStyleEngine(); |
| unsigned initial_element_count = style_engine.StyleForElementCount(); |
| |
| lifecycle_.AdvanceTo(DocumentLifecycle::kInStyleRecalc); |
| |
| // SetNeedsStyleRecalc should only happen on Element and Text nodes. |
| DCHECK(!NeedsStyleRecalc()); |
| |
| bool should_record_stats; |
| TRACE_EVENT_CATEGORY_GROUP_ENABLED("blink,blink_style", &should_record_stats); |
| |
| style_engine.SetStatsEnabled(should_record_stats); |
| style_engine.UpdateStyleAndLayoutTree(); |
| |
| LayoutView* layout_view = GetLayoutView(); |
| layout_view->UpdateMarkersAndCountersAfterStyleChange(); |
| layout_view->RecalcScrollableOverflow(); |
| |
| #if DCHECK_IS_ON() |
| AssertNodeClean(*this); |
| #endif |
| DCHECK(InStyleRecalc()); |
| lifecycle_.AdvanceTo(DocumentLifecycle::kStyleClean); |
| if (should_record_stats) { |
| TRACE_EVENT_END2( |
| "blink,blink_style", "Document::updateStyle", "resolverAccessCount", |
| style_engine.StyleForElementCount() - initial_element_count, "counters", |
| GetStyleEngine().Stats()->ToTracedValue()); |
| } else { |
| TRACE_EVENT_END1( |
| "blink,blink_style", "Document::updateStyle", "resolverAccessCount", |
| style_engine.StyleForElementCount() - initial_element_count); |
| } |
| } |
| |
| bool Document::NeedsLayoutTreeUpdateForNode(const Node& node) const { |
| // TODO(rakina): Switch some callers that may need to call |
| // NeedsLayoutTreeUpdateForNodeIncludingDisplayLocked instead of this. |
| if (DisplayLockUtilities::LockedAncestorPreventingStyle(node)) { |
| // |node| is in a locked-subtree, so we don't need to update it. |
| return false; |
| } |
| return NeedsLayoutTreeUpdateForNodeIncludingDisplayLocked(node); |
| } |
| |
| bool Document::NeedsLayoutTreeUpdateForNodeIncludingDisplayLocked( |
| const Node& node) const { |
| if (!node.isConnected()) |
| return false; |
| if (node.IsShadowRoot()) |
| return false; |
| const StyleAndLayoutTreeUpdate update = CalculateStyleAndLayoutTreeUpdate(); |
| if (update == StyleAndLayoutTreeUpdate::kFull) |
| return true; |
| bool analyze = update == StyleAndLayoutTreeUpdate::kAnalyzed; |
| |
| // If DisplayLockUtilities::IsUnlockedQuickCheck returns 'false', then |
| // we may or may not be unlocked: we have to traverse the ancestor chain |
| // to know for sure. |
| if (!analyze) |
| analyze = !DisplayLockUtilities::IsUnlockedQuickCheck(node); |
| |
| StyleEngine& style_engine = GetStyleEngine(); |
| bool maybe_affected_by_layout = style_engine.StyleMaybeAffectedByLayout(node); |
| // Even if we don't need layout *now*, any dirty style may invalidate layout. |
| bool maybe_needs_layout = |
| (update != StyleAndLayoutTreeUpdate::kNone) || View()->NeedsLayout(); |
| bool needs_update_inside_interleaving_root = |
| maybe_affected_by_layout && maybe_needs_layout; |
| |
| if (!analyze) |
| analyze = needs_update_inside_interleaving_root; |
| |
| if (!analyze) { |
| DCHECK_EQ(StyleAndLayoutTreeUpdate::kNone, update); |
| return false; |
| } |
| |
| switch (style_engine.AnalyzeAncestors(node)) { |
| case StyleEngine::AncestorAnalysis::kNone: |
| return false; |
| case StyleEngine::AncestorAnalysis::kInterleavingRoot: |
| return needs_update_inside_interleaving_root; |
| case StyleEngine::AncestorAnalysis::kStyleRoot: |
| return true; |
| } |
| } |
| |
| void Document::UpdateStyleAndLayoutTreeForElement(const Element* element, |
| DocumentUpdateReason) { |
| DCHECK(element); |
| if (!element->InActiveDocument()) { |
| // If |node| is not in the active document, we can't update its style or |
| // layout tree. |
| DCHECK_EQ(element->ownerDocument(), this); |
| return; |
| } |
| DCHECK(!InStyleRecalc()) |
| << "UpdateStyleAndLayoutTreeForElement called from within style recalc"; |
| if (!NeedsLayoutTreeUpdateForNodeIncludingDisplayLocked(*element)) { |
| return; |
| } |
| |
| DisplayLockUtilities::ScopedForcedUpdate scoped_update_forced( |
| element, DisplayLockContext::ForcedPhase::kStyleAndLayoutTree); |
| ElementLayoutUpgrade upgrade(*element); |
| UpdateStyleAndLayoutTree(upgrade); |
| } |
| |
| void Document::UpdateStyleAndLayoutTreeForSubtree(const Element* element, |
| DocumentUpdateReason) { |
| DCHECK(element); |
| if (!element->InActiveDocument()) { |
| DCHECK_EQ(element->ownerDocument(), this); |
| return; |
| } |
| DCHECK(!InStyleRecalc()) |
| << "UpdateStyleAndLayoutTreeForSubtree called from within style recalc"; |
| |
| if (NeedsLayoutTreeUpdateForNodeIncludingDisplayLocked(*element) || |
| element->ChildNeedsStyleRecalc() || |
| element->ChildNeedsStyleInvalidation()) { |
| DisplayLockUtilities::ScopedForcedUpdate scoped_update_forced( |
| element, DisplayLockContext::ForcedPhase::kStyleAndLayoutTree); |
| UpdateStyleAndLayoutTree(); |
| } |
| } |
| |
| void Document::UpdateStyleAndLayoutForRange(const Range* range, |
| DocumentUpdateReason reason) { |
| DisplayLockUtilities::ScopedForcedUpdate scoped_update_forced( |
| range, DisplayLockContext::ForcedPhase::kLayout); |
| UpdateStyleAndLayout(reason); |
| } |
| |
| void Document::UpdateStyleAndLayoutForNode(const Node* node, |
| DocumentUpdateReason reason) { |
| DCHECK(node); |
| if (!node->InActiveDocument()) |
| return; |
| |
| DisplayLockUtilities::ScopedForcedUpdate scoped_update_forced( |
| node, DisplayLockContext::ForcedPhase::kLayout); |
| |
| // For all nodes we must have up-to-date style and have performed layout to do |
| // any location-based calculation. |
| UpdateStyleAndLayout(reason); |
| } |
| |
| DocumentPartRoot& Document::getPartRoot() { |
| return EnsureDocumentPartRoot(); |
| } |
| |
| DocumentPartRoot& Document::EnsureDocumentPartRoot() { |
| CHECK(RuntimeEnabledFeatures::DOMPartsAPIEnabled()); |
| if (!document_part_root_) { |
| document_part_root_ = MakeGarbageCollected<DocumentPartRoot>(*this); |
| } |
| return *document_part_root_; |
| } |
| |
| void Document::ApplyScrollRestorationLogic() { |
| DCHECK(View()); |
| // This function is not re-entrant. However, the places that invoke this are |
| // re-entrant. Specifically, UpdateStyleAndLayout() calls this, which in turn |
| // can do a find-in-page for the scroll-to-text feature, which can cause |
| // UpdateStyleAndLayout to happen with content-visibility, which gets back |
| // here and recurses indefinitely. As a result, we ensure to early out from |
| // this function if are currently in process of restoring scroll. |
| if (applying_scroll_restoration_logic_) |
| return; |
| base::AutoReset<bool> applying_scroll_restoration_logic_scope( |
| &applying_scroll_restoration_logic_, true); |
| |
| if (AnnotationAgentContainerImpl* container = |
| AnnotationAgentContainerImpl::FromIfExists(*this)) { |
| // Check for cleanliness since that'll also account for parsing state. |
| if (container->IsLifecycleCleanForAttachment()) { |
| container->PerformInitialAttachments(); |
| } |
| } |
| |
| // If we're restoring a scroll position from history, that takes precedence |
| // over scrolling to the anchor in the URL. |
| View()->InvokeFragmentAnchor(); |
| LocalFrame* frame = GetFrame(); |
| auto& frame_loader = frame->Loader(); |
| auto* document_loader = frame_loader.GetDocumentLoader(); |
| if (!document_loader) |
| return; |
| if (frame->IsLoading() && |
| !FrameLoader::NeedsHistoryItemRestore(document_loader->LoadType())) { |
| return; |
| } |
| |
| HistoryItem* history_item = document_loader->GetHistoryItem(); |
| |
| if (!history_item || !history_item->GetViewState()) |
| return; |
| |
| if (!View()->GetScrollableArea()->HasPendingHistoryRestoreScrollOffset()) |
| return; |
| |
| bool should_restore_scroll = history_item->ScrollRestorationType() != |
| mojom::blink::ScrollRestorationType::kManual; |
| auto& scroll_offset = history_item->GetViewState()->scroll_offset_; |
| |
| // This tries to balance: |
| // 1. restoring as soon as possible. |
| // 2. not overriding user scroll (TODO(majidvp): also respect user scale). |
| // 3. detecting clamping to avoid repeatedly popping the scroll position |
| // down |
| // as the page height increases. |
| // 4. ignoring clamp detection if scroll state is not being restored, if |
| // load |
| // is complete, or if the navigation is same-document (as the new page |
| // may be smaller than the previous page). |
| bool can_restore_without_clamping = |
| View()->LayoutViewport()->ClampScrollOffset(scroll_offset) == |
| scroll_offset; |
| |
| bool can_restore_without_annoying_user = |
| !document_loader->GetInitialScrollState().was_scrolled_by_user && |
| (can_restore_without_clamping || !GetFrame()->IsLoading() || |
| !should_restore_scroll); |
| if (!can_restore_without_annoying_user) |
| return; |
| |
| // Apply scroll restoration to the LayoutView's scroller. Note that we do |
| // *not* apply it to the RootFrameViewport's LayoutViewport, because that |
| // may be for child frame's implicit root scroller, which is not the right |
| // one to apply to because scroll restoration does not affect implicit root |
| // scrollers. |
| auto* layout_scroller = View()->LayoutViewport(); |
| layout_scroller->ApplyPendingHistoryRestoreScrollOffset(); |
| |
| // Also apply restoration to the visual viewport of the root frame, if needed. |
| auto* root_frame_scroller = View()->GetScrollableArea(); |
| if (root_frame_scroller != layout_scroller) |
| root_frame_scroller->ApplyPendingHistoryRestoreScrollOffset(); |
| |
| document_loader->GetInitialScrollState().did_restore_from_history = true; |
| } |
| |
| void Document::MarkHasFindInPageRequest() { |
| // Only record the event once in a document. |
| if (had_find_in_page_request_) |
| return; |
| |
| auto* recorder = UkmRecorder(); |
| DCHECK(recorder); |
| DCHECK(UkmSourceID() != ukm::kInvalidSourceId); |
| ukm::builders::Blink_FindInPage(UkmSourceID()) |
| .SetDidSearch(true) |
| .Record(recorder); |
| had_find_in_page_request_ = true; |
| } |
| |
| void Document::MarkHasFindInPageContentVisibilityActiveMatch() { |
| // Only record the event once in a document. |
| if (had_find_in_page_render_subtree_active_match_) |
| return; |
| |
| auto* recorder = UkmRecorder(); |
| DCHECK(recorder); |
| DCHECK(UkmSourceID() != ukm::kInvalidSourceId); |
| // TODO(vmpstr): Rename UKM values if possible. |
| ukm::builders::Blink_FindInPage(UkmSourceID()) |
| .SetDidHaveRenderSubtreeMatch(true) |
| .Record(recorder); |
| had_find_in_page_render_subtree_active_match_ = true; |
| } |
| |
| void Document::MarkHasFindInPageBeforematchExpandedHiddenMatchable() { |
| // Only record the event once in a document. |
| if (had_find_in_page_beforematch_expanded_hidden_matchable_) |
| return; |
| |
| auto* recorder = UkmRecorder(); |
| DCHECK(recorder); |
| DCHECK(UkmSourceID() != ukm::kInvalidSourceId); |
| ukm::builders::Blink_FindInPage(UkmSourceID()) |
| .SetBeforematchExpandedHiddenMatchable(true) |
| .Record(recorder); |
| had_find_in_page_beforematch_expanded_hidden_matchable_ = true; |
| } |
| |
| void Document::UpdateStyleAndLayout(DocumentUpdateReason reason) { |
| DCHECK(IsMainThread()); |
| // TODO(paint-dev): LifecyclePostponed() and |
| // LocalFrameView::IsUpdatingLifecycle() overlap in functionality, but with |
| // slight differences. We should combine them. |
| if (Lifecycle().LifecyclePostponed()) { |
| return; |
| } |
| TRACE_EVENT("blink", "Document::UpdateStyleAndLayout"); |
| LocalFrameView* frame_view = View(); |
| |
| if (reason != DocumentUpdateReason::kBeginMainFrame && frame_view) |
| frame_view->WillStartForcedLayout(); |
| |
| HTMLFrameOwnerElement::PluginDisposeSuspendScope suspend_plugin_dispose; |
| ScriptForbiddenScope forbid_script; |
| |
| DCHECK(!frame_view || !frame_view->IsInPerformLayout()) |
| << "View layout should not be re-entrant"; |
| |
| if (HTMLFrameOwnerElement* owner = LocalOwner()) { |
| owner->GetDocument().UpdateStyleAndLayout(reason); |
| } |
| |
| if (!IsActive()) { |
| if (reason != DocumentUpdateReason::kBeginMainFrame && frame_view) |
| frame_view->DidFinishForcedLayout(reason); |
| return; |
| } |
| |
| if (frame_view) |
| frame_view->UpdateStyleAndLayout(); |
| |
| if (Lifecycle().GetState() < DocumentLifecycle::kLayoutClean) |
| Lifecycle().AdvanceTo(DocumentLifecycle::kLayoutClean); |
| |
| if (frame_view) |
| ApplyScrollRestorationLogic(); |
| |
| if (LocalFrameView* frame_view_anchored = View()) |
| frame_view_anchored->PerformScrollAnchoringAdjustments(); |
| |
| if (frame_view) { |
| frame_view->ExecutePendingSnapUpdates(); |
| } |
| |
| if (reason != DocumentUpdateReason::kBeginMainFrame && frame_view) |
| frame_view->DidFinishForcedLayout(reason); |
| |
| if (should_update_selection_after_layout_) |
| UpdateSelectionAfterLayout(); |
| } |
| |
| void Document::LayoutUpdated() { |
| DCHECK(GetFrame()); |
| DCHECK(View()); |
| |
| // Plugins can run script inside layout which can detach the page. |
| // TODO(dcheng): Does it make sense to do any of this work if detached? |
| if (auto* frame = GetFrame()) { |
| if (frame->IsMainFrame()) |
| frame->GetPage()->GetChromeClient().MainFrameLayoutUpdated(); |
| |
| // We do attach here, during lifecycle update, because until then we |
| // don't have a good place that has access to its local root's FrameWidget. |
| // TODO(dcheng): If we create FrameWidget before Frame then we could move |
| // this to Document::Initialize(). |
| AttachCompositorTimeline(Timeline().CompositorTimeline()); |
| } |
| |
| Markers().InvalidateRectsForAllTextMatchMarkers(); |
| } |
| |
| void Document::AttachCompositorTimeline(cc::AnimationTimeline* timeline) const { |
| if (!Platform::Current()->IsThreadedAnimationEnabled() || |
| !GetSettings()->GetAcceleratedCompositingEnabled()) |
| return; |
| |
| if (timeline->IsScrollTimeline() && timeline->animation_host()) |
| return; |
| |
| if (cc::AnimationHost* host = |
| GetPage()->GetChromeClient().GetCompositorAnimationHost( |
| *GetFrame())) { |
| host->AddAnimationTimeline(timeline); |
| } |
| } |
| |
| void Document::ClearFocusedElementIfNeeded() { |
| if (clear_focused_element_timer_.IsActive() || !focused_element_ || |
| focused_element_->IsFocusable( |
| Element::UpdateBehavior::kNoneForClearingFocus)) { |
| return; |
| } |
| clear_focused_element_timer_.StartOneShot(base::TimeDelta(), FROM_HERE); |
| } |
| |
| void Document::ClearFocusedElementTimerFired(TimerBase*) { |
| UpdateStyleAndLayoutTree(); |
| |
| if (focused_element_ && !focused_element_->IsFocusable()) |
| focused_element_->blur(); |
| } |
| |
| const ComputedStyle* Document::StyleForPage(uint32_t page_index) { |
| AtomicString page_name; |
| if (const LayoutView* layout_view = GetLayoutView()) |
| page_name = layout_view->NamedPageAtIndex(page_index); |
| return StyleForPage(page_index, page_name); |
| } |
| |
| const ComputedStyle* Document::StyleForPage(uint32_t page_index, |
| const AtomicString& page_name) { |
| return GetStyleEngine().GetStyleResolver().StyleForPage(page_index, |
| page_name); |
| } |
| |
| void Document::EnsurePaintLocationDataValidForNode( |
| const Node* node, |
| DocumentUpdateReason reason) { |
| UpdateStyleAndLayoutForNode(node, reason); |
| } |
| |
| WebPrintPageDescription Document::GetPageDescription(uint32_t page_index) { |
| View()->UpdateLifecycleToLayoutClean(DocumentUpdateReason::kUnknown); |
| return GetPageDescriptionNoLifecycleUpdate(*StyleForPage(page_index)); |
| } |
| |
| WebPrintPageDescription Document::GetPageDescriptionNoLifecycleUpdate( |
| const ComputedStyle& style) { |
| DocumentLifecycle::DisallowTransitionScope scope(Lifecycle()); |
| const WebPrintParams& print_params = GetFrame()->GetPrintParams(); |
| WebPrintPageDescription description = print_params.default_page_description; |
| |
| // TODO(mstensho): We may want to adjust page_size_type accordingly if we |
| // decide to disregard the specified @page size below. |
| description.page_size_type = style.GetPageSizeType(); |
| |
| switch (style.GetPageSizeType()) { |
| case PageSizeType::kAuto: |
| break; |
| case PageSizeType::kLandscape: |
| if (description.size.width() < description.size.height()) { |
| description.size.Transpose(); |
| } |
| break; |
| case PageSizeType::kPortrait: |
| if (description.size.width() > description.size.height()) { |
| description.size.Transpose(); |
| } |
| break; |
| case PageSizeType::kFixed: { |
| gfx::SizeF css_size = style.PageSize(); |
| if (!print_params.ignore_page_size) { |
| description.size = css_size; |
| break; |
| } |
| if ((css_size.width() > css_size.height()) != |
| (description.size.width() > description.size.height())) { |
| // Keep the page size, but match orientation. |
| description.size.Transpose(); |
| } |
| break; |
| } |
| default: |
| NOTREACHED(); |
| } |
| |
| if (!style.MarginTop().IsAuto()) { |
| description.margin_top = |
| FloatValueForLength(style.MarginTop(), description.size.height()); |
| } |
| if (!style.MarginRight().IsAuto()) { |
| description.margin_right = |
| FloatValueForLength(style.MarginRight(), description.size.width()); |
| } |
| if (!style.MarginBottom().IsAuto()) { |
| description.margin_bottom = |
| FloatValueForLength(style.MarginBottom(), description.size.height()); |
| } |
| if (!style.MarginLeft().IsAuto()) { |
| description.margin_left = |
| FloatValueForLength(style.MarginLeft(), description.size.width()); |
| } |
| |
| float page_area_width = description.size.width() - |
| (description.margin_left + description.margin_right); |
| float page_area_height = description.size.height() - |
| (description.margin_top + description.margin_bottom); |
| |
| if (page_area_width < 1 || page_area_height < 1) { |
| // The resulting page area size would become zero (or very close to |
| // it). Ignore CSS, and use the default values provided as input. There are |
| // tests that currently expect this behavior. But see |
| // https://github.com/w3c/csswg-drafts/issues/8335 |
| description = print_params.default_page_description; |
| } |
| |
| description.orientation = style.GetPageOrientation(); |
| |
| return description; |
| } |
| |
| void Document::SetIsXrOverlay(bool val, Element* overlay_element) { |
| if (!documentElement()) |
| return; |
| |
| if (val == is_xr_overlay_) |
| return; |
| |
| is_xr_overlay_ = val; |
| |
| // On navigation, the layout view may be invalid, skip style changes. |
| if (!GetLayoutView()) |
| return; |
| |
| if (overlay_element) { |
| // Now that the custom style sheet is loaded, update the pseudostyle for |
| // the overlay element. |
| overlay_element->PseudoStateChanged(CSSSelector::kPseudoXrOverlay); |
| } |
| |
| // The DOM overlay may change the effective root element. Need to update |
| // compositing inputs to avoid a mismatch in CompositingRequirementsUpdater. |
| GetLayoutView()->Layer()->SetNeedsCompositingInputsUpdate(); |
| } |
| |
| void Document::ScheduleUseShadowTreeUpdate(SVGUseElement& element) { |
| use_elements_needing_update_.insert(&element); |
| ScheduleLayoutTreeUpdateIfNeeded(); |
| } |
| |
| void Document::UnscheduleUseShadowTreeUpdate(SVGUseElement& element) { |
| use_elements_needing_update_.erase(&element); |
| } |
| |
| void Document::UpdateUseShadowTreesIfNeeded() { |
| ScriptForbiddenScope forbid_script; |
| |
| // Breadth-first search since nested use elements add to the queue. |
| while (!use_elements_needing_update_.empty()) { |
| HeapHashSet<Member<SVGUseElement>> elements; |
| use_elements_needing_update_.swap(elements); |
| for (SVGUseElement* element : elements) |
| element->BuildPendingResource(); |
| } |
| } |
| |
| void Document::ScheduleSVGResourceInvalidation(LocalSVGResource& resource) { |
| DCHECK(InStyleRecalc() || GetStyleEngine().InDetachLayoutTree()); |
| svg_resources_needing_invalidation_.insert(&resource); |
| } |
| |
| void Document::InvalidatePendingSVGResources() { |
| HeapHashSet<Member<LocalSVGResource>> pending_resources; |
| svg_resources_needing_invalidation_.swap(pending_resources); |
| for (LocalSVGResource* resource : pending_resources) { |
| resource->NotifyContentChanged(); |
| } |
| DCHECK(svg_resources_needing_invalidation_.empty()); |
| } |
| |
| StyleResolver& Document::GetStyleResolver() const { |
| return style_engine_->GetStyleResolver(); |
| } |
| |
| void Document::Initialize() { |
| TRACE_EVENT_WITH_FLOW0("blink", "Document::Initialize", TRACE_ID_LOCAL(this), |
| TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); |
| DCHECK_EQ(lifecycle_.GetState(), DocumentLifecycle::kInactive); |
| DCHECK(!ax_object_cache_ || this != &AXObjectCacheOwner()); |
| |
| UpdateForcedColors(); |
| const ComputedStyle* style = GetStyleResolver().StyleForViewport(); |
| layout_view_ = MakeGarbageCollected<LayoutView>(this); |
| SetLayoutObject(layout_view_); |
| |
| layout_view_->SetStyle(style); |
| |
| AttachContext context; |
| AttachLayoutTree(context); |
| |
| // The TextAutosizer can't update layout view info while the Document is |
| // detached, so update now in case anything changed. |
| if (TextAutosizer* autosizer = GetTextAutosizer()) |
| autosizer->UpdatePageInfo(); |
| |
| GetFrame()->DidAttachDocument(); |
| lifecycle_.AdvanceTo(DocumentLifecycle::kStyleClean); |
| |
| if (View()) |
| View()->DidAttachDocument(); |
| } |
| |
| void Document::Shutdown() { |
| TRACE_EVENT_WITH_FLOW0("blink", "Document::shutdown", TRACE_ID_LOCAL(this), |
| TRACE_EVENT_FLAG_FLOW_IN); |
| CHECK((!GetFrame() || GetFrame()->Tree().ChildCount() == 0) && |
| ConnectedSubframeCount() == 0); |
| if (!IsActive()) |
| return; |
| |
| // An active Document must have an associated window. |
| CHECK(dom_window_); |
| |
| // Frame navigation can cause a new Document to be attached. Don't allow that, |
| // since that will cause a situation where LocalFrame still has a Document |
| // attached after this finishes! Normally, it shouldn't actually be possible |
| // to trigger navigation here. However, plugins (see below) can cause lots of |
| // crazy things to happen, since plugin detach involves nested run loops. |
| FrameNavigationDisabler navigation_disabler(*GetFrame()); |
| // Defer plugin dispose to avoid plugins trying to run script inside |
| // ScriptForbiddenScope, which will crash the renderer after |
| // https://crrev.com/200984 |
| // TODO(dcheng): This is a temporary workaround, Document::Shutdown() should |
| // not be running script at all. |
| HTMLFrameOwnerElement::PluginDisposeSuspendScope suspend_plugin_dispose; |
| // Don't allow script to run in the middle of DetachLayoutTree() because a |
| // detaching Document is not in a consistent state. |
| ScriptForbiddenScope forbid_script; |
| |
| lifecycle_.AdvanceTo(DocumentLifecycle::kStopping); |
| |
| // Do not add code before this without a documented reason. A postcondition of |
| // Shutdown() is that |dom_window_| must not have an attached Document. |
| // Allowing script execution when the Document is shutting down can make it |
| // easy to accidentally violate this condition, and the ordering of the |
| // scopers above is subtle due to legacy interactions with plugins. |
| |
| if (num_canvases_ > 0) |
| UMA_HISTOGRAM_COUNTS_100("Blink.Canvas.NumCanvasesPerPage", num_canvases_); |
| |
| if (!data_->already_sent_automatic_lazy_load_frame_ukm_) { |
| data_->already_sent_automatic_lazy_load_frame_ukm_ = true; |
| if (data_->lazy_ads_frame_count_ > 0 || |
| data_->lazy_embeds_frame_count_ > 0) { |
| ukm::builders::Blink_AutomaticLazyLoadFrame(UkmSourceID()) |
| .SetLazyAdsFrameCount(ukm::GetExponentialBucketMinForCounts1000( |
| data_->lazy_ads_frame_count_)) |
| .SetLazyEmbedsFrameCount(ukm::GetExponentialBucketMinForCounts1000( |
| data_->lazy_embeds_frame_count_)) |
| .Record(UkmRecorder()); |
| } |
| } |
| |
| if (font_matching_metrics_) { |
| font_matching_metrics_->PublishAllMetrics(); |
| } |
| |
| GetViewportData().Shutdown(); |
| |
| View()->Dispose(); |
| DCHECK(!View()->IsAttached()); |
| |
| // If the EmbeddedContentView of the document's frame owner doesn't match |
| // view() then LocalFrameView::Dispose() didn't clear the owner's |
| // EmbeddedContentView. If we don't clear it here, it may be clobbered later |
| // in LocalFrame::CreateView(). See also https://crbug.com/673170 and the |
| // comment in LocalFrameView::Dispose(). |
| HTMLFrameOwnerElement* owner_element = GetFrame()->DeprecatedLocalOwner(); |
| |
| // In the case of a provisional frame, skip clearing the EmbeddedContentView. |
| // A provisional frame is not fully attached to the DOM yet and clearing the |
| // EmbeddedContentView here could clear a not-yet-swapped-out frame |
| // (https://crbug.com/807772). |
| if (owner_element && !GetFrame()->IsProvisional()) |
| owner_element->SetEmbeddedContentView(nullptr); |
| |
| markers_->PrepareForDestruction(); |
| |
| if (TextFragmentHandler* handler = GetFrame()->GetTextFragmentHandler()) |
| handler->DidDetachDocumentOrFrame(); |
| |
| GetPage()->DocumentDetached(this); |
| |
| probe::DocumentDetached(this); |
| |
| scripted_idle_task_controller_.Clear(); |
| |
| if (SvgExtensions()) |
| AccessSVGExtensions().PauseAnimations(); |
| |
| CancelPendingJavaScriptUrls(); |
| http_refresh_scheduler_->Cancel(); |
| |
| GetDocumentAnimations().DetachCompositorTimelines(); |
| |
| if (GetFrame()->IsLocalRoot()) |
| GetPage()->GetChromeClient().AttachRootLayer(nullptr, GetFrame()); |
| |
| MutationObserver::CleanSlotChangeList(*this); |
| |
| hover_element_ = nullptr; |
| active_element_ = nullptr; |
| autofocus_candidates_.clear(); |
| |
| if (focused_element_.Get()) { |
| Element* old_focused_element = focused_element_; |
| focused_element_ = nullptr; |
| NotifyFocusedElementChanged(old_focused_element, nullptr, |
| mojom::blink::FocusType::kNone); |
| } |
| sequential_focus_navigation_starting_point_ = nullptr; |
| focused_element_change_observers_.clear(); |
| |
| if (this == &AXObjectCacheOwner()) { |
| ax_contexts_.clear(); |
| ClearAXObjectCache(); |
| } else { |
| DCHECK(!ax_object_cache_ || ExistingAXObjectCache()) |
| << "Had AXObjectCache for parent, but not for popup document."; |
| if (AXObjectCache* cache = ExistingAXObjectCache()) { |
| // This is a popup document. Clear all accessibility state related to it |
| // by removing the AXObject for its root. The AXObjectCache is |
| // retrieved from the main document, but it maintains both documents. |
| cache->RemovePopup(this); |
| } |
| } |
| |
| DetachLayoutTree(); |
| layout_view_ = nullptr; |
| DCHECK(!View()->IsAttached()); |
| |
| GetStyleEngine().DidDetach(); |
| |
| GetFrame()->DocumentDetached(); |
| GetFrame()->GetEventHandlerRegistry().DocumentDetached(*this); |
| |
| // Signal destruction to mutation observers. |
| synchronous_mutation_observer_set_.ForEachObserver( |
| [](SynchronousMutationObserver* observer) { |
| observer->ContextDestroyed(); |
| observer->ObserverSetWillBeCleared(); |
| }); |
| synchronous_mutation_observer_set_.Clear(); |
| |
| cookie_jar_ = nullptr; // Not accessible after navigated away. |
| fetcher_->ClearContext(); |
| |
| if (media_query_matcher_) |
| media_query_matcher_->DocumentDetached(); |
| |
| lifecycle_.AdvanceTo(DocumentLifecycle::kStopped); |
| DCHECK(!View()->IsAttached()); |
| |
| // Don't create a |ukm_recorder_| and |ukm_source_id_| unless necessary. |
| if (IdentifiabilityStudySettings::Get()->IsActive()) { |
| IdentifiabilitySampleCollector::Get()->FlushSource(UkmRecorder(), |
| UkmSourceID()); |
| } |
| |
| mime_handler_view_before_unload_event_listener_ = nullptr; |
| |
| resource_coordinator_.reset(); |
| |
| // Because the document view transition supplement can get destroyed before |
| // the execution context notification, we should clean up the transition |
| // object here. |
| if (auto* transition = ViewTransitionUtils::GetTransition(*this)) { |
| transition->SkipTransition(); |
| } |
| |
| // This is required, as our LocalFrame might delete itself as soon as it |
| // detaches us. However, this violates Node::detachLayoutTree() semantics, as |
| // it's never possible to re-attach. Eventually Document::detachLayoutTree() |
| // should be renamed, or this setting of the frame to 0 could be made |
| // explicit in each of the callers of Document::detachLayoutTree(). |
| dom_window_ = nullptr; |
| execution_context_ = nullptr; |
| } |
| |
| void Document::RemovedEventListener( |
| const AtomicString& event_type, |
| const RegisteredEventListener& registered_listener) { |
| ContainerNode::RemovedEventListener(event_type, registered_listener); |
| |
| // We need to track the existence of the visibilitychange event listeners to |
| // enable/disable sudden terminations. |
| if (event_type == event_type_names::kVisibilitychange) { |
| if (auto* frame = GetFrame()) |
| frame->RemovedSuddenTerminationDisablerListener(*this, event_type); |
| } |
| } |
| |
| void Document::RemoveAllEventListeners() { |
| int previous_visibility_change_handlers_count = |
| NumberOfEventListeners(event_type_names::kVisibilitychange); |
| |
| ContainerNode::RemoveAllEventListeners(); |
| |
| if (LocalDOMWindow* dom_window = domWindow()) |
| dom_window->RemoveAllEventListeners(); |
| |
| // Update sudden termination disabler state if we previously have listeners |
| // for visibilitychange. |
| if (previous_visibility_change_handlers_count) { |
| if (auto* frame = GetFrame()) { |
| frame->RemovedSuddenTerminationDisablerListener( |
| *this, event_type_names::kVisibilitychange); |
| } |
| } |
| } |
| |
| Document& Document::AXObjectCacheOwner() const { |
| // Every document has its own axObjectCache if accessibility is enabled, |
| // except for page popups, which share the axObjectCache of their owner. |
| Document* doc = const_cast<Document*>(this); |
| auto* frame = doc->GetFrame(); |
| if (frame && frame->HasPagePopupOwner()) { |
| DCHECK(!doc->ax_object_cache_); |
| return frame->PagePopupOwner()->GetDocument().AXObjectCacheOwner(); |
| } |
| return *doc; |
| } |
| |
| static ui::AXMode ComputeAXModeFromAXContexts(Vector<AXContext*> ax_contexts) { |
| ui::AXMode ax_mode = 0; |
| for (AXContext* context : ax_contexts) |
| ax_mode |= context->GetAXMode(); |
| |
| if (!ax_contexts.empty()) { |
| DCHECK(!ax_mode.is_mode_off()) |
| << "The computed AX mode was empty but there were > 0 AXContext " |
| "objects. A caller should have called RemoveAXContext()."; |
| } |
| |
| return ax_mode; |
| } |
| |
| namespace { |
| |
| // Simple count of AXObjectCache objects that are reachable from Documents. The |
| // count assumes that multiple Documents in a single process can have such |
| // caches and that the caches will only ever be created from the main rendering |
| // thread. |
| size_t g_ax_object_cache_count = 0; |
| |
| } // namespace |
| |
| void Document::AddAXContext(AXContext* context) { |
| DCHECK(IsMainThread()); |
| // The only case when |&cache_owner| is not |this| is when this is a |
| // popup. We want popups to share the AXObjectCache of their parent |
| // document. However, there's no valid reason to explicitly create an |
| // AXContext for a popup document, so check to make sure we're not |
| // trying to do that here. |
| DCHECK_EQ(&AXObjectCacheOwner(), this); |
| |
| // If the document has already been detached, do not make a new AXObjectCache. |
| if (!GetLayoutView()) |
| return; |
| |
| ax_contexts_.push_back(context); |
| if (ax_contexts_.size() != 1) { |
| DCHECK(ax_object_cache_); |
| ax_object_cache_->SetAXMode(ComputeAXModeFromAXContexts(ax_contexts_)); |
| return; |
| } |
| |
| if (!ax_object_cache_) { |
| ax_object_cache_ = |
| AXObjectCache::Create(*this, ComputeAXModeFromAXContexts(ax_contexts_)); |
| // Invalidate style on the entire document, because accessibility |
| // needs to compute style on all elements, even those in |
| // content-visibility:auto subtrees. |
| if (documentElement()) { |
| documentElement()->SetNeedsStyleRecalc( |
| kSubtreeStyleChange, StyleChangeReasonForTracing::Create( |
| style_change_reason::kAccessibility)); |
| } |
| g_ax_object_cache_count++; |
| } |
| } |
| |
| void Document::AXContextModeChanged() { |
| DCHECK_GT(ax_contexts_.size(), 0u); |
| DCHECK(ax_object_cache_); |
| ax_object_cache_->SetAXMode(ComputeAXModeFromAXContexts(ax_contexts_)); |
| } |
| |
| void Document::RemoveAXContext(AXContext* context) { |
| auto** iter = base::ranges::find(ax_contexts_, context); |
| if (iter != ax_contexts_.end()) |
| ax_contexts_.erase(iter); |
| if (ax_contexts_.size() == 0) { |
| ClearAXObjectCache(); |
| } else { |
| DCHECK(ax_object_cache_); |
| ax_object_cache_->SetAXMode(ComputeAXModeFromAXContexts(ax_contexts_)); |
| } |
| } |
| |
| void Document::ClearAXObjectCache() { |
| DCHECK(IsMainThread()); |
| DCHECK_EQ(&AXObjectCacheOwner(), this); |
| |
| DCHECK_EQ(ax_contexts_.size(), 0U); |
| |
| // Clear the cache member variable before calling delete because attempts |
| // are made to access it during destruction. |
| if (ax_object_cache_) { |
| ax_object_cache_->Dispose(); |
| ax_object_cache_.Clear(); |
| DCHECK_NE(g_ax_object_cache_count, 0u); |
| g_ax_object_cache_count--; |
| } |
| } |
| |
| AXObjectCache* Document::ExistingAXObjectCache() const { |
| DCHECK(IsMainThread()); |
| if (g_ax_object_cache_count == 0) { |
| return nullptr; |
| } |
| |
| auto& cache_owner = AXObjectCacheOwner(); |
| |
| // If the LayoutView is gone then we are in the process of destruction. |
| if (!cache_owner.GetLayoutView()) |
| return nullptr; |
| |
| return cache_owner.ax_object_cache_.Get(); |
| } |
| |
| void Document::RefreshAccessibilityTree() const { |
| if (AXObjectCache* cache = ExistingAXObjectCache()) { |
| cache->MarkDocumentDirty(); |
| } |
| } |
| |
| CanvasFontCache* Document::GetCanvasFontCache() { |
| if (!canvas_font_cache_) |
| canvas_font_cache_ = MakeGarbageCollected<CanvasFontCache>(*this); |
| |
| return canvas_font_cache_.Get(); |
| } |
| |
| DocumentParser* Document::CreateParser() { |
| if (auto* html_document = DynamicTo<HTMLDocument>(this)) { |
| return MakeGarbageCollected<HTMLDocumentParser>(*html_document, |
| parser_sync_policy_); |
| } |
| // FIXME: this should probably pass the frame instead |
| return MakeGarbageCollected<XMLDocumentParser>(*this, View()); |
| } |
| |
| bool Document::IsFrameSet() const { |
| if (!IsA<HTMLDocument>(this)) |
| return false; |
| return IsA<HTMLFrameSetElement>(body()); |
| } |
| |
| ScriptableDocumentParser* Document::GetScriptableDocumentParser() const { |
| return Parser() ? Parser()->AsScriptableDocumentParser() : nullptr; |
| } |
| |
| void Document::DisplayNoneChangedForFrame() { |
| if (!documentElement()) |
| return; |
| // LayoutView()::CanHaveChildren(), hence the existence of style and |
| // layout tree, depends on the owner being display:none or not. Trigger |
| // detaching or attaching the style/layout-tree as a result of that |
| // changing. |
| documentElement()->SetNeedsStyleRecalc( |
| kLocalStyleChange, |
| StyleChangeReasonForTracing::Create(style_change_reason::kFrame)); |
| } |
| |
| bool Document::WillPrintSoon() { |
| loading_for_print_ = LazyImageHelper::LoadAllImagesAndBlockLoadEvent(*this); |
| |
| if (auto* view = View()) { |
| loading_for_print_ = loading_for_print_ || view->LoadAllLazyLoadedIframes(); |
| } |
| |
| return loading_for_print_; |
| } |
| |
| void Document::SetPrinting(PrintingState state) { |
| bool was_printing = Printing(); |
| printing_ = state; |
| bool is_printing = Printing(); |
| |
| if (was_printing != is_printing) { |
| GetDisplayLockDocumentState().NotifyPrintingOrPreviewChanged(); |
| |
| // We force the color-scheme to light for printing. |
| ColorSchemeChanged(); |
| // StyleResolver::InitialStyleForElement uses different zoom for printing. |
| GetStyleEngine().MarkViewportStyleDirty(); |
| // Separate UA sheet for printing. |
| GetStyleEngine().MarkAllElementsForStyleRecalc( |
| StyleChangeReasonForTracing::Create( |
| style_change_reason::kStyleSheetChange)); |
| |
| if (documentElement() && GetFrame() && !GetFrame()->IsMainFrame() && |
| GetFrame()->Owner() && GetFrame()->Owner()->IsDisplayNone()) { |
| // In non-printing mode we do not generate style or layout objects for |
| // display:none iframes, yet we do when printing (see |
| // LayoutView::CanHaveChildren). Trigger a style recalc on the root |
| // element to create a layout tree for printing. |
| DisplayNoneChangedForFrame(); |
| } |
| } |
| } |
| |
| // https://html.spec.whatwg.org/C/dynamic-markup-insertion.html#document-open-steps |
| void Document::open(LocalDOMWindow* entered_window, |
| ExceptionState& exception_state) { |
| // If |document| is an XML document, then throw an "InvalidStateError" |
| // DOMException exception. |
| if (!IsA<HTMLDocument>(this)) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, |
| "Only HTML documents support open()."); |
| return; |
| } |
| |
| // If |document|'s throw-on-dynamic-markup-insertion counter is greater than |
| // 0, then throw an "InvalidStateError" DOMException. |
| if (throw_on_dynamic_markup_insertion_count_) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidStateError, |
| "Custom Element constructor should not use open()."); |
| return; |
| } |
| |
| if (!AllowedToUseDynamicMarkUpInsertion("open", exception_state)) |
| return; |
| |
| if (entered_window && !entered_window->GetFrame()) |
| return; |
| |
| // If |document|'s origin is not same origin to the origin of the responsible |
| // document specified by the entry settings object, then throw a |
| // "SecurityError" DOMException. |
| if (entered_window && GetExecutionContext() && |
| !GetExecutionContext()->GetSecurityOrigin()->IsSameOriginWith( |
| entered_window->GetSecurityOrigin())) { |
| exception_state.ThrowSecurityError( |
| "Can only call open() on same-origin documents."); |
| return; |
| } |
| |
| // If |document| has an active parser whose script nesting level is greater |
| // than 0, then return |document|. |
| if (ScriptableDocumentParser* parser = GetScriptableDocumentParser()) { |
| if (parser->IsParsing() && parser->IsExecutingScript()) |
| return; |
| } |
| |
| // Similarly, if |document|'s ignore-opens-during-unload counter is greater |
| // than 0, then return |document|. |
| if (ignore_opens_during_unload_count_) |
| return; |
| |
| // If |document|'s active parser was aborted is true, then return |document|. |
| if (ignore_opens_and_writes_for_abort_) |
| return; |
| |
| if (cookie_jar_) { |
| // open() can affect security context which can change cookie values. Make |
| // sure cached values are thrown out. see |
| // third_party/blink/web_tests/http/tests/security/aboutBlank/. |
| cookie_jar_->InvalidateCache(); |
| } |
| |
| // If this document is fully active, then update the URL |
| // for this document with the entered window's url. |
| if (dom_window_ && entered_window) { |
| KURL new_url = entered_window->Url(); |
| if (new_url.IsAboutBlankURL()) { |
| // When updating the URL to about:blank due to a document.open() call, |
| // the opened document should also end up with the same base URL as the |
| // opener about:blank document. Propagate the fallback information here |
| // so that SetURL() below will take it into account. |
| fallback_base_url_ = entered_window->BaseURL(); |
| } |
| // Clear the hash fragment from the inherited URL to prevent a |
| // scroll-into-view for any document.open()'d frame. |
| if (dom_window_ != entered_window) { |
| new_url.SetFragmentIdentifier(String()); |
| } |
| // If an about:srcdoc frame .open()s another frame, then we don't set the |
| // url, and we leave the value of `is_srcdoc_document` untouched. Otherwise |
| // we should reset `is_srcdoc_document_`. |
| if (!new_url.IsAboutSrcdocURL()) { |
| is_srcdoc_document_ = false; |
| SetURL(new_url); |
| } |
| if (Loader()) |
| Loader()->DidOpenDocumentInputStream(new_url); |
| |
| if (dom_window_ != entered_window) { |
| // 2023-03-28: Page use is 0.1%. Too much for a removal. |
| // https://chromestatus.com/metrics/feature/timeline/popularity/4374 |
| CountUse(WebFeature::kDocumentOpenDifferentWindow); |
| |
| if ((dom_window_->GetSecurityContext().GetSandboxFlags() | |
| entered_window->GetSandboxFlags()) != |
| dom_window_->GetSecurityContext().GetSandboxFlags()) { |
| // 2023-03-28. Page use is 0.000005%. Most of the days, it is not even |
| // recorded. Ready for removal! |
| // https://chromestatus.com/metrics/feature/timeline/popularity/4375 |
| CountUse(WebFeature::kDocumentOpenMutateSandbox); |
| } |
| |
| if (!RuntimeEnabledFeatures:: |
| DocumentOpenSandboxInheritanceRemovalEnabled()) { |
| // We inherit the sandbox flags of the entered document, so mask on |
| // the ones contained in the CSP. The operator| is a bitwise operation |
| // on the sandbox flags bits. It makes the sandbox policy stricter (or |
| // as strict) as both policy. |
| // |
| // TODO(arthursonzogni): Why merging sandbox flags? |
| // This doesn't look great at many levels: |
| // - The browser process won't be notified of the update. |
| // - The origin won't be made opaque, despite the new flags. |
| // - The sandbox flags of the document can't be considered to be an |
| // immutable property anymore. |
| // |
| // Ideally: |
| // - javascript-url document. |
| // - XSLT document. |
| // - document.open. |
| // should not mutate the security properties of the current document. |
| // From the browser process point of view, all of those operations are |
| // not considered to produce new documents. No IPCs are sent, it is as |
| // if it was a no-op. |
| // |
| // TODO(https://crbug.com/1360795) Remove this. Only Chrome implements |
| // it. Safari/Firefox do not. |
| dom_window_->GetSecurityContext().SetSandboxFlags( |
| dom_window_->GetSecurityContext().GetSandboxFlags() | |
| entered_window->GetSandboxFlags()); |
| } |
| |
| // We would like to remove this block. See: |
| // https://docs.google.com/document/d/1_89X4cNUab-PZE0iBDTKIftaQZsFbk7SbFmHbqY54os |
| // |
| // This is not specified. Only Webkit/Blink implement it. Gecko doesn't. |
| // |
| // 2023-06-02: Removal would impact 0.02% page load. |
| // https://chromestatus.com/metrics/feature/timeline/popularity/4535 |
| // We hope the document.domain deprecation is going to drive this number |
| // down quickly: |
| // https://developer.chrome.com/blog/document-domain-setter-deprecation/ |
| if (!RuntimeEnabledFeatures::DocumentOpenOriginAliasRemovalEnabled()) { |
| dom_window_->GetSecurityContext().SetSecurityOrigin( |
| entered_window->GetMutableSecurityOrigin()); |
| |
| // The SecurityOrigin is now shared in between two different window. It |
| // means mutating one can have side effect on the other. |
| entered_window->GetMutableSecurityOrigin() |
| ->set_aliased_by_document_open(); |
| } |
| |
| // Question: Should we remove the inheritance of the CookieURL via |
| // document.open? |
| // |
| // Arguments in favor of maintaining this behavior include the fact that |
| // document.open can be used to alter the document's URL. According to |
| // prior talks, this is necessary for web compatibility. It looks nicer if |
| // all URL variations change uniformly and simultaneously. |
| // |
| // Arguments in favor of eliminating this behavior include the fact that |
| // cookie URLs are extremely particular pieces of state that resemble the |
| // origin more than they do actual URLs. The less we inherit via |
| // document.open, the better. |
| cookie_url_ = entered_window->document()->CookieURL(); |
| } |
| } |
| |
| open(); |
| } |
| |
| // https://html.spec.whatwg.org/C/dynamic-markup-insertion.html#document-open-steps |
| void Document::open() { |
| DCHECK(!ignore_opens_during_unload_count_); |
| if (ScriptableDocumentParser* parser = GetScriptableDocumentParser()) |
| DCHECK(!parser->IsParsing() || !parser->IsExecutingScript()); |
| |
| // If |document| has a browsing context and there is an existing attempt to |
| // navigate |document|'s browsing context, then stop document loading given |
| // |document|. |
| // |
| // As noted in the spec and https://github.com/whatwg/html/issues/3975, we |
| // want to treat ongoing navigation and queued navigation the same way. |
| // However, we don't want to consider navigations scheduled too much into the |
| // future through Refresh headers or a <meta> refresh pragma to be a current |
| // navigation. Thus, we cut it off with |
| // IsHttpRefreshScheduledWithin(base::TimeDelta()). |
| // |
| // This also prevents window.open(url) -- eg window.open("about:blank") -- |
| // from blowing away results from a subsequent window.document.open / |
| // window.document.write call. |
| if (GetFrame() && (GetFrame()->Loader().HasProvisionalNavigation() || |
| IsHttpRefreshScheduledWithin(base::TimeDelta()))) { |
| GetFrame()->Loader().StopAllLoaders(/*abort_client=*/true); |
| } |
| CancelPendingJavaScriptUrls(); |
| |
| // TODO(crbug.com/1085514): Consider making HasProvisionalNavigation() return |
| // true when form submission task is active, in which case we can delete this |
| // redundant attempt to cancel it. |
| if (GetFrame()) |
| GetFrame()->CancelFormSubmission(); |
| |
| // For each shadow-including inclusive descendant |node| of |document|, erase |
| // all event listeners and handlers given |node|. |
| // |
| // Erase all event listeners and handlers given |window|. |
| // |
| // NB: Document::RemoveAllEventListeners() (called by |
| // RemoveAllEventListenersRecursively()) erases event listeners from the |
| // Window object as well. |
| RemoveAllEventListenersRecursively(); |
| |
| // Create a new HTML parser and associate it with |document|. |
| // |
| // Set the current document readiness of |document| to "loading". |
| ImplicitOpen(kForceSynchronousParsing); |
| |
| // This is a script-created parser. |
| if (ScriptableDocumentParser* parser = GetScriptableDocumentParser()) |
| parser->SetWasCreatedByScript(true); |
| |
| // Calling document.open counts as committing the first real document load. |
| is_initial_empty_document_ = false; |
| if (GetFrame()) |
| GetFrame()->Loader().DidExplicitOpen(); |
| } |
| |
| void Document::DetachParser() { |
| if (!parser_) |
| return; |
| parser_->Detach(); |
| parser_.Clear(); |
| DocumentParserTiming::From(*this).MarkParserDetached(); |
| } |
| |
| void Document::CancelParsing() { |
| TRACE_EVENT_WITH_FLOW0("blink", "Document::CancelParsing", |
| TRACE_ID_LOCAL(this), |
| TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); |
| // There appears to be an unspecced assumption that a document.open() |
| // or document.write() immediately after a navigation start won't cancel |
| // the navigation. Firefox avoids cancelling the navigation by ignoring an |
| // open() or write() after an active parser is aborted. See |
| // https://github.com/whatwg/html/issues/4723 for discussion about |
| // standardizing this behavior. |
| if (parser_ && parser_->IsParsing()) { |
| ignore_opens_and_writes_for_abort_ = true; |
| if (GetFrame()) { |
| // Only register the sticky feature when the parser was parsing and then |
| // was cancelled. |
| GetFrame()->GetFrameScheduler()->RegisterStickyFeature( |
| SchedulingPolicy::Feature::kParserAborted, |
| {SchedulingPolicy::DisableBackForwardCache()}); |
| } |
| } |
| DetachParser(); |
| SetParsingState(kFinishedParsing); |
| SetReadyState(kComplete); |
| if (!LoadEventFinished()) |
| load_event_progress_ = kLoadEventCompleted; |
| CancelPendingJavaScriptUrls(); |
| http_refresh_scheduler_->Cancel(); |
| } |
| |
| DocumentParser* Document::OpenForNavigation( |
| ParserSynchronizationPolicy parser_sync_policy, |
| const AtomicString& mime_type, |
| const AtomicString& encoding) { |
| TRACE_EVENT_WITH_FLOW0("blink", "Document::OpenForNavigation", |
| TRACE_ID_LOCAL(this), |
| TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); |
| DocumentParser* parser = ImplicitOpen(parser_sync_policy); |
| if (parser->NeedsDecoder()) { |
| parser->SetDecoder( |
| BuildTextResourceDecoder(GetFrame(), Url(), mime_type, encoding)); |
| } |
| if (AnchorElementInteractionTracker::IsFeatureEnabled() && |
| !GetFrame()->IsProvisional()) { |
| anchor_element_interaction_tracker_ = |
| MakeGarbageCollected<AnchorElementInteractionTracker>(*this); |
| } |
| return parser; |
| } |
| |
| DocumentParser* Document::ImplicitOpen( |
| ParserSynchronizationPolicy parser_sync_policy) { |
| RemoveChildren(); |
| DCHECK(!focused_element_); |
| |
| SetCompatibilityMode(kNoQuirksMode); |
| |
| bool force_sync_policy = false; |
| // Give inspector a chance to force sync parsing when virtual time is on. |
| probe::WillCreateDocumentParser(this, force_sync_policy); |
| // Prefetch must be synchronous. |
| force_sync_policy |= ForceSynchronousParsingForTesting() || IsPrefetchOnly(); |
| if (force_sync_policy) |
| parser_sync_policy = kForceSynchronousParsing; |
| DetachParser(); |
| parser_sync_policy_ = parser_sync_policy; |
| parser_ = CreateParser(); |
| DocumentParserTiming::From(*this).MarkParserStart(); |
| SetParsingState(kParsing); |
| SetReadyState(kLoading); |
| if (load_event_progress_ != kLoadEventInProgress && |
| PageDismissalEventBeingDispatched() == kNoDismissal) { |
| load_event_progress_ = kLoadEventNotRun; |
| } |
| DispatchHandleLoadStart(); |
| return parser_.Get(); |
| } |
| |
| void Document::DispatchHandleLoadStart() { |
| if (AXObjectCache* cache = ExistingAXObjectCache()) |
| cache->HandleLoadStart(this); |
| } |
| |
| void Document::DispatchHandleLoadComplete() { |
| if (AXObjectCache* cache = ExistingAXObjectCache()) |
| cache->HandleLoadComplete(this); |
| } |
| |
| HTMLElement* Document::body() const { |
| if (!IsA<HTMLHtmlElement>(documentElement())) |
| return nullptr; |
| |
| for (HTMLElement* child = |
| Traversal<HTMLElement>::FirstChild(*documentElement()); |
| child; child = Traversal<HTMLElement>::NextSibling(*child)) { |
| if (IsA<HTMLFrameSetElement>(*child) || IsA<HTMLBodyElement>(*child)) |
| return child; |
| } |
| |
| return nullptr; |
| } |
| |
| HTMLBodyElement* Document::FirstBodyElement() const { |
| if (!IsA<HTMLHtmlElement>(documentElement())) |
| return nullptr; |
| |
| for (HTMLElement* child = |
| Traversal<HTMLElement>::FirstChild(*documentElement()); |
| child; child = Traversal<HTMLElement>::NextSibling(*child)) { |
| if (auto* body = DynamicTo<HTMLBodyElement>(*child)) |
| return body; |
| } |
| |
| return nullptr; |
| } |
| |
| void Document::setBody(HTMLElement* prp_new_body, |
| ExceptionState& exception_state) { |
| HTMLElement* new_body = prp_new_body; |
| |
| if (!new_body) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kHierarchyRequestError, |
| ExceptionMessages::ArgumentNullOrIncorrectType(1, "HTMLElement")); |
| return; |
| } |
| if (!documentElement()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kHierarchyRequestError, |
| "No document element exists."); |
| return; |
| } |
| |
| if (!IsA<HTMLBodyElement>(*new_body) && |
| !IsA<HTMLFrameSetElement>(*new_body)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kHierarchyRequestError, |
| "The new body element is of type '" + new_body->tagName() + |
| "'. It must be either a 'BODY' or 'FRAMESET' element."); |
| return; |
| } |
| |
| HTMLElement* old_body = body(); |
| if (old_body == new_body) |
| return; |
| |
| if (old_body) |
| documentElement()->ReplaceChild(new_body, old_body, exception_state); |
| else |
| documentElement()->AppendChild(new_body, exception_state); |
| } |
| |
| void Document::WillInsertBody() { |
| if (Loader()) |
| fetcher_->LoosenLoadThrottlingPolicy(); |
| |
| if (auto* supplement = ViewTransitionSupplement::FromIfExists(*this)) { |
| supplement->WillInsertBody(); |
| } |
| |
| if (render_blocking_resource_manager_) { |
| render_blocking_resource_manager_->WillInsertDocumentBody(); |
| } |
| |
| // If we get to the <body> try to resume commits since we should have content |
| // to paint now. |
| // TODO(esprehn): Is this really optimal? We might start producing frames |
| // for very little content, should we wait for some heuristic like |
| // isVisuallyNonEmpty() ? |
| BeginLifecycleUpdatesIfRenderingReady(); |
| } |
| |
| HTMLHeadElement* Document::head() const { |
| Node* de = documentElement(); |
| if (!de) |
| return nullptr; |
| |
| return Traversal<HTMLHeadElement>::FirstChild(*de); |
| } |
| |
| Element* Document::ViewportDefiningElement() const { |
| // If a BODY element sets non-visible overflow, it is to be propagated to the |
| // viewport, as long as the following conditions are all met: |
| // (1) The root element is HTML. |
| // (2) It is the primary BODY element. |
| // (3) The root element has visible overflow. |
| // (4) The root or BODY elements do not apply any containment. |
| // Otherwise it's the root element's properties that are to be propagated. |
| |
| // This method is called in the middle of a lifecycle update, for instance |
| // from a LayoutObject which is created but not yet inserted into the box |
| // tree, which is why we have to do the decision based on the ComputedStyle |
| // and not the LayoutObject style and the containment checks below also. |
| |
| Element* root_element = documentElement(); |
| if (!root_element) |
| return nullptr; |
| const ComputedStyle* root_style = root_element->GetComputedStyle(); |
| if (!root_style || root_style->IsEnsuredInDisplayNone()) |
| return nullptr; |
| if (!root_style->IsOverflowVisibleAlongBothAxes()) |
| return root_element; |
| HTMLBodyElement* body_element = FirstBodyElement(); |
| if (!body_element) |
| return root_element; |
| const ComputedStyle* body_style = body_element->GetComputedStyle(); |
| if (!body_style || body_style->IsEnsuredInDisplayNone()) |
| return root_element; |
| if (root_style->ShouldApplyAnyContainment(*root_element) || |
| body_style->ShouldApplyAnyContainment(*body_element)) { |
| return root_element; |
| } |
| return body_element; |
| } |
| |
| Document* Document::open(v8::Isolate* isolate, |
| const AtomicString& type, |
| const AtomicString& replace, |
| ExceptionState& exception_state) { |
| if (replace == "replace") { |
| CountUse(WebFeature::kDocumentOpenTwoArgsWithReplace); |
| } |
| open(EnteredDOMWindow(isolate), exception_state); |
| return this; |
| } |
| |
| DOMWindow* Document::open(v8::Isolate* isolate, |
| const String& url_string, |
| const AtomicString& name, |
| const AtomicString& features, |
| ExceptionState& exception_state) { |
| if (!domWindow()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidAccessError, |
| "The document has no window associated."); |
| return nullptr; |
| } |
| |
| return domWindow()->open(isolate, url_string, name, features, |
| exception_state); |
| } |
| |
| // https://html.spec.whatwg.org/C/dynamic-markup-insertion.html#dom-document-close |
| void Document::close(ExceptionState& exception_state) { |
| // If the Document object is an XML document, then throw an |
| // "InvalidStateError" DOMException. |
| if (!IsA<HTMLDocument>(this)) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, |
| "Only HTML documents support close()."); |
| return; |
| } |
| |
| // If the Document object's throw-on-dynamic-markup-insertion counter is |
| // greater than zero, then throw an "InvalidStateError" DOMException. |
| if (throw_on_dynamic_markup_insertion_count_) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidStateError, |
| "Custom Element constructor should not use close()."); |
| return; |
| } |
| |
| if (!AllowedToUseDynamicMarkUpInsertion("close", exception_state)) |
| return; |
| |
| close(); |
| } |
| |
| // https://html.spec.whatwg.org/C/dynamic-markup-insertion.html#dom-document-close |
| void Document::close() { |
| // If there is no script-created parser associated with the document, then |
| // return. |
| if (!GetScriptableDocumentParser() || |
| !GetScriptableDocumentParser()->WasCreatedByScript() || |
| !GetScriptableDocumentParser()->IsParsing()) |
| return; |
| |
| // Insert an explicit "EOF" character at the end of the parser's input |
| // stream. |
| parser_->Finish(); |
| |
| // TODO(timothygu): We should follow the specification more closely. |
| if (!parser_ || !parser_->IsParsing()) |
| SetReadyState(kComplete); |
| CheckCompleted(); |
| } |
| |
| void Document::ImplicitClose() { |
| DCHECK(!InStyleRecalc()); |
| |
| load_event_progress_ = kLoadEventInProgress; |
| |
| // We have to clear the parser, in case someone document.write()s from the |
| // onLoad event handler, as in Radar 3206524. |
| DetachParser(); |
| |
| // JS running below could remove the frame or destroy the LayoutView so we |
| // call those two functions repeatedly and don't save them on the stack. |
| |
| // To align the HTML load event and the SVGLoad event for the outermost <svg> |
| // element, fire it from here, instead of doing it from |
| // SVGElement::finishedParsingChildren. |
| if (SvgExtensions()) |
| AccessSVGExtensions().DispatchSVGLoadEventToOutermostSVGElements(); |
| |
| if (domWindow()) |
| domWindow()->DocumentWasClosed(); |
| |
| if (GetFrame() && GetFrame()->IsMainFrame()) |
| GetFrame()->GetLocalFrameHostRemote().DocumentOnLoadCompleted(); |
| |
| if (GetFrame()) { |
| GetFrame()->Client()->DispatchDidHandleOnloadEvents(); |
| } |
| |
| if (!GetFrame()) { |
| load_event_progress_ = kLoadEventCompleted; |
| return; |
| } |
| |
| if (GetFrame()->Loader().HasProvisionalNavigation() && |
| start_time_.Elapsed() < kCLayoutScheduleThreshold) { |
| // Just bail out. Before or during the onload we were shifted to another |
| // page. The old i-Bench suite does this. When this happens don't bother |
| // painting or laying out. |
| load_event_progress_ = kLoadEventCompleted; |
| return; |
| } |
| |
| if (HaveRenderBlockingStylesheetsLoaded()) { |
| // The initial empty document might be loaded synchronously. |
| // When this occurs and we also synchronously update the style and layout |
| // here, which is needed for things like autofill, it creates a chain |
| // reaction where inserting iframes without a src to a document causes |
| // expensive layout thrashing of the embedding document. Since this is a |
| // common scenario, special-casing it here, and avoiding that layout if |
| // this is an initial-empty document in a subframe. |
| if (!base::FeatureList::IsEnabled( |
| features::kAvoidForcedLayoutOnInitialEmptyDocumentInSubframe) || |
| Loader()->HasLoadedNonInitialEmptyDocument() || |
| GetFrame()->IsMainFrame()) { |
| UpdateStyleAndLayout(DocumentUpdateReason::kUnknown); |
| } |
| } |
| |
| load_event_progress_ = kLoadEventCompleted; |
| |
| if (GetFrame() && GetLayoutView()) { |
| DispatchHandleLoadComplete(); |
| FontFaceSetDocument::DidLayout(*this); |
| } |
| |
| if (SvgExtensions()) |
| AccessSVGExtensions().StartAnimations(); |
| |
| if (base::FeatureList::IsEnabled( |
| blink::features::kSpeculativeServiceWorkerWarmUp)) { |
| if (auto* observer = |
| AnchorElementObserverForServiceWorker::From(TopDocument())) { |
| observer->MaybeSendPendingWarmUpRequests(); |
| } |
| } |
| } |
| |
| static bool AllDescendantsAreComplete(Document* document) { |
| Frame* frame = document->GetFrame(); |
| if (!frame) |
| return true; |
| |
| for (Frame* child = frame->Tree().FirstChild(); child; |
| child = child->Tree().TraverseNext(frame)) { |
| if (child->IsLoading()) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool Document::ShouldComplete() { |
| return parsing_state_ == kFinishedParsing && |
| !fetcher_->BlockingRequestCount() && !IsDelayingLoadEvent() && |
| !javascript_url_task_handle_.IsActive() && |
| load_event_progress_ != kLoadEventInProgress && |
| AllDescendantsAreComplete(this) && !Fetcher()->IsInRequestResource(); |
| } |
| |
| void Document::Abort() { |
| CancelParsing(); |
| CheckCompletedInternal(); |
| } |
| |
| void Document::CheckCompleted() { |
| if (CheckCompletedInternal()) { |
| CHECK(GetFrame()); |
| GetFrame()->Loader().DidFinishNavigation( |
| FrameLoader::NavigationFinishState::kSuccess); |
| } |
| } |
| |
| void Document::FetchDictionaryFromLinkHeader() { |
| if (!CompressionDictionaryTransportFullyEnabled(GetExecutionContext()) || |
| !Loader()) { |
| return; |
| } |
| Loader()->DispatchLinkHeaderPreloads( |
| nullptr /* viewport */, |
| PreloadHelper::LoadLinksFromHeaderMode::kDocumentAfterLoadCompleted); |
| } |
| |
| bool Document::CheckCompletedInternal() { |
| if (!ShouldComplete()) |
| return false; |
| |
| if (GetFrame() && !UnloadStarted()) { |
| GetFrame()->Client()->RunScriptsAtDocumentIdle(); |
| |
| // Injected scripts may have disconnected this frame. |
| if (!GetFrame()) |
| return false; |
| |
| // Check again, because runScriptsAtDocumentIdle() may have delayed the load |
| // event. |
| if (!ShouldComplete()) |
| return false; |
| } |
| |
| // OK, completed. Fire load completion events as needed. |
| SetReadyState(kComplete); |
| const bool load_event_needed = LoadEventStillNeeded(); |
| if (load_event_needed) { |
| ImplicitClose(); |
| } |
| |
| DCHECK(fetcher_); |
| |
| fetcher_->ScheduleWarnUnusedPreloads( |
| WTF::BindOnce(&Document::OnWarnUnusedPreloads, WrapWeakPersistent(this))); |
| |
| // The readystatechanged or load event may have disconnected this frame. |
| if (!GetFrame() || !GetFrame()->IsAttached()) |
| return false; |
| http_refresh_scheduler_->MaybeStartTimer(); |
| View()->HandleLoadCompleted(); |
| // The document itself is complete, but if a child frame was restarted due to |
| // an event, this document is still considered to be in progress. |
| if (!AllDescendantsAreComplete(this)) |
| return false; |
| |
| // No need to repeat if we've already notified this load as finished. |
| if (!Loader()->SentDidFinishLoad()) { |
| if (GetFrame()->IsOutermostMainFrame()) { |
| GetViewportData().GetViewportDescription().ReportMobilePageStats( |
| GetFrame()); |
| } |
| Loader()->SetSentDidFinishLoad(); |
| GetFrame()->Client()->DispatchDidFinishLoad(); |
| // RenderFrameObservers may execute script, which could detach this frame. |
| if (!GetFrame()) |
| return false; |
| GetFrame()->GetLocalFrameHostRemote().DidFinishLoad(Loader()->Url()); |
| |
| GetFrame()->GetFrameScheduler()->RegisterStickyFeature( |
| SchedulingPolicy::Feature::kDocumentLoaded, |
| {SchedulingPolicy::DisableBackForwardCache()}); |
| |
| DetectJavascriptFrameworksOnLoad(*this); |
| // Only load the dictionary after the full document load completes. |
| // The compression dictionary is of low priority and shall be only loaded |
| // when the browser is idle. |
| FetchDictionaryFromLinkHeader(); |
| } else if (loading_for_print_) { |
| loading_for_print_ = false; |
| GetFrame()->Client()->DispatchDidFinishLoadForPrinting(); |
| // Refresh the page when the print preview pops up. |
| // DispatchDidFinishLoadForPrinting could detach this frame |
| if (!GetFrame()) { |
| return false; |
| } |
| } |
| |
| if (auto* view = View()) { |
| if (view->GetFragmentAnchor()) { |
| // Schedule an animation frame to process fragment anchors. The frame |
| // can't be scheduled when the fragment anchor is set because, per spec, |
| // we must wait for the document to be loaded before invoking fragment |
| // anchors. |
| View()->ScheduleAnimation(); |
| } |
| } |
| |
| if (load_event_needed) { |
| if (LCPCriticalPathPredictor* lcpp = GetFrame()->GetLCPP()) { |
| lcpp->OnOutermostMainFrameDocumentLoad(); |
| fetcher_->MaybeRecordLCPPSubresourceMetrics(Url()); |
| } |
| } |
| |
| return true; |
| } |
| |
| namespace { |
| |
| enum class BeforeUnloadUse { |
| kNoDialogNoText, |
| kNoDialogNoUserGesture, |
| kNoDialogMultipleConfirmationForNavigation, |
| kShowDialog, |
| kNoDialogAutoCancelTrue, |
| kNotSupportedInDocumentPictureInPicture, |
| kMaxValue = kNotSupportedInDocumentPictureInPicture, |
| }; |
| |
| void RecordBeforeUnloadUse(BeforeUnloadUse metric) { |
| base::UmaHistogramEnumeration("Document.BeforeUnloadDialog", metric); |
| } |
| |
| } // namespace |
| |
| bool Document::DispatchBeforeUnloadEvent(ChromeClient* chrome_client, |
| bool is_reload, |
| bool& did_allow_navigation) { |
| TRACE_EVENT_WITH_FLOW0("blink", "Document::DispatchBeforeUnloadEvent", |
| TRACE_ID_LOCAL(this), |
| TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); |
| if (!dom_window_) |
| return true; |
| |
| if (!body()) |
| return true; |
| |
| if (ProcessingBeforeUnload()) |
| return false; |
| |
| if (dom_window_->IsPictureInPictureWindow()) { |
| RecordBeforeUnloadUse( |
| BeforeUnloadUse::kNotSupportedInDocumentPictureInPicture); |
| return true; |
| } |
| |
| // Since we do not allow registering the beforeunload event handlers in |
| // fenced frames, it should not be fired by fencedframes. |
| DCHECK(!GetFrame() || !GetFrame()->IsInFencedFrameTree() || |
| !GetEventTargetData() || |
| !GetEventTargetData()->event_listener_map.Contains( |
| event_type_names::kBeforeunload)); |
| |
| PageDismissalScope in_page_dismissal; |
| auto& before_unload_event = *MakeGarbageCollected<BeforeUnloadEvent>(); |
| before_unload_event.initEvent(event_type_names::kBeforeunload, false, true); |
| |
| { |
| // We want to avoid progressing to kBeforeUnloadEventHandled if the page |
| // cancels the unload. Because a subframe may cancel unload on our behalf, |
| // only the caller, which makes this call over the frame subtree, can know |
| // whether or not we'll unload so the caller is responsible for advancing |
| // to kBeforeUnloadEventHandled. Here, we'll reset back to our prior value |
| // once the handler has run. |
| base::AutoReset<LoadEventProgress> set_in_progress( |
| &load_event_progress_, kBeforeUnloadEventInProgress); |
| dom_window_->DispatchEvent(before_unload_event, this); |
| } |
| |
| if (!before_unload_event.defaultPrevented()) |
| DefaultEventHandler(before_unload_event); |
| |
| bool cancelled_by_script = |
| RuntimeEnabledFeatures::BeforeunloadEventCancelByPreventDefaultEnabled() |
| ? !before_unload_event.returnValue().empty() || |
| before_unload_event.defaultPrevented() |
| : !before_unload_event.returnValue().IsNull(); |
| |
| if (cancelled_by_script) { |
| RecordBeforeUnloadUse(BeforeUnloadUse::kNoDialogNoText); |
| } |
| |
| if (!GetFrame() || !cancelled_by_script) { |
| return true; |
| } |
| |
| if (!GetFrame()->HasStickyUserActivation()) { |
| RecordBeforeUnloadUse(BeforeUnloadUse::kNoDialogNoUserGesture); |
| String message = |
| "Blocked attempt to show a 'beforeunload' confirmation panel for a " |
| "frame that never had a user gesture since its load. " |
| "https://www.chromestatus.com/feature/5082396709879808"; |
| Intervention::GenerateReport(GetFrame(), "BeforeUnloadNoGesture", message); |
| return true; |
| } |
| |
| if (did_allow_navigation) { |
| RecordBeforeUnloadUse( |
| BeforeUnloadUse::kNoDialogMultipleConfirmationForNavigation); |
| String message = |
| "Blocked attempt to show multiple 'beforeunload' confirmation panels " |
| "for a single navigation."; |
| Intervention::GenerateReport(GetFrame(), "BeforeUnloadMultiple", message); |
| return true; |
| } |
| |
| // If |chrome_client| is null simply indicate that the navigation should |
| // not proceed. |
| if (!chrome_client) { |
| RecordBeforeUnloadUse(BeforeUnloadUse::kNoDialogAutoCancelTrue); |
| did_allow_navigation = false; |
| return false; |
| } |
| |
| String text = before_unload_event.returnValue(); |
| RecordBeforeUnloadUse(BeforeUnloadUse::kShowDialog); |
| const base::TimeTicks beforeunload_confirmpanel_start = |
| base::TimeTicks::Now(); |
| did_allow_navigation = |
| chrome_client->OpenBeforeUnloadConfirmPanel(text, GetFrame(), is_reload); |
| const base::TimeTicks beforeunload_confirmpanel_end = base::TimeTicks::Now(); |
| if (did_allow_navigation) { |
| // Only record when a navigation occurs, since we want to understand |
| // the impact of the before unload dialog on overall input to navigation. |
| UMA_HISTOGRAM_MEDIUM_TIMES( |
| "DocumentEventTiming.BeforeUnloadDialogDuration.ByNavigation", |
| beforeunload_confirmpanel_end - beforeunload_confirmpanel_start); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void Document::DispatchUnloadEvents(UnloadEventTimingInfo* unload_timing_info) { |
| TRACE_EVENT_WITH_FLOW0("blink", "Document::DispatchUnloadEvents", |
| TRACE_ID_LOCAL(this), |
| TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); |
| PluginScriptForbiddenScope forbid_plugin_destructor_scripting; |
| PageDismissalScope in_page_dismissal; |
| if (parser_) { |
| parser_->StopParsing(); |
| } |
| |
| if (IsPrerendering()) { |
| // We do not dispatch unload event while prerendering. |
| return; |
| } |
| |
| if (load_event_progress_ == kLoadEventNotRun || |
| // TODO(dcheng): We should consider if we can make this conditional check |
| // stronger with a DCHECK() that this isn't called if the unload event is |
| // already complete. |
| load_event_progress_ > kUnloadEventInProgress) { |
| return; |
| } |
| |
| Element* current_focused_element = FocusedElement(); |
| if (auto* input = DynamicTo<HTMLInputElement>(current_focused_element)) |
| input->EndEditing(); |
| |
| // Since we do not allow registering the unload event handlers in |
| // fenced frames, it should not be fired by fencedframes. |
| DCHECK(!GetFrame() || !GetFrame()->IsInFencedFrameTree() || |
| !GetEventTargetData() || |
| !GetEventTargetData()->event_listener_map.Contains( |
| event_type_names::kUnload)); |
| |
| // If we've dispatched the pagehide event with 'persisted' set to true, it |
| // means we've dispatched the visibilitychange event before too. Also, we |
| // shouldn't dispatch the unload event because that event should only be |
| // fired when the pagehide event's 'persisted' bit is set to false. |
| bool dispatched_pagehide_persisted = |
| GetPage() && GetPage()->DispatchedPagehidePersistedAndStillHidden(); |
| |
| if (load_event_progress_ >= kPageHideInProgress || |
| dispatched_pagehide_persisted) { |
| load_event_progress_ = kUnloadEventHandled; |
| return; |
| } |
| |
| load_event_progress_ = kPageHideInProgress; |
| LocalDOMWindow* window = domWindow(); |
| // We check for DispatchedPagehideAndStillHidden() here because it's possible |
| // to dispatch pagehide with 'persisted' set to false before this and pass the |
| // |dispatched_pagehide_persisted| above, if we enable same-site |
| // ProactivelySwapBrowsingInstance but not BackForwardCache. |
| if (window && !GetPage()->DispatchedPagehideAndStillHidden()) { |
| window->DispatchEvent( |
| *PageTransitionEvent::Create(event_type_names::kPagehide, false), this); |
| } |
| if (!dom_window_) |
| return; |
| |
| // This must be queried before |load_event_progress_| is changed to |
| // kUnloadVisibilityChangeInProgress because that would change the result. |
| bool page_visible = IsPageVisible(); |
| load_event_progress_ = kUnloadVisibilityChangeInProgress; |
| if (page_visible) { |
| // Dispatch visibilitychange event, but don't bother doing |
| // other notifications as we're about to be unloaded. |
| DispatchEvent(*Event::CreateBubble(event_type_names::kVisibilitychange)); |
| DispatchEvent( |
| *Event::CreateBubble(event_type_names::kWebkitvisibilitychange)); |
| } |
| if (!dom_window_) |
| return; |
| |
| GetFrame()->Loader().SaveScrollAnchor(); |
| |
| load_event_progress_ = kUnloadEventInProgress; |
| Event& unload_event = *Event::Create(event_type_names::kUnload); |
| const base::TimeTicks unload_event_start = base::TimeTicks::Now(); |
| dom_window_->DispatchEvent(unload_event, this); |
| const base::TimeTicks unload_event_end = base::TimeTicks::Now(); |
| |
| if (unload_timing_info) { |
| // Record unload event timing when navigating cross-document. |
| auto& timing = unload_timing_info->unload_timing.emplace(); |
| timing.can_request = |
| unload_timing_info->new_document_origin->CanRequest(Url()); |
| timing.unload_event_start = unload_event_start; |
| timing.unload_event_end = unload_event_end; |
| } |
| load_event_progress_ = kUnloadEventHandled; |
| } |
| |
| void Document::DispatchFreezeEvent() { |
| SetFreezingInProgress(true); |
| DispatchEvent(*Event::Create(event_type_names::kFreeze)); |
| SetFreezingInProgress(false); |
| UseCounter::Count(*this, WebFeature::kPageLifeCycleFreeze); |
| } |
| |
| Document::PageDismissalType Document::PageDismissalEventBeingDispatched() |
| const { |
| switch (load_event_progress_) { |
| case kBeforeUnloadEventInProgress: |
| return kBeforeUnloadDismissal; |
| case kPageHideInProgress: |
| return kPageHideDismissal; |
| case kUnloadVisibilityChangeInProgress: |
| return kUnloadVisibilityChangeDismissal; |
| case kUnloadEventInProgress: |
| return kUnloadDismissal; |
| |
| case kLoadEventNotRun: |
| case kLoadEventInProgress: |
| case kLoadEventCompleted: |
| case kBeforeUnloadEventHandled: |
| case kUnloadEventHandled: |
| return kNoDismissal; |
| } |
| NOTREACHED(); |
| return kNoDismissal; |
| } |
| |
| void Document::SetParsingState(ParsingState parsing_state) { |
| ParsingState previous_state = parsing_state_; |
| parsing_state_ = parsing_state; |
| |
| if (Parsing() && !element_data_cache_) |
| element_data_cache_ = MakeGarbageCollected<ElementDataCache>(); |
| if (previous_state != kFinishedParsing && |
| parsing_state_ == kFinishedParsing) { |
| if (form_controller_ && form_controller_->HasControlStates()) |
| form_controller_->ScheduleRestore(); |
| } |
| } |
| |
| bool Document::ShouldScheduleLayout() const { |
| // This function will only be called when LocalFrameView thinks a layout is |
| // needed. This enforces a couple extra rules. |
| // |
| // (a) Only schedule a layout once the stylesheets are loaded. |
| // (b) Only schedule layout once we have a body element, or parsing has |
| // finished and we don't have a body element (if e.g. a script has |
| // removed the body element, but the root element, and maybe even the |
| // head elements are styled to render, we should allow layout of those |
| // elements). |
| if (!IsActive()) { |
| return false; |
| } |
| if (HaveRenderBlockingResourcesLoaded() && (body() || HasFinishedParsing())) { |
| return true; |
| } |
| if (documentElement() && !IsA<HTMLHtmlElement>(documentElement())) { |
| return true; |
| } |
| return false; |
| } |
| |
| void Document::write(const String& text, |
| LocalDOMWindow* entered_window, |
| ExceptionState& exception_state) { |
| if (!IsA<HTMLDocument>(this)) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, |
| "Only HTML documents support write()."); |
| return; |
| } |
| |
| if (throw_on_dynamic_markup_insertion_count_) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidStateError, |
| "Custom Element constructor should not use write()."); |
| return; |
| } |
| |
| if (entered_window && !entered_window->GetFrame()) |
| return; |
| |
| if (entered_window && GetExecutionContext() && |
| !GetExecutionContext()->GetSecurityOrigin()->IsSameOriginWith( |
| entered_window->GetSecurityOrigin())) { |
| exception_state.ThrowSecurityError( |
| "Can only call write() on same-origin documents."); |
| return; |
| } |
| |
| if (ignore_opens_and_writes_for_abort_) |
| return; |
| |
| NestingLevelIncrementer nesting_level_incrementer(write_recursion_depth_); |
| |
| write_recursion_is_too_deep_ = |
| (write_recursion_depth_ > 1) && write_recursion_is_too_deep_; |
| write_recursion_is_too_deep_ = |
| (write_recursion_depth_ > kCMaxWriteRecursionDepth) || |
| write_recursion_is_too_deep_; |
| |
| if (write_recursion_is_too_deep_) |
| return; |
| |
| bool has_insertion_point = parser_ && parser_->HasInsertionPoint(); |
| |
| if (!has_insertion_point) { |
| if (ignore_destructive_write_count_) { |
| AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| ConsoleMessage::Source::kJavaScript, ConsoleMessage::Level::kWarning, |
| ExceptionMessages::FailedToExecute( |
| "write", "Document", |
| "It isn't possible to write into a document " |
| "from an asynchronously-loaded external " |
| "script unless it is explicitly opened."))); |
| return; |
| } |
| if (ignore_opens_during_unload_count_) |
| return; |
| |
| if (ignore_destructive_write_module_script_count_) { |
| UseCounter::Count(*this, |
| WebFeature::kDestructiveDocumentWriteAfterModuleScript); |
| } |
| open(entered_window, ASSERT_NO_EXCEPTION); |
| } |
| |
| DCHECK(parser_); |
| PerformanceMonitor::ReportGenericViolation( |
| domWindow(), PerformanceMonitor::kDiscouragedAPIUse, |
| "Avoid using document.write(). " |
| "https://developers.google.com/web/updates/2016/08/" |
| "removing-document-write", |
| base::TimeDelta(), nullptr); |
| probe::BreakableLocation(domWindow(), "Document.write"); |
| parser_->insert(text); |
| } |
| |
| void Document::writeln(const String& text, |
| LocalDOMWindow* entered_window, |
| ExceptionState& exception_state) { |
| write(text, entered_window, exception_state); |
| if (exception_state.HadException()) |
| return; |
| write("\n", entered_window); |
| } |
| |
| void Document::write(v8::Isolate* isolate, |
| const Vector<String>& text, |
| ExceptionState& exception_state) { |
| if (!AllowedToUseDynamicMarkUpInsertion("write", exception_state)) |
| return; |
| |
| StringBuilder builder; |
| for (const String& string : text) |
| builder.Append(string); |
| String string = TrustedTypesCheckForHTML( |
| builder.ReleaseString(), GetExecutionContext(), exception_state); |
| if (exception_state.HadException()) |
| return; |
| |
| write(string, EnteredDOMWindow(isolate), exception_state); |
| } |
| |
| void Document::writeln(v8::Isolate* isolate, |
| const Vector<String>& text, |
| ExceptionState& exception_state) { |
| if (!AllowedToUseDynamicMarkUpInsertion("writeln", exception_state)) |
| return; |
| |
| StringBuilder builder; |
| for (const String& string : text) |
| builder.Append(string); |
| String string = TrustedTypesCheckForHTML( |
| builder.ReleaseString(), GetExecutionContext(), exception_state); |
| if (exception_state.HadException()) |
| return; |
| |
| writeln(string, EnteredDOMWindow(isolate), exception_state); |
| } |
| |
| void Document::write(v8::Isolate* isolate, |
| TrustedHTML* text, |
| ExceptionState& exception_state) { |
| write(text->toString(), EnteredDOMWindow(isolate), exception_state); |
| } |
| |
| void Document::writeln(v8::Isolate* isolate, |
| TrustedHTML* text, |
| ExceptionState& exception_state) { |
| writeln(text->toString(), EnteredDOMWindow(isolate), exception_state); |
| } |
| |
| KURL Document::urlForBinding() const { |
| if (!Url().IsNull()) { |
| return Url(); |
| } |
| return BlankURL(); |
| } |
| |
| void Document::SetURL(const KURL& url) { |
| KURL new_url = url.IsEmpty() ? BlankURL() : url; |
| if (new_url == url_) |
| return; |
| |
| TRACE_EVENT_WITH_FLOW1("blink", "Document::SetURL", TRACE_ID_LOCAL(this), |
| TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, |
| "url", new_url.GetString().Utf8()); |
| |
| // Strip the fragment directive from the URL fragment. E.g. "#id:~:text=a" |
| // --> "#id". See https://github.com/WICG/scroll-to-text-fragment. |
| new_url = fragment_directive_->ConsumeFragmentDirective(new_url); |
| |
| url_ = new_url; |
| UpdateBaseURL(); |
| |
| if (GetFrame()) { |
| if (FrameScheduler* frame_scheduler = GetFrame()->GetFrameScheduler()) |
| frame_scheduler->TraceUrlChange(url_.GetString()); |
| } |
| } |
| |
| KURL Document::ValidBaseElementURL() const { |
| if (base_element_url_.IsValid()) |
| return base_element_url_; |
| |
| return KURL(); |
| } |
| |
| void Document::UpdateBaseURL() { |
| KURL old_base_url = base_url_; |
| // DOM 3 Core: When the Document supports the feature "HTML" [DOM Level 2 |
| // HTML], the base URI is computed using first the value of the href attribute |
| // of the HTML BASE element if any, and the value of the documentURI attribute |
| // from the Document interface otherwise (which we store, preparsed, in |
| // |url_|). |
| if (!base_element_url_.IsEmpty()) |
| base_url_ = base_element_url_; |
| else if (!base_url_override_.IsEmpty()) |
| base_url_ = base_url_override_; |
| else |
| base_url_ = FallbackBaseURL(); |
| |
| GetSelectorQueryCache().Invalidate(); |
| |
| if (!base_url_.IsValid()) |
| base_url_ = KURL(); |
| |
| if (elem_sheet_) { |
| // Element sheet is silly. It never contains anything. |
| DCHECK(!elem_sheet_->Contents()->RuleCount()); |
| elem_sheet_ = nullptr; |
| } |
| |
| GetStyleEngine().BaseURLChanged(); |
| |
| if (!EqualIgnoringFragmentIdentifier(old_base_url, base_url_)) { |
| // Base URL change changes any relative visited links. |
| // FIXME: There are other URLs in the tree that would need to be |
| // re-evaluated on dynamic base URL change. Style should be invalidated too. |
| for (HTMLAnchorElement& anchor : |
| Traversal<HTMLAnchorElement>::StartsAfter(*this)) |
| anchor.InvalidateCachedVisitedLinkHash(); |
| } |
| |
| if (auto* document_rules = DocumentSpeculationRules::FromIfExists(*this)) { |
| document_rules->DocumentBaseURLChanged(); |
| } |
| } |
| |
| // [spec] https://html.spec.whatwg.org/C/#fallback-base-url |
| KURL Document::FallbackBaseURL() const { |
| const bool is_parent_cross_origin = |
| GetFrame() && GetFrame()->IsCrossOriginToParentOrOuterDocument(); |
| // TODO(https://crbug.com/751329, https://crbug.com/1336904): Referring to |
| // ParentDocument() is not correct. |
| // We avoid using it when it is cross-origin, to avoid leaking cross-origin. |
| const Document* same_origin_parent = |
| is_parent_cross_origin ? nullptr : ParentDocument(); |
| |
| // TODO(https://github.com/whatwg/html/issues/9025): Don't let a sandboxed |
| // iframe (without 'allow-same-origin') inherit a fallback base url. |
| // https://chromium-review.googlesource.com/c/chromium/src/+/4324738 |
| |
| // [spec] 1. If document is an iframe srcdoc document, then return the |
| // document base URL of document's browsing context's container |
| // document. |
| if (IsSrcdocDocument()) { |
| // Return the base_url value that was sent from the initiator along with the |
| // srcdoc attribute's value. |
| if (fallback_base_url_.IsValid()) { |
| return fallback_base_url_; |
| } |
| // The fallback base URL can be missing in some cases (e.g., for |
| // browser-initiated navigations like |
| // NavigationBrowserTest.BlockedSrcDocBrowserInitiated, or for cases where |
| // the srcdoc's initiator origin does not match the parent origin, which |
| // should be removed in https://crbug.com/1169736). |
| // |
| // In these cases, only use the parent frame's base URL if it is |
| // same-origin. |
| // TODO(https://crbug.com/1169736): Enforce that the parent is same-origin, |
| // which should already be true. |
| if (same_origin_parent) { |
| return same_origin_parent->BaseURL(); |
| } |
| } |
| |
| // [spec] 2. If document's URL matches about:blank and document's about base |
| // URL is non-null, then return document's about base URL. |
| if (urlForBinding().IsAboutBlankURL()) { |
| if (!RuntimeEnabledFeatures::DocumentBaseURIFixEnabled() && !dom_window_ && |
| execution_context_) { |
| return execution_context_->BaseURL(); |
| } |
| |
| if (!fallback_base_url_.IsEmpty()) { |
| // Note: if we get here, it's not worth worrying if |
| // same_origin_parent->BaseURL() exists and matches fallback_base_url_, |
| // since if the latter exists it's based on the initiator, which won't |
| // always be the parent. |
| return fallback_base_url_; |
| } |
| } |
| |
| // [spec] 3. Return document's URL. |
| return urlForBinding(); |
| } |
| |
| const KURL& Document::BaseURL() const { |
| if (!base_url_.IsNull()) |
| return base_url_; |
| return BlankURL(); |
| } |
| |
| void Document::SetBaseURLOverride(const KURL& url) { |
| base_url_override_ = url; |
| UpdateBaseURL(); |
| } |
| |
| void Document::ProcessBaseElement() { |
| UseCounter::Count(*this, WebFeature::kBaseElement); |
| |
| // Find the first href attribute in a base element and the first target |
| // attribute in a base element. |
| const AtomicString* href = nullptr; |
| const AtomicString* target = nullptr; |
| for (HTMLBaseElement* base = Traversal<HTMLBaseElement>::FirstWithin(*this); |
| base && (!href || !target); |
| base = Traversal<HTMLBaseElement>::Next(*base)) { |
| if (!href) { |
| const AtomicString& value = base->FastGetAttribute(html_names::kHrefAttr); |
| if (!value.IsNull()) |
| href = &value; |
| } |
| if (!target) { |
| const AtomicString& value = |
| base->FastGetAttribute(html_names::kTargetAttr); |
| if (!value.IsNull()) |
| target = &value; |
| } |
| if (GetExecutionContext() && |
| GetExecutionContext()->GetContentSecurityPolicy()->IsActive()) { |
| UseCounter::Count(*this, |
| WebFeature::kContentSecurityPolicyWithBaseElement); |
| } |
| } |
| |
| // FIXME: Since this doesn't share code with completeURL it may not handle |
| // encodings correctly. |
| KURL base_element_url; |
| if (href) { |
| String stripped_href = StripLeadingAndTrailingHTMLSpaces(*href); |
| if (!stripped_href.empty()) |
| base_element_url = KURL(FallbackBaseURL(), stripped_href); |
| } |
| |
| if (!base_element_url.IsEmpty()) { |
| if (base_element_url.ProtocolIsData() || |
| base_element_url.ProtocolIsJavaScript()) { |
| UseCounter::Count(*this, WebFeature::kBaseWithDataHref); |
| AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| ConsoleMessage::Source::kSecurity, ConsoleMessage::Level::kError, |
| "'" + base_element_url.Protocol() + |
| "' URLs may not be used as base URLs for a document.")); |
| } |
| if (GetExecutionContext() && |
| !GetExecutionContext()->GetSecurityOrigin()->CanRequest( |
| base_element_url)) { |
| UseCounter::Count(*this, WebFeature::kBaseWithCrossOriginHref); |
| } |
| } |
| |
| if (base_element_url != base_element_url_ && |
| !base_element_url.ProtocolIsData() && |
| !base_element_url.ProtocolIsJavaScript() && GetExecutionContext() && |
| GetExecutionContext()->GetContentSecurityPolicy()->AllowBaseURI( |
| base_element_url)) { |
| base_element_url_ = base_element_url; |
| UpdateBaseURL(); |
| } |
| |
| AtomicString old_base_target = base_target_; |
| if (target) { |
| if (target->Contains('\n') || target->Contains('\r')) |
| UseCounter::Count(*this, WebFeature::kBaseWithNewlinesInTarget); |
| if (target->Contains('<')) |
| UseCounter::Count(*this, WebFeature::kBaseWithOpenBracketInTarget); |
| base_target_ = *target; |
| } else { |
| base_target_ = g_null_atom; |
| } |
| if (old_base_target != base_target_) { |
| if (auto* document_rules = DocumentSpeculationRules::FromIfExists(*this)) { |
| document_rules->DocumentBaseTargetChanged(); |
| } |
| } |
| } |
| |
| void Document::DidAddPendingParserBlockingStylesheet() { |
| if (ScriptableDocumentParser* parser = GetScriptableDocumentParser()) |
| parser->DidAddPendingParserBlockingStylesheet(); |
| } |
| |
| void Document::DidRemoveAllPendingStylesheets() { |
| DidLoadAllScriptBlockingResources(); |
| } |
| |
| void Document::DidLoadAllPendingParserBlockingStylesheets() { |
| if (ScriptableDocumentParser* parser = GetScriptableDocumentParser()) |
| parser->DidLoadAllPendingParserBlockingStylesheets(); |
| } |
| |
| void Document::DidLoadAllScriptBlockingResources() { |
| // Use wrapWeakPersistent because the task should not keep this Document alive |
| // just for executing scripts. |
| execute_scripts_waiting_for_resources_task_handle_ = PostCancellableTask( |
| *GetTaskRunner(TaskType::kNetworking), FROM_HERE, |
| WTF::BindOnce(&Document::ExecuteScriptsWaitingForResources, |
| WrapWeakPersistent(this))); |
| |
| if (IsA<HTMLDocument>(this) && body()) { |
| // For HTML if we have no more stylesheets to load and we're past the body |
| // tag, we should have something to paint so resume. |
| BeginLifecycleUpdatesIfRenderingReady(); |
| } else if (!IsA<HTMLDocument>(this) && documentElement()) { |
| // For non-HTML there is no body so resume as soon as the sheets are loaded. |
| BeginLifecycleUpdatesIfRenderingReady(); |
| } |
| } |
| |
| void Document::ExecuteScriptsWaitingForResources() { |
| if (!IsScriptExecutionReady()) |
| return; |
| if (ScriptableDocumentParser* parser = GetScriptableDocumentParser()) |
| parser->ExecuteScriptsWaitingForResources(); |
| } |
| |
| CSSStyleSheet& Document::ElementSheet() { |
| if (!elem_sheet_) |
| elem_sheet_ = CSSStyleSheet::CreateInline(*this, base_url_); |
| return *elem_sheet_; |
| } |
| |
| bool Document::InvalidationDisallowed() const { |
| return View() && View()->InvalidationDisallowed(); |
| } |
| |
| void Document::MaybeHandleHttpRefresh(const String& content, |
| HttpRefreshType http_refresh_type) { |
| if (is_view_source_ || !dom_window_) |
| return; |
| |
| base::TimeDelta delay; |
| String refresh_url_string; |
| if (!ParseHTTPRefresh(content, |
| http_refresh_type == kHttpRefreshFromMetaTag |
| ? IsHTMLSpace<UChar> |
| : nullptr, |
| delay, refresh_url_string)) |
| return; |
| KURL refresh_url = |
| refresh_url_string.empty() ? Url() : CompleteURL(refresh_url_string); |
| |
| if (refresh_url.ProtocolIsJavaScript()) { |
| String message = |
| "Refused to refresh " + url_.ElidedString() + " to a javascript: URL"; |
| AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| ConsoleMessage::Source::kSecurity, ConsoleMessage::Level::kError, |
| message)); |
| return; |
| } |
| |
| if (http_refresh_type == kHttpRefreshFromMetaTag && |
| dom_window_->IsSandboxed( |
| network::mojom::blink::WebSandboxFlags::kAutomaticFeatures)) { |
| String message = |
| "Refused to execute the redirect specified via '<meta " |
| "http-equiv='refresh' content='...'>'. The document is sandboxed, and " |
| "the 'allow-scripts' keyword is not set."; |
| AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| ConsoleMessage::Source::kSecurity, ConsoleMessage::Level::kError, |
| message)); |
| return; |
| } |
| |
| // Monitor blocking refresh usage when scripting is disabled. |
| // See https://crbug.com/63107 |
| if (!dom_window_->CanExecuteScripts(kNotAboutToExecuteScript)) |
| UseCounter::Count(this, WebFeature::kHttpRefreshWhenScriptingDisabled); |
| |
| if (http_refresh_type == kHttpRefreshFromHeader) { |
| UseCounter::Count(this, WebFeature::kRefreshHeader); |
| } |
| http_refresh_scheduler_->Schedule(delay, refresh_url, http_refresh_type); |
| } |
| |
| bool Document::IsHttpRefreshScheduledWithin(base::TimeDelta interval) { |
| return http_refresh_scheduler_->IsScheduledWithin(interval); |
| } |
| |
| bool Document::HasDocumentPictureInPictureWindow() const { |
| return PictureInPictureController::GetDocumentPictureInPictureWindow(*this); |
| } |
| |
| network::mojom::ReferrerPolicy Document::GetReferrerPolicy() const { |
| return GetExecutionContext() ? GetExecutionContext()->GetReferrerPolicy() |
| : network::mojom::ReferrerPolicy::kDefault; |
| } |
| |
| MouseEventWithHitTestResults Document::PerformMouseEventHitTest( |
| const HitTestRequest& request, |
| const PhysicalOffset& document_point, |
| const WebMouseEvent& event) { |
| DCHECK(!GetLayoutView() || IsA<LayoutView>(GetLayoutView())); |
| |
| // LayoutView::hitTest causes a layout, and we don't want to hit that until |
| // the first layout because until then, there is nothing shown on the screen - |
| // the user can't have intentionally clicked on something belonging to this |
| // page. Furthermore, mousemove events before the first layout should not |
| // lead to a premature layout() happening, which could show a flash of white. |
| // See also the similar code in EventHandler::hitTestResultAtPoint. |
| if (!GetLayoutView() || !View() || !View()->DidFirstLayout()) { |
| HitTestLocation location((PhysicalOffset())); |
| return MouseEventWithHitTestResults(event, location, |
| HitTestResult(request, location)); |
| } |
| |
| HitTestLocation location(document_point); |
| HitTestResult result(request, location); |
| GetLayoutView()->HitTest(location, result); |
| |
| if (!request.ReadOnly()) { |
| UpdateHoverActiveState(request.Active(), !request.Move(), |
| result.InnerElement()); |
| } |
| |
| return MouseEventWithHitTestResults(event, location, result); |
| } |
| |
| // DOM Section 1.1.1 |
| bool Document::ChildTypeAllowed(NodeType type) const { |
| switch (type) { |
| case kAttributeNode: |
| case kCdataSectionNode: |
| case kDocumentFragmentNode: |
| case kDocumentNode: |
| case kTextNode: |
| return false; |
| case kCommentNode: |
| case kProcessingInstructionNode: |
| return true; |
| case kDocumentTypeNode: |
| case kElementNode: |
| // Documents may contain no more than one of each of these. |
| // (One Element and one DocumentType.) |
| for (Node& c : NodeTraversal::ChildrenOf(*this)) { |
| if (c.getNodeType() == type) |
| return false; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| // This is an implementation of step 6 of |
| // https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity |
| // and https://dom.spec.whatwg.org/#concept-node-replace . |
| // |
| // 6. If parent is a document, and any of the statements below, switched on |
| // node, are true, throw a HierarchyRequestError. |
| // -> DocumentFragment node |
| // If node has more than one element child or has a Text node child. |
| // Otherwise, if node has one element child and either parent has an element |
| // child, child is a doctype, or child is not null and a doctype is |
| // following child. |
| // -> element |
| // parent has an element child, child is a doctype, or child is not null and |
| // a doctype is following child. |
| // -> doctype |
| // parent has a doctype child, child is non-null and an element is preceding |
| // child, or child is null and parent has an element child. |
| // |
| // 6. If parent is a document, and any of the statements below, switched on |
| // node, are true, throw a HierarchyRequestError. |
| // -> DocumentFragment node |
| // If node has more than one element child or has a Text node child. |
| // Otherwise, if node has one element child and either parent has an element |
| // child that is not child or a doctype is following child. |
| // -> element |
| // parent has an element child that is not child or a doctype is following |
| // child. |
| // -> doctype |
| // parent has a doctype child that is not child, or an element is preceding |
| // child. |
| bool Document::CanAcceptChild(const Node& new_child, |
| const Node* next, |
| const Node* old_child, |
| ExceptionState& exception_state) const { |
| DCHECK(!(next && old_child)); |
| if (old_child && old_child->getNodeType() == new_child.getNodeType()) |
| return true; |
| |
| int num_doctypes = 0; |
| int num_elements = 0; |
| bool has_doctype_after_reference_node = false; |
| bool has_element_after_reference_node = false; |
| |
| // First, check how many doctypes and elements we have, not counting |
| // the child we're about to remove. |
| bool saw_reference_node = false; |
| for (Node& child : NodeTraversal::ChildrenOf(*this)) { |
| if (old_child && *old_child == child) { |
| saw_reference_node = true; |
| continue; |
| } |
| if (&child == next) |
| saw_reference_node = true; |
| |
| switch (child.getNodeType()) { |
| case kDocumentTypeNode: |
| num_doctypes++; |
| has_doctype_after_reference_node = saw_reference_node; |
| break; |
| case kElementNode: |
| num_elements++; |
| has_element_after_reference_node = saw_reference_node; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| // Then, see how many doctypes and elements might be added by the new child. |
| if (auto* new_child_fragment = DynamicTo<DocumentFragment>(new_child)) { |
| for (Node& child : NodeTraversal::ChildrenOf(*new_child_fragment)) { |
| switch (child.getNodeType()) { |
| case kAttributeNode: |
| case kCdataSectionNode: |
| case kDocumentFragmentNode: |
| case kDocumentNode: |
| case kTextNode: |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kHierarchyRequestError, |
| "Nodes of type '" + new_child.nodeName() + |
| "' may not be inserted inside nodes of type '#document'."); |
| return false; |
| case kCommentNode: |
| case kProcessingInstructionNode: |
| break; |
| case kDocumentTypeNode: |
| num_doctypes++; |
| break; |
| case kElementNode: |
| num_elements++; |
| if (has_doctype_after_reference_node) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kHierarchyRequestError, |
| "Can't insert an element before a doctype."); |
| return false; |
| } |
| break; |
| } |
| } |
| } else { |
| switch (new_child.getNodeType()) { |
| case kAttributeNode: |
| case kCdataSectionNode: |
| case kDocumentFragmentNode: |
| case kDocumentNode: |
| case kTextNode: |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kHierarchyRequestError, |
| "Nodes of type '" + new_child.nodeName() + |
| "' may not be inserted inside nodes of type '#document'."); |
| return false; |
| case kCommentNode: |
| case kProcessingInstructionNode: |
| return true; |
| case kDocumentTypeNode: |
| num_doctypes++; |
| if (num_elements > 0 && !has_element_after_reference_node) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kHierarchyRequestError, |
| "Can't insert a doctype before the root element."); |
| return false; |
| } |
| break; |
| case kElementNode: |
| num_elements++; |
| if (has_doctype_after_reference_node) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kHierarchyRequestError, |
| "Can't insert an element before a doctype."); |
| return false; |
| } |
| break; |
| } |
| } |
| |
| if (num_elements > 1 || num_doctypes > 1) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kHierarchyRequestError, |
| String::Format("Only one %s on document allowed.", |
| num_elements > 1 ? "element" : "doctype")); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| Node* Document::Clone(Document& factory, |
| NodeCloningData& data, |
| ContainerNode* append_to, |
| ExceptionState& append_exception_state) const { |
| DCHECK_EQ(this, &factory) |
| << "Document::Clone() doesn't support importNode mode."; |
| DCHECK_EQ(append_to, nullptr) |
| << "Document::Clone() doesn't support append_to"; |
| if (!execution_context_) |
| return nullptr; |
| Document* clone = CloneDocumentWithoutChildren(); |
| DocumentPartRoot* part_root = nullptr; |
| if (data.Has(CloneOption::kPreserveDOMParts)) { |
| DCHECK(RuntimeEnabledFeatures::DOMPartsAPIEnabled()); |
| part_root = &clone->getPartRoot(); |
| data.PushPartRoot(*part_root); |
| } |
| clone->CloneDataFromDocument(*this); |
| PartRoot::CloneParts(*this, *clone, data); |
| if (data.Has(CloneOption::kIncludeDescendants)) { |
| clone->CloneChildNodesFrom(*this, data); |
| } |
| DCHECK(!part_root || &data.CurrentPartRoot() == part_root); |
| return clone; |
| } |
| |
| ResizeObserver& Document::EnsureResizeObserver() { |
| if (!intrinsic_size_observer_) { |
| intrinsic_size_observer_ = ResizeObserver::Create( |
| domWindow(), |
| MakeGarbageCollected<IntrinsicSizeResizeObserverDelegate>()); |
| } |
| return *intrinsic_size_observer_; |
| } |
| |
| void Document::ObserveForIntrinsicSize(Element* element) { |
| // Defaults to content-box, which is what we want. |
| EnsureResizeObserver().observe(element); |
| } |
| |
| void Document::UnobserveForIntrinsicSize(Element* element) { |
| if (intrinsic_size_observer_) |
| intrinsic_size_observer_->unobserve(element); |
| } |
| |
| class LazyLoadedAutoSizedImgResizeObserverDelegate final |
| : public ResizeObserver::Delegate { |
| void OnResize(const HeapVector<Member<ResizeObserverEntry>>& entries) final { |
| for (const auto& entry : entries) { |
| DCHECK(entry->contentRect()); |
| if (auto* img = DynamicTo<HTMLImageElement>(entry->target())) { |
| img->OnResize(); |
| } |
| } |
| } |
| |
| ResizeObserver::DeliveryTime Delivery() const final { |
| return ResizeObserver::DeliveryTime::kBeforeOthers; |
| } |
| }; |
| |
| ResizeObserver& Document::GetLazyLoadedAutoSizedImgObserver() { |
| if (!lazy_loaded_auto_sized_img_observer_) { |
| lazy_loaded_auto_sized_img_observer_ = ResizeObserver::Create( |
| domWindow(), |
| MakeGarbageCollected<LazyLoadedAutoSizedImgResizeObserverDelegate>()); |
| } |
| |
| return *lazy_loaded_auto_sized_img_observer_; |
| } |
| |
| void Document::ObserveForLazyLoadedAutoSizedImg(HTMLImageElement* img) { |
| GetLazyLoadedAutoSizedImgObserver().observe(img); |
| } |
| |
| void Document::UnobserveForLazyLoadedAutoSizedImg(HTMLImageElement* img) { |
| if (lazy_loaded_auto_sized_img_observer_) { |
| lazy_loaded_auto_sized_img_observer_->unobserve(img); |
| } |
| } |
| |
| Document* Document::CloneDocumentWithoutChildren() const { |
| DocumentInit init = |
| DocumentInit::Create() |
| .WithExecutionContext(execution_context_.Get()) |
| .WithAgent(GetAgent()) |
| .WithURL(Url()) |
| .WithFallbackBaseURL(Url().IsAboutBlankURL() ? fallback_base_url_ |
| : KURL()); |
| if (IsA<XMLDocument>(this)) { |
| if (IsXHTMLDocument()) |
| return XMLDocument::CreateXHTML(init); |
| return MakeGarbageCollected<XMLDocument>(init); |
| } |
| return MakeGarbageCollected<Document>(init); |
| } |
| |
| void Document::CloneDataFromDocument(const Document& other) { |
| SetCompatibilityMode(other.GetCompatibilityMode()); |
| SetEncodingData(other.encoding_data_); |
| SetMimeType(other.contentType()); |
| } |
| |
| void Document::EvaluateMediaQueryListIfNeeded() { |
| if (!evaluate_media_queries_on_style_recalc_) |
| return; |
| EvaluateMediaQueryList(); |
| evaluate_media_queries_on_style_recalc_ = false; |
| } |
| |
| void Document::EvaluateMediaQueryList() { |
| if (media_query_matcher_) |
| media_query_matcher_->MediaFeaturesChanged(); |
| } |
| |
| void Document::LayoutViewportWasResized() { |
| MediaQueryAffectingValueChanged(MediaValueChange::kSize); |
| if (media_query_matcher_) |
| media_query_matcher_->ViewportChanged(); |
| |
| // We need to be careful not to trigger a resize event when setting the |
| // initial layout size. It might seem like the correct check should be |
| // (load_event_progress_ >= kLoadEventInProgress), but that doesn't actually |
| // work because the initial value of load_event_progress_ is |
| // kLoadEventCompleted. DidFirstLayout() is a reliable indicator that the load |
| // event *actually* completed; but we also need to fire a resize event if the |
| // window size changes during load event dispatch. |
| // Note that in the case of the initial empty document, the load may hav |
| // completed before performing the first layout. |
| if (View()->DidFirstLayout() || |
| load_event_progress_ == kLoadEventInProgress || IsLoadCompleted()) { |
| EnqueueResizeEvent(); |
| EnqueueVisualViewportResizeEvent(); |
| if (GetFrame()->IsMainFrame() && !Printing()) |
| probe::DidResizeMainFrame(GetFrame()); |
| } |
| |
| MarkViewportUnitsDirty(); |
| } |
| |
| void Document::MarkViewportUnitsDirty() { |
| if (!HasViewportUnits()) |
| return; |
| GetStyleResolver().SetResizedForViewportUnits(); |
| GetStyleEngine().MarkViewportUnitDirty(ViewportUnitFlag::kStatic); |
| GetStyleEngine().MarkViewportUnitDirty(ViewportUnitFlag::kDynamic); |
| } |
| |
| void Document::DynamicViewportUnitsChanged() { |
| MediaQueryAffectingValueChanged(MediaValueChange::kDynamicViewport); |
| if (media_query_matcher_) |
| media_query_matcher_->DynamicViewportChanged(); |
| if (!HasDynamicViewportUnits()) |
| return; |
| GetStyleResolver().SetResizedForViewportUnits(); |
| GetStyleEngine().MarkViewportUnitDirty(ViewportUnitFlag::kDynamic); |
| } |
| |
| void EmitDidChangeHoverElement(Document& document, Element* new_hover_element) { |
| LocalFrame* local_frame = document.GetFrame(); |
| if (!local_frame) { |
| return; |
| } |
| |
| WebLinkPreviewTriggerer* triggerer = |
| local_frame->GetOrCreateLinkPreviewTriggerer(); |
| if (!triggerer) { |
| return; |
| } |
| |
| WebElement web_element = WebElement(DynamicTo<Element>(new_hover_element)); |
| triggerer->DidChangeHoverElement(web_element); |
| } |
| |
| void Document::SetHoverElement(Element* new_hover_element) { |
| HTMLElement::HoveredElementChanged(hover_element_, new_hover_element); |
| EmitDidChangeHoverElement(*this, new_hover_element); |
| |
| hover_element_ = new_hover_element; |
| } |
| |
| void Document::SetActiveElement(Element* new_active_element) { |
| if (!new_active_element) { |
| active_element_.Clear(); |
| return; |
| } |
| |
| active_element_ = new_active_element; |
| } |
| |
| void Document::RemoveFocusedElementOfSubtree(Node& node, |
| bool among_children_only) { |
| if (!node.isConnected() || !focused_element_ || |
| !node.IsShadowIncludingInclusiveAncestorOf(*focused_element_)) { |
| return; |
| } |
| const auto& focused_element = *node.GetTreeScope().AdjustedFocusedElement(); |
| if (focused_element.IsDescendantOf(&node) || |
| (!among_children_only && node == focused_element)) { |
| bool omit_blur_events = |
| RuntimeEnabledFeatures::OmitBlurEventOnElementRemovalEnabled(); |
| ClearFocusedElement(omit_blur_events); |
| } |
| } |
| |
| static Element* SkipDisplayNoneAncestors(Element* element) { |
| for (; element; element = FlatTreeTraversal::ParentElement(*element)) { |
| if (element->GetLayoutObject() || element->HasDisplayContentsStyle()) |
| return element; |
| } |
| return nullptr; |
| } |
| |
| static Element* SkipDisplayNoneAncestorsOrReturnNullIfFlatTreeIsDirty( |
| Element& element) { |
| if (element.GetDocument().IsSlotAssignmentDirty()) { |
| // We shouldn't use FlatTreeTraversal during detach if slot assignment is |
| // dirty because it might trigger assignment recalc. The hover and active |
| // elements are then set to null. The hover element is updated on the next |
| // lifecycle update instead. |
| // |
| // TODO(crbug.com/939769): The active element is not updated on the next |
| // lifecycle update, and is generally not correctly updated on re-slotting. |
| return nullptr; |
| } |
| return SkipDisplayNoneAncestors(&element); |
| } |
| |
| void Document::HoveredElementDetached(Element& element) { |
| if (!hover_element_) |
| return; |
| if (element != hover_element_) |
| return; |
| hover_element_ = |
| SkipDisplayNoneAncestorsOrReturnNullIfFlatTreeIsDirty(element); |
| |
| // If the mouse cursor is not visible, do not clear existing |
| // hover effects on the ancestors of |element| and do not invoke |
| // new hover effects on any other element. |
| if (!GetPage()->IsCursorVisible()) |
| return; |
| |
| if (GetFrame()) |
| GetFrame()->GetEventHandler().ScheduleHoverStateUpdate(); |
| } |
| |
| void Document::ActiveChainNodeDetached(Element& element) { |
| if (active_element_ && element == active_element_) { |
| active_element_ = |
| SkipDisplayNoneAncestorsOrReturnNullIfFlatTreeIsDirty(element); |
| } |
| } |
| |
| const Vector<DraggableRegionValue>& Document::DraggableRegions() const { |
| return draggable_regions_; |
| } |
| |
| void Document::SetDraggableRegions( |
| const Vector<DraggableRegionValue>& regions) { |
| draggable_regions_ = regions; |
| SetDraggableRegionsDirty(false); |
| } |
| |
| void Document::SetLastFocusType(mojom::blink::FocusType last_focus_type) { |
| last_focus_type_ = last_focus_type; |
| } |
| |
| bool Document::SetFocusedElement(Element* new_focused_element, |
| const FocusParams& params) { |
| DCHECK(!lifecycle_.InDetach()); |
| |
| clear_focused_element_timer_.Stop(); |
| |
| // Make sure new_focused_element is actually in this document. |
| if (new_focused_element) { |
| if (new_focused_element->GetDocument() != this) |
| return true; |
| |
| if (NodeChildRemovalTracker::IsBeingRemoved(*new_focused_element)) |
| return true; |
| } |
| |
| if (focused_element_ == new_focused_element) |
| return true; |
| |
| bool focus_change_blocked = false; |
| Element* old_focused_element = focused_element_; |
| focused_element_ = nullptr; |
| |
| Element* ancestor = |
| (old_focused_element && old_focused_element->isConnected() && |
| new_focused_element) |
| ? DynamicTo<Element>(FlatTreeTraversal::CommonAncestor( |
| *old_focused_element, *new_focused_element)) |
| : nullptr; |
| |
| // Remove focus from the existing focus node (if any) |
| if (old_focused_element) { |
| old_focused_element->SetFocused(false, params.type); |
| old_focused_element->SetHasFocusWithinUpToAncestor(false, ancestor, true); |
| |
| DisplayLockUtilities::ElementLostFocus(old_focused_element); |
| |
| // Dispatch the blur event and let the node do any other blur related |
| // activities (important for text fields) |
| // If page lost focus, blur event will have already been dispatched |
| if (!params.omit_blur_events && GetPage() && |
| (GetPage()->GetFocusController().IsFocused())) { |
| old_focused_element->DispatchBlurEvent(new_focused_element, params.type, |
| params.source_capabilities); |
| if (focused_element_) { |
| // handler shifted focus |
| focus_change_blocked = true; |
| new_focused_element = nullptr; |
| } |
| |
| // 'focusout' is a DOM level 3 name for the bubbling blur event. |
| old_focused_element->DispatchFocusOutEvent(event_type_names::kFocusout, |
| new_focused_element, |
| params.source_capabilities); |
| // 'DOMFocusOut' is a DOM level 2 name for compatibility. |
| // FIXME: We should remove firing DOMFocusOutEvent event when we are sure |
| // no content depends on it, probably when <rdar://problem/8503958> is |
| // resolved. |
| old_focused_element->DispatchFocusOutEvent(event_type_names::kDOMFocusOut, |
| new_focused_element, |
| params.source_capabilities); |
| |
| if (focused_element_) { |
| // handler shifted focus |
| focus_change_blocked = true; |
| new_focused_element = nullptr; |
| } |
| } |
| } |
| |
| // Blur/focusout handlers could have moved the new element out of this |
| // document. See crbug.com/1204223. |
| if (new_focused_element && new_focused_element->GetDocument() != this) |
| return true; |
| |
| if (new_focused_element) { |
| UpdateStyleAndLayoutTreeForElement(new_focused_element, |
| DocumentUpdateReason::kFocus); |
| } |
| |
| if (new_focused_element && new_focused_element->IsFocusable()) { |
| if (IsRootEditableElement(*new_focused_element) && |
| !AcceptsEditingFocus(*new_focused_element)) { |
| // delegate blocks focus change |
| UpdateStyleAndLayoutTree(); |
| if (LocalFrame* frame = GetFrame()) |
| frame->Selection().DidChangeFocus(); |
| return false; |
| } |
| // Set focus on the new node |
| focused_element_ = new_focused_element; |
| SetSequentialFocusNavigationStartingPoint(focused_element_.Get()); |
| |
| // Keep track of last focus from user interaction, ignoring focus from code |
| // and other non-user internal interventions. |
| if (params.type != mojom::blink::FocusType::kNone && |
| params.type != mojom::blink::FocusType::kScript) |
| SetLastFocusType(params.type); |
| |
| for (auto& observer : focused_element_change_observers_) |
| observer->DidChangeFocus(); |
| |
| focused_element_->SetFocused(true, params.type); |
| // Setting focus can cause the element to become detached (e.g. if an |
| // ancestor element's onblur removes it), so return early here if that's |
| // happened. |
| if (focused_element_ == nullptr) { |
| return false; |
| } |
| focused_element_->SetHasFocusWithinUpToAncestor(true, ancestor, true); |
| DisplayLockUtilities::ElementGainedFocus(focused_element_.Get()); |
| |
| // Element::setFocused for frames can dispatch events. |
| if (focused_element_ != new_focused_element) { |
| UpdateStyleAndLayoutTree(); |
| if (LocalFrame* frame = GetFrame()) |
| frame->Selection().DidChangeFocus(); |
| return false; |
| } |
| SetShouldUpdateSelectionAfterLayout(false); |
| EnsurePaintLocationDataValidForNode(focused_element_, |
| DocumentUpdateReason::kFocus); |
| focused_element_->UpdateSelectionOnFocus(params.selection_behavior, |
| params.options); |
| |
| // Dispatch the focus event and let the node do any other focus related |
| // activities (important for text fields) |
| // If page lost focus, event will be dispatched on page focus, don't |
| // duplicate |
| if (GetPage() && (GetPage()->GetFocusController().IsFocused())) { |
| focused_element_->DispatchFocusEvent(old_focused_element, params.type, |
| params.source_capabilities); |
| |
| if (focused_element_ != new_focused_element) { |
| // handler shifted focus |
| UpdateStyleAndLayoutTree(); |
| if (LocalFrame* frame = GetFrame()) |
| frame->Selection().DidChangeFocus(); |
| return false; |
| } |
| // DOM level 3 bubbling focus event. |
| focused_element_->DispatchFocusInEvent(event_type_names::kFocusin, |
| old_focused_element, params.type, |
| params.source_capabilities); |
| |
| if (focused_element_ != new_focused_element) { |
| // handler shifted focus |
| UpdateStyleAndLayoutTree(); |
| if (LocalFrame* frame = GetFrame()) |
| frame->Selection().DidChangeFocus(); |
| return false; |
| } |
| |
| // For DOM level 2 compatibility. |
| // FIXME: We should remove firing DOMFocusInEvent event when we are sure |
| // no content depends on it, probably when <rdar://problem/8503958> is m. |
| focused_element_->DispatchFocusInEvent(event_type_names::kDOMFocusIn, |
| old_focused_element, params.type, |
| params.source_capabilities); |
| |
| if (focused_element_ != new_focused_element) { |
| // handler shifted focus |
| UpdateStyleAndLayoutTree(); |
| if (LocalFrame* frame = GetFrame()) |
| frame->Selection().DidChangeFocus(); |
| return false; |
| } |
| } |
| } |
| |
| if (!focus_change_blocked) { |
| NotifyFocusedElementChanged(old_focused_element, focused_element_.Get(), |
| params.type); |
| } |
| |
| UpdateStyleAndLayoutTree(); |
| if (LocalFrame* frame = GetFrame()) |
| frame->Selection().DidChangeFocus(); |
| return !focus_change_blocked; |
| } |
| |
| void Document::ClearFocusedElement(bool omit_blur_events) { |
| FocusParams params(SelectionBehaviorOnFocus::kNone, |
| mojom::blink::FocusType::kNone, nullptr); |
| params.omit_blur_events = omit_blur_events; |
| SetFocusedElement(nullptr, params); |
| } |
| |
| void Document::SendFocusNotification(Element* new_focused_element, |
| mojom::blink::FocusType focus_type) { |
| if (!GetPage()) |
| return; |
| |
| bool is_editable = false; |
| bool is_richly_editable = false; |
| gfx::Rect element_bounds_in_dips; |
| if (new_focused_element) { |
| auto* text_control = ToTextControlOrNull(new_focused_element); |
| is_editable = |
| IsEditable(*new_focused_element) || |
| (text_control && !text_control->IsDisabledOrReadOnly()) || |
| EqualIgnoringASCIICase( |
| new_focused_element->FastGetAttribute(html_names::kRoleAttr), |
| "textbox"); |
| is_richly_editable = IsRichlyEditable(*new_focused_element); |
| gfx::Rect bounds_in_viewport; |
| |
| if (new_focused_element->IsSVGElement()) { |
| // Convert to window coordinate system (this will be in DIPs). |
| bounds_in_viewport = new_focused_element->BoundsInWidget(); |
| } else { |
| Vector<gfx::Rect> outline_rects = |
| new_focused_element->OutlineRectsInWidget( |
| DocumentUpdateReason::kFocus); |
| for (auto& outline_rect : outline_rects) |
| bounds_in_viewport.Union(outline_rect); |
| } |
| |
| if (GetFrame()->GetWidgetForLocalRoot()) { |
| element_bounds_in_dips = |
| GetFrame()->GetWidgetForLocalRoot()->BlinkSpaceToEnclosedDIPs( |
| bounds_in_viewport); |
| } else { |
| element_bounds_in_dips = bounds_in_viewport; |
| } |
| } |
| |
| GetFrame()->GetLocalFrameHostRemote().FocusedElementChanged( |
| is_editable, is_richly_editable, element_bounds_in_dips, focus_type); |
| } |
| |
| void Document::NotifyFocusedElementChanged(Element* old_focused_element, |
| Element* new_focused_element, |
| mojom::blink::FocusType focus_type) { |
| // |old_focused_element| may not belong to this document by invoking |
| // adoptNode in event handlers during moving the focus to the new element. |
| DCHECK(!new_focused_element || new_focused_element->GetDocument() == this); |
| |
| if (AXObjectCache* cache = ExistingAXObjectCache()) { |
| cache->HandleFocusedUIElementChanged(old_focused_element, |
| new_focused_element); |
| } |
| |
| if (GetPage()) { |
| GetPage()->GetValidationMessageClient().DidChangeFocusTo( |
| new_focused_element); |
| |
| SendFocusNotification(new_focused_element, focus_type); |
| |
| Document* old_document = |
| old_focused_element ? &old_focused_element->GetDocument() : nullptr; |
| if (old_document && old_document != this && old_document->GetFrame()) |
| old_document->GetFrame()->Client()->FocusedElementChanged(nullptr); |
| |
| // Ensures that further text input state can be sent even when previously |
| // focused input and the newly focused input share the exact same state. |
| if (GetFrame()->GetWidgetForLocalRoot()) |
| GetFrame()->GetWidgetForLocalRoot()->ClearTextInputState(); |
| GetFrame()->Client()->FocusedElementChanged(new_focused_element); |
| |
| GetPage()->GetChromeClient().SetKeyboardFocusURL(new_focused_element); |
| } |
| |
| blink::NotifyPriorityScrollAnchorStatusChanged(old_focused_element, |
| new_focused_element); |
| } |
| |
| // This forwards to the TreeScope implementation. |
| void Document::OnAdoptedStyleSheetSet( |
| ScriptState* script_state, |
| V8ObservableArrayCSSStyleSheet& observable_array, |
| uint32_t index, |
| Member<CSSStyleSheet>& sheet, |
| ExceptionState& exception_state) { |
| TreeScope::OnAdoptedStyleSheetSet(script_state, observable_array, index, |
| sheet, exception_state); |
| } |
| |
| // This forwards to the TreeScope implementation. |
| void Document::OnAdoptedStyleSheetDelete( |
| ScriptState* script_state, |
| V8ObservableArrayCSSStyleSheet& observable_array, |
| uint32_t index, |
| ExceptionState& exception_state) { |
| TreeScope::OnAdoptedStyleSheetDelete(script_state, observable_array, index, |
| exception_state); |
| } |
| |
| void Document::SetSequentialFocusNavigationStartingPoint(Node* node) { |
| if (!dom_window_) |
| return; |
| if (!node || node->GetDocument() != this) { |
| sequential_focus_navigation_starting_point_ = nullptr; |
| return; |
| } |
| if (!sequential_focus_navigation_starting_point_) |
| sequential_focus_navigation_starting_point_ = Range::Create(*this); |
| sequential_focus_navigation_starting_point_->selectNodeContents( |
| node, ASSERT_NO_EXCEPTION); |
| } |
| |
| Element* Document::SequentialFocusNavigationStartingPoint( |
| mojom::blink::FocusType type) const { |
| if (focused_element_) |
| return focused_element_.Get(); |
| if (!sequential_focus_navigation_starting_point_) |
| return nullptr; |
| DCHECK(sequential_focus_navigation_starting_point_->IsConnected()); |
| if (!sequential_focus_navigation_starting_point_->collapsed()) { |
| Node* node = sequential_focus_navigation_starting_point_->startContainer(); |
| DCHECK_EQ(node, |
| sequential_focus_navigation_starting_point_->endContainer()); |
| if (auto* element = DynamicTo<Element>(node)) |
| return element; |
| if (Element* neighbor_element = type == mojom::blink::FocusType::kForward |
| ? ElementTraversal::Previous(*node) |
| : ElementTraversal::Next(*node)) |
| return neighbor_element; |
| return node->ParentOrShadowHostElement(); |
| } |
| |
| // Range::selectNodeContents didn't select contents because the element had |
| // no children. |
| auto* element = DynamicTo<Element>( |
| sequential_focus_navigation_starting_point_->startContainer()); |
| if (element && !element->hasChildren() && |
| sequential_focus_navigation_starting_point_->startOffset() == 0) |
| return element; |
| |
| // A node selected by Range::selectNodeContents was removed from the |
| // document tree. |
| if (Node* next_node = |
| sequential_focus_navigation_starting_point_->FirstNode()) { |
| if (next_node->IsShadowRoot()) |
| return next_node->OwnerShadowHost(); |
| // TODO(tkent): Using FlatTreeTraversal is inconsistent with |
| // FocusController. Ideally we should find backward/forward focusable |
| // elements before the starting point is disconnected. crbug.com/606582 |
| if (type == mojom::blink::FocusType::kForward) { |
| Node* previous = FlatTreeTraversal::Previous(*next_node); |
| for (; previous; previous = FlatTreeTraversal::Previous(*previous)) { |
| if (auto* previous_element = DynamicTo<Element>(previous)) |
| return previous_element; |
| } |
| } |
| for (Node* next = next_node; next; next = FlatTreeTraversal::Next(*next)) { |
| if (auto* next_element = DynamicTo<Element>(next)) |
| return next_element; |
| } |
| } |
| return nullptr; |
| } |
| |
| void Document::SetSelectorFragmentAnchorCSSTarget(Element* new_target) { |
| SetCSSTarget(new_target); |
| if (css_target_) { |
| css_target_is_selector_fragment_ = true; |
| css_target_->PseudoStateChanged(CSSSelector::kPseudoSelectorFragmentAnchor); |
| } |
| } |
| |
| void Document::SetCSSTarget(Element* new_target) { |
| if (css_target_) { |
| css_target_->PseudoStateChanged(CSSSelector::kPseudoTarget); |
| if (css_target_is_selector_fragment_) { |
| css_target_->PseudoStateChanged( |
| CSSSelector::kPseudoSelectorFragmentAnchor); |
| } |
| css_target_->ClearTargetedSnapAreaIdsForSnapContainers(); |
| } |
| css_target_ = new_target; |
| css_target_is_selector_fragment_ = false; |
| if (css_target_) { |
| css_target_->PseudoStateChanged(CSSSelector::kPseudoTarget); |
| css_target_->SetTargetedSnapAreaIdsForSnapContainers(); |
| } |
| } |
| |
| void Document::RegisterNodeList(const LiveNodeListBase* list) { |
| node_lists_.Add(list, list->InvalidationType()); |
| if (list->IsRootedAtTreeScope()) |
| lists_invalidated_at_document_.insert(list); |
| } |
| |
| void Document::UnregisterNodeList(const LiveNodeListBase* list) { |
| node_lists_.Remove(list, list->InvalidationType()); |
| if (list->IsRootedAtTreeScope()) { |
| DCHECK(lists_invalidated_at_document_.Contains(list)); |
| lists_invalidated_at_document_.erase(list); |
| } |
| } |
| |
| void Document::RegisterNodeListWithIdNameCache(const LiveNodeListBase* list) { |
| node_lists_.Add(list, kInvalidateOnIdNameAttrChange); |
| } |
| |
| void Document::UnregisterNodeListWithIdNameCache(const LiveNodeListBase* list) { |
| node_lists_.Remove(list, kInvalidateOnIdNameAttrChange); |
| } |
| |
| void Document::AttachNodeIterator(NodeIterator* ni) { |
| node_iterators_.insert(ni); |
| } |
| |
| void Document::DetachNodeIterator(NodeIterator* ni) { |
| // The node iterator can be detached without having been attached if its root |
| // node didn't have a document when the iterator was created, but has it now. |
| node_iterators_.erase(ni); |
| } |
| |
| void Document::MoveNodeIteratorsToNewDocument(Node& node, |
| Document& new_document) { |
| HeapHashSet<WeakMember<NodeIterator>> node_iterators_list = node_iterators_; |
| for (NodeIterator* ni : node_iterators_list) { |
| if (ni->root() == node) { |
| DetachNodeIterator(ni); |
| new_document.AttachNodeIterator(ni); |
| } |
| } |
| } |
| |
| void Document::DidMoveTreeToNewDocument(const Node& root) { |
| DCHECK_NE(root.GetDocument(), this); |
| if (!ranges_.empty()) { |
| AttachedRangeSet ranges = ranges_; |
| for (Range* range : ranges) |
| range->UpdateOwnerDocumentIfNeeded(); |
| } |
| synchronous_mutation_observer_set_.ForEachObserver( |
| [&](SynchronousMutationObserver* observer) { |
| observer->DidMoveTreeToNewDocument(root); |
| }); |
| } |
| |
| void Document::NodeChildrenWillBeRemoved(ContainerNode& container) { |
| EventDispatchForbiddenScope assert_no_event_dispatch; |
| for (Range* range : ranges_) { |
| range->NodeChildrenWillBeRemoved(container); |
| if (range == sequential_focus_navigation_starting_point_) |
| range->FixupRemovedChildrenAcrossShadowBoundary(container); |
| } |
| |
| for (NodeIterator* ni : node_iterators_) { |
| for (Node& n : NodeTraversal::ChildrenOf(container)) |
| ni->NodeWillBeRemoved(n); |
| } |
| |
| synchronous_mutation_observer_set_.ForEachObserver( |
| [&](SynchronousMutationObserver* observer) { |
| observer->NodeChildrenWillBeRemoved(container); |
| }); |
| |
| if (MayContainShadowRoots()) { |
| for (Node& n : NodeTraversal::ChildrenOf(container)) |
| n.CheckSlotChangeBeforeRemoved(); |
| } |
| } |
| |
| void Document::NodeWillBeRemoved(Node& n) { |
| for (NodeIterator* ni : node_iterators_) |
| ni->NodeWillBeRemoved(n); |
| |
| for (Range* range : ranges_) { |
| range->NodeWillBeRemoved(n); |
| if (range == sequential_focus_navigation_starting_point_) |
| range->FixupRemovedNodeAcrossShadowBoundary(n); |
| } |
| |
| synchronous_mutation_observer_set_.ForEachObserver( |
| [&](SynchronousMutationObserver* observer) { |
| observer->NodeWillBeRemoved(n); |
| }); |
| |
| if (MayContainShadowRoots()) |
| n.CheckSlotChangeBeforeRemoved(); |
| |
| if (n.InActiveDocument()) |
| GetStyleEngine().NodeWillBeRemoved(n); |
| } |
| |
| void Document::NotifyUpdateCharacterData(CharacterData* character_data, |
| const TextDiffRange& diff) { |
| synchronous_mutation_observer_set_.ForEachObserver( |
| [&](SynchronousMutationObserver* observer) { |
| observer->DidUpdateCharacterData(character_data, diff.offset, |
| diff.old_size, diff.new_size); |
| }); |
| } |
| |
| void Document::NotifyChangeChildren( |
| const ContainerNode& container, |
| const ContainerNode::ChildrenChange& change) { |
| if (LocalFrameView* frame_view = View()) { |
| if (FragmentAnchor* anchor = frame_view->GetFragmentAnchor()) { |
| anchor->NewContentMayBeAvailable(); |
| } |
| } |
| |
| synchronous_mutation_observer_set_.ForEachObserver( |
| [&](SynchronousMutationObserver* observer) { |
| observer->DidChangeChildren(container, change); |
| }); |
| } |
| |
| void Document::NotifyAttributeChanged(const Element& element, |
| const QualifiedName& name, |
| const AtomicString& old_value, |
| const AtomicString& new_value) { |
| // There are other attributes (not to mention style changes) that could |
| // potentially make more content available to the fragment anchor but |
| // this is a best effort heuristic, based on commonly seen patterns in the |
| // wild, so isn't meant to be comprehensive. |
| if (name == html_names::kHiddenAttr) { |
| if (LocalFrameView* frame_view = View()) { |
| if (FragmentAnchor* anchor = frame_view->GetFragmentAnchor()) { |
| anchor->NewContentMayBeAvailable(); |
| } |
| } |
| } |
| |
| synchronous_mutation_observer_set_.ForEachObserver( |
| [&](SynchronousMutationObserver* observer) { |
| observer->AttributeChanged(element, name, old_value, new_value); |
| }); |
| } |
| |
| void Document::DidInsertText(const CharacterData& text, |
| unsigned offset, |
| unsigned length) { |
| for (Range* range : ranges_) |
| range->DidInsertText(text, offset, length); |
| } |
| |
| void Document::DidRemoveText(const CharacterData& text, |
| unsigned offset, |
| unsigned length) { |
| for (Range* range : ranges_) |
| range->DidRemoveText(text, offset, length); |
| } |
| |
| void Document::DidMergeTextNodes(const Text& merged_node, |
| const Text& node_to_be_removed, |
| unsigned old_length) { |
| NodeWithIndex node_to_be_removed_with_index( |
| const_cast<Text&>(node_to_be_removed)); |
| if (!ranges_.empty()) { |
| for (Range* range : ranges_) |
| range->DidMergeTextNodes(node_to_be_removed_with_index, old_length); |
| } |
| |
| synchronous_mutation_observer_set_.ForEachObserver( |
| [&](SynchronousMutationObserver* observer) { |
| observer->DidMergeTextNodes(merged_node, node_to_be_removed_with_index, |
| old_length); |
| }); |
| |
| // FIXME: This should update markers for spelling and grammar checking. |
| } |
| |
| void Document::DidSplitTextNode(const Text& old_node) { |
| for (Range* range : ranges_) |
| range->DidSplitTextNode(old_node); |
| |
| synchronous_mutation_observer_set_.ForEachObserver( |
| [&](SynchronousMutationObserver* observer) { |
| observer->DidSplitTextNode(old_node); |
| }); |
| |
| // FIXME: This should update markers for spelling and grammar checking. |
| } |
| |
| void Document::SetWindowAttributeEventListener(const AtomicString& event_type, |
| EventListener* listener) { |
| LocalDOMWindow* dom_window = domWindow(); |
| if (!dom_window) |
| return; |
| dom_window->SetAttributeEventListener(event_type, listener); |
| } |
| |
| EventListener* Document::GetWindowAttributeEventListener( |
| const AtomicString& event_type) { |
| LocalDOMWindow* dom_window = domWindow(); |
| if (!dom_window) |
| return nullptr; |
| return dom_window->GetAttributeEventListener(event_type); |
| } |
| |
| void Document::EnqueueDisplayLockActivationTask(base::OnceClosure task) { |
| scripted_animation_controller_->EnqueueTask(std::move(task)); |
| } |
| |
| void Document::EnqueueAnimationFrameTask(base::OnceClosure task) { |
| scripted_animation_controller_->EnqueueTask(std::move(task)); |
| } |
| |
| void Document::EnqueueAnimationFrameEvent(Event* event) { |
| scripted_animation_controller_->EnqueueEvent(event); |
| } |
| |
| void Document::EnqueueUniqueAnimationFrameEvent(Event* event) { |
| scripted_animation_controller_->EnqueuePerFrameEvent(event); |
| } |
| |
| void Document::EnqueueScrollEventForNode(Node* target) { |
| // Per the W3C CSSOM View Module only scroll events fired at the document |
| // should bubble. |
| overscroll_accumulated_delta_x_ = overscroll_accumulated_delta_y_ = 0; |
| Event* scroll_event = target->IsDocumentNode() |
| ? Event::CreateBubble(event_type_names::kScroll) |
| : Event::Create(event_type_names::kScroll); |
| scroll_event->SetTarget(target); |
| scripted_animation_controller_->EnqueuePerFrameEvent(scroll_event); |
| } |
| |
| void Document::EnqueueScrollEndEventForNode(Node* target) { |
| // Mimic bubbling behavior of scroll event for consistency. |
| overscroll_accumulated_delta_x_ = overscroll_accumulated_delta_y_ = 0; |
| Event* scroll_end_event = |
| target->IsDocumentNode() |
| ? Event::CreateBubble(event_type_names::kScrollend) |
| : Event::Create(event_type_names::kScrollend); |
| scroll_end_event->SetTarget(target); |
| scripted_animation_controller_->EnqueuePerFrameEvent(scroll_end_event); |
| } |
| |
| void Document::EnqueueOverscrollEventForNode(Node* target, |
| double delta_x, |
| double delta_y) { |
| // Mimic bubbling behavior of scroll event for consistency. |
| overscroll_accumulated_delta_x_ += delta_x; |
| overscroll_accumulated_delta_y_ += delta_y; |
| bool bubbles = target->IsDocumentNode(); |
| Event* overscroll_event = OverscrollEvent::Create( |
| event_type_names::kOverscroll, bubbles, overscroll_accumulated_delta_x_, |
| overscroll_accumulated_delta_y_); |
| overscroll_event->SetTarget(target); |
| scripted_animation_controller_->EnqueuePerFrameEvent(overscroll_event); |
| } |
| |
| void Document::EnqueueSnapChangedEvent(Node* target, |
| Member<Node>& block_target, |
| Member<Node>& inline_target) { |
| Event* snapchanged_event = SnapEvent::Create(event_type_names::kSnapchanged, |
| block_target, inline_target); |
| snapchanged_event->SetTarget(target); |
| scripted_animation_controller_->EnqueuePerFrameEvent(snapchanged_event); |
| } |
| |
| void Document::EnqueueSnapChangingEvent(Node* target, |
| Member<Node>& block_target, |
| Member<Node>& inline_target) { |
| Event* snapchanged_event = SnapEvent::Create(event_type_names::kSnapchanging, |
| block_target, inline_target); |
| snapchanged_event->SetTarget(target); |
| scripted_animation_controller_->EnqueuePerFrameEvent(snapchanged_event); |
| } |
| |
| void Document::EnqueueMoveEvent() { |
| CHECK( |
| RuntimeEnabledFeatures::DesktopPWAsAdditionalWindowingControlsEnabled()); |
| |
| Event* event = Event::Create(event_type_names::kMove); |
| event->SetTarget(domWindow()); |
| |
| // TODO(crbug.com/1515101): When launching AWC, requires spec work. |
| scripted_animation_controller_->EnqueuePerFrameEvent(event); |
| } |
| |
| void Document::EnqueueResizeEvent() { |
| Event* event = Event::Create(event_type_names::kResize); |
| event->SetTarget(domWindow()); |
| scripted_animation_controller_->EnqueuePerFrameEvent(event); |
| } |
| |
| void Document::EnqueueMediaQueryChangeListeners( |
| HeapVector<Member<MediaQueryListListener>>& listeners) { |
| scripted_animation_controller_->EnqueueMediaQueryChangeListeners(listeners); |
| } |
| |
| void Document::EnqueueVisualViewportScrollEvent() { |
| VisualViewportScrollEvent* event = |
| MakeGarbageCollected<VisualViewportScrollEvent>(); |
| event->SetTarget(domWindow()->visualViewport()); |
| scripted_animation_controller_->EnqueuePerFrameEvent(event); |
| } |
| |
| void Document::EnqueueVisualViewportScrollEndEvent() { |
| VisualViewportScrollEndEvent* event = |
| MakeGarbageCollected<VisualViewportScrollEndEvent>(); |
| event->SetTarget(domWindow()->visualViewport()); |
| scripted_animation_controller_->EnqueuePerFrameEvent(event); |
| } |
| |
| void Document::EnqueueVisualViewportResizeEvent() { |
| VisualViewportResizeEvent* event = |
| MakeGarbageCollected<VisualViewportResizeEvent>(); |
| event->SetTarget(domWindow()->visualViewport()); |
| scripted_animation_controller_->EnqueuePerFrameEvent(event); |
| } |
| |
| void Document::DispatchEventsForPrinting() { |
| scripted_animation_controller_->DispatchEventsAndCallbacksForPrinting(); |
| } |
| |
| Document::EventFactorySet& Document::EventFactories() { |
| DEFINE_STATIC_LOCAL(EventFactorySet, event_factory, ()); |
| return event_factory; |
| } |
| |
| void Document::RegisterEventFactory( |
| std::unique_ptr<EventFactoryBase> event_factory) { |
| DCHECK(!EventFactories().Contains(event_factory.get())); |
| EventFactories().insert(std::move(event_factory)); |
| } |
| |
| Event* Document::createEvent(ScriptState* script_state, |
| const String& event_type, |
| ExceptionState& exception_state) { |
| Event* event = nullptr; |
| ExecutionContext* execution_context = ExecutionContext::From(script_state); |
| for (const auto& factory : EventFactories()) { |
| event = factory->Create(script_state, execution_context, event_type); |
| if (event) { |
| // createEvent for TouchEvent should throw DOM exception if touch event |
| // feature detection is not enabled. See crbug.com/392584#c22 |
| if (EqualIgnoringASCIICase(event_type, "TouchEvent") && |
| !RuntimeEnabledFeatures::TouchEventFeatureDetectionEnabled( |
| execution_context)) |
| break; |
| return event; |
| } |
| } |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotSupportedError, |
| "The provided event type ('" + event_type + "') is invalid."); |
| return nullptr; |
| } |
| |
| void Document::AddMutationEventListenerTypeIfEnabled( |
| ListenerType listener_type) { |
| // Mutation events can be disabled by the embedder, or via the runtime enabled |
| // feature. |
| if (!SupportsLegacyDOMMutations()) { |
| return; |
| } |
| AddListenerType(listener_type); |
| } |
| |
| bool Document::HasListenerType(ListenerType listener_type) const { |
| DCHECK(!execution_context_ || |
| RuntimeEnabledFeatures::MutationEventsEnabled(execution_context_) || |
| !(listener_types_ & kDOMMutationEventListener)); |
| return (listener_types_ & listener_type); |
| } |
| |
| void Document::AddListenerTypeIfNeeded(const AtomicString& event_type, |
| EventTarget& event_target) { |
| auto info = event_util::IsDOMMutationEventType(event_type); |
| if (info.is_mutation_event) { |
| AddMutationEventListenerTypeIfEnabled(info.listener_type); |
| } else if (event_type == event_type_names::kWebkitAnimationStart || |
| event_type == event_type_names::kAnimationstart) { |
| AddListenerType(kAnimationStartListener); |
| } else if (event_type == event_type_names::kWebkitAnimationEnd || |
| event_type == event_type_names::kAnimationend) { |
| AddListenerType(kAnimationEndListener); |
| } else if (event_type == event_type_names::kWebkitAnimationIteration || |
| event_type == event_type_names::kAnimationiteration) { |
| AddListenerType(kAnimationIterationListener); |
| if (View()) { |
| // Need to re-evaluate time-to-effect-change for any running animations. |
| View()->ScheduleAnimation(); |
| } |
| } else if (event_type == event_type_names::kAnimationcancel) { |
| AddListenerType(kAnimationCancelListener); |
| } else if (event_type == event_type_names::kTransitioncancel) { |
| AddListenerType(kTransitionCancelListener); |
| } else if (event_type == event_type_names::kTransitionrun) { |
| AddListenerType(kTransitionRunListener); |
| } else if (event_type == event_type_names::kTransitionstart) { |
| AddListenerType(kTransitionStartListener); |
| } else if (event_type == event_type_names::kWebkitTransitionEnd || |
| event_type == event_type_names::kTransitionend) { |
| AddListenerType(kTransitionEndListener); |
| } else if (event_type == event_type_names::kScroll) { |
| AddListenerType(kScrollListener); |
| } else if (event_type == event_type_names::kLoad) { |
| if (Node* node = event_target.ToNode()) { |
| if (IsA<HTMLStyleElement>(*node)) { |
| AddListenerType(kLoadListenerAtCapturePhaseOrAtStyleElement); |
| return; |
| } |
| } |
| if (event_target.HasCapturingEventListeners(event_type)) |
| AddListenerType(kLoadListenerAtCapturePhaseOrAtStyleElement); |
| } |
| } |
| |
| HTMLFrameOwnerElement* Document::LocalOwner() const { |
| if (!GetFrame()) |
| return nullptr; |
| // FIXME: This probably breaks the attempts to layout after a load is finished |
| // in implicitClose(), and probably tons of other things... |
| return GetFrame()->DeprecatedLocalOwner(); |
| } |
| |
| void Document::WillChangeFrameOwnerProperties( |
| int margin_width, |
| int margin_height, |
| mojom::blink::ScrollbarMode scrollbar_mode, |
| bool is_display_none, |
| mojom::blink::ColorScheme color_scheme) { |
| DCHECK(GetFrame() && GetFrame()->Owner()); |
| FrameOwner* owner = GetFrame()->Owner(); |
| |
| if (is_display_none != owner->IsDisplayNone()) |
| DisplayNoneChangedForFrame(); |
| // body() may become null as a result of modification event listeners, so we |
| // check before each call. |
| if (margin_width != owner->MarginWidth()) { |
| if (auto* body_element = body()) { |
| body_element->SetIntegralAttribute(html_names::kMarginwidthAttr, |
| margin_width); |
| } |
| } |
| if (margin_height != owner->MarginHeight()) { |
| if (auto* body_element = body()) { |
| body_element->SetIntegralAttribute(html_names::kMarginheightAttr, |
| margin_height); |
| } |
| } |
| if (scrollbar_mode != owner->ScrollbarMode() && View()) { |
| View()->SetCanHaveScrollbars(scrollbar_mode != |
| mojom::blink::ScrollbarMode::kAlwaysOff); |
| View()->SetNeedsLayout(); |
| } |
| GetStyleEngine().SetOwnerColorScheme(color_scheme); |
| } |
| |
| String Document::cookie(ExceptionState& exception_state) const { |
| if (!dom_window_ || !GetSettings()->GetCookieEnabled()) |
| return String(); |
| |
| CountUse(WebFeature::kCookieGet); |
| |
| if (!dom_window_->GetSecurityOrigin()->CanAccessCookies()) { |
| if (dom_window_->IsSandboxed( |
| network::mojom::blink::WebSandboxFlags::kOrigin)) { |
| exception_state.ThrowSecurityError( |
| "The document is sandboxed and lacks the 'allow-same-origin' flag."); |
| } else if (Url().ProtocolIsData()) { |
| exception_state.ThrowSecurityError( |
| "Cookies are disabled inside 'data:' URLs."); |
| } else { |
| exception_state.ThrowSecurityError("Access is denied for this document."); |
| } |
| return String(); |
| } else if (dom_window_->GetSecurityOrigin()->IsLocal()) { |
| CountUse(WebFeature::kFileAccessedCookies); |
| } |
| |
| return cookie_jar_->Cookies(); |
| } |
| |
| void Document::setCookie(const String& value, ExceptionState& exception_state) { |
| if (!dom_window_ || !GetSettings()->GetCookieEnabled()) |
| return; |
| |
| UseCounter::Count(*this, WebFeature::kCookieSet); |
| |
| if (!dom_window_->GetSecurityOrigin()->CanAccessCookies()) { |
| if (dom_window_->IsSandboxed( |
| network::mojom::blink::WebSandboxFlags::kOrigin)) { |
| exception_state.ThrowSecurityError( |
| "The document is sandboxed and lacks the 'allow-same-origin' flag."); |
| } else if (Url().ProtocolIsData()) { |
| exception_state.ThrowSecurityError( |
| "Cookies are disabled inside 'data:' URLs."); |
| } else { |
| exception_state.ThrowSecurityError("Access is denied for this document."); |
| } |
| return; |
| } else if (dom_window_->GetSecurityOrigin()->IsLocal()) { |
| UseCounter::Count(*this, WebFeature::kFileAccessedCookies); |
| } |
| |
| cookie_jar_->SetCookie(value); |
| } |
| |
| bool Document::CookiesEnabled() const { |
| if (!dom_window_) |
| return false; |
| // Compatible behavior in contexts that don't have cookie access. |
| if (!dom_window_->GetSecurityOrigin()->CanAccessCookies()) |
| return true; |
| return cookie_jar_->CookiesEnabled(); |
| } |
| |
| void Document::SetCookieManager( |
| mojo::PendingRemote<network::mojom::blink::RestrictedCookieManager> |
| cookie_manager) { |
| cookie_jar_->SetCookieManager(std::move(cookie_manager)); |
| } |
| |
| const base::Uuid& Document::base_auction_nonce() { |
| return base_auction_nonce_; |
| } |
| |
| const AtomicString& Document::referrer() const { |
| if (Loader()) |
| return Loader()->GetReferrer(); |
| return g_null_atom; |
| } |
| |
| String Document::domain() const { |
| return GetExecutionContext() |
| ? GetExecutionContext()->GetSecurityOrigin()->Domain() |
| : String(); |
| } |
| |
| void Document::setDomain(const String& raw_domain, |
| ExceptionState& exception_state) { |
| UseCounter::Count(*this, WebFeature::kDocumentSetDomain); |
| |
| if (!dom_window_) { |
| exception_state.ThrowSecurityError( |
| "A browsing context is required to set a domain."); |
| return; |
| } |
| |
| if (dom_window_->IsSandboxed( |
| network::mojom::blink::WebSandboxFlags::kDocumentDomain)) { |
| exception_state.ThrowSecurityError( |
| dom_window_->GetFrame()->IsInFencedFrameTree() |
| ? "Assignment is forbidden in a fenced frame tree." |
| : "Assignment is forbidden for sandboxed iframes."); |
| return; |
| } |
| |
| if (SchemeRegistry::IsDomainRelaxationForbiddenForURLScheme( |
| dom_window_->GetSecurityOrigin()->Protocol())) { |
| exception_state.ThrowSecurityError( |
| "Assignment is forbidden for the '" + |
| dom_window_->GetSecurityOrigin()->Protocol() + "' scheme."); |
| return; |
| } |
| |
| bool success = false; |
| String new_domain = SecurityOrigin::CanonicalizeHost(raw_domain, &success); |
| if (!success) { |
| exception_state.ThrowSecurityError("'" + raw_domain + |
| "' could not be parsed properly."); |
| return; |
| } |
| |
| if (new_domain.empty()) { |
| exception_state.ThrowSecurityError("'" + new_domain + |
| "' is an empty domain."); |
| return; |
| } |
| |
| scoped_refptr<SecurityOrigin> new_origin = |
| dom_window_->GetSecurityOrigin()->IsolatedCopy(); |
| new_origin->SetDomainFromDOM(new_domain); |
| OriginAccessEntry access_entry( |
| *new_origin, network::mojom::CorsDomainMatchMode::kAllowSubdomains); |
| network::cors::OriginAccessEntry::MatchResult result = |
| access_entry.MatchesOrigin(*dom_window_->GetSecurityOrigin()); |
| if (result == network::cors::OriginAccessEntry::kDoesNotMatchOrigin) { |
| exception_state.ThrowSecurityError( |
| "'" + new_domain + "' is not a suffix of '" + domain() + "'."); |
| return; |
| } |
| |
| if (result == |
| network::cors::OriginAccessEntry::kMatchesOriginButIsPublicSuffix) { |
| exception_state.ThrowSecurityError("'" + new_domain + |
| "' is a top-level domain."); |
| return; |
| } |
| |
| // We technically only need to IsOriginKeyed(), as IsCrossOriginIsolated() |
| // implies IsOriginKeyed(). (The spec only checks "is origin-keyed".) But, |
| // we'll check both, in order to give warning messages that are more specific |
| // about the cause. Note: this means the order of the checks is important. |
| |
| if (Agent::IsCrossOriginIsolated()) { |
| AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| ConsoleMessage::Source::kSecurity, ConsoleMessage::Level::kWarning, |
| "document.domain mutation is ignored because the surrounding agent " |
| "cluster is cross-origin isolated.")); |
| return; |
| } |
| |
| if (RuntimeEnabledFeatures::OriginIsolationHeaderEnabled(dom_window_) && |
| dom_window_->GetAgent()->IsOriginKeyed()) { |
| AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| ConsoleMessage::Source::kSecurity, ConsoleMessage::Level::kWarning, |
| "document.domain mutation is ignored because the surrounding agent " |
| "cluster is origin-keyed.")); |
| return; |
| } |
| |
| // TODO(crbug.com/1259920): Remove this check once the Origin-Agent-Cluster |
| // default behaviour change has been default-enabled. |
| if (base::FeatureList::IsEnabled( |
| blink::features::kOriginAgentClusterDefaultWarning) && |
| Loader()) { |
| const AtomicString& origin_agent_cluster_header = |
| Loader()->GetResponse().HttpHeaderField( |
| http_names::kOriginAgentCluster); |
| if (origin_agent_cluster_header != "?0" && |
| origin_agent_cluster_header != "?1") { |
| DCHECK(!dom_window_->GetAgent()->IsOriginKeyed()); |
| Deprecation::CountDeprecation( |
| GetExecutionContext(), |
| WebFeature::kDocumentDomainSettingWithoutOriginAgentClusterHeader); |
| // No return; warning only. |
| } |
| } |
| |
| if (GetFrame()) { |
| // This code should never fire for fenced frames because it should be |
| // blocked by permission policy. |
| DCHECK(!GetFrame()->IsInFencedFrameTree()); |
| UseCounter::Count(*this, |
| dom_window_->GetSecurityOrigin()->Port() == 0 |
| ? WebFeature::kDocumentDomainSetWithDefaultPort |
| : WebFeature::kDocumentDomainSetWithNonDefaultPort); |
| bool was_cross_origin_to_nearest_main_frame = |
| GetFrame()->IsCrossOriginToNearestMainFrame(); |
| bool was_cross_origin_to_parent_frame = |
| GetFrame()->IsCrossOriginToParentOrOuterDocument(); |
| SecurityOrigin* security_origin = dom_window_->GetMutableSecurityOrigin(); |
| security_origin->SetDomainFromDOM(new_domain); |
| if (security_origin->aliased_by_document_open()) { |
| UseCounter::Count(*this, |
| WebFeature::kDocumentOpenAliasedOriginDocumentDomain); |
| } |
| |
| bool is_cross_origin_to_nearest_main_frame = |
| GetFrame()->IsCrossOriginToNearestMainFrame(); |
| if (FrameScheduler* frame_scheduler = GetFrame()->GetFrameScheduler()) { |
| frame_scheduler->SetCrossOriginToNearestMainFrame( |
| is_cross_origin_to_nearest_main_frame); |
| } |
| if (View() && (was_cross_origin_to_nearest_main_frame != |
| is_cross_origin_to_nearest_main_frame)) { |
| View()->CrossOriginToNearestMainFrameChanged(); |
| } |
| if (GetFrame()->IsMainFrame()) { |
| // Notify descendants if their cross-origin-to-main-frame status changed. |
| // TODO(pdr): This will notify even if |
| // |Frame::IsCrossOriginToNearestMainFrame| is the same. Track whether |
| // each child was cross-origin to main before and after changing the |
| // domain, and only notify the changed ones. |
| for (Frame* child = GetFrame()->Tree().FirstChild(); child; |
| child = child->Tree().TraverseNext(GetFrame())) { |
| auto* child_local_frame = DynamicTo<LocalFrame>(child); |
| if (child_local_frame && child_local_frame->View()) |
| child_local_frame->View()->CrossOriginToNearestMainFrameChanged(); |
| } |
| } |
| |
| if (View() && was_cross_origin_to_parent_frame != |
| GetFrame()->IsCrossOriginToParentOrOuterDocument()) { |
| View()->CrossOriginToParentFrameChanged(); |
| } |
| // Notify all child frames if their cross-origin-to-parent status changed. |
| // TODO(pdr): This will notify even if |
| // |Frame::IsCrossOriginToParentOrOuterDocument| is the same. Track whether |
| // each child was cross-origin-to-parent before and after changing the |
| // domain, and only notify the changed ones. |
| for (Frame* child = GetFrame()->Tree().FirstChild(); child; |
| child = child->Tree().NextSibling()) { |
| auto* child_local_frame = DynamicTo<LocalFrame>(child); |
| if (child_local_frame && child_local_frame->View()) |
| child_local_frame->View()->CrossOriginToParentFrameChanged(); |
| } |
| |
| dom_window_->GetScriptController().UpdateSecurityOrigin( |
| dom_window_->GetSecurityOrigin()); |
| } |
| } |
| |
| std::optional<base::Time> Document::lastModifiedTime() const { |
| AtomicString http_last_modified = override_last_modified_; |
| if (http_last_modified.empty()) { |
| if (DocumentLoader* document_loader = Loader()) { |
| http_last_modified = document_loader->GetResponse().HttpHeaderField( |
| http_names::kLastModified); |
| } |
| } |
| if (!http_last_modified.empty()) { |
| return ParseDate(http_last_modified); |
| } |
| return std::nullopt; |
| } |
| |
| // https://html.spec.whatwg.org/C#dom-document-lastmodified |
| String Document::lastModified() const { |
| return String(base::UnlocalizedTimeFormatWithPattern( |
| lastModifiedTime().value_or(base::Time::Now()), "MM/dd/yyyy HH:mm:ss")); |
| } |
| |
| scoped_refptr<const SecurityOrigin> Document::TopFrameOrigin() const { |
| if (!GetFrame()) |
| return scoped_refptr<const SecurityOrigin>(); |
| |
| return GetFrame()->Tree().Top().GetSecurityContext()->GetSecurityOrigin(); |
| } |
| |
| net::SiteForCookies Document::SiteForCookies() const { |
| if (!GetFrame()) |
| return net::SiteForCookies(); |
| |
| Frame& top = GetFrame()->Tree().Top(); |
| const SecurityOrigin* origin = top.GetSecurityContext()->GetSecurityOrigin(); |
| // TODO(yhirano): Ideally |origin| should not be null here. |
| if (!origin) |
| return net::SiteForCookies(); |
| |
| // Fake a 1P site for cookies for top-level documents that are rendering media |
| // like images or video. We do so because when third-party cookie blocking is |
| // enabled, access-controlled media cannot be rendered. We only make this |
| // exception in this special case to minimize security/privacy risk. |
| url::Origin url_origin = origin->ToUrlOrigin(); |
| if (url_origin.opaque() && |
| !url_origin.GetTupleOrPrecursorTupleIfOpaque().host().empty() && |
| override_site_for_cookies_for_csp_media_) { |
| return net::SiteForCookies::FromOrigin(url::Origin::Create( |
| url_origin.GetTupleOrPrecursorTupleIfOpaque().GetURL())); |
| } |
| |
| net::SiteForCookies candidate = net::SiteForCookies::FromOrigin(url_origin); |
| |
| if (SchemeRegistry::ShouldTreatURLSchemeAsFirstPartyWhenTopLevel( |
| origin->Protocol())) { |
| return candidate; |
| } |
| |
| const Frame* current_frame = GetFrame(); |
| if (SchemeRegistry:: |
| ShouldTreatURLSchemeAsFirstPartyWhenTopLevelEmbeddingSecure( |
| origin->Protocol(), current_frame->GetSecurityContext() |
| ->GetSecurityOrigin() |
| ->Protocol())) { |
| return candidate; |
| } |
| |
| while (current_frame) { |
| const url::Origin cur_security_origin = |
| current_frame->GetSecurityContext()->GetSecurityOrigin()->ToUrlOrigin(); |
| if (!candidate.CompareWithFrameTreeOriginAndRevise(cur_security_origin)) |
| return candidate; |
| current_frame = current_frame->Tree().Parent(); |
| } |
| |
| return candidate; |
| } |
| |
| mojom::blink::PermissionService* Document::GetPermissionService( |
| ExecutionContext* execution_context) { |
| if (!data_->permission_service_.is_bound()) { |
| execution_context->GetBrowserInterfaceBroker().GetInterface( |
| data_->permission_service_.BindNewPipeAndPassReceiver( |
| execution_context->GetTaskRunner(TaskType::kPermission))); |
| data_->permission_service_.set_disconnect_handler(WTF::BindOnce( |
| &Document::PermissionServiceConnectionError, WrapWeakPersistent(this))); |
| } |
| return data_->permission_service_.get(); |
| } |
| |
| void Document::PermissionServiceConnectionError() { |
| data_->permission_service_.reset(); |
| } |
| |
| ScriptPromise<IDLBoolean> Document::hasStorageAccess( |
| ScriptState* script_state) { |
| // See |
| // https://privacycg.github.io/storage-access/#dom-document-hasstorageaccess |
| // for the steps implemented here. |
| |
| // Step #2: if doc is not fully active, reject p with an InvalidStateError and |
| // return p. |
| if (!GetFrame()) { |
| // Note that in detached frames, resolvers are not able to return a promise. |
| return ScriptPromise<IDLBoolean>::RejectWithDOMException( |
| script_state, MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kInvalidStateError, |
| "hasStorageAccess: Cannot be used unless the " |
| "document is fully active.")); |
| } |
| |
| auto* resolver = |
| MakeGarbageCollected<ScriptPromiseResolver<IDLBoolean>>(script_state); |
| auto promise = resolver->Promise(); |
| resolver->Resolve([&]() -> bool { |
| // #3: if doc's origin is opaque, return false. |
| if (GetExecutionContext()->GetSecurityOrigin()->IsOpaque()) { |
| return false; |
| } |
| |
| // #?: if window.credentialless is true, return false. |
| if (dom_window_->credentialless()) { |
| return false; |
| } |
| |
| // #5: if global is not a secure context, return false. |
| if (!dom_window_->isSecureContext()) { |
| return false; |
| } |
| |
| // #6: if the top-level origin of doc's relevant settings object is an |
| // opaque origin, return false. |
| if (TopFrameOrigin()->IsOpaque()) { |
| return false; |
| } |
| |
| // #7 - #10: checks unpartitioned cookie availability with global's `has |
| // storage access`. |
| return CookiesEnabled(); |
| }()); |
| return promise; |
| } |
| |
| ScriptPromise<IDLUndefined> Document::requestStorageAccessFor( |
| ScriptState* script_state, |
| const AtomicString& origin) { |
| if (!GetFrame()) { |
| FireRequestStorageAccessForHistogram( |
| RequestStorageResult::REJECTED_NO_ORIGIN); |
| // Note that in detached frames, resolvers are not able to return a promise. |
| return ScriptPromise<IDLUndefined>::RejectWithDOMException( |
| script_state, MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kInvalidStateError, |
| "requestStorageAccessFor: Cannot be used unless " |
| "the document is fully active.")); |
| } |
| |
| auto* resolver = |
| MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(script_state); |
| |
| // Access the promise first to ensure it is created so that the proper state |
| // can be changed when it is resolved or rejected. |
| auto promise = resolver->Promise(); |
| |
| if (!IsInOutermostMainFrame()) { |
| AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| ConsoleMessage::Source::kSecurity, ConsoleMessage::Level::kError, |
| "requestStorageAccessFor: Only supported in primary top-level " |
| "browsing contexts.")); |
| FireRequestStorageAccessForHistogram( |
| RequestStorageResult::REJECTED_INCORRECT_FRAME); |
| resolver->Reject(V8ThrowDOMException::CreateOrEmpty( |
| script_state->GetIsolate(), DOMExceptionCode::kNotAllowedError, |
| "requestStorageAccessFor not allowed")); |
| return promise; |
| } |
| |
| if (dom_window_->GetSecurityOrigin()->IsOpaque()) { |
| AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| ConsoleMessage::Source::kSecurity, ConsoleMessage::Level::kError, |
| "requestStorageAccessFor: Cannot be used by opaque origins.")); |
| |
| FireRequestStorageAccessForHistogram( |
| RequestStorageResult::REJECTED_OPAQUE_ORIGIN); |
| resolver->Reject(V8ThrowDOMException::CreateOrEmpty( |
| script_state->GetIsolate(), DOMExceptionCode::kNotAllowedError, |
| "requestStorageAccessFor not allowed")); |
| return promise; |
| } |
| |
| // `requestStorageAccessFor` must be rejected for any given iframe. In |
| // particular, it must have been rejected by credentialless iframes: |
| CHECK(!dom_window_->credentialless()); |
| |
| if (!dom_window_->isSecureContext()) { |
| AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| ConsoleMessage::Source::kSecurity, ConsoleMessage::Level::kError, |
| "requestStorageAccessFor: May not be used in an insecure " |
| "context.")); |
| FireRequestStorageAccessForHistogram( |
| RequestStorageResult::REJECTED_INSECURE_CONTEXT); |
| |
| resolver->Reject(V8ThrowDOMException::CreateOrEmpty( |
| script_state->GetIsolate(), DOMExceptionCode::kNotAllowedError, |
| "requestStorageAccessFor not allowed")); |
| return promise; |
| } |
| |
| KURL origin_as_kurl{origin}; |
| if (!origin_as_kurl.IsValid()) { |
| AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| ConsoleMessage::Source::kSecurity, ConsoleMessage::Level::kError, |
| "requestStorageAccessFor: Invalid origin.")); |
| FireRequestStorageAccessForHistogram( |
| RequestStorageResult::REJECTED_INVALID_ORIGIN); |
| resolver->Reject(V8ThrowException::CreateTypeError( |
| script_state->GetIsolate(), "Invalid origin")); |
| return promise; |
| } |
| |
| scoped_refptr<SecurityOrigin> supplied_origin = |
| SecurityOrigin::Create(origin_as_kurl); |
| if (supplied_origin->IsOpaque()) { |
| AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| ConsoleMessage::Source::kSecurity, ConsoleMessage::Level::kError, |
| "requestStorageAccessFor: Invalid origin parameter.")); |
| FireRequestStorageAccessForHistogram( |
| RequestStorageResult::REJECTED_OPAQUE_ORIGIN); |
| resolver->Reject(V8ThrowDOMException::CreateOrEmpty( |
| script_state->GetIsolate(), DOMExceptionCode::kNotAllowedError, |
| "requestStorageAccessFor not allowed")); |
| return promise; |
| } |
| |
| if (dom_window_->GetSecurityOrigin()->IsSameSiteWith(supplied_origin.get())) { |
| // Access is not actually disabled, so accept the request. |
| resolver->Resolve(); |
| FireRequestStorageAccessForHistogram( |
| RequestStorageResult::APPROVED_EXISTING_ACCESS); |
| return promise; |
| } |
| |
| auto descriptor = mojom::blink::PermissionDescriptor::New(); |
| descriptor->name = mojom::blink::PermissionName::TOP_LEVEL_STORAGE_ACCESS; |
| auto top_level_storage_access_extension = |
| mojom::blink::TopLevelStorageAccessPermissionDescriptor::New(); |
| top_level_storage_access_extension->requestedOrigin = supplied_origin; |
| descriptor->extension = |
| mojom::blink::PermissionDescriptorExtension::NewTopLevelStorageAccess( |
| std::move(top_level_storage_access_extension)); |
| |
| GetPermissionService(ExecutionContext::From(script_state)) |
| ->RequestPermission( |
| std::move(descriptor), |
| LocalFrame::HasTransientUserActivation(GetFrame()), |
| WTF::BindOnce(&Document::ProcessTopLevelStorageAccessPermissionState, |
| WrapPersistent(this), WrapPersistent(resolver))); |
| |
| return promise; |
| } |
| |
| ScriptPromise<IDLUndefined> Document::requestStorageAccess( |
| ScriptState* script_state) { |
| // Requesting storage access via `requestStorageAccess()` idl always requests |
| // unpartitioned cookie access. |
| return RequestStorageAccessImpl(script_state, |
| /*request_unpartitioned_cookie_access=*/true); |
| } |
| |
| ScriptPromise<IDLUndefined> Document::RequestStorageAccessImpl( |
| ScriptState* script_state, |
| bool request_unpartitioned_cookie_access) { |
| if (!GetFrame()) { |
| FireRequestStorageAccessHistogram(RequestStorageResult::REJECTED_NO_ORIGIN); |
| |
| // Note that in detached frames, resolvers are not able to return a promise. |
| return ScriptPromise<IDLUndefined>::RejectWithDOMException( |
| script_state, MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kInvalidStateError, |
| "requestStorageAccess: Cannot be used unless the " |
| "document is fully active.")); |
| } |
| |
| if (cookie_jar_) { |
| // Storage access might be about to change in which case the ability for |
| // |cookie_jar_| to retrieve values might also. Invalidate its cache in case |
| // that happens so it can't return data that shouldn't be accessible. |
| cookie_jar_->InvalidateCache(); |
| } |
| |
| auto* resolver = |
| MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(script_state); |
| |
| // Access the promise first to ensure it is created so that the proper state |
| // can be changed when it is resolved or rejected. |
| auto promise = resolver->Promise(); |
| |
| if (!dom_window_->isSecureContext()) { |
| AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| ConsoleMessage::Source::kSecurity, ConsoleMessage::Level::kError, |
| "requestStorageAccess: May not be used in an insecure context.")); |
| FireRequestStorageAccessHistogram( |
| RequestStorageResult::REJECTED_INSECURE_CONTEXT); |
| |
| resolver->Reject(V8ThrowDOMException::CreateOrEmpty( |
| script_state->GetIsolate(), DOMExceptionCode::kNotAllowedError, |
| "requestStorageAccess not allowed")); |
| return promise; |
| } |
| |
| if (IsInOutermostMainFrame()) { |
| FireRequestStorageAccessHistogram( |
| RequestStorageResult::APPROVED_PRIMARY_FRAME); |
| |
| // If this is the outermost frame we no longer need to make a request and |
| // can resolve the promise. |
| resolver->Resolve(); |
| return promise; |
| } |
| |
| if (dom_window_->GetSecurityOrigin()->IsOpaque()) { |
| AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| ConsoleMessage::Source::kSecurity, ConsoleMessage::Level::kError, |
| "requestStorageAccess: Cannot be used by opaque origins.")); |
| FireRequestStorageAccessHistogram( |
| RequestStorageResult::REJECTED_OPAQUE_ORIGIN); |
| |
| resolver->Reject(V8ThrowDOMException::CreateOrEmpty( |
| script_state->GetIsolate(), DOMExceptionCode::kNotAllowedError, |
| "requestStorageAccess not allowed")); |
| return promise; |
| } |
| |
| if (dom_window_->credentialless()) { |
| AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| ConsoleMessage::Source::kSecurity, ConsoleMessage::Level::kError, |
| "requestStorageAccess: May not be used in a credentialless iframe")); |
| FireRequestStorageAccessHistogram( |
| RequestStorageResult::REJECTED_CREDENTIALLESS_IFRAME); |
| |
| resolver->Reject(V8ThrowDOMException::CreateOrEmpty( |
| script_state->GetIsolate(), DOMExceptionCode::kNotAllowedError, |
| "requestStorageAccess not allowed")); |
| return promise; |
| } |
| |
| if (dom_window_->IsSandboxed(network::mojom::blink::WebSandboxFlags:: |
| kStorageAccessByUserActivation)) { |
| AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| ConsoleMessage::Source::kSecurity, ConsoleMessage::Level::kError, |
| dom_window_->GetFrame()->IsInFencedFrameTree() |
| ? "requestStorageAccess: Refused to execute request. The document " |
| "is in a fenced frame tree." |
| : "requestStorageAccess: Refused to execute request. The document " |
| "is sandboxed, and the 'allow-storage-access-by-user-activation' " |
| "keyword is not set.")); |
| |
| FireRequestStorageAccessHistogram( |
| dom_window_->GetFrame()->IsInFencedFrameTree() |
| ? RequestStorageResult::REJECTED_FENCED_FRAME |
| : RequestStorageResult::REJECTED_SANDBOXED); |
| |
| resolver->Reject(V8ThrowDOMException::CreateOrEmpty( |
| script_state->GetIsolate(), DOMExceptionCode::kNotAllowedError, |
| "requestStorageAccess not allowed")); |
| return promise; |
| } |
| // RequestPermission may return `GRANTED` without actually creating a |
| // permission grant if cookies are already accessible. |
| auto descriptor = mojom::blink::PermissionDescriptor::New(); |
| descriptor->name = mojom::blink::PermissionName::STORAGE_ACCESS; |
| GetPermissionService(ExecutionContext::From(resolver->GetScriptState())) |
| ->RequestPermission( |
| std::move(descriptor), |
| LocalFrame::HasTransientUserActivation(GetFrame()), |
| WTF::BindOnce(&Document::ProcessStorageAccessPermissionState, |
| WrapPersistent(this), WrapPersistent(resolver), |
| request_unpartitioned_cookie_access)); |
| |
| return promise; |
| } |
| |
| void Document::ProcessStorageAccessPermissionState( |
| ScriptPromiseResolver<IDLUndefined>* resolver, |
| bool request_unpartitioned_cookie_access, |
| mojom::blink::PermissionStatus status) { |
| DCHECK(resolver); |
| |
| ScriptState* script_state = resolver->GetScriptState(); |
| DCHECK(script_state); |
| ScriptState::Scope scope(script_state); |
| |
| // document could be no longer alive. |
| if (!dom_window_) { |
| resolver->Reject(V8ThrowDOMException::CreateOrEmpty( |
| script_state->GetIsolate(), DOMExceptionCode::kNotAllowedError, |
| "document shutdown")); |
| return; |
| } |
| |
| if (status == mojom::blink::PermissionStatus::GRANTED) { |
| FireRequestStorageAccessHistogram( |
| RequestStorageResult::APPROVED_NEW_OR_EXISTING_GRANT); |
| if (request_unpartitioned_cookie_access) { |
| dom_window_->SetHasStorageAccess(); |
| } |
| resolver->Resolve(); |
| } else { |
| LocalFrame::ConsumeTransientUserActivation(GetFrame()); |
| FireRequestStorageAccessHistogram( |
| RequestStorageResult::REJECTED_GRANT_DENIED); |
| AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| ConsoleMessage::Source::kSecurity, ConsoleMessage::Level::kError, |
| "requestStorageAccess: Permission denied.")); |
| resolver->Reject(V8ThrowDOMException::CreateOrEmpty( |
| script_state->GetIsolate(), DOMExceptionCode::kNotAllowedError, |
| "requestStorageAccess not allowed")); |
| } |
| } |
| |
| void Document::ProcessTopLevelStorageAccessPermissionState( |
| ScriptPromiseResolver<IDLUndefined>* resolver, |
| mojom::blink::PermissionStatus status) { |
| DCHECK(resolver); |
| DCHECK(GetFrame()); |
| ScriptState* script_state = resolver->GetScriptState(); |
| DCHECK(script_state); |
| ScriptState::Scope scope(script_state); |
| |
| if (status == mojom::blink::PermissionStatus::GRANTED) { |
| FireRequestStorageAccessForHistogram( |
| RequestStorageResult::APPROVED_NEW_OR_EXISTING_GRANT); |
| resolver->Resolve(); |
| } else { |
| LocalFrame::ConsumeTransientUserActivation(GetFrame()); |
| FireRequestStorageAccessForHistogram( |
| RequestStorageResult::REJECTED_GRANT_DENIED); |
| AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| ConsoleMessage::Source::kSecurity, ConsoleMessage::Level::kError, |
| "requestStorageAccessFor: Permission denied.")); |
| resolver->Reject(V8ThrowDOMException::CreateOrEmpty( |
| script_state->GetIsolate(), DOMExceptionCode::kNotAllowedError, |
| "requestStorageAccessFor not allowed")); |
| } |
| } |
| |
| FragmentDirective& Document::fragmentDirective() const { |
| return *fragment_directive_; |
| } |
| |
| ScriptPromise<IDLBoolean> Document::hasPrivateToken( |
| ScriptState* script_state, |
| const String& issuer, |
| ExceptionState& exception_state) { |
| // Private State Tokens state is keyed by issuer and top-frame origins that |
| // are both (1) HTTP or HTTPS and (2) potentially trustworthy. Consequently, |
| // we can return early if either the issuer or the top-frame origin fails to |
| // satisfy either of these requirements. |
| KURL issuer_url = KURL(issuer); |
| auto issuer_origin = SecurityOrigin::Create(issuer_url); |
| if (!issuer_url.ProtocolIsInHTTPFamily() || |
| !issuer_origin->IsPotentiallyTrustworthy()) { |
| exception_state.ThrowTypeError( |
| "hasPrivateToken: Private Token issuer origins must be both HTTP(S) " |
| "and secure (\"potentially trustworthy\")."); |
| return ScriptPromise<IDLBoolean>(); |
| } |
| |
| scoped_refptr<const SecurityOrigin> top_frame_origin = TopFrameOrigin(); |
| if (!top_frame_origin) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, |
| "hasPrivateToken: Cannot execute in " |
| "documents lacking top-frame origins."); |
| return ScriptPromise<IDLBoolean>(); |
| } |
| |
| DCHECK(top_frame_origin->IsPotentiallyTrustworthy()); |
| if (top_frame_origin->Protocol() != url::kHttpsScheme && |
| top_frame_origin->Protocol() != url::kHttpScheme) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotAllowedError, |
| "hasPrivateToken: Cannot execute in " |
| "documents without secure, HTTP(S), top-frame origins."); |
| return ScriptPromise<IDLBoolean>(); |
| } |
| |
| if (!data_->trust_token_query_answerer_.is_bound()) { |
| GetFrame()->GetBrowserInterfaceBroker().GetInterface( |
| data_->trust_token_query_answerer_.BindNewPipeAndPassReceiver( |
| GetExecutionContext()->GetTaskRunner(TaskType::kInternalDefault))); |
| data_->trust_token_query_answerer_.set_disconnect_handler( |
| WTF::BindOnce(&Document::TrustTokenQueryAnswererConnectionError, |
| WrapWeakPersistent(this))); |
| } |
| auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLBoolean>>( |
| script_state, exception_state.GetContext()); |
| data_->pending_trust_token_query_resolvers_.insert(resolver); |
| |
| data_->trust_token_query_answerer_->HasTrustTokens( |
| issuer_origin, |
| WTF::BindOnce( |
| [](WeakPersistent<ScriptPromiseResolver<IDLBoolean>> resolver, |
| WeakPersistent<Document> document, |
| network::mojom::blink::HasTrustTokensResultPtr result) { |
| // If there was a Mojo connection error, the promise was already |
| // resolved and deleted. |
| if (!base::Contains( |
| document->data_->pending_trust_token_query_resolvers_, |
| resolver)) { |
| return; |
| } |
| |
| switch (result->status) { |
| case network::mojom::blink::TrustTokenOperationStatus::kOk: { |
| resolver->Resolve(result->has_trust_tokens); |
| break; |
| } |
| case network::mojom::blink::TrustTokenOperationStatus:: |
| kInvalidArgument: { |
| ScriptState* state = resolver->GetScriptState(); |
| ScriptState::Scope scope(state); |
| resolver->Reject(V8ThrowDOMException::CreateOrEmpty( |
| state->GetIsolate(), DOMExceptionCode::kOperationError, |
| "Failed to retrieve hasPrivateToken response. Issuer " |
| "configuration is missing or unsuitable.")); |
| break; |
| } |
| case network::mojom::blink::TrustTokenOperationStatus:: |
| kResourceExhausted: { |
| ScriptState* state = resolver->GetScriptState(); |
| ScriptState::Scope scope(state); |
| resolver->Reject(V8ThrowDOMException::CreateOrEmpty( |
| state->GetIsolate(), DOMExceptionCode::kOperationError, |
| "Failed to retrieve hasPrivateToken response. Exceeded the " |
| "number-of-issuers limit.")); |
| break; |
| } |
| default: { |
| ScriptState* state = resolver->GetScriptState(); |
| ScriptState::Scope scope(state); |
| resolver->Reject(V8ThrowDOMException::CreateOrEmpty( |
| state->GetIsolate(), DOMExceptionCode::kOperationError, |
| "Failed to retrieve hasPrivateToken response.")); |
| } |
| } |
| |
| document->data_->pending_trust_token_query_resolvers_.erase( |
| resolver); |
| }, |
| WrapWeakPersistent(resolver), WrapWeakPersistent(this))); |
| |
| return resolver->Promise(); |
| } |
| |
| ScriptPromise<IDLBoolean> Document::hasRedemptionRecord( |
| ScriptState* script_state, |
| const String& issuer, |
| ExceptionState& exception_state) { |
| auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLBoolean>>( |
| script_state, exception_state.GetContext()); |
| auto promise = resolver->Promise(); |
| |
| // Private State Tokens state is keyed by issuer and top-frame origins that |
| // are both (1) HTTP or HTTPS and (2) potentially trustworthy. Consequently, |
| // we can return early if either the issuer or the top-frame origin fails to |
| // satisfy either of these requirements. |
| KURL issuer_url = KURL(issuer); |
| auto issuer_origin = SecurityOrigin::Create(issuer_url); |
| if (!issuer_url.ProtocolIsInHTTPFamily() || |
| !issuer_origin->IsPotentiallyTrustworthy()) { |
| exception_state.ThrowTypeError( |
| "hasRedemptionRecord: Private Token issuer origins must be both " |
| "HTTP(S) and secure (\"potentially trustworthy\")."); |
| resolver->Reject(exception_state); |
| return promise; |
| } |
| |
| scoped_refptr<const SecurityOrigin> top_frame_origin = TopFrameOrigin(); |
| if (!top_frame_origin) { |
| // Note: One case where there might be no top frame origin is if this |
| // document is destroyed. In this case, this function will return |
| // `undefined`. Still bother adding the exception and rejecting, just in |
| // case there are other situations in which the top frame origin might be |
| // absent. |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, |
| "hasRedemptionRecord: Cannot execute in " |
| "documents lacking top-frame origins."); |
| resolver->Reject(exception_state); |
| return promise; |
| } |
| |
| DCHECK(top_frame_origin->IsPotentiallyTrustworthy()); |
| if (top_frame_origin->Protocol() != url::kHttpsScheme && |
| top_frame_origin->Protocol() != url::kHttpScheme) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotAllowedError, |
| "hasRedemptionRecord: Cannot execute in " |
| "documents without secure, HTTP(S), top-frame origins."); |
| resolver->Reject(exception_state); |
| return promise; |
| } |
| |
| if (!data_->trust_token_query_answerer_.is_bound()) { |
| GetFrame()->GetBrowserInterfaceBroker().GetInterface( |
| data_->trust_token_query_answerer_.BindNewPipeAndPassReceiver( |
| GetExecutionContext()->GetTaskRunner(TaskType::kInternalDefault))); |
| data_->trust_token_query_answerer_.set_disconnect_handler( |
| WTF::BindOnce(&Document::TrustTokenQueryAnswererConnectionError, |
| WrapWeakPersistent(this))); |
| } |
| |
| data_->pending_trust_token_query_resolvers_.insert(resolver); |
| |
| data_->trust_token_query_answerer_->HasRedemptionRecord( |
| issuer_origin, |
| WTF::BindOnce( |
| [](WeakPersistent<ScriptPromiseResolver<IDLBoolean>> resolver, |
| WeakPersistent<Document> document, |
| network::mojom::blink::HasRedemptionRecordResultPtr result) { |
| // If there was a Mojo connection error, the promise was already |
| // resolved and deleted. |
| if (!base::Contains( |
| document->data_->pending_trust_token_query_resolvers_, |
| resolver)) { |
| return; |
| } |
| |
| switch (result->status) { |
| case network::mojom::blink::TrustTokenOperationStatus::kOk: { |
| resolver->Resolve(result->has_redemption_record); |
| break; |
| } |
| case network::mojom::blink::TrustTokenOperationStatus:: |
| kInvalidArgument: { |
| ScriptState* state = resolver->GetScriptState(); |
| ScriptState::Scope scope(state); |
| resolver->Reject(V8ThrowDOMException::CreateOrEmpty( |
| state->GetIsolate(), DOMExceptionCode::kOperationError, |
| "Failed to retrieve hasRedemptionRecord response. Issuer " |
| "configuration is missing or unsuitable.")); |
| break; |
| } |
| default: { |
| ScriptState* state = resolver->GetScriptState(); |
| ScriptState::Scope scope(state); |
| resolver->Reject(V8ThrowDOMException::CreateOrEmpty( |
| state->GetIsolate(), DOMExceptionCode::kOperationError, |
| "Failed to retrieve hasRedemptionRecord response.")); |
| } |
| } |
| |
| document->data_->pending_trust_token_query_resolvers_.erase( |
| resolver); |
| }, |
| WrapWeakPersistent(resolver), WrapWeakPersistent(this))); |
| |
| return promise; |
| } |
| |
| void Document::TrustTokenQueryAnswererConnectionError() { |
| data_->trust_token_query_answerer_.reset(); |
| for (const auto& resolver : data_->pending_trust_token_query_resolvers_) { |
| ScriptState* state = resolver->GetScriptState(); |
| ScriptState::Scope scope(state); |
| resolver->Reject(V8ThrowDOMException::CreateOrEmpty( |
| state->GetIsolate(), DOMExceptionCode::kOperationError, |
| "Internal error retrieving trust token response.")); |
| } |
| data_->pending_trust_token_query_resolvers_.clear(); |
| } |
| |
| void Document::ariaNotify(const String& announcement, |
| const AriaNotificationOptions* options) { |
| DCHECK(RuntimeEnabledFeatures::AriaNotifyEnabled()); |
| |
| if (auto* cache = ExistingAXObjectCache()) { |
| cache->HandleAriaNotification(this, announcement, options); |
| } |
| } |
| |
| static bool IsValidNameNonASCII(const LChar* characters, unsigned length) { |
| if (!IsValidNameStart(characters[0])) |
| return false; |
| |
| for (unsigned i = 1; i < length; ++i) { |
| if (!IsValidNamePart(characters[i])) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool IsValidNameNonASCII(const UChar* characters, unsigned length) { |
| for (unsigned i = 0; i < length;) { |
| bool first = i == 0; |
| UChar32 c; |
| U16_NEXT(characters, i, length, c); // Increments i. |
| if (first ? !IsValidNameStart(c) : !IsValidNamePart(c)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| template <typename CharType> |
| static inline bool IsValidNameASCII(const CharType* characters, |
| unsigned length) { |
| CharType c = characters[0]; |
| if (!(IsASCIIAlpha(c) || c == ':' || c == '_')) |
| return false; |
| |
| for (unsigned i = 1; i < length; ++i) { |
| c = characters[i]; |
| if (!(IsASCIIAlphanumeric(c) || c == ':' || c == '_' || c == '-' || |
| c == '.')) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool Document::IsValidName(const StringView& name) { |
| unsigned length = name.length(); |
| if (!length) |
| return false; |
| |
| if (name.Is8Bit()) { |
| const LChar* characters = name.Characters8(); |
| |
| if (IsValidNameASCII(characters, length)) |
| return true; |
| |
| return IsValidNameNonASCII(characters, length); |
| } |
| |
| const UChar* characters = name.Characters16(); |
| |
| if (IsValidNameASCII(characters, length)) |
| return true; |
| |
| return IsValidNameNonASCII(characters, length); |
| } |
| |
| enum QualifiedNameStatus { |
| kQNValid, |
| kQNMultipleColons, |
| kQNInvalidStartChar, |
| kQNInvalidChar, |
| kQNEmptyPrefix, |
| kQNEmptyLocalName |
| }; |
| |
| struct ParseQualifiedNameResult { |
| QualifiedNameStatus status; |
| UChar32 character; |
| ParseQualifiedNameResult() = default; |
| explicit ParseQualifiedNameResult(QualifiedNameStatus status) |
| : status(status) {} |
| ParseQualifiedNameResult(QualifiedNameStatus status, UChar32 character) |
| : status(status), character(character) {} |
| }; |
| |
| template <typename CharType> |
| static ParseQualifiedNameResult ParseQualifiedNameInternal( |
| const AtomicString& qualified_name, |
| const CharType* characters, |
| unsigned length, |
| AtomicString& prefix, |
| AtomicString& local_name) { |
| bool name_start = true; |
| bool saw_colon = false; |
| unsigned colon_pos = 0; |
| |
| for (unsigned i = 0; i < length;) { |
| UChar32 c; |
| U16_NEXT(characters, i, length, c); |
| if (c == ':') { |
| if (saw_colon) |
| return ParseQualifiedNameResult(kQNMultipleColons); |
| name_start = true; |
| saw_colon = true; |
| colon_pos = i - 1; |
| } else if (name_start) { |
| if (!IsValidNameStart(c)) |
| return ParseQualifiedNameResult(kQNInvalidStartChar, c); |
| name_start = false; |
| } else { |
| if (!IsValidNamePart(c)) |
| return ParseQualifiedNameResult(kQNInvalidChar, c); |
| } |
| } |
| |
| if (!saw_colon) { |
| prefix = g_null_atom; |
| local_name = qualified_name; |
| } else { |
| prefix = AtomicString(characters, colon_pos); |
| if (prefix.empty()) |
| return ParseQualifiedNameResult(kQNEmptyPrefix); |
| int prefix_start = colon_pos + 1; |
| local_name = AtomicString(characters + prefix_start, length - prefix_start); |
| } |
| |
| if (local_name.empty()) |
| return ParseQualifiedNameResult(kQNEmptyLocalName); |
| |
| return ParseQualifiedNameResult(kQNValid); |
| } |
| |
| bool Document::ParseQualifiedName(const AtomicString& qualified_name, |
| AtomicString& prefix, |
| AtomicString& local_name, |
| ExceptionState& exception_state) { |
| unsigned length = qualified_name.length(); |
| |
| if (!length) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidCharacterError, |
| "The qualified name provided is empty."); |
| return false; |
| } |
| |
| ParseQualifiedNameResult return_value; |
| if (qualified_name.Is8Bit()) |
| return_value = |
| ParseQualifiedNameInternal(qualified_name, qualified_name.Characters8(), |
| length, prefix, local_name); |
| else |
| return_value = ParseQualifiedNameInternal(qualified_name, |
| qualified_name.Characters16(), |
| length, prefix, local_name); |
| if (return_value.status == kQNValid) |
| return true; |
| |
| StringBuilder message; |
| message.Append("The qualified name provided ('"); |
| message.Append(qualified_name); |
| message.Append("') "); |
| |
| if (return_value.status == kQNMultipleColons) { |
| message.Append("contains multiple colons."); |
| } else if (return_value.status == kQNInvalidStartChar) { |
| message.Append("contains the invalid name-start character '"); |
| message.Append(return_value.character); |
| message.Append("'."); |
| } else if (return_value.status == kQNInvalidChar) { |
| message.Append("contains the invalid character '"); |
| message.Append(return_value.character); |
| message.Append("'."); |
| } else if (return_value.status == kQNEmptyPrefix) { |
| message.Append("has an empty namespace prefix."); |
| } else { |
| DCHECK_EQ(return_value.status, kQNEmptyLocalName); |
| message.Append("has an empty local name."); |
| } |
| |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidCharacterError, |
| message.ReleaseString()); |
| return false; |
| } |
| |
| void Document::SetEncodingData(const DocumentEncodingData& new_data) { |
| // It's possible for the encoding of the document to change while we're |
| // decoding data. That can only occur while we're processing the <head> |
| // portion of the document. There isn't much user-visible content in the |
| // <head>, but there is the <title> element. This function detects that |
| // situation and re-decodes the document's title so that the user doesn't see |
| // an incorrectly decoded title in the title bar. |
| if (title_element_ && Encoding() != new_data.Encoding() && |
| !ElementTraversal::FirstWithin(*title_element_) && |
| Encoding() == Latin1Encoding() && |
| title_element_->textContent().ContainsOnlyLatin1OrEmpty()) { |
| std::string original_bytes = title_element_->textContent().Latin1(); |
| std::unique_ptr<TextCodec> codec = NewTextCodec(new_data.Encoding()); |
| String correctly_decoded_title = |
| codec->Decode(original_bytes.c_str(), |
| static_cast<wtf_size_t>(original_bytes.length()), |
| WTF::FlushBehavior::kDataEOF); |
| title_element_->setTextContent(correctly_decoded_title); |
| } |
| |
| DCHECK(new_data.Encoding().IsValid()); |
| encoding_data_ = new_data; |
| |
| // FIXME: Should be removed as part of |
| // https://code.google.com/p/chromium/issues/detail?id=319643 |
| bool should_use_visual_ordering = |
| encoding_data_.Encoding().UsesVisualOrdering(); |
| if (should_use_visual_ordering != visually_ordered_) { |
| visually_ordered_ = should_use_visual_ordering; |
| GetStyleEngine().MarkViewportStyleDirty(); |
| GetStyleEngine().MarkAllElementsForStyleRecalc( |
| StyleChangeReasonForTracing::Create( |
| style_change_reason::kVisuallyOrdered)); |
| } |
| } |
| |
| KURL Document::CompleteURL( |
| const String& url, |
| const CompleteURLPreloadStatus preload_status) const { |
| return CompleteURLWithOverride(url, base_url_, preload_status); |
| } |
| |
| KURL Document::CompleteURLWithOverride( |
| const String& url, |
| const KURL& base_url_override, |
| CompleteURLPreloadStatus preload_status) const { |
| DCHECK(base_url_override.IsEmpty() || base_url_override.IsValid()); |
| |
| // Always return a null URL when passed a null string. |
| // FIXME: Should we change the KURL constructor to have this behavior? |
| // See also [CSS]StyleSheet::completeURL(const String&) |
| if (url.IsNull()) |
| return KURL(); |
| |
| KURL result = Encoding().IsValid() ? KURL(base_url_override, url, Encoding()) |
| : KURL(base_url_override, url); |
| // If the conditions are met for |
| // `should_record_sandboxed_srcdoc_baseurl_metrics_` to be set, we should |
| // only record the metric if there's no `base_element_url_` set via a base |
| // element. We must also check the preload status below, since a |
| // PreloadRequest could call this function before `base_element_url_` is set. |
| if (should_record_sandboxed_srcdoc_baseurl_metrics_ && |
| base_element_url_.IsEmpty() && preload_status != kIsPreload) { |
| // Compute the same thing assuming an empty base url, to see if it changes. |
| // This will allow us to ignore trivial changes, such as 'https://foo.com' |
| // resolving as 'https://foo.com/', which happens whether the base url is |
| // specified or not. |
| // While the following computation is non-trivial overhead, it's not |
| // expected to be needed often enough to be problematic, and it will be |
| // removed once we've collected data for https://crbug.com/330744612. |
| KURL empty_baseurl_result = Encoding().IsValid() |
| ? KURL(KURL(), url, Encoding()) |
| : KURL(KURL(), url); |
| if (result != empty_baseurl_result) { |
| CountUse(WebFeature::kSandboxedSrcdocFrameResolvesRelativeURL); |
| // Let's not repeat the parallel computation again now we've found a |
| // instance to record. |
| should_record_sandboxed_srcdoc_baseurl_metrics_ = false; |
| } |
| } |
| return result; |
| } |
| |
| // static |
| bool Document::ShouldInheritSecurityOriginFromOwner(const KURL& url) { |
| // https://html.spec.whatwg.org/C/#origin |
| // |
| // If a Document is the initial "about:blank" document, the origin and |
| // effective script origin of the Document are those it was assigned when its |
| // browsing context was created. |
| // |
| // Note: We generalize this to all "blank" URLs and invalid URLs because we |
| // treat all of these URLs as about:blank. This is okay to do for |
| // "about:mumble" because the Browser process will translate such URLs into |
| // "about:blank#blocked". This is necessary, because of practices pointed out |
| // in https://crbug.com/1220186. |
| return url.IsEmpty() || url.ProtocolIsAbout(); |
| } |
| |
| KURL Document::OpenSearchDescriptionURL() { |
| static const char kOpenSearchMIMEType[] = |
| "application/opensearchdescription+xml"; |
| static const char kOpenSearchRelation[] = "search"; |
| |
| // FIXME: Why do only top-level frames have openSearchDescriptionURLs? |
| if (!GetFrame() || GetFrame()->Tree().Parent()) |
| return KURL(); |
| |
| // FIXME: Why do we need to wait for load completion? |
| if (!LoadEventFinished()) |
| return KURL(); |
| |
| if (!head()) |
| return KURL(); |
| |
| for (HTMLLinkElement* link_element = |
| Traversal<HTMLLinkElement>::FirstChild(*head()); |
| link_element; |
| link_element = Traversal<HTMLLinkElement>::NextSibling(*link_element)) { |
| if (!EqualIgnoringASCIICase(link_element->GetType(), kOpenSearchMIMEType) || |
| !EqualIgnoringASCIICase(link_element->Rel(), kOpenSearchRelation)) |
| continue; |
| if (link_element->Href().IsEmpty()) |
| continue; |
| |
| // Count usage; perhaps we can lock this to secure contexts. |
| WebFeature osd_disposition; |
| scoped_refptr<const SecurityOrigin> target = |
| SecurityOrigin::Create(link_element->Href()); |
| if (execution_context_->IsSecureContext()) { |
| osd_disposition = target->IsPotentiallyTrustworthy() |
| ? WebFeature::kOpenSearchSecureOriginSecureTarget |
| : WebFeature::kOpenSearchSecureOriginInsecureTarget; |
| } else { |
| osd_disposition = |
| target->IsPotentiallyTrustworthy() |
| ? WebFeature::kOpenSearchInsecureOriginSecureTarget |
| : WebFeature::kOpenSearchInsecureOriginInsecureTarget; |
| } |
| UseCounter::Count(*this, osd_disposition); |
| |
| return link_element->Href(); |
| } |
| |
| return KURL(); |
| } |
| |
| V8HTMLOrSVGScriptElement* Document::currentScriptForBinding() const { |
| if (current_script_stack_.empty()) |
| return nullptr; |
| ScriptElementBase* script_element_base = current_script_stack_.back(); |
| if (!script_element_base) |
| return nullptr; |
| return script_element_base->AsV8HTMLOrSVGScriptElement(); |
| } |
| |
| void Document::PushCurrentScript(ScriptElementBase* new_current_script) { |
| current_script_stack_.push_back(new_current_script); |
| } |
| |
| void Document::PopCurrentScript(ScriptElementBase* script) { |
| DCHECK(!current_script_stack_.empty()); |
| DCHECK_EQ(current_script_stack_.back(), script); |
| current_script_stack_.pop_back(); |
| } |
| |
| void Document::SetTransformSource(std::unique_ptr<TransformSource> source) { |
| transform_source_ = std::move(source); |
| } |
| |
| String Document::designMode() const { |
| return InDesignMode() ? "on" : "off"; |
| } |
| |
| void Document::setDesignMode(const String& value) { |
| bool new_value = design_mode_; |
| if (EqualIgnoringASCIICase(value, "on")) { |
| new_value = true; |
| UseCounter::Count(*this, WebFeature::kDocumentDesignModeEnabeld); |
| } else if (EqualIgnoringASCIICase(value, "off")) { |
| new_value = false; |
| } |
| if (new_value == design_mode_) |
| return; |
| design_mode_ = new_value; |
| GetStyleEngine().MarkViewportStyleDirty(); |
| GetStyleEngine().MarkAllElementsForStyleRecalc( |
| StyleChangeReasonForTracing::Create(style_change_reason::kDesignMode)); |
| } |
| |
| Document* Document::ParentDocument() const { |
| if (!GetFrame()) |
| return nullptr; |
| auto* parent_local_frame = DynamicTo<LocalFrame>(GetFrame()->Tree().Parent()); |
| if (!parent_local_frame) |
| return nullptr; |
| return parent_local_frame->GetDocument(); |
| } |
| |
| Document& Document::TopDocument() const { |
| // FIXME: Not clear what topDocument() should do in the OOPI case--should it |
| // return the topmost available Document, or something else? |
| Document* doc = const_cast<Document*>(this); |
| for (HTMLFrameOwnerElement* element = doc->LocalOwner(); element; |
| element = doc->LocalOwner()) |
| doc = &element->GetDocument(); |
| |
| DCHECK(doc); |
| return *doc; |
| } |
| |
| ExecutionContext* Document::GetExecutionContext() const { |
| return execution_context_.Get(); |
| } |
| |
| Agent& Document::GetAgent() const { |
| return *agent_; |
| } |
| |
| Attr* Document::createAttribute(const AtomicString& name, |
| ExceptionState& exception_state) { |
| if (!IsValidName(name)) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidCharacterError, |
| "The localName provided ('" + name + |
| "') contains an invalid character."); |
| return nullptr; |
| } |
| return MakeGarbageCollected<Attr>( |
| *this, QualifiedName(ConvertLocalName(name)), g_empty_atom); |
| } |
| |
| Attr* Document::createAttributeNS(const AtomicString& namespace_uri, |
| const AtomicString& qualified_name, |
| ExceptionState& exception_state) { |
| AtomicString prefix, local_name; |
| if (!ParseQualifiedName(qualified_name, prefix, local_name, exception_state)) |
| return nullptr; |
| |
| QualifiedName q_name(prefix, local_name, namespace_uri); |
| |
| if (!HasValidNamespaceForAttributes(q_name)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNamespaceError, |
| "The namespace URI provided ('" + namespace_uri + |
| "') is not valid for the qualified name provided ('" + |
| qualified_name + "')."); |
| return nullptr; |
| } |
| |
| return MakeGarbageCollected<Attr>(*this, q_name, g_empty_atom); |
| } |
| |
| const SVGDocumentExtensions* Document::SvgExtensions() const { |
| return svg_extensions_.Get(); |
| } |
| |
| SVGDocumentExtensions& Document::AccessSVGExtensions() { |
| if (!svg_extensions_) |
| svg_extensions_ = MakeGarbageCollected<SVGDocumentExtensions>(this); |
| return *svg_extensions_; |
| } |
| |
| bool Document::HasSVGRootNode() const { |
| return IsA<SVGSVGElement>(documentElement()); |
| } |
| |
| HTMLCollection* Document::images() { |
| return EnsureCachedCollection<HTMLCollection>(kDocImages); |
| } |
| |
| HTMLCollection* Document::applets() { |
| return EnsureCachedCollection<HTMLCollection>(kDocApplets); |
| } |
| |
| HTMLCollection* Document::embeds() { |
| return EnsureCachedCollection<HTMLCollection>(kDocEmbeds); |
| } |
| |
| HTMLCollection* Document::scripts() { |
| return EnsureCachedCollection<HTMLCollection>(kDocScripts); |
| } |
| |
| HTMLCollection* Document::links() { |
| return EnsureCachedCollection<HTMLCollection>(kDocLinks); |
| } |
| |
| HTMLCollection* Document::forms() { |
| return EnsureCachedCollection<HTMLCollection>(kDocForms); |
| } |
| |
| HTMLCollection* Document::anchors() { |
| return EnsureCachedCollection<HTMLCollection>(kDocAnchors); |
| } |
| |
| HTMLAllCollection* Document::all() { |
| return EnsureCachedCollection<HTMLAllCollection>(kDocAll); |
| } |
| |
| HTMLCollection* Document::WindowNamedItems(const AtomicString& name) { |
| return EnsureCachedCollection<WindowNameCollection>(kWindowNamedItems, name); |
| } |
| |
| DocumentNameCollection* Document::DocumentNamedItems(const AtomicString& name) { |
| return EnsureCachedCollection<DocumentNameCollection>(kDocumentNamedItems, |
| name); |
| } |
| |
| HTMLCollection* Document::DocumentAllNamedItems(const AtomicString& name) { |
| return EnsureCachedCollection<DocumentAllNameCollection>( |
| kDocumentAllNamedItems, name); |
| } |
| |
| void Document::IncrementLazyAdsFrameCount() { |
| data_->lazy_ads_frame_count_++; |
| } |
| |
| void Document::IncrementLazyEmbedsFrameCount() { |
| data_->lazy_embeds_frame_count_++; |
| } |
| |
| void Document::IncrementImmediateChildFrameCreationCount() { |
| data_->immediate_child_frame_creation_count_++; |
| } |
| |
| int Document::GetImmediateChildFrameCreationCount() const { |
| return data_->immediate_child_frame_creation_count_; |
| } |
| |
| DOMWindow* Document::defaultView() const { |
| return dom_window_; |
| } |
| |
| AllowState Document::GetDeclarativeShadowRootAllowState() const { |
| return declarative_shadow_root_allow_state_; |
| } |
| |
| void Document::setAllowDeclarativeShadowRoots(bool val) { |
| declarative_shadow_root_allow_state_ = |
| val ? AllowState::kAllow : AllowState::kDeny; |
| } |
| |
| void Document::MaybeExecuteDelayedAsyncScripts( |
| MilestoneForDelayedAsyncScript milestone) { |
| // This is called on each paint when DelayAsyncScriptDelayType is kEachPaint, |
| // which causes regression. Cache the feature status to avoid frequent |
| // calculation. |
| static const bool delay_async_script_execution_is_enabled = |
| base::FeatureList::IsEnabled(features::kDelayAsyncScriptExecution); |
| if (!delay_async_script_execution_is_enabled) |
| return; |
| |
| // Cache for performance reason. |
| static const features::DelayAsyncScriptDelayType |
| delay_async_script_delay_type = |
| features::kDelayAsyncScriptExecutionDelayParam.Get(); |
| switch (delay_async_script_delay_type) { |
| case features::DelayAsyncScriptDelayType::kFirstPaintOrFinishedParsing: |
| // Notify the ScriptRunner if the first paint has been recorded and |
| // we're delaying async scripts until first paint or finished parsing |
| // (whichever comes first). |
| if (milestone == MilestoneForDelayedAsyncScript::kFirstPaint || |
| milestone == MilestoneForDelayedAsyncScript::kFinishedParsing) { |
| script_runner_delayer_->Deactivate(); |
| } |
| break; |
| case features::DelayAsyncScriptDelayType::kFinishedParsing: |
| // Notify the ScriptRunner if we're finished parsing and we're delaying |
| // async scripts until finished parsing occurs. |
| if (milestone == MilestoneForDelayedAsyncScript::kFinishedParsing) |
| script_runner_delayer_->Deactivate(); |
| break; |
| case features::DelayAsyncScriptDelayType::kEachLcpCandidate: |
| // Notify the ScriptRunner if a LCP candidate is reported. |
| if (milestone == MilestoneForDelayedAsyncScript::kLcpCandidate) { |
| // Flush all async scripts that are already prepared but forced to be |
| // delayed. |
| script_runner_delayer_->Deactivate(); |
| // Delay async scripts until next LCP candidate occurs or reaches the |
| // time limit. |
| script_runner_delayer_->Activate(); |
| } |
| break; |
| case features::DelayAsyncScriptDelayType::kEachPaint: |
| // Notify the ScriptRunner if paint happened. |
| if (milestone == MilestoneForDelayedAsyncScript::kPaint) { |
| // Flush all async scripts that are already prepared but forced to be |
| // delayed. |
| script_runner_delayer_->Deactivate(); |
| // Delay async scripts until next paint or reaches the time limit. |
| script_runner_delayer_->Activate(); |
| } |
| break; |
| case features::DelayAsyncScriptDelayType::kTillFirstLcpCandidate: |
| // Notify the ScriptRunner if a LCP candidate is reported. |
| if (milestone == MilestoneForDelayedAsyncScript::kLcpCandidate) { |
| // Flush all async scripts that are already prepared but forced to be |
| // delayed. |
| script_runner_delayer_->Deactivate(); |
| } |
| break; |
| } |
| } |
| |
| void Document::MarkFirstPaint() { |
| MaybeExecuteDelayedAsyncScripts(MilestoneForDelayedAsyncScript::kFirstPaint); |
| } |
| |
| void Document::OnPaintFinished() { |
| MaybeExecuteDelayedAsyncScripts(MilestoneForDelayedAsyncScript::kPaint); |
| } |
| |
| void Document::OnLargestContentfulPaintUpdated() { |
| MaybeExecuteDelayedAsyncScripts( |
| MilestoneForDelayedAsyncScript::kLcpCandidate); |
| } |
| |
| void Document::OnPrepareToStopParsing() { |
| if (render_blocking_resource_manager_) { |
| render_blocking_resource_manager_->ClearPendingParsingElements(); |
| } |
| MaybeExecuteDelayedAsyncScripts( |
| MilestoneForDelayedAsyncScript::kFinishedParsing); |
| } |
| |
| void Document::FinishedParsing() { |
| TRACE_EVENT_WITH_FLOW0("blink", "Document::FinishedParsing", |
| TRACE_ID_LOCAL(this), |
| TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); |
| DCHECK(!GetScriptableDocumentParser() || !parser_->IsParsing()); |
| DCHECK(!GetScriptableDocumentParser() || ready_state_ != kLoading); |
| SetParsingState(kInDOMContentLoaded); |
| DocumentParserTiming::From(*this).MarkParserStop(); |
| |
| // FIXME: DOMContentLoaded is dispatched synchronously, but this should be |
| // dispatched in a queued task, see https://crbug.com/961428 |
| if (document_timing_.DomContentLoadedEventStart().is_null()) |
| document_timing_.MarkDomContentLoadedEventStart(); |
| if (!ScriptForbiddenScope::IsScriptForbidden()) { |
| DispatchEvent(*Event::CreateBubble(event_type_names::kDOMContentLoaded)); |
| |
| if (LocalFrame* frame = GetFrame()) { |
| if (frame->IsAttached()) { |
| DEVTOOLS_TIMELINE_TRACE_EVENT_INSTANT( |
| "MarkDOMContent", inspector_mark_load_event::Data, frame); |
| probe::DomContentLoadedEventFired(frame); |
| } |
| } |
| } |
| |
| if (document_timing_.DomContentLoadedEventEnd().is_null()) |
| document_timing_.MarkDomContentLoadedEventEnd(); |
| SetParsingState(kFinishedParsing); |
| |
| // Ensure Custom Element callbacks are drained before DOMContentLoaded. |
| // FIXME: Remove this ad-hoc checkpoint when DOMContentLoaded is dispatched in |
| // a queued task, which will do a checkpoint anyway. https://crbug.com/425790 |
| agent_->event_loop()->PerformMicrotaskCheckpoint(); |
| |
| ScriptableDocumentParser* parser = GetScriptableDocumentParser(); |
| well_formed_ = parser && parser->WellFormed(); |
| |
| if (LocalFrame* frame = GetFrame()) { |
| // Guarantee at least one call to the client specifying a title. (If |
| // |title_| is not empty, then the title has already been dispatched.) |
| if (title_.empty()) |
| DispatchDidReceiveTitle(); |
| |
| // Don't update the layout tree if we haven't requested the main resource |
| // yet to avoid adding extra latency. Note that the first layout tree update |
| // can be expensive since it triggers the parsing of the default stylesheets |
| // which are compiled-in. |
| // FrameLoader::finishedParsing() might end up calling |
| // Document::implicitClose() if all resource loads are |
| // complete. HTMLObjectElements can start loading their resources from post |
| // attach callbacks triggered by recalcStyle(). This means if we parse out |
| // an <object> tag and then reach the end of the document without updating |
| // styles, we might not have yet started the resource load and might fire |
| // the window load event too early. To avoid this we force the styles to be |
| // up to date before calling FrameLoader::finishedParsing(). See |
| // https://bugs.webkit.org/show_bug.cgi?id=36864 starting around comment 35. |
| if (!is_initial_empty_document_ && HaveRenderBlockingStylesheetsLoaded()) { |
| // The is_initial_empty_document_ flag is only true when the document is |
| // initialized, but then it is synchronously loaded and the flag goes out |
| // of sync. Loader()->HasLoadedNonInitialEmptyDocument() is more correct. |
| // Keeping both for now behind a flag so that it's finch-testable. |
| if (GetFrame()->IsMainFrame() || |
| |
| Loader()->HasLoadedNonInitialEmptyDocument() || |
| !base::FeatureList::IsEnabled( |
| blink::features:: |
| kAvoidForcedLayoutOnInitialEmptyDocumentInSubframe)) { |
| UpdateStyleAndLayoutTree(); |
| } |
| } |
| |
| BeginLifecycleUpdatesIfRenderingReady(); |
| |
| frame->GetIdlenessDetector()->DomContentLoadedEventFired(); |
| |
| if (ShouldMarkFontPerformance()) { |
| FontPerformance::MarkDomContentLoaded(); |
| } |
| |
| frame->Loader().FinishedParsing(); |
| } |
| |
| // Schedule dropping of the ElementDataCache. We keep it alive for a while |
| // after parsing finishes so that dynamically inserted content can also |
| // benefit from sharing optimizations. Note that we don't refresh the timer |
| // on cache access since that could lead to huge caches being kept alive |
| // indefinitely by something innocuous like JS setting .innerHTML repeatedly |
| // on a timer. |
| element_data_cache_clear_timer_.StartOneShot(base::Seconds(10), FROM_HERE); |
| |
| // Parser should have picked up all preloads by now |
| fetcher_->ClearPreloads(ResourceFetcher::kClearSpeculativeMarkupPreloads); |
| } |
| |
| void Document::ElementDataCacheClearTimerFired(TimerBase*) { |
| element_data_cache_.Clear(); |
| } |
| |
| void Document::BeginLifecycleUpdatesIfRenderingReady() { |
| TRACE_EVENT2("blink", "Document::BeginLifecycleUpdatesIfRenderingReady", |
| "is_active", IsActive(), "have_render_blocking_resources_loaded", |
| HaveRenderBlockingResourcesLoaded()); |
| if (!IsActive()) |
| return; |
| if (!HaveRenderBlockingResourcesLoaded()) |
| return; |
| if (!rendering_has_begun_) { |
| RenderBlockingMetricsReporter::From(*this).RenderBlockingResourcesLoaded(); |
| rendering_has_begun_ = true; |
| } |
| // TODO(japhet): If IsActive() is true, View() should always be non-null. |
| // Speculative fix for https://crbug.com/1171891 |
| if (auto* view = View()) { |
| view->BeginLifecycleUpdates(); |
| } else { |
| NOTREACHED(); |
| base::debug::DumpWithoutCrashing(); |
| } |
| } |
| |
| Vector<IconURL> Document::IconURLs(int icon_types_mask) { |
| IconURL first_favicon; |
| IconURL first_touch_icon; |
| IconURL first_touch_precomposed_icon; |
| Vector<IconURL> secondary_icons; |
| |
| using TraversalFunction = HTMLLinkElement* (*)(const Node&); |
| TraversalFunction find_next_candidate = |
| &Traversal<HTMLLinkElement>::NextSibling; |
| |
| HTMLLinkElement* first_element = nullptr; |
| if (head()) { |
| first_element = Traversal<HTMLLinkElement>::FirstChild(*head()); |
| } else if (IsSVGDocument() && IsA<SVGSVGElement>(documentElement())) { |
| first_element = Traversal<HTMLLinkElement>::FirstWithin(*documentElement()); |
| find_next_candidate = &Traversal<HTMLLinkElement>::Next; |
| } |
| |
| // Start from the first child node so that icons seen later take precedence as |
| // required by the spec. |
| for (HTMLLinkElement* link_element = first_element; link_element; |
| link_element = find_next_candidate(*link_element)) { |
| if (!((1 << static_cast<int>(link_element->GetIconType())) & |
| icon_types_mask)) { |
| continue; |
| } |
| if (link_element->Href().IsEmpty()) |
| continue; |
| |
| if (!link_element->Media().empty()) { |
| auto* media_query = |
| GetMediaQueryMatcher().MatchMedia(link_element->Media()); |
| if (!media_query->matches()) |
| continue; |
| } |
| |
| IconURL new_url(link_element->Href(), link_element->IconSizes(), |
| link_element->GetType(), link_element->GetIconType()); |
| if (link_element->GetIconType() == |
| mojom::blink::FaviconIconType::kFavicon) { |
| if (first_favicon.icon_type_ != mojom::blink::FaviconIconType::kInvalid) |
| secondary_icons.push_back(first_favicon); |
| first_favicon = new_url; |
| } else if (link_element->GetIconType() == |
| mojom::blink::FaviconIconType::kTouchIcon) { |
| if (first_touch_icon.icon_type_ != |
| mojom::blink::FaviconIconType::kInvalid) |
| secondary_icons.push_back(first_touch_icon); |
| first_touch_icon = new_url; |
| } else if (link_element->GetIconType() == |
| mojom::blink::FaviconIconType::kTouchPrecomposedIcon) { |
| if (first_touch_precomposed_icon.icon_type_ != |
| mojom::blink::FaviconIconType::kInvalid) |
| secondary_icons.push_back(first_touch_precomposed_icon); |
| first_touch_precomposed_icon = new_url; |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| Vector<IconURL> icon_urls; |
| if (first_favicon.icon_type_ != mojom::blink::FaviconIconType::kInvalid) { |
| icon_urls.push_back(first_favicon); |
| } else if (url_.ProtocolIsInHTTPFamily() && |
| icon_types_mask & 1 << static_cast<int>( |
| mojom::blink::FaviconIconType::kFavicon)) { |
| IconURL default_favicon = IconURL::DefaultFavicon(url_); |
| if (DefaultFaviconAllowedByCSP(this, default_favicon)) |
| icon_urls.push_back(std::move(default_favicon)); |
| } |
| |
| if (first_touch_icon.icon_type_ != mojom::blink::FaviconIconType::kInvalid) |
| icon_urls.push_back(first_touch_icon); |
| if (first_touch_precomposed_icon.icon_type_ != |
| mojom::blink::FaviconIconType::kInvalid) |
| icon_urls.push_back(first_touch_precomposed_icon); |
| for (int i = secondary_icons.size() - 1; i >= 0; --i) |
| icon_urls.push_back(secondary_icons[i]); |
| return icon_urls; |
| } |
| |
| void Document::UpdateThemeColorCache() { |
| meta_theme_color_elements_.clear(); |
| auto* root_element = documentElement(); |
| if (!root_element) |
| return; |
| |
| for (HTMLMetaElement& meta_element : |
| Traversal<HTMLMetaElement>::DescendantsOf(*root_element)) { |
| if (EqualIgnoringASCIICase(meta_element.GetName(), "theme-color")) |
| meta_theme_color_elements_.push_back(meta_element); |
| } |
| } |
| |
| std::optional<Color> Document::ThemeColor() { |
| // Returns the color of the first meta[name=theme-color] element in |
| // tree order that matches and is valid. |
| // https://html.spec.whatwg.org/multipage/semantics.html#meta-theme-color |
| for (auto& element : meta_theme_color_elements_) { |
| if (!element->Media().empty()) { |
| auto* media_query = GetMediaQueryMatcher().MatchMedia( |
| element->Media().GetString().StripWhiteSpace()); |
| if (!media_query->matches()) |
| continue; |
| } |
| Color color; |
| if (CSSParser::ParseColor( |
| color, element->Content().GetString().StripWhiteSpace(), true)) { |
| return color; |
| } |
| } |
| return std::nullopt; |
| } |
| |
| void Document::UpdateAppTitle() { |
| auto* root_element = documentElement(); |
| if (!root_element) { |
| return; |
| } |
| |
| for (HTMLMetaElement& meta_element : |
| Traversal<HTMLMetaElement>::DescendantsOf(*root_element)) { |
| if (EqualIgnoringASCIICase(meta_element.GetName(), "app-title")) { |
| GetFrame()->GetLocalFrameHostRemote().UpdateAppTitle( |
| meta_element.Content().GetString()); |
| return; |
| } |
| } |
| |
| // Handle case of meta tag being removed by setting app title to empty string. |
| GetFrame()->GetLocalFrameHostRemote().UpdateAppTitle(String("")); |
| } |
| |
| void Document::ColorSchemeMetaChanged() { |
| const CSSValue* color_scheme = nullptr; |
| if (auto* root_element = documentElement()) { |
| for (HTMLMetaElement& meta_element : |
| Traversal<HTMLMetaElement>::DescendantsOf(*root_element)) { |
| if (EqualIgnoringASCIICase(meta_element.GetName(), "color-scheme")) { |
| if ((color_scheme = CSSParser::ParseSingleValue( |
| CSSPropertyID::kColorScheme, |
| meta_element.Content().GetString().StripWhiteSpace(), |
| ElementSheet().Contents()->ParserContext()))) { |
| break; |
| } |
| } |
| } |
| } |
| GetStyleEngine().SetPageColorSchemes(color_scheme); |
| } |
| |
| void Document::SupportsReducedMotionMetaChanged() { |
| auto* root_element = documentElement(); |
| if (!root_element) |
| return; |
| |
| bool supports_reduced_motion = false; |
| for (HTMLMetaElement& meta_element : |
| Traversal<HTMLMetaElement>::DescendantsOf(*root_element)) { |
| if (EqualIgnoringASCIICase(meta_element.GetName(), |
| "supports-reduced-motion")) { |
| SpaceSplitString split_content( |
| AtomicString(meta_element.Content().GetString().LowerASCII())); |
| if (split_content.Contains(AtomicString("reduce"))) { |
| supports_reduced_motion = true; |
| } |
| break; |
| } |
| } |
| // TODO(crbug.com/1287263): Recreate existing interpolations. |
| supports_reduced_motion_ = supports_reduced_motion; |
| } |
| |
| bool Document::ShouldForceReduceMotion() const { |
| if (!RuntimeEnabledFeatures::ForceReduceMotionEnabled(GetExecutionContext())) |
| return false; |
| |
| return GetFrame()->GetSettings()->GetPrefersReducedMotion() && |
| !supports_reduced_motion_; |
| } |
| |
| static HTMLLinkElement* GetLinkElement(const Document* doc, |
| bool (*match_fn)(HTMLLinkElement&)) { |
| HTMLHeadElement* head = doc->head(); |
| if (!head) |
| return nullptr; |
| |
| // The first matching link element is used. Others are ignored. |
| for (HTMLLinkElement& link_element : |
| Traversal<HTMLLinkElement>::ChildrenOf(*head)) { |
| if (match_fn(link_element)) |
| return &link_element; |
| } |
| return nullptr; |
| } |
| |
| HTMLLinkElement* Document::LinkManifest() const { |
| return GetLinkElement(this, [](HTMLLinkElement& link_element) { |
| return link_element.RelAttribute().IsManifest(); |
| }); |
| } |
| |
| HTMLLinkElement* Document::LinkCanonical() const { |
| return GetLinkElement(this, [](HTMLLinkElement& link_element) { |
| return link_element.RelAttribute().IsCanonical(); |
| }); |
| } |
| |
| bool Document::AllowedToUseDynamicMarkUpInsertion( |
| const char* api_name, |
| ExceptionState& exception_state) { |
| if (!RuntimeEnabledFeatures::ExperimentalPoliciesEnabled()) { |
| return true; |
| } |
| if (!GetFrame() || GetExecutionContext()->IsFeatureEnabled( |
| mojom::blink::DocumentPolicyFeature::kDocumentWrite, |
| ReportOptions::kReportOnFailure)) { |
| return true; |
| } |
| |
| // TODO(ekaramad): Throwing an exception seems an ideal resolution to mishaps |
| // in using the API against the policy. But this cannot be applied to cross- |
| // origin as there are security risks involved. We should perhaps unload the |
| // whole frame instead of throwing. |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotAllowedError, |
| String::Format( |
| "The use of method '%s' has been blocked by permissions policy. The " |
| "feature " |
| "'document-write' is disabled in this document.", |
| api_name)); |
| return false; |
| } |
| |
| ukm::UkmRecorder* Document::UkmRecorder() { |
| if (!ukm_recorder_) { |
| mojo::Remote<ukm::mojom::UkmRecorderFactory> factory; |
| Platform::Current()->GetBrowserInterfaceBroker()->GetInterface( |
| factory.BindNewPipeAndPassReceiver()); |
| auto mojo_recorder = ukm::MojoUkmRecorder::Create(*factory); |
| if (WebTestSupport::IsRunningWebTest()) { |
| ukm::DelegatingUkmRecorder::Get()->AddDelegate( |
| mojo_recorder->GetWeakPtr()); |
| } |
| ukm_recorder_ = std::move(mojo_recorder); |
| } |
| |
| if (WebTestSupport::IsRunningWebTest()) { |
| return ukm::DelegatingUkmRecorder::Get(); |
| } else { |
| return ukm_recorder_.get(); |
| } |
| } |
| |
| ukm::SourceId Document::UkmSourceID() const { |
| return ukm_source_id_; |
| } |
| |
| FontMatchingMetrics* Document::GetFontMatchingMetrics() { |
| if (Lifecycle().GetState() >= DocumentLifecycle::LifecycleState::kStopping) { |
| return nullptr; |
| } |
| if (font_matching_metrics_) |
| return font_matching_metrics_.get(); |
| font_matching_metrics_ = std::make_unique<FontMatchingMetrics>( |
| dom_window_, GetTaskRunner(TaskType::kInternalDefault)); |
| return font_matching_metrics_.get(); |
| } |
| |
| bool Document::AllowInlineEventHandler(Node* node, |
| EventListener* listener, |
| const String& context_url, |
| const WTF::OrdinalNumber& context_line) { |
| auto* element = DynamicTo<Element>(node); |
| // HTML says that inline script needs browsing context to create its execution |
| // environment. |
| // http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#event-handler-attributes |
| // Also, if the listening node came from other document, which happens on |
| // context-less event dispatching, we also need to ask the owner document of |
| // the node. |
| LocalDOMWindow* window = domWindow(); |
| if (!window) |
| return false; |
| |
| // https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-content-attributes |
| // Step 5.1. If the Should element's inline behavior be blocked by Content |
| // Security Policy? algorithm returns "Blocked" when executed upon element, |
| // "script attribute", and value, then return. [CSP] [spec text] |
| if (!window->GetContentSecurityPolicyForCurrentWorld()->AllowInline( |
| ContentSecurityPolicy::InlineType::kScriptAttribute, element, |
| listener->ScriptBody(), String() /* nonce */, context_url, |
| context_line)) |
| return false; |
| |
| if (!window->CanExecuteScripts(kNotAboutToExecuteScript)) |
| return false; |
| if (node && node->GetDocument() != this && |
| !node->GetDocument().AllowInlineEventHandler(node, listener, context_url, |
| context_line)) |
| return false; |
| |
| return true; |
| } |
| |
| void Document::UpdateSelectionAfterLayout() { |
| should_update_selection_after_layout_ = false; |
| Element* element = FocusedElement(); |
| if (!element) |
| return; |
| if (element->IsFocusable()) |
| element->UpdateSelectionOnFocus(SelectionBehaviorOnFocus::kRestore); |
| } |
| |
| void Document::AttachRange(Range* range) { |
| DCHECK(!ranges_.Contains(range)); |
| ranges_.insert(range); |
| } |
| |
| void Document::DetachRange(Range* range) { |
| // We don't DCHECK ranges_.contains(range) to allow us to call this |
| // unconditionally to fix: https://bugs.webkit.org/show_bug.cgi?id=26044 |
| ranges_.erase(range); |
| } |
| |
| void Document::InitDNSPrefetch() { |
| Settings* settings = GetSettings(); |
| |
| have_explicitly_disabled_dns_prefetch_ = false; |
| is_dns_prefetch_enabled_ = |
| settings && settings->GetDNSPrefetchingEnabled() && |
| dom_window_->GetSecurityContext().GetSecurityOrigin()->Protocol() == |
| "http"; |
| |
| // Inherit DNS prefetch opt-out from parent frame |
| if (Document* parent = ParentDocument()) { |
| if (!parent->IsDNSPrefetchEnabled()) |
| is_dns_prefetch_enabled_ = false; |
| } |
| } |
| |
| void Document::ParseDNSPrefetchControlHeader( |
| const String& dns_prefetch_control) { |
| if (EqualIgnoringASCIICase(dns_prefetch_control, "on") && |
| !have_explicitly_disabled_dns_prefetch_) { |
| is_dns_prefetch_enabled_ = true; |
| return; |
| } |
| |
| is_dns_prefetch_enabled_ = false; |
| have_explicitly_disabled_dns_prefetch_ = true; |
| } |
| |
| IntersectionObserverController* Document::GetIntersectionObserverController() { |
| return intersection_observer_controller_; |
| } |
| |
| IntersectionObserverController& |
| Document::EnsureIntersectionObserverController() { |
| if (!intersection_observer_controller_) { |
| intersection_observer_controller_ = |
| MakeGarbageCollected<IntersectionObserverController>( |
| GetExecutionContext()); |
| } |
| return *intersection_observer_controller_; |
| } |
| |
| ElementIntersectionObserverData* |
| Document::DocumentExplicitRootIntersectionObserverData() const { |
| return document_explicit_root_intersection_observer_data_.Get(); |
| } |
| |
| ElementIntersectionObserverData& |
| Document::EnsureDocumentExplicitRootIntersectionObserverData() { |
| if (!document_explicit_root_intersection_observer_data_) { |
| document_explicit_root_intersection_observer_data_ = |
| MakeGarbageCollected<ElementIntersectionObserverData>(); |
| } |
| return *document_explicit_root_intersection_observer_data_; |
| } |
| |
| const ScriptRegexp& Document::EnsureEmailRegexp() const { |
| if (!data_->email_regexp_) { |
| data_->email_regexp_ = |
| EmailInputType::CreateEmailRegexp(GetAgent().isolate()); |
| } |
| return *data_->email_regexp_; |
| } |
| |
| void Document::SetMediaFeatureEvaluated(int feature) { |
| evaluated_media_features_ |= (1 << feature); |
| } |
| |
| bool Document::WasMediaFeatureEvaluated(int feature) { |
| return (evaluated_media_features_ >> feature) & 1; |
| } |
| |
| void Document::AddConsoleMessage(ConsoleMessage* message, |
| bool discard_duplicates) const { |
| // Don't let non-attached Documents spam the console. |
| if (domWindow()) |
| domWindow()->AddConsoleMessage(message, discard_duplicates); |
| } |
| |
| void Document::AddToTopLayer(Element* element, const Element* before) { |
| if (element->IsInTopLayer()) { |
| if (IsScheduledForTopLayerRemoval(element)) { |
| // Since the html spec currently says close() should remove the dialog |
| // element from the top layer immediately, we need to remove any |
| // transitioning elements out of the top layer in order to keep the |
| // behavior of re-adding the element to the end of the top layer list for |
| // cases where style change events do not happen between close() and |
| // showModal(): |
| // |
| // dialog.close(); |
| // dialog.showModal(); |
| RemoveFromTopLayerImmediately(element); |
| } else { |
| return; |
| } |
| } |
| |
| DCHECK(!IsScheduledForTopLayerRemoval(element)); |
| DCHECK(!before || top_layer_elements_.Contains(before)); |
| |
| if (before) { |
| DCHECK(element->IsBackdropPseudoElement()) |
| << "If this invariant changes, we might need to revisit Container " |
| "Queries for top layer elements."; |
| wtf_size_t before_position = top_layer_elements_.Find(before); |
| top_layer_elements_.insert(before_position, element); |
| } else { |
| top_layer_elements_.push_back(element); |
| } |
| |
| element->SetIsInTopLayer(true); |
| display_lock_document_state_->ElementAddedToTopLayer(element); |
| |
| probe::TopLayerElementsChanged(this); |
| } |
| |
| void Document::ScheduleForTopLayerRemoval(Element* element, |
| TopLayerReason reason) { |
| if (!element->IsInTopLayer()) { |
| return; |
| } |
| |
| std::optional<TopLayerReason> existing_pending_removal = std::nullopt; |
| for (const auto& pending_removal : top_layer_elements_pending_removal_) { |
| if (pending_removal->element == element) { |
| existing_pending_removal = pending_removal->reason; |
| break; |
| } |
| } |
| |
| if (existing_pending_removal) { |
| CHECK_EQ(*existing_pending_removal, reason); |
| } else { |
| top_layer_elements_pending_removal_.push_back( |
| MakeGarbageCollected<TopLayerPendingRemoval>(element, reason)); |
| } |
| ScheduleLayoutTreeUpdateIfNeeded(); |
| } |
| |
| void Document::RemoveFinishedTopLayerElements() { |
| if (top_layer_elements_pending_removal_.empty()) { |
| return; |
| } |
| HeapVector<Member<Element>> to_remove; |
| for (const auto& pending_removal : top_layer_elements_pending_removal_) { |
| Element* element = pending_removal->element; |
| const ComputedStyle* style = element->GetComputedStyle(); |
| if (!style || style->Overlay() == EOverlay::kNone) { |
| to_remove.push_back(element); |
| } |
| } |
| for (Element* remove_element : to_remove) { |
| RemoveFromTopLayerImmediately(remove_element); |
| } |
| } |
| |
| void Document::RemoveFromTopLayerImmediately(Element* element) { |
| if (!element->IsInTopLayer()) { |
| return; |
| } |
| wtf_size_t position = top_layer_elements_.Find(element); |
| DCHECK_NE(position, kNotFound); |
| top_layer_elements_.EraseAt(position); |
| for (unsigned i = 0; i < top_layer_elements_pending_removal_.size(); i++) { |
| if (top_layer_elements_pending_removal_[i]->element == element) { |
| top_layer_elements_pending_removal_.EraseAt(i); |
| break; |
| } |
| } |
| element->SetIsInTopLayer(false); |
| display_lock_document_state_->ElementRemovedFromTopLayer(element); |
| |
| probe::TopLayerElementsChanged(this); |
| } |
| |
| std::optional<Document::TopLayerReason> Document::IsScheduledForTopLayerRemoval( |
| Element* element) const { |
| for (const auto& entry : top_layer_elements_pending_removal_) { |
| if (entry->element == element) { |
| return entry->reason; |
| } |
| } |
| return std::nullopt; |
| } |
| |
| HTMLDialogElement* Document::ActiveModalDialog() const { |
| for (const auto& element : base::Reversed(top_layer_elements_)) { |
| if (auto* dialog = DynamicTo<HTMLDialogElement>(*element)) { |
| if (dialog->IsModal()) { |
| // Modal dialogs transitioning out after being closed are not considered |
| // to be active. |
| if (!IsScheduledForTopLayerRemoval(dialog)) { |
| return dialog; |
| } |
| } |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| HTMLElement* Document::TopmostPopoverOrHint() const { |
| if (!PopoverHintStack().empty()) { |
| CHECK(RuntimeEnabledFeatures::HTMLPopoverHintEnabled()); |
| return PopoverHintStack().back(); |
| } |
| if (!PopoverAutoStack().empty()) { |
| return PopoverAutoStack().back(); |
| } |
| return nullptr; |
| } |
| void Document::SetPopoverPointerdownTarget(const HTMLElement* popover) { |
| DCHECK(!popover || popover->HasPopoverAttribute()); |
| popover_pointerdown_target_ = popover; |
| } |
| |
| void Document::exitPointerLock() { |
| if (!GetPage()) |
| return; |
| if (Element* target = GetPage()->GetPointerLockController().GetElement()) { |
| if (target->GetDocument() != this) |
| return; |
| GetPage()->GetPointerLockController().ExitPointerLock(); |
| } |
| } |
| |
| Element* Document::PointerLockElement() const { |
| if (!GetPage() || GetPage()->GetPointerLockController().LockPending()) |
| return nullptr; |
| if (Element* element = GetPage()->GetPointerLockController().GetElement()) { |
| if (element->GetDocument() == this) |
| return element; |
| } |
| return nullptr; |
| } |
| |
| void Document::DecrementLoadEventDelayCount() { |
| DCHECK(load_event_delay_count_); |
| --load_event_delay_count_; |
| |
| if (!load_event_delay_count_) |
| CheckLoadEventSoon(); |
| } |
| |
| void Document::DecrementLoadEventDelayCountAndCheckLoadEvent() { |
| DCHECK(load_event_delay_count_); |
| --load_event_delay_count_; |
| |
| if (!load_event_delay_count_) |
| CheckCompleted(); |
| } |
| |
| void Document::CheckLoadEventSoon() { |
| if (GetFrame() && !load_event_delay_timer_.IsActive()) |
| load_event_delay_timer_.StartOneShot(base::TimeDelta(), FROM_HERE); |
| } |
| |
| bool Document::IsDelayingLoadEvent() { |
| return load_event_delay_count_; |
| } |
| |
| void Document::LoadEventDelayTimerFired(TimerBase*) { |
| CheckCompleted(); |
| } |
| |
| void Document::LoadPluginsSoon() { |
| // FIXME: Remove this timer once we don't need to compute layout to load |
| // plugins. |
| if (!plugin_loading_timer_.IsActive()) |
| plugin_loading_timer_.StartOneShot(base::TimeDelta(), FROM_HERE); |
| } |
| |
| void Document::PluginLoadingTimerFired(TimerBase*) { |
| UpdateStyleAndLayout(DocumentUpdateReason::kPlugin); |
| } |
| |
| ScriptedAnimationController& Document::GetScriptedAnimationController() { |
| return *scripted_animation_controller_; |
| } |
| |
| int Document::RequestAnimationFrame(FrameCallback* callback) { |
| return scripted_animation_controller_->RegisterFrameCallback(callback); |
| } |
| |
| void Document::CancelAnimationFrame(int id) { |
| scripted_animation_controller_->CancelFrameCallback(id); |
| } |
| |
| ScriptedIdleTaskController& Document::EnsureScriptedIdleTaskController() { |
| if (!scripted_idle_task_controller_) { |
| scripted_idle_task_controller_ = |
| ScriptedIdleTaskController::Create(domWindow()); |
| // We need to make sure that we don't start up if we're detached. |
| if (!domWindow() || domWindow()->IsContextDestroyed()) { |
| scripted_idle_task_controller_->ContextLifecycleStateChanged( |
| mojom::FrameLifecycleState::kFrozen); |
| } |
| } |
| return *scripted_idle_task_controller_; |
| } |
| |
| int Document::RequestIdleCallback(IdleTask* idle_task, |
| const IdleRequestOptions* options) { |
| return EnsureScriptedIdleTaskController().RegisterCallback(idle_task, |
| options); |
| } |
| |
| void Document::CancelIdleCallback(int id) { |
| if (!scripted_idle_task_controller_) |
| return; |
| scripted_idle_task_controller_->CancelCallback(id); |
| } |
| |
| DocumentLoader* Document::Loader() const { |
| return GetFrame() ? GetFrame()->Loader().GetDocumentLoader() : nullptr; |
| } |
| |
| Node* EventTargetNodeForDocument(Document* doc) { |
| if (!doc) |
| return nullptr; |
| Node* node = doc->FocusedElement(); |
| auto* plugin_document = DynamicTo<PluginDocument>(doc); |
| if (plugin_document && !node) { |
| node = plugin_document->PluginNode(); |
| } |
| if (!node && IsA<HTMLDocument>(doc)) |
| node = doc->body(); |
| if (!node) |
| node = doc->documentElement(); |
| return node; |
| } |
| |
| void Document::AdjustQuadsForScrollAndAbsoluteZoom( |
| Vector<gfx::QuadF>& quads, |
| const LayoutObject& layout_object) const { |
| if (!View()) { |
| return; |
| } |
| |
| for (auto& quad : quads) |
| AdjustForAbsoluteZoom::AdjustQuadMaybeExcludingCSSZoom(quad, layout_object); |
| } |
| |
| void Document::AdjustRectForScrollAndAbsoluteZoom( |
| gfx::RectF& rect, |
| const LayoutObject& layout_object) const { |
| if (!View()) { |
| return; |
| } |
| |
| AdjustForAbsoluteZoom::AdjustRectMaybeExcludingCSSZoom(rect, layout_object); |
| } |
| |
| void Document::SetForceSynchronousParsingForTesting(bool enabled) { |
| g_force_synchronous_parsing_for_testing = enabled; |
| } |
| |
| bool Document::ForceSynchronousParsingForTesting() { |
| return g_force_synchronous_parsing_for_testing; |
| } |
| |
| void Document::UpdateHoverActiveState(bool is_active, |
| bool update_active_chain, |
| Element* inner_element) { |
| if (is_active && GetFrame()) |
| GetFrame()->GetEventHandler().NotifyElementActivated(); |
| |
| Element* inner_element_in_document = inner_element; |
| |
| while (inner_element_in_document && |
| inner_element_in_document->GetDocument() != this) { |
| inner_element_in_document->GetDocument().UpdateHoverActiveState( |
| is_active, update_active_chain, inner_element_in_document); |
| inner_element_in_document = |
| inner_element_in_document->GetDocument().LocalOwner(); |
| } |
| |
| UpdateActiveState(is_active, update_active_chain, inner_element_in_document); |
| UpdateHoverState(inner_element_in_document); |
| } |
| |
| void Document::UpdateActiveState(bool is_active, |
| bool update_active_chain, |
| Element* new_active_element) { |
| Element* old_active_element = GetActiveElement(); |
| if (old_active_element && !is_active) { |
| // The oldActiveElement layoutObject is null, dropped on :active by setting |
| // display: none, for instance. We still need to clear the ActiveChain as |
| // the mouse is released. |
| for (Element* element = old_active_element; element; |
| element = FlatTreeTraversal::ParentElement(*element)) { |
| element->SetActive(false); |
| user_action_elements_.SetInActiveChain(element, false); |
| } |
| SetActiveElement(nullptr); |
| } else { |
| if (!old_active_element && new_active_element && is_active) { |
| // We are setting the :active chain and freezing it. If future moves |
| // happen, they will need to reference this chain. |
| for (Element* element = new_active_element; element; |
| element = FlatTreeTraversal::ParentElement(*element)) { |
| user_action_elements_.SetInActiveChain(element, true); |
| } |
| SetActiveElement(new_active_element); |
| } |
| } |
| |
| // If the mouse has just been pressed, set :active on the chain. Those (and |
| // only those) nodes should remain :active until the mouse is released. |
| bool allow_active_changes = !old_active_element && GetActiveElement(); |
| if (!allow_active_changes) |
| return; |
| |
| DCHECK(is_active); |
| |
| Element* new_element = SkipDisplayNoneAncestors(new_active_element); |
| |
| // Now set the active state for our new object up to the root. If the mouse |
| // is down and if this is a mouse move event, we want to restrict changes in |
| // :active to only apply to elements that are in the :active chain that we |
| // froze at the time the mouse went down. |
| for (Element* curr = new_element; curr; |
| curr = FlatTreeTraversal::ParentElement(*curr)) { |
| if (update_active_chain || curr->InActiveChain()) |
| curr->SetActive(true); |
| } |
| } |
| |
| void Document::UpdateHoverState(Element* inner_element_in_document) { |
| Element* old_hover_element = HoverElement(); |
| |
| // The passed in innerElement may not be a result of a hit test for the |
| // current up-to-date flat/layout tree. That means the element may be |
| // display:none at this point. Skip up the ancestor chain until we reach an |
| // element with a layoutObject or a display:contents element. |
| Element* new_hover_element = |
| SkipDisplayNoneAncestors(inner_element_in_document); |
| |
| if (old_hover_element == new_hover_element) |
| return; |
| |
| // Update our current hover element. |
| SetHoverElement(new_hover_element); |
| |
| Node* ancestor_element = nullptr; |
| if (old_hover_element && old_hover_element->isConnected() && |
| new_hover_element) { |
| Node* ancestor = FlatTreeTraversal::CommonAncestor(*old_hover_element, |
| *new_hover_element); |
| if (auto* element = DynamicTo<Element>(ancestor)) |
| ancestor_element = element; |
| } |
| |
| HeapVector<Member<Element>, 32> elements_to_remove_from_chain; |
| HeapVector<Member<Element>, 32> elements_to_add_to_hover_chain; |
| |
| // The old hover path only needs to be cleared up to (and not including) the |
| // common ancestor; |
| // |
| // TODO(emilio): old_hover_element may be disconnected from the tree already. |
| if (old_hover_element && old_hover_element->isConnected()) { |
| for (Element* curr = old_hover_element; curr && curr != ancestor_element; |
| curr = FlatTreeTraversal::ParentElement(*curr)) { |
| elements_to_remove_from_chain.push_back(curr); |
| } |
| } |
| |
| // Now set the hover state for our new object up to the root. |
| for (Element* curr = new_hover_element; curr; |
| curr = FlatTreeTraversal::ParentElement(*curr)) { |
| elements_to_add_to_hover_chain.push_back(curr); |
| } |
| |
| for (Element* element : elements_to_remove_from_chain) |
| element->SetHovered(false); |
| |
| bool saw_common_ancestor = false; |
| for (Element* element : elements_to_add_to_hover_chain) { |
| if (element == ancestor_element) |
| saw_common_ancestor = true; |
| if (!saw_common_ancestor || element == hover_element_) |
| element->SetHovered(true); |
| } |
| } |
| |
| bool Document::HaveScriptBlockingStylesheetsLoaded() const { |
| return style_engine_->HaveScriptBlockingStylesheetsLoaded(); |
| } |
| |
| bool Document::HaveRenderBlockingStylesheetsLoaded() const { |
| return !render_blocking_resource_manager_ || |
| !render_blocking_resource_manager_->HasPendingStylesheets(); |
| } |
| |
| bool Document::HaveRenderBlockingResourcesLoaded() const { |
| return !render_blocking_resource_manager_ || |
| !render_blocking_resource_manager_->HasRenderBlockingResources(); |
| } |
| |
| Locale& Document::GetCachedLocale(const AtomicString& locale) { |
| AtomicString locale_key = locale; |
| if (locale.empty() || |
| !RuntimeEnabledFeatures::LangAttributeAwareFormControlUIEnabled()) |
| return Locale::DefaultLocale(); |
| LocaleIdentifierToLocaleMap::AddResult result = |
| locale_cache_.insert(locale_key, nullptr); |
| if (result.is_new_entry) |
| result.stored_value->value = Locale::Create(locale_key); |
| return *(result.stored_value->value); |
| } |
| |
| AnimationClock& Document::GetAnimationClock() { |
| return animation_clock_; |
| } |
| |
| const AnimationClock& Document::GetAnimationClock() const { |
| return animation_clock_; |
| } |
| |
| Document& Document::EnsureTemplateDocument() { |
| if (IsTemplateDocument()) |
| return *this; |
| |
| if (template_document_) |
| return *template_document_; |
| |
| if (IsA<HTMLDocument>(this)) { |
| template_document_ = MakeGarbageCollected<HTMLDocument>( |
| DocumentInit::Create() |
| .WithExecutionContext(execution_context_.Get()) |
| .WithAgent(GetAgent()) |
| .WithURL(BlankURL())); |
| } else { |
| template_document_ = MakeGarbageCollected<Document>( |
| DocumentInit::Create() |
| .WithExecutionContext(execution_context_.Get()) |
| .WithAgent(GetAgent()) |
| .WithURL(BlankURL())); |
| } |
| |
| template_document_->template_document_host_ = this; // balanced in dtor. |
| |
| return *template_document_.Get(); |
| } |
| |
| void Document::DidChangeFormRelatedElementDynamically( |
| HTMLElement* element, |
| WebFormRelatedChangeType form_related_change) { |
| if (!GetFrame() || !GetFrame()->GetPage() || !HasFinishedParsing() || |
| !GetFrame()->IsAttached()) { |
| return; |
| } |
| |
| GetFrame() |
| ->GetPage() |
| ->GetChromeClient() |
| .DidChangeFormRelatedElementDynamically(GetFrame(), element, |
| form_related_change); |
| } |
| |
| float Document::DevicePixelRatio() const { |
| return GetFrame() ? GetFrame()->DevicePixelRatio() : 1.0; |
| } |
| |
| TextAutosizer* Document::GetTextAutosizer() { |
| if (!text_autosizer_) |
| text_autosizer_ = MakeGarbageCollected<TextAutosizer>(this); |
| return text_autosizer_.Get(); |
| } |
| |
| bool Document::SetPseudoStateForTesting(Element& element, |
| const String& pseudo, |
| bool matches) { |
| DCHECK(WebTestSupport::IsRunningWebTest()); |
| auto& set = UserActionElements(); |
| if (pseudo == ":focus") { |
| set.SetFocused(&element, matches); |
| element.PseudoStateChangedForTesting(CSSSelector::kPseudoFocus); |
| } else if (pseudo == ":focus-within") { |
| set.SetHasFocusWithin(&element, matches); |
| element.PseudoStateChangedForTesting(CSSSelector::kPseudoFocusWithin); |
| } else if (pseudo == ":active") { |
| set.SetActive(&element, matches); |
| element.PseudoStateChangedForTesting(CSSSelector::kPseudoActive); |
| } else if (pseudo == ":hover") { |
| set.SetHovered(&element, matches); |
| element.PseudoStateChangedForTesting(CSSSelector::kPseudoHover); |
| } else { |
| return false; |
| } |
| return true; |
| } |
| |
| void Document::EnqueueAutofocusCandidate(Element& element) { |
| // https://html.spec.whatwg.org/C#the-autofocus-attribute |
| // 7. If topDocument's autofocus processed flag is false, then remove the |
| // element from topDocument's autofocus candidates, and append the element |
| // to topDocument's autofocus candidates. |
| if (autofocus_processed_flag_) |
| return; |
| wtf_size_t index = autofocus_candidates_.Find(&element); |
| if (index != WTF::kNotFound) |
| autofocus_candidates_.EraseAt(index); |
| autofocus_candidates_.push_back(element); |
| } |
| |
| bool Document::HasAutofocusCandidates() const { |
| return autofocus_candidates_.size() > 0; |
| } |
| |
| // https://html.spec.whatwg.org/C/#flush-autofocus-candidates |
| void Document::FlushAutofocusCandidates() { |
| // 1. If topDocument's autofocus processed flag is true, then return. |
| if (autofocus_processed_flag_) |
| return; |
| |
| // 3. If candidates is empty, then return. |
| if (autofocus_candidates_.empty()) |
| return; |
| |
| // 4. If topDocument's focused area is not topDocument itself, or |
| // topDocument's URL's fragment is not empty, then: |
| // 1. Empty candidates. |
| // 2. Set topDocument's autofocus processed flag to true. |
| // 3. Return. |
| if (AdjustedFocusedElement()) { |
| autofocus_candidates_.clear(); |
| autofocus_processed_flag_ = true; |
| AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| mojom::ConsoleMessageSource::kRendering, |
| mojom::ConsoleMessageLevel::kInfo, |
| "Autofocus processing was blocked because a " |
| "document already has a focused element.")); |
| return; |
| } |
| if (CssTarget()) { |
| autofocus_candidates_.clear(); |
| autofocus_processed_flag_ = true; |
| AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| mojom::ConsoleMessageSource::kRendering, |
| mojom::ConsoleMessageLevel::kInfo, |
| "Autofocus processing was blocked because a " |
| "document's URL has a fragment '#" + |
| Url().FragmentIdentifier() + "'.")); |
| return; |
| } |
| |
| // 5. While candidates is not empty: |
| while (!autofocus_candidates_.empty()) { |
| // 5.1. Let element be candidates[0]. |
| Element& element = *autofocus_candidates_[0]; |
| |
| // 5.2. Let doc be element's node document. |
| Document* doc = &element.GetDocument(); |
| |
| // 5.3. If doc is not fully active, then remove element from candidates, |
| // and continue. |
| // 5.4. If doc's browsing context's top-level browsing context is not same |
| // as topDocument's browsing context, then remove element from candidates, |
| // and continue. |
| if (&doc->TopDocument() != this) { |
| autofocus_candidates_.EraseAt(0); |
| continue; |
| } |
| |
| // The element is in the fallback content of an OBJECT of which |
| // fallback state is not fixed yet. |
| // TODO(tkent): Standardize this behavior. |
| if (IsInIndeterminateObjectAncestor(&element)) { |
| return; |
| } |
| |
| // 5.5. If doc's script-blocking style sheet counter is greater than 0, |
| // then return. |
| // TODO(tkent): Is this necessary? WPT spin-by-blocking-style-sheet.html |
| // doesn't hit this condition, and FlushAutofocusCandidates() is not called |
| // until the stylesheet is loaded. |
| if (GetStyleEngine().HasPendingScriptBlockingSheets() || |
| !HaveRenderBlockingStylesheetsLoaded()) { |
| return; |
| } |
| |
| // 5.6. Remove element from candidates. |
| autofocus_candidates_.EraseAt(0); |
| |
| // 5.7. Let inclusiveAncestorDocuments be a list consisting of doc, plus |
| // the active documents of each of doc's browsing context's ancestor |
| // browsing contexts. |
| // 5.8. If URL's fragment of any Document in inclusiveAncestorDocuments |
| // is not empty, then continue. |
| if (doc != this) { |
| for (HTMLFrameOwnerElement* frameOwner = doc->LocalOwner(); |
| !doc->CssTarget() && frameOwner; frameOwner = doc->LocalOwner()) { |
| doc = &frameOwner->GetDocument(); |
| } |
| if (doc->CssTarget()) { |
| AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| mojom::ConsoleMessageSource::kRendering, |
| mojom::ConsoleMessageLevel::kInfo, |
| "Autofocus processing was blocked because a " |
| "document's URL has a fragment '#" + |
| doc->Url().FragmentIdentifier() + "'.")); |
| continue; |
| } |
| DCHECK_EQ(doc, this); |
| } |
| |
| // 9. Let target be element. |
| Element* target = &element; |
| |
| // 10. If target is not a focusable area, then set target to the result of |
| // getting the focusable area for target. |
| element.GetDocument().UpdateStyleAndLayoutTree(); |
| if (!target->IsFocusable()) |
| target = target->GetFocusableArea(); |
| |
| // 11. If target is not null, then: |
| if (target) { |
| // 11.1. Empty candidates. |
| // 11.2. Set topDocument's autofocus processed flag to true. |
| FinalizeAutofocus(); |
| // 11.3. Run the focusing steps for element. |
| element.Focus(); |
| } else { |
| // TODO(tkent): Show a console message, and fix LocalNTP*Test.* |
| // in browser_tests. |
| } |
| } |
| } |
| |
| void Document::FinalizeAutofocus() { |
| autofocus_candidates_.clear(); |
| autofocus_processed_flag_ = true; |
| } |
| |
| // https://html.spec.whatwg.org/C/#autofocus-delegate, although most uses are |
| // of Element::GetAutofocusDelegate(). |
| Element* Document::GetAutofocusDelegate() const { |
| if (HTMLElement* body_element = body()) |
| return body_element->GetAutofocusDelegate(); |
| |
| return nullptr; |
| } |
| |
| Element* Document::ActiveElement() const { |
| return activeElement(); |
| } |
| |
| bool Document::hasFocus() const { |
| return GetPage() && GetPage()->GetFocusController().IsDocumentFocused(*this); |
| } |
| |
| const AtomicString& Document::BodyAttributeValue( |
| const QualifiedName& name) const { |
| if (auto* bodyElement = body()) |
| return bodyElement->FastGetAttribute(name); |
| return g_null_atom; |
| } |
| |
| void Document::SetBodyAttribute(const QualifiedName& name, |
| const AtomicString& value) { |
| if (auto* bodyElement = body()) { |
| // FIXME: This check is apparently for benchmarks that set the same value |
| // repeatedly. It's not clear what benchmarks though, it's also not clear |
| // why we don't avoid causing a style recalc when setting the same value to |
| // a presentational attribute in the common case. |
| if (bodyElement->FastGetAttribute(name) != value) |
| bodyElement->setAttribute(name, value); |
| } |
| } |
| |
| const AtomicString& Document::bgColor() const { |
| return BodyAttributeValue(html_names::kBgcolorAttr); |
| } |
| |
| void Document::setBgColor(const AtomicString& value) { |
| if (!IsFrameSet()) |
| SetBodyAttribute(html_names::kBgcolorAttr, value); |
| } |
| |
| const AtomicString& Document::fgColor() const { |
| return BodyAttributeValue(html_names::kTextAttr); |
| } |
| |
| void Document::setFgColor(const AtomicString& value) { |
| if (!IsFrameSet()) |
| SetBodyAttribute(html_names::kTextAttr, value); |
| } |
| |
| const AtomicString& Document::alinkColor() const { |
| return BodyAttributeValue(html_names::kAlinkAttr); |
| } |
| |
| void Document::setAlinkColor(const AtomicString& value) { |
| if (!IsFrameSet()) |
| SetBodyAttribute(html_names::kAlinkAttr, value); |
| } |
| |
| const AtomicString& Document::linkColor() const { |
| return BodyAttributeValue(html_names::kLinkAttr); |
| } |
| |
| void Document::setLinkColor(const AtomicString& value) { |
| if (!IsFrameSet()) |
| SetBodyAttribute(html_names::kLinkAttr, value); |
| } |
| |
| const AtomicString& Document::vlinkColor() const { |
| return BodyAttributeValue(html_names::kVlinkAttr); |
| } |
| |
| void Document::setVlinkColor(const AtomicString& value) { |
| if (!IsFrameSet()) |
| SetBodyAttribute(html_names::kVlinkAttr, value); |
| } |
| |
| FontFaceSet* Document::fonts() { |
| return FontFaceSetDocument::From(*this); |
| } |
| |
| template <unsigned type> |
| bool ShouldInvalidateNodeListCachesForAttr( |
| const LiveNodeListRegistry& node_lists, |
| const QualifiedName& attr_name) { |
| auto invalidation_type = static_cast<NodeListInvalidationType>(type); |
| if (node_lists.ContainsInvalidationType(invalidation_type) && |
| LiveNodeListBase::ShouldInvalidateTypeOnAttributeChange(invalidation_type, |
| attr_name)) |
| return true; |
| return ShouldInvalidateNodeListCachesForAttr<type + 1>(node_lists, attr_name); |
| } |
| |
| template <> |
| bool ShouldInvalidateNodeListCachesForAttr<kNumNodeListInvalidationTypes>( |
| const LiveNodeListRegistry&, |
| const QualifiedName&) { |
| return false; |
| } |
| |
| bool Document::ShouldInvalidateNodeListCaches( |
| const QualifiedName* attr_name) const { |
| if (attr_name) { |
| return node_lists_.NeedsInvalidateOnAttributeChange() && |
| ShouldInvalidateNodeListCachesForAttr< |
| kDoNotInvalidateOnAttributeChanges + 1>(node_lists_, *attr_name); |
| } |
| |
| // If the invalidation is not for an attribute, invalidation is needed if |
| // there is any node list present (with any invalidation type). |
| return !node_lists_.IsEmpty(); |
| } |
| |
| void Document::InvalidateNodeListCaches(const QualifiedName* attr_name) { |
| for (const LiveNodeListBase* list : lists_invalidated_at_document_) |
| list->InvalidateCacheForAttribute(attr_name); |
| } |
| |
| void Document::PlatformColorsChanged() { |
| if (!IsActive()) |
| return; |
| |
| GetStyleEngine().PlatformColorsChanged(); |
| } |
| |
| PropertyRegistry& Document::EnsurePropertyRegistry() { |
| if (!property_registry_) |
| property_registry_ = MakeGarbageCollected<PropertyRegistry>(); |
| return *property_registry_; |
| } |
| |
| DocumentResourceCoordinator* Document::GetResourceCoordinator() { |
| if (!resource_coordinator_ && GetFrame()) { |
| resource_coordinator_ = DocumentResourceCoordinator::MaybeCreate( |
| GetFrame()->GetBrowserInterfaceBroker()); |
| } |
| return resource_coordinator_.get(); |
| } |
| |
| scoped_refptr<base::SingleThreadTaskRunner> Document::GetTaskRunner( |
| TaskType type) { |
| DCHECK(IsMainThread()); |
| if (GetExecutionContext()) |
| return GetExecutionContext()->GetTaskRunner(type); |
| // GetExecutionContext() can be nullptr in unit tests and after Shutdown(). |
| // Fallback to the Agent's default task runner for this thread if all else |
| // fails. |
| return To<WindowAgent>(GetAgent()) |
| .GetAgentGroupScheduler() |
| .DefaultTaskRunner(); |
| } |
| |
| DOMFeaturePolicy* Document::featurePolicy() { |
| if (!policy_ && GetExecutionContext()) |
| policy_ = MakeGarbageCollected<DOMFeaturePolicy>(GetExecutionContext()); |
| return policy_.Get(); |
| } |
| |
| StylePropertyMapReadOnly* Document::ComputedStyleMap(Element* element) { |
| ElementComputedStyleMap::AddResult add_result = |
| element_computed_style_map_.insert(element, nullptr); |
| if (add_result.is_new_entry) { |
| add_result.stored_value->value = |
| MakeGarbageCollected<ComputedStylePropertyMap>(element); |
| } |
| return add_result.stored_value->value; |
| } |
| |
| void Document::AddComputedStyleMapItem( |
| Element* element, |
| StylePropertyMapReadOnly* computed_style) { |
| element_computed_style_map_.insert(element, computed_style); |
| } |
| |
| StylePropertyMapReadOnly* Document::RemoveComputedStyleMapItem( |
| Element* element) { |
| return element_computed_style_map_.Take(element); |
| } |
| |
| void Document::DelayAsyncScriptExecution() { |
| script_runner_delayer_->Activate(); |
| } |
| |
| void Document::ResumeAsyncScriptExecution() { |
| script_runner_delayer_->Deactivate(); |
| } |
| |
| void Document::Trace(Visitor* visitor) const { |
| visitor->Trace(doc_type_); |
| visitor->Trace(implementation_); |
| visitor->Trace(autofocus_candidates_); |
| visitor->Trace(focused_element_); |
| visitor->Trace(sequential_focus_navigation_starting_point_); |
| visitor->Trace(hover_element_); |
| visitor->Trace(active_element_); |
| visitor->Trace(document_element_); |
| visitor->Trace(root_scroller_controller_); |
| visitor->Trace(title_element_); |
| visitor->Trace(ax_object_cache_); |
| visitor->Trace(markers_); |
| visitor->Trace(css_target_); |
| visitor->Trace(current_script_stack_); |
| visitor->Trace(script_runner_); |
| visitor->Trace(script_runner_delayer_); |
| visitor->Trace(lists_invalidated_at_document_); |
| visitor->Trace(node_lists_); |
| visitor->Trace(top_layer_elements_); |
| visitor->Trace(top_layer_elements_pending_removal_); |
| visitor->Trace(popover_auto_stack_); |
| visitor->Trace(popover_hint_stack_); |
| visitor->Trace(popover_pointerdown_target_); |
| visitor->Trace(popovers_waiting_to_hide_); |
| visitor->Trace(all_open_popovers_); |
| visitor->Trace(document_part_root_); |
| visitor->Trace(load_event_delay_timer_); |
| visitor->Trace(plugin_loading_timer_); |
| visitor->Trace(elem_sheet_); |
| visitor->Trace(pending_javascript_urls_); |
| visitor->Trace(clear_focused_element_timer_); |
| visitor->Trace(node_iterators_); |
| visitor->Trace(ranges_); |
| visitor->Trace(document_explicit_root_intersection_observer_data_); |
| visitor->Trace(style_engine_); |
| visitor->Trace(form_controller_); |
| visitor->Trace(visited_link_state_); |
| visitor->Trace(element_computed_style_map_); |
| visitor->Trace(dom_window_); |
| visitor->Trace(fetcher_); |
| visitor->Trace(parser_); |
| visitor->Trace(http_refresh_scheduler_); |
| visitor->Trace(document_timing_); |
| visitor->Trace(media_query_matcher_); |
| visitor->Trace(scripted_animation_controller_); |
| visitor->Trace(scripted_idle_task_controller_); |
| visitor->Trace(text_autosizer_); |
| visitor->Trace(element_data_cache_clear_timer_); |
| visitor->Trace(element_data_cache_); |
| visitor->Trace(use_elements_needing_update_); |
| visitor->Trace(svg_resources_needing_invalidation_); |
| visitor->Trace(template_document_); |
| visitor->Trace(template_document_host_); |
| visitor->Trace(user_action_elements_); |
| visitor->Trace(svg_extensions_); |
| visitor->Trace(layout_view_); |
| visitor->Trace(document_animations_); |
| visitor->Trace(timeline_); |
| visitor->Trace(pending_animations_); |
| visitor->Trace(worklet_animation_controller_); |
| visitor->Trace(execution_context_); |
| visitor->Trace(agent_); |
| visitor->Trace(canvas_font_cache_); |
| visitor->Trace(intersection_observer_controller_); |
| visitor->Trace(property_registry_); |
| visitor->Trace(policy_); |
| visitor->Trace(slot_assignment_engine_); |
| visitor->Trace(viewport_data_); |
| visitor->Trace(lazy_load_image_observer_); |
| visitor->Trace(mime_handler_view_before_unload_event_listener_); |
| visitor->Trace(cookie_jar_); |
| visitor->Trace(synchronous_mutation_observer_set_); |
| visitor->Trace(fragment_directive_); |
| visitor->Trace(element_explicitly_set_attr_elements_map_); |
| visitor->Trace(element_cached_attr_associated_elements_map_); |
| visitor->Trace(display_lock_document_state_); |
| visitor->Trace(render_blocking_resource_manager_); |
| visitor->Trace(find_in_page_active_match_node_); |
| visitor->Trace(data_); |
| visitor->Trace(meta_theme_color_elements_); |
| visitor->Trace(unassociated_listed_elements_); |
| visitor->Trace(top_level_forms_); |
| visitor->Trace(intrinsic_size_observer_); |
| visitor->Trace(lazy_loaded_auto_sized_img_observer_); |
| visitor->Trace(anchor_element_interaction_tracker_); |
| visitor->Trace(focused_element_change_observers_); |
| visitor->Trace(pending_link_header_preloads_); |
| visitor->Trace(elements_needing_shadow_tree_); |
| Supplementable<Document>::Trace(visitor); |
| TreeScope::Trace(visitor); |
| ContainerNode::Trace(visitor); |
| } |
| |
| SlotAssignmentEngine& Document::GetSlotAssignmentEngine() { |
| if (!slot_assignment_engine_) |
| slot_assignment_engine_ = MakeGarbageCollected<SlotAssignmentEngine>(); |
| return *slot_assignment_engine_; |
| } |
| |
| bool Document::IsSlotAssignmentDirty() const { |
| return slot_assignment_engine_ && |
| slot_assignment_engine_->HasPendingSlotAssignmentRecalc(); |
| } |
| |
| bool Document::IsFocusAllowed() const { |
| LocalFrame* frame = GetFrame(); |
| if (!frame || frame->IsMainFrame() || |
| LocalFrame::HasTransientUserActivation(frame)) { |
| // 'autofocus' runs Element::focus asynchronously at which point the |
| // document might not have a frame (see https://crbug.com/960224). |
| return true; |
| } |
| |
| WebFeature uma_type; |
| bool sandboxed = dom_window_->IsSandboxed( |
| network::mojom::blink::WebSandboxFlags::kNavigation); |
| bool ad = frame->IsAdFrame(); |
| if (sandboxed) { |
| uma_type = ad ? WebFeature::kFocusWithoutUserActivationSandboxedAdFrame |
| : WebFeature::kFocusWithoutUserActivationSandboxedNotAdFrame; |
| } else { |
| uma_type = |
| ad ? WebFeature::kFocusWithoutUserActivationNotSandboxedAdFrame |
| : WebFeature::kFocusWithoutUserActivationNotSandboxedNotAdFrame; |
| } |
| CountUse(uma_type); |
| if (!RuntimeEnabledFeatures::BlockingFocusWithoutUserActivationEnabled()) |
| return true; |
| return GetExecutionContext()->IsFeatureEnabled( |
| mojom::blink::PermissionsPolicyFeature::kFocusWithoutUserActivation); |
| } |
| |
| LazyLoadImageObserver& Document::EnsureLazyLoadImageObserver() { |
| if (!lazy_load_image_observer_) { |
| lazy_load_image_observer_ = MakeGarbageCollected<LazyLoadImageObserver>(); |
| } |
| return *lazy_load_image_observer_; |
| } |
| |
| void Document::IncrementNumberOfCanvases() { |
| num_canvases_++; |
| } |
| |
| void Document::ExecuteJavaScriptUrls() { |
| DCHECK(dom_window_); |
| HeapVector<Member<PendingJavascriptUrl>> urls_to_execute; |
| urls_to_execute.swap(pending_javascript_urls_); |
| |
| for (auto& url_to_execute : urls_to_execute) { |
| dom_window_->GetScriptController().ExecuteJavaScriptURL( |
| url_to_execute->url, network::mojom::CSPDisposition::CHECK, |
| url_to_execute->world.Get()); |
| if (!GetFrame()) |
| break; |
| } |
| CheckCompleted(); |
| } |
| |
| void Document::ProcessJavaScriptUrl(const KURL& url, |
| const DOMWrapperWorld* world) { |
| DCHECK(url.ProtocolIsJavaScript()); |
| if (is_initial_empty_document_) |
| load_event_progress_ = kLoadEventNotRun; |
| GetFrame()->Loader().Progress().ProgressStarted(); |
| pending_javascript_urls_.push_back( |
| MakeGarbageCollected<PendingJavascriptUrl>(url, world)); |
| if (!javascript_url_task_handle_.IsActive()) { |
| javascript_url_task_handle_ = |
| PostCancellableTask(*GetTaskRunner(TaskType::kNetworking), FROM_HERE, |
| WTF::BindOnce(&Document::ExecuteJavaScriptUrls, |
| WrapWeakPersistent(this))); |
| } |
| } |
| |
| DisplayLockDocumentState& Document::GetDisplayLockDocumentState() const { |
| return *display_lock_document_state_; |
| } |
| |
| void Document::CancelPendingJavaScriptUrls() { |
| if (javascript_url_task_handle_.IsActive()) |
| javascript_url_task_handle_.Cancel(); |
| pending_javascript_urls_.clear(); |
| } |
| |
| bool Document::IsInWebAppScope() const { |
| if (!GetSettings()) |
| return false; |
| |
| const String& web_app_scope = GetSettings()->GetWebAppScope(); |
| if (web_app_scope.IsNull() || web_app_scope.empty()) |
| return false; |
| |
| DCHECK_EQ(KURL(web_app_scope).GetString(), web_app_scope); |
| return Url().GetString().StartsWith(web_app_scope); |
| } |
| |
| bool Document::ChildrenCanHaveStyle() const { |
| if (LayoutObject* view = GetLayoutView()) |
| return view->CanHaveChildren(); |
| return false; |
| } |
| |
| ComputedAccessibleNode* Document::GetOrCreateComputedAccessibleNode(AXID axid) { |
| DCHECK(axid) << "Invalid ax_id"; |
| if (AXObjectCache* cache = ExistingAXObjectCache()) { |
| return cache->GetOrCreateComputedAccessibleNode(axid); |
| } |
| |
| return nullptr; |
| } |
| |
| void Document::SetShowBeforeUnloadDialog(bool show_dialog) { |
| if (!mime_handler_view_before_unload_event_listener_) { |
| if (!show_dialog) |
| return; |
| |
| mime_handler_view_before_unload_event_listener_ = |
| MakeGarbageCollected<BeforeUnloadEventListener>(this); |
| domWindow()->addEventListener( |
| event_type_names::kBeforeunload, |
| mime_handler_view_before_unload_event_listener_, false); |
| } |
| mime_handler_view_before_unload_event_listener_->SetShowBeforeUnloadDialog( |
| show_dialog); |
| } |
| |
| mojom::blink::PreferredColorScheme Document::GetPreferredColorScheme() const { |
| return style_engine_->GetPreferredColorScheme(); |
| } |
| |
| void Document::ColorSchemeChanged() { |
| UpdateForcedColors(); |
| GetStyleEngine().ColorSchemeChanged(); |
| MediaQueryAffectingValueChanged(MediaValueChange::kOther); |
| } |
| |
| void Document::VisionDeficiencyChanged() { |
| GetStyleEngine().VisionDeficiencyChanged(); |
| } |
| |
| void Document::UpdateForcedColors() { |
| Settings* settings = GetSettings(); |
| if (RuntimeEnabledFeatures::ForcedColorsEnabled() && settings) { |
| in_forced_colors_mode_ = settings->GetInForcedColors(); |
| } |
| if (in_forced_colors_mode_) |
| GetStyleEngine().EnsureUAStyleForForcedColors(); |
| } |
| |
| bool Document::InForcedColorsMode() const { |
| return in_forced_colors_mode_ && !Printing(); |
| } |
| |
| bool Document::InDarkMode() { |
| return !InForcedColorsMode() && !Printing() && |
| GetStyleEngine().GetPreferredColorScheme() == |
| mojom::blink::PreferredColorScheme::kDark; |
| } |
| |
| const ui::ColorProvider* Document::GetColorProviderForPainting( |
| mojom::blink::ColorScheme color_scheme) const { |
| if (!GetPage()) { |
| return nullptr; |
| } |
| |
| return GetPage()->GetColorProviderForPainting(color_scheme, |
| in_forced_colors_mode_); |
| } |
| |
| void Document::CountUse(mojom::WebFeature feature) const { |
| if (execution_context_) { |
| execution_context_->CountUse(feature); |
| } |
| } |
| |
| void Document::CountUse(mojom::WebFeature feature) { |
| if (execution_context_) |
| execution_context_->CountUse(feature); |
| } |
| |
| void Document::CountDeprecation(mojom::WebFeature feature) { |
| if (execution_context_) |
| execution_context_->CountDeprecation(feature); |
| } |
| |
| void Document::CountProperty(CSSPropertyID property) const { |
| if (DocumentLoader* loader = Loader()) { |
| loader->GetUseCounter().Count( |
| property, UseCounterImpl::CSSPropertyType::kDefault, GetFrame()); |
| } |
| } |
| |
| void Document::CountAnimatedProperty(CSSPropertyID property) const { |
| if (DocumentLoader* loader = Loader()) { |
| loader->GetUseCounter().Count( |
| property, UseCounterImpl::CSSPropertyType::kAnimation, GetFrame()); |
| } |
| } |
| |
| bool Document::IsUseCounted(mojom::WebFeature feature) const { |
| if (DocumentLoader* loader = Loader()) { |
| return loader->GetUseCounter().IsCounted(feature); |
| } |
| return false; |
| } |
| |
| bool Document::IsPropertyCounted(CSSPropertyID property) const { |
| if (DocumentLoader* loader = Loader()) { |
| return loader->GetUseCounter().IsCounted( |
| property, UseCounterImpl::CSSPropertyType::kDefault); |
| } |
| return false; |
| } |
| |
| bool Document::IsAnimatedPropertyCounted(CSSPropertyID property) const { |
| if (DocumentLoader* loader = Loader()) { |
| return loader->GetUseCounter().IsCounted( |
| property, UseCounterImpl::CSSPropertyType::kAnimation); |
| } |
| return false; |
| } |
| |
| void Document::ClearUseCounterForTesting(mojom::WebFeature feature) { |
| if (DocumentLoader* loader = Loader()) |
| loader->GetUseCounter().ClearMeasurementForTesting(feature); |
| } |
| |
| void Document::RenderBlockingResourceUnblocked() { |
| // Only HTML documents can ever be render-blocked by external resources. |
| // https://html.spec.whatwg.org/#allows-adding-render-blocking-elements |
| DCHECK(IsA<HTMLDocument>(this)); |
| if (body()) |
| BeginLifecycleUpdatesIfRenderingReady(); |
| } |
| |
| void Document::SetFindInPageActiveMatchNode(Node* node) { |
| blink::NotifyPriorityScrollAnchorStatusChanged( |
| find_in_page_active_match_node_, node); |
| find_in_page_active_match_node_ = node; |
| } |
| |
| const Node* Document::GetFindInPageActiveMatchNode() const { |
| return find_in_page_active_match_node_; |
| } |
| |
| void Document::ActivateForPrerendering( |
| const mojom::blink::PrerenderPageActivationParams& params) { |
| TRACE_EVENT_WITH_FLOW0("navigation", "Document::ActivateForPrerendering", |
| TRACE_ID_LOCAL(this), |
| TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); |
| |
| DCHECK(is_prerendering_); |
| is_prerendering_ = false; |
| |
| if (DocumentLoader* loader = Loader()) { |
| loader->NotifyPrerenderingDocumentActivated(params); |
| } |
| |
| Vector<base::OnceClosure> callbacks; |
| callbacks.swap(will_dispatch_prerenderingchange_callbacks_); |
| for (auto& callback : callbacks) { |
| std::move(callback).Run(); |
| } |
| |
| // https://wicg.github.io/nav-speculation/prerendering.html#prerendering-browsing-context-activate |
| // Step 8.3.4 "Fire an event named prerenderingchange at doc." |
| DispatchEvent(*Event::Create(event_type_names::kPrerenderingchange)); |
| |
| // Step 8.3.5 "For each steps in doc’s post-prerendering activation steps |
| // list:" |
| RunPostPrerenderingActivationSteps(); |
| } |
| |
| void Document::AddWillDispatchPrerenderingchangeCallback( |
| base::OnceClosure closure) { |
| DCHECK(is_prerendering_); |
| will_dispatch_prerenderingchange_callbacks_.push_back(std::move(closure)); |
| } |
| |
| void Document::AddPostPrerenderingActivationStep(base::OnceClosure callback) { |
| DCHECK(is_prerendering_); |
| post_prerendering_activation_callbacks_.push_back(std::move(callback)); |
| } |
| |
| void Document::RunPostPrerenderingActivationSteps() { |
| TRACE_EVENT_WITH_FLOW1( |
| "blink", "Document::RunPostPrerenderingActivationSteps", |
| TRACE_ID_LOCAL(this), |
| TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "deferred_callback", |
| post_prerendering_activation_callbacks_.size()); |
| |
| DCHECK(!is_prerendering_); |
| for (auto& callback : post_prerendering_activation_callbacks_) |
| std::move(callback).Run(); |
| post_prerendering_activation_callbacks_.clear(); |
| } |
| |
| bool Document::InStyleRecalc() const { |
| return lifecycle_.GetState() == DocumentLifecycle::kInStyleRecalc || |
| style_engine_->InContainerQueryStyleRecalc() || |
| style_engine_->InPositionTryStyleRecalc() || |
| style_engine_->InEnsureComputedStyle(); |
| } |
| |
| void Document::DelayLoadEventUntilLayoutTreeUpdate() { |
| if (delay_load_event_until_layout_tree_update_) |
| return; |
| delay_load_event_until_layout_tree_update_ = true; |
| IncrementLoadEventDelayCount(); |
| } |
| |
| void Document::UnblockLoadEventAfterLayoutTreeUpdate() { |
| if (delay_load_event_until_layout_tree_update_) { |
| delay_load_event_until_layout_tree_update_ = false; |
| DecrementLoadEventDelayCount(); |
| } |
| } |
| |
| void Document::AddPendingLinkHeaderPreload(const PendingLinkPreload& preload) { |
| pending_link_header_preloads_.insert(&preload); |
| } |
| |
| void Document::RemovePendingLinkHeaderPreloadIfNeeded( |
| const PendingLinkPreload& preload) { |
| pending_link_header_preloads_.erase(&preload); |
| } |
| |
| void Document::AddFocusedElementChangeObserver( |
| FocusedElementChangeObserver* observer) { |
| DCHECK(observer); |
| focused_element_change_observers_.insert(observer); |
| } |
| |
| void Document::RemoveFocusedElementChangeObserver( |
| FocusedElementChangeObserver* observer) { |
| DCHECK(focused_element_change_observers_.Contains(observer)); |
| focused_element_change_observers_.erase(observer); |
| } |
| |
| void Document::WriteIntoTrace(perfetto::TracedValue ctx) const { |
| perfetto::TracedDictionary dict = std::move(ctx).WriteDictionary(); |
| dict.Add("url", Url()); |
| } |
| |
| bool Document::DeferredCompositorCommitIsAllowed() const { |
| // Don't defer commits if a transition is in progress. It requires commits to |
| // send directives to the compositor and uses a separate mechanism to pause |
| // all rendering when needed. |
| if (ViewTransitionUtils::GetTransition(*this)) { |
| return false; |
| } |
| return deferred_compositor_commit_is_allowed_; |
| } |
| |
| Document::PaintPreviewScope::PaintPreviewScope(Document& document, |
| PaintPreviewState state) |
| : document_(document) { |
| document_.paint_preview_ = state; |
| document_.GetDisplayLockDocumentState().NotifyPrintingOrPreviewChanged(); |
| } |
| |
| Document::PaintPreviewScope::~PaintPreviewScope() { |
| document_.paint_preview_ = kNotPaintingPreview; |
| document_.GetDisplayLockDocumentState().NotifyPrintingOrPreviewChanged(); |
| } |
| |
| Document::PendingJavascriptUrl::PendingJavascriptUrl( |
| const KURL& input_url, |
| const DOMWrapperWorld* world) |
| : url(input_url), world(world) {} |
| |
| Document::PendingJavascriptUrl::~PendingJavascriptUrl() = default; |
| |
| void Document::PendingJavascriptUrl::Trace(Visitor* visitor) const { |
| visitor->Trace(world); |
| } |
| |
| void Document::ResetAgent(Agent& agent) { |
| agent_ = agent; |
| } |
| |
| bool Document::SupportsLegacyDOMMutations() { |
| if (!RuntimeEnabledFeatures::MutationEventsEnabled(GetExecutionContext())) { |
| return false; |
| } |
| if (!legacy_dom_mutations_supported_.has_value()) { |
| // We load the `LocalFrame` from the `ExecutionContext`'s so that documents |
| // that do not have a frame are given the same setting consistently across |
| // the `ExecutionContext`. |
| auto* execution_dom_window = |
| DynamicTo<LocalDOMWindow>(GetExecutionContext()); |
| LocalFrame* frame = |
| execution_dom_window ? execution_dom_window->GetFrame() : nullptr; |
| if (frame && frame->GetContentSettingsClient()) { |
| legacy_dom_mutations_supported_ = |
| frame->GetContentSettingsClient()->AllowMutationEvents( |
| /*default_value=*/true); |
| } else { |
| legacy_dom_mutations_supported_ = true; |
| } |
| } |
| return legacy_dom_mutations_supported_.value(); |
| } |
| |
| void Document::EnqueuePageRevealEvent() { |
| CHECK(RuntimeEnabledFeatures::PageRevealEventEnabled()); |
| CHECK(dom_window_); |
| |
| dom_window_->SetHasBeenRevealed(false); |
| auto* page_reveal_event = MakeGarbageCollected<PageRevealEvent>(); |
| page_reveal_event->SetTarget(dom_window_); |
| page_reveal_event->SetCurrentTarget(dom_window_); |
| EnqueueAnimationFrameEvent(page_reveal_event); |
| } |
| |
| Resource* Document::GetPendingLinkPreloadForTesting(const KURL& url) { |
| for (auto pending_preload : pending_link_header_preloads_) { |
| Resource* resource = pending_preload->GetResourceForTesting(); |
| if (resource && resource->Url() == url) { |
| return resource; |
| } |
| } |
| return nullptr; |
| } |
| |
| void Document::SetLcpElementFoundInHtml(bool found) { |
| data_->lcpp_encountered_lcp_in_html = found; |
| } |
| |
| bool Document::IsLcpElementFoundInHtml() { |
| return data_->lcpp_encountered_lcp_in_html; |
| } |
| |
| void Document::ScheduleShadowTreeCreation(HTMLInputElement& element) { |
| elements_needing_shadow_tree_.insert(&element); |
| } |
| |
| void Document::UnscheduleShadowTreeCreation(HTMLInputElement& element) { |
| elements_needing_shadow_tree_.erase(&element); |
| } |
| |
| void Document::ProcessScheduledShadowTreeCreationsNow() { |
| if (elements_needing_shadow_tree_.empty()) { |
| return; |
| } |
| HeapHashSet<Member<HTMLInputElement>> elements_needing_shadow_tree; |
| std::swap(elements_needing_shadow_tree, elements_needing_shadow_tree_); |
| for (auto& element : elements_needing_shadow_tree) { |
| element->EnsureShadowSubtree(); |
| } |
| } |
| |
| // static |
| Document* Document::parseHTMLUnsafe(ExecutionContext* context, |
| const String& html) { |
| CHECK(RuntimeEnabledFeatures::HTMLUnsafeMethodsEnabled()); |
| Document* doc = DocumentInit::Create() |
| .WithTypeFrom(kTextHtml) |
| .WithExecutionContext(context) |
| .WithAgent(*context->GetAgent()) |
| .CreateDocument(); |
| doc->setAllowDeclarativeShadowRoots(true); |
| doc->SetContent(html); |
| doc->SetMimeType(AtomicString(kTextHtml)); |
| return doc; |
| } |
| |
| void Document::SetOverrideSiteForCookiesForCSPMedia(bool value) { |
| CHECK(IsMediaDocument()); |
| // Only top-level documents can use this method. |
| if (!GetFrame() || !GetFrame()->IsMainFrame()) { |
| return; |
| } |
| override_site_for_cookies_for_csp_media_ = value; |
| } |
| |
| void Document::OnWarnUnusedPreloads(Vector<KURL> unused_preloads) { |
| if (!GetFrame() || !GetFrame()->GetLCPP()) { |
| return; |
| } |
| |
| if (LCPCriticalPathPredictor* lcpp = GetFrame()->GetLCPP()) { |
| lcpp->OnWarnedUnusedPreloads(unused_preloads); |
| } |
| } |
| |
| template class CORE_TEMPLATE_EXPORT Supplement<Document>; |
| |
| } // namespace blink |
| #ifndef NDEBUG |
| static WeakDocumentSet& LiveDocumentSet() { |
| DEFINE_STATIC_LOCAL(blink::Persistent<WeakDocumentSet>, set, |
| (blink::MakeGarbageCollected<WeakDocumentSet>())); |
| return *set; |
| } |
| |
| void ShowLiveDocumentInstances() { |
| WeakDocumentSet& set = LiveDocumentSet(); |
| fprintf(stderr, "There are %u documents currently alive:\n", set.size()); |
| for (blink::Document* document : set) { |
| fprintf(stderr, "- Document %p URL: %s\n", document, |
| document->Url().GetString().Utf8().c_str()); |
| } |
| } |
| #endif |