| /* |
| * 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 "base/auto_reset.h" |
| #include "base/optional.h" |
| #include "services/metrics/public/cpp/mojo_ukm_recorder.h" |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| #include "services/metrics/public/cpp/ukm_source_id.h" |
| #include "services/metrics/public/mojom/ukm_interface.mojom-shared.h" |
| #include "services/service_manager/public/cpp/interface_provider.h" |
| #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" |
| #include "third_party/blink/public/mojom/net/ip_address_space.mojom-blink.h" |
| #include "third_party/blink/public/mojom/page/page_visibility_state.mojom-blink.h" |
| #include "third_party/blink/public/platform/interface_provider.h" |
| #include "third_party/blink/public/platform/modules/insecure_input/insecure_input_service.mojom-blink.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/public/platform/task_type.h" |
| #include "third_party/blink/public/platform/ukm.mojom-blink.h" |
| #include "third_party/blink/public/platform/web_content_settings_client.h" |
| #include "third_party/blink/public/platform/web_prerendering_support.h" |
| #include "third_party/blink/renderer/bindings/core/v8/html_script_element_or_svg_script_element.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_controller.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_value.h" |
| #include "third_party/blink/renderer/bindings/core/v8/source_location.h" |
| #include "third_party/blink/renderer/bindings/core/v8/string_or_element_creation_options.h" |
| #include "third_party/blink/renderer/bindings/core/v8/usv_string_or_trusted_url.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v0_custom_element_constructor_builder.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_element_creation_options.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/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/css_style_sheet_init.h" |
| #include "third_party/blink/renderer/core/css/cssom/computed_style_property_map.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/media_query_matcher.h" |
| #include "third_party/blink/renderer/core/css/parser/css_parser.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/dom/attr.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/context_features.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_parser_timing.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_creation_options.h" |
| #include "third_party/blink/renderer/core/dom/element_data_cache.h" |
| #include "third_party/blink/renderer/core/dom/element_registration_options.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/scoped_event_queue.h" |
| #include "third_party/blink/renderer/core/dom/flat_tree_traversal.h" |
| #include "third_party/blink/renderer/core/dom/layout_tree_builder.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_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/nth_index_cache.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/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/static_node_list.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/serializers/serialization.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/hash_change_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/feature_policy/document_policy.h" |
| #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" |
| #include "third_party/blink/renderer/core/frame/dom_timer.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/feature_policy_violation_report_body.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_view.h" |
| #include "third_party/blink/renderer/core/frame/performance_monitor.h" |
| #include "third_party/blink/renderer/core/frame/report.h" |
| #include "third_party/blink/renderer/core/frame/reporting_context.h" |
| #include "third_party/blink/renderer/core/frame/settings.h" |
| #include "third_party/blink/renderer/core/frame/use_counter.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_metrics.h" |
| #include "third_party/blink/renderer/core/html/canvas/canvas_font_cache.h" |
| #include "third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h" |
| #include "third_party/blink/renderer/core/html/canvas/html_canvas_element.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/custom/v0_custom_element_microtask_run_queue.h" |
| #include "third_party/blink/renderer/core/html/custom/v0_custom_element_registration_context.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/form_controller.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_link_element.h" |
| #include "third_party/blink/renderer/core/html/html_meta_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/imports/html_import_loader.h" |
| #include "third_party/blink/renderer/core/html/imports/html_imports_controller.h" |
| #include "third_party/blink/renderer/core/html/lazy_load_image_observer.h" |
| #include "third_party/blink/renderer/core/html/parser/html_document_parser.h" |
| #include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" |
| #include "third_party/blink/renderer/core/html/parser/nesting_level_incrementer.h" |
| #include "third_party/blink/renderer/core/html/parser/text_resource_decoder.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/inspector/main_thread_debugger.h" |
| #include "third_party/blink/renderer/core/intersection_observer/intersection_observer_controller.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/loader/appcache/application_cache_host.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/idleness_detector.h" |
| #include "third_party/blink/renderer/core/loader/interactive_detector.h" |
| #include "third_party/blink/renderer/core/loader/navigation_scheduler.h" |
| #include "third_party/blink/renderer/core/loader/prerenderer_client.h" |
| #include "third_party/blink/renderer/core/loader/text_resource_decoder_builder.h" |
| #include "third_party/blink/renderer/core/origin_trials/origin_trials.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/pointer_lock_controller.h" |
| #include "third_party/blink/renderer/core/page/scrolling/overscroll_controller.h" |
| #include "third_party/blink/renderer/core/page/scrolling/root_scroller_controller.h" |
| #include "third_party/blink/renderer/core/page/scrolling/scroll_state_callback.h" |
| #include "third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.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/paint/compositing/paint_layer_compositor.h" |
| #include "third_party/blink/renderer/core/paint/first_meaningful_paint_detector.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/probe/core_probes.h" |
| #include "third_party/blink/renderer/core/resize_observer/resize_observer_controller.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/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/dom_window_performance.h" |
| #include "third_party/blink/renderer/core/timing/window_performance.h" |
| #include "third_party/blink/renderer/core/trustedtypes/trusted_html.h" |
| #include "third_party/blink/renderer/core/trustedtypes/trusted_url.h" |
| #include "third_party/blink/renderer/core/workers/shared_worker_repository_client.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/exception_messages.h" |
| #include "third_party/blink/renderer/platform/bindings/exception_state.h" |
| #include "third_party/blink/renderer/platform/bindings/microtask.h" |
| #include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.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/cross_thread_functional.h" |
| #include "third_party/blink/renderer/platform/date_components.h" |
| #include "third_party/blink/renderer/platform/geometry/length_functions.h" |
| #include "third_party/blink/renderer/platform/histogram.h" |
| #include "third_party/blink/renderer/platform/instance_counters.h" |
| #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" |
| #include "third_party/blink/renderer/platform/language.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/network/network_state_notifier.h" |
| #include "third_party/blink/renderer/platform/plugins/plugin_script_forbidden_scope.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/frame_or_worker_scheduler.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h" |
| #include "third_party/blink/renderer/platform/text/platform_locale.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/wtf/date_math.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" |
| #include "third_party/blink/renderer/platform/wtf/time.h" |
| |
| #ifndef NDEBUG |
| using WeakDocumentSet = blink::HeapHashSet<blink::WeakMember<blink::Document>>; |
| static WeakDocumentSet& liveDocumentSet(); |
| #endif |
| |
| namespace blink { |
| |
| using namespace html_names; |
| |
| class DocumentOutliveTimeReporter : public BlinkGCObserver { |
| public: |
| explicit DocumentOutliveTimeReporter(Document* document) |
| : BlinkGCObserver(ThreadState::Current()), document_(document) {} |
| |
| ~DocumentOutliveTimeReporter() override { |
| // As not all documents are destroyed before the process dies, this might |
| // miss some long-lived documents or leaked documents. |
| UMA_HISTOGRAM_EXACT_LINEAR( |
| "Document.OutliveTimeAfterShutdown.DestroyedBeforeProcessDies", |
| GetOutliveTimeCount() + 1, 101); |
| } |
| |
| void OnCompleteSweepDone() override { |
| enum GCCount { |
| kGCCount5, |
| kGCCount10, |
| kGCCountMax, |
| }; |
| |
| // There are some cases that a document can live after shutting down because |
| // the document can still be referenced (e.g. a document opened via |
| // window.open can be referenced by the opener even after shutting down). To |
| // avoid such cases as much as possible, outlive time count is started after |
| // all DomWrapper of the document have disappeared. |
| if (!gc_age_when_document_detached_) { |
| if (document_->domWindow() && |
| DOMWrapperWorld::HasWrapperInAnyWorldInMainThread( |
| document_->domWindow())) { |
| return; |
| } |
| gc_age_when_document_detached_ = ThreadState::Current()->GcAge(); |
| } |
| |
| int outlive_time_count = GetOutliveTimeCount(); |
| if (outlive_time_count == 5 || outlive_time_count == 10) { |
| const char* kUMAString = "Document.OutliveTimeAfterShutdown.GCCount"; |
| |
| if (outlive_time_count == 5) |
| UMA_HISTOGRAM_ENUMERATION(kUMAString, kGCCount5, kGCCountMax); |
| else if (outlive_time_count == 10) |
| UMA_HISTOGRAM_ENUMERATION(kUMAString, kGCCount10, kGCCountMax); |
| else |
| NOTREACHED(); |
| } |
| |
| if (outlive_time_count == 5 || outlive_time_count == 10 || |
| outlive_time_count == 20 || outlive_time_count == 50) { |
| document_->RecordUkmOutliveTimeAfterShutdown(outlive_time_count); |
| } |
| } |
| |
| private: |
| int GetOutliveTimeCount() const { |
| if (!gc_age_when_document_detached_) |
| return 0; |
| return ThreadState::Current()->GcAge() - gc_age_when_document_detached_; |
| } |
| |
| WeakPersistent<Document> document_; |
| int gc_age_when_document_detached_ = 0; |
| }; |
| |
| 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 int kCLayoutScheduleThreshold = 250; |
| |
| // After a document has been committed for this time, it can create a history |
| // entry even if the user hasn't interacted with the document. |
| static const int kElapsedTimeForHistoryEntryWithoutUserGestureMS = 5000; |
| |
| // 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(HasEditableStyle(element)); |
| |
| return element.GetDocument().GetFrame() && RootEditableElement(element); |
| } |
| |
| uint64_t Document::global_tree_version_ = 0; |
| |
| static bool g_threaded_parsing_enabled_for_testing = true; |
| |
| // This doesn't work with non-Document ExecutionContext. |
| static void RunAutofocusTask(ExecutionContext* context) { |
| // Document lifecycle check is done in Element::focus() |
| if (!context) |
| return; |
| |
| Document* document = To<Document>(context); |
| if (Element* element = document->AutofocusElement()) { |
| document->SetAutofocusElement(nullptr); |
| element->focus(); |
| } |
| } |
| |
| class Document::NetworkStateObserver final |
| : public GarbageCollectedFinalized<Document::NetworkStateObserver>, |
| public NetworkStateNotifier::NetworkStateObserver, |
| public ContextLifecycleObserver { |
| USING_GARBAGE_COLLECTED_MIXIN(Document::NetworkStateObserver); |
| |
| public: |
| explicit NetworkStateObserver(Document& document) |
| : ContextLifecycleObserver(&document) { |
| online_observer_handle_ = GetNetworkStateNotifier().AddOnLineObserver( |
| this, GetExecutionContext()->GetTaskRunner(TaskType::kNetworking)); |
| } |
| |
| void OnLineStateChange(bool on_line) override { |
| AtomicString event_name = |
| on_line ? event_type_names::kOnline : event_type_names::kOffline; |
| Document* document = To<Document>(GetExecutionContext()); |
| if (!document->domWindow()) |
| return; |
| document->domWindow()->DispatchEvent(*Event::Create(event_name)); |
| probe::networkStateChanged(document->GetFrame(), on_line); |
| } |
| |
| void ContextDestroyed(ExecutionContext* context) override { |
| UnregisterAsObserver(context); |
| } |
| |
| void UnregisterAsObserver(ExecutionContext* context) { |
| DCHECK(context); |
| online_observer_handle_ = nullptr; |
| } |
| |
| void Trace(blink::Visitor* visitor) override { |
| ContextLifecycleObserver::Trace(visitor); |
| } |
| |
| private: |
| std::unique_ptr<NetworkStateNotifier::NetworkStateObserverHandle> |
| online_observer_handle_; |
| }; |
| |
| Document* Document::CreateForTest() { |
| return MakeGarbageCollected<Document>(DocumentInit::Create()); |
| } |
| |
| Document* Document::Create(Document& document) { |
| Document* new_document = MakeGarbageCollected<Document>( |
| DocumentInit::Create().WithContextDocument(&document).WithURL( |
| BlankURL())); |
| new_document->SetSecurityOrigin(document.GetMutableSecurityOrigin()); |
| new_document->SetContextFeatures(document.GetContextFeatures()); |
| return new_document; |
| } |
| |
| Document::Document(const DocumentInit& initializer, |
| DocumentClassFlags document_classes) |
| : ContainerNode(nullptr, kCreateDocument), |
| TreeScope(*this), |
| has_nodes_with_placeholder_style_(false), |
| evaluate_media_queries_on_style_recalc_(false), |
| pending_sheet_layout_(kNoLayoutWithPendingSheets), |
| frame_(initializer.GetFrame()), |
| // TODO(dcheng): Why does this need both a LocalFrame and LocalDOMWindow |
| // pointer? |
| dom_window_(frame_ ? frame_->DomWindow() : nullptr), |
| imports_controller_(initializer.ImportsController()), |
| context_document_(initializer.ContextDocument()), |
| context_features_(ContextFeatures::DefaultSwitch()), |
| well_formed_(false), |
| printing_(kNotPrinting), |
| compatibility_mode_(kNoQuirksMode), |
| compatibility_mode_locked_(false), |
| has_autofocused_(false), |
| last_focus_type_(kWebFocusTypeNone), |
| 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_(VisitedLinkState::Create(*this)), |
| visually_ordered_(false), |
| ready_state_(kComplete), |
| parsing_state_(kFinishedParsing), |
| contains_validity_style_rules_(false), |
| 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)), |
| update_focus_appearance_timer_( |
| GetTaskRunner(TaskType::kInternalUserInteraction), |
| this, |
| &Document::UpdateFocusAppearanceTimerFired), |
| css_target_(nullptr), |
| was_discarded_(false), |
| load_event_progress_(kLoadEventCompleted), |
| is_freezing_in_progress_(false), |
| start_time_(CurrentTime()), |
| script_runner_(ScriptRunner::Create(this)), |
| xml_version_("1.0"), |
| xml_standalone_(kStandaloneUnspecified), |
| has_xml_declaration_(0), |
| design_mode_(false), |
| is_running_exec_command_(false), |
| has_annotated_regions_(false), |
| annotated_regions_dirty_(false), |
| document_classes_(document_classes), |
| is_view_source_(false), |
| saw_elements_in_known_namespaces_(false), |
| is_srcdoc_document_(initializer.ShouldTreatURLAsSrcdocDocument()), |
| is_mobile_document_(false), |
| layout_view_(nullptr), |
| has_fullscreen_supplement_(false), |
| load_event_delay_count_(0), |
| load_event_delay_timer_(GetTaskRunner(TaskType::kNetworking), |
| 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), |
| registration_context_(initializer.RegistrationContext(this)), |
| element_data_cache_clear_timer_( |
| GetTaskRunner(TaskType::kInternalUserInteraction), |
| this, |
| &Document::ElementDataCacheClearTimerFired), |
| timeline_(DocumentTimeline::Create(this)), |
| pending_animations_(new PendingAnimations(*this)), |
| worklet_animation_controller_(new WorkletAnimationController(this)), |
| template_document_host_(nullptr), |
| did_associate_form_controls_timer_( |
| GetTaskRunner(TaskType::kInternalLoading), |
| this, |
| &Document::DidAssociateFormControlsTimerFired), |
| timers_(GetTaskRunner(TaskType::kJavascriptTimer)), |
| has_viewport_units_(false), |
| parser_sync_policy_(kAllowAsynchronousParsing), |
| node_count_(0), |
| password_count_(0), |
| logged_field_edit_(false), |
| secure_context_state_(SecureContextState::kUnknown), |
| ukm_source_id_(ukm::UkmRecorder::GetNewSourceID()), |
| #if DCHECK_IS_ON() |
| slot_assignment_recalc_forbidden_recursion_depth_(0), |
| #endif |
| needs_to_record_ukm_outlive_time_(false), |
| viewport_data_(new ViewportData(*this)), |
| agent_cluster_id_(base::UnguessableToken::Create()), |
| parsed_feature_policies_( |
| static_cast<int>(mojom::FeaturePolicyFeature::kMaxValue) + 1) { |
| if (frame_) { |
| DCHECK(frame_->GetPage()); |
| ProvideContextFeaturesToDocumentFrom(*this, *frame_->GetPage()); |
| |
| fetcher_ = frame_->Loader().GetDocumentLoader()->Fetcher(); |
| FrameFetchContext::ProvideDocumentToContext(fetcher_->Context(), this); |
| |
| // TODO(dcheng): Why does this need to check that DOMWindow is non-null? |
| CustomElementRegistry* registry = |
| frame_->DomWindow() ? frame_->DomWindow()->MaybeCustomElements() |
| : nullptr; |
| if (registry && registration_context_) |
| registry->Entangle(registration_context_); |
| } else if (imports_controller_) { |
| fetcher_ = FrameFetchContext::CreateFetcherFromDocument(this); |
| } else { |
| fetcher_ = ResourceFetcher::Create(nullptr); |
| } |
| DCHECK(fetcher_); |
| |
| root_scroller_controller_ = RootScrollerController::Create(*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(); |
| } |
| |
| InitSecurityContext(initializer); |
| if (frame_) |
| frame_->Client()->DidSetFramePolicyHeaders(GetSandboxFlags(), {}); |
| |
| InitDNSPrefetch(); |
| |
| InstanceCounters::IncrementCounter(InstanceCounters::kDocumentCounter); |
| |
| lifecycle_.AdvanceTo(DocumentLifecycle::kInactive); |
| |
| // Since CSSFontSelector requires Document::fetcher_ and StyleEngine owns |
| // CSSFontSelector, need to initialize style_engine_ after initializing |
| // fetcher_. |
| style_engine_ = StyleEngine::Create(*this); |
| |
| // 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()->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); |
| } |
| |
| Range* Document::CreateRangeAdjustedToTreeScope(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 Range::Create(tree_scope.GetDocument(), position, position); |
| Node* const shadow_host = tree_scope.AncestorInThisScope(anchor_node); |
| return Range::Create(tree_scope.GetDocument(), |
| Position::BeforeNode(*shadow_host), |
| 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_ = MediaQueryMatcher::Create(*this); |
| return *media_query_matcher_; |
| } |
| |
| void Document::MediaQueryAffectingValueChanged() { |
| GetStyleEngine().MediaQueryAffectingValueChanged(); |
| 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 (compatibility_mode_ == kQuirksMode) |
| UseCounter::Count(*this, WebFeature::kQuirksModeDocument); |
| else if (compatibility_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_->ViewportRulesChanged(); |
| } |
| } |
| } |
| |
| DOMImplementation& Document::implementation() { |
| if (!implementation_) |
| implementation_ = DOMImplementation::Create(*this); |
| return *implementation_; |
| } |
| |
| Location* Document::location() const { |
| if (!GetFrame()) |
| return nullptr; |
| |
| return domWindow()->location(); |
| } |
| |
| bool Document::ShouldInstallV8Extensions() const { |
| return frame_->Client()->AllowScriptExtensions(); |
| } |
| |
| 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_ && !IsHTMLDocument()) |
| BeginLifecycleUpdatesIfRenderingReady(); |
| } |
| |
| void Document::setRootScroller(Element* new_scroller, ExceptionState&) { |
| root_scroller_controller_->Set(new_scroller); |
| } |
| |
| Element* Document::rootScroller() const { |
| return root_scroller_controller_->Get(); |
| } |
| |
| bool Document::IsInMainFrame() const { |
| return GetFrame() && GetFrame()->IsMainFrame(); |
| } |
| |
| AtomicString Document::ConvertLocalName(const AtomicString& name) { |
| return IsHTMLDocument() ? 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/multipage/dom.html#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 = HTMLElement::Create(qname, *this); |
| else |
| element = HTMLUnknownElement::Create(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 = SVGUnknownElement::Create(qname, *this); |
| saw_elements_in_known_namespaces_ = true; |
| } else { |
| element = Element::Create(qname, this); |
| } |
| |
| if (element->prefix() != qname.Prefix()) |
| element->SetTagNameForCreateElementNS(qname); |
| DCHECK(qname == element->TagQName()); |
| |
| return element; |
| } |
| |
| // https://dom.spec.whatwg.org/#dom-document-createelement |
| Element* Document::CreateElementForBinding(const AtomicString& name, |
| ExceptionState& exception_state) { |
| if (!IsValidElementName(this, name)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidCharacterError, |
| "The tag name provided ('" + name + "') is not a valid name."); |
| return nullptr; |
| } |
| |
| if (IsXHTMLDocument() || IsHTMLDocument()) { |
| // 2. If the context object is an HTML document, let localName be |
| // converted to ASCII lowercase. |
| AtomicString local_name = ConvertLocalName(name); |
| if (CustomElement::ShouldCreateCustomElement(local_name)) { |
| return CustomElement::CreateCustomElement( |
| *this, |
| QualifiedName(g_null_atom, local_name, html_names::xhtmlNamespaceURI), |
| CreateElementFlags::ByCreateElement()); |
| } |
| if (auto* element = HTMLElementFactory::Create( |
| local_name, *this, CreateElementFlags::ByCreateElement())) |
| return element; |
| QualifiedName q_name(g_null_atom, local_name, |
| html_names::xhtmlNamespaceURI); |
| if (RegistrationContext() && V0CustomElement::IsValidName(local_name)) |
| return RegistrationContext()->CreateCustomTagElement(*this, q_name); |
| return HTMLUnknownElement::Create(q_name, *this); |
| } |
| return Element::Create(QualifiedName(g_null_atom, name, g_null_atom), this); |
| } |
| |
| AtomicString GetTypeExtension( |
| Document* document, |
| const StringOrElementCreationOptions& string_or_options) { |
| if (string_or_options.IsNull()) |
| return AtomicString(); |
| |
| if (string_or_options.IsString()) { |
| UseCounter::Count(document, |
| WebFeature::kDocumentCreateElement2ndArgStringHandling); |
| return AtomicString(string_or_options.GetAsString()); |
| } |
| |
| if (string_or_options.IsElementCreationOptions()) { |
| const ElementCreationOptions& options = |
| *string_or_options.GetAsElementCreationOptions(); |
| if (options.hasIs()) |
| return AtomicString(options.is()); |
| } |
| |
| return AtomicString(); |
| } |
| |
| // https://dom.spec.whatwg.org/#dom-document-createelement |
| Element* Document::CreateElementForBinding( |
| const AtomicString& local_name, |
| const StringOrElementCreationOptions& string_or_options, |
| ExceptionState& exception_state) { |
| if (string_or_options.IsNull()) { |
| return CreateElementForBinding(local_name, exception_state); |
| } |
| |
| // 1. If localName does not match Name production, throw InvalidCharacterError |
| if (!IsValidElementName(this, 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 = ConvertLocalName(local_name); |
| QualifiedName q_name(g_null_atom, converted_local_name, |
| IsXHTMLDocument() || IsHTMLDocument() |
| ? html_names::xhtmlNamespaceURI |
| : g_null_atom); |
| |
| bool is_v1 = |
| string_or_options.IsElementCreationOptions() || !RegistrationContext(); |
| bool create_v1_builtin = string_or_options.IsElementCreationOptions(); |
| bool should_create_builtin = |
| create_v1_builtin || string_or_options.IsString(); |
| |
| // 3. |
| const AtomicString& is = GetTypeExtension(this, string_or_options); |
| |
| // 5. Let element be the result of creating an element given ... |
| Element* element = |
| CreateElement(q_name, |
| is_v1 ? CreateElementFlags::ByCreateElementV1() |
| : CreateElementFlags::ByCreateElementV0(), |
| should_create_builtin ? is : g_null_atom); |
| |
| // 8. If 'is' is non-null, set 'is' attribute |
| if (!is_v1 && !is.IsEmpty()) |
| element->setAttribute(html_names::kIsAttr, 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; |
| } |
| |
| Element* Document::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, flags); |
| if (RegistrationContext() && V0CustomElement::IsValidName(q_name.LocalName())) |
| return RegistrationContext()->CreateCustomTagElement(*this, q_name); |
| return CreateRawElement(q_name, flags); |
| } |
| |
| // https://dom.spec.whatwg.org/#internal-createelementns-steps |
| Element* Document::createElementNS( |
| const AtomicString& namespace_uri, |
| const AtomicString& qualified_name, |
| const StringOrElementCreationOptions& string_or_options, |
| ExceptionState& exception_state) { |
| if (string_or_options.IsNull()) |
| return createElementNS(namespace_uri, qualified_name, exception_state); |
| |
| // 1. Validate and extract |
| QualifiedName q_name( |
| CreateQualifiedName(namespace_uri, qualified_name, exception_state)); |
| if (q_name == QualifiedName::Null()) |
| return nullptr; |
| |
| bool is_v1 = |
| string_or_options.IsElementCreationOptions() || !RegistrationContext(); |
| bool create_v1_builtin = string_or_options.IsElementCreationOptions(); |
| bool should_create_builtin = |
| create_v1_builtin || string_or_options.IsString(); |
| |
| // 2. |
| const AtomicString& is = GetTypeExtension(this, string_or_options); |
| |
| if (!IsValidElementName(this, 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, |
| is_v1 ? CreateElementFlags::ByCreateElementV1() |
| : CreateElementFlags::ByCreateElementV0(), |
| should_create_builtin ? is : g_null_atom); |
| |
| // 4. If 'is' is non-null, set 'is' attribute |
| if (!is_v1 && !is.IsEmpty()) |
| element->setAttribute(html_names::kIsAttr, is); |
| |
| return element; |
| } |
| |
| // Entry point of "create an element". |
| // https://dom.spec.whatwg.org/#concept-create-element |
| Element* Document::CreateElement(const QualifiedName& q_name, |
| const CreateElementFlags flags, |
| const AtomicString& is) { |
| CustomElementDefinition* definition = nullptr; |
| if (flags.IsCustomElementsV1() && |
| 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(*this, q_name, flags); |
| |
| return CustomElement::CreateUncustomizedOrUndefinedElement(*this, q_name, |
| flags, is); |
| } |
| |
| ScriptPromise Document::createCSSStyleSheet(ScriptState* script_state, |
| const String& text, |
| ExceptionState& exception_state) { |
| return Document::createCSSStyleSheet( |
| script_state, text, CSSStyleSheetInit::Create(), exception_state); |
| } |
| |
| ScriptPromise Document::createCSSStyleSheet(ScriptState* script_state, |
| const String& text, |
| const CSSStyleSheetInit* options, |
| ExceptionState& exception_state) { |
| // Even though this function returns a Promise, it actually does all the work |
| // at once here because CSS parsing is done synchronously on the main thread. |
| // TODO(rakina): Find a way to improve this. |
| CSSStyleSheet* sheet = CSSStyleSheet::Create(*this, options, exception_state); |
| sheet->SetText(text, true /* allow_import_rules */, exception_state); |
| sheet->SetAssociatedDocument(this); |
| return ScriptPromise::Cast(script_state, ToV8(sheet, script_state)); |
| } |
| |
| CSSStyleSheet* Document::createCSSStyleSheetSync( |
| ScriptState* script_state, |
| const String& text, |
| ExceptionState& exception_state) { |
| return Document::createCSSStyleSheetSync( |
| script_state, text, CSSStyleSheetInit::Create(), exception_state); |
| } |
| |
| CSSStyleSheet* Document::createCSSStyleSheetSync( |
| ScriptState* script_state, |
| const String& text, |
| const CSSStyleSheetInit* options, |
| ExceptionState& exception_state) { |
| CSSStyleSheet* sheet = CSSStyleSheet::Create(*this, options, exception_state); |
| sheet->SetText(text, false /* allow_import_rules */, exception_state); |
| if (exception_state.HadException()) |
| return nullptr; |
| sheet->SetAssociatedDocument(this); |
| return sheet; |
| } |
| |
| CSSStyleSheet* Document::createEmptyCSSStyleSheet( |
| ScriptState* script_state, |
| const CSSStyleSheetInit* options, |
| ExceptionState& exception_state) { |
| CSSStyleSheet* sheet = CSSStyleSheet::Create(*this, options, exception_state); |
| sheet->SetAssociatedDocument(this); |
| return sheet; |
| } |
| |
| CSSStyleSheet* Document::createEmptyCSSStyleSheet( |
| ScriptState* script_state, |
| ExceptionState& exception_state) { |
| return Document::createEmptyCSSStyleSheet( |
| script_state, CSSStyleSheetInit::Create(), exception_state); |
| } |
| |
| ScriptValue Document::registerElement(ScriptState* script_state, |
| const AtomicString& name, |
| const ElementRegistrationOptions* options, |
| ExceptionState& exception_state) { |
| if (!RegistrationContext()) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotSupportedError, |
| "No element registration context is available."); |
| return ScriptValue(); |
| } |
| |
| // Polymer V1 uses Custom Elements V0. <dom-module> is defined in its base |
| // library and is a strong signal that this is a Polymer V1. |
| // This counter is used to research how much users are affected once Custom |
| // Element V0 is deprecated. |
| if (name == "dom-module") |
| UseCounter::Count(*this, WebFeature::kPolymerV1Detected); |
| |
| V0CustomElementConstructorBuilder constructor_builder(script_state, options); |
| RegistrationContext()->RegisterElement(this, &constructor_builder, name, |
| exception_state); |
| return constructor_builder.BindingsReturnValue(); |
| } |
| |
| V0CustomElementMicrotaskRunQueue* Document::CustomElementMicrotaskRunQueue() { |
| if (!custom_element_microtask_run_queue_) |
| custom_element_microtask_run_queue_ = |
| V0CustomElementMicrotaskRunQueue::Create(); |
| return custom_element_microtask_run_queue_.Get(); |
| } |
| |
| void Document::ClearImportsController() { |
| if (!Loader()) |
| fetcher_->ClearContext(); |
| imports_controller_ = nullptr; |
| } |
| |
| HTMLImportsController* Document::EnsureImportsController() { |
| if (!imports_controller_) { |
| DCHECK(frame_); |
| imports_controller_ = HTMLImportsController::Create(*this); |
| } |
| |
| return imports_controller_; |
| } |
| |
| HTMLImportLoader* Document::ImportLoader() const { |
| if (!imports_controller_) |
| return nullptr; |
| return imports_controller_->LoaderFor(*this); |
| } |
| |
| bool Document::IsHTMLImport() const { |
| return imports_controller_ && imports_controller_->Master() != this; |
| } |
| |
| Document& Document::MasterDocument() const { |
| if (!imports_controller_) |
| return *const_cast<Document*>(this); |
| |
| Document* master = imports_controller_->Master(); |
| DCHECK(master); |
| return *master; |
| } |
| |
| bool Document::HaveImportsLoaded() const { |
| if (!imports_controller_) |
| return true; |
| return !imports_controller_->ShouldBlockScriptExecution(*this); |
| } |
| |
| LocalDOMWindow* Document::ExecutingWindow() const { |
| if (LocalDOMWindow* owning_window = domWindow()) |
| return owning_window; |
| if (HTMLImportsController* import = ImportsController()) |
| return import->Master()->domWindow(); |
| return nullptr; |
| } |
| |
| LocalFrame* Document::ExecutingFrame() { |
| LocalDOMWindow* window = ExecutingWindow(); |
| if (!window) |
| return nullptr; |
| return window->GetFrame(); |
| } |
| |
| 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 (IsHTMLDocument()) { |
| 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 (IsHTMLDocument()) { |
| UseCounter::Count(*this, |
| WebFeature::kHTMLDocumentCreateProcessingInstruction); |
| } |
| return ProcessingInstruction::Create(*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 and the clone children flag |
| // set if deep is true. |
| return imported_node->Clone( |
| *this, deep ? CloneChildrenFlag::kClone : CloneChildrenFlag::kSkip); |
| } |
| |
| 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: { |
| Attr* attr = ToAttr(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 (source->IsFrameOwnerElement()) { |
| HTMLFrameOwnerElement* frame_owner_element = |
| ToHTMLFrameOwnerElement(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(ConsoleMessage::Create( |
| kJSMessageSource, kWarningMessageLevel, |
| 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().IsEmpty() && 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().IsEmpty() && 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) { |
| 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; |
| 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. |
| SetNeedsStyleRecalc(kSubtreeStyleChange, 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(); |
| } |
| |
| String Document::SuggestedMIMEType() const { |
| if (IsXMLDocument()) { |
| if (IsXHTMLDocument()) |
| return "application/xhtml+xml"; |
| if (IsSVGDocument()) |
| return "image/svg+xml"; |
| return "application/xml"; |
| } |
| if (xmlStandalone()) |
| return "text/xml"; |
| if (IsHTMLDocument()) |
| return "text/html"; |
| |
| 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_.IsEmpty()) |
| return mime_type_; |
| |
| if (DocumentLoader* document_loader = Loader()) |
| return document_loader->MimeType(); |
| |
| String mime_type = SuggestedMIMEType(); |
| if (!mime_type.IsEmpty()) |
| return AtomicString(mime_type); |
| |
| return AtomicString("application/xml"); |
| } |
| |
| Element* Document::ElementFromPoint(double x, double y) const { |
| if (!GetLayoutView()) |
| return nullptr; |
| |
| return TreeScope::ElementFromPoint(x, y); |
| } |
| |
| HeapVector<Member<Element>> Document::ElementsFromPoint(double x, |
| double y) const { |
| if (!GetLayoutView()) |
| return HeapVector<Member<Element>>(); |
| return TreeScope::ElementsFromPoint(x, y); |
| } |
| |
| 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); |
| } |
| |
| Element* Document::scrollingElement() { |
| if (RuntimeEnabledFeatures::ScrollTopLeftInteropEnabled() && InQuirksMode()) |
| UpdateStyleAndLayoutTree(); |
| return ScrollingElementNoLayout(); |
| } |
| |
| Element* Document::ScrollingElementNoLayout() { |
| if (RuntimeEnabledFeatures::ScrollTopLeftInteropEnabled()) { |
| if (InQuirksMode()) { |
| DCHECK(lifecycle_.GetState() >= DocumentLifecycle::kStyleClean); |
| HTMLBodyElement* body = FirstBodyElement(); |
| if (body && body->GetLayoutObject() && |
| body->GetLayoutObject()->HasOverflowClip()) |
| 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_.IsEmpty()) |
| title_ = String(); |
| else if (raw_title_.Is8Bit()) |
| title_ = CanonicalizedTitle<LChar>(this, raw_title_); |
| else |
| title_ = CanonicalizedTitle<UChar>(this, raw_title_); |
| |
| if (!frame_ || old_title == title_) |
| return; |
| DispatchDidReceiveTitle(); |
| |
| if (AXObjectCache* cache = ExistingAXObjectCache()) |
| cache->DocumentTitleChanged(); |
| } |
| |
| void Document::DispatchDidReceiveTitle() { |
| frame_->Client()->DispatchDidReceiveTitle(title_); |
| } |
| |
| void Document::setTitle(const String& title) { |
| // Title set by JavaScript -- overrides any title elements. |
| Element* element = documentElement(); |
| if (IsSVGSVGElement(element)) { |
| if (!title_element_) { |
| title_element_ = SVGTitleElement::Create(*this); |
| element->InsertBefore(title_element_.Get(), element->firstChild()); |
| } |
| if (auto* svg_title = ToSVGTitleElementOrNull(title_element_)) |
| svg_title->SetText(title); |
| } else if (element && element->IsHTMLElement()) { |
| if (!title_element_) { |
| HTMLElement* head_element = head(); |
| if (!head_element) |
| return; |
| title_element_ = HTMLTitleElement::Create(*this); |
| head_element->AppendChild(title_element_.Get()); |
| } |
| if (auto* html_title = ToHTMLTitleElementOrNull(title_element_)) |
| 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 (IsSVGSVGElement(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 (IsSVGTitleElement(title_element_)) { |
| title_element_ = nullptr; |
| return; |
| } |
| } |
| |
| if (auto* html_title = ToHTMLTitleElementOrNull(title_element_)) |
| UpdateTitle(html_title->text()); |
| else if (auto* svg_title = ToSVGTitleElementOrNull(title_element_)) |
| 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 (IsHTMLDocument() || 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 = ToHTMLHtmlElementOrNull(root_element)) |
| return html->dir(); |
| return g_null_atom; |
| } |
| |
| void Document::setDir(const AtomicString& value) { |
| Element* root_element = documentElement(); |
| if (auto* html = ToHTMLHtmlElementOrNull(root_element)) |
| html->setDir(value); |
| } |
| |
| mojom::PageVisibilityState Document::GetPageVisibilityState() 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 (!frame_ || !frame_->GetPage()) |
| return mojom::PageVisibilityState::kHidden; |
| // 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 mojom::PageVisibilityState::kHidden; |
| return frame_->GetPage()->VisibilityState(); |
| } |
| |
| bool Document::IsPrefetchOnly() const { |
| if (!frame_ || !frame_->GetPage()) |
| return false; |
| |
| PrerendererClient* prerenderer_client = |
| PrerendererClient::From(frame_->GetPage()); |
| return prerenderer_client && prerenderer_client->IsPrefetchOnly(); |
| } |
| |
| String Document::visibilityState() const { |
| return PageVisibilityStateString(GetPageVisibilityState()); |
| } |
| |
| bool Document::hidden() const { |
| return GetPageVisibilityState() != mojom::PageVisibilityState::kVisible; |
| } |
| |
| bool Document::wasDiscarded() const { |
| return was_discarded_; |
| } |
| |
| void Document::SetWasDiscarded(bool was_discarded) { |
| was_discarded_ = was_discarded; |
| } |
| |
| void Document::DidChangeVisibilityState() { |
| 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 (GetPageVisibilityState() == mojom::PageVisibilityState::kVisible) |
| Timeline().SetAllCompositorPending(); |
| |
| if (hidden() && canvas_font_cache_) |
| canvas_font_cache_->PruneAll(); |
| |
| InteractiveDetector* interactive_detector = InteractiveDetector::From(*this); |
| if (interactive_detector) { |
| interactive_detector->OnPageVisibilityChanged(GetPageVisibilityState()); |
| } |
| } |
| |
| String Document::nodeName() const { |
| return "#document"; |
| } |
| |
| Node::NodeType Document::getNodeType() const { |
| return kDocumentNode; |
| } |
| |
| FormController& Document::GetFormController() { |
| if (!form_controller_) { |
| form_controller_ = FormController::Create(); |
| HistoryItem* history_item = Loader() ? Loader()->GetHistoryItem() : nullptr; |
| if (history_item) |
| history_item->SetDocumentState(form_controller_->FormElementsState()); |
| } |
| return *form_controller_; |
| } |
| |
| DocumentState* Document::FormElementsState() const { |
| if (!form_controller_) |
| return nullptr; |
| return form_controller_->FormElementsState(); |
| } |
| |
| void Document::SetStateForNewFormElements(const Vector<String>& state_vector) { |
| if (!state_vector.size() && !form_controller_) |
| return; |
| GetFormController().SetStateForNewFormElements(state_vector); |
| } |
| |
| LocalFrameView* Document::View() const { |
| return frame_ ? frame_->View() : nullptr; |
| } |
| |
| Page* Document::GetPage() const { |
| return frame_ ? frame_->GetPage() : nullptr; |
| } |
| |
| LocalFrame* Document::GetFrameOfMasterDocument() const { |
| if (frame_) |
| return frame_; |
| if (imports_controller_) |
| return imports_controller_->Master()->GetFrame(); |
| return nullptr; |
| } |
| |
| Settings* Document::GetSettings() const { |
| return frame_ ? frame_->GetSettings() : nullptr; |
| } |
| |
| Range* Document::createRange() { |
| return Range::Create(*this); |
| } |
| |
| NodeIterator* Document::createNodeIterator(Node* root, |
| unsigned what_to_show, |
| V8NodeFilter* filter) { |
| DCHECK(root); |
| return NodeIterator::Create(root, what_to_show, filter); |
| } |
| |
| TreeWalker* Document::createTreeWalker(Node* root, |
| unsigned what_to_show, |
| V8NodeFilter* filter) { |
| DCHECK(root); |
| return TreeWalker::Create(root, what_to_show, filter); |
| } |
| |
| bool Document::NeedsLayoutTreeUpdate() const { |
| if (!IsActive() || !View()) |
| return false; |
| if (NeedsFullLayoutTreeUpdate()) |
| return true; |
| if (ChildNeedsStyleRecalc()) |
| return true; |
| if (ChildNeedsStyleInvalidation()) |
| return true; |
| if (ChildNeedsReattachLayoutTree()) { |
| DCHECK(InStyleRecalc()); |
| return true; |
| } |
| if (GetLayoutView() && GetLayoutView()->WasNotifiedOfSubtreeChange()) |
| return true; |
| return false; |
| } |
| |
| bool Document::NeedsFullLayoutTreeUpdate() const { |
| if (!IsActive() || !View()) |
| return false; |
| if (style_engine_->NeedsActiveStyleUpdate()) |
| return true; |
| if (style_engine_->NeedsWhitespaceReattachment()) |
| return true; |
| if (!use_elements_needing_update_.IsEmpty()) |
| return true; |
| if (NeedsStyleRecalc()) |
| return true; |
| if (NeedsStyleInvalidation()) |
| return true; |
| if (IsSlotAssignmentOrLegacyDistributionDirty()) |
| return true; |
| if (DocumentAnimations::NeedsAnimationTimingUpdate(*this)) |
| return true; |
| return false; |
| } |
| |
| bool Document::ShouldScheduleLayoutTreeUpdate() const { |
| if (!IsActive()) |
| return false; |
| if (InStyleRecalc()) |
| return false; |
| // InPreLayout will recalc style itself. There's no reason to schedule another |
| // recalc. |
| if (lifecycle_.GetState() == DocumentLifecycle::kInPreLayout) |
| return false; |
| if (!ShouldScheduleLayout()) |
| return false; |
| return true; |
| } |
| |
| void Document::ScheduleLayoutTreeUpdate() { |
| DCHECK(!HasPendingVisualUpdate()); |
| DCHECK(ShouldScheduleLayoutTreeUpdate()); |
| DCHECK(NeedsLayoutTreeUpdate()); |
| |
| if (!View()->CanThrottleRendering()) |
| GetPage()->Animator().ScheduleVisualUpdate(GetFrame()); |
| lifecycle_.EnsureStateAtMost(DocumentLifecycle::kVisualUpdatePending); |
| |
| TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), |
| "ScheduleStyleRecalculation", TRACE_EVENT_SCOPE_THREAD, |
| "data", |
| 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; |
| |
| if (!ChildNeedsStyleInvalidation() && !NeedsStyleInvalidation()) |
| return; |
| TRACE_EVENT0("blink", "Document::updateStyleInvalidationIfNeeded"); |
| SCOPED_BLINK_UMA_HISTOGRAM_TIMER_HIGHRES("Style.InvalidationTime"); |
| GetStyleEngine().InvalidateStyle(); |
| } |
| |
| void Document::SetupFontBuilder(ComputedStyle& document_style) { |
| FontBuilder font_builder(this); |
| CSSFontSelector* selector = GetStyleEngine().GetFontSelector(); |
| font_builder.CreateFontForDocument(selector, document_style); |
| } |
| |
| void Document::PropagateStyleToViewport() { |
| DCHECK(InStyleRecalc()); |
| if (!documentElement()) |
| return; |
| |
| HTMLElement* body = this->body(); |
| |
| const ComputedStyle* body_style = |
| body ? body->EnsureComputedStyle() : nullptr; |
| const ComputedStyle* document_element_style = |
| documentElement()->EnsureComputedStyle(); |
| |
| TouchAction effective_touch_action = |
| document_element_style->GetEffectiveTouchAction(); |
| WritingMode root_writing_mode = document_element_style->GetWritingMode(); |
| TextDirection root_direction = document_element_style->Direction(); |
| if (body_style) { |
| root_writing_mode = body_style->GetWritingMode(); |
| root_direction = body_style->Direction(); |
| } |
| |
| const ComputedStyle* background_style = document_element_style; |
| // http://www.w3.org/TR/css3-background/#body-background |
| // <html> root element with no background steals background from its first |
| // <body> child. |
| // Also see LayoutBoxModelObject::BackgroundTransfersToView() |
| if (IsHTMLHtmlElement(documentElement()) && |
| document_element_style->Display() != EDisplay::kNone && |
| IsHTMLBodyElement(body) && !background_style->HasBackground()) { |
| background_style = body_style; |
| } |
| |
| Color background_color = Color::kTransparent; |
| FillLayer background_layers(EFillLayerType::kBackground, true); |
| EImageRendering image_rendering = EImageRendering::kAuto; |
| |
| if (background_style->Display() != EDisplay::kNone) { |
| background_color = background_style->VisitedDependentColor( |
| GetCSSPropertyBackgroundColor()); |
| background_layers = background_style->BackgroundLayers(); |
| for (auto* current_layer = &background_layers; current_layer; |
| current_layer = current_layer->Next()) { |
| // http://www.w3.org/TR/css3-background/#root-background |
| // The root element background always have painting area of the whole |
| // canvas. |
| current_layer->SetClip(EFillBox::kBorder); |
| |
| // The root element doesn't scroll. It always propagates its layout |
| // overflow to the viewport. Positioning background against either box is |
| // equivalent to positioning against the scrolled box of the viewport. |
| if (current_layer->Attachment() == EFillAttachment::kScroll) |
| current_layer->SetAttachment(EFillAttachment::kLocal); |
| } |
| image_rendering = background_style->ImageRendering(); |
| } |
| |
| const ComputedStyle* overflow_style = nullptr; |
| Element* viewport_element = ViewportDefiningElement(); |
| DCHECK(viewport_element); |
| if (viewport_element == body) { |
| overflow_style = body_style; |
| } else { |
| DCHECK_EQ(viewport_element, documentElement()); |
| overflow_style = document_element_style; |
| |
| // The body element has its own scrolling box, independent from the |
| // viewport. This is a bit of a weird edge case in the CSS spec that we |
| // might want to try to eliminate some day (eg. for ScrollTopLeftInterop - |
| // see http://crbug.com/157855). |
| if (body_style && !body_style->IsOverflowVisible()) |
| UseCounter::Count(*this, WebFeature::kBodyScrollsInAdditionToViewport); |
| } |
| DCHECK(overflow_style); |
| |
| EOverflowAnchor overflow_anchor = overflow_style->OverflowAnchor(); |
| EOverflow overflow_x = overflow_style->OverflowX(); |
| EOverflow overflow_y = overflow_style->OverflowY(); |
| // Visible overflow on the viewport is meaningless, and the spec says to |
| // treat it as 'auto': |
| if (overflow_x == EOverflow::kVisible) |
| overflow_x = EOverflow::kAuto; |
| if (overflow_y == EOverflow::kVisible) |
| overflow_y = EOverflow::kAuto; |
| if (overflow_anchor == EOverflowAnchor::kVisible) |
| overflow_anchor = EOverflowAnchor::kAuto; |
| // Column-gap is (ab)used by the current paged overflow implementation (in |
| // lack of other ways to specify gaps between pages), so we have to |
| // propagate it too. |
| GapLength column_gap = overflow_style->ColumnGap(); |
| |
| ScrollSnapType snap_type = overflow_style->GetScrollSnapType(); |
| ScrollBehavior scroll_behavior = document_element_style->GetScrollBehavior(); |
| |
| EOverscrollBehavior overscroll_behavior_x = |
| overflow_style->OverscrollBehaviorX(); |
| EOverscrollBehavior overscroll_behavior_y = |
| overflow_style->OverscrollBehaviorY(); |
| using OverscrollBehaviorType = cc::OverscrollBehavior::OverscrollBehaviorType; |
| if (IsInMainFrame()) { |
| GetPage()->GetOverscrollController().SetOverscrollBehavior( |
| cc::OverscrollBehavior( |
| static_cast<OverscrollBehaviorType>(overscroll_behavior_x), |
| static_cast<OverscrollBehaviorType>(overscroll_behavior_y))); |
| } |
| |
| Length scroll_padding_top = overflow_style->ScrollPaddingTop(); |
| Length scroll_padding_right = overflow_style->ScrollPaddingRight(); |
| Length scroll_padding_bottom = overflow_style->ScrollPaddingBottom(); |
| Length scroll_padding_left = overflow_style->ScrollPaddingLeft(); |
| |
| const ComputedStyle& viewport_style = GetLayoutView()->StyleRef(); |
| if (viewport_style.GetWritingMode() != root_writing_mode || |
| viewport_style.Direction() != root_direction || |
| viewport_style.VisitedDependentColor(GetCSSPropertyBackgroundColor()) != |
| background_color || |
| viewport_style.BackgroundLayers() != background_layers || |
| viewport_style.ImageRendering() != image_rendering || |
| viewport_style.OverflowAnchor() != overflow_anchor || |
| viewport_style.OverflowX() != overflow_x || |
| viewport_style.OverflowY() != overflow_y || |
| viewport_style.ColumnGap() != column_gap || |
| viewport_style.GetScrollSnapType() != snap_type || |
| viewport_style.GetScrollBehavior() != scroll_behavior || |
| viewport_style.OverscrollBehaviorX() != overscroll_behavior_x || |
| viewport_style.OverscrollBehaviorY() != overscroll_behavior_y || |
| viewport_style.ScrollPaddingTop() != scroll_padding_top || |
| viewport_style.ScrollPaddingRight() != scroll_padding_right || |
| viewport_style.ScrollPaddingBottom() != scroll_padding_bottom || |
| viewport_style.ScrollPaddingLeft() != scroll_padding_left || |
| viewport_style.GetEffectiveTouchAction() != effective_touch_action) { |
| scoped_refptr<ComputedStyle> new_style = |
| ComputedStyle::Clone(viewport_style); |
| new_style->SetWritingMode(root_writing_mode); |
| new_style->UpdateFontOrientation(); |
| new_style->SetDirection(root_direction); |
| new_style->SetBackgroundColor(background_color); |
| new_style->AccessBackgroundLayers() = background_layers; |
| new_style->SetImageRendering(image_rendering); |
| new_style->SetOverflowAnchor(overflow_anchor); |
| new_style->SetOverflowX(overflow_x); |
| new_style->SetOverflowY(overflow_y); |
| new_style->SetColumnGap(column_gap); |
| new_style->SetScrollSnapType(snap_type); |
| new_style->SetScrollBehavior(scroll_behavior); |
| new_style->SetOverscrollBehaviorX(overscroll_behavior_x); |
| new_style->SetOverscrollBehaviorY(overscroll_behavior_y); |
| new_style->SetScrollPaddingTop(scroll_padding_top); |
| new_style->SetScrollPaddingRight(scroll_padding_right); |
| new_style->SetScrollPaddingBottom(scroll_padding_bottom); |
| new_style->SetScrollPaddingLeft(scroll_padding_left); |
| new_style->SetEffectiveTouchAction(effective_touch_action); |
| GetLayoutView()->SetStyle(new_style); |
| SetupFontBuilder(*new_style); |
| |
| if (PaintLayerScrollableArea* scrollable_area = |
| GetLayoutView()->GetScrollableArea()) { |
| if (scrollable_area->HorizontalScrollbar() && |
| scrollable_area->HorizontalScrollbar()->IsCustomScrollbar()) |
| scrollable_area->HorizontalScrollbar()->StyleChanged(); |
| if (scrollable_area->VerticalScrollbar() && |
| scrollable_area->VerticalScrollbar()->IsCustomScrollbar()) |
| scrollable_area->VerticalScrollbar()->StyleChanged(); |
| } |
| } |
| } |
| |
| #if DCHECK_IS_ON() |
| static void AssertLayoutTreeUpdated(Node& root) { |
| Node* node = &root; |
| while (node) { |
| if (RuntimeEnabledFeatures::DisplayLockingEnabled() && |
| node->IsElementNode() && |
| ToElement(node)->StyleRecalcBlockedByDisplayLock()) { |
| node = NodeTraversal::NextSkippingChildren(*node); |
| continue; |
| } |
| |
| DCHECK(!node->NeedsStyleRecalc()); |
| DCHECK(!node->ChildNeedsStyleRecalc()); |
| DCHECK(!node->NeedsReattachLayoutTree()); |
| DCHECK(!node->ChildNeedsReattachLayoutTree()); |
| DCHECK(!node->ChildNeedsDistributionRecalc()); |
| DCHECK(!node->NeedsStyleInvalidation()); |
| DCHECK(!node->ChildNeedsStyleInvalidation()); |
| DCHECK(!node->GetForceReattachLayoutTree()); |
| // 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; |
| |
| if (ShadowRoot* shadow_root = node->GetShadowRoot()) |
| AssertLayoutTreeUpdated(*shadow_root); |
| node = NodeTraversal::Next(*node); |
| } |
| } |
| #endif |
| |
| void Document::UpdateStyleAndLayoutTree() { |
| DCHECK(IsMainThread()); |
| if (Lifecycle().LifecyclePostponed()) |
| return; |
| |
| HTMLFrameOwnerElement::PluginDisposeSuspendScope suspend_plugin_dispose; |
| ScriptForbiddenScope forbid_script; |
| |
| if (HTMLFrameOwnerElement* owner = LocalOwner()) { |
| owner->GetDocument().UpdateStyleAndLayoutTree(); |
| } |
| |
| if (!View() || !IsActive()) |
| return; |
| |
| if (View()->ShouldThrottleRendering()) |
| return; |
| |
| // RecalcSlotAssignments should be done before checking |
| // NeedsLayoutTreeUpdate(). |
| GetSlotAssignmentEngine().RecalcSlotAssignments(); |
| |
| #if DCHECK_IS_ON() |
| NestingLevelIncrementer slot_assignment_recalc_forbidden_scope( |
| slot_assignment_recalc_forbidden_recursion_depth_); |
| #endif |
| |
| if (!NeedsLayoutTreeUpdate()) { |
| if (Lifecycle().GetState() < DocumentLifecycle::kStyleClean) { |
| // needsLayoutTreeUpdate 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); |
| } |
| return; |
| } |
| |
| if (InStyleRecalc()) |
| return; |
| |
| // 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()); |
| |
| TRACE_EVENT_BEGIN1("blink,devtools.timeline", "UpdateLayoutTree", "beginData", |
| inspector_recalculate_styles_event::Data(GetFrame())); |
| |
| unsigned start_element_count = GetStyleEngine().StyleForElementCount(); |
| |
| probe::RecalculateStyle recalculate_style_scope(this); |
| |
| DocumentAnimations::UpdateAnimationTimingIfNeeded(*this); |
| EvaluateMediaQueryListIfNeeded(); |
| UpdateUseShadowTreesIfNeeded(); |
| |
| UpdateDistributionForLegacyDistributedNodes(); |
| |
| UpdateActiveStyle(); |
| UpdateStyleInvalidationIfNeeded(); |
| |
| // FIXME: We should update style on our ancestor chain before proceeding |
| // however doing so currently causes several tests to crash, as |
| // LocalFrame::setDocument calls Document::attach before setting the |
| // LocalDOMWindow on the LocalFrame, or the SecurityOrigin on the |
| // document. The attach, in turn resolves style (here) and then when we |
| // resolve style on the parent chain, we may end up re-attaching our |
| // containing iframe, which when asked HTMLFrameElementBase::isURLAllowed hits |
| // a null-dereference due to security code always assuming the document has a |
| // SecurityOrigin. |
| |
| UpdateStyle(); |
| |
| NotifyLayoutTreeOfSubtreeChanges(); |
| |
| // As a result of the style recalculation, the currently hovered element might |
| // have been detached (for example, by setting display:none in the :hover |
| // style), schedule another mouseMove event to check if any other elements |
| // ended up under the mouse pointer due to re-layout. |
| if (HoverElement() && !HoverElement()->GetLayoutObject() && GetFrame()) { |
| GetFrame()->GetEventHandler().MayUpdateHoverWhenContentUnderMouseChanged( |
| MouseEventManager::UpdateHoverReason::kLayoutOrStyleChanged); |
| } |
| |
| if (focused_element_ && !focused_element_->IsFocusable()) |
| ClearFocusedElementSoon(); |
| GetLayoutView()->ClearHitTestCache(); |
| |
| DCHECK(!DocumentAnimations::NeedsAnimationTimingUpdate(*this)); |
| |
| unsigned element_count = |
| GetStyleEngine().StyleForElementCount() - start_element_count; |
| |
| TRACE_EVENT_END1("blink,devtools.timeline", "UpdateLayoutTree", |
| "elementCount", element_count); |
| |
| #if DCHECK_IS_ON() |
| AssertLayoutTreeUpdated(*this); |
| #endif |
| } |
| |
| void Document::UpdateActiveStyle() { |
| DCHECK(IsActive()); |
| DCHECK(IsMainThread()); |
| TRACE_EVENT0("blink", "Document::updateActiveStyle"); |
| GetStyleEngine().UpdateActiveStyle(); |
| } |
| |
| void Document::UpdateStyle() { |
| DCHECK(!View()->ShouldThrottleRendering()); |
| TRACE_EVENT_BEGIN0("blink,blink_style", "Document::updateStyle"); |
| RUNTIME_CALL_TIMER_SCOPE(V8PerIsolateData::MainThreadIsolate(), |
| RuntimeCallStats::CounterId::kUpdateStyle); |
| |
| unsigned initial_element_count = GetStyleEngine().StyleForElementCount(); |
| |
| lifecycle_.AdvanceTo(DocumentLifecycle::kInStyleRecalc); |
| |
| StyleRecalcChange change = kNoChange; |
| if (GetStyleChangeType() >= kSubtreeStyleChange) |
| change = kForce; |
| |
| NthIndexCache nth_index_cache(*this); |
| |
| // TODO(futhark@chromium.org): Cannot access the EnsureStyleResolver() before |
| // calling StyleForViewport() below because apparently the StyleResolver's |
| // constructor has side effects. We should fix it. See |
| // printing/setPrinting.html, printing/width-overflow.html though they only |
| // fail on mac when accessing the resolver by what appears to be a viewport |
| // size difference. |
| |
| if (change == kForce) { |
| has_nodes_with_placeholder_style_ = false; |
| scoped_refptr<ComputedStyle> viewport_style = |
| StyleResolver::StyleForViewport(*this); |
| StyleRecalcChange local_change = ComputedStyle::StylePropagationDiff( |
| viewport_style.get(), GetLayoutView()->Style()); |
| if (local_change != kNoChange) |
| GetLayoutView()->SetStyle(std::move(viewport_style)); |
| } |
| |
| ClearNeedsStyleRecalc(); |
| ClearNeedsReattachLayoutTree(); |
| |
| StyleResolver& resolver = EnsureStyleResolver(); |
| |
| bool should_record_stats; |
| TRACE_EVENT_CATEGORY_GROUP_ENABLED("blink,blink_style", &should_record_stats); |
| GetStyleEngine().SetStatsEnabled(should_record_stats); |
| |
| if (Element* document_element = documentElement()) { |
| if (document_element->ShouldCallRecalcStyle(change)) { |
| TRACE_EVENT0("blink,blink_style", "Document::recalcStyle"); |
| SCOPED_BLINK_UMA_HISTOGRAM_TIMER_HIGHRES("Style.RecalcTime"); |
| Element* viewport_defining = ViewportDefiningElement(); |
| GetStyleEngine().RecalcStyle(change); |
| if (viewport_defining != ViewportDefiningElement()) |
| ViewportDefiningElementDidChange(); |
| } |
| GetStyleEngine().MarkForWhitespaceReattachment(); |
| if (document_element->NeedsReattachLayoutTree() || |
| document_element->ChildNeedsReattachLayoutTree()) { |
| TRACE_EVENT0("blink,blink_style", "Document::rebuildLayoutTree"); |
| SCOPED_BLINK_UMA_HISTOGRAM_TIMER_HIGHRES("Style.RebuildLayoutTreeTime"); |
| ReattachLegacyLayoutObjectList legacy_layout_objects(*this); |
| GetStyleEngine().RebuildLayoutTree(); |
| legacy_layout_objects.ForceLegacyLayoutIfNeeded(); |
| } |
| } |
| GetStyleEngine().ClearWhitespaceReattachSet(); |
| ClearChildNeedsStyleRecalc(); |
| ClearChildNeedsReattachLayoutTree(); |
| |
| PropagateStyleToViewport(); |
| View()->UpdateCountersAfterStyleChange(); |
| GetLayoutView()->RecalcOverflow(); |
| |
| DCHECK(!NeedsStyleRecalc()); |
| DCHECK(!ChildNeedsStyleRecalc()); |
| DCHECK(!NeedsReattachLayoutTree()); |
| DCHECK(!ChildNeedsReattachLayoutTree()); |
| DCHECK(InStyleRecalc()); |
| DCHECK_EQ(GetStyleResolver(), &resolver); |
| lifecycle_.AdvanceTo(DocumentLifecycle::kStyleClean); |
| if (should_record_stats) { |
| TRACE_EVENT_END2( |
| "blink,blink_style", "Document::updateStyle", "resolverAccessCount", |
| GetStyleEngine().StyleForElementCount() - initial_element_count, |
| "counters", GetStyleEngine().Stats()->ToTracedValue()); |
| } else { |
| TRACE_EVENT_END1( |
| "blink,blink_style", "Document::updateStyle", "resolverAccessCount", |
| GetStyleEngine().StyleForElementCount() - initial_element_count); |
| } |
| } |
| |
| void Document::ViewportDefiningElementDidChange() { |
| HTMLBodyElement* body = FirstBodyElement(); |
| if (!body) |
| return; |
| LayoutObject* layout_object = body->GetLayoutObject(); |
| if (layout_object && layout_object->IsLayoutBlock()) { |
| // When the overflow style for documentElement changes to or from visible, |
| // it changes whether the body element's box should have scrollable overflow |
| // on its own box or propagated to the viewport. If the body style did not |
| // need a recalc, this will not be updated as its done as part of setting |
| // ComputedStyle on the LayoutObject. Force a SetStyle for body when the |
| // ViewportDefiningElement changes in order to trigger an update of |
| // HasOverflowClip() and the PaintLayer in StyleDidChange(). |
| layout_object->SetStyle(ComputedStyle::Clone(*layout_object->Style())); |
| // CompositingReason::kClipsCompositingDescendants depends on the root |
| // element having a clip-related style. Since style update due to changes of |
| // viewport-defining element don't end up as a StyleDifference, we need a |
| // special dirty bit for this situation. |
| if (layout_object->HasLayer()) { |
| ToLayoutBoxModelObject(layout_object) |
| ->Layer() |
| ->SetNeeedsCompositingReasonsUpdate(); |
| } |
| } |
| } |
| |
| void Document::NotifyLayoutTreeOfSubtreeChanges() { |
| if (!GetLayoutView()->WasNotifiedOfSubtreeChange()) |
| return; |
| |
| lifecycle_.AdvanceTo(DocumentLifecycle::kInLayoutSubtreeChange); |
| |
| GetLayoutView()->HandleSubtreeModifications(); |
| DCHECK(!GetLayoutView()->WasNotifiedOfSubtreeChange()); |
| |
| lifecycle_.AdvanceTo(DocumentLifecycle::kLayoutSubtreeChangeClean); |
| } |
| |
| bool Document::NeedsLayoutTreeUpdateForNode(const Node& node, |
| bool ignore_adjacent_style) const { |
| if (!node.CanParticipateInFlatTree()) |
| return false; |
| if (!NeedsLayoutTreeUpdate()) |
| return false; |
| if (!node.isConnected()) |
| return false; |
| |
| if (NeedsFullLayoutTreeUpdate() || node.NeedsStyleRecalc() || |
| node.NeedsStyleInvalidation()) |
| return true; |
| for (const ContainerNode* ancestor = LayoutTreeBuilderTraversal::Parent(node); |
| ancestor; ancestor = LayoutTreeBuilderTraversal::Parent(*ancestor)) { |
| if (ShadowRoot* root = ancestor->GetShadowRoot()) { |
| if (root->NeedsStyleRecalc() || root->NeedsStyleInvalidation() || |
| root->NeedsAdjacentStyleRecalc()) { |
| return true; |
| } |
| } |
| if (ancestor->NeedsStyleRecalc() || ancestor->NeedsStyleInvalidation() || |
| (ancestor->NeedsAdjacentStyleRecalc() && !ignore_adjacent_style)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void Document::UpdateStyleAndLayoutTreeForNode(const Node* node) { |
| DCHECK(node); |
| if (!NeedsLayoutTreeUpdateForNode(*node)) |
| return; |
| UpdateStyleAndLayoutTree(); |
| } |
| |
| void Document::UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(Node* node) { |
| DCHECK(node); |
| if (!node->InActiveDocument()) |
| return; |
| UpdateStyleAndLayoutIgnorePendingStylesheets(); |
| } |
| |
| void Document::UpdateStyleAndLayout() { |
| DCHECK(IsMainThread()); |
| |
| HTMLFrameOwnerElement::PluginDisposeSuspendScope suspend_plugin_dispose; |
| |
| LocalFrameView* frame_view = View(); |
| DCHECK(!frame_view || !frame_view->IsInPerformLayout()) |
| << "View layout should not be re-entrant"; |
| |
| if (HTMLFrameOwnerElement* owner = LocalOwner()) |
| owner->GetDocument().UpdateStyleAndLayout(); |
| |
| UpdateStyleAndLayoutTree(); |
| |
| if (!IsActive()) |
| return; |
| |
| if (frame_view && frame_view->NeedsLayout()) |
| frame_view->UpdateLayout(); |
| |
| if (Lifecycle().GetState() < DocumentLifecycle::kLayoutClean) |
| Lifecycle().AdvanceTo(DocumentLifecycle::kLayoutClean); |
| |
| if (AXObjectCache* cache = ExistingAXObjectCache()) |
| cache->ProcessUpdatesAfterLayout(*this); |
| |
| if (LocalFrameView* frame_view_anchored = View()) |
| frame_view_anchored->PerformScrollAnchoringAdjustments(); |
| } |
| |
| void Document::LayoutUpdated() { |
| DCHECK(GetFrame()); |
| DCHECK(View()); |
| |
| // If we're restoring a scroll position from history, that takes precedence |
| // over scrolling to the anchor in the URL. |
| View()->ScrollAndFocusFragmentAnchor(); |
| |
| // Script run in the call above may detach the document. |
| if (GetFrame() && View()) { |
| GetFrame()->Loader().RestoreScrollPositionAndViewState(); |
| |
| // The focus call above can execute JS which can dirty layout. Ensure |
| // layout is clean since this is called from UpdateLayout. |
| if (View()->NeedsLayout()) |
| View()->UpdateLayout(); |
| } |
| |
| // 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 (GetFrame() && GetFrame()->IsMainFrame()) |
| GetFrame()->GetPage()->GetChromeClient().MainFrameLayoutUpdated(); |
| |
| Markers().InvalidateRectsForAllTextMatchMarkers(); |
| |
| // The layout system may perform layouts with pending stylesheets. When |
| // recording first layout time, we ignore these layouts, since painting is |
| // suppressed for them. We're interested in tracking the time of the |
| // first real or 'paintable' layout. |
| // TODO(esprehn): This doesn't really make sense, why not track the first |
| // beginFrame? This will catch the first layout in a page that does lots |
| // of layout thrashing even though that layout might not be followed by |
| // a paint for many seconds. |
| if (IsRenderingReady() && body() && HaveRenderBlockingResourcesLoaded()) { |
| if (document_timing_.FirstLayout().is_null()) |
| document_timing_.MarkFirstLayout(); |
| } |
| } |
| |
| void Document::ClearFocusedElementSoon() { |
| if (!clear_focused_element_timer_.IsActive()) |
| clear_focused_element_timer_.StartOneShot(TimeDelta(), FROM_HERE); |
| } |
| |
| void Document::ClearFocusedElementTimerFired(TimerBase*) { |
| UpdateStyleAndLayoutTree(); |
| |
| if (focused_element_ && !focused_element_->IsFocusable()) |
| focused_element_->blur(); |
| } |
| |
| // FIXME: This is a bad idea and needs to be removed eventually. |
| // Other browsers load stylesheets before they continue parsing the web page. |
| // Since we don't, we can run JavaScript code that needs answers before the |
| // stylesheets are loaded. Doing a layout ignoring the pending stylesheets |
| // lets us get reasonable answers. The long term solution to this problem is |
| // to instead suspend JavaScript execution. |
| void Document::UpdateStyleAndLayoutTreeIgnorePendingStylesheets() { |
| if (RuntimeEnabledFeatures::CSSInBodyDoesNotBlockPaintEnabled()) { |
| UpdateStyleAndLayoutTree(); |
| return; |
| } |
| if (Lifecycle().LifecyclePostponed()) |
| return; |
| // See comment for equivalent CHECK in Document::UpdateStyleAndLayoutTree. |
| // Updating style and layout can dirty state that must remain clean during |
| // lifecycle updates. |
| CHECK(Lifecycle().StateAllowsTreeMutations()); |
| StyleEngine::IgnoringPendingStylesheet ignoring(GetStyleEngine()); |
| |
| if (!HaveRenderBlockingResourcesLoaded()) { |
| // FIXME: We are willing to attempt to suppress painting with outdated style |
| // info only once. Our assumption is that it would be dangerous to try to |
| // stop it a second time, after page content has already been loaded and |
| // displayed with accurate style information. (Our suppression involves |
| // blanking the whole page at the moment. If it were more refined, we might |
| // be able to do something better.) It's worth noting though that this |
| // entire method is a hack, since what we really want to do is suspend JS |
| // instead of doing a layout with inaccurate information. |
| HTMLElement* body_element = body(); |
| if (body_element && !body_element->GetLayoutObject() && |
| pending_sheet_layout_ == kNoLayoutWithPendingSheets) { |
| pending_sheet_layout_ = kDidLayoutWithPendingSheets; |
| GetStyleEngine().MarkAllTreeScopesDirty(); |
| } |
| if (has_nodes_with_placeholder_style_) { |
| // If new nodes have been added or style recalc has been done with style |
| // sheets still pending, some nodes may not have had their real style |
| // calculated yet. Normally this gets cleaned when style sheets arrive |
| // but here we need up-to-date style immediately. |
| SetNeedsStyleRecalc(kSubtreeStyleChange, |
| StyleChangeReasonForTracing::Create( |
| style_change_reason::kCleanupPlaceholderStyles)); |
| } |
| } |
| UpdateStyleAndLayoutTree(); |
| } |
| |
| void Document::UpdateStyleAndLayoutIgnorePendingStylesheets( |
| Document::RunPostLayoutTasks run_post_layout_tasks) { |
| LocalFrameView* local_view = View(); |
| if (local_view) |
| local_view->WillStartForcedLayout(); |
| if (!RuntimeEnabledFeatures::CSSInBodyDoesNotBlockPaintEnabled()) |
| UpdateStyleAndLayoutTreeIgnorePendingStylesheets(); |
| UpdateStyleAndLayout(); |
| |
| if (local_view) { |
| if (run_post_layout_tasks == kRunPostLayoutTasksSynchronously) |
| local_view->FlushAnyPendingPostLayoutTasks(); |
| |
| local_view->DidFinishForcedLayout(); |
| } |
| } |
| |
| scoped_refptr<ComputedStyle> |
| Document::StyleForElementIgnoringPendingStylesheets(Element* element) { |
| DCHECK_EQ(element->GetDocument(), this); |
| StyleEngine::IgnoringPendingStylesheet ignoring(GetStyleEngine()); |
| if (!element->CanParticipateInFlatTree()) |
| return EnsureStyleResolver().StyleForElement(element, nullptr); |
| |
| ContainerNode* parent = LayoutTreeBuilderTraversal::Parent(*element); |
| const ComputedStyle* parent_style = |
| parent ? parent->EnsureComputedStyle() : nullptr; |
| |
| ContainerNode* layout_parent = |
| parent ? LayoutTreeBuilderTraversal::LayoutParent(*element) : nullptr; |
| const ComputedStyle* layout_parent_style = |
| layout_parent ? layout_parent->EnsureComputedStyle() : parent_style; |
| |
| return EnsureStyleResolver().StyleForElement(element, parent_style, |
| layout_parent_style); |
| } |
| |
| scoped_refptr<ComputedStyle> Document::StyleForPage(int page_index) { |
| UpdateDistributionForUnknownReasons(); |
| return EnsureStyleResolver().StyleForPage(page_index); |
| } |
| |
| void Document::EnsurePaintLocationDataValidForNode(const Node* node) { |
| DCHECK(node); |
| if (!node->InActiveDocument()) |
| return; |
| |
| // For all nodes we must have up-to-date style and have performed layout to do |
| // any location-based calculation. |
| UpdateStyleAndLayoutIgnorePendingStylesheets(); |
| |
| // The location of elements that are position: sticky is not known until |
| // compositing inputs are cleaned. Therefore, for any elements that are either |
| // sticky or are in a sticky sub-tree (e.g. are affected by a sticky element), |
| // we need to also clean compositing inputs. |
| if (View() && node->GetLayoutObject() && |
| node->GetLayoutObject()->StyleRef().SubtreeIsSticky()) { |
| bool success = false; |
| if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) { |
| // In SPv2, compositing inputs are cleaned as part of PrePaint. |
| success = View()->UpdateAllLifecyclePhasesExceptPaint(); |
| } else { |
| success = View()->UpdateLifecycleToCompositingInputsClean(); |
| } |
| // The lifecycle update should always succeed, because forced lifecycles |
| // from script are never throttled. |
| DCHECK(success); |
| } |
| } |
| |
| bool Document::IsPageBoxVisible(int page_index) { |
| return StyleForPage(page_index)->Visibility() != |
| EVisibility::kHidden; // display property doesn't apply to @page. |
| } |
| |
| void Document::PageSizeAndMarginsInPixels(int page_index, |
| DoubleSize& page_size, |
| int& margin_top, |
| int& margin_right, |
| int& margin_bottom, |
| int& margin_left) { |
| scoped_refptr<ComputedStyle> style = StyleForPage(page_index); |
| |
| double width = page_size.Width(); |
| double height = page_size.Height(); |
| switch (style->PageSizeType()) { |
| case EPageSizeType::kAuto: |
| break; |
| case EPageSizeType::kLandscape: |
| if (width < height) |
|