| /* |
| * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| * (C) 1999 Antti Koivisto (koivisto@kde.org) |
| * (C) 2001 Peter Kelly (pmk@post.com) |
| * (C) 2001 Dirk Mueller (mueller@kde.org) |
| * (C) 2007 David Smith (catfish.man@gmail.com) |
| * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012, 2013 Apple Inc. |
| * All rights reserved. |
| * (C) 2007 Eric Seidel (eric@webkit.org) |
| * |
| * 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/element.h" |
| |
| #include <bitset> |
| #include <memory> |
| |
| #include "cc/input/snap_selection_strategy.h" |
| #include "third_party/blink/public/platform/web_scroll_into_view_params.h" |
| #include "third_party/blink/renderer/bindings/core/v8/dictionary.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" |
| #include "third_party/blink/renderer/bindings/core/v8/scroll_into_view_options_or_boolean.h" |
| #include "third_party/blink/renderer/bindings/core/v8/string_or_trusted_html.h" |
| #include "third_party/blink/renderer/bindings/core/v8/string_or_trusted_html_or_trusted_script_or_trusted_script_url.h" |
| #include "third_party/blink/renderer/bindings/core/v8/string_or_trusted_script.h" |
| #include "third_party/blink/renderer/bindings/core/v8/string_or_trusted_script_url.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/css/css_animations.h" |
| #include "third_party/blink/renderer/core/aom/computed_accessible_node.h" |
| #include "third_party/blink/renderer/core/css/css_identifier_value.h" |
| #include "third_party/blink/renderer/core/css/css_numeric_literal_value.h" |
| #include "third_party/blink/renderer/core/css/css_primitive_value.h" |
| #include "third_party/blink/renderer/core/css/css_property_value_set.h" |
| #include "third_party/blink/renderer/core/css/css_selector_watch.h" |
| #include "third_party/blink/renderer/core/css/css_style_sheet.h" |
| #include "third_party/blink/renderer/core/css/css_value.h" |
| #include "third_party/blink/renderer/core/css/parser/css_parser.h" |
| #include "third_party/blink/renderer/core/css/property_set_css_style_declaration.h" |
| #include "third_party/blink/renderer/core/css/resolver/selector_filter_parent_scope.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_value_keywords.h" |
| #include "third_party/blink/renderer/core/display_lock/display_lock_context.h" |
| #include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h" |
| #include "third_party/blink/renderer/core/display_lock/render_subtree_activation_event.h" |
| #include "third_party/blink/renderer/core/dom/attr.h" |
| #include "third_party/blink/renderer/core/dom/dataset_dom_string_map.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/dom_token_list.h" |
| #include "third_party/blink/renderer/core/dom/element_data_cache.h" |
| #include "third_party/blink/renderer/core/dom/element_rare_data.h" |
| #include "third_party/blink/renderer/core/dom/element_traversal.h" |
| #include "third_party/blink/renderer/core/dom/events/event_dispatch_forbidden_scope.h" |
| #include "third_party/blink/renderer/core/dom/events/event_dispatcher.h" |
| #include "third_party/blink/renderer/core/dom/first_letter_pseudo_element.h" |
| #include "third_party/blink/renderer/core/dom/layout_tree_builder.h" |
| #include "third_party/blink/renderer/core/dom/mutation_observer_interest_group.h" |
| #include "third_party/blink/renderer/core/dom/mutation_record.h" |
| #include "third_party/blink/renderer/core/dom/named_node_map.h" |
| #include "third_party/blink/renderer/core/dom/node_computed_style.h" |
| #include "third_party/blink/renderer/core/dom/pointer_lock_options.h" |
| #include "third_party/blink/renderer/core/dom/presentation_attribute_style.h" |
| #include "third_party/blink/renderer/core/dom/pseudo_element.h" |
| #include "third_party/blink/renderer/core/dom/scriptable_document_parser.h" |
| #include "third_party/blink/renderer/core/dom/shadow_root.h" |
| #include "third_party/blink/renderer/core/dom/shadow_root_init.h" |
| #include "third_party/blink/renderer/core/dom/shadow_root_v0.h" |
| #include "third_party/blink/renderer/core/dom/slot_assignment.h" |
| #include "third_party/blink/renderer/core/dom/space_split_string.h" |
| #include "third_party/blink/renderer/core/dom/text.h" |
| #include "third_party/blink/renderer/core/dom/v0_insertion_point.h" |
| #include "third_party/blink/renderer/core/dom/whitespace_attacher.h" |
| #include "third_party/blink/renderer/core/editing/editing_utilities.h" |
| #include "third_party/blink/renderer/core/editing/ephemeral_range.h" |
| #include "third_party/blink/renderer/core/editing/frame_selection.h" |
| #include "third_party/blink/renderer/core/editing/selection_template.h" |
| #include "third_party/blink/renderer/core/editing/serializers/serialization.h" |
| #include "third_party/blink/renderer/core/editing/set_selection_options.h" |
| #include "third_party/blink/renderer/core/editing/visible_selection.h" |
| #include "third_party/blink/renderer/core/events/focus_event.h" |
| #include "third_party/blink/renderer/core/frame/csp/content_security_policy.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_view.h" |
| #include "third_party/blink/renderer/core/frame/scroll_into_view_options.h" |
| #include "third_party/blink/renderer/core/frame/scroll_to_options.h" |
| #include "third_party/blink/renderer/core/frame/settings.h" |
| #include "third_party/blink/renderer/core/frame/visual_viewport.h" |
| #include "third_party/blink/renderer/core/fullscreen/fullscreen.h" |
| #include "third_party/blink/renderer/core/geometry/dom_rect.h" |
| #include "third_party/blink/renderer/core/geometry/dom_rect_list.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_registry.h" |
| #include "third_party/blink/renderer/core/html/custom/v0_custom_element.h" |
| #include "third_party/blink/renderer/core/html/custom/v0_custom_element_registration_context.h" |
| #include "third_party/blink/renderer/core/html/forms/html_form_controls_collection.h" |
| #include "third_party/blink/renderer/core/html/forms/html_options_collection.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_document.h" |
| #include "third_party/blink/renderer/core/html/html_element.h" |
| #include "third_party/blink/renderer/core/html/html_frame_element_base.h" |
| #include "third_party/blink/renderer/core/html/html_frame_owner_element.h" |
| #include "third_party/blink/renderer/core/html/html_html_element.h" |
| #include "third_party/blink/renderer/core/html/html_plugin_element.h" |
| #include "third_party/blink/renderer/core/html/html_slot_element.h" |
| #include "third_party/blink/renderer/core/html/html_table_rows_collection.h" |
| #include "third_party/blink/renderer/core/html/html_template_element.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/input/event_handler.h" |
| #include "third_party/blink/renderer/core/inspector/console_message.h" |
| #include "third_party/blink/renderer/core/intersection_observer/element_intersection_observer_data.h" |
| #include "third_party/blink/renderer/core/intersection_observer/intersection_observer_controller.h" |
| #include "third_party/blink/renderer/core/invisible_dom/activate_invisible_event.h" |
| #include "third_party/blink/renderer/core/layout/adjust_for_absolute_zoom.h" |
| #include "third_party/blink/renderer/core/layout/layout_text_fragment.h" |
| #include "third_party/blink/renderer/core/layout/layout_view.h" |
| #include "third_party/blink/renderer/core/page/chrome_client.h" |
| #include "third_party/blink/renderer/core/page/focus_controller.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/root_scroller_controller.h" |
| #include "third_party/blink/renderer/core/page/spatial_navigation.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_observation.h" |
| #include "third_party/blink/renderer/core/scroll/scrollable_area.h" |
| #include "third_party/blink/renderer/core/scroll/scrollbar_theme.h" |
| #include "third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.h" |
| #include "third_party/blink/renderer/core/svg/svg_a_element.h" |
| #include "third_party/blink/renderer/core/svg/svg_element.h" |
| #include "third_party/blink/renderer/core/svg_names.h" |
| #include "third_party/blink/renderer/core/trustedtypes/trusted_types_util.h" |
| #include "third_party/blink/renderer/core/xml_names.h" |
| #include "third_party/blink/renderer/platform/bindings/dom_data_store.h" |
| #include "third_party/blink/renderer/platform/bindings/exception_state.h" |
| #include "third_party/blink/renderer/platform/bindings/v8_dom_activity_logger.h" |
| #include "third_party/blink/renderer/platform/bindings/v8_dom_wrapper.h" |
| #include "third_party/blink/renderer/platform/bindings/v8_per_context_data.h" |
| #include "third_party/blink/renderer/platform/heap/heap.h" |
| #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" |
| #include "third_party/blink/renderer/platform/instrumentation/use_counter.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/wtf/hash_functions.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" |
| #include "third_party/blink/renderer/platform/wtf/text/text_position.h" |
| |
| namespace blink { |
| |
| enum class ClassStringContent { kEmpty, kWhiteSpaceOnly, kHasClasses }; |
| |
| namespace { |
| |
| class DisplayLockStyleScope { |
| public: |
| DisplayLockStyleScope(Element* element) : element_(element) { |
| // Note that we don't store context as a member of this scope, since it may |
| // get created as part of element self style recalc. |
| auto* context = element->GetDisplayLockContext(); |
| should_update_self_ = |
| !context || context->ShouldStyle(DisplayLockLifecycleTarget::kSelf); |
| } |
| |
| ~DisplayLockStyleScope() { |
| if (auto* context = element_->GetDisplayLockContext()) { |
| if (did_update_children_) |
| context->DidStyle(DisplayLockLifecycleTarget::kChildren); |
| } |
| } |
| |
| bool ShouldUpdateSelfStyle() const { return should_update_self_; } |
| bool ShouldUpdateChildStyle() const { |
| // We can't calculate this on construction time, because the element's lock |
| // state may changes after self-style calculation ShouldStyle(children). |
| auto* context = element_->GetDisplayLockContext(); |
| return !context || |
| context->ShouldStyle(DisplayLockLifecycleTarget::kChildren); |
| } |
| void DidUpdateChildStyle() { did_update_children_ = true; } |
| void DidUpdateSelfStyle() { |
| DCHECK(should_update_self_); |
| if (auto* context = element_->GetDisplayLockContext()) |
| context->DidStyle(DisplayLockLifecycleTarget::kSelf); |
| } |
| |
| void NotifyUpdateWasBlocked(DisplayLockContext::StyleType style) { |
| DCHECK(!ShouldUpdateChildStyle()); |
| // The only way to be blocked here is if we have a display lock context. |
| DCHECK(element_->GetDisplayLockContext()); |
| |
| element_->GetDisplayLockContext()->NotifyStyleRecalcWasBlocked(style); |
| } |
| |
| private: |
| UntracedMember<Element> element_; |
| bool should_update_self_ = false; |
| bool did_update_children_ = false; |
| }; |
| |
| bool IsRootEditableElementWithCounting(const Element& element) { |
| bool is_editable = IsRootEditableElement(element); |
| Document& doc = element.GetDocument(); |
| if (!doc.IsActive()) |
| return is_editable; |
| // -webkit-user-modify doesn't affect text control elements. |
| if (element.IsTextControl()) |
| return is_editable; |
| const auto* style = element.GetComputedStyle(); |
| if (!style) |
| return is_editable; |
| auto user_modify = style->UserModify(); |
| const AtomicString& ce_value = |
| element.FastGetAttribute(html_names::kContenteditableAttr); |
| if (ce_value.IsNull() || DeprecatedEqualIgnoringCase(ce_value, "false")) { |
| if (user_modify == EUserModify::kReadWritePlaintextOnly) { |
| UseCounter::Count(doc, WebFeature::kPlainTextEditingEffective); |
| UseCounter::Count(doc, WebFeature::kWebKitUserModifyPlainTextEffective); |
| UseCounter::Count(doc, WebFeature::kWebKitUserModifyEffective); |
| } else if (user_modify == EUserModify::kReadWrite) { |
| UseCounter::Count(doc, WebFeature::kWebKitUserModifyReadWriteEffective); |
| UseCounter::Count(doc, WebFeature::kWebKitUserModifyEffective); |
| } |
| } else if (ce_value.IsEmpty() || |
| DeprecatedEqualIgnoringCase(ce_value, "true")) { |
| if (user_modify == EUserModify::kReadWritePlaintextOnly) { |
| UseCounter::Count(doc, WebFeature::kPlainTextEditingEffective); |
| UseCounter::Count(doc, WebFeature::kWebKitUserModifyPlainTextEffective); |
| UseCounter::Count(doc, WebFeature::kWebKitUserModifyEffective); |
| } else if (user_modify == EUserModify::kReadOnly) { |
| UseCounter::Count(doc, WebFeature::kWebKitUserModifyReadOnlyEffective); |
| UseCounter::Count(doc, WebFeature::kWebKitUserModifyEffective); |
| } |
| } else if (DeprecatedEqualIgnoringCase(ce_value, "plaintext-only")) { |
| UseCounter::Count(doc, WebFeature::kPlainTextEditingEffective); |
| if (user_modify == EUserModify::kReadWrite) { |
| UseCounter::Count(doc, WebFeature::kWebKitUserModifyReadWriteEffective); |
| UseCounter::Count(doc, WebFeature::kWebKitUserModifyEffective); |
| } else if (user_modify == EUserModify::kReadOnly) { |
| UseCounter::Count(doc, WebFeature::kWebKitUserModifyReadOnlyEffective); |
| UseCounter::Count(doc, WebFeature::kWebKitUserModifyEffective); |
| } |
| } |
| return is_editable; |
| } |
| |
| // Return true if we're absolutely sure that this node is going to establish a |
| // new formatting context. Whether or not it establishes a new formatting |
| // context cannot be accurately determined until we have actually created the |
| // object (see LayoutBlockFlow::CreatesNewFormattingContext()), so this function |
| // may (and is allowed to) return false negatives, but NEVER false positives. |
| bool DefinitelyNewFormattingContext(const Node& node, |
| const ComputedStyle& style) { |
| auto display = style.Display(); |
| if (display == EDisplay::kInline || display == EDisplay::kContents) |
| return false; |
| // The only block-container display types that potentially don't establish a |
| // new formatting context, are 'block' and 'list-item'. |
| if (display != EDisplay::kBlock && display != EDisplay::kListItem) { |
| // DETAILS and SUMMARY elements partially or completely ignore the display |
| // type, though, and may end up disregarding the display type and just |
| // create block containers. And those don't necessarily create a formatting |
| // context. |
| if (!IsA<HTMLDetailsElement>(node) && !IsHTMLSummaryElement(node)) |
| return true; |
| } |
| if (!style.IsOverflowVisible()) |
| return node.GetDocument().ViewportDefiningElement() != &node; |
| if (style.HasOutOfFlowPosition() || style.IsFloating() || |
| style.ContainsPaint() || style.ContainsLayout() || |
| style.SpecifiesColumns()) |
| return true; |
| if (node.GetDocument().documentElement() == &node) |
| return true; |
| if (const Element* element = DynamicTo<Element>(&node)) { |
| // Replaced elements are considered to create a new formatting context, in |
| // the sense that they can't possibly have children that participate in the |
| // same formatting context as their parent. |
| if (IsHTMLObjectElement(element)) { |
| // OBJECT elements are special, though. If they use fallback content, they |
| // act as regular elements, and we can't claim that they establish a |
| // formatting context, just based on element type, since children may very |
| // well participate in the same formatting context as the parent of the |
| // OBJECT. |
| if (!element->ChildrenCanHaveStyle()) |
| return true; |
| } else if (IsHTMLImageElement(element) || element->IsFormControlElement() || |
| element->IsMediaElement() || element->IsFrameOwnerElement()) { |
| return true; |
| } |
| } |
| if (const Node* parent = LayoutTreeBuilderTraversal::LayoutParent(node)) |
| return parent->ComputedStyleRef().IsDisplayFlexibleOrGridBox(); |
| return false; |
| } |
| |
| bool CalculateStyleShouldForceLegacyLayout(const Element& element, |
| const ComputedStyle& style) { |
| const Document& document = element.GetDocument(); |
| |
| if (style.Display() == EDisplay::kLayoutCustom || |
| style.Display() == EDisplay::kInlineLayoutCustom) |
| return false; |
| |
| // TODO(layout-dev): Once LayoutNG handles inline content editable, we |
| // should get rid of following code fragment. |
| if (!RuntimeEnabledFeatures::EditingNGEnabled()) { |
| if (style.UserModify() != EUserModify::kReadOnly || document.InDesignMode()) |
| return true; |
| } |
| |
| if (style.IsDeprecatedWebkitBox()) |
| return true; |
| |
| if (!RuntimeEnabledFeatures::LayoutNGBlockFragmentationEnabled()) { |
| // Disable NG for the entire subtree if we're establishing a block |
| // fragmentation context. |
| if (style.SpecifiesColumns()) |
| return true; |
| if (document.Printing() && element == document.documentElement()) |
| return true; |
| |
| // Fall back to legacy layout for frameset documents. The frameset itself |
| // (and the frames) can only create legacy layout objects anyway (no NG |
| // counterpart for them yet). However, the layout object for the HTML root |
| // element would be an NG one. If we'd then print the document, we'd fall |
| // back to legacy layout (because of the above check), which would re-attach |
| // all layout objects, which would cause the frameset to lose state of some |
| // sort, leaving everything blank when printed. |
| if (document.IsFrameSet()) |
| return true; |
| } |
| |
| // 'text-combine-upright' property is not supported yet. |
| if (style.HasTextCombine() && !style.IsHorizontalWritingMode()) |
| return true; |
| |
| if (style.InsideNGFragmentationContext()) { |
| // If we're inside an NG block fragmentation context, all fragmentable boxes |
| // must be laid out by NG natively. We only allow legacy layout objects if |
| // they are monolithic (e.g. replaced content, inline-table, and so |
| // on). Inline display types end up on a line, and are therefore monolithic, |
| // so we can allow those. |
| if (!style.IsDisplayInlineType()) { |
| if (style.IsDisplayTableType() || style.IsDisplayFlexibleOrGridBox()) |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool HasLeftwardDirection(const Element& element) { |
| auto* style = element.GetComputedStyle(); |
| if (!style) |
| return false; |
| |
| WritingMode writing_mode = style->GetWritingMode(); |
| bool is_rtl = !style->IsLeftToRightDirection(); |
| return (writing_mode == WritingMode::kHorizontalTb && is_rtl) || |
| writing_mode == WritingMode::kVerticalRl || |
| writing_mode == WritingMode::kSidewaysRl; |
| } |
| |
| bool HasUpwardDirection(const Element& element) { |
| auto* style = element.GetComputedStyle(); |
| if (!style) |
| return false; |
| |
| WritingMode writing_mode = style->GetWritingMode(); |
| bool is_rtl = !style->IsLeftToRightDirection(); |
| return (is_rtl && (writing_mode == WritingMode::kVerticalRl || |
| writing_mode == WritingMode::kVerticalLr || |
| writing_mode == WritingMode::kSidewaysRl)) || |
| (!is_rtl && writing_mode == WritingMode::kSidewaysLr); |
| } |
| |
| // TODO(meredithl): Automatically generate this method once the IDL compiler has |
| // been refactored. See http://crbug.com/839389 for details. |
| bool IsElementReflectionAttribute(const QualifiedName& name) { |
| if (name == html_names::kAriaActivedescendantAttr) |
| return true; |
| if (name == html_names::kAriaControlsAttr) |
| return true; |
| if (name == html_names::kAriaDescribedbyAttr) |
| return true; |
| if (name == html_names::kAriaDetailsAttr) |
| return true; |
| if (name == html_names::kAriaErrormessageAttr) |
| return true; |
| if (name == html_names::kAriaFlowtoAttr) |
| return true; |
| if (name == html_names::kAriaLabelledbyAttr) |
| return true; |
| if (name == html_names::kAriaOwnsAttr) |
| return true; |
| return false; |
| } |
| |
| HeapVector<Member<Element>>* GetExplicitlySetElementsForAttr( |
| Element* element, |
| QualifiedName name) { |
| ExplicitlySetAttrElementsMap* element_attribute_map = |
| element->GetDocument().GetExplicitlySetAttrElementsMap(element); |
| return element_attribute_map->at(name); |
| } |
| |
| // Checks that the given element |candidate| is a descendant of |
| // |attribute_element|'s shadow including ancestors. |
| bool ElementIsDescendantOfShadowIncludingAncestor( |
| const Element& attribute_element, |
| const Element& candidate) { |
| // TODO(meredithl): Update this to allow setting relationships for elements |
| // outside of the DOM once the spec is finalized. For consistency and |
| // simplicity, for now it is disallowed. |
| if (!attribute_element.IsInTreeScope() || !candidate.IsInTreeScope()) |
| return false; |
| ShadowRoot* nearest_root = attribute_element.ContainingShadowRoot(); |
| const Element* shadow_host = &attribute_element; |
| while (nearest_root) { |
| shadow_host = &nearest_root->host(); |
| if (candidate.IsDescendantOf(nearest_root)) |
| return true; |
| nearest_root = shadow_host->ContainingShadowRoot(); |
| } |
| |
| Element* document_element = shadow_host->GetDocument().documentElement(); |
| return candidate.IsDescendantOf(document_element); |
| } |
| |
| // The first algorithm in |
| // https://html.spec.whatwg.org/C/#the-autofocus-attribute |
| void EnqueueAutofocus(Element& element) { |
| // When an element with the autofocus attribute specified is inserted into a |
| // document, run the following steps: |
| DCHECK(element.isConnected()); |
| if (!element.IsAutofocusable()) |
| return; |
| |
| // 1. If the user has indicated (for example, by starting to type in a form |
| // control) that they do not wish focus to be changed, then optionally return. |
| |
| // We don't implement this optional step. If other browsers have such |
| // behavior, we should follow it or standardize it. |
| |
| // 2. Let target be the element's node document. |
| Document& doc = element.GetDocument(); |
| |
| // 3. If target's browsing context is null, then return. |
| if (!doc.GetFrame()) |
| return; |
| |
| // 4. If target's active sandboxing flag set has the sandboxed automatic |
| // features browsing context flag, then return. |
| if (doc.IsSandboxed(WebSandboxFlags::kAutomaticFeatures)) { |
| doc.AddConsoleMessage(ConsoleMessage::Create( |
| mojom::ConsoleMessageSource::kSecurity, |
| mojom::ConsoleMessageLevel::kError, |
| "Blocked autofocusing on a form control because the form's frame is " |
| "sandboxed and the 'allow-scripts' permission is not set.")); |
| return; |
| } |
| |
| // 5. Let topDocument be the active document of target's browsing context's |
| // top-level browsing context. |
| // 6. If target's origin is not the same as the origin of topDocument, |
| // then return. |
| if (!doc.IsInMainFrame() && |
| !doc.TopFrameOrigin()->CanAccess(doc.GetSecurityOrigin())) { |
| doc.AddConsoleMessage(ConsoleMessage::Create( |
| mojom::ConsoleMessageSource::kSecurity, |
| mojom::ConsoleMessageLevel::kError, |
| "Blocked autofocusing on a form control in a cross-origin subframe.")); |
| return; |
| } |
| |
| element.GetDocument().TopDocument().EnqueueAutofocusCandidate(element); |
| } |
| |
| } // namespace |
| |
| Element::Element(const QualifiedName& tag_name, |
| Document* document, |
| ConstructionType type) |
| : ContainerNode(document, type), tag_name_(tag_name) {} |
| |
| Element* Element::GetAnimationTarget() { |
| return this; |
| } |
| |
| inline ElementRareData* Element::GetElementRareData() const { |
| DCHECK(HasRareData()); |
| return static_cast<ElementRareData*>(RareData()); |
| } |
| |
| inline ElementRareData& Element::EnsureElementRareData() { |
| return static_cast<ElementRareData&>(EnsureRareData()); |
| } |
| |
| bool Element::HasElementFlagInternal(ElementFlags mask) const { |
| return GetElementRareData()->HasElementFlag(mask); |
| } |
| |
| void Element::SetElementFlag(ElementFlags mask, bool value) { |
| if (!HasRareData() && !value) |
| return; |
| EnsureElementRareData().SetElementFlag(mask, value); |
| } |
| |
| void Element::ClearElementFlag(ElementFlags mask) { |
| if (!HasRareData()) |
| return; |
| GetElementRareData()->ClearElementFlag(mask); |
| } |
| |
| void Element::ClearTabIndexExplicitlyIfNeeded() { |
| if (HasRareData()) |
| GetElementRareData()->ClearTabIndexExplicitly(); |
| } |
| |
| void Element::SetTabIndexExplicitly() { |
| EnsureElementRareData().SetTabIndexExplicitly(); |
| } |
| |
| void Element::setTabIndex(int value) { |
| SetIntegralAttribute(html_names::kTabindexAttr, value); |
| } |
| |
| int Element::tabIndex() const { |
| // https://html.spec.whatwg.org/C/#dom-tabindex |
| // The tabIndex IDL attribute must reflect the value of the tabindex content |
| // attribute. The default value is 0 if the element is an a, area, button, |
| // frame, iframe, input, object, select, textarea, or SVG a element, or is a |
| // summary element that is a summary for its parent details. The default value |
| // is −1 otherwise. |
| return GetIntegralAttribute(html_names::kTabindexAttr, DefaultTabIndex()); |
| } |
| |
| int Element::DefaultTabIndex() const { |
| return -1; |
| } |
| |
| bool Element::IsFocusableStyle() const { |
| // Elements in canvas fallback content are not rendered, but they are allowed |
| // to be focusable as long as their canvas is displayed and visible. |
| if (IsInCanvasSubtree()) { |
| const HTMLCanvasElement* canvas = |
| Traversal<HTMLCanvasElement>::FirstAncestorOrSelf(*this); |
| DCHECK(canvas); |
| return canvas->GetLayoutObject() && |
| canvas->GetLayoutObject()->Style()->Visibility() == |
| EVisibility::kVisible; |
| } |
| |
| // Update style if we're in a display-locked subtree, because it isn't |
| // included in the normal style updates. We also need to update the layout |
| // here because some callers expect the layout stays clean. |
| if (DisplayLockUtilities::NearestLockedExclusiveAncestor(*this)) |
| GetDocument().UpdateStyleAndLayoutForNode(this); |
| |
| if (IsInsideInvisibleSubtree()) { |
| const ComputedStyle* style = |
| const_cast<Element*>(this)->EnsureComputedStyle(); |
| return style->Visibility() == EVisibility::kVisible && |
| style->Display() != EDisplay::kNone; |
| } |
| // FIXME: Even if we are not visible, we might have a child that is visible. |
| // Hyatt wants to fix that some day with a "has visible content" flag or the |
| // like. |
| return GetLayoutObject() && |
| GetLayoutObject()->Style()->Visibility() == EVisibility::kVisible; |
| } |
| |
| Node* Element::Clone(Document& factory, CloneChildrenFlag flag) const { |
| return flag == CloneChildrenFlag::kClone ? &CloneWithChildren(&factory) |
| : &CloneWithoutChildren(&factory); |
| } |
| |
| Element& Element::CloneWithChildren(Document* nullable_factory) const { |
| Element& clone = CloneWithoutAttributesAndChildren( |
| nullable_factory ? *nullable_factory : GetDocument()); |
| // This will catch HTML elements in the wrong namespace that are not correctly |
| // copied. This is a sanity check as HTML overloads some of the DOM methods. |
| DCHECK_EQ(IsHTMLElement(), clone.IsHTMLElement()); |
| |
| clone.CloneAttributesFrom(*this); |
| clone.CloneNonAttributePropertiesFrom(*this, CloneChildrenFlag::kClone); |
| clone.CloneChildNodesFrom(*this); |
| return clone; |
| } |
| |
| Element& Element::CloneWithoutChildren(Document* nullable_factory) const { |
| Element& clone = CloneWithoutAttributesAndChildren( |
| nullable_factory ? *nullable_factory : GetDocument()); |
| // This will catch HTML elements in the wrong namespace that are not correctly |
| // copied. This is a sanity check as HTML overloads some of the DOM methods. |
| DCHECK_EQ(IsHTMLElement(), clone.IsHTMLElement()); |
| |
| clone.CloneAttributesFrom(*this); |
| clone.CloneNonAttributePropertiesFrom(*this, CloneChildrenFlag::kSkip); |
| return clone; |
| } |
| |
| Element& Element::CloneWithoutAttributesAndChildren(Document& factory) const { |
| return *factory.CreateElement(TagQName(), CreateElementFlags::ByCloneNode(), |
| IsValue()); |
| } |
| |
| Attr* Element::DetachAttribute(wtf_size_t index) { |
| DCHECK(GetElementData()); |
| const Attribute& attribute = GetElementData()->Attributes().at(index); |
| Attr* attr_node = AttrIfExists(attribute.GetName()); |
| if (attr_node) { |
| DetachAttrNodeAtIndex(attr_node, index); |
| } else { |
| attr_node = MakeGarbageCollected<Attr>(GetDocument(), attribute.GetName(), |
| attribute.Value()); |
| RemoveAttributeInternal(index, kNotInSynchronizationOfLazyAttribute); |
| } |
| return attr_node; |
| } |
| |
| void Element::DetachAttrNodeAtIndex(Attr* attr, wtf_size_t index) { |
| DCHECK(attr); |
| DCHECK(GetElementData()); |
| |
| const Attribute& attribute = GetElementData()->Attributes().at(index); |
| DCHECK(attribute.GetName() == attr->GetQualifiedName()); |
| DetachAttrNodeFromElementWithValue(attr, attribute.Value()); |
| RemoveAttributeInternal(index, kNotInSynchronizationOfLazyAttribute); |
| } |
| |
| void Element::removeAttribute(const QualifiedName& name) { |
| if (!GetElementData()) |
| return; |
| |
| wtf_size_t index = GetElementData()->Attributes().FindIndex(name); |
| if (index == kNotFound) |
| return; |
| |
| RemoveAttributeInternal(index, kNotInSynchronizationOfLazyAttribute); |
| } |
| |
| void Element::SetBooleanAttribute(const QualifiedName& name, bool value) { |
| if (value) |
| setAttribute(name, g_empty_atom); |
| else |
| removeAttribute(name); |
| } |
| |
| void Element::SynchronizeContentAttributeAndElementReference( |
| const QualifiedName& name) { |
| ExplicitlySetAttrElementsMap* element_attribute_map = |
| GetDocument().GetExplicitlySetAttrElementsMap(this); |
| element_attribute_map->erase(name); |
| } |
| |
| void Element::SetElementAttribute(const QualifiedName& name, Element* element) { |
| ExplicitlySetAttrElementsMap* explicitly_set_attr_elements_map_ = |
| GetDocument().GetExplicitlySetAttrElementsMap(this); |
| |
| // If the reflected element is explicitly null, or is not a member of this |
| // elements shadow including ancestor tree, then we remove the content |
| // attribute and the explicitly set attr-element. |
| // Note this means that explicitly set elements can cross ancestral shadow |
| // boundaries, but not descendant ones. See the spec for more details: |
| // https://whatpr.org/html/3917/common-dom-interfaces.html#reflecting-content-attributes-in-idl-attributes:concept-shadow-including-ancestor |
| if (!element || |
| !ElementIsDescendantOfShadowIncludingAncestor(*this, *element)) { |
| explicitly_set_attr_elements_map_->erase(name); |
| removeAttribute(name); |
| return; |
| } |
| |
| const AtomicString id = element->GetIdAttribute(); |
| |
| // Explicitly set attr-elements must have a valid id attribute, and also |
| // refer to the first element in tree order of |this| elements node tree in |
| // order for the content attribute to reflect the ID. Where these conditions |
| // aren't met, the content attribute should reflect the empty string. Note |
| // that the explicitly set attr-element is still set. See the spec for more |
| // details: |
| // https://whatpr.org/html/3917/common-dom-interfaces.html#reflecting-content-attributes-in-idl-attributes:root-2 |
| if (id.IsNull() || GetTreeScope() != element->GetTreeScope() || |
| GetTreeScope().getElementById(id) != element) |
| setAttribute(name, g_empty_atom); |
| else |
| setAttribute(name, id); |
| |
| auto result = explicitly_set_attr_elements_map_->insert(name, nullptr); |
| if (result.is_new_entry) { |
| result.stored_value->value = |
| MakeGarbageCollected<HeapVector<Member<Element>>>(); |
| } else { |
| result.stored_value->value->clear(); |
| } |
| result.stored_value->value->push_back(element); |
| } |
| |
| Element* Element::GetElementAttribute(const QualifiedName& name) { |
| HeapVector<Member<Element>>* element_attribute_vector = |
| GetExplicitlySetElementsForAttr(this, name); |
| if (element_attribute_vector) { |
| DCHECK_EQ(element_attribute_vector->size(), 1u); |
| Element* explicitly_set_element = element_attribute_vector->at(0); |
| // Only return the explicit element if it still exists in the same scope. |
| if (explicitly_set_element) |
| return explicitly_set_element; |
| } |
| |
| // Compute the attr-associated element, this can be null. |
| AtomicString id = getAttribute(name); |
| if (id.IsNull()) |
| return nullptr; |
| |
| // Will return null if the id is empty. |
| return GetTreeScope().getElementById(id); |
| } |
| |
| void Element::SetElementArrayAttribute( |
| const QualifiedName& name, |
| HeapVector<Member<Element>> given_elements, |
| bool is_null) { |
| ExplicitlySetAttrElementsMap* element_attribute_map = |
| GetDocument().GetExplicitlySetAttrElementsMap(this); |
| |
| if (is_null) { |
| element_attribute_map->erase(name); |
| removeAttribute(name); |
| return; |
| } |
| |
| // Get or create element array, and remove any pre-existing elements. |
| // Note that this performs two look ups on |name| within the map, as |
| // modifying the content attribute will cause the synchronization steps to |
| // be run which with modifies the hash map, and can cause a rehash. |
| HeapVector<Member<Element>>* elements = element_attribute_map->at(name); |
| if (!elements) |
| elements = MakeGarbageCollected<HeapVector<Member<Element>>>(); |
| else |
| elements->clear(); |
| SpaceSplitString value; |
| |
| for (auto element : given_elements) { |
| // Elements that are not descendants of this element's shadow including |
| // ancestors are dropped. |
| if (!ElementIsDescendantOfShadowIncludingAncestor(*this, *element)) |
| continue; |
| |
| // If |value| is null, this means a previous element must have been invalid, |
| // and the content attribute should reflect the empty string, so we don't |
| // continue trying to compute it. |
| if (value.IsNull() && !elements->IsEmpty()) { |
| elements->push_back(element); |
| continue; |
| } |
| |
| elements->push_back(element); |
| const AtomicString given_element_id = element->GetIdAttribute(); |
| |
| // We compute the content attribute string as a space separated string of |
| // the given |element| ids. Every |element| in |given_elements| must have an |
| // id, must be in the same tree scope and must be the first id in tree order |
| // with that id, otherwise the content attribute should reflect the empty |
| // string. |
| if (given_element_id.IsNull() || |
| GetTreeScope() != element->GetTreeScope() || |
| GetTreeScope().getElementById(given_element_id) != element) { |
| value.Clear(); |
| continue; |
| } |
| |
| // Whitespace between elements is added when the string is serialized. |
| value.Add(given_element_id); |
| } |
| |
| setAttribute(name, value.SerializeToString()); |
| element_attribute_map->Set(name, elements); |
| } |
| |
| HeapVector<Member<Element>> Element::GetElementArrayAttribute( |
| const QualifiedName& name, |
| bool& is_null) { |
| HeapVector<Member<Element>>* explicitly_set_elements = |
| GetExplicitlySetElementsForAttr(this, name); |
| is_null = false; |
| if (explicitly_set_elements) { |
| return *explicitly_set_elements; |
| } |
| |
| String attribute_value = getAttribute(name).GetString(); |
| HeapVector<Member<Element>> content_elements; |
| |
| Vector<String> tokens; |
| attribute_value = attribute_value.SimplifyWhiteSpace(); |
| attribute_value.Split(' ', tokens); |
| |
| for (auto token : tokens) { |
| Element* candidate = GetTreeScope().getElementById(AtomicString(token)); |
| if (candidate) |
| content_elements.push_back(candidate); |
| } |
| if (content_elements.IsEmpty()) |
| is_null = true; |
| return content_elements; |
| } |
| |
| NamedNodeMap* Element::attributesForBindings() const { |
| ElementRareData& rare_data = |
| const_cast<Element*>(this)->EnsureElementRareData(); |
| if (NamedNodeMap* attribute_map = rare_data.AttributeMap()) |
| return attribute_map; |
| |
| rare_data.SetAttributeMap( |
| MakeGarbageCollected<NamedNodeMap>(const_cast<Element*>(this))); |
| return rare_data.AttributeMap(); |
| } |
| |
| Vector<AtomicString> Element::getAttributeNames() const { |
| Vector<AtomicString> attributesVector; |
| if (!hasAttributes()) |
| return attributesVector; |
| |
| AttributeCollection attributes = element_data_->Attributes(); |
| attributesVector.ReserveInitialCapacity(attributes.size()); |
| for (const Attribute& attr : attributes) |
| attributesVector.UncheckedAppend(attr.GetName().ToString()); |
| return attributesVector; |
| } |
| |
| ElementAnimations* Element::GetElementAnimations() const { |
| if (HasRareData()) |
| return GetElementRareData()->GetElementAnimations(); |
| return nullptr; |
| } |
| |
| ElementAnimations& Element::EnsureElementAnimations() { |
| ElementRareData& rare_data = EnsureElementRareData(); |
| if (!rare_data.GetElementAnimations()) |
| rare_data.SetElementAnimations(MakeGarbageCollected<ElementAnimations>()); |
| return *rare_data.GetElementAnimations(); |
| } |
| |
| bool Element::HasAnimations() const { |
| if (!HasRareData()) |
| return false; |
| |
| ElementAnimations* element_animations = |
| GetElementRareData()->GetElementAnimations(); |
| return element_animations && !element_animations->IsEmpty(); |
| } |
| |
| Node::NodeType Element::getNodeType() const { |
| return kElementNode; |
| } |
| |
| bool Element::hasAttribute(const QualifiedName& name) const { |
| return hasAttributeNS(name.NamespaceURI(), name.LocalName()); |
| } |
| |
| bool Element::HasAttributeIgnoringNamespace( |
| const AtomicString& local_name) const { |
| if (!GetElementData()) |
| return false; |
| SynchronizeAttribute(local_name); |
| AtomicString name = LowercaseIfNecessary(local_name); |
| for (const Attribute& attribute : GetElementData()->Attributes()) { |
| if (attribute.LocalName() == name) |
| return true; |
| } |
| return false; |
| } |
| |
| void Element::SynchronizeAllAttributes() const { |
| if (!GetElementData()) |
| return; |
| // NOTE: AnyAttributeMatches in selector_checker.cc currently assumes that all |
| // lazy attributes have a null namespace. If that ever changes we'll need to |
| // fix that code. |
| if (GetElementData()->style_attribute_is_dirty_) { |
| DCHECK(IsStyledElement()); |
| SynchronizeStyleAttributeInternal(); |
| } |
| if (GetElementData()->animated_svg_attributes_are_dirty_) |
| To<SVGElement>(this)->SynchronizeAnimatedSVGAttribute(AnyQName()); |
| } |
| |
| inline void Element::SynchronizeAttribute(const QualifiedName& name) const { |
| if (!GetElementData()) |
| return; |
| if (UNLIKELY(name == html_names::kStyleAttr && |
| GetElementData()->style_attribute_is_dirty_)) { |
| DCHECK(IsStyledElement()); |
| SynchronizeStyleAttributeInternal(); |
| return; |
| } |
| if (UNLIKELY(GetElementData()->animated_svg_attributes_are_dirty_)) { |
| // See comment in the AtomicString version of SynchronizeAttribute() |
| // also. |
| To<SVGElement>(this)->SynchronizeAnimatedSVGAttribute(name); |
| } |
| } |
| |
| void Element::SynchronizeAttribute(const AtomicString& local_name) const { |
| // This version of synchronizeAttribute() is streamlined for the case where |
| // you don't have a full QualifiedName, e.g when called from DOM API. |
| if (!GetElementData()) |
| return; |
| if (GetElementData()->style_attribute_is_dirty_ && |
| LowercaseIfNecessary(local_name) == html_names::kStyleAttr.LocalName()) { |
| DCHECK(IsStyledElement()); |
| SynchronizeStyleAttributeInternal(); |
| return; |
| } |
| if (GetElementData()->animated_svg_attributes_are_dirty_) { |
| // We're not passing a namespace argument on purpose. SVGNames::*Attr are |
| // defined w/o namespaces as well. |
| |
| // FIXME: this code is called regardless of whether name is an |
| // animated SVG Attribute. It would seem we should only call this method |
| // if SVGElement::isAnimatableAttribute is true, but the list of |
| // animatable attributes in isAnimatableAttribute does not suffice to |
| // pass all web tests. Also, animated_svg_attributes_are_dirty_ stays |
| // dirty unless SynchronizeAnimatedSVGAttribute is called with |
| // AnyQName(). This means that even if Element::SynchronizeAttribute() |
| // is called on all attributes, animated_svg_attributes_are_dirty_ remains |
| // true. |
| To<SVGElement>(this)->SynchronizeAnimatedSVGAttribute( |
| QualifiedName(g_null_atom, local_name, g_null_atom)); |
| } |
| } |
| |
| const AtomicString& Element::getAttribute(const QualifiedName& name) const { |
| if (!GetElementData()) |
| return g_null_atom; |
| SynchronizeAttribute(name); |
| if (const Attribute* attribute = GetElementData()->Attributes().Find(name)) |
| return attribute->Value(); |
| return g_null_atom; |
| } |
| |
| AtomicString Element::LowercaseIfNecessary(const AtomicString& name) const { |
| return IsHTMLElement() && GetDocument().IsHTMLDocument() ? name.LowerASCII() |
| : name; |
| } |
| |
| const AtomicString& Element::nonce() const { |
| return HasRareData() ? GetElementRareData()->GetNonce() : g_null_atom; |
| } |
| |
| void Element::setNonce(const AtomicString& nonce) { |
| EnsureElementRareData().SetNonce(nonce); |
| } |
| |
| void Element::scrollIntoView(ScrollIntoViewOptionsOrBoolean arg) { |
| ScrollIntoViewOptions* options = ScrollIntoViewOptions::Create(); |
| if (arg.IsBoolean()) { |
| if (arg.GetAsBoolean()) |
| options->setBlock("start"); |
| else |
| options->setBlock("end"); |
| options->setInlinePosition("nearest"); |
| } else if (arg.IsScrollIntoViewOptions()) { |
| options = arg.GetAsScrollIntoViewOptions(); |
| } |
| scrollIntoViewWithOptions(options); |
| } |
| |
| void Element::scrollIntoView(bool align_to_top) { |
| ScrollIntoViewOptionsOrBoolean arg; |
| arg.SetBoolean(align_to_top); |
| scrollIntoView(arg); |
| } |
| |
| static ScrollAlignment ToPhysicalAlignment(const ScrollIntoViewOptions* options, |
| ScrollOrientation axis, |
| WritingMode writing_mode, |
| bool is_ltr) { |
| bool is_horizontal_writing_mode = IsHorizontalWritingMode(writing_mode); |
| String alignment = |
| ((axis == kHorizontalScroll && is_horizontal_writing_mode) || |
| (axis == kVerticalScroll && !is_horizontal_writing_mode)) |
| ? options->inlinePosition() |
| : options->block(); |
| |
| if (alignment == "center") |
| return ScrollAlignment::kAlignCenterAlways; |
| if (alignment == "nearest") |
| return ScrollAlignment::kAlignToEdgeIfNeeded; |
| if (alignment == "start") { |
| if (axis == kHorizontalScroll) { |
| switch (writing_mode) { |
| case WritingMode::kHorizontalTb: |
| return is_ltr ? ScrollAlignment::kAlignLeftAlways |
| : ScrollAlignment::kAlignRightAlways; |
| case WritingMode::kVerticalRl: |
| case WritingMode::kSidewaysRl: |
| return ScrollAlignment::kAlignRightAlways; |
| case WritingMode::kVerticalLr: |
| case WritingMode::kSidewaysLr: |
| return ScrollAlignment::kAlignLeftAlways; |
| default: |
| NOTREACHED(); |
| return ScrollAlignment::kAlignLeftAlways; |
| } |
| } else { |
| switch (writing_mode) { |
| case WritingMode::kHorizontalTb: |
| return ScrollAlignment::kAlignTopAlways; |
| case WritingMode::kVerticalRl: |
| case WritingMode::kSidewaysRl: |
| case WritingMode::kVerticalLr: |
| return is_ltr ? ScrollAlignment::kAlignTopAlways |
| : ScrollAlignment::kAlignBottomAlways; |
| case WritingMode::kSidewaysLr: |
| return is_ltr ? ScrollAlignment::kAlignBottomAlways |
| : ScrollAlignment::kAlignTopAlways; |
| default: |
| NOTREACHED(); |
| return ScrollAlignment::kAlignTopAlways; |
| } |
| } |
| } |
| if (alignment == "end") { |
| if (axis == kHorizontalScroll) { |
| switch (writing_mode) { |
| case WritingMode::kHorizontalTb: |
| return is_ltr ? ScrollAlignment::kAlignRightAlways |
| : ScrollAlignment::kAlignLeftAlways; |
| case WritingMode::kVerticalRl: |
| case WritingMode::kSidewaysRl: |
| return ScrollAlignment::kAlignLeftAlways; |
| case WritingMode::kVerticalLr: |
| case WritingMode::kSidewaysLr: |
| return ScrollAlignment::kAlignRightAlways; |
| default: |
| NOTREACHED(); |
| return ScrollAlignment::kAlignRightAlways; |
| } |
| } else { |
| switch (writing_mode) { |
| case WritingMode::kHorizontalTb: |
| return ScrollAlignment::kAlignBottomAlways; |
| case WritingMode::kVerticalRl: |
| case WritingMode::kSidewaysRl: |
| case WritingMode::kVerticalLr: |
| return is_ltr ? ScrollAlignment::kAlignBottomAlways |
| : ScrollAlignment::kAlignTopAlways; |
| case WritingMode::kSidewaysLr: |
| return is_ltr ? ScrollAlignment::kAlignTopAlways |
| : ScrollAlignment::kAlignBottomAlways; |
| default: |
| NOTREACHED(); |
| return ScrollAlignment::kAlignBottomAlways; |
| } |
| } |
| } |
| |
| // Default values |
| if (is_horizontal_writing_mode) { |
| return (axis == kHorizontalScroll) ? ScrollAlignment::kAlignToEdgeIfNeeded |
| : ScrollAlignment::kAlignTopAlways; |
| } |
| return (axis == kHorizontalScroll) ? ScrollAlignment::kAlignLeftAlways |
| : ScrollAlignment::kAlignToEdgeIfNeeded; |
| } |
| |
| void Element::scrollIntoViewWithOptions(const ScrollIntoViewOptions* options) { |
| ActivateDisplayLockIfNeeded(DisplayLockActivationReason::kScrollIntoView); |
| GetDocument().EnsurePaintLocationDataValidForNode(this); |
| ScrollIntoViewNoVisualUpdate(options); |
| } |
| |
| void Element::ScrollIntoViewNoVisualUpdate( |
| const ScrollIntoViewOptions* options) { |
| if (!GetLayoutObject() || !GetDocument().GetPage()) |
| return; |
| |
| if (DisplayLockPreventsActivation( |
| DisplayLockActivationReason::kScrollIntoView)) |
| return; |
| |
| ScrollBehavior behavior = (options->behavior() == "smooth") |
| ? kScrollBehaviorSmooth |
| : kScrollBehaviorAuto; |
| |
| WritingMode writing_mode = GetComputedStyle()->GetWritingMode(); |
| bool is_ltr = GetComputedStyle()->IsLeftToRightDirection(); |
| ScrollAlignment align_x = |
| ToPhysicalAlignment(options, kHorizontalScroll, writing_mode, is_ltr); |
| ScrollAlignment align_y = |
| ToPhysicalAlignment(options, kVerticalScroll, writing_mode, is_ltr); |
| |
| PhysicalRect bounds = BoundingBoxForScrollIntoView(); |
| GetLayoutObject()->ScrollRectToVisible( |
| bounds, {align_x, align_y, kProgrammaticScroll, |
| /*make_visible_in_visual_viewport=*/true, behavior}); |
| |
| GetDocument().SetSequentialFocusNavigationStartingPoint(this); |
| } |
| |
| void Element::scrollIntoViewIfNeeded(bool center_if_needed) { |
| GetDocument().EnsurePaintLocationDataValidForNode(this); |
| |
| if (!GetLayoutObject()) |
| return; |
| |
| PhysicalRect bounds = BoundingBoxForScrollIntoView(); |
| if (center_if_needed) { |
| GetLayoutObject()->ScrollRectToVisible( |
| bounds, {ScrollAlignment::kAlignCenterIfNeeded, |
| ScrollAlignment::kAlignCenterIfNeeded}); |
| } else { |
| GetLayoutObject()->ScrollRectToVisible( |
| bounds, {ScrollAlignment::kAlignToEdgeIfNeeded, |
| ScrollAlignment::kAlignToEdgeIfNeeded}); |
| } |
| } |
| |
| int Element::OffsetLeft() { |
| GetDocument().EnsurePaintLocationDataValidForNode(this); |
| if (LayoutBoxModelObject* layout_object = GetLayoutBoxModelObject()) |
| return AdjustForAbsoluteZoom::AdjustLayoutUnit( |
| LayoutUnit( |
| layout_object->PixelSnappedOffsetLeft(OffsetParent())), |
| layout_object->StyleRef()) |
| .Round(); |
| return 0; |
| } |
| |
| int Element::OffsetTop() { |
| GetDocument().EnsurePaintLocationDataValidForNode(this); |
| if (LayoutBoxModelObject* layout_object = GetLayoutBoxModelObject()) |
| return AdjustForAbsoluteZoom::AdjustLayoutUnit( |
| LayoutUnit(layout_object->PixelSnappedOffsetTop(OffsetParent())), |
| layout_object->StyleRef()) |
| .Round(); |
| return 0; |
| } |
| |
| int Element::OffsetWidth() { |
| GetDocument().EnsurePaintLocationDataValidForNode(this); |
| if (LayoutBoxModelObject* layout_object = GetLayoutBoxModelObject()) |
| return AdjustForAbsoluteZoom::AdjustLayoutUnit( |
| LayoutUnit( |
| layout_object->PixelSnappedOffsetWidth(OffsetParent())), |
| layout_object->StyleRef()) |
| .Round(); |
| return 0; |
| } |
| |
| int Element::OffsetHeight() { |
| GetDocument().EnsurePaintLocationDataValidForNode(this); |
| if (LayoutBoxModelObject* layout_object = GetLayoutBoxModelObject()) |
| return AdjustForAbsoluteZoom::AdjustLayoutUnit( |
| LayoutUnit( |
| layout_object->PixelSnappedOffsetHeight(OffsetParent())), |
| layout_object->StyleRef()) |
| .Round(); |
| return 0; |
| } |
| |
| Element* Element::OffsetParent() { |
| GetDocument().UpdateStyleAndLayoutForNode(this); |
| |
| LayoutObject* layout_object = GetLayoutObject(); |
| return layout_object ? layout_object->OffsetParent() : nullptr; |
| } |
| |
| int Element::clientLeft() { |
| GetDocument().UpdateStyleAndLayoutForNode(this); |
| |
| if (LayoutBox* layout_object = GetLayoutBox()) |
| return AdjustForAbsoluteZoom::AdjustLayoutUnit(layout_object->ClientLeft(), |
| layout_object->StyleRef()) |
| .Round(); |
| return 0; |
| } |
| |
| int Element::clientTop() { |
| GetDocument().UpdateStyleAndLayoutForNode(this); |
| |
| if (LayoutBox* layout_object = GetLayoutBox()) |
| return AdjustForAbsoluteZoom::AdjustLayoutUnit(layout_object->ClientTop(), |
| layout_object->StyleRef()) |
| .Round(); |
| return 0; |
| } |
| |
| int Element::clientWidth() { |
| // When in strict mode, clientWidth for the document element should return the |
| // width of the containing frame. |
| // When in quirks mode, clientWidth for the body element should return the |
| // width of the containing frame. |
| bool in_quirks_mode = GetDocument().InQuirksMode(); |
| if ((!in_quirks_mode && GetDocument().documentElement() == this) || |
| (in_quirks_mode && IsHTMLElement() && GetDocument().body() == this)) { |
| auto* layout_view = GetDocument().GetLayoutView(); |
| if (layout_view) { |
| // TODO(crbug.com/740879): Use per-page overlay scrollbar settings. |
| if (!ScrollbarThemeSettings::OverlayScrollbarsEnabled() || |
| !GetDocument().GetFrame()->IsLocalRoot()) |
| GetDocument().UpdateStyleAndLayoutForNode(this); |
| if (GetDocument().GetPage()->GetSettings().GetForceZeroLayoutHeight()) |
| return AdjustForAbsoluteZoom::AdjustLayoutUnit( |
| layout_view->OverflowClipRect(PhysicalOffset()).Width(), |
| layout_view->StyleRef()) |
| .Round(); |
| return AdjustForAbsoluteZoom::AdjustLayoutUnit( |
| LayoutUnit(layout_view->GetLayoutSize().Width()), |
| layout_view->StyleRef()) |
| .Round(); |
| } |
| } |
| |
| GetDocument().UpdateStyleAndLayoutForNode(this); |
| |
| if (LayoutBox* layout_object = GetLayoutBox()) |
| return AdjustForAbsoluteZoom::AdjustLayoutUnit( |
| LayoutUnit( |
| layout_object |
| ->PixelSnappedClientWidthWithTableSpecialBehavior()), |
| layout_object->StyleRef()) |
| .Round(); |
| return 0; |
| } |
| |
| int Element::clientHeight() { |
| // When in strict mode, clientHeight for the document element should return |
| // the height of the containing frame. |
| // When in quirks mode, clientHeight for the body element should return the |
| // height of the containing frame. |
| bool in_quirks_mode = GetDocument().InQuirksMode(); |
| |
| if ((!in_quirks_mode && GetDocument().documentElement() == this) || |
| (in_quirks_mode && IsHTMLElement() && GetDocument().body() == this)) { |
| auto* layout_view = GetDocument().GetLayoutView(); |
| if (layout_view) { |
| // TODO(crbug.com/740879): Use per-page overlay scrollbar settings. |
| if (!ScrollbarThemeSettings::OverlayScrollbarsEnabled() || |
| !GetDocument().GetFrame()->IsLocalRoot()) |
| GetDocument().UpdateStyleAndLayoutForNode(this); |
| if (GetDocument().GetPage()->GetSettings().GetForceZeroLayoutHeight()) |
| return AdjustForAbsoluteZoom::AdjustLayoutUnit( |
| layout_view->OverflowClipRect(PhysicalOffset()).Height(), |
| layout_view->StyleRef()) |
| .Round(); |
| return AdjustForAbsoluteZoom::AdjustLayoutUnit( |
| LayoutUnit(layout_view->GetLayoutSize().Height()), |
| layout_view->StyleRef()) |
| .Round(); |
| } |
| } |
| |
| GetDocument().UpdateStyleAndLayoutForNode(this); |
| |
| if (LayoutBox* layout_object = GetLayoutBox()) |
| return AdjustForAbsoluteZoom::AdjustLayoutUnit( |
| LayoutUnit( |
| layout_object |
| ->PixelSnappedClientHeightWithTableSpecialBehavior()), |
| layout_object->StyleRef()) |
| .Round(); |
| return 0; |
| } |
| |
| PaintLayerScrollableArea* Element::GetScrollableArea() const { |
| LayoutBox* box = GetLayoutBox(); |
| if (!box || !box->HasOverflowClip()) |
| return nullptr; |
| return box->GetScrollableArea(); |
| } |
| |
| double Element::scrollLeft() { |
| if (!InActiveDocument()) |
| return 0; |
| |
| GetDocument().UpdateStyleAndLayoutForNode(this); |
| |
| if (GetDocument().ScrollingElementNoLayout() == this) { |
| if (GetDocument().domWindow()) |
| return GetDocument().domWindow()->scrollX(); |
| return 0; |
| } |
| |
| if (PaintLayerScrollableArea* scrollable_area = GetScrollableArea()) { |
| DCHECK(GetLayoutBox()); |
| |
| if (HasLeftwardDirection(*this)) { |
| UseCounter::Count( |
| GetDocument(), |
| WebFeature:: |
| kElementWithLeftwardOrUpwardOverflowDirection_ScrollLeftOrTop); |
| } |
| |
| // In order to keep the behavior of element scroll consistent with document |
| // scroll, and consistent with the behavior of other vendors, the scrollLeft |
| // of a box is changed to the offset from |ScrollOrigin()|. |
| if (RuntimeEnabledFeatures::CSSOMViewScrollCoordinatesEnabled()) { |
| return AdjustForAbsoluteZoom::AdjustScroll( |
| scrollable_area->GetScrollOffset().Width(), *GetLayoutBox()); |
| } else { |
| return AdjustForAbsoluteZoom::AdjustScroll( |
| scrollable_area->ScrollPosition().X(), *GetLayoutBox()); |
| } |
| } |
| |
| return 0; |
| } |
| |
| double Element::scrollTop() { |
| if (!InActiveDocument()) |
| return 0; |
| |
| GetDocument().UpdateStyleAndLayoutForNode(this); |
| |
| if (GetDocument().ScrollingElementNoLayout() == this) { |
| if (GetDocument().domWindow()) |
| return GetDocument().domWindow()->scrollY(); |
| return 0; |
| } |
| |
| if (PaintLayerScrollableArea* scrollable_area = GetScrollableArea()) { |
| DCHECK(GetLayoutBox()); |
| |
| if (HasUpwardDirection(*this)) { |
| UseCounter::Count( |
| GetDocument(), |
| WebFeature:: |
| kElementWithLeftwardOrUpwardOverflowDirection_ScrollLeftOrTop); |
| } |
| |
| // In order to keep the behavior of element scroll consistent with document |
| // scroll, and consistent with the behavior of other vendors, the scrollTop |
| // of a box is changed to the offset from |ScrollOrigin()|. |
| if (RuntimeEnabledFeatures::CSSOMViewScrollCoordinatesEnabled()) { |
| return AdjustForAbsoluteZoom::AdjustScroll( |
| scrollable_area->GetScrollOffset().Height(), *GetLayoutBox()); |
| } else { |
| return AdjustForAbsoluteZoom::AdjustScroll( |
| scrollable_area->ScrollPosition().Y(), *GetLayoutBox()); |
| } |
| } |
| |
| return 0; |
| } |
| |
| void Element::setScrollLeft(double new_left) { |
| if (!InActiveDocument()) |
| return; |
| |
| GetDocument().UpdateStyleAndLayoutForNode(this); |
| |
| new_left = ScrollableArea::NormalizeNonFiniteScroll(new_left); |
| |
| if (GetDocument().ScrollingElementNoLayout() == this) { |
| if (LocalDOMWindow* window = GetDocument().domWindow()) { |
| ScrollToOptions* options = ScrollToOptions::Create(); |
| options->setLeft(new_left); |
| window->scrollTo(options); |
| } |
| } else if (PaintLayerScrollableArea* scrollable_area = GetScrollableArea()) { |
| LayoutBox* box = GetLayoutBox(); |
| DCHECK(box); |
| |
| if (HasLeftwardDirection(*this)) { |
| UseCounter::Count( |
| GetDocument(), |
| WebFeature:: |
| kElementWithLeftwardOrUpwardOverflowDirection_ScrollLeftOrTop); |
| if (new_left > 0) { |
| UseCounter::Count( |
| GetDocument(), |
| WebFeature:: |
| kElementWithLeftwardOrUpwardOverflowDirection_ScrollLeftOrTopSetPositive); |
| } |
| } |
| |
| if (RuntimeEnabledFeatures::CSSOMViewScrollCoordinatesEnabled()) { |
| ScrollOffset end_offset(new_left * box->Style()->EffectiveZoom(), |
| scrollable_area->GetScrollOffset().Height()); |
| std::unique_ptr<cc::SnapSelectionStrategy> strategy = |
| cc::SnapSelectionStrategy::CreateForEndPosition( |
| gfx::ScrollOffset( |
| scrollable_area->ScrollOffsetToPosition(end_offset)), |
| true, false); |
| base::Optional<FloatPoint> snap_point = |
| scrollable_area->GetSnapPositionAndSetTarget(*strategy); |
| if (snap_point.has_value()) { |
| end_offset = |
| scrollable_area->ScrollPositionToOffset(snap_point.value()); |
| } |
| scrollable_area->SetScrollOffset(end_offset, kProgrammaticScroll, |
| kScrollBehaviorAuto); |
| } else { |
| FloatPoint end_point(new_left * box->Style()->EffectiveZoom(), |
| scrollable_area->ScrollPosition().Y()); |
| std::unique_ptr<cc::SnapSelectionStrategy> strategy = |
| cc::SnapSelectionStrategy::CreateForEndPosition( |
| gfx::ScrollOffset(end_point), true, false); |
| end_point = |
| scrollable_area->GetSnapPositionAndSetTarget(*strategy).value_or( |
| end_point); |
| |
| FloatPoint new_position(end_point.X(), |
| scrollable_area->ScrollPosition().Y()); |
| scrollable_area->ScrollToAbsolutePosition(new_position, |
| kScrollBehaviorAuto); |
| } |
| } |
| } |
| |
| void Element::setScrollTop(double new_top) { |
| if (!InActiveDocument()) |
| return; |
| |
| GetDocument().UpdateStyleAndLayoutForNode(this); |
| |
| new_top = ScrollableArea::NormalizeNonFiniteScroll(new_top); |
| |
| if (GetDocument().ScrollingElementNoLayout() == this) { |
| if (LocalDOMWindow* window = GetDocument().domWindow()) { |
| ScrollToOptions* options = ScrollToOptions::Create(); |
| options->setTop(new_top); |
| window->scrollTo(options); |
| } |
| } else if (PaintLayerScrollableArea* scrollable_area = GetScrollableArea()) { |
| LayoutBox* box = GetLayoutBox(); |
| DCHECK(box); |
| |
| if (HasUpwardDirection(*this)) { |
| UseCounter::Count( |
| GetDocument(), |
| WebFeature:: |
| kElementWithLeftwardOrUpwardOverflowDirection_ScrollLeftOrTop); |
| if (new_top > 0) { |
| UseCounter::Count( |
| GetDocument(), |
| WebFeature:: |
| kElementWithLeftwardOrUpwardOverflowDirection_ScrollLeftOrTopSetPositive); |
| } |
| } |
| |
| if (RuntimeEnabledFeatures::CSSOMViewScrollCoordinatesEnabled()) { |
| ScrollOffset end_offset(scrollable_area->GetScrollOffset().Width(), |
| new_top * box->Style()->EffectiveZoom()); |
| std::unique_ptr<cc::SnapSelectionStrategy> strategy = |
| cc::SnapSelectionStrategy::CreateForEndPosition( |
| gfx::ScrollOffset( |
| scrollable_area->ScrollOffsetToPosition(end_offset)), |
| false, true); |
| base::Optional<FloatPoint> snap_point = |
| scrollable_area->GetSnapPositionAndSetTarget(*strategy); |
| if (snap_point.has_value()) { |
| end_offset = |
| scrollable_area->ScrollPositionToOffset(snap_point.value()); |
| } |
| |
| scrollable_area->SetScrollOffset(end_offset, kProgrammaticScroll, |
| kScrollBehaviorAuto); |
| } else { |
| FloatPoint end_point(scrollable_area->ScrollPosition().X(), |
| new_top * box->Style()->EffectiveZoom()); |
| std::unique_ptr<cc::SnapSelectionStrategy> strategy = |
| cc::SnapSelectionStrategy::CreateForEndPosition( |
| gfx::ScrollOffset(end_point), false, true); |
| end_point = |
| scrollable_area->GetSnapPositionAndSetTarget(*strategy).value_or( |
| end_point); |
| FloatPoint new_position(scrollable_area->ScrollPosition().X(), |
| end_point.Y()); |
| scrollable_area->ScrollToAbsolutePosition(new_position, |
| kScrollBehaviorAuto); |
| } |
| } |
| } |
| |
| int Element::scrollWidth() { |
| if (!InActiveDocument()) |
| return 0; |
| |
| GetDocument().UpdateStyleAndLayoutForNode(this); |
| |
| if (GetDocument().ScrollingElementNoLayout() == this) { |
| if (GetDocument().View()) { |
| return AdjustForAbsoluteZoom::AdjustInt( |
| GetDocument().View()->LayoutViewport()->ContentsSize().Width(), |
| GetDocument().GetFrame()->PageZoomFactor()); |
| } |
| return 0; |
| } |
| |
| if (LayoutBox* box = GetLayoutBox()) { |
| return AdjustForAbsoluteZoom::AdjustInt(box->PixelSnappedScrollWidth(), |
| box); |
| } |
| return 0; |
| } |
| |
| int Element::scrollHeight() { |
| if (!InActiveDocument()) |
| return 0; |
| |
| GetDocument().UpdateStyleAndLayoutForNode(this); |
| |
| if (GetDocument().ScrollingElementNoLayout() == this) { |
| if (GetDocument().View()) { |
| return AdjustForAbsoluteZoom::AdjustInt( |
| GetDocument().View()->LayoutViewport()->ContentsSize().Height(), |
| GetDocument().GetFrame()->PageZoomFactor()); |
| } |
| return 0; |
| } |
| |
| if (LayoutBox* box = GetLayoutBox()) { |
| return AdjustForAbsoluteZoom::AdjustInt(box->PixelSnappedScrollHeight(), |
| box); |
| } |
| return 0; |
| } |
| |
| void Element::scrollBy(double x, double y) { |
| ScrollToOptions* scroll_to_options = ScrollToOptions::Create(); |
| scroll_to_options->setLeft(x); |
| scroll_to_options->setTop(y); |
| scrollBy(scroll_to_options); |
| } |
| |
| void Element::scrollBy(const ScrollToOptions* scroll_to_options) { |
| if (!InActiveDocument()) |
| return; |
| |
| // FIXME: This should be removed once scroll updates are processed only after |
| // the compositing update. See http://crbug.com/420741. |
| GetDocument().UpdateStyleAndLayoutForNode(this); |
| |
| if (GetDocument().ScrollingElementNoLayout() == this) { |
| ScrollFrameBy(scroll_to_options); |
| } else { |
| ScrollLayoutBoxBy(scroll_to_options); |
| } |
| } |
| |
| void Element::scrollTo(double x, double y) { |
| ScrollToOptions* scroll_to_options = ScrollToOptions::Create(); |
| scroll_to_options->setLeft(x); |
| scroll_to_options->setTop(y); |
| scrollTo(scroll_to_options); |
| } |
| |
| void Element::scrollTo(const ScrollToOptions* scroll_to_options) { |
| if (!InActiveDocument()) |
| return; |
| |
| // FIXME: This should be removed once scroll updates are processed only after |
| // the compositing update. See http://crbug.com/420741. |
| GetDocument().UpdateStyleAndLayoutForNode(this); |
| |
| if (GetDocument().ScrollingElementNoLayout() == this) { |
| ScrollFrameTo(scroll_to_options); |
| } else { |
| ScrollLayoutBoxTo(scroll_to_options); |
| } |
| } |
| |
| void Element::ScrollLayoutBoxBy(const ScrollToOptions* scroll_to_options) { |
| gfx::ScrollOffset displacement; |
| if (scroll_to_options->hasLeft()) { |
| displacement.set_x( |
| ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options->left())); |
| } |
| if (scroll_to_options->hasTop()) { |
| displacement.set_y( |
| ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options->top())); |
| } |
| |
| ScrollBehavior scroll_behavior = kScrollBehaviorAuto; |
| ScrollableArea::ScrollBehaviorFromString(scroll_to_options->behavior(), |
| scroll_behavior); |
| if (PaintLayerScrollableArea* scrollable_area = GetScrollableArea()) { |
| LayoutBox* box = GetLayoutBox(); |
| DCHECK(box); |
| gfx::ScrollOffset current_position(scrollable_area->ScrollPosition().X(), |
| scrollable_area->ScrollPosition().Y()); |
| displacement.Scale(box->Style()->EffectiveZoom()); |
| gfx::ScrollOffset new_offset(current_position + displacement); |
| FloatPoint new_position(new_offset.x(), new_offset.y()); |
| |
| std::unique_ptr<cc::SnapSelectionStrategy> strategy = |
| cc::SnapSelectionStrategy::CreateForEndAndDirection(current_position, |
| displacement); |
| new_position = |
| scrollable_area->GetSnapPositionAndSetTarget(*strategy).value_or( |
| new_position); |
| scrollable_area->ScrollToAbsolutePosition(new_position, scroll_behavior); |
| } |
| } |
| |
| void Element::ScrollLayoutBoxTo(const ScrollToOptions* scroll_to_options) { |
| ScrollBehavior scroll_behavior = kScrollBehaviorAuto; |
| ScrollableArea::ScrollBehaviorFromString(scroll_to_options->behavior(), |
| scroll_behavior); |
| |
| if (PaintLayerScrollableArea* scrollable_area = GetScrollableArea()) { |
| LayoutBox* box = GetLayoutBox(); |
| DCHECK(box); |
| if (scroll_to_options->hasLeft() && HasLeftwardDirection(*this)) { |
| UseCounter::Count( |
| GetDocument(), |
| WebFeature:: |
| kElementWithLeftwardOrUpwardOverflowDirection_ScrollLeftOrTop); |
| if (scroll_to_options->left() > 0) { |
| UseCounter::Count( |
| GetDocument(), |
| WebFeature:: |
| kElementWithLeftwardOrUpwardOverflowDirection_ScrollLeftOrTopSetPositive); |
| } |
| } |
| if (scroll_to_options->hasTop() && HasUpwardDirection(*this)) { |
| UseCounter::Count( |
| GetDocument(), |
| WebFeature:: |
| kElementWithLeftwardOrUpwardOverflowDirection_ScrollLeftOrTop); |
| if (scroll_to_options->top() > 0) { |
| UseCounter::Count( |
| GetDocument(), |
| WebFeature:: |
| kElementWithLeftwardOrUpwardOverflowDirection_ScrollLeftOrTopSetPositive); |
| } |
| } |
| |
| // In order to keep the behavior of element scroll consistent with document |
| // scroll, and consistent with the behavior of other vendors, the |
| // offsets in |scroll_to_options| are treated as the offset from |
| // |ScrollOrigin()|. |
| if (RuntimeEnabledFeatures::CSSOMViewScrollCoordinatesEnabled()) { |
| ScrollOffset new_offset = scrollable_area->GetScrollOffset(); |
| if (scroll_to_options->hasLeft()) { |
| new_offset.SetWidth(ScrollableArea::NormalizeNonFiniteScroll( |
| scroll_to_options->left()) * |
| box->Style()->EffectiveZoom()); |
| } |
| if (scroll_to_options->hasTop()) { |
| new_offset.SetHeight( |
| ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options->top()) * |
| box->Style()->EffectiveZoom()); |
| } |
| |
| std::unique_ptr<cc::SnapSelectionStrategy> strategy = |
| cc::SnapSelectionStrategy::CreateForEndPosition( |
| gfx::ScrollOffset( |
| scrollable_area->ScrollOffsetToPosition(new_offset)), |
| scroll_to_options->hasLeft(), scroll_to_options->hasTop()); |
| base::Optional<FloatPoint> snap_point = |
| scrollable_area->GetSnapPositionAndSetTarget(*strategy); |
| if (snap_point.has_value()) { |
| new_offset = |
| scrollable_area->ScrollPositionToOffset(snap_point.value()); |
| } |
| |
| scrollable_area->SetScrollOffset(new_offset, kProgrammaticScroll, |
| scroll_behavior); |
| } else { |
| FloatPoint new_position(scrollable_area->ScrollPosition().X(), |
| scrollable_area->ScrollPosition().Y()); |
| if (scroll_to_options->hasLeft()) { |
| new_position.SetX(ScrollableArea::NormalizeNonFiniteScroll( |
| scroll_to_options->left()) * |
| box->Style()->EffectiveZoom()); |
| } |
| if (scroll_to_options->hasTop()) { |
| new_position.SetY( |
| ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options->top()) * |
| box->Style()->EffectiveZoom()); |
| } |
| |
| std::unique_ptr<cc::SnapSelectionStrategy> strategy = |
| cc::SnapSelectionStrategy::CreateForEndPosition( |
| gfx::ScrollOffset(new_position), scroll_to_options->hasLeft(), |
| scroll_to_options->hasTop()); |
| new_position = |
| scrollable_area->GetSnapPositionAndSetTarget(*strategy).value_or( |
| new_position); |
| scrollable_area->ScrollToAbsolutePosition(new_position, scroll_behavior); |
| } |
| } |
| } |
| |
| void Element::ScrollFrameBy(const ScrollToOptions* scroll_to_options) { |
| gfx::ScrollOffset displacement; |
| if (scroll_to_options->hasLeft()) { |
| displacement.set_x( |
| ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options->left())); |
| } |
| if (scroll_to_options->hasTop()) { |
| displacement.set_y( |
| ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options->top())); |
| } |
| |
| ScrollBehavior scroll_behavior = kScrollBehaviorAuto; |
| ScrollableArea::ScrollBehaviorFromString(scroll_to_options->behavior(), |
| scroll_behavior); |
| LocalFrame* frame = GetDocument().GetFrame(); |
| if (!frame || !frame->View() || !GetDocument().GetPage()) |
| return; |
| |
| ScrollableArea* viewport = frame->View()->LayoutViewport(); |
| if (!viewport) |
| return; |
| |
| displacement.Scale(frame->PageZoomFactor()); |
| FloatPoint new_position = viewport->ScrollPosition() + |
| FloatPoint(displacement.x(), displacement.y()); |
| |
| gfx::ScrollOffset current_position(viewport->ScrollPosition()); |
| std::unique_ptr<cc::SnapSelectionStrategy> strategy = |
| cc::SnapSelectionStrategy::CreateForEndAndDirection(current_position, |
| displacement); |
| new_position = |
| viewport->GetSnapPositionAndSetTarget(*strategy).value_or(new_position); |
| viewport->SetScrollOffset(viewport->ScrollPositionToOffset(new_position), |
| kProgrammaticScroll, scroll_behavior); |
| } |
| |
| void Element::ScrollFrameTo(const ScrollToOptions* scroll_to_options) { |
| ScrollBehavior scroll_behavior = kScrollBehaviorAuto; |
| ScrollableArea::ScrollBehaviorFromString(scroll_to_options->behavior(), |
| scroll_behavior); |
| LocalFrame* frame = GetDocument().GetFrame(); |
| if (!frame || !frame->View() || !GetDocument().GetPage()) |
| return; |
| |
| ScrollableArea* viewport = frame->View()->LayoutViewport(); |
| if (!viewport) |
| return; |
| |
| ScrollOffset new_offset = viewport->GetScrollOffset(); |
| if (scroll_to_options->hasLeft()) { |
| new_offset.SetWidth( |
| ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options->left()) * |
| frame->PageZoomFactor()); |
| } |
| if (scroll_to_options->hasTop()) { |
| new_offset.SetHeight( |
| ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options->top()) * |
| frame->PageZoomFactor()); |
| } |
| |
| FloatPoint new_position = viewport->ScrollOffsetToPosition(new_offset); |
| std::unique_ptr<cc::SnapSelectionStrategy> strategy = |
| cc::SnapSelectionStrategy::CreateForEndPosition( |
| gfx::ScrollOffset(new_position), scroll_to_options->hasLeft(), |
| scroll_to_options->hasTop()); |
| new_position = |
| viewport->GetSnapPositionAndSetTarget(*strategy).value_or(new_position); |
| new_offset = viewport->ScrollPositionToOffset(new_position); |
| viewport->SetScrollOffset(new_offset, kProgrammaticScroll, scroll_behavior); |
| } |
| |
| IntRect Element::BoundsInViewport() const { |
| GetDocument().EnsurePaintLocationDataValidForNode(this); |
| |
| LocalFrameView* view = GetDocument().View(); |
| if (!view) |
| return IntRect(); |
| |
| Vector<FloatQuad> quads; |
| |
| // TODO(pdr): Unify the quad/bounds code with Element::ClientQuads. |
| |
| // Foreign objects need to convert between SVG and HTML coordinate spaces and |
| // cannot use LocalToAbsoluteQuad directly with ObjectBoundingBox which is |
| // SVG coordinates and not HTML coordinates. Instead, use the AbsoluteQuads |
| // codepath below. |
| auto* svg_element = DynamicTo<SVGElement>(this); |
| if (svg_element && GetLayoutObject() && |
| !GetLayoutObject()->IsSVGForeignObject()) { |
| // Get the bounding rectangle from the SVG model. |
| // TODO(pdr): This should include stroke. |
| if (svg_element->IsSVGGraphicsElement()) { |
| quads.push_back(GetLayoutObject()->LocalToAbsoluteQuad( |
| GetLayoutObject()->ObjectBoundingBox())); |
| } |
| } else { |
| // Get the bounding rectangle from the box model. |
| if (GetLayoutBoxModelObject()) |
| GetLayoutBoxModelObject()->AbsoluteQuads(quads); |
| } |
| |
| if (quads.IsEmpty()) |
| return IntRect(); |
| |
| IntRect result = quads[0].EnclosingBoundingBox(); |
| for (wtf_size_t i = 1; i < quads.size(); ++i) |
| result.Unite(quads[i].EnclosingBoundingBox()); |
| |
| return view->FrameToViewport(result); |
| } |
| |
| IntRect Element::VisibleBoundsInVisualViewport() const { |
| if (!GetLayoutObject() || !GetDocument().GetPage() || |
| !GetDocument().GetFrame()) |
| return IntRect(); |
| |
| // We don't use absoluteBoundingBoxRect() because it can return an IntRect |
| // larger the actual size by 1px. crbug.com/470503 |
| PhysicalRect rect( |
| RoundedIntRect(GetLayoutObject()->AbsoluteBoundingBoxFloatRect())); |
| PhysicalRect frame_clip_rect = |
| GetDocument().View()->GetLayoutView()->ClippingRect(PhysicalOffset()); |
| rect.Intersect(frame_clip_rect); |
| |
| // MapToVisualRectInAncestorSpace, called with a null ancestor argument, |
| // returns the viewport-visible rect in the root frame's coordinate space. |
| // MapToVisualRectInAncestorSpace applies ancestors' frame's clipping but does |
| // not apply (overflow) element clipping. |
| GetDocument().View()->GetLayoutView()->MapToVisualRectInAncestorSpace( |
| nullptr, rect, kTraverseDocumentBoundaries, kDefaultVisualRectFlags); |
| |
| // TODO(layout-dev): Callers of this method don't expect the offset of the |
| // local frame root from a remote top-level frame to be applied here. They |
| // expect the result to be in the coordinate system of the local root frame. |
| // Either the method should be renamed to something which communicates that, |
| // or callers should be updated to expect actual top-level frame coordinates. |
| rect.Move(-PhysicalOffset( |
| GetDocument().GetFrame()->LocalFrameRoot().RemoteViewportOffset())); |
| |
| IntRect visible_rect = PixelSnappedIntRect(rect); |
| // If the rect is in the coordinates of the main frame, then it should |
| // also be clipped to the viewport to account for page scale. For OOPIFs, |
| // local frame root -> viewport coordinate conversion is done in the |
| // browser process. |
| if (GetDocument().GetFrame()->LocalFrameRoot().IsMainFrame()) { |
| IntSize viewport_size = GetDocument().GetPage()->GetVisualViewport().Size(); |
| visible_rect = |
| GetDocument().GetPage()->GetVisualViewport().RootFrameToViewport( |
| visible_rect); |
| visible_rect.Intersect(IntRect(IntPoint(), viewport_size)); |
| } |
| return visible_rect; |
| } |
| |
| void Element::ClientQuads(Vector<FloatQuad>& quads) { |
| GetDocument().EnsurePaintLocationDataValidForNode(this); |
| |
| LayoutObject* element_layout_object = GetLayoutObject(); |
| if (!element_layout_object) |
| return; |
| |
| // Foreign objects need to convert between SVG and HTML coordinate spaces and |
| // cannot use LocalToAbsoluteQuad directly with ObjectBoundingBox which is |
| // SVG coordinates and not HTML coordinates. Instead, use the AbsoluteQuads |
| // codepath below. |
| auto* svg_element = DynamicTo<SVGElement>(this); |
| if (svg_element && !element_layout_object->IsSVGRoot() && |
| !element_layout_object->IsSVGForeignObject()) { |
| // Get the bounding rectangle from the SVG model. |
| // TODO(pdr): ObjectBoundingBox does not include stroke and the spec is not |
| // clear (see: https://github.com/w3c/svgwg/issues/339, crbug.com/529734). |
| // If stroke is desired, we can update this to use AbsoluteQuads, below. |
| if (svg_element->IsSVGGraphicsElement()) { |
| quads.push_back(element_layout_object->LocalToAbsoluteQuad( |
| element_layout_object->ObjectBoundingBox())); |
| } |
| return; |
| } |
| |
| // FIXME: Handle table/inline-table with a caption. |
| if (element_layout_object->IsBoxModelObject() || |
| element_layout_object->IsBR()) |
| element_layout_object->AbsoluteQuads(quads); |
| } |
| |
| DOMRectList* Element::getClientRects() { |
| Vector<FloatQuad> quads; |
| ClientQuads(quads); |
| if (quads.IsEmpty()) |
| return DOMRectList::Create(); |
| |
| LayoutObject* element_layout_object = GetLayoutObject(); |
| DCHECK(element_layout_object); |
| GetDocument().AdjustFloatQuadsForScrollAndAbsoluteZoom( |
| quads, *element_layout_object); |
| return DOMRectList::Create(quads); |
| } |
| |
| DOMRect* Element::getBoundingClientRect() { |
| Vector<FloatQuad> quads; |
| ClientQuads(quads); |
| if (quads.IsEmpty()) |
| return DOMRect::Create(); |
| |
| FloatRect result = quads[0].BoundingBox(); |
| for (wtf_size_t i = 1; i < quads.size(); ++i) |
| result.Unite(quads[i].BoundingBox()); |
| |
| LayoutObject* element_layout_object = GetLayoutObject(); |
| DCHECK(element_layout_object); |
| GetDocument().AdjustFloatRectForScrollAndAbsoluteZoom(result, |
| *element_layout_object); |
| return DOMRect::FromFloatRect(result); |
| } |
| |
| const AtomicString& Element::computedRole() { |
| Document& document = GetDocument(); |
| if (!document.IsActive()) |
| return g_null_atom; |
| document.UpdateStyleAndLayoutForNode(this); |
| UpdateDistributionForFlatTreeTraversal(); |
| AXContext ax_context(document); |
| return ax_context.GetAXObjectCache().ComputedRoleForNode(this); |
| } |
| |
| String Element::computedName() { |
| Document& document = GetDocument(); |
| if (!document.IsActive()) |
| return String(); |
| document.UpdateStyleAndLayoutForNode(this); |
| UpdateDistributionForFlatTreeTraversal(); |
| AXContext ax_context(document); |
| return ax_context.GetAXObjectCache().ComputedNameForNode(this); |
| } |
| |
| AccessibleNode* Element::ExistingAccessibleNode() const { |
| if (!RuntimeEnabledFeatures::AccessibilityObjectModelEnabled()) |
| return nullptr; |
| |
| if (!HasRareData()) |
| return nullptr; |
| |
| return GetElementRareData()->GetAccessibleNode(); |
| } |
| |
| AccessibleNode* Element::accessibleNode() { |
| if (!RuntimeEnabledFeatures::AccessibilityObjectModelEnabled()) |
| return nullptr; |
| |
| ElementRareData& rare_data = EnsureElementRareData(); |
| return rare_data.EnsureAccessibleNode(this); |
| } |
| |
| InvisibleState Element::Invisible() const { |
| const AtomicString& value = FastGetAttribute(html_names::kInvisibleAttr); |
| if (value.IsNull()) |
| return InvisibleState::kMissing; |
| if (EqualIgnoringASCIICase(value, "static")) |
| return InvisibleState::kStatic; |
| return InvisibleState::kInvisible; |
| } |
| |
| bool Element::HasInvisibleAttribute() const { |
| return Invisible() != InvisibleState::kMissing; |
| } |
| |
| void Element::DispatchActivateInvisibleEventIfNeeded() { |
| if (!RuntimeEnabledFeatures::InvisibleDOMEnabled()) |
| return; |
| // Traverse all inclusive flat-tree ancestor and send activateinvisible |
| // on the ones that have the invisible attribute. Default event handler |
| // will remove invisible attribute of all invisible element if the event is |
| // not canceled, making this element and all ancestors visible again. |
| // We're saving them and the retargeted activated element as DOM structure |
| // may change due to event handlers. |
| HeapVector<Member<Element>> invisible_ancestors; |
| HeapVector<Member<Element>> activated_elements; |
| for (Node& ancestor : FlatTreeTraversal::InclusiveAncestorsOf(*this)) { |
| auto* ancestor_element = DynamicTo<Element>(ancestor); |
| if (ancestor_element && |
| ancestor_element->Invisible() != InvisibleState::kMissing) { |
| invisible_ancestors.push_back(ancestor_element); |
| activated_elements.push_back(ancestor.GetTreeScope().Retarget(*this)); |
| } |
| } |
| auto* activated_element_iterator = activated_elements.begin(); |
| for (Element* ancestor : invisible_ancestors) { |
| DCHECK(activated_element_iterator != activated_elements.end()); |
| ancestor->DispatchEvent(*MakeGarbageCollected<ActivateInvisibleEvent>( |
| *activated_element_iterator)); |
| ++activated_element_iterator; |
| } |
| } |
| |
| bool Element::IsInsideInvisibleStaticSubtree() const { |
| if (!RuntimeEnabledFeatures::InvisibleDOMEnabled()) |
| return false; |
| for (Node& ancestor : FlatTreeTraversal::InclusiveAncestorsOf(*this)) { |
| auto* ancestor_element = DynamicTo<Element>(ancestor); |
| if (ancestor_element && |
| ancestor_element->Invisible() == InvisibleState::kStatic) |
| return true; |
| } |
| return false; |
| } |
| |
| bool Element::IsInsideInvisibleSubtree() const { |
| if (!RuntimeEnabledFeatures::InvisibleDOMEnabled() || |
| !CanParticipateInFlatTree()) |
| return false; |
| for (Node& ancestor : FlatTreeTraversal::InclusiveAncestorsOf(*this)) { |
| auto* ancestor_element = DynamicTo<Element>(ancestor); |
| if (ancestor_element && |
| ancestor_element->Invisible() != InvisibleState::kMissing) |
| return true; |
| } |
| return false; |
| } |
| |
| void Element::InvisibleAttributeChanged(const AtomicString& old_value, |
| const AtomicString& new_value) { |
| if (old_value.IsNull() != new_value.IsNull()) { |
| SetNeedsStyleRecalc(kLocalStyleChange, |
| StyleChangeReasonForTracing::Create( |
| style_change_reason::kInvisibleChange)); |
| } |
| if (EqualIgnoringASCIICase(old_value, "static") && |
| !IsInsideInvisibleStaticSubtree()) { |
| // This element and its descendants are not in an invisible="static" tree |
| // anymore. |
| CustomElement::Registry(*this)->upgrade(this); |
| } |
| } |
| |
| void Element::DefaultEventHandler(Event& event) { |
| if (RuntimeEnabledFeatures::InvisibleDOMEnabled() && |
| event.type() == event_type_names::kActivateinvisible && |
| event.target() == this) { |
| removeAttribute(html_names::kInvisibleAttr); |
| event.SetDefaultHandled(); |
| return; |
| } |
| ContainerNode::DefaultEventHandler(event); |
| } |
| |
| bool Element::toggleAttribute(const AtomicString& qualified_name, |
| ExceptionState& exception_state) { |
| // https://dom.spec.whatwg.org/#dom-element-toggleattribute |
| // 1. If qualifiedName does not match the Name production in XML, then throw |
| // an "InvalidCharacterError" DOMException. |
| if (!Document::IsValidName(qualified_name)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidCharacterError, |
| "'" + qualified_name + "' is not a valid attribute name."); |
| return false; |
| } |
| // 2. If the context object is in the HTML namespace and its node document is |
| // an HTML document, then set qualifiedName to qualifiedName in ASCII |
| // lowercase. |
| AtomicString lower_case_name = LowercaseIfNecessary(qualified_name); |
| // 3. Let attribute be the first attribute in the context object’s attribute |
| // list whose qualified name is qualifiedName, and null otherwise. |
| // 4. If attribute is null, then |
| if (!getAttribute(lower_case_name)) { |
| // 4. 1. If force is not given or is true, create an attribute whose local |
| // name is qualifiedName, value is the empty string, and node document is |
| // the context object’s node document, then append this attribute to the |
| // context object, and then return true. |
| setAttribute(lower_case_name, g_empty_atom); |
| return true; |
| } |
| // 5. Otherwise, if force is not given or is false, remove an attribute given |
| // qualifiedName and the context object, and then return false. |
| removeAttribute(lower_case_name); |
| return false; |
| } |
| |
| bool Element::toggleAttribute(const AtomicString& qualified_name, |
| bool force, |
| ExceptionState& exception_state) { |
| // https://dom.spec.whatwg.org/#dom-element-toggleattribute |
| // 1. If qualifiedName does not match the Name production in XML, then throw |
| // an "InvalidCharacterError" DOMException. |
| if (!Document::IsValidName(qualified_name)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidCharacterError, |
| "'" + qualified_name + "' is not a valid attribute name."); |
| return false; |
| } |
| // 2. If the context object is in the HTML namespace and its node document is |
| // an HTML document, then set qualifiedName to qualifiedName in ASCII |
| // lowercase. |
| AtomicString lower_case_name = LowercaseIfNecessary(qualified_name); |
| // 3. Let attribute be the first attribute in the context object’s attribute |
| // list whose qualified name is qualifiedName, and null otherwise. |
| // 4. If attribute is null, then |
| if (!getAttribute(lower_case_name)) { |
| // 4. 1. If force is not given or is true, create an attribute whose local |
| // name is qualifiedName, value is the empty string, and node document is |
| // the context object’s node document, then append this attribute to the |
| // context object, and then return true. |
| if (force) { |
| setAttribute(lower_case_name, g_empty_atom); |
| return true; |
| } |
| // 4. 2. Return false. |
| return false; |
| } |
| // 5. Otherwise, if force is not given or is false, remove an attribute given |
| // qualifiedName and the context object, and then return false. |
| if (!force) { |
| removeAttribute(lower_case_name); |
| return false; |
| } |
| // 6. Return true. |
| return true; |
| } |
| |
| const AtomicString& Element::getAttribute( |
| const AtomicString& local_name) const { |
| if (!GetElementData()) |
| return g_null_atom; |
| SynchronizeAttribute(local_name); |
| if (const Attribute* attribute = |
| GetElementData()->Attributes().Find(LowercaseIfNecessary(local_name))) |
| return attribute->Value(); |
| return g_null_atom; |
| } |
| |
| const AtomicString& Element::getAttributeNS( |
| const AtomicString& namespace_uri, |
| const AtomicString& local_name) const { |
| return getAttribute(QualifiedName(g_null_atom, local_name, namespace_uri)); |
| } |
| |
| std::pair<wtf_size_t, const QualifiedName> |
| Element::LookupAttributeQNameInternal(const AtomicString& local_name) const { |
| AtomicString case_adjusted_local_name = LowercaseIfNecessary(local_name); |
| if (!GetElementData()) { |
| return std::make_pair( |
| kNotFound, |
| QualifiedName(g_null_atom, case_adjusted_local_name, g_null_atom)); |
| } |
| |
| AttributeCollection attributes = GetElementData()->Attributes(); |
| wtf_size_t index = attributes.FindIndex(case_adjusted_local_name); |
| return std::make_pair( |
| index, |
| index != kNotFound |
| ? attributes[index].GetName() |
| : QualifiedName(g_null_atom, case_adjusted_local_name, g_null_atom)); |
| } |
| |
| void Element::setAttribute(const AtomicString& local_name, |
| const AtomicString& value, |
| ExceptionState& exception_state) { |
| if (!Document::IsValidName(local_name)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidCharacterError, |
| "'" + local_name + "' is not a valid attribute name."); |
| return; |
| } |
| |
| SynchronizeAttribute(local_name); |
| wtf_size_t index; |
| QualifiedName q_name = QualifiedName::Null(); |
| std::tie(index, q_name) = LookupAttributeQNameInternal(local_name); |
| |
| String trusted_value = GetStringFromSpecificTrustedType( |
| value, ExpectedTrustedTypeForAttribute(q_name), &GetDocument(), |
| exception_state); |
| if (exception_state.HadException()) |
| return; |
| |
| SetAttributeInternal(index, q_name, AtomicString(trusted_value), |
| kNotInSynchronizationOfLazyAttribute); |
| } |
| |
| void Element::setAttribute(const AtomicString& name, |
| const AtomicString& value) { |
| setAttribute(name, value, ASSERT_NO_EXCEPTION); |
| } |
| |
| void Element::setAttribute(const QualifiedName& name, |
| const AtomicString& value) { |
| SynchronizeAttribute(name); |
| wtf_size_t index = GetElementData() |
| ? GetElementData()->Attributes().FindIndex(name) |
| : kNotFound; |
| SetAttributeInternal(index, name, value, |
| kNotInSynchronizationOfLazyAttribute); |
| } |
| |
| void Element::setAttribute(const QualifiedName& name, |
| const AtomicString& value, |
| ExceptionState& exception_state) { |
| SynchronizeAttribute(name); |
| wtf_size_t index = GetElementData() |
| ? GetElementData()->Attributes().FindIndex(name) |
| : kNotFound; |
| |
| String trusted_value = GetStringFromSpecificTrustedType( |
| value, ExpectedTrustedTypeForAttribute(name), &GetDocument(), |
| exception_state); |
| if (exception_state.HadException()) |
| return; |
| |
| SetAttributeInternal(index, name, AtomicString(trusted_value), |
| kNotInSynchronizationOfLazyAttribute); |
| } |
| |
| void Element::SetSynchronizedLazyAttribute(const QualifiedName& name, |
| const AtomicString& value) { |
| wtf_size_t index = GetElementData() |
| ? GetElementData()->Attributes().FindIndex(name) |
| : kNotFound; |
| SetAttributeInternal(index, name, value, kInSynchronizationOfLazyAttribute); |
| } |
| |
| void Element::setAttribute( |
| const AtomicString& local_name, |
| const StringOrTrustedHTMLOrTrustedScriptOrTrustedScriptURL& string_or_TT, |
| ExceptionState& exception_state) { |
| if (!Document::IsValidName(local_name)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidCharacterError, |
| "'" + local_name + "' is not a valid attribute name."); |
| return; |
| } |
| |
| SynchronizeAttribute(local_name); |
| wtf_size_t index; |
| QualifiedName q_name = QualifiedName::Null(); |
| std::tie(index, q_name) = LookupAttributeQNameInternal(local_name); |
| String value = GetStringFromSpecificTrustedType( |
| string_or_TT, ExpectedTrustedTypeForAttribute(q_name), &GetDocument(), |
| exception_state); |
| if (exception_state.HadException()) |
| return; |
| SetAttributeInternal(index, q_name, AtomicString(value), |
| kNotInSynchronizationOfLazyAttribute); |
| } |
| |
| const AttrNameToTrustedType& Element::GetCheckedAttributeTypes() const { |
| DEFINE_STATIC_LOCAL(AttrNameToTrustedType, attribute_map, ({})); |
| return attribute_map; |
| } |
| |
| SpecificTrustedType Element::ExpectedTrustedTypeForAttribute( |
| const QualifiedName& q_name) const { |
| // There are only a handful of namespaced attributes we care about |
| // (xlink:href), and all of those have identical Trusted Types |
| // properties to their namespace-less counterpart. So we check whether this |
| // is one of SVG's 'known' attributes, and if so just check the local |
| // name part as usual. |
| if (!q_name.NamespaceURI().IsNull() && |
| !SVGAnimatedHref::IsKnownAttribute(q_name)) { |
| return SpecificTrustedType::kNone; |
| } |
| |
| const AttrNameToTrustedType* attribute_types = &GetCheckedAttributeTypes(); |
| AttrNameToTrustedType::const_iterator iter = |
| attribute_types->find(q_name.LocalName()); |
| if (iter != attribute_types->end()) |
| return iter->value; |
| |
| if (q_name.LocalName().StartsWith("on")) { |
| // TODO(jakubvrana): This requires TrustedScript in all attributes |
| // starting with "on", including e.g. "one". We use this pattern elsewhere |
| // (e.g. in IsEventHandlerAttribute) but it's not ideal. Consider using |
| // the event attribute of the resulting AttributeTriggers. |
| return SpecificTrustedType::kTrustedScript; |
| } |
| |
| return SpecificTrustedType::kNone; |
| } |
| |
| void Element::setAttribute(const QualifiedName& name, |
| const StringOrTrustedHTML& stringOrHTML, |
| ExceptionState& exception_state) { |
| String valueString = |
| GetStringFromTrustedHTML(stringOrHTML, &GetDocument(), exception_state); |
| if (!exception_state.HadException()) { |
| setAttribute(name, AtomicString(valueString)); |
| } |
| } |
| |
| void Element::setAttribute(const QualifiedName& name, |
| const StringOrTrustedScript& stringOrScript, |
| ExceptionState& exception_state) { |
| String valueString = GetStringFromTrustedScript( |
| stringOrScript, &GetDocument(), exception_state); |
| if (!exception_state.HadException()) { |
| setAttribute(name, AtomicString(valueString)); |
| } |
| } |
| |
| void Element::setAttribute(const QualifiedName& name, |
| const StringOrTrustedScriptURL& stringOrURL, |
| ExceptionState& exception_state) { |
| String valueString = GetStringFromTrustedScriptURL( |
| stringOrURL, &GetDocument(), exception_state); |
| if (!exception_state.HadException()) { |
| setAttribute(name, AtomicString(valueString)); |
| } |
| } |
| |
| ALWAYS_INLINE void Element::SetAttributeInternal( |
| wtf_size_t index, |
| const QualifiedName& name, |
| const AtomicString& new_value, |
| SynchronizationOfLazyAttribute in_synchronization_of_lazy_attribute) { |
| if (new_value.IsNull()) { |
| if (index != kNotFound) |
| RemoveAttributeInternal(index, in_synchronization_of_lazy_attribute); |
| return; |
| } |
| |
| if (index == kNotFound) { |
| AppendAttributeInternal(name, new_value, |
| in_synchronization_of_lazy_attribute); |
| return; |
| } |
| |
| const Attribute& existing_attribute = |
| GetElementData()->Attributes().at(index); |
| AtomicString existing_attribute_value = existing_attribute.Value(); |
| QualifiedName existing_attribute_name = existing_attribute.GetName(); |
| |
| if (!in_synchronization_of_lazy_attribute) |
| WillModifyAttribute(existing_attribute_name, existing_attribute_value, |
| new_value); |
| if (new_value != existing_attribute_value) |
| EnsureUniqueElementData().Attributes().at(index).SetValue(new_value); |
| if (!in_synchronization_of_lazy_attribute) |
| DidModifyAttribute(existing_attribute_name, existing_attribute_value, |
| new_value); |
| } |
| |
| static inline AtomicString MakeIdForStyleResolution(const AtomicString& value, |
| bool in_quirks_mode) { |
| if (in_quirks_mode) |
| return value.LowerASCII(); |
| return value; |
| } |
| |
| DISABLE_CFI_PERF |
| void Element::AttributeChanged(const AttributeModificationParams& params) { |
| const QualifiedName& name = params.name; |
| if (ShadowRoot* parent_shadow_root = |
| ShadowRootWhereNodeCanBeDistributedForV0(*this)) { |
| if (ShouldInvalidateDistributionWhenAttributeChanged( |
| *parent_shadow_root, name, params.new_value)) |
| parent_shadow_root->SetNeedsDistributionRecalc(); |
| } |
| if (name == html_names::kSlotAttr && params.old_value != params.new_value) { |
| if (ShadowRoot* root = V1ShadowRootOfParent()) |
| root->DidChangeHostChildSlotName(params.old_value, params.new_value); |
| } |
| |
| ParseAttribute(params); |
| |
| GetDocument().IncDOMTreeVersion(); |
| |
| if (name == html_names::kIdAttr) { |
| AtomicString old_id = GetElementData()->IdForStyleResolution(); |
| AtomicString new_id = MakeIdForStyleResolution( |
| params.new_value, GetDocument().InQuirksMode()); |
| if (new_id != old_id) { |
| GetElementData()->SetIdForStyleResolution(new_id); |
| GetDocument().GetStyleEngine().IdChangedForElement(old_id, new_id, *this); |
| } |
| } else if (name == html_names::kClassAttr) { |
| ClassAttributeChanged(params.new_value); |
| if (HasRareData() && GetElementRareData()->GetClassList()) { |
| GetElementRareData()->GetClassList()->DidUpdateAttributeValue( |
| params.old_value, params.new_value); |
| } |
| } else if (name == html_names::kNameAttr) { |
| SetHasName(!params.new_value.IsNull()); |
| } else if (name == html_names::kPartAttr) { |
| part().DidUpdateAttributeValue(params.old_value, params.new_value); |
| GetDocument().GetStyleEngine().PartChangedForElement(*this); |
| } else if (name == html_names::kExportpartsAttr) { |
| EnsureElementRareData().SetPartNamesMap(params.new_value); |
| GetDocument().GetStyleEngine().ExportpartsChangedForElement(*this); |
| } else if (IsElementReflectionAttribute(name)) { |
| SynchronizeContentAttributeAndElementReference(name); |
| } else if (IsStyledElement()) { |
| if (name == html_names::kStyleAttr) { |
| StyleAttributeChanged(params.new_value, params.reason); |
| } else if (IsPresentationAttribute(name)) { |
| GetElementData()->presentation_attribute_style_is_dirty_ = true; |
| SetNeedsStyleRecalc(kLocalStyleChange, |
| StyleChangeReasonForTracing::FromAttribute(name)); |
| } else if (RuntimeEnabledFeatures::DisplayLockingEnabled( |
| GetExecutionContext()) && |
| name == html_names::kRendersubtreeAttr && |
| params.old_value != params.new_value && |
| DisplayLockContext::IsAttributeVersion( |
| GetDisplayLockContext())) { |
| UseCounter::Count(GetDocument(), WebFeature::kRenderSubtreeAttribute); |
| |
| // This is needed to ensure that proper containment is put in place. |
| SetNeedsStyleRecalc(kLocalStyleChange, |
| StyleChangeReasonForTracing::FromAttribute(name)); |
| SpaceSplitString tokens(params.new_value.LowerASCII()); |
| uint16_t activation_mask = |
| static_cast<uint16_t>(DisplayLockActivationReason::kAny); |
| |
| // Figure out the activation mask. |
| if (tokens.Contains("skip-activation")) |
| activation_mask = 0; |
| if (tokens.Contains("skip-viewport-activation")) { |
| activation_mask &= |
| ~static_cast<uint16_t>(DisplayLockActivationReason::kViewport); |
| } |
| |
| EnsureDisplayLockContext(DisplayLockContextCreateMethod::kAttribute) |
| .SetActivatable(activation_mask); |
| const bool should_be_invisible = tokens.Contains("invisible"); |
| if (should_be_invisible) { |
| if (!GetDisplayLockContext()->IsLocked()) |
| GetDisplayLockContext()->StartAcquire(); |
| } else { |
| // Getting unlocked. |
| if (GetDisplayLockContext()->IsLocked()) |
| GetDisplayLockContext()->StartCommit(); |
| } |
| |
| } else if (RuntimeEnabledFeatures::InvisibleDOMEnabled() && |
| name == html_names::kInvisibleAttr && |
| params.old_value != params.new_value) { |
| InvisibleAttributeChanged(params.old_value, params.new_value); |
| } |
| } |
| |
| InvalidateNodeListCachesInAncestors(&name, this, nullptr); |
| |
| if (isConnected()) { |
| if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) { |
| if (params.old_value != params.new_value) |
| cache->HandleAttributeChanged(name, this); |
| } |
| } |
| |
| if (params.reason == AttributeModificationReason::kDirectly && |
| name == html_names::kTabindexAttr && |
| AdjustedFocusedElementInTreeScope() == this) { |
| // The attribute change may cause supportsFocus() to return false |
| // for the element which had focus. |
| // |
| // TODO(tkent): We should avoid updating style. We'd like to check only |
| // DOM-level focusability here. |
| GetDocument().UpdateStyleAndLayoutTreeForNode(this); |
| if (!SupportsFocus()) |
| blur(); |
| } |
| } |
| |
| bool Element::HasLegalLinkAttribute(const QualifiedName&) const { |
| return false; |
| } |
| |
| const QualifiedName& Element::SubResourceAttributeName() const { |
| return QualifiedName::Null(); |
| } |
| |
| template <typename CharacterType> |
| static inline ClassStringContent ClassStringHasClassName( |
| const CharacterType* characters, |
| unsigned length) { |
| DCHECK_GT(length, 0u); |
| |
| unsigned i = 0; |
| do { |
| if (IsNotHTMLSpace<CharacterType>(characters[i])) |
| break; |
| ++i; |
| } while (i < length); |
| |
| if (i == length && length >= 1) |
| return ClassStringContent::kWhiteSpaceOnly; |
| |
| return ClassStringContent::kHasClasses; |
| } |
| |
| static inline ClassStringContent ClassStringHasClassName( |
| const AtomicString& new_class_string) { |
| unsigned length = new_class_string.length(); |
| |
| if (!length) |
| return ClassStringContent::kEmpty; |
| |
| if (new_class_string.Is8Bit()) |
| return ClassStringHasClassName(new_class_string.Characters8(), length); |
| return ClassStringHasClassName(new_class_string.Characters16(), length); |
| } |
| |
| void Element::ClassAttributeChanged(const AtomicString& new_class_string) { |
| DCHECK(GetElementData()); |
| ClassStringContent class_string_content_type = |
| ClassStringHasClassName(new_class_string); |
| const bool should_fold_case = GetDocument().InQuirksMode(); |
| if (class_string_content_type == ClassStringContent::kHasClasses) { |
| const SpaceSplitString old_classes = GetElementData()->ClassNames(); |
| GetElementData()->SetClass(new_class_string, should_fold_case); |
| const SpaceSplitString& new_classes = GetElementData()->ClassNames(); |
| GetDocument().GetStyleEngine().ClassChangedForElement(old_classes, |
| new_classes, *this); |
| } else { |
| const SpaceSplitString& old_classes = GetElementData()->ClassNames(); |
| GetDocument().GetStyleEngine().ClassChangedForElement(old_classes, *this); |
| if (class_string_content_type == ClassStringContent::kWhiteSpaceOnly) |
| GetElementData()->SetClass(new_class_string, should_fold_case); |
| else |
| GetElementData()->ClearClass(); |
| } |
| } |
| |
| bool Element::ShouldInvalidateDistributionWhenAttributeChanged( |
| ShadowRoot& shadow_root, |
| const QualifiedName& name, |
| const AtomicString& new_value) { |
| if (shadow_root.IsV1()) |
| return false; |
| const SelectRuleFeatureSet& feature_set = |
| shadow_root.V0().EnsureSelectFeatureSet(); |
| |
| if (name == html_names::kIdAttr) { |
| AtomicString old_id = GetElementData()->IdForStyleResolution(); |
| AtomicString new_id = |
| MakeIdForStyleResolution(new_value, GetDocument().InQuirksMode()); |
| if (new_id != old_id) { |
| if (!old_id.IsEmpty() && feature_set.HasSelectorForId(old_id)) |
| return true; |
| if (!new_id.IsEmpty() && feature_set.HasSelectorForId(new_id)) |
| return true; |
| } |
| } |
| |
| if (name == html_names::kClassAttr) { |
| const AtomicString& new_class_string = new_value; |
| if (ClassStringHasClassName(new_class_string) == |
| ClassStringContent::kHasClasses) { |
| const SpaceSplitString& old_classes = GetElementData()->ClassNames(); |
| const SpaceSplitString new_classes(GetDocument().InQuirksMode() |
| ? new_class_string.LowerASCII() |
| : new_class_string); |
| if (feature_set.CheckSelectorsForClassChange(old_classes, new_classes)) |
| return true; |
| } else { |
| const SpaceSplitString& old_classes = GetElementData()->ClassNames(); |
| if (feature_set.CheckSelectorsForClassChange(old_classes)) |
| return true; |
| } |
| } |
| |
| return feature_set.HasSelectorForAttribute(name.LocalName()); |
| } |
| |
| // Returns true if the given attribute is an event handler. |
| // We consider an event handler any attribute that begins with "on". |
| // It is a simple solution that has the advantage of not requiring any |
| // code or configuration change if a new event handler is defined. |
| |
| static inline bool IsEventHandlerAttribute(const Attribute& attribute) { |
| return attribute.GetName().NamespaceURI().IsNull() && |
| attribute.GetName().LocalName().StartsWith("on"); |
| } |
| |
| bool Element::AttributeValueIsJavaScriptURL(const Attribute& attribute) { |
| return ProtocolIsJavaScript( |
| StripLeadingAndTrailingHTMLSpaces(attribute.Value())); |
| } |
| |
| bool Element::IsJavaScriptURLAttribute(const Attribute& attribute) const { |
| return IsURLAttribute(attribute) && AttributeValueIsJavaScriptURL(attribute); |
| } |
| |
| bool Element::IsScriptingAttribute(const Attribute& attribute) const { |
| return IsEventHandlerAttribute(attribute) || |
| IsJavaScriptURLAttribute(attribute) || |
| IsHTMLContentAttribute(attribute) || |
|