blob: 2bff5dd11379c36c67f38b0ed22ff2aca0c99c7e [file] [log] [blame]
/*
* 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 "core/dom/Element.h"
#include <memory>
#include "bindings/core/v8/Dictionary.h"
#include "bindings/core/v8/ExceptionMessages.h"
#include "bindings/core/v8/ExceptionState.h"
#include "bindings/core/v8/ScrollIntoViewOptionsOrBoolean.h"
#include "bindings/core/v8/V8DOMActivityLogger.h"
#include "core/CSSValueKeywords.h"
#include "core/SVGNames.h"
#include "core/XMLNames.h"
#include "core/animation/AnimationTimeline.h"
#include "core/animation/CustomCompositorAnimations.h"
#include "core/animation/css/CSSAnimations.h"
#include "core/css/CSSIdentifierValue.h"
#include "core/css/CSSPrimitiveValue.h"
#include "core/css/CSSStyleSheet.h"
#include "core/css/CSSValue.h"
#include "core/css/PropertySetCSSStyleDeclaration.h"
#include "core/css/StylePropertySet.h"
#include "core/css/parser/CSSParser.h"
#include "core/css/resolver/SelectorFilterParentScope.h"
#include "core/css/resolver/StyleResolver.h"
#include "core/css/resolver/StyleResolverStats.h"
#include "core/css/resolver/StyleSharingDepthScope.h"
#include "core/dom/AXObjectCache.h"
#include "core/dom/Attr.h"
#include "core/dom/CSSSelectorWatch.h"
#include "core/dom/ClientRect.h"
#include "core/dom/ClientRectList.h"
#include "core/dom/DOMTokenList.h"
#include "core/dom/DatasetDOMStringMap.h"
#include "core/dom/ElementDataCache.h"
#include "core/dom/ElementIntersectionObserverData.h"
#include "core/dom/ElementRareData.h"
#include "core/dom/ElementTraversal.h"
#include "core/dom/ExceptionCode.h"
#include "core/dom/FirstLetterPseudoElement.h"
#include "core/dom/Fullscreen.h"
#include "core/dom/LayoutTreeBuilder.h"
#include "core/dom/MutationObserverInterestGroup.h"
#include "core/dom/MutationRecord.h"
#include "core/dom/NamedNodeMap.h"
#include "core/dom/NodeComputedStyle.h"
#include "core/dom/PresentationAttributeStyle.h"
#include "core/dom/PseudoElement.h"
#include "core/dom/ResizeObservation.h"
#include "core/dom/ScriptableDocumentParser.h"
#include "core/dom/SelectorQuery.h"
#include "core/dom/StyleChangeReason.h"
#include "core/dom/StyleEngine.h"
#include "core/dom/Text.h"
#include "core/dom/custom/CustomElement.h"
#include "core/dom/custom/CustomElementRegistry.h"
#include "core/dom/custom/V0CustomElement.h"
#include "core/dom/custom/V0CustomElementRegistrationContext.h"
#include "core/dom/shadow/ElementShadow.h"
#include "core/dom/shadow/ElementShadowV0.h"
#include "core/dom/shadow/InsertionPoint.h"
#include "core/dom/shadow/ShadowRoot.h"
#include "core/dom/shadow/ShadowRootInit.h"
#include "core/dom/shadow/SlotAssignment.h"
#include "core/editing/EditingUtilities.h"
#include "core/editing/FrameSelection.h"
#include "core/editing/iterators/TextIterator.h"
#include "core/editing/serializers/Serialization.h"
#include "core/events/EventDispatcher.h"
#include "core/events/FocusEvent.h"
#include "core/frame/HostsUsingFeatures.h"
#include "core/frame/LocalDOMWindow.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/LocalFrameView.h"
#include "core/frame/ScrollIntoViewOptions.h"
#include "core/frame/ScrollToOptions.h"
#include "core/frame/Settings.h"
#include "core/frame/UseCounter.h"
#include "core/frame/VisualViewport.h"
#include "core/frame/csp/ContentSecurityPolicy.h"
#include "core/html/HTMLCanvasElement.h"
#include "core/html/HTMLCollection.h"
#include "core/html/HTMLDocument.h"
#include "core/html/HTMLElement.h"
#include "core/html/HTMLFormControlsCollection.h"
#include "core/html/HTMLFrameElementBase.h"
#include "core/html/HTMLFrameOwnerElement.h"
#include "core/html/HTMLOptionsCollection.h"
#include "core/html/HTMLPlugInElement.h"
#include "core/html/HTMLSlotElement.h"
#include "core/html/HTMLTableRowsCollection.h"
#include "core/html/HTMLTemplateElement.h"
#include "core/html/parser/HTMLParserIdioms.h"
#include "core/input/EventHandler.h"
#include "core/layout/LayoutTextFragment.h"
#include "core/layout/api/LayoutBoxItem.h"
#include "core/layout/api/LayoutViewItem.h"
#include "core/loader/DocumentLoader.h"
#include "core/page/ChromeClient.h"
#include "core/page/FocusController.h"
#include "core/page/Page.h"
#include "core/page/PointerLockController.h"
#include "core/page/SpatialNavigation.h"
#include "core/page/scrolling/RootScrollerController.h"
#include "core/page/scrolling/ScrollCustomizationCallbacks.h"
#include "core/page/scrolling/ScrollState.h"
#include "core/page/scrolling/ScrollStateCallback.h"
#include "core/page/scrolling/TopDocumentRootScrollerController.h"
#include "core/paint/PaintLayer.h"
#include "core/probe/CoreProbes.h"
#include "core/svg/SVGAElement.h"
#include "core/svg/SVGElement.h"
#include "core/svg/SVGTreeScopeResources.h"
#include "platform/EventDispatchForbiddenScope.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/bindings/DOMDataStore.h"
#include "platform/bindings/V8DOMWrapper.h"
#include "platform/bindings/V8PerContextData.h"
#include "platform/graphics/CompositorMutableProperties.h"
#include "platform/graphics/CompositorMutation.h"
#include "platform/scroll/ScrollableArea.h"
#include "platform/scroll/SmoothScrollSequencer.h"
#include "platform/wtf/BitVector.h"
#include "platform/wtf/HashFunctions.h"
#include "platform/wtf/text/CString.h"
#include "platform/wtf/text/StringBuilder.h"
#include "platform/wtf/text/TextPosition.h"
namespace blink {
namespace {
// We need to retain the scroll customization callbacks until the element
// they're associated with is destroyed. It would be simplest if the callbacks
// could be stored in ElementRareData, but we can't afford the space increase.
// Instead, keep the scroll customization callbacks here. The other option would
// be to store these callbacks on the Page or document, but that necessitates a
// bunch more logic for transferring the callbacks between Pages when elements
// are moved around.
ScrollCustomizationCallbacks& GetScrollCustomizationCallbacks() {
DEFINE_STATIC_LOCAL(ScrollCustomizationCallbacks,
scroll_customization_callbacks,
(new ScrollCustomizationCallbacks));
return scroll_customization_callbacks;
}
} // namespace
using namespace HTMLNames;
using namespace XMLNames;
enum class ClassStringContent { kEmpty, kWhiteSpaceOnly, kHasClasses };
Element* Element::Create(const QualifiedName& tag_name, Document* document) {
return new Element(tag_name, document, kCreateElement);
}
Element::Element(const QualifiedName& tag_name,
Document* document,
ConstructionType type)
: ContainerNode(document, type), tag_name_(tag_name) {}
Element::~Element() {
DCHECK(NeedsAttach());
}
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(tabindexAttr, value);
}
int Element::tabIndex() const {
return HasElementFlag(kTabIndexWasSetExplicitly)
? GetIntegralAttribute(tabindexAttr)
: 0;
}
bool Element::LayoutObjectIsFocusable() 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;
}
// 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::cloneNode(bool deep, ExceptionState&) {
return deep ? CloneElementWithChildren() : CloneElementWithoutChildren();
}
Element* Element::CloneElementWithChildren() {
Element* clone = CloneElementWithoutChildren();
CloneChildNodes(clone);
return clone;
}
Element* Element::CloneElementWithoutChildren() {
Element* clone = CloneElementWithoutAttributesAndChildren();
// 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->CloneDataFromElement(*this);
return clone;
}
Element* Element::CloneElementWithoutAttributesAndChildren() {
return GetDocument().createElement(TagQName(), kCreatedByCloneNode);
}
Attr* Element::DetachAttribute(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 =
Attr::Create(GetDocument(), attribute.GetName(), attribute.Value());
RemoveAttributeInternal(index, kNotInSynchronizationOfLazyAttribute);
}
return attr_node;
}
void Element::DetachAttrNodeAtIndex(Attr* attr, 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;
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);
}
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(NamedNodeMap::Create(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(new 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());
}
void Element::SynchronizeAllAttributes() const {
if (!GetElementData())
return;
// NOTE: anyAttributeMatches in SelectorChecker.cpp
// 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_) {
DCHECK(IsSVGElement());
ToSVGElement(this)->SynchronizeAnimatedSVGAttribute(AnyQName());
}
}
inline void Element::SynchronizeAttribute(const QualifiedName& name) const {
if (!GetElementData())
return;
if (UNLIKELY(name == styleAttr &&
GetElementData()->style_attribute_is_dirty_)) {
DCHECK(IsStyledElement());
SynchronizeStyleAttributeInternal();
return;
}
if (UNLIKELY(GetElementData()->animated_svg_attributes_are_dirty_)) {
DCHECK(IsSVGElement());
// See comment in the AtomicString version of synchronizeAttribute()
// also.
ToSVGElement(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) == styleAttr.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 layout tests. Also, m_animatedSVGAttributesAreDirty stays
// dirty unless synchronizeAnimatedSVGAttribute is called with
// anyQName(). This means that even if Element::synchronizeAttribute()
// is called on all attributes, m_animatedSVGAttributesAreDirty remains
// true.
ToSVGElement(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_empty_atom;
}
void Element::setNonce(const AtomicString& nonce) {
EnsureElementRareData().SetNonce(nonce);
}
void Element::scrollIntoView(ScrollIntoViewOptionsOrBoolean arg) {
ScrollIntoViewOptions options;
if (arg.isBoolean()) {
if (arg.getAsBoolean())
options.setBlock("start");
else
options.setBlock("end");
options.setInlinePosition("nearest");
} else if (arg.isScrollIntoViewOptions()) {
options = arg.getAsScrollIntoViewOptions();
if (!RuntimeEnabledFeatures::CSSOMSmoothScrollEnabled() &&
options.behavior() == "smooth") {
options.setBehavior("instant");
}
}
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,
bool is_horizontal_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") {
return (axis == kHorizontalScroll) ? ScrollAlignment::kAlignLeftAlways
: ScrollAlignment::kAlignTopAlways;
}
if (alignment == "end") {
return (axis == kHorizontalScroll) ? ScrollAlignment::kAlignRightAlways
: 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) {
GetDocument().EnsurePaintLocationDataValidForNode(this);
if (!GetLayoutObject() || !GetDocument().GetPage())
return;
bool make_visible_in_visual_viewport =
!GetDocument().GetPage()->GetSettings().GetInertVisualViewport();
ScrollBehavior behavior = (options.behavior() == "smooth")
? kScrollBehaviorSmooth
: kScrollBehaviorAuto;
bool is_horizontal_writing_mode =
GetComputedStyle()->IsHorizontalWritingMode();
ScrollAlignment align_x = ToPhysicalAlignment(options, kHorizontalScroll,
is_horizontal_writing_mode);
ScrollAlignment align_y =
ToPhysicalAlignment(options, kVerticalScroll, is_horizontal_writing_mode);
GetDocument().GetPage()->GetSmoothScrollSequencer()->AbortAnimations();
LayoutRect bounds = BoundingBox();
GetLayoutObject()->ScrollRectToVisible(
bounds, align_x, align_y, kProgrammaticScroll,
make_visible_in_visual_viewport, behavior);
if (behavior == kScrollBehaviorSmooth)
GetDocument().GetPage()->GetSmoothScrollSequencer()->RunQueuedAnimations();
GetDocument().SetSequentialFocusNavigationStartingPoint(this);
}
void Element::scrollIntoViewIfNeeded(bool center_if_needed) {
GetDocument().EnsurePaintLocationDataValidForNode(this);
if (!GetLayoutObject())
return;
bool make_visible_in_visual_viewport =
!GetDocument().GetPage()->GetSettings().GetInertVisualViewport();
LayoutRect bounds = BoundingBox();
if (center_if_needed)
GetLayoutObject()->ScrollRectToVisible(
bounds, ScrollAlignment::kAlignCenterIfNeeded,
ScrollAlignment::kAlignCenterIfNeeded, kProgrammaticScroll,
make_visible_in_visual_viewport);
else
GetLayoutObject()->ScrollRectToVisible(
bounds, ScrollAlignment::kAlignToEdgeIfNeeded,
ScrollAlignment::kAlignToEdgeIfNeeded, kProgrammaticScroll,
make_visible_in_visual_viewport);
}
void Element::setDistributeScroll(ScrollStateCallback* scroll_state_callback,
String native_scroll_behavior) {
scroll_state_callback->SetNativeScrollBehavior(
ScrollStateCallback::ToNativeScrollBehavior(native_scroll_behavior));
GetScrollCustomizationCallbacks().SetDistributeScroll(this,
scroll_state_callback);
}
void Element::setApplyScroll(ScrollStateCallback* scroll_state_callback,
String native_scroll_behavior) {
scroll_state_callback->SetNativeScrollBehavior(
ScrollStateCallback::ToNativeScrollBehavior(native_scroll_behavior));
GetScrollCustomizationCallbacks().SetApplyScroll(this, scroll_state_callback);
}
void Element::RemoveApplyScroll() {
GetScrollCustomizationCallbacks().RemoveApplyScroll(this);
}
ScrollStateCallback* Element::GetApplyScroll() {
return GetScrollCustomizationCallbacks().GetApplyScroll(this);
}
void Element::NativeDistributeScroll(ScrollState& scroll_state) {
if (scroll_state.FullyConsumed())
return;
scroll_state.distributeToScrollChainDescendant();
// If the scroll doesn't propagate, and we're currently scrolling
// an element other than this one, prevent the scroll from
// propagating to this element.
if (!scroll_state.shouldPropagate() &&
scroll_state.DeltaConsumedForScrollSequence() &&
scroll_state.CurrentNativeScrollingElement() != this) {
return;
}
const double delta_x = scroll_state.deltaX();
const double delta_y = scroll_state.deltaY();
CallApplyScroll(scroll_state);
if (delta_x != scroll_state.deltaX() || delta_y != scroll_state.deltaY())
scroll_state.SetCurrentNativeScrollingElement(this);
}
void Element::CallDistributeScroll(ScrollState& scroll_state) {
ScrollStateCallback* callback =
GetScrollCustomizationCallbacks().GetDistributeScroll(this);
// TODO(bokan): Need to add tests before we allow calling custom callbacks
// for non-touch modalities. For now, just call into the native callback but
// allow the viewport scroll callback so we don't disable overscroll.
// crbug.com/623079.
bool disable_custom_callbacks = !scroll_state.isDirectManipulation() &&
!GetDocument()
.GetPage()
->GlobalRootScrollerController()
.IsViewportScrollCallback(callback);
if (!callback || disable_custom_callbacks) {
NativeDistributeScroll(scroll_state);
return;
}
if (callback->NativeScrollBehavior() !=
WebNativeScrollBehavior::kPerformAfterNativeScroll)
callback->handleEvent(&scroll_state);
if (callback->NativeScrollBehavior() !=
WebNativeScrollBehavior::kDisableNativeScroll)
NativeDistributeScroll(scroll_state);
if (callback->NativeScrollBehavior() ==
WebNativeScrollBehavior::kPerformAfterNativeScroll)
callback->handleEvent(&scroll_state);
};
void Element::NativeApplyScroll(ScrollState& scroll_state) {
// All elements in the scroll chain should be boxes.
DCHECK(!GetLayoutObject() || GetLayoutObject()->IsBox());
if (scroll_state.FullyConsumed())
return;
FloatSize delta(scroll_state.deltaX(), scroll_state.deltaY());
if (delta.IsZero())
return;
// TODO(esprehn): This should use
// updateStyleAndLayoutIgnorePendingStylesheetsForNode.
GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets();
LayoutBox* box_to_scroll = nullptr;
if (GetDocument().GetRootScrollerController().ScrollsViewport(*this))
box_to_scroll = GetDocument().GetLayoutView();
else if (GetLayoutObject())
box_to_scroll = ToLayoutBox(GetLayoutObject());
if (!box_to_scroll)
return;
ScrollResult result = LayoutBoxItem(box_to_scroll)
.EnclosingBox()
.Scroll(ScrollGranularity(static_cast<int>(
scroll_state.deltaGranularity())),
delta);
if (!result.DidScroll())
return;
// FIXME: Native scrollers should only consume the scroll they
// apply. See crbug.com/457765.
scroll_state.ConsumeDeltaNative(delta.Width(), delta.Height());
// We need to setCurrentNativeScrollingElement in both the
// distributeScroll and applyScroll default implementations so
// that if JS overrides one of these methods, but not the
// other, this bookkeeping remains accurate.
scroll_state.SetCurrentNativeScrollingElement(this);
if (scroll_state.fromUserInput()) {
if (DocumentLoader* document_loader = GetDocument().Loader())
document_loader->GetInitialScrollState().was_scrolled_by_user = true;
}
};
void Element::CallApplyScroll(ScrollState& scroll_state) {
// Hits ASSERTs when trying to determine whether we need to scroll on main
// or CC. http://crbug.com/625676.
DisableCompositingQueryAsserts disabler;
if (!GetDocument().GetPage()) {
// We should always have a Page if we're scrolling. See
// crbug.com/689074 for details.
return;
}
ScrollStateCallback* callback =
GetScrollCustomizationCallbacks().GetApplyScroll(this);
// TODO(bokan): Need to add tests before we allow calling custom callbacks
// for non-touch modalities. For now, just call into the native callback but
// allow the viewport scroll callback so we don't disable overscroll.
// crbug.com/623079.
bool disable_custom_callbacks = !scroll_state.isDirectManipulation() &&
!GetDocument()
.GetPage()
->GlobalRootScrollerController()
.IsViewportScrollCallback(callback);
if (!callback || disable_custom_callbacks) {
NativeApplyScroll(scroll_state);
return;
}
if (callback->NativeScrollBehavior() !=
WebNativeScrollBehavior::kPerformAfterNativeScroll)
callback->handleEvent(&scroll_state);
if (callback->NativeScrollBehavior() !=
WebNativeScrollBehavior::kDisableNativeScroll)
NativeApplyScroll(scroll_state);
if (callback->NativeScrollBehavior() ==
WebNativeScrollBehavior::kPerformAfterNativeScroll)
callback->handleEvent(&scroll_state);
}
int Element::OffsetLeft() {
GetDocument().EnsurePaintLocationDataValidForNode(this);
if (LayoutBoxModelObject* layout_object = GetLayoutBoxModelObject())
return AdjustLayoutUnitForAbsoluteZoom(
LayoutUnit(
layout_object->PixelSnappedOffsetLeft(OffsetParent())),
layout_object->StyleRef())
.Round();
return 0;
}
int Element::OffsetTop() {
GetDocument().EnsurePaintLocationDataValidForNode(this);
if (LayoutBoxModelObject* layout_object = GetLayoutBoxModelObject())
return AdjustLayoutUnitForAbsoluteZoom(
LayoutUnit(layout_object->PixelSnappedOffsetTop(OffsetParent())),
layout_object->StyleRef())
.Round();
return 0;
}
int Element::OffsetWidth() {
GetDocument().EnsurePaintLocationDataValidForNode(this);
if (LayoutBoxModelObject* layout_object = GetLayoutBoxModelObject())
return AdjustLayoutUnitForAbsoluteZoom(
LayoutUnit(
layout_object->PixelSnappedOffsetWidth(OffsetParent())),
layout_object->StyleRef())
.Round();
return 0;
}
int Element::OffsetHeight() {
GetDocument().EnsurePaintLocationDataValidForNode(this);
if (LayoutBoxModelObject* layout_object = GetLayoutBoxModelObject())
return AdjustLayoutUnitForAbsoluteZoom(
LayoutUnit(
layout_object->PixelSnappedOffsetHeight(OffsetParent())),
layout_object->StyleRef())
.Round();
return 0;
}
Element* Element::OffsetParent() {
GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(this);
LayoutObject* layout_object = this->GetLayoutObject();
return layout_object ? layout_object->OffsetParent() : nullptr;
}
int Element::clientLeft() {
GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(this);
if (LayoutBox* layout_object = GetLayoutBox())
return AdjustLayoutUnitForAbsoluteZoom(layout_object->ClientLeft(),
layout_object->StyleRef())
.Round();
return 0;
}
int Element::clientTop() {
GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(this);
if (LayoutBox* layout_object = GetLayoutBox())
return AdjustLayoutUnitForAbsoluteZoom(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)) {
LayoutViewItem layout_view = GetDocument().GetLayoutViewItem();
if (!layout_view.IsNull()) {
if (!RuntimeEnabledFeatures::OverlayScrollbarsEnabled() ||
!GetDocument().GetFrame()->IsLocalRoot())
GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(this);
if (GetDocument().GetPage()->GetSettings().GetForceZeroLayoutHeight())
return AdjustLayoutUnitForAbsoluteZoom(
layout_view.OverflowClipRect(LayoutPoint()).Width(),
layout_view.StyleRef())
.Round();
return AdjustLayoutUnitForAbsoluteZoom(
LayoutUnit(layout_view.GetLayoutSize().Width()),
layout_view.StyleRef())
.Round();
}
}
GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(this);
if (LayoutBox* layout_object = GetLayoutBox())
return AdjustLayoutUnitForAbsoluteZoom(
LayoutUnit(layout_object->PixelSnappedClientWidth()),
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)) {
LayoutViewItem layout_view = GetDocument().GetLayoutViewItem();
if (!layout_view.IsNull()) {
if (!RuntimeEnabledFeatures::OverlayScrollbarsEnabled() ||
!GetDocument().GetFrame()->IsLocalRoot())
GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(this);
if (GetDocument().GetPage()->GetSettings().GetForceZeroLayoutHeight())
return AdjustLayoutUnitForAbsoluteZoom(
layout_view.OverflowClipRect(LayoutPoint()).Height(),
layout_view.StyleRef())
.Round();
return AdjustLayoutUnitForAbsoluteZoom(
LayoutUnit(layout_view.GetLayoutSize().Height()),
layout_view.StyleRef())
.Round();
}
}
GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(this);
if (LayoutBox* layout_object = GetLayoutBox())
return AdjustLayoutUnitForAbsoluteZoom(
LayoutUnit(layout_object->PixelSnappedClientHeight()),
layout_object->StyleRef())
.Round();
return 0;
}
double Element::scrollLeft() {
if (!InActiveDocument())
return 0;
GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(this);
if (GetDocument().ScrollingElementNoLayout() == this) {
if (GetDocument().domWindow())
return GetDocument().domWindow()->scrollX();
return 0;
}
if (LayoutBox* box = GetLayoutBox())
return AdjustScrollForAbsoluteZoom(box->ScrollLeft(), *box);
return 0;
}
double Element::scrollTop() {
if (!InActiveDocument())
return 0;
GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(this);
if (GetDocument().ScrollingElementNoLayout() == this) {
if (GetDocument().domWindow())
return GetDocument().domWindow()->scrollY();
return 0;
}
if (LayoutBox* box = GetLayoutBox())
return AdjustScrollForAbsoluteZoom(box->ScrollTop(), *box);
return 0;
}
void Element::setScrollLeft(double new_left) {
if (!InActiveDocument())
return;
GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(this);
new_left = ScrollableArea::NormalizeNonFiniteScroll(new_left);
if (GetDocument().ScrollingElementNoLayout() == this) {
if (LocalDOMWindow* window = GetDocument().domWindow())
window->scrollTo(new_left, window->scrollY());
} else {
LayoutBox* box = GetLayoutBox();
if (box)
box->SetScrollLeft(
LayoutUnit::FromFloatRound(new_left * box->Style()->EffectiveZoom()));
}
}
void Element::setScrollTop(double new_top) {
if (!InActiveDocument())
return;
GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(this);
new_top = ScrollableArea::NormalizeNonFiniteScroll(new_top);
if (GetDocument().ScrollingElementNoLayout() == this) {
if (LocalDOMWindow* window = GetDocument().domWindow())
window->scrollTo(window->scrollX(), new_top);
} else {
LayoutBox* box = GetLayoutBox();
if (box)
box->SetScrollTop(
LayoutUnit::FromFloatRound(new_top * box->Style()->EffectiveZoom()));
}
}
int Element::scrollWidth() {
if (!InActiveDocument())
return 0;
GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(this);
if (GetDocument().ScrollingElementNoLayout() == this) {
if (GetDocument().View())
return AdjustForAbsoluteZoom(GetDocument().View()->ContentsWidth(),
GetDocument().GetFrame()->PageZoomFactor());
return 0;
}
if (LayoutBox* box = GetLayoutBox())
return AdjustForAbsoluteZoom(box->PixelSnappedScrollWidth(), box);
return 0;
}
int Element::scrollHeight() {
if (!InActiveDocument())
return 0;
GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(this);
if (GetDocument().ScrollingElementNoLayout() == this) {
if (GetDocument().View())
return AdjustForAbsoluteZoom(GetDocument().View()->ContentsHeight(),
GetDocument().GetFrame()->PageZoomFactor());
return 0;
}
if (LayoutBox* box = GetLayoutBox())
return AdjustForAbsoluteZoom(box->PixelSnappedScrollHeight(), box);
return 0;
}
void Element::scrollBy(double x, double y) {
ScrollToOptions scroll_to_options;
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().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(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;
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().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(this);
if (GetDocument().ScrollingElementNoLayout() == this) {
ScrollFrameTo(scroll_to_options);
} else {
ScrollLayoutBoxTo(scroll_to_options);
}
}
void Element::ScrollLayoutBoxBy(const ScrollToOptions& scroll_to_options) {
double left =
scroll_to_options.hasLeft()
? ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options.left())
: 0.0;
double top =
scroll_to_options.hasTop()
? ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options.top())
: 0.0;
ScrollBehavior scroll_behavior = kScrollBehaviorAuto;
ScrollableArea::ScrollBehaviorFromString(scroll_to_options.behavior(),
scroll_behavior);
LayoutBox* box = GetLayoutBox();
if (box) {
float current_scaled_left = box->ScrollLeft().ToFloat();
float current_scaled_top = box->ScrollTop().ToFloat();
float new_scaled_left =
left * box->Style()->EffectiveZoom() + current_scaled_left;
float new_scaled_top =
top * box->Style()->EffectiveZoom() + current_scaled_top;
box->ScrollToPosition(FloatPoint(new_scaled_left, new_scaled_top),
scroll_behavior);
}
}
void Element::ScrollLayoutBoxTo(const ScrollToOptions& scroll_to_options) {
ScrollBehavior scroll_behavior = kScrollBehaviorAuto;
ScrollableArea::ScrollBehaviorFromString(scroll_to_options.behavior(),
scroll_behavior);
LayoutBox* box = GetLayoutBox();
if (box) {
float scaled_left = box->ScrollLeft().ToFloat();
float scaled_top = box->ScrollTop().ToFloat();
if (scroll_to_options.hasLeft())
scaled_left =
ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options.left()) *
box->Style()->EffectiveZoom();
if (scroll_to_options.hasTop())
scaled_top =
ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options.top()) *
box->Style()->EffectiveZoom();
box->ScrollToPosition(FloatPoint(scaled_left, scaled_top), scroll_behavior);
}
}
void Element::ScrollFrameBy(const ScrollToOptions& scroll_to_options) {
double left =
scroll_to_options.hasLeft()
? ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options.left())
: 0.0;
double top =
scroll_to_options.hasTop()
? ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options.top())
: 0.0;
ScrollBehavior scroll_behavior = kScrollBehaviorAuto;
ScrollableArea::ScrollBehaviorFromString(scroll_to_options.behavior(),
scroll_behavior);
LocalFrame* frame = GetDocument().GetFrame();
if (!frame)
return;
ScrollableArea* viewport =
frame->View() ? frame->View()->GetScrollableArea() : 0;
if (!viewport)
return;
float new_scaled_left =
left * frame->PageZoomFactor() + viewport->GetScrollOffset().Width();
float new_scaled_top =
top * frame->PageZoomFactor() + viewport->GetScrollOffset().Height();
viewport->SetScrollOffset(ScrollOffset(new_scaled_left, new_scaled_top),
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)
return;
ScrollableArea* viewport =
frame->View() ? frame->View()->GetScrollableArea() : 0;
if (!viewport)
return;
float scaled_left = viewport->GetScrollOffset().Width();
float scaled_top = viewport->GetScrollOffset().Height();
if (scroll_to_options.hasLeft())
scaled_left =
ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options.left()) *
frame->PageZoomFactor();
if (scroll_to_options.hasTop())
scaled_top =
ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options.top()) *
frame->PageZoomFactor();
viewport->SetScrollOffset(ScrollOffset(scaled_left, scaled_top),
kProgrammaticScroll, scroll_behavior);
}
bool Element::HasCompositorProxy() const {
return HasRareData() && GetElementRareData()->ProxiedPropertyCounts();
}
void Element::IncrementCompositorProxiedProperties(
uint32_t mutable_properties) {
ElementRareData& rare_data = EnsureElementRareData();
if (!rare_data.ProxiedPropertyCounts())
SetNeedsStyleRecalc(kLocalStyleChange,
StyleChangeReasonForTracing::Create(
StyleChangeReason::kCompositorProxy));
rare_data.IncrementCompositorProxiedProperties(mutable_properties);
}
void Element::DecrementCompositorProxiedProperties(
uint32_t mutable_properties) {
ElementRareData& rare_data = *GetElementRareData();
rare_data.DecrementCompositorProxiedProperties(mutable_properties);
if (!rare_data.ProxiedPropertyCounts())
SetNeedsStyleRecalc(kLocalStyleChange,
StyleChangeReasonForTracing::Create(
StyleChangeReason::kCompositorProxy));
}
void Element::UpdateFromCompositorMutation(const CompositorMutation& mutation) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("compositor-worker"),
"Element::updateFromCompositorMutation");
if (mutation.IsOpacityMutated() || mutation.IsTransformMutated())
EnsureElementAnimations().GetCustomCompositorAnimations().ApplyUpdate(
*this, mutation);
}
uint32_t Element::CompositorMutableProperties() const {
if (!HasRareData())
return CompositorMutableProperty::kNone;
if (CompositorProxiedPropertySet* set =
GetElementRareData()->ProxiedPropertyCounts())
return set->ProxiedProperties();
return CompositorMutableProperty::kNone;
}
bool Element::HasNonEmptyLayoutSize() const {
GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets();
if (LayoutBoxModelObject* box = GetLayoutBoxModelObject())
return box->HasNonEmptyLayoutSize();
return false;
}
IntRect Element::BoundsInViewport() const {
GetDocument().EnsurePaintLocationDataValidForNode(this);
LocalFrameView* view = GetDocument().View();
if (!view)
return IntRect();
Vector<FloatQuad> quads;
if (IsSVGElement() && GetLayoutObject()) {
// Get the bounding rectangle from the SVG model.
if (ToSVGElement(this)->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 (size_t i = 1; i < quads.size(); ++i)
result.Unite(quads[i].EnclosingBoundingBox());
return view->ContentsToViewport(result);
}
IntRect Element::VisibleBoundsInVisualViewport() const {
if (!GetLayoutObject() || !GetDocument().GetPage())
return IntRect();
// TODO(tkent): Can we check invisibility by scrollable non-frame elements?
IntSize viewport_size = GetDocument().GetPage()->GetVisualViewport().Size();
IntRect rect(0, 0, viewport_size.Width(), viewport_size.Height());
// We don't use absoluteBoundingBoxRect() because it can return an IntRect
// larger the actual size by 1px. crbug.com/470503
rect.Intersect(GetDocument().View()->ContentsToViewport(
RoundedIntRect(GetLayoutObject()->AbsoluteBoundingBoxFloatRect())));
return rect;
}
void Element::ClientQuads(Vector<FloatQuad>& quads) {
GetDocument().EnsurePaintLocationDataValidForNode(this);
LayoutObject* element_layout_object = GetLayoutObject();
if (!element_layout_object)
return;
if (IsSVGElement() && !element_layout_object->IsSVGRoot()) {
// Get the bounding rectangle from the SVG model.
if (ToSVGElement(this)->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);
}
ClientRectList* Element::getClientRects() {
Vector<FloatQuad> quads;
ClientQuads(quads);
if (quads.IsEmpty())
return ClientRectList::Create();
LayoutObject* element_layout_object = GetLayoutObject();
DCHECK(element_layout_object);
GetDocument().AdjustFloatQuadsForScrollAndAbsoluteZoom(
quads, *element_layout_object);
return ClientRectList::Create(quads);
}
ClientRect* Element::getBoundingClientRect() {
Vector<FloatQuad> quads;
ClientQuads(quads);
if (quads.IsEmpty())
return ClientRect::Create();
FloatRect result = quads[0].BoundingBox();
for (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 ClientRect::Create(result);
}
const AtomicString& Element::computedRole() {
GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(this);
std::unique_ptr<ScopedAXObjectCache> cache =
ScopedAXObjectCache::Create(GetDocument());
return cache->Get()->ComputedRoleForNode(this);
}
String Element::computedName() {
GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(this);
std::unique_ptr<ScopedAXObjectCache> cache =
ScopedAXObjectCache::Create(GetDocument());
return cache->Get()->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);
}
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));
}
void Element::setAttribute(const AtomicString& local_name,
const AtomicString& value,
ExceptionState& exception_state) {
if (!Document::IsValidName(local_name)) {
exception_state.ThrowDOMException(
kInvalidCharacterError,
"'" + local_name + "' is not a valid attribute name.");
return;
}
SynchronizeAttribute(local_name);
AtomicString case_adjusted_local_name = LowercaseIfNecessary(local_name);
if (!GetElementData()) {
SetAttributeInternal(
kNotFound,
QualifiedName(g_null_atom, case_adjusted_local_name, g_null_atom),
value, kNotInSynchronizationOfLazyAttribute);
return;
}
AttributeCollection attributes = GetElementData()->Attributes();
size_t index = attributes.FindIndex(case_adjusted_local_name);
const QualifiedName& q_name =
index != kNotFound
? attributes[index].GetName()
: QualifiedName(g_null_atom, case_adjusted_local_name, g_null_atom);
SetAttributeInternal(index, q_name, value,
kNotInSynchronizationOfLazyAttribute);
}
void Element::setAttribute(const QualifiedName& name,
const AtomicString& value) {
SynchronizeAttribute(name);
size_t index = GetElementData()
? GetElementData()->Attributes().FindIndex(name)
: kNotFound;
SetAttributeInternal(index, name, value,
kNotInSynchronizationOfLazyAttribute);
}
void Element::SetSynchronizedLazyAttribute(const QualifiedName& name,
const AtomicString& value) {
size_t index = GetElementData()
? GetElementData()->Attributes().FindIndex(name)
: kNotFound;
SetAttributeInternal(index, name, value, kInSynchronizationOfLazyAttribute);
}
ALWAYS_INLINE void Element::SetAttributeInternal(
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 (ElementShadow* parent_element_shadow =
ShadowWhereNodeCanBeDistributedForV0(*this)) {
if (ShouldInvalidateDistributionWhenAttributeChanged(
parent_element_shadow, name, params.new_value))
parent_element_shadow->SetNeedsDistributionRecalc();
}
if (name == HTMLNames::slotAttr && params.old_value != params.new_value) {
if (ShadowRoot* root = V1ShadowRootOfParent())
root->DidChangeHostChildSlotName(params.old_value, params.new_value);
}
ParseAttribute(params);
GetDocument().IncDOMTreeVersion();
if (name == HTMLNames::idAttr) {
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 == classAttr) {
ClassAttributeChanged(params.new_value);
if (HasRareData() && GetElementRareData()->GetClassList()) {
GetElementRareData()->GetClassList()->DidUpdateAttributeValue(
params.old_value, params.new_value);
}
} else if (name == HTMLNames::nameAttr) {
SetHasName(!params.new_value.IsNull());
} else if (IsStyledElement()) {
if (name == styleAttr) {
StyleAttributeChanged(params.new_value, params.reason);
} else if (IsPresentationAttribute(name)) {
GetElementData()->presentation_attribute_style_is_dirty_ = true;
SetNeedsStyleRecalc(kLocalStyleChange,
StyleChangeReasonForTracing::FromAttribute(name));
}
}
InvalidateNodeListCachesInAncestors(&name, this);
if (isConnected()) {
if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache())
cache->HandleAttributeChanged(name, this);
}
if (params.reason == AttributeModificationReason::kDirectly &&
name == tabindexAttr && 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();
}
inline void Element::AttributeChangedFromParserOrByCloning(
const QualifiedName& name,
const AtomicString& new_value,
AttributeModificationReason reason) {
if (name == isAttr)
V0CustomElementRegistrationContext::SetTypeExtension(this, new_value);
AttributeChanged(
AttributeModificationParams(name, g_null_atom, new_value, reason));
}
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(
ElementShadow* element_shadow,
const QualifiedName& name,
const AtomicString& new_value) {
DCHECK(element_shadow);
if (element_shadow->IsV1())
return false;
const SelectRuleFeatureSet& feature_set =
element_shadow->V0().EnsureSelectFeatureSet();
if (name == HTMLNames::idAttr) {
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 == HTMLNames::classAttr) {
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) ||
IsSVGAnimationAttributeSettingJavaScriptURL(attribute);
}
void Element::StripScriptingAttributes(
Vector<Attribute>& attribute_vector) const {
size_t destination = 0;
for (size_t source = 0; source < attribute_vector.size(); ++source) {
if (IsScriptingAttribute(attribute_vector[source]))
continue;
if (source != destination)
attribute_vector[destination] = attribute_vector[source];
++destination;
}
attribute_vector.Shrink(destination);
}
void Element::ParserSetAttributes(const Vector<Attribute>& attribute_vector) {
DCHECK(!isConnected());
DCHECK(!parentNode());
DCHECK(!element_data_);
if (!attribute_vector.IsEmpty()) {
if (GetDocument().GetElementDataCache())
element_data_ =
GetDocument()
.GetElementDataCache()
->CachedShareableElementDataWithAttributes(attribute_vector);
else
element_data_ =
ShareableElementData::CreateWithAttributes(attribute_vector);
}
ParserDidSetAttributes();
// Use attributeVector instead of m_elementData because attributeChanged might
// modify m_elementData.
for (const auto& attribute : attribute_vector) {
AttributeChangedFromParserOrByCloning(
attribute.GetName(), attribute.Value(),
AttributeModificationReason::kByParser);
}
}
bool Element::HasEquivalentAttributes(const Element* other) const {
SynchronizeAllAttributes();
other->SynchronizeAllAttributes();
if (GetElementData() == other->GetElementData())
return true;
if (GetElementData())
return GetElementData()->IsEquivalent(other->GetElementData());
if (other->GetElementData())
return other->GetElementData()->IsEquivalent(GetElementData());
return true;
}
String Element::nodeName() const {
return tag_name_.ToString();
}
const AtomicString& Element::LocateNamespacePrefix(
const AtomicString& namespace_to_locate) const {
if (!prefix().IsNull() && namespaceURI() == namespace_to_locate)
return prefix();
AttributeCollection attributes = this->Attributes();
for (const Attribute& attr : attributes) {
if (attr.Prefix() == g_xmlns_atom && attr.Value() == namespace_to_locate)
return attr.LocalName();
}
if (Element* parent = parentElement())
return parent->LocateNamespacePrefix(namespace_to_locate);
return g_null_atom;
}
const AtomicString Element::ImageSourceURL() const {
return getAttribute(srcAttr);
}
bool Element::LayoutObjectIsNeeded(const ComputedStyle& style) {
return style.Display() != EDisplay::kNone &&
style.Display() != EDisplay::kContents;
}
LayoutObject* Element::CreateLayoutObject(const ComputedStyle& style) {
return LayoutObject::CreateObject(this, style);
}
Node::InsertionNotificationRequest Element::InsertedInto(
ContainerNode* insertion_point) {
// need to do superclass processing first so isConnected() is true
// by the time we reach updateId
ContainerNode::InsertedInto(insertion_point);
if (ContainsFullScreenElement() && parentElement() &&
!parentElement()->ContainsFullScreenElement())
SetContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(true);
DCHECK(!HasRareData() || !GetElementRareData()->HasPseudoElements());
if (!insertion_point->IsInTreeScope())
return kInsertionDone;
if (HasRareData()) {
ElementRareData* rare_data = GetElementRareData();
if (rare_data->IntersectionObserverData())
rare_data->IntersectionObserverData()->ActivateValidIntersectionObservers(
*this);
}
if (isConnected()) {
if (GetCustomElementState() == CustomElementState::kCustom)
CustomElement::EnqueueConnectedCallback(this);
else if (IsUpgradedV0CustomElement())
V0CustomElement::DidAttach(this, GetDocument());
else if (GetCustomElementState() == CustomElementState::kUndefined)
CustomElement::TryToUpgrade(this);
}
TreeScope& scope = insertion_point->GetTreeScope();
if (scope != GetTreeScope())
return kInsertionDone;
const AtomicString& id_value = GetIdAttribute();
if (!id_value.IsNull())
UpdateId(scope, g_null_atom, id_value);
const AtomicString& name_value = GetNameAttribute();
if (!name_value.IsNull())
UpdateName(g_null_atom, name_value);
if (parentElement() && parentElement()->IsInCanvasSubtree())
SetIsInCanvasSubtree(true);
return kInsertionDone;
}
void Element::RemovedFrom(ContainerNode* insertion_point) {
bool was_in_document = insertion_point->isConnected();
DCHECK(!HasRareData() || !GetElementRareData()->HasPseudoElements());
if (Fullscreen::IsCurrentFullScreenElement(*this)) {
SetContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false);
if (insertion_point->IsElementNode()) {
ToElement(insertion_point)->SetContainsFullScreenElement(false);
ToElement(insertion_point)
->SetContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(
false);
}
}
if (Fullscreen* fullscreen = Fullscreen::FromIfExists(GetDocument()))
fullscreen->ElementRemoved(*this);
if (GetDocument().GetPage())
GetDocument().GetPage()->GetPointerLockController().ElementRemoved(this);
SetSavedLayerScrollOffset(ScrollOffset());
if (insertion_point->IsInTreeScope() && GetTreeScope() == GetDocument()) {
const AtomicString& id_value = GetIdAttribute();
if (!id_value.IsNull())
UpdateId(insertion_point->GetTreeScope(), id_value, g_null_atom);
const AtomicString& name_value = GetNameAttribute();
if (!name_value.IsNull())
UpdateName(name_value, g_null_atom);
}
ContainerNode::RemovedFrom(insertion_point);
if (was_in_document) {
if (this == GetDocument().CssTarget())
GetDocument().SetCSSTarget(nullptr);
if (HasPendingResources()) {
GetTreeScope()
.EnsureSVGTreeScopedResources()
.RemoveElementFromPendingResources(*this);
}
if (GetCustomElementState() == CustomElementState::kCustom)
CustomElement::EnqueueDisconnectedCallback(this);
else if (IsUpgradedV0CustomElement())
V0CustomElement::DidDetach(this, insertion_point->GetDocument());
if (NeedsStyleInvalidation())
GetDocument().GetStyleEngine().GetStyleInvalidator().ClearInvalidation(
*this);
}
GetDocument().RemoveFromTopLayer(this);
ClearElementFlag(kIsInCanvasSubtree);
if (HasRareData()) {
ElementRareData* data = GetElementRareData();
data->ClearRestyleFlags();
if (ElementAnimations* element_animations = data->GetElementAnimations())
element_animations->CssAnimations().Cancel();
if (data->IntersectionObserverData())
data->IntersectionObserverData()->DeactivateAllIntersectionObservers(
*this);
}
if (GetDocument().GetFrame())
GetDocument().GetFrame()->GetEventHandler().ElementRemoved(this);
}
void Element::AttachLayoutTree(const AttachContext& context) {
DCHECK(GetDocument().InStyleRecalc());
// We've already been through detach when doing an attach, but we might
// need to clear any state that's been added since then.
if (HasRareData() && NeedsAttach()) {
ElementRareData* data = GetElementRareData();
data->ClearComputedStyle();
}
if (!IsActiveSlotOrActiveInsertionPoint()) {
LayoutTreeBuilderForElement builder(*this, context.resolved_style);
builder.CreateLayoutObjectIfNeeded();
if (ComputedStyle* style = builder.ResolvedStyle()) {
if (!GetLayoutObject() && ShouldStoreNonLayoutObjectComputedStyle(*style))
StoreNonLayoutObjectComputedStyle(style);
}
}
AddCallbackSelectors();
if (HasRareData() && !GetLayoutObject()) {
if (ElementAnimations* element_animations =
GetElementRareData()->GetElementAnimations()) {
element_animations->CssAnimations().Cancel();
element_animations->SetAnimationStyleChange(false);
}
}
SelectorFilterParentScope filter_scope(*this);
StyleSharingDepthScope sharing_scope(*this);
CreatePseudoElementIfNeeded(kPseudoIdBefore);
// When a shadow root exists, it does the work of attaching the children.
if (ElementShadow* shadow = this->Shadow())
shadow->Attach(context);
ContainerNode::AttachLayoutTree(context);
CreatePseudoElementIfNeeded(kPseudoIdAfter);
CreatePseudoElementIfNeeded(kPseudoIdBackdrop);
// We create the first-letter element after the :before, :after and
// children are attached because the first letter text could come
// from any of them.
CreatePseudoElementIfNeeded(kPseudoIdFirstLetter);
}
void Element::DetachLayoutTree(const AttachContext& context) {
HTMLFrameOwnerElement::PluginDisposeSuspendScope suspend_plugin_dispose;
CancelFocusAppearanceUpdate();
RemoveCallbackSelectors();
if (HasRareData()) {
ElementRareData* data = GetElementRareData();
data->ClearPseudoElements();
// attachLayoutTree() will clear the computed style for us when inside
// recalcStyle.
if (!GetDocument().InStyleRecalc())
data->ClearComputedStyle();
if (ElementAnimations* element_animations = data->GetElementAnimations()) {
if (context.performing_reattach) {
// FIXME: We call detach from within style recalc, so compositingState
// is not up to date.
// https://code.google.com/p/chromium/issues/detail?id=339847
DisableCompositingQueryAsserts disabler;
// FIXME: restart compositor animations rather than pull back to the
// main thread
element_animations->RestartAnimationOnCompositor();
} else {
element_animations->CssAnimations().Cancel();
element_animations->SetAnimationStyleChange(false);
}
element_animations->ClearBaseComputedStyle();
}
if (ElementShadow* shadow = data->Shadow())
shadow->Detach(context);
}
ContainerNode::DetachLayoutTree(context);
if (!context.performing_reattach && IsUserActionElement()) {
if (IsHovered())
GetDocument().HoveredElementDetached(*this);
if (InActiveChain())
GetDocument().ActiveChainNodeDetached(*this);
GetDocument().UserActionElements().DidDetach(*this);
}
if (context.clear_invalidation)
GetDocument().GetStyleEngine().GetStyleInvalidator().ClearInvalidation(
*this);
SetNeedsResizeObserverUpdate();
DCHECK(NeedsAttach());
}
PassRefPtr<ComputedStyle> Element::StyleForLayoutObject() {
DCHECK(GetDocument().InStyleRecalc());
RefPtr<ComputedStyle> style;
// FIXME: Instead of clearing updates that may have been added from calls to
// styleForElement outside recalcStyle, we should just never set them if we're
// not inside recalcStyle.
if (ElementAnimations* element_animations = this->GetElementAnimations())
element_animations->CssAnimations().ClearPendingUpdate();
if (HasCustomStyleCallbacks())
style = CustomStyleForLayoutObject();
if (!style)
style = OriginalStyleForLayoutObject();
DCHECK(style);
// styleForElement() might add active animations so we need to get it again.
if (ElementAnimations* element_animations = this->GetElementAnimations()) {
element_animations->CssAnimations().MaybeApplyPendingUpdate(this);
element_animations->UpdateAnimationFlags(*style);
}
if (style->HasTransform()) {
if (const StylePropertySet* inline_style = this->InlineStyle())
style->SetHasInlineTransform(
inline_style->HasProperty(CSSPropertyTransform));
}
style->UpdateIsStackingContext(this == GetDocument().documentElement(),
IsInTopLayer());
return style.Release();
}
PassRefPtr<ComputedStyle> Element::OriginalStyleForLayoutObject() {
DCHECK(GetDocument().InStyleRecalc());
return GetDocument().EnsureStyleResolver().StyleForElement(this);
}
void Element::RecalcStyle(StyleRecalcChange change) {
DCHECK(GetDocument().InStyleRecalc());
DCHECK(!GetDocument().Lifecycle().InDetach());
DCHECK(!ParentOrShadowHostNode()->NeedsStyleRecalc());
DCHECK(InActiveDocument());
if (HasCustomStyleCallbacks())
WillRecalcStyle(change);
if (change >= kIndependentInherit || NeedsStyleRecalc()) {
if (HasRareData()) {
ElementRareData* data = GetElementRareData();
if (change != kIndependentInherit) {
// We keep the old computed style around for display: contents, option
// and optgroup. This way we can call stylePropagationDiff accurately.
//
// We could clear it always, but we'd have more expensive restyles for
// children.
//
// Note that we can't just keep stored other kind of non-layout object
// computed style (like the one that gets set when getComputedStyle is
// called on a display: none element), because that is a sizable memory
// hit.
//
// Also, we don't want to leave a stale computed style, which may happen
// if we don't end up calling recalcOwnStyle because there's no parent
// style.
const ComputedStyle* non_layout_style = NonLayoutObjectComputedStyle();
if (!non_layout_style ||
!ShouldStoreNonLayoutObjectComputedStyle(*non_layout_style) ||
!ParentComputedStyle()) {
data->ClearComputedStyle();
}
}
if (change >= kIndependentInherit) {
if (ElementAnimations* element_animations =
data->GetElementAnimations())
element_animations->SetAnimationStyleChange(false);
}
}
if (ParentComputedStyle()) {
change = RecalcOwnStyle(change);
} else if (NeedsAttach()) {
SetNeedsReattachLayoutTree();
change = kReattach;
}
// Needed because the rebuildLayoutTree code needs to see what the
// styleChangeType() was on reattach roots. See Node::reattachLayoutTree()
// for an example.
if (change != kReattach)
ClearNeedsStyleRecalc();
}
// If we are going to reattach we don't need to recalc the style of
// our descendants anymore.
if (change < kReattach &&
(change >= kUpdatePseudoElements || ChildNeedsStyleRecalc())) {
SelectorFilterParentScope filter_scope(*this);
StyleSharingDepthScope sharing_scope(*this);
UpdatePseudoElement(kPseudoIdBefore, change);
if (change > kUpdatePseudoElements || ChildNeedsStyleRecalc()) {
for (ShadowRoot* root = YoungestShadowRoot(); root;
root = root->OlderShadowRoot()) {
if (root->ShouldCallRecalcStyle(change))
root->RecalcStyle(change);
}
RecalcDescendantStyles(change);
}
UpdatePseudoElement(kPseudoIdAfter, change);
UpdatePseudoElement(kPseudoIdBackdrop, change);
// If our children have changed then we need to force the first-letter
// checks as we don't know if they effected the first letter or not.
// This can be seen when a child transitions from floating to
// non-floating we have to take it into account for the first letter.
UpdatePseudoElement(kPseudoIdFirstLetter,
ChildNeedsStyleRecalc() ? kForce : change);
ClearChildNeedsStyleRecalc();
}
if (HasCustomStyleCallbacks())
DidRecalcStyle();
}
PassRefPtr<ComputedStyle> Element::PropagateInheritedProperties(
StyleRecalcChange change) {
if (change != kIndependentInherit)
return nullptr;
if (IsPseudoElement())
return nullptr;
if (NeedsStyleRecalc())
return nullptr;
if (HasAnimations())
return nullptr;
const ComputedStyle* parent_style = ParentComputedStyle();
DCHECK(parent_style);
const ComputedStyle* style = GetComputedStyle();
if (!style || style->Animations() || style->Transitions())
return nullptr;
RefPtr<ComputedStyle> new_style = ComputedStyle::Clone(*style);
new_style->PropagateIndependentInheritedProperties(*parent_style);
INCREMENT_STYLE_STATS_COUNTER(GetDocument().GetStyleEngine(),
independent_inherited_styles_propagated, 1);
return new_style;
}
StyleRecalcChange Element::RecalcOwnStyle(StyleRecalcChange change) {
DCHECK(GetDocument().InStyleRecalc());
DCHECK(!ParentOrShadowHostNode()->NeedsStyleRecalc());
DCHECK(change >= kIndependentInherit || NeedsStyleRecalc());
DCHECK(ParentComputedStyle());
RefPtr<ComputedStyle> old_style = MutableComputedStyle();
// When propagating inherited changes, we don't need to do a full style recalc
// if the only changed properties are independent. In this case, we can simply
// set these directly on the ComputedStyle object.
RefPtr<ComputedStyle> new_style = PropagateInheritedProperties(change);
if (!new_style)
new_style = StyleForLayoutObject();
DCHECK(new_style);
StyleRecalcChange local_change =
ComputedStyle::StylePropagationDiff(old_style.Get(), new_style.Get());
if (local_change == kNoChange) {
INCREMENT_STYLE_STATS_COUNTER(GetDocument().GetStyleEngine(),
styles_unchanged, 1);
} else {
INCREMENT_STYLE_STATS_COUNTER(GetDocument().GetStyleEngine(),
styles_changed, 1);
}
if (local_change == kReattach) {
SetNonAttachedStyle(std::move(new_style));
SetNeedsReattachLayoutTree();
return kReattach;
}
DCHECK(old_style);
if (local_change != kNoChange)
UpdateCallbackSelectors(old_style.Get(), new_style.Get());
if (LayoutObject* layout_object = this->GetLayoutObject()) {
if (local_change != kNoChange) {
layout_object->SetStyle(new_style.Get());
} else {
// Although no change occurred, we use the new style so that the cousin
// style sharing code won't get fooled into believing this style is the
// same.
// FIXME: We may be able to remove this hack, see discussion in
// https://codereview.chromium.org/30453002/
layout_object->SetStyleInternal(new_style.Get());
}
} else {
if (local_change != kNoChange) {
if (ShouldStoreNonLayoutObjectComputedStyle(*new_style))
StoreNonLayoutObjectComputedStyle(new_style);
else if (HasRareData())
GetElementRareData()->ClearComputedStyle();
}
}
if (GetStyleChangeType() >= kSubtreeStyleChange)
return kForce;
if (change > kInherit || local_change > kInherit)
return max(local_change, change);
if (local_change < kIndependentInherit) {
if (old_style->HasChildDependentFlags()) {
if (ChildNeedsStyleRecalc())
return kInherit;
new_style->CopyChildDependentFlagsFrom(*old_style);
}
if (old_style->HasPseudoElementStyle() ||
new_style->HasPseudoElementStyle())
return kUpdatePseudoElements;
}
return local_change;
}
void Element::RebuildLayoutTree(Text* next_text_sibling) {
DCHECK(InActiveDocument());
DCHECK(parentNode());
if (NeedsReattachLayoutTree()) {
AttachContext reattach_context;
reattach_context.resolved_style = GetNonAttachedStyle();
bool layout_object_will_change = NeedsAttach() || GetLayoutObject();
ReattachLayoutTree(reattach_context);
if (layout_object_will_change || GetLayoutObject())
ReattachWhitespaceSiblingsIfNeeded(next_text_sibling);
SetNonAttachedStyle(nullptr);
} else if (ChildNeedsReattachLayoutTree()) {
DCHECK(!NeedsReattachLayoutTree());
SelectorFilterParentScope filter_scope(*this);
StyleSharingDepthScope sharing_scope(*this);
Text* next_text_sibling = nullptr;
RebuildPseudoElementLayoutTree(kPseudoIdAfter);
RebuildShadowRootLayoutTree(next_text_sibling);
RebuildChildrenLayoutTrees(next_text_sibling);
RebuildPseudoElementLayoutTree(kPseudoIdBefore, next_text_sibling);
RebuildPseudoElementLayoutTree(kPseudoIdBackdrop);
RebuildPseudoElementLayoutTree(kPseudoIdFirstLetter);
}
DCHECK(!NeedsStyleRecalc());
DCHECK(!ChildNeedsStyleRecalc());
DCHECK(!NeedsReattachLayoutTree());
DCHECK(!ChildNeedsReattachLayoutTree());
}
void Element::RebuildShadowRootLayoutTree(Text*& next_text_sibling) {
for (ShadowRoot* root = YoungestShadowRoot(); root;
root = root->OlderShadowRoot()) {
// TODO(rune@opera.com): nextTextSibling is not set correctly when we have
// slotted nodes (crbug.com/648931). Also, it may be incorrect when we have
// multiple shadow roots (for V0 shadow hosts).
root->RebuildLayoutTree(next_text_sibling);
}
}
void Element::RebuildPseudoElementLayoutTree(PseudoId pseudo_id,
Text* next_text_sibling) {
if (PseudoElement* element = GetPseudoElement(pseudo_id)) {
if (pseudo_id == kPseudoIdFirstLetter && UpdateFirstLetter(element))
return;
if (element->NeedsReattachLayoutTree() ||
element->ChildNeedsReattachLayoutTree()) {
element->RebuildLayoutTree(next_text_sibling);
}
} else {
CreatePseudoElementIfNeeded(pseudo_id);
}
}
void Element::UpdateCallbackSelectors(const ComputedStyle* old_style,
const ComputedStyle* new_style) {
Vector<String> empty_vector;
const Vector<String>& old_callback_selectors =
old_style ? old_style->CallbackSelectors() : empty_vector;
const Vector<String>& new_callback_selectors =
new_style ? new_style->CallbackSelectors() : empty_vector;
if (old_callback_selectors.IsEmpty() && new_callback_selectors.IsEmpty())
return;
if (old_callback_selectors != new_callback_selectors)
CSSSelectorWatch::From(GetDocument())
.UpdateSelectorMatches(old_callback_selectors, new_callback_selectors);
}
void Element::AddCallbackSelectors() {
UpdateCallbackSelectors(0, GetComputedStyle());
}
void Element::RemoveCallbackSelectors() {
UpdateCallbackSelectors(GetComputedStyle(), 0);
}
ElementShadow* Element::Shadow() const {
return HasRareData() ? GetElementRareData()->Shadow() : nullptr;
}
ElementShadow& Element::EnsureShadow() {
return EnsureElementRareData().EnsureShadow();
}
void Element::PseudoStateChanged(CSSSelector::PseudoType pseudo) {
// We can't schedule invaliation sets from inside style recalc otherwise
// we'd never process them.
// TODO(esprehn): Make this an ASSERT and fix places that call into this
// like HTMLSelectElement.
if (GetDocument().InStyleRecalc())
return;
GetDocument().GetStyleEngine().PseudoStateChangedForElement(pseudo, *this);
}
void Element::SetAnimationStyleChange(bool animation_style_change) {
if (animation_style_change && GetDocument().InStyleRecalc())
return;
if (!HasRareData())
return;
if (ElementAnimations* element_animations =
GetElementRareData()->GetElementAnimations())
element_animations->SetAnimationStyleChange(animation_style_change);
}
void Element::ClearAnimationStyleChange() {
if (!HasRareData())
return;
if (ElementAnimations* element_animations =
GetElementRareData()->GetElementAnimations())
element_animations->SetAnimationStyleChange(false);
}
void Element::SetNeedsAnimationStyleRecalc() {
if (GetStyleChangeType() != kNoStyleChange)
return;
SetNeedsStyleRecalc(kLocalStyleChange, StyleChangeReasonForTracing::Create(
StyleChangeReason::kAnimation));
SetAnimationStyleChange(true);
}
void Element::SetNeedsCompositingUpdate() {
if (!GetDocument().IsActive())
return;
LayoutBoxModelObject* layout_object = GetLayoutBoxModelObject();
if (!layout_object)
return;
if (!layout_object->HasLayer())
return;
layout_object->Layer()->SetNeedsCompositingInputsUpdate();
// Changes in the return value of requiresAcceleratedCompositing change if
// the PaintLayer is self-painting.
layout_object->Layer()->UpdateSelfPaintingLayer();
}
void Element::V0SetCustomElementDefinition(
V0CustomElementDefinition* definition) {
if (!HasRareData() && !definition)
return;
DCHECK(!GetV0CustomElementDefinition());
EnsureElementRareData().V0SetCustomElementDefinition(definition);
}
V0CustomElementDefinition* Element::GetV0CustomElementDefinition() const {
if (HasRareData())
return GetElementRareData()->GetV0CustomElementDefinition();
return nullptr;
}
void Element::SetCustomElementDefinition(CustomElementDefinition* definition) {
DCHECK(definition);
DCHECK(!GetCustomElementDefinition());
EnsureElementRareData().SetCustomElementDefinition(definition);
this->SetCustomElementState(CustomElementState::kCustom);
}
CustomElementDefinition* Element::GetCustomElementDefinition() const {
if (HasRareData())
return GetElementRareData()->GetCustomElementDefinition();
return nullptr;
}
ShadowRoot* Element::createShadowRoot(const ScriptState* script_state,
ExceptionState& exception_state) {
HostsUsingFeatures::CountMainWorldOnly(
script_state, GetDocument(),
HostsUsingFeatures::Feature::kElementCreateShadowRoot);
if (ShadowRoot* root = GetShadowRoot()) {
if (root->IsV1()) {
exception_state.ThrowDOMException(kInvalidStateError,
"Shadow root cannot be created on a "
"host which already hosts a v1 shadow "
"tree.");
return nullptr;
}
if (root->GetType() == ShadowRootType::kUserAgent) {
exception_state.ThrowDOMException(
kInvalidStateError,
"Shadow root cannot be created on a "
"host which already hosts an user-agent "
"shadow tree.");
return nullptr;
}
} else if (AlwaysCreateUserAgentShadowRoot()) {
exception_state.ThrowDOMException(
kInvalidStateError,
"Shadow root cannot be created on a host "
"which already hosts an user-agent shadow "
"tree.");
return nullptr;
}
GetDocument().SetShadowCascadeOrder(ShadowCascadeOrder::kShadowCascadeV0);
return CreateShadowRootInternal(ShadowRootType::V0, exception_state);
}
ShadowRoot* Element::attachShadow(const ScriptState* script_state,
const ShadowRootInit& shadow_root_init_dict,
ExceptionState& exception_state) {
HostsUsingFeatures::CountMainWorldOnly(
script_state, GetDocument(),
HostsUsingFeatures::Feature::kElementAttachShadow);
const AtomicString& tag_name = localName();
bool tag_name_is_supported =
IsV0CustomElement() ||
GetCustomElementState() != CustomElementState::kUncustomized ||
tag_name == HTMLNames::articleTag || tag_name == HTMLNames::asideTag ||
tag_name == HTMLNames::blockquoteTag || tag_name == HTMLNames::bodyTag ||
tag_name == HTMLNames::divTag || tag_name == HTMLNames::footerTag ||
tag_name == HTMLNames::h1Tag || tag_name == HTMLNames::h2Tag ||
tag_name == HTMLNames::h3Tag || tag_name == HTMLNames::h4Tag ||
tag_name == HTMLNames::h5Tag || tag_name == HTMLNames::h6Tag ||
tag_name == HTMLNames::headerTag || tag_name == HTMLNames::navTag ||
tag_name == HTMLNames::mainTag || tag_name == HTMLNames::pTag ||
tag_name == HTMLNames::sectionTag || tag_name == HTMLNames::spanTag;
if (!tag_name_is_supported) {
exception_state.ThrowDOMException(
kNotSupportedError, "This element does not support attachShadow");
return nullptr;
}
if (shadow_root_init_dict.hasMode() && GetShadowRoot()) {
exception_state.ThrowDOMException(kInvalidStateError,
"Shadow root cannot be created on a host "
"which already hosts a shadow tree.");
return nullptr;
}
GetDocument().SetShadowCascadeOrder(ShadowCascadeOrder::kShadowCascadeV1);
ShadowRootType type = ShadowRootType::V0;
if (shadow_root_init_dict.hasMode())
type = shadow_root_init_dict.mode() == "open" ? ShadowRootType::kOpen
: ShadowRootType::kClosed;
if (type == ShadowRootType::kClosed)
UseCounter::Count(GetDocument(), WebFeature::kElementAttachShadowClosed);
else if (type == ShadowRootType::kOpen)
UseCounter::Count(GetDocument(), WebFeature::kElementAttachShadowOpen);
ShadowRoot* shadow_root = CreateShadowRootInternal(type, exception_state);
if (shadow_root_init_dict.hasDelegatesFocus()) {
shadow_root->SetDelegatesFocus(shadow_root_init_dict.delegatesFocus());
UseCounter::Count(GetDocument(), WebFeature::kShadowRootDelegatesFocus);
}
return shadow_root;
}
ShadowRoot* Element::CreateShadowRootInternal(ShadowRootType type,
ExceptionState& exception_state) {
DCHECK(!ClosedShadowRoot());
if (AlwaysCreateUserAgentShadowRoot())
EnsureUserAgentShadowRoot();
// Some elements make assumptions about what kind of layoutObjects they allow
// as children so we can't allow author shadows on them for now.
if (!AreAuthorShadowsAllowed()) {
exception_state.ThrowDOMException(
kHierarchyRequestError,
"Author-created shadow roots are disabled for this element.");
return nullptr;
}
return &EnsureShadow().AddShadowRoot(*this, type);
}
ShadowRoot* Element::GetShadowRoot() const {
ElementShadow* element_shadow = Shadow();
if (!element_shadow)
return nullptr;
return &element_shadow->YoungestShadowRoot();
}
ShadowRoot* Element::openShadowRoot() const {
ShadowRoot* root = GetShadowRoot();
if (!root)
return nullptr;
return root->GetType() == ShadowRootType::V0 ||
root->GetType() == ShadowRootType::kOpen
? root
: nullptr;
}
ShadowRoot* Element::ClosedShadowRoot() const {
ShadowRoot* root = GetShadowRoot();
if (!root)
return nullptr;
return root->GetType() == ShadowRootType::kClosed ? root : nullptr;
}
ShadowRoot* Element::AuthorShadowRoot() const {
ShadowRoot* root = GetShadowRoot();
if (!root)
return nullptr;
return root->GetType() != ShadowRootType::kUserAgent ? root : nullptr;
}
ShadowRoot* Element::UserAgentShadowRoot() const {
if (ElementShadow* element_shadow = Shadow()) {
ShadowRoot& root = element_shadow->OldestShadowRoot();
DCHECK(root.GetType() == ShadowRootType::kUserAgent);
return &root;
}
return nullptr;
}
ShadowRoot& Element::EnsureUserAgentShadowRoot() {
if (ShadowRoot* shadow_root = UserAgentShadowRoot())
return *shadow_root;
ShadowRoot& shadow_root =
EnsureShadow().AddShadowRoot(*this, ShadowRootType::kUserAgent);
DidAddUserAgentShadowRoot(shadow_root);
return shadow_root;
}
bool Element::ChildTypeAllowed(NodeType type) const {
switch (type) {
case kElementNode:
case kTextNode:
case kCommentNode:
case kProcessingInstructionNode:
case kCdataSectionNode:
return true;
default:
break;
}
return false;
}
void Element::CheckForEmptyStyleChange() {
const ComputedStyle* style = GetComputedStyle();
if (!style && !StyleAffectedByEmpty())
return;
if (!InActiveDocument())
return;
if (!style ||
(StyleAffectedByEmpty() && (!style->EmptyState() || HasChildren())))
PseudoStateChanged(CSSSelector::kPseudoEmpty);
}
void Element::ChildrenChanged(const ChildrenChange& change) {
ContainerNode::ChildrenChanged(change);
CheckForEmptyStyleChange();
if (!change.by_parser && change.IsChildElementChange())
CheckForSiblingStyleChanges(
change.type == kElementRemoved ? kSiblingElementRemoved
: kSiblingElementInserted,
ToElement(change.sibling_changed), change.sibling_before_change,
change.sibling_after_change);
// TODO(hayato): Confirm that we can skip this if a shadow tree is v1.
if (ElementShadow* shadow = this->Shadow())
shadow->SetNeedsDistributionRecalc();
}
void Element::FinishParsingChildren() {
SetIsFinishedParsingChildren(true);
CheckForEmptyStyleChange();
CheckForSiblingStyleChanges(kFinishedParsingChildren, nullptr, lastChild(),
nullptr);
}
AttrNodeList* Element::GetAttrNodeList() {
return HasRareData() ? GetElementRareData()->GetAttrNodeList() : nullptr;
}
void Element::RemoveAttrNodeList() {
DCHECK(GetAttrNodeList());
if (HasRareData())
GetElementRareData()->RemoveAttrNodeList();
}
Attr* Element::setAttributeNode(Attr* attr_node,
ExceptionState& exception_state) {
Attr* old_attr_node = AttrIfExists(attr_node->GetQualifiedName());
if (old_attr_node == attr_node)
return attr_node; // This Attr is already attached to the element.
// InUseAttributeError: Raised if node is an Attr that is already an attribute
// of another Element object. The DOM user must explicitly clone Attr nodes
// to re-use them in other elements.
if (attr_node->ownerElement()) {
exception_state.ThrowDOMException(
kInUseAttributeError,
"The node provided is an attribute node that is already an attribute "
"of another Element; attribute nodes must be explicitly cloned.");
return nullptr;
}
if (!IsHTMLElement() && attr_node->GetDocument().IsHTMLDocument() &&
attr_node->name() != attr_node->name().LowerASCII())
UseCounter::Count(
GetDocument(),
WebFeature::
kNonHTMLElementSetAttributeNodeFromHTMLDocumentNameNotLowercase);
SynchronizeAllAttributes();
const UniqueElementData& element_data = EnsureUniqueElementData();
AttributeCollection attributes = element_data.Attributes();
size_t index = attributes.FindIndex(attr_node->GetQualifiedName());
AtomicString local_name;
if (index != kNotFound) {
const Attribute& attr = attributes[index];
// If the name of the ElementData attribute doesn't
// (case-sensitively) match that of the Attr node, record it
// on the Attr so that it can correctly resolve the value on
// the Element.
if (!attr.GetName().Matches(attr_node->GetQualifiedName()))
local_name = attr.LocalName();
if (old_attr_node) {
DetachAttrNodeFromElementWithValue(old_attr_node, attr.Value());
} else {
// FIXME: using attrNode's name rather than the
// Attribute's for the replaced Attr is compatible with
// all but Gecko (and, arguably, the DOM Level1 spec text.)
// Consider switching.
old_attr_node = Attr::Create(GetDocument(), attr_node->GetQualifiedName(),
attr.Value());
}
}
SetAttributeInternal(index, attr_node->GetQualifiedName(), attr_node->value(),
kNotInSynchronizationOfLazyAttribute);
attr_node->AttachToElement(this, local_name);
GetTreeScope().AdoptIfNeeded(*attr_node);
EnsureElementRareData().AddAttr(attr_node);
return old_attr_node;
}
Attr* Element::setAttributeNodeNS(Attr* attr, ExceptionState& exception_state) {
return setAttributeNode(attr, exception_state);
}
Attr* Element::removeAttributeNode(Attr* attr,
ExceptionState& exception_state) {
if (attr->ownerElement() != this) {
exception_state.ThrowDOMException(
kNotFoundError, "The node provided is owned by another element.");
return nullptr;
}
DCHECK_EQ(GetDocument(), attr->GetDocument());
SynchronizeAttribute(attr->GetQualifiedName());
size_t index =
GetElementData()->Attributes().FindIndex(attr->GetQualifiedName());
if (index == kNotFound) {
exception_state.ThrowDOMException(
kNotFoundError, "The attribute was not found on this element.");
return nullptr;
}
DetachAttrNodeAtIndex(attr, index);
return attr;
}
void Element::ParseAttribute(const AttributeModificationParams& params) {
if (params.name == tabindexAttr) {
int tabindex = 0;
if (params.new_value.IsEmpty() ||
!ParseHTMLInteger(params.new_value, tabindex)) {
ClearTabIndexExplicitlyIfNeeded();
} else {
// We only set when value is in integer range.
SetTabIndexExplicitly();
}
} else if (params.name == XMLNames::langAttr) {
PseudoStateChanged(CSSSelector::kPseudoLang);
}
}
bool Element::ParseAttributeName(QualifiedName& out,
const AtomicString& namespace_uri,
const AtomicString& qualified_name,
ExceptionState& exception_state) {
AtomicString prefix, local_name;
if (!Document::ParseQualifiedName(qualified_name, prefix, local_name,
exception_state))
return false;
DCHECK(!exception_state.HadException());
QualifiedName q_name(prefix, local_name, namespace_uri);
if (!Document::HasValidNamespaceForAttributes(q_name)) {
exception_state.ThrowDOMException(
kNamespaceError,
"'" + namespace_uri + "' is an invalid namespace for attributes.");
return false;
}
out = q_name;
return true;
}
void Element::setAttributeNS(const AtomicString& namespace_uri,
const AtomicString& qualified_name,
const AtomicString& value,
ExceptionState& exception_state) {
QualifiedName parsed_name = g_any_name;
if (!ParseAttributeName(parsed_name, namespace_uri, qualified_name,
exception_state))
return;
setAttribute(parsed_name, value);
}
void Element::RemoveAttributeInternal(
size_t index,
SynchronizationOfLazyAttribute in_synchronization_of_lazy_attribute) {
MutableAttributeCollection attributes =
EnsureUniqueElementData().Attributes();
SECURITY_DCHECK(index < attributes.size());
QualifiedName name = attributes[index].GetName();
AtomicString value_being_removed = attributes[index].Value();
if (!in_synchronization_of_lazy_attribute) {
if (!value_being_removed.IsNull()) {
WillModifyAttribute(name, value_being_removed, g_null_atom);
} else if (GetCustomElementState() == CustomElementState::kCustom) {
// This would otherwise be enqueued by willModifyAttribute.
CustomElement::EnqueueAttributeChangedCallback(
this, name, value_being_removed, g_null_atom);
}
}
if (Attr* attr_node = AttrIfExists(name))
DetachAttrNodeFromElementWithValue(attr_node, attributes[index].Value());
attributes.Remove(index);
if (!in_synchronization_of_lazy_attribute)
DidRemoveAttribute(name, value_being_removed);
}
void Element::AppendAttributeInternal(
const QualifiedName& name,
const AtomicString& value,
SynchronizationOfLazyAttribute in_synchronization_of_lazy_attribute) {
if (!in_synchronization_of_lazy_attribute)
WillModifyAttribute(name, g_null_atom, value);
EnsureUniqueElementData().Attributes().Append(name, value);
if (!in_synchronization_of_lazy_attribute)
DidAddAttribute(name, value);
}
void Element::removeAttribute(const AtomicString& name) {
if (!GetElementData())
return;
AtomicString local_name = LowercaseIfNecessary(name);
size_t index = GetElementData()->Attributes().FindIndex(local_name);
if (index == kNotFound) {
if (UNLIKELY(local_name == styleAttr) &&
GetElementData()->style_attribute_is_dirty_ && IsStyledElement())
RemoveAllInlineStyleProperties();
return;
}
RemoveAttributeInternal(index, kNotInSynchronizationOfLazyAttribute);
}
void Element::removeAttributeNS(const AtomicString& namespace_uri,
const AtomicString& local_name) {
removeAttribute(QualifiedName(g_null_atom, local_name, namespace_uri));
}
Attr* Element::getAttributeNode(const AtomicString& local_name) {
if (!GetElementData())
return nullptr;
SynchronizeAttribute(local_name);
const Attribute* attribute =
GetElementData()->Attributes().Find(LowercaseIfNecessary(local_name));
if (!attribute)
return nullptr;
return EnsureAttr(attribute->GetName());
}
Attr* Element::getAttributeNodeNS(const AtomicString& namespace_uri,
const AtomicString& local_name) {
if (!GetElementData())
return nullptr;
QualifiedName q_name(g_null_atom, local_name, namespace_uri);
SynchronizeAttribute(q_name);
const Attribute* attribute = GetElementData()->Attributes().Find(q_name);
if (!attribute)
return nullptr;
return EnsureAttr(attribute->GetName());
}
bool Element::hasAttribute(const AtomicString& local_name) const {
if (!GetElementData())
return false;
SynchronizeAttribute(local_name);
return GetElementData()->Attributes().FindIndex(
LowercaseIfNecessary(local_name)) != kNotFound;
}
bool Element::hasAttributeNS(const AtomicString& namespace_uri,
const AtomicString& local_name) const {
if (!GetElementData())
return false;
QualifiedName q_name(g_null_atom, local_name, namespace_uri);
SynchronizeAttribute(q_name);
return GetElementData()->Attributes().Find(q_name);
}
void Element::focus(const FocusParams& params) {
if (!isConnected())
return;
if (GetDocument().FocusedElement() == this)
return;
if (!GetDocument().IsActive())
return;
if (IsFrameOwnerElement() &&
ToHTMLFrameOwnerElement(this)->contentDocument() &&
ToHTMLFrameOwnerElement(this)->contentDocument()->UnloadStarted())
return;
GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(this);
if (!IsFocusable())
return;
if (AuthorShadowRoot() && AuthorShadowRoot()->delegatesFocus()) {
if (IsShadowIncludingInclusiveAncestorOf(GetDocument().FocusedElement()))
return;
// Slide the focus to its inner node.
Element* found = GetDocument()
.GetPage()
->GetFocusController()
.FindFocusableElementInShadowHost(*this);
if (found && IsShadowIncludingInclusiveAncestorOf(found)) {
found->focus(FocusParams(SelectionBehaviorOnFocus::kReset,
kWebFocusTypeForward, nullptr));
return;
}
}
if (!GetDocument().GetPage()->GetFocusController().SetFocusedElement(
this, GetDocument().GetFrame(), params))
return;
if (GetDocument().FocusedElement() == this &&
GetDocument().GetFrame()->HasReceivedUserGesture()) {
// Bring up the keyboard in the context of anything triggered by a user
// gesture. Since tracking that across arbitrary boundaries (eg.
// animations) is difficult, for now we match IE's heuristic and bring
// up the keyboard if there's been any gesture since load.
GetDocument()
.GetPage()
->GetChromeClient()
.ShowVirtualKeyboardOnElementFocus(*GetDocument().GetFrame());
}
}
void Element::UpdateFocusAppearance(
SelectionBehaviorOnFocus selection_behavior) {
if (selection_behavior == SelectionBehaviorOnFocus::kNone)
return;
if (IsRootEditableElement(*this)) {
LocalFrame* frame = GetDocument().GetFrame();
if (!frame)
return;
// When focusing an editable element in an iframe, don't reset the selection
// if it already contains a selection.
if (this == frame->Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated()
.RootEditableElement())
return;
// FIXME: We should restore the previous selection if there is one.
// Passing DoNotSetFocus as this function is called after
// FocusController::setFocusedElement() and we don't want to change the
// focus to a new Element.
frame->Selection().SetSelection(
SelectionInDOMTree::Builder()
.Collapse(FirstPositionInOrBeforeNode(this))
.Build(),
FrameSelection::kCloseTyping | FrameSelection::kClearTypingStyle |
FrameSelection::kDoNotSetFocus);
frame->Selection().RevealSelection();
} else if (GetLayoutObject() &&
!GetLayoutObject()->IsLayoutEmbeddedContent()) {
GetLayoutObject()->ScrollRectToVisible(BoundingBox());
}
}
void Element::blur() {
CancelFocusAppearanceUpdate();
if (AdjustedFocusedElementInTreeScope() == this) {
Document& doc = GetDocument();
if (doc.GetPage())
doc.GetPage()->GetFocusController().SetFocusedElement(0, doc.GetFrame());
else
doc.ClearFocusedElement();
}
}
bool Element::SupportsFocus() const {
// FIXME: supportsFocus() can be called when layout is not up to date.
// Logic that deals with the layoutObject should be moved to
// layoutObjectIsFocusable().
// But supportsFocus must return true when the element is editable, or else
// it won't be focusable. Furthermore, supportsFocus cannot just return true
// always or else tabIndex() will change for all HTML elements.
return HasElementFlag(kTabIndexWasSetExplicitly) ||
IsRootEditableElement(*this) ||
(IsShadowHost(this) && AuthorShadowRoot() &&
AuthorShadowRoot()->delegatesFocus()) ||
SupportsSpatialNavigationFocus();
}
bool Element::SupportsSpatialNavigationFocus() const {
// This function checks whether the element satisfies the extended criteria
// for the element to be focusable, introduced by spatial navigation feature,
// i.e. checks if click or keyboard event handler is specified.
// This is the way to make it possible to navigate to (focus) elements
// which web designer meant for being active (made them respond to click
// events).
if (!IsSpatialNavigationEnabled(GetDocument().GetFrame()) ||
SpatialNavigationIgnoresEventHandlers(GetDocument().GetFrame()))
return false;
if (HasEventListeners(EventTypeNames::click) ||
HasEventListeners(EventTypeNames::keydown) ||
HasEventListeners(EventTypeNames::keypress) ||
HasEventListeners(EventTypeNames::keyup))
return true;
if (!IsSVGElement())
return false;
return (HasEventListeners(EventTypeNames::focus) ||
HasEventListeners(EventTypeNames::blur) ||
HasEventListeners(EventTypeNames::focusin) ||
HasEventListeners(EventTypeNames::focusout));
}
bool Element::IsFocusable() const {
// Style cannot be cleared out for non-active documents, so in that case the
// needsLayoutTreeUpdateForNode check is invalid.
DCHECK(!GetDocument().IsActive() ||
!GetDocument().NeedsLayoutTreeUpdateForNode(*this));
return isConnected() && SupportsFocus() && !IsInert() &&
LayoutObjectIsFocusable();
}
bool Element::IsKeyboardFocusable() const {
return IsFocusable() && tabIndex() >= 0;
}
bool Element::IsMouseFocusable() const {
return IsFocusable();
}
bool Element::IsFocusedElementInDocument() const {
return this == GetDocument().FocusedElement();
}
Element* Element::AdjustedFocusedElementInTreeScope() const {
return IsInTreeScope() ? ContainingTreeScope().AdjustedFocusedElement()
: nullptr;
}
void Element::DispatchFocusEvent(Element* old_focused_element,
WebFocusType type,
InputDeviceCapabilities* source_capabilities) {
DispatchEvent(FocusEvent::Create(EventTypeNames::focus, false, false,
GetDocument().domWindow(), 0,
old_focused_element, source_capabilities));
}
void Element::DispatchBlurEvent(Element* new_focused_element,
WebFocusType type,
InputDeviceCapabilities* source_capabilities) {
DispatchEvent(FocusEvent::Create(EventTypeNames::blur, false, false,
GetDocument().domWindow(), 0,
new_focused_element, source_capabilities));
}
void Element::DispatchFocusInEvent(
const AtomicString& event_type,
Element* old_focused_element,
WebFocusType,
InputDeviceCapabilities* source_capabilities) {
#if DCHECK_IS_ON()
DCHECK(!EventDispatchForbiddenScope::IsEventDispatchForbidden());
#endif
DCHECK(event_type == EventTypeNames::focusin ||
event_type == EventTypeNames::DOMFocusIn);
DispatchScopedEvent(
FocusEvent::Create(event_type, true, false, GetDocument().domWindow(), 0,
old_focused_element, source_capabilities));
}
void Element::DispatchFocusOutEvent(
const AtomicString& event_type,
Element* new_focused_element,
InputDeviceCapabilities* source_capabilities) {
#if DCHECK_IS_ON()
DCHECK(!EventDispatchForbiddenScope::IsEventDispatchForbidden());
#endif
DCHECK(event_type == EventTypeNames::focusout ||
event_type == EventTypeNames::DOMFocusOut);
DispatchScopedEvent(
FocusEvent::Create(event_type, true, false, GetDocument().domWindow(), 0,
new_focused_element, source_capabilities));
}
String Element::innerHTML() const {
return CreateMarkup(this, kChildrenOnly);
}
String Element::outerHTML() const {
return CreateMarkup(this);
}
void Element::setInnerHTML(const String& html,
ExceptionState& exception_state) {
probe::breakableLocation(&GetDocument(), "Element.setInnerHTML");
if (DocumentFragment* fragment = CreateFragmentForInnerOuterHTML(
html, this, kAllowScriptingContent, "innerHTML", exception_state)) {
ContainerNode* container = this;
if (isHTMLTemplateElement(*this))
container = toHTMLTemplateElement(this)->content();
ReplaceChildrenWithFragment(container, fragment, exception_state);
}
}
void Element::setOuterHTML(const String& html,
ExceptionState& exception_state) {
Node* p = parentNode();
if (!p) {
exception_state.ThrowDOMException(kNoModificationAllowedError,
"This element has no parent node.");
return;
}
if (!p->IsElementNode()) {
exception_state.ThrowDOMException(kNoModificationAllowedError,
"This element's parent is of type '" +
p->nodeName() +
"', which is not an element node.");
return;
}
Element* parent = ToElement(p);
Node* prev = previousSibling();
Node* next = nextSibling();
DocumentFragment* fragment = CreateFragmentForInnerOuterHTML(
html, parent, kAllowScriptingContent, "outerHTML", exception_state);
if (exception_state.HadException())
return;
parent->ReplaceChild(fragment, this, exception_state);
Node* node = next ? next->previousSibling() : nullptr;
if (!exception_state.HadException() && node && node->IsTextNode())
MergeWithNextTextNode(ToText(node), exception_state);
if (!exception_state.HadException() && prev && prev->IsTextNode())
MergeWithNextTextNode(ToText(prev), exception_state);
}
Node* Element::InsertAdjacent(const String& where,
Node* new_child,
ExceptionState& exception_state) {
if (DeprecatedEqualIgnoringCase(where, "beforeBegin")) {
if (ContainerNode* parent = this->parentNode()) {
parent->InsertBefore(new_child, this, exception_state);
if (!exception_state.HadException())
return new_child;
}
return nullptr;
}
if (DeprecatedEqualIgnoringCase(where, "afterBegin")) {
InsertBefore(new_child, firstChild(), exception_state);
return exception_state.HadException() ? nullptr : new_child;
}
if (DeprecatedEqualIgnoringCase(where, "beforeEnd")) {
AppendChild(new_child, exception_state);
return exception_state.HadException() ? nullptr : new_child;
}
if (DeprecatedEqualIgnoringCase(where, "afterEnd")) {
if (ContainerNode* parent = this->parentNode()) {
parent->InsertBefore(new_child, nextSibling(), exception_state);
if (!exception_state.HadException())
return new_child;
}
return nullptr;
}
exception_state.ThrowDOMException(
kSyntaxError, "The value provided ('" + where +
"') is not one of 'beforeBegin', 'afterBegin', "
"'beforeEnd', or 'afterEnd'.");
return nullptr;
}
ElementIntersectionObserverData* Element::IntersectionObserverData() const {
if (HasRareData())
return GetElementRareData()->IntersectionObserverData();
return nullptr;
}
ElementIntersectionObserverData& Element::EnsureIntersectionObserverData() {
return EnsureElementRareData().EnsureIntersectionObserverData();
}
HeapHashMap<Member<ResizeObserver>, Member<ResizeObservation>>*
Element::ResizeObserverData() const {
if (HasRareData())
return GetElementRareData()->ResizeObserverData();
return nullptr;
}
HeapHashMap<Member<ResizeObserver>, Member<ResizeObservation>>&
Element::EnsureResizeObserverData() {
return EnsureElementRareData().EnsureResizeObserverData();
}
void Element::SetNeedsResizeObserverUpdate() {
if (auto* data = ResizeObserverData()) {
for (auto& observation : data->Values())
observation->ElementSizeChanged();
}
}
// Step 1 of http://domparsing.spec.whatwg.org/#insertadjacenthtml()
static Element* ContextElementForInsertion(const String& where,
Element* element,
ExceptionState& exception_state) {
if (DeprecatedEqualIgnoringCase(where, "beforeBegin") ||
DeprecatedEqualIgnoringCase(where, "afterEnd")) {
Element* parent = element->parentElement();
if (!parent) {
exception_state.ThrowDOMException(kNoModificationAllowedError,
"The element has no parent.");
return nullptr;
}
return parent;
}
if (DeprecatedEqualIgnoringCase(where, "afterBegin") ||
DeprecatedEqualIgnoringCase(where, "beforeEnd"))
return element;
exception_state.ThrowDOMException(
kSyntaxError, "The value provided ('" + where +
"') is not one of 'beforeBegin', 'afterBegin', "
"'beforeEnd', or 'afterEnd'.");
return nullptr;
}
Element* Element::insertAdjacentElement(const String& where,
Element* new_child,
ExceptionState& exception_state) {
Node* return_value = InsertAdjacent(where, new_child, exception_state);
return ToElement(return_value);
}
void Element::insertAdjacentText(const String& where,
const String& text,
ExceptionState& exception_state) {
InsertAdjacent(where, GetDocument().createTextNode(text), exception_state);
}
void Element::insertAdjacentHTML(const String& where,
const String& markup,
ExceptionState& exception_state) {
Element* context_element =
ContextElementForInsertion(where, this, exception_state);
if (!context_element)
return;
DocumentFragment* fragment = CreateFragmentForInnerOuterHTML(
markup, context_element, kAllowScriptingContent, "insertAdjacentHTML",
exception_state);
if (!fragment)
return;
InsertAdjacent(where, fragment, exception_state);
}
void Element::setPointerCapture(int pointer_id,
ExceptionState& exception_state) {
if (GetDocument().GetFrame()) {
if (!GetDocument().GetFrame()->GetEventHandler().IsPointerEventActive(
pointer_id)) {
exception_state.ThrowDOMException(kInvalidPointerId, "InvalidPointerId");
} else if (!isConnected() ||
(GetDocument().GetPage() && GetDocument()
.GetPage()
->GetPointerLockController()
.GetElement())) {
exception_state.ThrowDOMException(kInvalidStateError,
"InvalidStateError");
} else {
GetDocument().GetFrame()->GetEventHandler().SetPointerCapture(pointer_id,
this);
}
}
}
void Element::releasePointerCapture(int pointer_id,
ExceptionState& exception_state) {
if (GetDocument().GetFrame()) {
if (!GetDocument().GetFrame()->GetEventHandler().IsPointerEventActive(
pointer_id))
exception_state.ThrowDOMException(kInvalidPointerId, "InvalidPointerId");
else
GetDocument().GetFrame()->GetEventHandler().ReleasePointerCapture(
pointer_id, this);
}
}
bool Element::hasPointerCapture(int pointer_id) const {
return GetDocument().GetFrame() &&
GetDocument().GetFrame()->GetEventHandler().HasPointerCapture(
pointer_id, this);
}
bool Element::HasProcessedPointerCapture(int pointer_id) const {
return GetDocument().GetFrame() &&
GetDocument().GetFrame()->GetEventHandler().HasProcessedPointerCapture(
pointer_id, this);
}
String Element::innerText() {
// We need to update layout, since plainText uses line boxes in the layout
// tree.
GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(this);
if (!GetLayoutObject())
return textContent(true);
return PlainText(
EphemeralRange::RangeOfContents(*this),
TextIteratorBehavior::Builder().SetForInnerText(true).Build());
}
String Element::outerText() {
// Getting outerText is the same as getting innerText, only
// setting is different. You would think this should get the plain
// text for the outer range, but this is wrong, <br> for instance
// would return different values for inner and outer text by such
// a rule, but it doesn't in WinIE, and we want to match that.
return innerText();
}
String Element::TextFromChildren() {
Text* first_text_node = 0;
bool found_multiple_text_nodes = false;
unsigned total_length = 0;
for (Node* child = firstChild(); child; child = child->nextSibling()) {
if (!child->IsTextNode())
continue;
Text* text = ToText(child);
if (!first_text_node)
first_text_node = text;
else
found_multiple_text_nodes = true;
unsigned length = text->data().length();
if (length > std::numeric_limits<unsigned>::max() - total_length)
return g_empty_string;
total_length += length;
}
if (!first_text_node)
return g_empty_string;
if (first_text_node && !found_multiple_text_nodes) {
first_text_node->Atomize();
return first_text_node->data();
}
StringBuilder content;
content.ReserveCapacity(total_length);
for (Node* child = first_text_node; child; child = child->nextSibling()) {
if (!child->IsTextNode())
continue;
content.Append(ToText(child)->data());
}
DCHECK_EQ(content.length(), total_length);
return content.ToString();
}
const AtomicString& Element::ShadowPseudoId() const {
if (ShadowRoot* root = ContainingShadowRoot()) {
if (root->GetType() == ShadowRootType::kUserAgent)
return FastGetAttribute(pseudoAttr);
}
return g_null_atom;
}
void Element::SetShadowPseudoId(const AtomicString& id) {
DCHECK(CSSSelector::ParsePseudoType(id, false) ==
CSSSelector::kPseudoWebKitCustomElement ||
CSSSelector::ParsePseudoType(id, false) ==
CSSSelector::kPseudoBlinkInternalElement);
setAttribute(pseudoAttr, id);
}
bool Element::IsInDescendantTreeOf(const Element* shadow_host) const {
DCHECK(shadow_host);
DCHECK(IsShadowHost(shadow_host));
for (const Element* ancestor_shadow_host = this->OwnerShadowHost();
ancestor_shadow_host;
ancestor_shadow_host = ancestor_shadow_host->OwnerShadowHost()) {
if (ancestor_shadow_host == shadow_host)
return true;
}
return false;
}
LayoutSize Element::MinimumSizeForResizing() const {
return HasRareData() ? GetElementRareData()->MinimumSizeForResizing()
: DefaultMinimumSizeForResizing();
}
void Element::SetMinimumSizeForResizing(const LayoutSize& size) {
if (!HasRareData() && size == DefaultMinimumSizeForResizing())
return;
EnsureElementRareData().SetMinimumSizeForResizing(size);
}
const ComputedStyle* Element::EnsureComputedStyle(
PseudoId pseudo_element_specifier) {
if (PseudoElement* element = GetPseudoElement(pseudo_element_specifier))
return element->EnsureComputedStyle();
if (!InActiveDocument()) {
// FIXME: Try to do better than this. Ensure that styleForElement() works
// for elements that are not in the document tree and figure out when to
// destroy the computed style for such elements.
return nullptr;
}
// FIXME: Find and use the layoutObject from the pseudo element instead of the
// actual element so that the 'length' properties, which are only known by the
// layoutObject because it did the layout, will be correct and so that the
// values returned for the ":selection" pseudo-element will be correct.
ComputedStyle* element_style = MutableComputedStyle();
if (!element_style) {
ElementRareData& rare_data = EnsureElementRareData();
if (!rare_data.GetComputedStyle())
rare_data.SetComputedStyle(
GetDocument().StyleForElementIgnoringPendingStylesheets(this));
element_style = rare_data.GetComputedStyle();
}
if (!pseudo_element_specifier)
return element_style;
if (ComputedStyle* pseudo_element_style =
element_style->GetCachedPseudoStyle(pseudo_element_specifier))
return pseudo_element_style;
const ComputedStyle* layout_parent_style = element_style;
if (HasDisplayContentsStyle()) {
LayoutObject* parent_layout_object =
LayoutTreeBuilderTraversal::ParentLayoutObject(*this);
if (parent_layout_object)
layout_parent_style = parent_layout_object->Style();
}
RefPtr<ComputedStyle> result =
GetDocument().EnsureStyleResolver().PseudoStyleForElement(
this,
PseudoStyleRequest(pseudo_element_specifier,
PseudoStyleRequest::kForComputedStyle),
element_style, layout_parent_style);
DCHECK(result);
return element_style->AddCachedPseudoStyle(std::move(result));
}
const ComputedStyle* Element::NonLayoutObjectComputedStyle() const {
if (GetLayoutObject() || !HasRareData())
return nullptr;
return GetElementRareData()->GetComputedStyle();
}
bool Element::HasDisplayContentsStyle() const {
if (const ComputedStyle* style = NonLayoutObjectComputedStyle())
return style->Display() == EDisplay::kContents;
return false;
}
bool Element::ShouldStoreNonLayoutObjectComputedStyle(
const ComputedStyle& style) const {
#if DCHECK_IS_ON()
if (style.Display() == EDisplay::kContents)
DCHECK(!GetLayoutObject());
#endif
return style.Display() == EDisplay::kContents ||
isHTMLOptGroupElement(*this) || isHTMLOptionElement(*this);
}
void Element::StoreNonLayoutObjectComputedStyle(
PassRefPtr<ComputedStyle> style) {
DCHECK(style);
DCHECK(ShouldStoreNonLayoutObjectComputedStyle(*style));
EnsureElementRareData().SetComputedStyle(std::move(style));
}
AtomicString Element::ComputeInheritedLanguage() const {
const Node* n = this;
AtomicString value;
// The language property is inherited, so we iterate over the parents to find
// the first language.
do {
if (n->IsElementNode()) {
if (const ElementData* element_data = ToElement(n)->GetElementData()) {
AttributeCollection attributes = element_data->Attributes();
// Spec: xml:lang takes precedence -- http://www.w3.org/TR/xhtml1/#C_7
if (const Attribute* attribute = attributes.Find(XMLNames::langAttr))
value = attribute->Value();
else if (const Attribute* attribute =
attributes.Find(HTMLNames::langAttr))
value = attribute->Value();
}
} else if (n->IsDocumentNode()) {
// checking the MIME content-language
value = ToDocument(n)->ContentLanguage();
}
n = n->ParentOrShadowHostNode();
} while (n && value.IsNull());
return value;
}
Locale& Element::GetLocale() const {
return GetDocument().GetCachedLocale(ComputeInheritedLanguage());
}
void Element::CancelFocusAppearanceUpdate() {
if (GetDocument().FocusedElement() == this)
GetDocument().CancelFocusAppearanceUpdate();
}
void Element::UpdatePseudoElement(PseudoId pseudo_id,
StyleRecalcChange change) {
DCHECK(!NeedsStyleRecalc());
PseudoElement* element = GetPseudoElement(pseudo_id);
if (element && (change == kUpdatePseudoElements ||
element->ShouldCallRecalcStyle(change))) {
if (pseudo_id == kPseudoIdFirstLetter && UpdateFirstLetter(element))
return;
// Need to clear the cached style if the PseudoElement wants a recalc so it
// computes a new style.
if (element->NeedsStyleRecalc())
MutableComputedStyle()->RemoveCachedPseudoStyle(pseudo_id);
// PseudoElement styles hang off their parent element's style so if we
// needed a style recalc we should Force one on the pseudo. FIXME: We
// should figure out the right text sibling to pass.
element->RecalcStyle(change == kUpdatePseudoElements ? kForce : change);
// Wait until our parent is not displayed or
// pseudoElementLayoutObjectIsNeeded is false, otherwise we could
// continuously create and destroy PseudoElements when
// LayoutObject::isChildAllowed on our parent returns false for the
// PseudoElement's layoutObject for each style recalc.
if (!CanGeneratePseudoElement(pseudo_id) ||
!PseudoElementLayoutObjectIsNeeded(
PseudoStyle(PseudoStyleRequest(pseudo_id))))
GetElementRareData()->SetPseudoElement(pseudo_id, nullptr);
} else if (pseudo_id == kPseudoIdFirstLetter && element &&
change >= kUpdatePseudoElements &&
!FirstLetterPseudoElement::FirstLetterTextLayoutObject(*element)) {
// This can happen if we change to a float, for example. We need to cleanup
// the first-letter pseudoElement and then fix the text of the original
// remaining text layoutObject. This can be seen in Test 7 of
// fast/css/first-letter-removed-added.html
GetElementRareData()->SetPseudoElement(pseudo_id, nullptr);
} else if (change >= kUpdatePseudoElements) {
CreatePseudoElementIfNeeded(pseudo_id);
}
}
// If we're updating first letter, and the current first letter layoutObject
// is not the same as the one we're currently using we need to re-create
// the first letter layoutObject.
bool Element::UpdateFirstLetter(Element* element) {
LayoutObject* remaining_text_layout_object =
FirstLetterPseudoElement::FirstLetterTextLayoutObject(*element);
if (!remaining_text_layout_object ||
remaining_text_layout_object !=
ToFirstLetterPseudoElement(element)->RemainingTextLayoutObject()) {
// We have to clear out the old first letter here because when it is
// disposed it will set the original text back on the remaining text
// layoutObject. If we dispose after creating the new one we will get
// incorrect results due to setting the first letter back.
if (remaining_text_layout_object)
element->ReattachLayoutTree();
else
GetElementRareData()->SetPseudoElement(kPseudoIdFirstLetter, nullptr);
return true;
}
return false;
}
void Element::CreatePseudoElementIfNeeded(PseudoId pseudo_id) {
if (IsPseudoElement())
return;
// Document::ensureStyleResolver is not inlined and shows up on profiles,
// avoid it here.
PseudoElement* element = GetDocument()
.GetStyleEngine()
.EnsureResolver()
.CreatePseudoElementIfNeeded(*this, pseudo_id);
if (!element)
return;
if (pseudo_id == kPseudoIdBackdrop)
GetDocument().AddToTopLayer(element, this);
element->InsertedInto(this);
element->AttachLayoutTree();
probe::pseudoElementCreated(element);
EnsureElementRareData().SetPseudoElement(pseudo_id, element);
}
PseudoElement* Element::GetPseudoElement(PseudoId pseudo_id) const {
return HasRareData() ? GetElementRareData()->GetPseudoElement(pseudo_id)
: nullptr;
}
LayoutObject* Element::PseudoElementLayoutObject(PseudoId pseudo_id) const {
if (PseudoElement* element = GetPseudoElement(pseudo_id))
return element->GetLayoutObject();
return nullptr;
}
ComputedStyle* Element::PseudoStyle(const PseudoStyleRequest& request,
const ComputedStyle* parent_style) {
ComputedStyle* style = MutableComputedStyle();
if (!style || (request.pseudo_id < kFirstInternalPseudoId &&
!style->HasPseudoStyle(request.pseudo_id))) {
return nullptr;
}
if (ComputedStyle* cached = style->GetCachedPseudoStyle(request.pseudo_id))
return cached;
RefPtr<ComputedStyle> result = GetUncachedPseudoStyle(request, parent_style);
if (result)
return style->AddCachedPseudoStyle(std::move(result));
return nullptr;
}
PassRefPtr<ComputedStyle> Element::GetUncachedPseudoStyle(
const PseudoStyleRequest& request,
const ComputedStyle* parent_style) {
const ComputedStyle* style = GetComputedStyle();
const bool is_before_or_after = request.pseudo_id == kPseudoIdBefore ||
request.pseudo_id == kPseudoIdAfter;
DCHECK(style);
DCHECK(!parent_style || !is_before_or_after);
if (is_before_or_after) {
LayoutObject* parent_layout_object = GetLayoutObject();
if (!parent_layout_object && HasDisplayContentsStyle()) {
parent_layout_object =
LayoutTreeBuilderTraversal::ParentLayoutObject(*this);
}
if (!parent_layout_object)
return nullptr;
return GetDocument().EnsureStyleResolver().PseudoStyleForElement(
this, request, style, parent_layout_object->Style());
}
if (!GetLayoutObject())
return nullptr;
if (!parent_style)
parent_style = style;
if (request.pseudo_id == kPseudoIdFirstLineInherited) {
RefPtr<ComputedStyle> result =
GetDocument().EnsureStyleResolver().StyleForElement(
this, parent_style, parent_style, kDisallowStyleSharing);
result->SetStyleType(kPseudoIdFirstLineInherited);
return result.Release();
}
return GetDocument().EnsureStyleResolver().PseudoStyleForElement(
this, request, parent_style, parent_style);
}
// For display: contents elements, we still need to generate ::before and
// ::after, but the rest of the pseudo-elements should only be used for elements
// with an actual layout object.
bool Element::CanGeneratePseudoElement(PseudoId pseudo_id) const {
if (HasDisplayContentsStyle())
return pseudo_id == kPseudoIdBefore || pseudo_id == kPseudoIdAfter;
return !!GetLayoutObject();
}
bool Element::matches(const AtomicString& selectors,
ExceptionState& exception_state) {
SelectorQuery* selector_query = GetDocument().GetSelectorQueryCache().Add(
selectors, GetDocument(), exception_state);
if (!selector_query)
return false;
return selector_query->Matches(*this);
}
Element* Element::closest(const AtomicString& selectors,
ExceptionState& exception_state) {
SelectorQuery* selector_query = GetDocument().GetSelectorQueryCache().Add(
selectors, GetDocument(), exception_state);
if (!selector_query)
return nullptr;
return selector_query->Closest(*this);
}
DOMTokenList& Element::classList() {
ElementRareData& rare_data = EnsureElementRareData();
if (!rare_data.GetClassList()) {
DOMTokenList* class_list = DOMTokenList::Create(*this, classAttr);
class_list->DidUpdateAttributeValue(g_null_atom, getAttribute(classAttr));
rare_data.SetClassList(class_list);
}
return *rare_data.GetClassList();
}
DOMStringMap& Element::dataset() {
ElementRareData& rare_data = EnsureElementRareData();
if (!rare_data.Dataset())
rare_data.SetDataset(DatasetDOMStringMap::Create(this));
return *rare_data.Dataset();
}
KURL Element::HrefURL() const {
// FIXME: These all have href() or url(), but no common super class. Why
// doesn't <link> implement URLUtils?
if (isHTMLAnchorElement(*this) || isHTMLAreaElement(*this) ||
isHTMLLinkElement(*this))
return GetURLAttribute(hrefAttr);
if (isSVGAElement(*this))
return toSVGAElement(*this).LegacyHrefURL(GetDocument());
return KURL();
}
KURL Element::GetURLAttribute(const QualifiedName& name) const {
#if DCHECK_IS_ON()
if (GetElementData()) {
if (const Attribute* attribute = Attributes().Find(name))
DCHECK(IsURLAttribute(*attribute));
}
#endif
return GetDocument().CompleteURL(
StripLeadingAndTrailingHTMLSpaces(getAttribute(name)));
}
KURL Element::GetNonEmptyURLAttribute(const QualifiedName& name) const {
#if DCHECK_IS_ON()
if (GetElementData()) {
if (const Attribute* attribute = Attributes().Find(name))
DCHECK(IsURLAttribute(*attribute));
}
#endif
String value = StripLeadingAndTrailingHTMLSpaces(getAttribute(name));
if (value.IsEmpty())
return KURL();
return GetDocument().CompleteURL(value);
}
int Element::GetIntegralAttribute(const QualifiedName& attribute_name) const {
int integral_value = 0;
ParseHTMLInteger(getAttribute(attribute_name), integral_value);
return integral_value;
}
void Element::SetIntegralAttribute(const QualifiedName& attribute_name,
int value) {
setAttribute(attribute_name, AtomicString::Number(value));
}
void Element::SetUnsignedIntegralAttribute(const QualifiedName& attribute_name,
unsigned value) {
// Range restrictions are enforced for unsigned IDL attributes that
// reflect content attributes,
// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#reflecting-content-attributes-in-idl-attributes
if (value > 0x7fffffffu)
value = 0;
setAttribute(attribute_name, AtomicString::Number(value));
}
double Element::GetFloatingPointAttribute(const QualifiedName& attribute_name,
double fallback_value) const {
return ParseToDoubleForNumberType(getAttribute(attribute_name),
fallback_value);
}
void Element::SetFloatingPointAttribute(const QualifiedName& attribute_name,
double value) {
setAttribute(attribute_name, AtomicString::Number(value));
}
void Element::SetContainsFullScreenElement(bool flag) {
SetElementFlag(kContainsFullScreenElement, flag);
GetDocument().GetStyleEngine().EnsureUAStyleForFullscreen();
PseudoStateChanged(CSSSelector::kPseudoFullScreenAncestor);
}
// Unlike Node::parentOrShadowHostElement, this can cross frame boundaries.
static Element* NextAncestorElement(Element* element) {
DCHECK(element);
if (element->ParentOrShadowHostElement())
return element->ParentOrShadowHostElement();
Frame* frame = element->GetDocument().GetFrame();
if (!frame || !frame->Owner())
return nullptr;
// Find the next LocalFrame on the ancestor chain, and return the
// corresponding <iframe> element for the remote child if it exists.
while (frame->Tree().Parent() && frame->Tree().Parent()->IsRemoteFrame())
frame = frame->Tree().Parent();
if (frame->Owner() && frame->Owner()->IsLocal())
return ToHTMLFrameOwnerElement(frame->Owner());
return nullptr;
}
void Element::SetContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(
bool flag) {
for (Element* element = NextAncestorElement(this); element;
element = NextAncestorElement(element))
element->SetContainsFullScreenElement(flag);
}
void Element::SetContainsPersistentVideo(bool value) {
SetElementFlag(kContainsPersistentVideo, value);
PseudoStateChanged(CSSSelector::kPseudoVideoPersistentAncestor);
// In some rare situations, when the persistent video has been removed from
// the tree, part of the tree might still carry the flag.
if (!value && Fullscreen::IsCurrentFullScreenElement(*this)) {
for (Node* node = firstChild(); node;) {
if (!node->IsElementNode() ||
!ToElement(node)->ContainsPersistentVideo()) {
node = node->nextSibling();
break;
}
ToElement(node)->SetContainsPersistentVideo(false);
node = node->firstChild();
}
}
}
void Element::SetIsInTopLayer(bool in_top_layer) {
if (IsInTopLayer() == in_top_layer)
return;
SetElementFlag(kIsInTopLayer, in_top_layer);
// We must ensure a reattach occurs so the layoutObject is inserted in the
// correct sibling order under LayoutView according to its top layer position,
// or in its usual place if not in the top layer.
LazyReattachIfAttached();
}
void Element::requestPointerLock() {
if (GetDocument().GetPage())
GetDocument().GetPage()->GetPointerLockController().RequestPointerLock(
this);
}
SpellcheckAttributeState Element::GetSpellcheckAttributeState() const {
const AtomicString& value = FastGetAttribute(spellcheckAttr);
if (value == g_null_atom)
return kSpellcheckAttributeDefault;
if (DeprecatedEqualIgnoringCase(value, "true") ||
DeprecatedEqualIgnoringCase(value, ""))
return kSpellcheckAttributeTrue;
if (DeprecatedEqualIgnoringCase(value, "false"))
return kSpellcheckAttributeFalse;
return kSpellcheckAttributeDefault;
}
bool Element::IsSpellCheckingEnabled() const {
for (const Element* element = this; element;
element = element->ParentOrShadowHostElement()) {
switch (element->GetSpellcheckAttributeState()) {
case kSpellcheckAttributeTrue:
return true;
case kSpellcheckAttributeFalse:
return false;
case kSpellcheckAttributeDefault:
break;
}
}
if (!GetDocument().GetPage())
return true;
return GetDocument().GetPage()->GetSettings().GetSpellCheckEnabledByDefault();
}
#if DCHECK_IS_ON()
bool Element::FastAttributeLookupAllowed(const QualifiedName& name) const {
if (name == HTMLNames::styleAttr)
return false;
if (IsSVGElement())
return !ToSVGElement(this)->IsAnimatableAttribute(name);
return true;
}
#endif
#ifdef DUMP_NODE_STATISTICS
bool Element::HasNamedNodeMap() const {
return HasRareData() && GetElementRareData()->AttributeMap();
}
#endif
inline void Element::UpdateName(const AtomicString& old_name,
const AtomicString& new_name) {
if (!IsInDocumentTree())
return;
if (old_name == new_name)
return;
if (ShouldRegisterAsNamedItem())
UpdateNamedItemRegistration(old_name, new_name);
}
inline void Element::UpdateId(const AtomicString& old_id,
const AtomicString& new_id) {
if (!IsInTreeScope())
return;
if (old_id == new_id)
return;
UpdateId(ContainingTreeScope(), old_id, new_id);
}
inline void Element::UpdateId(TreeScope& scope,
const AtomicString& old_id,
const AtomicString& new_id) {
DCHECK(IsInTreeScope());
DCHECK_NE(old_id, new_id);
if (!old_id.IsEmpty())
scope.RemoveElementById(old_id, this);
if (!new_id.IsEmpty())
scope.AddElementById(new_id, this);
if (ShouldRegisterAsExtraNamedItem())
UpdateExtraNamedItemRegistration(old_id, new_id);
}
void Element::WillModifyAttribute(const QualifiedName& name,
const AtomicString& old_value,
const AtomicString& new_value) {
if (name == HTMLNames::nameAttr) {
UpdateName(old_value, new_value);
}
if (GetCustomElementState() == CustomElementState::kCustom) {
CustomElement::EnqueueAttributeChangedCallback(this, name, old_value,
new_value);
}
if (old_value != new_value) {
GetDocument().GetStyleEngine().AttributeChangedForElement(name, *this);
if (IsUpgradedV0CustomElement()) {
V0CustomElement::AttributeDidChange(this, name.LocalName(), old_value,
new_value);
}
}
if (MutationObserverInterestGroup* recipients =
MutationObserverInterestGroup::CreateForAttributesMutation(*this,
name))
recipients->EnqueueMutationRecord(
MutationRecord::CreateAttributes(this, name, old_value));
probe::willModifyDOMAttr(this, old_value, new_value);
}
DISABLE_CFI_PERF
void Element::DidAddAttribute(const QualifiedName& name,
const AtomicString& value) {
if (name == HTMLNames::idAttr)
UpdateId(g_null_atom, value);
AttributeChanged(AttributeModificationParams(
name, g_null_atom, value, AttributeModificationReason::kDirectly));
probe::didModifyDOMAttr(this, name, value);
DispatchSubtreeModifiedEvent();
}
void Element::DidModifyAttribute(const QualifiedName& name,
const AtomicString& old_value,
const AtomicString& new_value) {
if (name == HTMLNames::idAttr)
UpdateId(old_value, new_value);
AttributeChanged(AttributeModificationParams(
name, old_value, new_value, AttributeModificationReason::kDirectly));
probe::didModifyDOMAttr(this, name, new_value);
// Do not dispatch a DOMSubtreeModified event here; see bug 81141.
}
void Element::DidRemoveAttribute(const QualifiedName& name,
const AtomicString& old_value) {
if (name == HTMLNames::idAttr)
UpdateId(old_value, g_null_atom);
AttributeChanged(AttributeModificationParams(
name, old_value, g_null_atom, AttributeModificationReason::kDirectly));
probe::didRemoveDOMAttr(this, name);
DispatchSubtreeModifiedEvent();
}
static bool NeedsURLResolutionForInlineStyle(const Element& element,
const Document& old_document,
const Document& new_document) {
if (old_document == new_document)
return false;
if (old_document.BaseURL() == new_document.BaseURL())
return false;
const StylePropertySet* style = element.InlineStyle();
if (!style)
return false;
for (unsigned i = 0; i < style->PropertyCount(); ++i) {
if (style->PropertyAt(i).Value().MayContainUrl())
return true;
}
return false;
}
static void ReResolveURLsInInlineStyle(const Document& document,
MutableStylePropertySet& style) {
for (unsigned i = 0; i < style.PropertyCount(); ++i) {
const CSSValue& value = style.PropertyAt(i).Value();
if (value.MayContainUrl())
value.ReResolveUrl(document);
}
}
void Element::DidMoveToNewDocument(Document& old_document) {
Node::DidMoveToNewDocument(old_document);
// If the documents differ by quirks mode then they differ by case sensitivity
// for class and id names so we need to go through the attribute change logic
// to pick up the new casing in the ElementData.
if (old_document.InQuirksMode() != GetDocument().InQuirksMode()) {
if (HasID())
SetIdAttribute(GetIdAttribute());
if (HasClass())
setAttribute(HTMLNames::classAttr, GetClassAttribute());
}
if (NeedsURLResolutionForInlineStyle(*this, old_document, GetDocument()))
ReResolveURLsInInlineStyle(GetDocument(), EnsureMutableInlineStyle());
}
void Element::UpdateNamedItemRegistration(const AtomicString& old_name,
const AtomicString& new_name) {
if (!GetDocument().IsHTMLDocument())
return;
if (!old_name.IsEmpty())
ToHTMLDocument(GetDocument()).RemoveNamedItem(old_name);
if (!new_name.IsEmpty())
ToHTMLDocument(GetDocument()).AddNamedItem(new_name);
}
void Element::UpdateExtraNamedItemRegistration(const AtomicString& old_id,
const AtomicString& new_id) {
if (!GetDocument().IsHTMLDocument())
return;
if (!old_id.IsEmpty())
ToHTMLDocument(GetDocument()).RemoveExtraNamedItem(old_id);
if (!new_id.IsEmpty())
ToHTMLDocument(GetDocument()).AddExtraNamedItem(new_id);
}
ScrollOffset Element::SavedLayerScrollOffset() const {
return HasRareData() ? GetElementRareData()->SavedLayerScrollOffset()
: ScrollOffset();
}
void Element::SetSavedLayerScrollOffset(const ScrollOffset& size) {
if (size.IsZero() && !HasRareData())
return;
EnsureElementRareData().SetSavedLayerScrollOffset(size);
}
Attr* Element::AttrIfExists(const QualifiedName& name) {
if (AttrNodeList* attr_node_list = this->GetAttrNodeList()) {
for (const auto& attr : *attr_node_list) {
if (attr->GetQualifiedName().Matches(name))
return attr.Get();
}
}
return nullptr;
}
Attr* Element::EnsureAttr(const QualifiedName& name) {
Attr* attr_node = AttrIfExists(name);
if (!attr_node) {
attr_node = Attr::Create(*this, name);
GetTreeScope().AdoptIfNeeded(*attr_node);
EnsureElementRareData().AddAttr(attr_node);
}
return attr_node;
}
void Element::DetachAttrNodeFromElementWithValue(Attr* attr_node,
const AtomicString& value) {
DCHECK(GetAttrNodeList());
attr_node->DetachFromElementWithValue(value);
AttrNodeList* list = GetAttrNodeList();
size_t index = list->Find(attr_node);
DCHECK_NE(index, kNotFound);
list->erase(index);
if (list->IsEmpty())
RemoveAttrNodeList();
}
void Element::DetachAllAttrNodesFromElement() {
AttrNodeList* list = this->GetAttrNodeList();
if (!list)
return;
AttributeCollection attributes = GetElementData()->Attributes();
for (const Attribute& attr : attributes) {
if (Attr* attr_node = AttrIfExists(attr.GetName()))
attr_node->DetachFromElementWithValue(attr.Value());
}
RemoveAttrNodeList();
}
void Element::WillRecalcStyle(StyleRecalcChange) {
DCHECK(HasCustomStyleCallbacks());
}
void Element::DidRecalcStyle() {
DCHECK(HasCustomStyleCallbacks());
}
PassRefPtr<ComputedStyle> Element::CustomStyleForLayoutObject() {
DCHECK(HasCustomStyleCallbacks());
return nullptr;
}
void Element::CloneAttributesFromElement(const Element& other) {
if (HasRareData())
DetachAllAttrNodesFromElement();
other.SynchronizeAllAttributes();
if (!other.element_data_) {
element_data_.Clear();
return;
}
const AtomicString& old_id = GetIdAttribute();
const AtomicString& new_id = other.GetIdAttribute();
if (!old_id.IsNull() || !new_id.IsNull())
UpdateId(old_id, new_id);
const AtomicString& old_name = GetNameAttribute();
const AtomicString& new_name = other.GetNameAttribute();
if (!old_name.IsNull() || !new_name.IsNull())
UpdateName(old_name, new_name);
// Quirks mode makes class and id not case sensitive. We can't share the
// ElementData if the idForStyleResolution and the className need different
// casing.
bool owner_documents_have_different_case_sensitivity = false;
if (other.HasClass() || other.HasID())
owner_documents_have_different_case_sensitivity =
other.GetDocument().InQuirksMode() != GetDocument().InQuirksMode();
// If 'other' has a mutable ElementData, convert it to an immutable one so we
// can share it between both elements.
// We can only do this if there are no presentation attributes and sharing the
// data won't result in different case sensitivity of class or id.
if (other.element_data_->IsUnique() &&
!owner_documents_have_different_case_sensitivity &&
!other.element_data_->PresentationAttributeStyle())
const_cast<Element&>(other).element_data_ =
ToUniqueElementData(other.element_data_)->MakeShareableCopy();
if (!other.element_data_->IsUnique() &&
!owner_documents_have_different_case_sensitivity &&
!NeedsURLResolutionForInlineStyle(other, other.GetDocument(),
GetDocument()))
element_data_ = other.element_data_;
else
element_data_ = other.element_data_->MakeUniqueCopy();
AttributeCollection attributes = element_data_->Attributes();
for (const Attribute& attr : attributes) {
AttributeChangedFromParserOrByCloning(
attr.GetName(), attr.Value(), AttributeModificationReason::kByCloning);
}
if (other.nonce() != g_null_atom)
setNonce(other.nonce());
}
void Element::CloneDataFromElement(const Element& other) {
CloneAttributesFromElement(other);
CopyNonAttributePropertiesFromElement(other);
}
void Element::CreateUniqueElementData() {
if (!element_data_) {
element_data_ = UniqueElementData::Create();
} else {
DCHECK(!element_data_->IsUnique());
element_data_ = ToShareableElementData(element_data_)->MakeUniqueCopy();
}
}
void Element::SynchronizeStyleAttributeInternal() const {
DCHECK(IsStyledElement());
DCHECK(GetElementData());
DCHECK(GetElementData()->style_attribute_is_dirty_);
GetElementData()->style_attribute_is_dirty_ = false;
const StylePropertySet* inline_style = this->InlineStyle();
const_cast<Element*>(this)->SetSynchronizedLazyAttribute(
styleAttr,
inline_style ? AtomicString(inline_style->AsText()) : g_empty_atom);
}
CSSStyleDeclaration* Element::style() {
if (!IsStyledElement())
return nullptr;
return &EnsureElementRareData().EnsureInlineCSSStyleDeclaration(this);
}
StylePropertyMap* Element::styleMap() {
if (!IsStyledElement())
return nullptr;
return &EnsureElementRareData().EnsureInlineStylePropertyMap(this);
}
MutableStylePropertySet& Element::EnsureMutableInlineStyle() {
DCHECK(IsStyledElement());
Member<StylePropertySet>& inline_style =
EnsureUniqueElementData().inline_style_;
if (!inline_style) {
CSSParserMode mode = (!IsHTMLElement() || GetDocument().InQuirksMode())
? kHTMLQuirksMode
: kHTMLStandardMode;
inline_style = MutableStylePropertySet::Create(mode);
} else if (!inline_style->IsMutable()) {
inline_style = inline_style->MutableCopy();
}
return *ToMutableStylePropertySet(inline_style);
}
void Element::ClearMutableInlineStyleIfEmpty() {
if (EnsureMutableInlineStyle().IsEmpty()) {
EnsureUniqueElementData().inline_style_.Clear();
}
}
inline void Element::SetInlineStyleFromString(
const AtomicString& new_style_string) {
DCHECK(IsStyledElement());
Member<StylePropertySet>& inline_style = GetElementData()->inline_style_;
// Avoid redundant work if we're using shared attribute data with already
// parsed inline style.
if (inline_style && !GetElementData()->IsUnique())
return;
// We reconstruct the property set instead of mutating if there is no CSSOM
// wrapper. This makes wrapperless property sets immutable and so cacheable.
if (inline_style && !inline_style->IsMutable())
inline_style.Clear();
if (!inline_style) {
inline_style =
CSSParser::ParseInlineStyleDeclaration(new_style_string, this);
} else {
DCHECK(inline_style->IsMutable());
static_cast<MutableStylePropertySet*>(inline_style.Get())
->ParseDeclarationList(new_style_string,
GetDocument().ElementSheet().Contents());
}
}
void Element::StyleAttributeChanged(
const AtomicString& new_style_string,
AttributeModificationReason modification_reason) {
DCHECK(IsStyledElement());
WTF::OrdinalNumber start_line_number = WTF::OrdinalNumber::BeforeFirst();
if (GetDocument().GetScriptableDocumentParser() &&
!GetDocument().IsInDocumentWrite())
start_line_number =
GetDocument().GetScriptableDocumentParser()->LineNumber();
if (new_style_string.IsNull()) {
EnsureUniqueElementData().inline_style_.Clear();
} else if (modification_reason == AttributeModificationReason::kByCloning ||
ContentSecurityPolicy::ShouldBypassMainWorld(&GetDocument()) ||
(ContainingShadowRoot() && ContainingShadowRoot()->GetType() ==
ShadowRootType::kUserAgent) ||
GetDocument().GetContentSecurityPolicy()->AllowInlineStyle(
this, GetDocument().Url(), String(), start_line_number,
new_style_string)) {
SetInlineStyleFromString(new_style_string);
}
GetElementData()->style_attribute_is_dirty_ = false;
SetNeedsStyleRecalc(kLocalStyleChange,
StyleChangeReasonForTracing::Create(
StyleChangeReason::kStyleSheetChange));
probe::didInvalidateStyleAttr(this);
}
void Element::InlineStyleChanged() {
DCHECK(IsStyledElement());
SetNeedsStyleRecalc(kLocalStyleChange, StyleChangeReasonForTracing::Create(
StyleChangeReason::kInline));
DCHECK(GetElementData());
GetElementData()->style_attribute_is_dirty_ = true;
probe::didInvalidateStyleAttr(this);
if (MutationObserverInterestGroup* recipients =
MutationObserverInterestGroup::CreateForAttributesMutation(
*this, styleAttr)) {
// We don't use getAttribute() here to get a style attribute value
// before the change.
AtomicString old_value;
if (const Attribute* attribute =
GetElementData()->Attributes().Find(styleAttr))
old_value = attribute->Value();
recipients->EnqueueMutationRecord(
MutationRecord::CreateAttributes(this, styleAttr, old_value));
// Need to synchronize every time so that following MutationRecords will
// have correct oldValues.
SynchronizeAttribute(styleAttr);
}
}
void Element::SetInlineStyleProperty(CSSPropertyID property_id,
CSSValueID identifier,
bool important) {
SetInlineStyleProperty(property_id, CSSIdentifierValue::Create(identifier),
important);
}
void Element::SetInlineStyleProperty(CSSPropertyID property_id,
double value,
CSSPrimitiveValue::UnitType unit,
bool important) {
SetInlineStyleProperty(property_id, CSSPrimitiveValue::Create(value, unit),
important);
}
void Element::SetInlineStyleProperty(CSSPropertyID property_id,
const CSSValue* value,
bool important) {
DCHECK(IsStyledElement());
EnsureMutableInlineStyle().SetProperty(property_id, *value, important);
InlineStyleChanged();
}
bool Element::SetInlineStyleProperty(CSSPropertyID property_id,
const String& value,
bool important) {
DCHECK(IsStyledElement());
bool did_change = EnsureMutableInlineStyle()
.SetProperty(property_id, value, important,
GetDocument().ElementSheet().Contents())
.did_change;
if (did_change)
InlineStyleChanged();
return did_change;
}
bool Element::RemoveInlineStyleProperty(CSSPropertyID property_id) {
DCHECK(IsStyledElement());
if (!InlineStyle())
return false;
bool did_change = EnsureMutableInlineStyle().RemoveProperty(property_id);
if (did_change)
InlineStyleChanged();
return did_change;
}
void Element::RemoveAllInlineStyleProperties() {
DCHECK(IsStyledElement());
if (!InlineStyle())
return;
EnsureMutableInlineStyle().Clear();
InlineStyleChanged();
}
void Element::UpdatePresentationAttributeStyle() {
SynchronizeAllAttributes();
// ShareableElementData doesn't store presentation attribute style, so make
// sure we have a UniqueElementData.
UniqueElementData& element_data = EnsureUniqueElementData();
element_data.presentation_attribute_style_is_dirty_ = false;
element_data.presentation_attribute_style_ =
ComputePresentationAttributeStyle(*this);
}
void Element::AddPropertyToPresentationAttributeStyle(
MutableStylePropertySet* style,
CSSPropertyID property_id,
CSSValueID identifier) {
DCHECK(IsStyledElement());
style->SetProperty(property_id, *CSSIdentifierValue::Create(identifier));
}
void Element::AddPropertyToPresentationAttributeStyle(
MutableStylePropertySet* style,
CSSPropertyID property_id,
double value,
CSSPrimitiveValue::UnitType unit) {
DCHECK(IsStyledElement());
style->SetProperty(property_id, *CSSPrimitiveValue::Create(value, unit));
}
void Element::AddPropertyToPresentationAttributeStyle(
MutableStylePropertySet* style,
CSSPropertyID property_id,
const String& value) {
DCHECK(IsStyledElement());
style->SetProperty(property_id, value, false);
}
void Element::AddPropertyToPresentationAttributeStyle(
MutableStylePropertySet* style,
CSSPropertyID property_id,
const CSSValue* value) {
DCHECK(IsStyledElement());
style->SetProperty(property_id, *value);
}
bool Element::SupportsStyleSharing() const {
if (!RuntimeEnabledFeatures::StyleSharingEnabled())
return false;
if (!IsStyledElement() || !ParentOrShadowHostElement())
return false;
// If the element has inline style it is probably unique.
if (InlineStyle())
return false;
if (IsSVGElement() && ToSVGElement(this)->AnimatedSMILStyleProperties())
return false;
// Ids stop style sharing if they show up in the stylesheets.
if (HasID() &&
GetDocument().GetStyleEngine().HasRulesForId(IdForStyleResolution()))
return false;
// :active and :hover elements always make a chain towards the document node
// and no siblings or cousins will have the same state. There's also only one
// :focus element per scope so we don't need to attempt to share.
if (IsUserActionElement())
return false;
if (!ParentOrShadowHostElement()->ChildrenSupportStyleSharing())
return false;
if (this == GetDocument().CssTarget())
return false;
if (IsHTMLElement() && ToHTMLElement(this)->HasDirectionAuto())
return false;
if (HasAnimations())
return false;
if (Fullscreen::IsCurrentFullScreenElement(*this))
return false;
return true;
}
void Element::LogAddElementIfIsolatedWorldAndInDocument(
const char element[],
const QualifiedName& attr1) {
if (!isConnected())
return;
V8DOMActivityLogger* activity_logger =
V8DOMActivityLogger::CurrentActivityLoggerIfIsolatedWorld();
if (!activity_logger)
return;
Vector<String, 2> argv;
argv.push_back(element);
argv.push_back(FastGetAttribute(attr1));
activity_logger->LogEvent("blinkAddElement", argv.size(), argv.data());
}
void Element::LogAddElementIfIsolatedWorldAndInDocument(
const char element[],
const QualifiedName& attr1,
const QualifiedName& attr2) {
if (!isConnected())
return;
V8DOMActivityLogger* activity_logger =
V8DOMActivityLogger::CurrentActivityLoggerIfIsolatedWorld();
if (!activity_logger)
return;
Vector<String, 3> argv;
argv.push_back(element);
argv.push_back(FastGetAttribute(attr1));
argv.push_back(FastGetAttribute(attr2));
activity_logger->LogEvent("blinkAddElement", argv.size(), argv.data());
}
void Element::LogAddElementIfIsolatedWorldAndInDocument(
const char element[],
const QualifiedName& attr1,
const QualifiedName& attr2,
const QualifiedName& attr3) {
if (!isConnected())
return;
V8DOMActivityLogger* activity_logger =
V8DOMActivityLogger::CurrentActivityLoggerIfIsolatedWorld();
if (!activity_logger)
return;
Vector<String, 4> argv;
argv.push_back(element);
argv.push_back(FastGetAttribute(attr1));
argv.push_back(FastGetAttribute(attr2));
argv.push_back(FastGetAttribute(attr3));
activity_logger->LogEvent("blinkAddElement", argv.size(), argv.data());
}
void Element::LogUpdateAttributeIfIsolatedWorldAndInDocument(
const char element[],
const AttributeModificationParams& params) {
if (!isConnected())
return;
V8DOMActivityLogger* activity_logger =
V8DOMActivityLogger::CurrentActivityLoggerIfIsolatedWorld();
if (!activity_logger)
return;
Vector<String, 4> argv;
argv.push_back(element);
argv.push_back(params.name.ToString());
argv.push_back(params.old_value);
argv.push_back(params.new_value);
activity_logger->LogEvent("blinkSetAttribute", argv.size(), argv.data());
}
DEFINE_TRACE(Element) {
if (HasRareData())
visitor->Trace(GetElementRareData());
visitor->Trace(element_data_);
ContainerNode::Trace(visitor);
}
DEFINE_TRACE_WRAPPERS(Element) {
if (HasRareData()) {
visitor->TraceWrappers(GetElementRareData());
}
ContainerNode::TraceWrappers(visitor);
}
} // namespace blink