summaryrefslogtreecommitdiff
path: root/Source/WebCore/dom/Element.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/dom/Element.cpp')
-rw-r--r--Source/WebCore/dom/Element.cpp2678
1 files changed, 1663 insertions, 1015 deletions
diff --git a/Source/WebCore/dom/Element.cpp b/Source/WebCore/dom/Element.cpp
index 46ee2e83f..bd43d03eb 100644
--- a/Source/WebCore/dom/Element.cpp
+++ b/Source/WebCore/dom/Element.cpp
@@ -4,7 +4,7 @@
* (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-2014 Apple Inc. All rights reserved.
+ * Copyright (C) 2004-2017 Apple Inc. All rights reserved.
* (C) 2007 Eric Seidel (eric@webkit.org)
*
* This library is free software; you can redistribute it and/or
@@ -28,124 +28,152 @@
#include "AXObjectCache.h"
#include "Attr.h"
+#include "AttributeChangeInvalidation.h"
#include "CSSParser.h"
#include "Chrome.h"
#include "ChromeClient.h"
+#include "ClassChangeInvalidation.h"
#include "ClientRect.h"
#include "ClientRectList.h"
+#include "ComposedTreeAncestorIterator.h"
#include "ContainerNodeAlgorithms.h"
+#include "CustomElementReactionQueue.h"
+#include "CustomElementRegistry.h"
#include "DOMTokenList.h"
+#include "DocumentAnimation.h"
#include "DocumentSharedObjectPool.h"
#include "ElementIterator.h"
#include "ElementRareData.h"
#include "EventDispatcher.h"
+#include "EventHandler.h"
+#include "EventNames.h"
#include "FlowThreadController.h"
#include "FocusController.h"
#include "FocusEvent.h"
#include "FrameSelection.h"
#include "FrameView.h"
+#include "HTMLBodyElement.h"
+#include "HTMLCanvasElement.h"
#include "HTMLCollection.h"
#include "HTMLDocument.h"
-#include "HTMLElement.h"
-#include "HTMLFormControlsCollection.h"
+#include "HTMLHtmlElement.h"
#include "HTMLLabelElement.h"
#include "HTMLNameCollection.h"
-#include "HTMLOptionsCollection.h"
+#include "HTMLObjectElement.h"
#include "HTMLParserIdioms.h"
-#include "HTMLSelectElement.h"
-#include "HTMLTableRowsCollection.h"
+#include "HTMLTemplateElement.h"
+#include "IdChangeInvalidation.h"
#include "IdTargetObserverRegistry.h"
-#include "InsertionPoint.h"
+#include "InspectorInstrumentation.h"
+#include "JSLazyEventListener.h"
#include "KeyboardEvent.h"
+#include "KeyframeEffect.h"
+#include "MainFrame.h"
#include "MutationObserverInterestGroup.h"
#include "MutationRecord.h"
+#include "NoEventDispatchAssertion.h"
#include "NodeRenderStyle.h"
#include "PlatformWheelEvent.h"
#include "PointerLockController.h"
+#include "RenderFlowThread.h"
+#include "RenderLayer.h"
#include "RenderNamedFlowFragment.h"
#include "RenderRegion.h"
#include "RenderTheme.h"
+#include "RenderTreeUpdater.h"
#include "RenderView.h"
#include "RenderWidget.h"
+#include "SVGDocumentExtensions.h"
+#include "SVGElement.h"
+#include "SVGNames.h"
+#include "SVGSVGElement.h"
+#include "ScrollLatchingState.h"
#include "SelectorQuery.h"
#include "Settings.h"
+#include "SimulatedClick.h"
+#include "SlotAssignment.h"
#include "StyleProperties.h"
#include "StyleResolver.h"
+#include "StyleScope.h"
+#include "StyleTreeResolver.h"
#include "TextIterator.h"
#include "VoidCallback.h"
#include "WheelEvent.h"
+#include "XLinkNames.h"
#include "XMLNSNames.h"
#include "XMLNames.h"
#include "htmlediting.h"
-#include <wtf/BitVector.h>
+#include "markup.h"
#include <wtf/CurrentTime.h>
+#include <wtf/NeverDestroyed.h>
#include <wtf/text/CString.h>
-#if ENABLE(SVG)
-#include "SVGDocumentExtensions.h"
-#include "SVGElement.h"
-#include "SVGNames.h"
-#endif
-
namespace WebCore {
using namespace HTMLNames;
using namespace XMLNames;
-static inline bool shouldIgnoreAttributeCase(const Element& element)
+static HashMap<Element*, Vector<RefPtr<Attr>>>& attrNodeListMap()
{
- return element.isHTMLElement() && element.document().isHTMLDocument();
+ static NeverDestroyed<HashMap<Element*, Vector<RefPtr<Attr>>>> map;
+ return map;
}
-typedef Vector<RefPtr<Attr>> AttrNodeList;
-typedef HashMap<Element*, OwnPtr<AttrNodeList>> AttrNodeListMap;
+static Vector<RefPtr<Attr>>* attrNodeListForElement(Element& element)
+{
+ if (!element.hasSyntheticAttrChildNodes())
+ return nullptr;
+ ASSERT(attrNodeListMap().contains(&element));
+ return &attrNodeListMap().find(&element)->value;
+}
-static AttrNodeListMap& attrNodeListMap()
+static Vector<RefPtr<Attr>>& ensureAttrNodeListForElement(Element& element)
{
- DEFINE_STATIC_LOCAL(AttrNodeListMap, map, ());
- return map;
+ if (element.hasSyntheticAttrChildNodes()) {
+ ASSERT(attrNodeListMap().contains(&element));
+ return attrNodeListMap().find(&element)->value;
+ }
+ ASSERT(!attrNodeListMap().contains(&element));
+ element.setHasSyntheticAttrChildNodes(true);
+ return attrNodeListMap().add(&element, Vector<RefPtr<Attr>>()).iterator->value;
}
-static AttrNodeList* attrNodeListForElement(Element* element)
+static void removeAttrNodeListForElement(Element& element)
{
- if (!element->hasSyntheticAttrChildNodes())
- return 0;
- ASSERT(attrNodeListMap().contains(element));
- return attrNodeListMap().get(element);
+ ASSERT(element.hasSyntheticAttrChildNodes());
+ ASSERT(attrNodeListMap().contains(&element));
+ attrNodeListMap().remove(&element);
+ element.setHasSyntheticAttrChildNodes(false);
}
-static AttrNodeList& ensureAttrNodeListForElement(Element* element)
+static Attr* findAttrNodeInList(Vector<RefPtr<Attr>>& attrNodeList, const QualifiedName& name)
{
- if (element->hasSyntheticAttrChildNodes()) {
- ASSERT(attrNodeListMap().contains(element));
- return *attrNodeListMap().get(element);
+ for (auto& node : attrNodeList) {
+ if (node->qualifiedName().matches(name))
+ return node.get();
}
- ASSERT(!attrNodeListMap().contains(element));
- element->setHasSyntheticAttrChildNodes(true);
- AttrNodeListMap::AddResult result = attrNodeListMap().add(element, adoptPtr(new AttrNodeList));
- return *result.iterator->value;
+ return nullptr;
}
-static void removeAttrNodeListForElement(Element* element)
+static Attr* findAttrNodeInList(Vector<RefPtr<Attr>>& attrNodeList, const AtomicString& localName, bool shouldIgnoreAttributeCase)
{
- ASSERT(element->hasSyntheticAttrChildNodes());
- ASSERT(attrNodeListMap().contains(element));
- attrNodeListMap().remove(element);
- element->setHasSyntheticAttrChildNodes(false);
+ const AtomicString& caseAdjustedName = shouldIgnoreAttributeCase ? localName.convertToASCIILowercase() : localName;
+ for (auto& node : attrNodeList) {
+ if (node->qualifiedName().localName() == caseAdjustedName)
+ return node.get();
+ }
+ return nullptr;
}
-static Attr* findAttrNodeInList(AttrNodeList& attrNodeList, const QualifiedName& name)
+Ref<Element> Element::create(const QualifiedName& tagName, Document& document)
{
- for (unsigned i = 0; i < attrNodeList.size(); ++i) {
- if (attrNodeList.at(i)->qualifiedName() == name)
- return attrNodeList.at(i).get();
- }
- return 0;
+ return adoptRef(*new Element(tagName, document, CreateElement));
}
-PassRefPtr<Element> Element::create(const QualifiedName& tagName, Document& document)
+Element::Element(const QualifiedName& tagName, Document& document, ConstructionType type)
+ : ContainerNode(document, type)
+ , m_tagName(tagName)
{
- return adoptRef(new Element(tagName, document, CreateElement));
}
Element::~Element()
@@ -154,8 +182,8 @@ Element::~Element()
if (document().hasLivingRenderTree()) {
// When the document is not destroyed, an element that was part of a named flow
// content nodes should have been removed from the content nodes collection
- // and the inNamedFlow flag reset.
- ASSERT(!inNamedFlow());
+ // and the isNamedFlowContentElement flag reset.
+ ASSERT_WITH_SECURITY_IMPLICATION(!isNamedFlowContentElement());
}
#endif
@@ -167,12 +195,10 @@ Element::~Element()
if (hasSyntheticAttrChildNodes())
detachAllAttrNodesFromElement();
-#if ENABLE(SVG)
if (hasPendingResources()) {
- document().accessSVGExtensions()->removeElementFromPendingResources(this);
+ document().accessSVGExtensions().removeElementFromPendingResources(this);
ASSERT(!hasPendingResources());
}
-#endif
}
inline ElementRareData* Element::elementRareData() const
@@ -192,27 +218,37 @@ void Element::clearTabIndexExplicitlyIfNeeded()
elementRareData()->clearTabIndexExplicitly();
}
-void Element::setTabIndexExplicitly(short tabIndex)
+void Element::setTabIndexExplicitly(int tabIndex)
{
ensureElementRareData().setTabIndexExplicitly(tabIndex);
}
-bool Element::supportsFocus() const
+bool Element::tabIndexSetExplicitly() const
{
return hasRareData() && elementRareData()->tabIndexSetExplicitly();
}
+bool Element::supportsFocus() const
+{
+ return tabIndexSetExplicitly();
+}
+
Element* Element::focusDelegate()
{
return this;
}
-short Element::tabIndex() const
+int Element::tabIndex() const
{
return hasRareData() ? elementRareData()->tabIndex() : 0;
}
-bool Element::isKeyboardFocusable(KeyboardEvent*) const
+void Element::setTabIndex(int value)
+{
+ setIntegralAttribute(tabindexAttr, value);
+}
+
+bool Element::isKeyboardFocusable(KeyboardEvent&) const
{
return isFocusable() && tabIndex() >= 0;
}
@@ -224,7 +260,12 @@ bool Element::isMouseFocusable() const
bool Element::shouldUseInputMethod()
{
- return isContentEditable(UserSelectAllIsAlwaysNonEditable);
+ return computeEditability(UserSelectAllIsAlwaysNonEditable, ShouldUpdateStyle::Update) != Editability::ReadOnly;
+}
+
+static bool isForceEvent(const PlatformMouseEvent& platformEvent)
+{
+ return platformEvent.type() == PlatformEvent::MouseForceChanged || platformEvent.type() == PlatformEvent::MouseForceDown || platformEvent.type() == PlatformEvent::MouseForceUp;
}
bool Element::dispatchMouseEvent(const PlatformMouseEvent& platformEvent, const AtomicString& eventType, int detail, Element* relatedTarget)
@@ -232,7 +273,10 @@ bool Element::dispatchMouseEvent(const PlatformMouseEvent& platformEvent, const
if (isDisabledFormControl())
return false;
- RefPtr<MouseEvent> mouseEvent = MouseEvent::create(eventType, document().defaultView(), platformEvent, detail, relatedTarget);
+ if (isForceEvent(platformEvent) && !document().hasListenerTypeForEventType(platformEvent.type()))
+ return false;
+
+ Ref<MouseEvent> mouseEvent = MouseEvent::create(eventType, document().defaultView(), platformEvent, detail, relatedTarget);
if (mouseEvent->type().isEmpty())
return true; // Shouldn't happen.
@@ -244,12 +288,11 @@ bool Element::dispatchMouseEvent(const PlatformMouseEvent& platformEvent, const
// Special case: If it's a double click event, we also send the dblclick event. This is not part
// of the DOM specs, but is used for compatibility with the ondblclick="" attribute. This is treated
// as a separate event in other DOM-compliant browsers like Firefox, and so we do the same.
- RefPtr<MouseEvent> doubleClickEvent = MouseEvent::create();
- doubleClickEvent->initMouseEvent(eventNames().dblclickEvent,
+ Ref<MouseEvent> doubleClickEvent = MouseEvent::create(eventNames().dblclickEvent,
mouseEvent->bubbles(), mouseEvent->cancelable(), mouseEvent->view(), mouseEvent->detail(),
mouseEvent->screenX(), mouseEvent->screenY(), mouseEvent->clientX(), mouseEvent->clientY(),
mouseEvent->ctrlKey(), mouseEvent->altKey(), mouseEvent->shiftKey(), mouseEvent->metaKey(),
- mouseEvent->button(), relatedTarget);
+ mouseEvent->button(), mouseEvent->syntheticClickType(), relatedTarget);
if (mouseEvent->defaultHandled())
doubleClickEvent->setDefaultHandled();
@@ -261,75 +304,73 @@ bool Element::dispatchMouseEvent(const PlatformMouseEvent& platformEvent, const
return didNotSwallowEvent;
}
-inline static unsigned deltaMode(const PlatformWheelEvent& event)
-{
- return event.granularity() == ScrollByPageWheelEvent ? WheelEvent::DOM_DELTA_PAGE : WheelEvent::DOM_DELTA_PIXEL;
-}
bool Element::dispatchWheelEvent(const PlatformWheelEvent& event)
{
- if (!(event.deltaX() || event.deltaY()))
- return true;
+ Ref<WheelEvent> wheelEvent = WheelEvent::create(event, document().defaultView());
- RefPtr<WheelEvent> wheelEvent = WheelEvent::create(
- FloatPoint(event.wheelTicksX(), event.wheelTicksY()),
- FloatPoint(event.deltaX(), event.deltaY()),
- deltaMode(event),
- document().defaultView(),
- event.globalPosition(),
- event.position(),
- event.ctrlKey(), event.altKey(), event.shiftKey(), event.metaKey(),
- event.directionInvertedFromDevice(),
- event.timestamp());
+ // Events with no deltas are important because they convey platform information about scroll gestures
+ // and momentum beginning or ending. However, those events should not be sent to the DOM since some
+ // websites will break. They need to be dispatched because dispatching them will call into the default
+ // event handler, and our platform code will correctly handle the phase changes. Calling stopPropogation()
+ // will prevent the event from being sent to the DOM, but will still call the default event handler.
+ if (!event.deltaX() && !event.deltaY())
+ wheelEvent->stopPropagation();
- return EventDispatcher::dispatchEvent(this, wheelEvent) && !wheelEvent->defaultHandled();
+ return EventDispatcher::dispatchEvent(*this, wheelEvent) && !wheelEvent->defaultHandled();
}
bool Element::dispatchKeyEvent(const PlatformKeyboardEvent& platformEvent)
{
- RefPtr<KeyboardEvent> event = KeyboardEvent::create(platformEvent, document().defaultView());
- return EventDispatcher::dispatchEvent(this, event) && !event->defaultHandled();
+ Ref<KeyboardEvent> event = KeyboardEvent::create(platformEvent, document().defaultView());
+ if (Frame* frame = document().frame()) {
+ if (frame->eventHandler().accessibilityPreventsEventPropogation(event))
+ event->stopPropagation();
+ }
+ return EventDispatcher::dispatchEvent(*this, event) && !event->defaultHandled();
}
void Element::dispatchSimulatedClick(Event* underlyingEvent, SimulatedClickMouseEventOptions eventOptions, SimulatedClickVisualOptions visualOptions)
{
- EventDispatcher::dispatchSimulatedClick(this, underlyingEvent, eventOptions, visualOptions);
+ simulateClick(*this, underlyingEvent, eventOptions, visualOptions, SimulatedClickSource::UserAgent);
}
-DEFINE_VIRTUAL_ATTRIBUTE_EVENT_LISTENER(Element, blur);
-DEFINE_VIRTUAL_ATTRIBUTE_EVENT_LISTENER(Element, error);
-DEFINE_VIRTUAL_ATTRIBUTE_EVENT_LISTENER(Element, focus);
-DEFINE_VIRTUAL_ATTRIBUTE_EVENT_LISTENER(Element, load);
-
-PassRefPtr<Node> Element::cloneNode(bool deep)
+Ref<Node> Element::cloneNodeInternal(Document& targetDocument, CloningOperation type)
{
- return deep ? cloneElementWithChildren() : cloneElementWithoutChildren();
+ switch (type) {
+ case CloningOperation::OnlySelf:
+ case CloningOperation::SelfWithTemplateContent:
+ return cloneElementWithoutChildren(targetDocument);
+ case CloningOperation::Everything:
+ break;
+ }
+ return cloneElementWithChildren(targetDocument);
}
-PassRefPtr<Element> Element::cloneElementWithChildren()
+Ref<Element> Element::cloneElementWithChildren(Document& targetDocument)
{
- RefPtr<Element> clone = cloneElementWithoutChildren();
- cloneChildNodes(clone.get());
- return clone.release();
+ Ref<Element> clone = cloneElementWithoutChildren(targetDocument);
+ cloneChildNodes(clone);
+ return clone;
}
-PassRefPtr<Element> Element::cloneElementWithoutChildren()
+Ref<Element> Element::cloneElementWithoutChildren(Document& targetDocument)
{
- RefPtr<Element> clone = cloneElementWithoutAttributesAndChildren();
+ Ref<Element> clone = cloneElementWithoutAttributesAndChildren(targetDocument);
// 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.
ASSERT(isHTMLElement() == clone->isHTMLElement());
clone->cloneDataFromElement(*this);
- return clone.release();
+ return clone;
}
-PassRefPtr<Element> Element::cloneElementWithoutAttributesAndChildren()
+Ref<Element> Element::cloneElementWithoutAttributesAndChildren(Document& targetDocument)
{
- return document().createElement(tagQName(), false);
+ return targetDocument.createElement(tagQName(), false);
}
-PassRefPtr<Attr> Element::detachAttribute(unsigned index)
+Ref<Attr> Element::detachAttribute(unsigned index)
{
ASSERT(elementData());
@@ -342,19 +383,20 @@ PassRefPtr<Attr> Element::detachAttribute(unsigned index)
attrNode = Attr::create(document(), attribute.name(), attribute.value());
removeAttributeInternal(index, NotInSynchronizationOfLazyAttribute);
- return attrNode.release();
+ return attrNode.releaseNonNull();
}
-void Element::removeAttribute(const QualifiedName& name)
+bool Element::removeAttribute(const QualifiedName& name)
{
if (!elementData())
- return;
+ return false;
unsigned index = elementData()->findAttributeIndexByName(name);
if (index == ElementData::attributeNotFound)
- return;
+ return false;
removeAttributeInternal(index, NotInSynchronizationOfLazyAttribute);
+ return true;
}
void Element::setBooleanAttribute(const QualifiedName& name, bool value)
@@ -365,14 +407,14 @@ void Element::setBooleanAttribute(const QualifiedName& name, bool value)
removeAttribute(name);
}
-NamedNodeMap* Element::attributes() const
+NamedNodeMap& Element::attributes() const
{
ElementRareData& rareData = const_cast<Element*>(this)->ensureElementRareData();
if (NamedNodeMap* attributeMap = rareData.attributeMap())
- return attributeMap;
+ return *attributeMap;
- rareData.setAttributeMap(NamedNodeMap::create(const_cast<Element&>(*this)));
- return rareData.attributeMap();
+ rareData.setAttributeMap(std::make_unique<NamedNodeMap>(const_cast<Element&>(*this)));
+ return *rareData.attributeMap();
}
Node::NodeType Element::nodeType() const
@@ -393,15 +435,14 @@ void Element::synchronizeAllAttributes() const
ASSERT(isStyledElement());
static_cast<const StyledElement*>(this)->synchronizeStyleAttributeInternal();
}
-#if ENABLE(SVG)
+
if (elementData()->animatedSVGAttributesAreDirty()) {
ASSERT(isSVGElement());
- toSVGElement(this)->synchronizeAnimatedSVGAttribute(anyQName());
+ downcast<SVGElement>(*this).synchronizeAnimatedSVGAttribute(anyQName());
}
-#endif
}
-inline void Element::synchronizeAttribute(const QualifiedName& name) const
+ALWAYS_INLINE void Element::synchronizeAttribute(const QualifiedName& name) const
{
if (!elementData())
return;
@@ -410,32 +451,36 @@ inline void Element::synchronizeAttribute(const QualifiedName& name) const
static_cast<const StyledElement*>(this)->synchronizeStyleAttributeInternal();
return;
}
-#if ENABLE(SVG)
+
if (UNLIKELY(elementData()->animatedSVGAttributesAreDirty())) {
ASSERT(isSVGElement());
- toSVGElement(this)->synchronizeAnimatedSVGAttribute(name);
+ downcast<SVGElement>(*this).synchronizeAnimatedSVGAttribute(name);
}
-#endif
}
-inline void Element::synchronizeAttribute(const AtomicString& localName) const
+static ALWAYS_INLINE bool isStyleAttribute(const Element& element, const AtomicString& attributeLocalName)
+{
+ if (shouldIgnoreAttributeCase(element))
+ return equalLettersIgnoringASCIICase(attributeLocalName, "style");
+ return attributeLocalName == styleAttr.localName();
+}
+
+ALWAYS_INLINE void Element::synchronizeAttribute(const AtomicString& localName) 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 (!elementData())
return;
- if (elementData()->styleAttributeIsDirty() && equalPossiblyIgnoringCase(localName, styleAttr.localName(), shouldIgnoreAttributeCase(*this))) {
+ if (elementData()->styleAttributeIsDirty() && isStyleAttribute(*this, localName)) {
ASSERT_WITH_SECURITY_IMPLICATION(isStyledElement());
static_cast<const StyledElement*>(this)->synchronizeStyleAttributeInternal();
return;
}
-#if ENABLE(SVG)
if (elementData()->animatedSVGAttributesAreDirty()) {
// We're not passing a namespace argument on purpose. SVGNames::*Attr are defined w/o namespaces as well.
ASSERT_WITH_SECURITY_IMPLICATION(isSVGElement());
- toSVGElement(this)->synchronizeAnimatedSVGAttribute(QualifiedName(nullAtom, localName, nullAtom));
+ downcast<SVGElement>(*this).synchronizeAnimatedSVGAttribute(QualifiedName(nullAtom, localName, nullAtom));
}
-#endif
}
const AtomicString& Element::getAttribute(const QualifiedName& name) const
@@ -448,25 +493,33 @@ const AtomicString& Element::getAttribute(const QualifiedName& name) const
return nullAtom;
}
+Vector<String> Element::getAttributeNames() const
+{
+ Vector<String> attributesVector;
+ if (!hasAttributes())
+ return attributesVector;
+
+ auto attributes = attributesIterator();
+ attributesVector.reserveInitialCapacity(attributes.attributeCount());
+ for (auto& attribute : attributes)
+ attributesVector.uncheckedAppend(attribute.name().toString());
+ return attributesVector;
+}
+
bool Element::isFocusable() const
{
- if (!inDocument() || !supportsFocus())
+ if (!isConnected() || !supportsFocus())
return false;
- // 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 Element* e = this;
- while (e && !e->hasLocalName(canvasTag))
- e = e->parentElement();
- ASSERT(e);
- return e->renderer() && e->renderer()->style().visibility() == VISIBLE;
- }
-
if (!renderer()) {
// If the node is in a display:none tree it might say it needs style recalc but
// the whole document is actually up to date.
ASSERT(!needsStyleRecalc() || !document().childNeedsStyleRecalc());
+
+ // 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 (auto* canvas = ancestorsOfType<HTMLCanvasElement>(*this).first())
+ return canvas->renderer() && canvas->renderer()->style().visibility() == VISIBLE;
}
// FIXME: Even if we are not visible, we might have a child that is visible.
@@ -508,14 +561,15 @@ void Element::setActive(bool flag, bool pause)
document().userActionElements().setActive(this, flag);
+ const RenderStyle* renderStyle = this->renderStyle();
+ bool reactsToPress = (renderStyle && renderStyle->affectedByActive()) || styleAffectedByActive();
+ if (reactsToPress)
+ invalidateStyleForSubtree();
+
if (!renderer())
return;
- bool reactsToPress = renderStyle()->affectedByActive() || childrenAffectedByActive();
- if (reactsToPress)
- setNeedsStyleRecalc();
-
- if (renderer()->style().hasAppearance() && renderer()->theme().stateChanged(renderer(), PressedState))
+ if (renderer()->style().hasAppearance() && renderer()->theme().stateChanged(*renderer(), ControlStates::PressedState))
reactsToPress = true;
// The rest of this function implements a feature that only works if the
@@ -537,7 +591,7 @@ void Element::setActive(bool flag, bool pause)
// Do an immediate repaint.
if (renderer())
- renderer()->repaint(true);
+ renderer()->repaint();
// FIXME: Come up with a less ridiculous way of doing this.
#ifdef HAVE_FUNC_USLEEP
@@ -555,7 +609,10 @@ void Element::setFocus(bool flag)
return;
document().userActionElements().setFocused(this, flag);
- setNeedsStyleRecalc();
+ invalidateStyleForSubtree();
+
+ for (Element* element = this; element; element = element->parentOrShadowHostElement())
+ element->setHasFocusWithin(flag);
}
void Element::setHovered(bool flag)
@@ -572,16 +629,16 @@ void Element::setHovered(bool flag)
// style, it would never go back to its normal style and remain
// stuck in its hovered style).
if (!flag)
- setNeedsStyleRecalc();
+ invalidateStyleForSubtree();
return;
}
if (renderer()->style().affectedByHover() || childrenAffectedByHover())
- setNeedsStyleRecalc();
+ invalidateStyleForSubtree();
if (renderer()->style().hasAppearance())
- renderer()->theme().stateChanged(renderer(), HoverState);
+ renderer()->theme().stateChanged(*renderer(), ControlStates::HoverState);
}
void Element::scrollIntoView(bool alignToTop)
@@ -591,12 +648,13 @@ void Element::scrollIntoView(bool alignToTop)
if (!renderer())
return;
- LayoutRect bounds = boundingBox();
+ bool insideFixed;
+ LayoutRect absoluteBounds = renderer()->absoluteAnchorRect(&insideFixed);
// Align to the top / bottom and to the closest edge.
if (alignToTop)
- renderer()->scrollRectToVisible(bounds, ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignTopAlways);
+ renderer()->scrollRectToVisible(SelectionRevealMode::Reveal, absoluteBounds, insideFixed, ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignTopAlways);
else
- renderer()->scrollRectToVisible(bounds, ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignBottomAlways);
+ renderer()->scrollRectToVisible(SelectionRevealMode::Reveal, absoluteBounds, insideFixed, ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignBottomAlways);
}
void Element::scrollIntoViewIfNeeded(bool centerIfNeeded)
@@ -606,21 +664,81 @@ void Element::scrollIntoViewIfNeeded(bool centerIfNeeded)
if (!renderer())
return;
- LayoutRect bounds = boundingBox();
+ bool insideFixed;
+ LayoutRect absoluteBounds = renderer()->absoluteAnchorRect(&insideFixed);
if (centerIfNeeded)
- renderer()->scrollRectToVisible(bounds, ScrollAlignment::alignCenterIfNeeded, ScrollAlignment::alignCenterIfNeeded);
+ renderer()->scrollRectToVisible(SelectionRevealMode::Reveal, absoluteBounds, insideFixed, ScrollAlignment::alignCenterIfNeeded, ScrollAlignment::alignCenterIfNeeded);
else
- renderer()->scrollRectToVisible(bounds, ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignToEdgeIfNeeded);
+ renderer()->scrollRectToVisible(SelectionRevealMode::Reveal, absoluteBounds, insideFixed, ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignToEdgeIfNeeded);
+}
+
+void Element::scrollIntoViewIfNotVisible(bool centerIfNotVisible)
+{
+ document().updateLayoutIgnorePendingStylesheets();
+
+ if (!renderer())
+ return;
+
+ bool insideFixed;
+ LayoutRect absoluteBounds = renderer()->absoluteAnchorRect(&insideFixed);
+ if (centerIfNotVisible)
+ renderer()->scrollRectToVisible(SelectionRevealMode::Reveal, absoluteBounds, insideFixed, ScrollAlignment::alignCenterIfNotVisible, ScrollAlignment::alignCenterIfNotVisible);
+ else
+ renderer()->scrollRectToVisible(SelectionRevealMode::Reveal, absoluteBounds, insideFixed, ScrollAlignment::alignToEdgeIfNotVisible, ScrollAlignment::alignToEdgeIfNotVisible);
+}
+
+void Element::scrollBy(const ScrollToOptions& options)
+{
+ return scrollBy(options.left.value_or(0), options.top.value_or(0));
+}
+
+static inline double normalizeNonFiniteValue(double f)
+{
+ return std::isfinite(f) ? f : 0;
+}
+
+void Element::scrollBy(double x, double y)
+{
+ scrollTo(scrollLeft() + normalizeNonFiniteValue(x), scrollTop() + normalizeNonFiniteValue(y));
+}
+
+void Element::scrollTo(const ScrollToOptions& options)
+{
+ // If the element is the root element and document is in quirks mode, terminate these steps.
+ // Note that WebKit always uses quirks mode document scrolling behavior. See Document::scrollingElement().
+ if (this == document().documentElement())
+ return;
+
+ document().updateLayoutIgnorePendingStylesheets();
+
+ // If the element does not have any associated CSS layout box, the element has no associated scrolling box,
+ // or the element has no overflow, terminate these steps.
+ RenderBox* renderer = renderBox();
+ if (!renderer || !renderer->hasOverflowClip())
+ return;
+
+ // Normalize non-finite values for left and top dictionary members of options, if present.
+ double x = options.left ? normalizeNonFiniteValue(options.left.value()) : adjustForAbsoluteZoom(renderer->scrollLeft(), *renderer);
+ double y = options.top ? normalizeNonFiniteValue(options.top.value()) : adjustForAbsoluteZoom(renderer->scrollTop(), *renderer);
+
+ renderer->setScrollLeft(clampToInteger(x * renderer->style().effectiveZoom()));
+ renderer->setScrollTop(clampToInteger(y * renderer->style().effectiveZoom()));
+}
+
+void Element::scrollTo(double x, double y)
+{
+ scrollTo({ x, y });
}
void Element::scrollByUnits(int units, ScrollGranularity granularity)
{
document().updateLayoutIgnorePendingStylesheets();
- if (!renderer())
+ auto* renderer = this->renderer();
+ if (!renderer)
return;
- if (!renderer()->hasOverflowClip())
+ if (!renderer->hasOverflowClip())
return;
ScrollDirection direction = ScrollDown;
@@ -629,7 +747,7 @@ void Element::scrollByUnits(int units, ScrollGranularity granularity)
units = -units;
}
Element* stopElement = this;
- toRenderBox(renderer())->scroll(direction, granularity, units, &stopElement);
+ downcast<RenderBox>(*renderer).scroll(direction, granularity, units, &stopElement);
}
void Element::scrollByLines(int lines)
@@ -642,16 +760,16 @@ void Element::scrollByPages(int pages)
scrollByUnits(pages, ScrollByPage);
}
-static float localZoomForRenderer(RenderElement* renderer)
+static double localZoomForRenderer(const RenderElement& renderer)
{
// FIXME: This does the wrong thing if two opposing zooms are in effect and canceled each
// other out, but the alternative is that we'd have to crawl up the whole render tree every
// time (or store an additional bit in the RenderStyle to indicate that a zoom was specified).
- float zoomFactor = 1;
- if (renderer->style().effectiveZoom() != 1) {
+ double zoomFactor = 1;
+ if (renderer.style().effectiveZoom() != 1) {
// Need to find the nearest enclosing RenderElement that set up
// a differing zoom, and then we divide our result by it to eliminate the zoom.
- RenderElement* prev = renderer;
+ const RenderElement* prev = &renderer;
for (RenderElement* curr = prev->parent(); curr; curr = curr->parent()) {
if (curr->style().effectiveZoom() != prev->style().effectiveZoom()) {
zoomFactor = prev->style().zoom();
@@ -665,58 +783,67 @@ static float localZoomForRenderer(RenderElement* renderer)
return zoomFactor;
}
-static int adjustForLocalZoom(LayoutUnit value, RenderElement* renderer)
+static double adjustForLocalZoom(LayoutUnit value, const RenderElement& renderer, double& zoomFactor)
{
- float zoomFactor = localZoomForRenderer(renderer);
+ zoomFactor = localZoomForRenderer(renderer);
if (zoomFactor == 1)
- return value;
-#if ENABLE(SUBPIXEL_LAYOUT)
- return lroundf(value / zoomFactor);
-#else
- // Needed because computeLengthInt truncates (rather than rounds) when scaling up.
- if (zoomFactor > 1)
- value++;
- return static_cast<int>(value / zoomFactor);
-#endif
+ return value.toDouble();
+ return value.toDouble() / zoomFactor;
+}
+
+enum LegacyCSSOMElementMetricsRoundingStrategy { Round, Floor };
+
+static bool subpixelMetricsEnabled(const Document& document)
+{
+ return document.settings().subpixelCSSOMElementMetricsEnabled();
+}
+
+static double convertToNonSubpixelValueIfNeeded(double value, const Document& document, LegacyCSSOMElementMetricsRoundingStrategy roundStrategy = Round)
+{
+ return subpixelMetricsEnabled(document) ? value : roundStrategy == Round ? round(value) : floor(value);
}
-int Element::offsetLeft()
+double Element::offsetLeft()
{
document().updateLayoutIgnorePendingStylesheets();
- if (RenderBoxModelObject* renderer = renderBoxModelObject())
- return adjustForLocalZoom(renderer->pixelSnappedOffsetLeft(), renderer);
+ if (RenderBoxModelObject* renderer = renderBoxModelObject()) {
+ LayoutUnit offsetLeft = subpixelMetricsEnabled(renderer->document()) ? renderer->offsetLeft() : LayoutUnit(roundToInt(renderer->offsetLeft()));
+ double zoomFactor = 1;
+ double offsetLeftAdjustedWithZoom = adjustForLocalZoom(offsetLeft, *renderer, zoomFactor);
+ return convertToNonSubpixelValueIfNeeded(offsetLeftAdjustedWithZoom, renderer->document(), zoomFactor == 1 ? Floor : Round);
+ }
return 0;
}
-int Element::offsetTop()
+double Element::offsetTop()
{
document().updateLayoutIgnorePendingStylesheets();
- if (RenderBoxModelObject* renderer = renderBoxModelObject())
- return adjustForLocalZoom(renderer->pixelSnappedOffsetTop(), renderer);
+ if (RenderBoxModelObject* renderer = renderBoxModelObject()) {
+ LayoutUnit offsetTop = subpixelMetricsEnabled(renderer->document()) ? renderer->offsetTop() : LayoutUnit(roundToInt(renderer->offsetTop()));
+ double zoomFactor = 1;
+ double offsetTopAdjustedWithZoom = adjustForLocalZoom(offsetTop, *renderer, zoomFactor);
+ return convertToNonSubpixelValueIfNeeded(offsetTopAdjustedWithZoom, renderer->document(), zoomFactor == 1 ? Floor : Round);
+ }
return 0;
}
-int Element::offsetWidth()
+double Element::offsetWidth()
{
- document().updateLayoutIgnorePendingStylesheets();
- if (RenderBoxModelObject* renderer = renderBoxModelObject())
-#if ENABLE(SUBPIXEL_LAYOUT)
- return adjustLayoutUnitForAbsoluteZoom(renderer->pixelSnappedOffsetWidth(), *renderer).round();
-#else
- return adjustForAbsoluteZoom(renderer->pixelSnappedOffsetWidth(), *renderer);
-#endif
+ document().updateLayoutIfDimensionsOutOfDate(*this, WidthDimensionsCheck);
+ if (RenderBoxModelObject* renderer = renderBoxModelObject()) {
+ LayoutUnit offsetWidth = subpixelMetricsEnabled(renderer->document()) ? renderer->offsetWidth() : LayoutUnit(roundToInt(renderer->offsetWidth()));
+ return convertToNonSubpixelValueIfNeeded(adjustLayoutUnitForAbsoluteZoom(offsetWidth, *renderer).toDouble(), renderer->document());
+ }
return 0;
}
-int Element::offsetHeight()
+double Element::offsetHeight()
{
- document().updateLayoutIgnorePendingStylesheets();
- if (RenderBoxModelObject* renderer = renderBoxModelObject())
-#if ENABLE(SUBPIXEL_LAYOUT)
- return adjustLayoutUnitForAbsoluteZoom(renderer->pixelSnappedOffsetHeight(), *renderer).round();
-#else
- return adjustForAbsoluteZoom(renderer->pixelSnappedOffsetHeight(), *renderer);
-#endif
+ document().updateLayoutIfDimensionsOutOfDate(*this, HeightDimensionsCheck);
+ if (RenderBoxModelObject* renderer = renderBoxModelObject()) {
+ LayoutUnit offsetHeight = subpixelMetricsEnabled(renderer->document()) ? renderer->offsetHeight() : LayoutUnit(roundToInt(renderer->offsetHeight()));
+ return convertToNonSubpixelValueIfNeeded(adjustLayoutUnitForAbsoluteZoom(offsetHeight, *renderer).toDouble(), renderer->document());
+ }
return 0;
}
@@ -725,7 +852,7 @@ Element* Element::bindingsOffsetParent()
Element* element = offsetParent();
if (!element || !element->isInShadowTree())
return element;
- return element->containingShadowRoot()->type() == ShadowRoot::UserAgentShadowRoot ? 0 : element;
+ return element->containingShadowRoot()->mode() == ShadowRootMode::UserAgent ? nullptr : element;
}
Element* Element::offsetParent()
@@ -740,163 +867,124 @@ Element* Element::offsetParent()
return offsetParent->element();
}
-int Element::clientLeft()
+double Element::clientLeft()
{
document().updateLayoutIgnorePendingStylesheets();
- if (RenderBox* renderer = renderBox())
- return adjustForAbsoluteZoom(roundToInt(renderer->clientLeft()), *renderer);
+ if (auto* renderer = renderBox()) {
+ LayoutUnit clientLeft = subpixelMetricsEnabled(renderer->document()) ? renderer->clientLeft() : LayoutUnit(roundToInt(renderer->clientLeft()));
+ return convertToNonSubpixelValueIfNeeded(adjustLayoutUnitForAbsoluteZoom(clientLeft, *renderer).toDouble(), renderer->document());
+ }
return 0;
}
-int Element::clientTop()
+double Element::clientTop()
{
document().updateLayoutIgnorePendingStylesheets();
- if (RenderBox* renderer = renderBox())
- return adjustForAbsoluteZoom(roundToInt(renderer->clientTop()), *renderer);
+ if (auto* renderer = renderBox()) {
+ LayoutUnit clientTop = subpixelMetricsEnabled(renderer->document()) ? renderer->clientTop() : LayoutUnit(roundToInt(renderer->clientTop()));
+ return convertToNonSubpixelValueIfNeeded(adjustLayoutUnitForAbsoluteZoom(clientTop, *renderer).toDouble(), renderer->document());
+ }
return 0;
}
-int Element::clientWidth()
+double Element::clientWidth()
{
- document().updateLayoutIgnorePendingStylesheets();
+ document().updateLayoutIfDimensionsOutOfDate(*this, WidthDimensionsCheck);
if (!document().hasLivingRenderTree())
return 0;
+
RenderView& renderView = *document().renderView();
// 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 inQuirksMode = document().inQuirksMode();
- if ((!inQuirksMode && document().documentElement() == this) || (inQuirksMode && isHTMLElement() && document().body() == this))
+ if ((!inQuirksMode && document().documentElement() == this) || (inQuirksMode && isHTMLElement() && document().bodyOrFrameset() == this))
return adjustForAbsoluteZoom(renderView.frameView().layoutWidth(), renderView);
- if (RenderBox* renderer = renderBox())
-#if ENABLE(SUBPIXEL_LAYOUT)
- return adjustLayoutUnitForAbsoluteZoom(renderer->pixelSnappedClientWidth(), *renderer).round();
-#else
- return adjustForAbsoluteZoom(renderer->pixelSnappedClientWidth(), *renderer);
-#endif
+ if (RenderBox* renderer = renderBox()) {
+ LayoutUnit clientWidth = subpixelMetricsEnabled(renderer->document()) ? renderer->clientWidth() : LayoutUnit(roundToInt(renderer->clientWidth()));
+ return convertToNonSubpixelValueIfNeeded(adjustLayoutUnitForAbsoluteZoom(clientWidth, *renderer).toDouble(), renderer->document());
+ }
return 0;
}
-int Element::clientHeight()
+double Element::clientHeight()
{
- document().updateLayoutIgnorePendingStylesheets();
-
+ document().updateLayoutIfDimensionsOutOfDate(*this, HeightDimensionsCheck);
if (!document().hasLivingRenderTree())
return 0;
+
RenderView& renderView = *document().renderView();
// 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 inQuirksMode = document().inQuirksMode();
- if ((!inQuirksMode && document().documentElement() == this) || (inQuirksMode && isHTMLElement() && document().body() == this))
+ if ((!inQuirksMode && document().documentElement() == this) || (inQuirksMode && isHTMLElement() && document().bodyOrFrameset() == this))
return adjustForAbsoluteZoom(renderView.frameView().layoutHeight(), renderView);
- if (RenderBox* renderer = renderBox())
-#if ENABLE(SUBPIXEL_LAYOUT)
- return adjustLayoutUnitForAbsoluteZoom(renderer->pixelSnappedClientHeight(), *renderer).round();
-#else
- return adjustForAbsoluteZoom(renderer->pixelSnappedClientHeight(), *renderer);
-#endif
+ if (RenderBox* renderer = renderBox()) {
+ LayoutUnit clientHeight = subpixelMetricsEnabled(renderer->document()) ? renderer->clientHeight() : LayoutUnit(roundToInt(renderer->clientHeight()));
+ return convertToNonSubpixelValueIfNeeded(adjustLayoutUnitForAbsoluteZoom(clientHeight, *renderer).toDouble(), renderer->document());
+ }
return 0;
}
int Element::scrollLeft()
{
- if (document().documentElement() == this && document().inQuirksMode())
- return 0;
-
document().updateLayoutIgnorePendingStylesheets();
- if (!document().hasLivingRenderTree())
- return 0;
- RenderView& renderView = *document().renderView();
-
- if (document().documentElement() == this)
- return adjustForAbsoluteZoom(renderView.frameView().scrollX(), renderView);
-
- if (RenderBox* rend = renderBox())
- return adjustForAbsoluteZoom(rend->scrollLeft(), *rend);
+ if (auto* renderer = renderBox())
+ return adjustForAbsoluteZoom(renderer->scrollLeft(), *renderer);
return 0;
}
int Element::scrollTop()
{
- if (document().documentElement() == this && document().inQuirksMode())
- return 0;
-
document().updateLayoutIgnorePendingStylesheets();
- if (!document().hasLivingRenderTree())
- return 0;
- RenderView& renderView = *document().renderView();
-
- if (document().documentElement() == this)
- return adjustForAbsoluteZoom(renderView.frameView().scrollY(), renderView);
-
- if (RenderBox* rend = renderBox())
- return adjustForAbsoluteZoom(rend->scrollTop(), *rend);
+ if (RenderBox* renderer = renderBox())
+ return adjustForAbsoluteZoom(renderer->scrollTop(), *renderer);
return 0;
}
void Element::setScrollLeft(int newLeft)
{
- if (document().documentElement() == this && document().inQuirksMode())
- return;
-
document().updateLayoutIgnorePendingStylesheets();
- if (!document().hasLivingRenderTree())
- return;
-
- if (document().documentElement() == this) {
- RenderView& renderView = *document().renderView();
- int zoom = renderView.style().effectiveZoom();
- renderView.frameView().setScrollPosition(IntPoint(newLeft * zoom, renderView.frameView().scrollY() * zoom));
- return;
+ if (auto* renderer = renderBox()) {
+ renderer->setScrollLeft(static_cast<int>(newLeft * renderer->style().effectiveZoom()));
+ if (auto* scrollableArea = renderer->layer())
+ scrollableArea->setScrolledProgrammatically(true);
}
-
- if (RenderBox* rend = renderBox())
- rend->setScrollLeft(static_cast<int>(newLeft * rend->style().effectiveZoom()));
}
void Element::setScrollTop(int newTop)
{
- if (document().documentElement() == this && document().inQuirksMode())
- return;
-
document().updateLayoutIgnorePendingStylesheets();
- if (!document().hasLivingRenderTree())
- return;
-
- if (document().documentElement() == this) {
- RenderView& renderView = *document().renderView();
- int zoom = renderView.style().effectiveZoom();
- renderView.frameView().setScrollPosition(IntPoint(renderView.frameView().scrollX() * zoom, newTop * zoom));
- return;
+ if (auto* renderer = renderBox()) {
+ renderer->setScrollTop(static_cast<int>(newTop * renderer->style().effectiveZoom()));
+ if (auto* scrollableArea = renderer->layer())
+ scrollableArea->setScrolledProgrammatically(true);
}
-
- if (RenderBox* rend = renderBox())
- rend->setScrollTop(static_cast<int>(newTop * rend->style().effectiveZoom()));
}
int Element::scrollWidth()
{
- document().updateLayoutIgnorePendingStylesheets();
- if (RenderBox* rend = renderBox())
- return adjustForAbsoluteZoom(rend->scrollWidth(), *rend);
+ document().updateLayoutIfDimensionsOutOfDate(*this, WidthDimensionsCheck);
+ if (auto* renderer = renderBox())
+ return adjustForAbsoluteZoom(renderer->scrollWidth(), *renderer);
return 0;
}
int Element::scrollHeight()
{
- document().updateLayoutIgnorePendingStylesheets();
- if (RenderBox* rend = renderBox())
- return adjustForAbsoluteZoom(rend->scrollHeight(), *rend);
+ document().updateLayoutIfDimensionsOutOfDate(*this, HeightDimensionsCheck);
+ if (auto* renderer = renderBox())
+ return adjustForAbsoluteZoom(renderer->scrollHeight(), *renderer);
return 0;
}
@@ -909,16 +997,14 @@ IntRect Element::boundsInRootViewSpace()
return IntRect();
Vector<FloatQuad> quads;
-#if ENABLE(SVG)
+
if (isSVGElement() && renderer()) {
// Get the bounding rectangle from the SVG model.
- SVGElement* svgElement = toSVGElement(this);
+ SVGElement& svgElement = downcast<SVGElement>(*this);
FloatRect localRect;
- if (svgElement->getBoundingBox(localRect))
+ if (svgElement.getBoundingBox(localRect))
quads.append(renderer()->localToAbsoluteQuad(localRect));
- } else
-#endif
- {
+ } else {
// Get the bounding rectangle from the box model.
if (renderBoxModelObject())
renderBoxModelObject()->absoluteQuads(quads);
@@ -935,7 +1021,131 @@ IntRect Element::boundsInRootViewSpace()
return result;
}
-PassRefPtr<ClientRectList> Element::getClientRects()
+static bool layoutOverflowRectContainsAllDescendants(const RenderBox& renderBox)
+{
+ if (renderBox.isRenderView())
+ return true;
+
+ if (!renderBox.element())
+ return false;
+
+ // If there are any position:fixed inside of us, game over.
+ if (auto* viewPositionedObjects = renderBox.view().positionedObjects()) {
+ for (auto* positionedBox : *viewPositionedObjects) {
+ if (positionedBox == &renderBox)
+ continue;
+ if (positionedBox->style().position() == FixedPosition && renderBox.element()->contains(positionedBox->element()))
+ return false;
+ }
+ }
+
+ if (renderBox.canContainAbsolutelyPositionedObjects()) {
+ // Our layout overflow will include all descendant positioned elements.
+ return true;
+ }
+
+ // This renderer may have positioned descendants whose containing block is some ancestor.
+ if (auto* containingBlock = renderBox.containingBlockForAbsolutePosition()) {
+ if (auto* positionedObjects = containingBlock->positionedObjects()) {
+ for (auto* positionedBox : *positionedObjects) {
+ if (positionedBox == &renderBox)
+ continue;
+ if (renderBox.element()->contains(positionedBox->element()))
+ return false;
+ }
+ }
+ }
+ return false;
+}
+
+LayoutRect Element::absoluteEventBounds(bool& boundsIncludeAllDescendantElements, bool& includesFixedPositionElements)
+{
+ boundsIncludeAllDescendantElements = false;
+ includesFixedPositionElements = false;
+
+ if (!renderer())
+ return LayoutRect();
+
+ LayoutRect result;
+ if (isSVGElement()) {
+ // Get the bounding rectangle from the SVG model.
+ SVGElement& svgElement = downcast<SVGElement>(*this);
+ FloatRect localRect;
+ if (svgElement.getBoundingBox(localRect, SVGLocatable::DisallowStyleUpdate))
+ result = LayoutRect(renderer()->localToAbsoluteQuad(localRect, UseTransforms, &includesFixedPositionElements).boundingBox());
+ } else {
+ auto* renderer = this->renderer();
+ if (is<RenderBox>(renderer)) {
+ auto& box = downcast<RenderBox>(*renderer);
+
+ bool computedBounds = false;
+
+ if (RenderFlowThread* flowThread = box.flowThreadContainingBlock()) {
+ bool wasFixed = false;
+ Vector<FloatQuad> quads;
+ FloatRect localRect(0, 0, box.width(), box.height());
+ if (flowThread->absoluteQuadsForBox(quads, &wasFixed, &box, localRect.y(), localRect.maxY())) {
+ FloatRect quadBounds = quads[0].boundingBox();
+ for (size_t i = 1; i < quads.size(); ++i)
+ quadBounds.unite(quads[i].boundingBox());
+
+ result = LayoutRect(quadBounds);
+ computedBounds = true;
+ } else {
+ // Probably columns. Just return the bounds of the multicol block for now.
+ // FIXME: this doesn't handle nested columns.
+ RenderElement* multicolContainer = flowThread->parent();
+ if (multicolContainer && is<RenderBox>(multicolContainer)) {
+ auto overflowRect = downcast<RenderBox>(*multicolContainer).layoutOverflowRect();
+ result = LayoutRect(multicolContainer->localToAbsoluteQuad(FloatRect(overflowRect), UseTransforms, &includesFixedPositionElements).boundingBox());
+ computedBounds = true;
+ }
+ }
+ }
+
+ if (!computedBounds) {
+ LayoutRect overflowRect = box.layoutOverflowRect();
+ result = LayoutRect(box.localToAbsoluteQuad(FloatRect(overflowRect), UseTransforms, &includesFixedPositionElements).boundingBox());
+ boundsIncludeAllDescendantElements = layoutOverflowRectContainsAllDescendants(box);
+ }
+ } else
+ result = LayoutRect(renderer->absoluteBoundingBoxRect(true /* useTransforms */, &includesFixedPositionElements));
+ }
+
+ return result;
+}
+
+LayoutRect Element::absoluteEventBoundsOfElementAndDescendants(bool& includesFixedPositionElements)
+{
+ bool boundsIncludeDescendants;
+ LayoutRect result = absoluteEventBounds(boundsIncludeDescendants, includesFixedPositionElements);
+ if (boundsIncludeDescendants)
+ return result;
+
+ for (auto& child : childrenOfType<Element>(*this)) {
+ bool includesFixedPosition = false;
+ LayoutRect childBounds = child.absoluteEventBoundsOfElementAndDescendants(includesFixedPosition);
+ includesFixedPositionElements |= includesFixedPosition;
+ result.unite(childBounds);
+ }
+
+ return result;
+}
+
+LayoutRect Element::absoluteEventHandlerBounds(bool& includesFixedPositionElements)
+{
+ // This is not web-exposed, so don't call the FOUC-inducing updateLayoutIgnorePendingStylesheets().
+ FrameView* frameView = document().view();
+ if (!frameView)
+ return LayoutRect();
+
+ if (frameView->needsLayout())
+ frameView->layout();
+
+ return absoluteEventBoundsOfElementAndDescendants(includesFixedPositionElements);
+}
+
+Ref<ClientRectList> Element::getClientRects()
{
document().updateLayoutIgnorePendingStylesheets();
@@ -952,21 +1162,18 @@ PassRefPtr<ClientRectList> Element::getClientRects()
return ClientRectList::create(quads);
}
-PassRefPtr<ClientRect> Element::getBoundingClientRect()
+Ref<ClientRect> Element::getBoundingClientRect()
{
document().updateLayoutIgnorePendingStylesheets();
Vector<FloatQuad> quads;
-#if ENABLE(SVG)
if (isSVGElement() && renderer() && !renderer()->isSVGRoot()) {
// Get the bounding rectangle from the SVG model.
- SVGElement* svgElement = toSVGElement(this);
+ SVGElement& svgElement = downcast<SVGElement>(*this);
FloatRect localRect;
- if (svgElement->getBoundingBox(localRect))
+ if (svgElement.getBoundingBox(localRect))
quads.append(renderer()->localToAbsoluteQuad(localRect));
- } else
-#endif
- {
+ } else {
// Get the bounding rectangle from the box model.
if (renderBoxModelObject())
renderBoxModelObject()->absoluteQuads(quads);
@@ -1012,19 +1219,18 @@ const AtomicString& Element::getAttributeNS(const AtomicString& namespaceURI, co
return getAttribute(QualifiedName(nullAtom, localName, namespaceURI));
}
-void Element::setAttribute(const AtomicString& localName, const AtomicString& value, ExceptionCode& ec)
+ExceptionOr<void> Element::setAttribute(const AtomicString& localName, const AtomicString& value)
{
- if (!Document::isValidName(localName)) {
- ec = INVALID_CHARACTER_ERR;
- return;
- }
+ if (!Document::isValidName(localName))
+ return Exception { INVALID_CHARACTER_ERR };
synchronizeAttribute(localName);
- const AtomicString& caseAdjustedLocalName = shouldIgnoreAttributeCase(*this) ? localName.lower() : localName;
-
+ auto caseAdjustedLocalName = shouldIgnoreAttributeCase(*this) ? localName.convertToASCIILowercase() : localName;
unsigned index = elementData() ? elementData()->findAttributeIndexByName(caseAdjustedLocalName, false) : ElementData::attributeNotFound;
- const QualifiedName& qName = index != ElementData::attributeNotFound ? attributeAt(index).name() : QualifiedName(nullAtom, caseAdjustedLocalName, nullAtom);
- setAttributeInternal(index, qName, value, NotInSynchronizationOfLazyAttribute);
+ auto name = index != ElementData::attributeNotFound ? attributeAt(index).name() : QualifiedName { nullAtom, caseAdjustedLocalName, nullAtom };
+ setAttributeInternal(index, name, value, NotInSynchronizationOfLazyAttribute);
+
+ return { };
}
void Element::setAttribute(const QualifiedName& name, const AtomicString& value)
@@ -1034,6 +1240,12 @@ void Element::setAttribute(const QualifiedName& name, const AtomicString& value)
setAttributeInternal(index, name, value, NotInSynchronizationOfLazyAttribute);
}
+void Element::setAttributeWithoutSynchronization(const QualifiedName& name, const AtomicString& value)
+{
+ unsigned index = elementData() ? elementData()->findAttributeIndexByName(name) : ElementData::attributeNotFound;
+ setAttributeInternal(index, name, value, NotInSynchronizationOfLazyAttribute);
+}
+
void Element::setSynchronizedLazyAttribute(const QualifiedName& name, const AtomicString& value)
{
unsigned index = elementData() ? elementData()->findAttributeIndexByName(name) : ElementData::attributeNotFound;
@@ -1053,85 +1265,83 @@ inline void Element::setAttributeInternal(unsigned index, const QualifiedName& n
return;
}
+ if (inSynchronizationOfLazyAttribute) {
+ ensureUniqueElementData().attributeAt(index).setValue(newValue);
+ return;
+ }
+
const Attribute& attribute = attributeAt(index);
+ QualifiedName attributeName = attribute.name();
AtomicString oldValue = attribute.value();
- bool valueChanged = newValue != oldValue;
- QualifiedName attributeName = (!inSynchronizationOfLazyAttribute || valueChanged) ? attribute.name() : name;
- if (!inSynchronizationOfLazyAttribute)
- willModifyAttribute(attributeName, oldValue, newValue);
+ willModifyAttribute(attributeName, oldValue, newValue);
- if (valueChanged) {
+ if (newValue != oldValue) {
// If there is an Attr node hooked to this attribute, the Attr::setValue() call below
// will write into the ElementData.
// FIXME: Refactor this so it makes some sense.
- if (RefPtr<Attr> attrNode = inSynchronizationOfLazyAttribute ? 0 : attrIfExists(attributeName))
+ if (RefPtr<Attr> attrNode = attrIfExists(attributeName))
attrNode->setValue(newValue);
- else
+ else {
+ Style::AttributeChangeInvalidation styleInvalidation(*this, name, oldValue, newValue);
ensureUniqueElementData().attributeAt(index).setValue(newValue);
+ }
}
- if (!inSynchronizationOfLazyAttribute)
- didModifyAttribute(attributeName, oldValue, newValue);
+ didModifyAttribute(attributeName, oldValue, newValue);
}
static inline AtomicString makeIdForStyleResolution(const AtomicString& value, bool inQuirksMode)
{
if (inQuirksMode)
- return value.lower();
+ return value.convertToASCIILowercase();
return value;
}
-static bool checkNeedsStyleInvalidationForIdChange(const AtomicString& oldId, const AtomicString& newId, StyleResolver* styleResolver)
-{
- ASSERT(newId != oldId);
- if (!oldId.isEmpty() && styleResolver->hasSelectorForId(oldId))
- return true;
- if (!newId.isEmpty() && styleResolver->hasSelectorForId(newId))
- return true;
- return false;
-}
-
void Element::attributeChanged(const QualifiedName& name, const AtomicString& oldValue, const AtomicString& newValue, AttributeModificationReason)
{
- parseAttribute(name, newValue);
-
- document().incDOMTreeVersion();
+ bool valueIsSameAsBefore = oldValue == newValue;
- if (oldValue == newValue)
- return;
+ if (!valueIsSameAsBefore) {
+ if (name == HTMLNames::idAttr) {
+ if (!oldValue.isEmpty())
+ treeScope().idTargetObserverRegistry().notifyObservers(*oldValue.impl());
+ if (!newValue.isEmpty())
+ treeScope().idTargetObserverRegistry().notifyObservers(*newValue.impl());
- StyleResolver* styleResolver = document().styleResolverIfExists();
- bool testShouldInvalidateStyle = inRenderedDocument() && styleResolver && styleChangeType() < FullStyleChange;
- bool shouldInvalidateStyle = false;
-
- if (isIdAttributeName(name)) {
- if (!oldValue.isEmpty())
- treeScope().idTargetObserverRegistry().notifyObservers(*oldValue.impl());
- if (!newValue.isEmpty())
- treeScope().idTargetObserverRegistry().notifyObservers(*newValue.impl());
-
- AtomicString oldId = elementData()->idForStyleResolution();
- AtomicString newId = makeIdForStyleResolution(newValue, document().inQuirksMode());
- if (newId != oldId) {
- elementData()->setIdForStyleResolution(newId);
- shouldInvalidateStyle = testShouldInvalidateStyle && checkNeedsStyleInvalidationForIdChange(oldId, newId, styleResolver);
+ AtomicString oldId = elementData()->idForStyleResolution();
+ AtomicString newId = makeIdForStyleResolution(newValue, document().inQuirksMode());
+ if (newId != oldId) {
+ Style::IdChangeInvalidation styleInvalidation(*this, oldId, newId);
+ elementData()->setIdForStyleResolution(newId);
+ }
+ } else if (name == classAttr)
+ classAttributeChanged(newValue);
+ else if (name == HTMLNames::nameAttr)
+ elementData()->setHasNameAttribute(!newValue.isNull());
+ else if (name == HTMLNames::pseudoAttr) {
+ if (needsStyleInvalidation() && isInShadowTree())
+ invalidateStyleForSubtree();
}
- } else if (name == classAttr)
- classAttributeChanged(newValue);
- else if (name == HTMLNames::nameAttr)
- elementData()->setHasNameAttribute(!newValue.isNull());
- else if (name == HTMLNames::pseudoAttr)
- shouldInvalidateStyle |= testShouldInvalidateStyle && isInShadowTree();
+ else if (name == HTMLNames::slotAttr) {
+ if (auto* parent = parentElement()) {
+ if (auto* shadowRoot = parent->shadowRoot())
+ shadowRoot->hostChildElementDidChangeSlotAttribute(*this, oldValue, newValue);
+ }
+ }
+ }
+ parseAttribute(name, newValue);
- invalidateNodeListAndCollectionCachesInAncestors(&name, this);
+ document().incDOMTreeVersion();
- // If there is currently no StyleResolver, we can't be sure that this attribute change won't affect style.
- shouldInvalidateStyle |= !styleResolver;
+ if (UNLIKELY(isDefinedCustomElement()))
+ CustomElementReactionQueue::enqueueAttributeChangedCallbackIfNeeded(*this, name, oldValue, newValue);
- if (shouldInvalidateStyle)
- setNeedsStyleRecalc();
+ if (valueIsSameAsBefore)
+ return;
+
+ invalidateNodeListAndCollectionCachesInAncestors(&name, this);
if (AXObjectCache* cache = document().existingAXObjectCache())
cache->handleAttributeChanged(name, this);
@@ -1164,74 +1374,114 @@ static inline bool classStringHasClassName(const AtomicString& newClassString)
return classStringHasClassName(newClassString.characters16(), length);
}
-static bool checkSelectorForClassChange(const SpaceSplitString& changedClasses, const StyleResolver& styleResolver)
+void Element::classAttributeChanged(const AtomicString& newClassString)
{
- unsigned changedSize = changedClasses.size();
- for (unsigned i = 0; i < changedSize; ++i) {
- if (styleResolver.hasSelectorForClass(changedClasses[i]))
- return true;
- }
- return false;
-}
+ // Note: We'll need ElementData, but it doesn't have to be UniqueElementData.
+ if (!elementData())
+ ensureUniqueElementData();
-static bool checkSelectorForClassChange(const SpaceSplitString& oldClasses, const SpaceSplitString& newClasses, const StyleResolver& styleResolver)
-{
- unsigned oldSize = oldClasses.size();
- if (!oldSize)
- return checkSelectorForClassChange(newClasses, styleResolver);
- BitVector remainingClassBits;
- remainingClassBits.ensureSize(oldSize);
- // Class vectors tend to be very short. This is faster than using a hash table.
- unsigned newSize = newClasses.size();
- for (unsigned i = 0; i < newSize; ++i) {
- bool foundFromBoth = false;
- for (unsigned j = 0; j < oldSize; ++j) {
- if (newClasses[i] == oldClasses[j]) {
- remainingClassBits.quickSet(j);
- foundFromBoth = true;
- }
- }
- if (foundFromBoth)
- continue;
- if (styleResolver.hasSelectorForClass(newClasses[i]))
- return true;
+ bool shouldFoldCase = document().inQuirksMode();
+ bool newStringHasClasses = classStringHasClassName(newClassString);
+
+ auto oldClassNames = elementData()->classNames();
+ auto newClassNames = newStringHasClasses ? SpaceSplitString(newClassString, shouldFoldCase) : SpaceSplitString();
+ {
+ Style::ClassChangeInvalidation styleInvalidation(*this, oldClassNames, newClassNames);
+ elementData()->setClassNames(newClassNames);
}
- for (unsigned i = 0; i < oldSize; ++i) {
- // If the bit is not set the the corresponding class has been removed.
- if (remainingClassBits.quickGet(i))
- continue;
- if (styleResolver.hasSelectorForClass(oldClasses[i]))
- return true;
+
+ if (hasRareData()) {
+ if (auto* classList = elementRareData()->classList())
+ classList->associatedAttributeValueChanged(newClassString);
}
- return false;
}
-void Element::classAttributeChanged(const AtomicString& newClassString)
+URL Element::absoluteLinkURL() const
{
- StyleResolver* styleResolver = document().styleResolverIfExists();
- bool testShouldInvalidateStyle = inRenderedDocument() && styleResolver && styleChangeType() < FullStyleChange;
- bool shouldInvalidateStyle = false;
-
- if (classStringHasClassName(newClassString)) {
- const bool shouldFoldCase = document().inQuirksMode();
- // Note: We'll need ElementData, but it doesn't have to be UniqueElementData.
- if (!elementData())
- ensureUniqueElementData();
- const SpaceSplitString oldClasses = elementData()->classNames();
- elementData()->setClass(newClassString, shouldFoldCase);
- const SpaceSplitString& newClasses = elementData()->classNames();
- shouldInvalidateStyle = testShouldInvalidateStyle && checkSelectorForClassChange(oldClasses, newClasses, *styleResolver);
- } else if (elementData()) {
- const SpaceSplitString& oldClasses = elementData()->classNames();
- shouldInvalidateStyle = testShouldInvalidateStyle && checkSelectorForClassChange(oldClasses, *styleResolver);
- elementData()->clearClass();
- }
+ if (!isLink())
+ return URL();
- if (hasRareData())
- elementRareData()->clearClassListValueForQuirksMode();
+ AtomicString linkAttribute;
+ if (hasTagName(SVGNames::aTag))
+ linkAttribute = getAttribute(XLinkNames::hrefAttr);
+ else
+ linkAttribute = getAttribute(HTMLNames::hrefAttr);
+
+ if (linkAttribute.isEmpty())
+ return URL();
- if (shouldInvalidateStyle)
- setNeedsStyleRecalc();
+ return document().completeURL(stripLeadingAndTrailingHTMLSpaces(linkAttribute));
+}
+
+#if ENABLE(TOUCH_EVENTS)
+bool Element::allowsDoubleTapGesture() const
+{
+ if (renderStyle() && renderStyle()->touchAction() != TouchAction::Auto)
+ return false;
+
+ Element* parent = parentElement();
+ return !parent || parent->allowsDoubleTapGesture();
+}
+#endif
+
+StyleResolver& Element::styleResolver()
+{
+ if (auto* shadowRoot = containingShadowRoot())
+ return shadowRoot->styleScope().resolver();
+
+ return document().styleScope().resolver();
+}
+
+ElementStyle Element::resolveStyle(const RenderStyle* parentStyle)
+{
+ return styleResolver().styleForElement(*this, parentStyle);
+}
+
+void Element::invalidateStyle()
+{
+ Node::invalidateStyle(Style::Validity::ElementInvalid);
+}
+
+void Element::invalidateStyleAndLayerComposition()
+{
+ Node::invalidateStyle(Style::Validity::ElementInvalid, Style::InvalidationMode::RecompositeLayer);
+}
+
+void Element::invalidateStyleForSubtree()
+{
+ Node::invalidateStyle(Style::Validity::SubtreeInvalid);
+}
+
+void Element::invalidateStyleAndRenderersForSubtree()
+{
+ Node::invalidateStyle(Style::Validity::SubtreeAndRenderersInvalid);
+}
+
+#if ENABLE(WEB_ANIMATIONS)
+WebAnimationVector Element::getAnimations()
+{
+ auto checkTarget = [this](AnimationEffect const& effect)
+ {
+ return (static_cast<KeyframeEffect const&>(effect).target() == this);
+ };
+
+ auto* document = DocumentAnimation::from(&this->document());
+ if (document)
+ return document->getAnimations(checkTarget);
+ return WebAnimationVector();
+}
+#endif
+
+bool Element::hasDisplayContents() const
+{
+ return hasRareData() && elementRareData()->hasDisplayContents();
+}
+
+void Element::setHasDisplayContents(bool value)
+{
+ if (hasDisplayContents() == value)
+ return;
+ ensureElementRareData().setHasDisplayContents(value);
}
// Returns true is the given attribute is an event handler.
@@ -1251,38 +1501,52 @@ bool Element::isJavaScriptURLAttribute(const Attribute& attribute) const
void Element::stripScriptingAttributes(Vector<Attribute>& attributeVector) const
{
- size_t destination = 0;
- for (size_t source = 0; source < attributeVector.size(); ++source) {
- if (isEventHandlerAttribute(attributeVector[source])
- || isJavaScriptURLAttribute(attributeVector[source])
- || isHTMLContentAttribute(attributeVector[source]))
- continue;
-
- if (source != destination)
- attributeVector[destination] = attributeVector[source];
-
- ++destination;
- }
- attributeVector.shrink(destination);
+ attributeVector.removeAllMatching([this](auto& attribute) -> bool {
+ return isEventHandlerAttribute(attribute)
+ || this->isJavaScriptURLAttribute(attribute)
+ || this->isHTMLContentAttribute(attribute);
+ });
}
void Element::parserSetAttributes(const Vector<Attribute>& attributeVector)
{
- ASSERT(!inDocument());
+ ASSERT(!isConnected());
ASSERT(!parentNode());
ASSERT(!m_elementData);
- if (attributeVector.isEmpty())
- return;
+ if (!attributeVector.isEmpty()) {
+ if (document().sharedObjectPool())
+ m_elementData = document().sharedObjectPool()->cachedShareableElementDataWithAttributes(attributeVector);
+ else
+ m_elementData = ShareableElementData::createWithAttributes(attributeVector);
- if (document().sharedObjectPool())
- m_elementData = document().sharedObjectPool()->cachedShareableElementDataWithAttributes(attributeVector);
- else
- m_elementData = ShareableElementData::createWithAttributes(attributeVector);
+ }
+
+ parserDidSetAttributes();
// Use attributeVector instead of m_elementData because attributeChanged might modify m_elementData.
- for (unsigned i = 0; i < attributeVector.size(); ++i)
- attributeChanged(attributeVector[i].name(), nullAtom, attributeVector[i].value(), ModifiedDirectly);
+ for (const auto& attribute : attributeVector)
+ attributeChanged(attribute.name(), nullAtom, attribute.value(), ModifiedDirectly);
+}
+
+void Element::parserDidSetAttributes()
+{
+}
+
+void Element::didMoveToNewDocument(Document& oldDocument)
+{
+ Node::didMoveToNewDocument(oldDocument);
+
+ if (oldDocument.inQuirksMode() != document().inQuirksMode()) {
+ // ElementData::m_classNames or ElementData::m_idForStyleResolution need to be updated with the right case.
+ if (hasID())
+ attributeChanged(idAttr, nullAtom, getIdAttribute());
+ if (hasClass())
+ attributeChanged(classAttr, nullAtom, getAttribute(classAttr));
+ }
+
+ if (UNLIKELY(isDefinedCustomElement()))
+ CustomElementReactionQueue::enqueueAdoptedCallbackIfNeeded(*this, oldDocument, document());
}
bool Element::hasAttributes() const
@@ -1314,72 +1578,60 @@ String Element::nodeNamePreservingCase() const
return m_tagName.toString();
}
-void Element::setPrefix(const AtomicString& prefix, ExceptionCode& ec)
+ExceptionOr<void> Element::setPrefix(const AtomicString& prefix)
{
- ec = 0;
- checkSetPrefix(prefix, ec);
- if (ec)
- return;
+ auto result = checkSetPrefix(prefix);
+ if (result.hasException())
+ return result.releaseException();
- m_tagName.setPrefix(prefix.isEmpty() ? AtomicString() : prefix);
-}
-
-URL Element::baseURI() const
-{
- const AtomicString& baseAttribute = getAttribute(baseAttr);
- URL base(URL(), baseAttribute);
- if (!base.protocol().isEmpty())
- return base;
-
- ContainerNode* parent = parentNode();
- if (!parent)
- return base;
-
- const URL& parentBase = parent->baseURI();
- if (parentBase.isNull())
- return base;
-
- return URL(parentBase, baseAttribute);
+ m_tagName.setPrefix(prefix.isEmpty() ? nullAtom : prefix);
+ return { };
}
const AtomicString& Element::imageSourceURL() const
{
- return getAttribute(srcAttr);
+ return attributeWithoutSynchronization(srcAttr);
}
bool Element::rendererIsNeeded(const RenderStyle& style)
{
- return style.display() != NONE;
+ return style.display() != NONE && style.display() != CONTENTS;
}
-RenderPtr<RenderElement> Element::createElementRenderer(PassRef<RenderStyle> style)
+RenderPtr<RenderElement> Element::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
{
- return RenderElement::createFor(*this, std::move(style));
+ return RenderElement::createFor(*this, WTFMove(style));
}
Node::InsertionNotificationRequest Element::insertedInto(ContainerNode& insertionPoint)
{
- bool wasInDocument = inDocument();
- // need to do superclass processing first so inDocument() is true
+ bool wasInDocument = isConnected();
+ // need to do superclass processing first so isConnected() is true
// by the time we reach updateId
ContainerNode::insertedInto(insertionPoint);
- ASSERT(!wasInDocument || inDocument());
+ ASSERT(!wasInDocument || isConnected());
#if ENABLE(FULLSCREEN_API)
if (containsFullScreenElement() && parentElement() && !parentElement()->containsFullScreenElement())
setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(true);
#endif
+ if (parentNode() == &insertionPoint) {
+ if (auto* shadowRoot = parentNode()->shadowRoot())
+ shadowRoot->hostChildElementDidChange(*this);
+ }
+
if (!insertionPoint.isInTreeScope())
return InsertionDone;
- if (hasRareData())
- elementRareData()->clearClassListValueForQuirksMode();
-
+ // This function could be called when this element's shadow root's host or its ancestor is inserted.
+ // This element is new to the shadow tree (and its tree scope) only if the parent into which this element
+ // or its ancestor is inserted belongs to the same tree scope as this element's.
TreeScope* newScope = &insertionPoint.treeScope();
- HTMLDocument* newDocument = !wasInDocument && inDocument() && newScope->documentScope()->isHTMLDocument() ? toHTMLDocument(newScope->documentScope()) : 0;
+ bool becomeConnected = !wasInDocument && isConnected();
+ HTMLDocument* newDocument = becomeConnected && is<HTMLDocument>(newScope->documentScope()) ? &downcast<HTMLDocument>(newScope->documentScope()) : nullptr;
if (newScope != &treeScope())
- newScope = 0;
+ newScope = nullptr;
const AtomicString& idValue = getIdAttribute();
if (!idValue.isNull()) {
@@ -1399,7 +1651,14 @@ Node::InsertionNotificationRequest Element::insertedInto(ContainerNode& insertio
if (newScope && hasTagName(labelTag)) {
if (newScope->shouldCacheLabelsByForAttribute())
- updateLabel(*newScope, nullAtom, fastGetAttribute(forAttr));
+ updateLabel(*newScope, nullAtom, attributeWithoutSynchronization(forAttr));
+ }
+
+ if (becomeConnected) {
+ if (UNLIKELY(isCustomElementUpgradeCandidate()))
+ CustomElementReactionQueue::enqueueElementUpgradeIfDefined(*this);
+ if (UNLIKELY(isDefinedCustomElement()))
+ CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded(*this);
}
return InsertionDone;
@@ -1413,16 +1672,20 @@ void Element::removedFrom(ContainerNode& insertionPoint)
#endif
#if ENABLE(POINTER_LOCK)
if (document().page())
- document().page()->pointerLockController()->elementRemoved(this);
+ document().page()->pointerLockController().elementRemoved(*this);
#endif
- setSavedLayerScrollOffset(IntSize());
+ setSavedLayerScrollPosition(ScrollPosition());
if (insertionPoint.isInTreeScope()) {
TreeScope* oldScope = &insertionPoint.treeScope();
- HTMLDocument* oldDocument = inDocument() && oldScope->documentScope()->isHTMLDocument() ? toHTMLDocument(oldScope->documentScope()) : 0;
- if (oldScope != &treeScope())
- oldScope = 0;
+ bool becomeDisconnected = isConnected();
+ HTMLDocument* oldDocument = becomeDisconnected && is<HTMLDocument>(oldScope->documentScope()) ? &downcast<HTMLDocument>(oldScope->documentScope()) : nullptr;
+
+ // ContainerNode::removeBetween always sets the removed chid's tree scope to Document's but InTreeScope flag is unset in Node::removedFrom.
+ // So this element has been removed from the old tree scope only if InTreeScope flag is set and this element's tree scope is Document's.
+ if (!isInTreeScope() || &treeScope() != &document())
+ oldScope = nullptr;
const AtomicString& idValue = getIdAttribute();
if (!idValue.isNull()) {
@@ -1442,75 +1705,65 @@ void Element::removedFrom(ContainerNode& insertionPoint)
if (oldScope && hasTagName(labelTag)) {
if (oldScope->shouldCacheLabelsByForAttribute())
- updateLabel(*oldScope, fastGetAttribute(forAttr), nullAtom);
+ updateLabel(*oldScope, attributeWithoutSynchronization(forAttr), nullAtom);
}
+
+ if (becomeDisconnected && UNLIKELY(isDefinedCustomElement()))
+ CustomElementReactionQueue::enqueueDisconnectedCallbackIfNeeded(*this);
+ }
+
+ if (!parentNode()) {
+ if (auto* shadowRoot = insertionPoint.shadowRoot())
+ shadowRoot->hostChildElementDidChange(*this);
}
ContainerNode::removedFrom(insertionPoint);
-#if ENABLE(SVG)
+
if (hasPendingResources())
- document().accessSVGExtensions()->removeElementFromPendingResources(this);
+ document().accessSVGExtensions().removeElementFromPendingResources(this);
+
+
+#if PLATFORM(MAC)
+ if (Frame* frame = document().frame())
+ frame->mainFrame().removeLatchingStateForTarget(*this);
#endif
}
void Element::unregisterNamedFlowContentElement()
{
- if (document().cssRegionsEnabled() && inNamedFlow() && document().renderView())
+ if (isNamedFlowContentElement() && document().renderView())
document().renderView()->flowThreadController().unregisterNamedFlowContentElement(*this);
}
-PassRef<RenderStyle> Element::styleForRenderer()
-{
- if (hasCustomStyleResolveCallbacks()) {
- if (RefPtr<RenderStyle> style = customStyleForRenderer())
- return style.releaseNonNull();
- }
-
- return document().ensureStyleResolver().styleForElement(this);
-}
-
ShadowRoot* Element::shadowRoot() const
{
- return hasRareData() ? elementRareData()->shadowRoot() : 0;
-}
-
-void Element::didAffectSelector(AffectedSelectorMask)
-{
- setNeedsStyleRecalc();
-}
-
-static bool shouldUseNodeRenderingTraversalSlowPath(const Element& element)
-{
- if (element.isShadowRoot())
- return true;
- if (element.isPseudoElement() || element.beforePseudoElement() || element.afterPseudoElement())
- return true;
- return element.isInsertionPoint() || element.shadowRoot();
-}
-
-void Element::resetNeedsNodeRenderingTraversalSlowPath()
-{
- setNeedsNodeRenderingTraversalSlowPath(shouldUseNodeRenderingTraversalSlowPath(*this));
+ return hasRareData() ? elementRareData()->shadowRoot() : nullptr;
}
-void Element::addShadowRoot(PassRefPtr<ShadowRoot> newShadowRoot)
+void Element::addShadowRoot(Ref<ShadowRoot>&& newShadowRoot)
{
ASSERT(!shadowRoot());
+
+ if (renderer())
+ RenderTreeUpdater::tearDownRenderers(*this);
- ShadowRoot* shadowRoot = newShadowRoot.get();
- ensureElementRareData().setShadowRoot(newShadowRoot);
+ ShadowRoot& shadowRoot = newShadowRoot;
+ ensureElementRareData().setShadowRoot(WTFMove(newShadowRoot));
- shadowRoot->setHostElement(this);
- shadowRoot->setParentTreeScope(&treeScope());
- shadowRoot->distributor().didShadowBoundaryChange(this);
+ shadowRoot.setHost(this);
+ shadowRoot.setParentTreeScope(treeScope());
- ChildNodeInsertionNotifier(*this).notify(*shadowRoot);
+ NodeVector postInsertionNotificationTargets;
+ notifyChildNodeInserted(*this, shadowRoot, postInsertionNotificationTargets);
+ for (auto& target : postInsertionNotificationTargets)
+ target->finishedInsertingSubtree();
- resetNeedsNodeRenderingTraversalSlowPath();
+ invalidateStyleAndRenderersForSubtree();
- setNeedsStyleRecalc(ReconstructRenderTree);
+ InspectorInstrumentation::didPushShadowRoot(*this, shadowRoot);
- InspectorInstrumentation::didPushShadowRoot(this, shadowRoot);
+ if (shadowRoot.mode() == ShadowRootMode::UserAgent)
+ didAddUserAgentShadowRoot(&shadowRoot);
}
void Element::removeShadowRoot()
@@ -1518,71 +1771,147 @@ void Element::removeShadowRoot()
RefPtr<ShadowRoot> oldRoot = shadowRoot();
if (!oldRoot)
return;
- InspectorInstrumentation::willPopShadowRoot(this, oldRoot.get());
- document().removeFocusedNodeOfSubtree(oldRoot.get());
+
+ InspectorInstrumentation::willPopShadowRoot(*this, *oldRoot);
+ document().removeFocusedNodeOfSubtree(*oldRoot);
ASSERT(!oldRoot->renderer());
elementRareData()->clearShadowRoot();
- oldRoot->setHostElement(0);
- oldRoot->setParentTreeScope(&document());
-
- ChildNodeRemovalNotifier(*this).notify(*oldRoot);
+ oldRoot->setHost(nullptr);
+ oldRoot->setParentTreeScope(document());
+}
+
+static bool canAttachAuthorShadowRoot(const Element& element)
+{
+ static NeverDestroyed<HashSet<AtomicString>> tagNames = [] {
+ static const HTMLQualifiedName* const tagList[] = {
+ &articleTag,
+ &asideTag,
+ &blockquoteTag,
+ &bodyTag,
+ &divTag,
+ &footerTag,
+ &h1Tag,
+ &h2Tag,
+ &h3Tag,
+ &h4Tag,
+ &h5Tag,
+ &h6Tag,
+ &headerTag,
+ &navTag,
+ &pTag,
+ &sectionTag,
+ &spanTag
+ };
+ HashSet<AtomicString> set;
+ for (auto& name : tagList)
+ set.add(name->localName());
+ return set;
+ }();
+
+ if (!is<HTMLElement>(element))
+ return false;
- oldRoot->distributor().invalidateDistribution(this);
+ const auto& localName = element.localName();
+ return tagNames.get().contains(localName) || Document::validateCustomElementName(localName) == CustomElementNameValidationStatus::Valid;
}
-PassRefPtr<ShadowRoot> Element::createShadowRoot(ExceptionCode& ec)
+ExceptionOr<ShadowRoot&> Element::attachShadow(const ShadowRootInit& init)
{
- if (alwaysCreateUserAgentShadowRoot())
- ensureUserAgentShadowRoot();
-
-#if ENABLE(SHADOW_DOM)
- if (RuntimeEnabledFeatures::sharedFeatures().authorShadowDOMForAnyElementEnabled()) {
- addShadowRoot(ShadowRoot::create(document(), ShadowRoot::AuthorShadowRoot));
- return shadowRoot();
- }
-#endif
+ if (!canAttachAuthorShadowRoot(*this))
+ return Exception { NOT_SUPPORTED_ERR };
+ if (shadowRoot())
+ return Exception { INVALID_STATE_ERR };
+ if (init.mode == ShadowRootMode::UserAgent)
+ return Exception { TypeError };
+ auto shadow = ShadowRoot::create(document(), init.mode);
+ auto& result = shadow.get();
+ addShadowRoot(WTFMove(shadow));
+ return result;
+}
- // Since some elements recreates shadow root dynamically, multiple shadow
- // subtrees won't work well in that element. Until they are fixed, we disable
- // adding author shadow root for them.
- if (!areAuthorShadowsAllowed()) {
- ec = HIERARCHY_REQUEST_ERR;
- return 0;
- }
- addShadowRoot(ShadowRoot::create(document(), ShadowRoot::AuthorShadowRoot));
+ShadowRoot* Element::shadowRootForBindings(JSC::ExecState& state) const
+{
+ auto* shadow = shadowRoot();
+ if (!shadow)
+ return nullptr;
+ if (shadow->mode() == ShadowRootMode::Open)
+ return shadow;
+ if (JSC::jsCast<JSDOMGlobalObject*>(state.lexicalGlobalObject())->world().shadowRootIsAlwaysOpen())
+ return shadow;
+ return nullptr;
+}
+ShadowRoot* Element::userAgentShadowRoot() const
+{
+ ASSERT(!shadowRoot() || shadowRoot()->mode() == ShadowRootMode::UserAgent);
return shadowRoot();
}
-ShadowRoot* Element::authorShadowRoot() const
+ShadowRoot& Element::ensureUserAgentShadowRoot()
{
- ShadowRoot* shadowRoot = this->shadowRoot();
- if (shadowRoot->type() == ShadowRoot::AuthorShadowRoot)
- return shadowRoot;
- return 0;
+ if (auto* shadow = userAgentShadowRoot())
+ return *shadow;
+ auto newShadow = ShadowRoot::create(document(), ShadowRootMode::UserAgent);
+ ShadowRoot& shadow = newShadow;
+ addShadowRoot(WTFMove(newShadow));
+ return shadow;
}
-ShadowRoot* Element::userAgentShadowRoot() const
+void Element::setIsDefinedCustomElement(JSCustomElementInterface& elementInterface)
{
- if (ShadowRoot* shadowRoot = this->shadowRoot()) {
- ASSERT(shadowRoot->type() == ShadowRoot::UserAgentShadowRoot);
- return shadowRoot;
- }
- return 0;
+ clearFlag(IsEditingTextOrUndefinedCustomElementFlag);
+ setFlag(IsCustomElement);
+ auto& data = ensureElementRareData();
+ if (!data.customElementReactionQueue())
+ data.setCustomElementReactionQueue(std::make_unique<CustomElementReactionQueue>(elementInterface));
+ InspectorInstrumentation::didChangeCustomElementState(*this);
}
-ShadowRoot& Element::ensureUserAgentShadowRoot()
+void Element::setIsFailedCustomElement(JSCustomElementInterface&)
{
- ShadowRoot* shadowRoot = userAgentShadowRoot();
- if (!shadowRoot) {
- addShadowRoot(ShadowRoot::create(document(), ShadowRoot::UserAgentShadowRoot));
- shadowRoot = userAgentShadowRoot();
- didAddUserAgentShadowRoot(shadowRoot);
+ ASSERT(isUndefinedCustomElement());
+ ASSERT(getFlag(IsEditingTextOrUndefinedCustomElementFlag));
+ clearFlag(IsCustomElement);
+
+ if (hasRareData()) {
+ // Clear the queue instead of deleting it since this function can be called inside CustomElementReactionQueue::invokeAll during upgrades.
+ if (auto* queue = elementRareData()->customElementReactionQueue())
+ queue->clear();
}
- return *shadowRoot;
+ InspectorInstrumentation::didChangeCustomElementState(*this);
+}
+
+void Element::setIsCustomElementUpgradeCandidate()
+{
+ ASSERT(!getFlag(IsCustomElement));
+ setFlag(IsCustomElement);
+ setFlag(IsEditingTextOrUndefinedCustomElementFlag);
+ InspectorInstrumentation::didChangeCustomElementState(*this);
+}
+
+void Element::enqueueToUpgrade(JSCustomElementInterface& elementInterface)
+{
+ ASSERT(!isDefinedCustomElement() && !isFailedCustomElement());
+ setFlag(IsCustomElement);
+ setFlag(IsEditingTextOrUndefinedCustomElementFlag);
+ InspectorInstrumentation::didChangeCustomElementState(*this);
+
+ auto& data = ensureElementRareData();
+ ASSERT(!data.customElementReactionQueue());
+
+ data.setCustomElementReactionQueue(std::make_unique<CustomElementReactionQueue>(elementInterface));
+ data.customElementReactionQueue()->enqueueElementUpgrade(*this);
+}
+
+CustomElementReactionQueue* Element::reactionQueue() const
+{
+ ASSERT(isDefinedCustomElement() || isCustomElementUpgradeCandidate());
+ if (!hasRareData())
+ return nullptr;
+ return elementRareData()->customElementReactionQueue();
}
const AtomicString& Element::shadowPseudoId() const
@@ -1598,7 +1927,6 @@ bool Element::childTypeAllowed(NodeType type) const
case COMMENT_NODE:
case PROCESSING_INSTRUCTION_NODE:
case CDATA_SECTION_NODE:
- case ENTITY_REFERENCE_NODE:
return true;
default:
break;
@@ -1606,90 +1934,138 @@ bool Element::childTypeAllowed(NodeType type) const
return false;
}
-static void checkForEmptyStyleChange(Element* element, RenderStyle* style)
+static void checkForEmptyStyleChange(Element& element)
{
- if (!style && !element->styleAffectedByEmpty())
- return;
-
- if (!style || (element->styleAffectedByEmpty() && (!style->emptyState() || element->hasChildNodes())))
- element->setNeedsStyleRecalc();
+ if (element.styleAffectedByEmpty()) {
+ auto* style = element.renderStyle();
+ if (!style || (!style->emptyState() || element.hasChildNodes()))
+ element.invalidateStyleForSubtree();
+ }
}
enum SiblingCheckType { FinishedParsingChildren, SiblingElementRemoved, Other };
-static void checkForSiblingStyleChanges(Element* parent, SiblingCheckType checkType, Element* elementBeforeChange, Element* elementAfterChange)
+static void checkForSiblingStyleChanges(Element& parent, SiblingCheckType checkType, Element* elementBeforeChange, Element* elementAfterChange)
{
- RenderStyle* style = parent->renderStyle();
// :empty selector.
- checkForEmptyStyleChange(parent, style);
-
- if (!style || (parent->needsStyleRecalc() && parent->childrenAffectedByPositionalRules()))
+ checkForEmptyStyleChange(parent);
+
+ if (parent.styleValidity() >= Style::Validity::SubtreeInvalid)
return;
// :first-child. In the parser callback case, we don't have to check anything, since we were right the first time.
// In the DOM case, we only need to do something if |afterChange| is not 0.
// |afterChange| is 0 in the parser case, so it works out that we'll skip this block.
- if (parent->childrenAffectedByFirstChildRules() && elementAfterChange) {
+ if (parent.childrenAffectedByFirstChildRules() && elementAfterChange) {
// Find our new first child.
Element* newFirstElement = ElementTraversal::firstChild(parent);
// Find the first element node following |afterChange|
// This is the insert/append case.
- if (newFirstElement != elementAfterChange && elementAfterChange->renderStyle() && elementAfterChange->renderStyle()->firstChildState())
- elementAfterChange->setNeedsStyleRecalc();
-
+ if (newFirstElement != elementAfterChange) {
+ auto* style = elementAfterChange->renderStyle();
+ if (!style || style->firstChildState())
+ elementAfterChange->invalidateStyleForSubtree();
+ }
+
// We also have to handle node removal.
- if (checkType == SiblingElementRemoved && newFirstElement == elementAfterChange && newFirstElement && (!newFirstElement->renderStyle() || !newFirstElement->renderStyle()->firstChildState()))
- newFirstElement->setNeedsStyleRecalc();
+ if (checkType == SiblingElementRemoved && newFirstElement == elementAfterChange && newFirstElement) {
+ auto* style = newFirstElement->renderStyle();
+ if (!style || !style->firstChildState())
+ newFirstElement->invalidateStyleForSubtree();
+ }
}
// :last-child. In the parser callback case, we don't have to check anything, since we were right the first time.
// In the DOM case, we only need to do something if |afterChange| is not 0.
- if (parent->childrenAffectedByLastChildRules() && elementBeforeChange) {
+ if (parent.childrenAffectedByLastChildRules() && elementBeforeChange) {
// Find our new last child.
Element* newLastElement = ElementTraversal::lastChild(parent);
- if (newLastElement != elementBeforeChange && elementBeforeChange->renderStyle() && elementBeforeChange->renderStyle()->lastChildState())
- elementBeforeChange->setNeedsStyleRecalc();
-
+ if (newLastElement != elementBeforeChange) {
+ auto* style = elementBeforeChange->renderStyle();
+ if (!style || style->lastChildState())
+ elementBeforeChange->invalidateStyleForSubtree();
+ }
+
// We also have to handle node removal. The parser callback case is similar to node removal as well in that we need to change the last child
// to match now.
- if ((checkType == SiblingElementRemoved || checkType == FinishedParsingChildren) && newLastElement == elementBeforeChange && newLastElement
- && (!newLastElement->renderStyle() || !newLastElement->renderStyle()->lastChildState()))
- newLastElement->setNeedsStyleRecalc();
+ if ((checkType == SiblingElementRemoved || checkType == FinishedParsingChildren) && newLastElement == elementBeforeChange && newLastElement) {
+ auto* style = newLastElement->renderStyle();
+ if (!style || !style->lastChildState())
+ newLastElement->invalidateStyleForSubtree();
+ }
}
- // The + selector. We need to invalidate the first element following the insertion point. It is the only possible element
- // that could be affected by this DOM change.
- if (parent->childrenAffectedByDirectAdjacentRules() && elementAfterChange) {
- elementAfterChange->setNeedsStyleRecalc();
+ if (elementAfterChange) {
+ if (elementAfterChange->styleIsAffectedByPreviousSibling())
+ elementAfterChange->invalidateStyleForSubtree();
+ else if (elementAfterChange->affectsNextSiblingElementStyle()) {
+ Element* elementToInvalidate = elementAfterChange;
+ do {
+ elementToInvalidate = elementToInvalidate->nextElementSibling();
+ } while (elementToInvalidate && !elementToInvalidate->styleIsAffectedByPreviousSibling());
+
+ if (elementToInvalidate)
+ elementToInvalidate->invalidateStyleForSubtree();
+ }
}
- // Forward positional selectors include the ~ selector, nth-child, nth-of-type, first-of-type and only-of-type.
// Backward positional selectors include nth-last-child, nth-last-of-type, last-of-type and only-of-type.
// We have to invalidate everything following the insertion point in the forward case, and everything before the insertion point in the
// backward case.
// |afterChange| is 0 in the parser callback case, so we won't do any work for the forward case if we don't have to.
// For performance reasons we just mark the parent node as changed, since we don't want to make childrenChanged O(n^2) by crawling all our kids
// here. recalcStyle will then force a walk of the children when it sees that this has happened.
- if (parent->childrenAffectedByForwardPositionalRules() && elementAfterChange)
- parent->setNeedsStyleRecalc();
- if (parent->childrenAffectedByBackwardPositionalRules() && elementBeforeChange)
- parent->setNeedsStyleRecalc();
+ if (parent.childrenAffectedByBackwardPositionalRules() && elementBeforeChange)
+ parent.invalidateStyleForSubtree();
}
void Element::childrenChanged(const ChildChange& change)
{
ContainerNode::childrenChanged(change);
if (change.source == ChildChangeSourceParser)
- checkForEmptyStyleChange(this, renderStyle());
+ checkForEmptyStyleChange(*this);
else {
SiblingCheckType checkType = change.type == ElementRemoved ? SiblingElementRemoved : Other;
- checkForSiblingStyleChanges(this, checkType, change.previousSiblingElement, change.nextSiblingElement);
+ checkForSiblingStyleChanges(*this, checkType, change.previousSiblingElement, change.nextSiblingElement);
}
- if (ShadowRoot* shadowRoot = this->shadowRoot())
- shadowRoot->invalidateDistribution();
+ if (ShadowRoot* shadowRoot = this->shadowRoot()) {
+ switch (change.type) {
+ case ElementInserted:
+ case ElementRemoved:
+ // For elements, we notify shadowRoot in Element::insertedInto and Element::removedFrom.
+ break;
+ case AllChildrenRemoved:
+ case AllChildrenReplaced:
+ shadowRoot->didRemoveAllChildrenOfShadowHost();
+ break;
+ case TextInserted:
+ case TextRemoved:
+ case TextChanged:
+ shadowRoot->didChangeDefaultSlot();
+ break;
+ case NonContentsChildInserted:
+ case NonContentsChildRemoved:
+ break;
+ }
+ }
+}
+
+void Element::setAttributeEventListener(const AtomicString& eventType, const QualifiedName& attributeName, const AtomicString& attributeValue)
+{
+ setAttributeEventListener(eventType, JSLazyEventListener::create(*this, attributeName, attributeValue), mainThreadNormalWorld());
+}
+
+void Element::setIsNamedFlowContentElement()
+{
+ ensureElementRareData().setIsNamedFlowContentElement(true);
+}
+
+void Element::clearIsNamedFlowContentElement()
+{
+ ensureElementRareData().setIsNamedFlowContentElement(false);
}
void Element::removeAllEventListeners()
@@ -1702,20 +2078,16 @@ void Element::removeAllEventListeners()
void Element::beginParsingChildren()
{
clearIsParsingChildrenFinished();
- if (auto styleResolver = document().styleResolverIfExists())
- styleResolver->pushParentElement(this);
}
void Element::finishParsingChildren()
{
ContainerNode::finishParsingChildren();
setIsParsingChildrenFinished();
- checkForSiblingStyleChanges(this, FinishedParsingChildren, ElementTraversal::lastChild(this), nullptr);
- if (auto styleResolver = document().styleResolverIfExists())
- styleResolver->popParentElement(this);
+ checkForSiblingStyleChanges(*this, FinishedParsingChildren, ElementTraversal::lastChild(*this), nullptr);
}
-#ifndef NDEBUG
+#if ENABLE(TREE_DEBUGGING)
void Element::formatForDebugger(char* buffer, unsigned length) const
{
StringBuilder result;
@@ -1746,103 +2118,149 @@ void Element::formatForDebugger(char* buffer, unsigned length) const
const Vector<RefPtr<Attr>>& Element::attrNodeList()
{
ASSERT(hasSyntheticAttrChildNodes());
- return *attrNodeListForElement(this);
+ return *attrNodeListForElement(*this);
}
-PassRefPtr<Attr> Element::setAttributeNode(Attr* attrNode, ExceptionCode& ec)
+void Element::attachAttributeNodeIfNeeded(Attr& attrNode)
{
- if (!attrNode) {
- ec = TYPE_MISMATCH_ERR;
- return 0;
- }
+ ASSERT(!attrNode.ownerElement() || attrNode.ownerElement() == this);
+ if (attrNode.ownerElement() == this)
+ return;
+
+ NoEventDispatchAssertion assertNoEventDispatch;
- RefPtr<Attr> oldAttrNode = attrIfExists(attrNode->qualifiedName());
- if (oldAttrNode.get() == attrNode)
- return attrNode; // This Attr is already attached to the element.
+ attrNode.attachToElement(*this);
+ treeScope().adoptIfNeeded(attrNode);
+ ensureAttrNodeListForElement(*this).append(&attrNode);
+}
+
+ExceptionOr<RefPtr<Attr>> Element::setAttributeNode(Attr& attrNode)
+{
+ RefPtr<Attr> oldAttrNode = attrIfExists(attrNode.localName(), shouldIgnoreAttributeCase(*this));
+ if (oldAttrNode.get() == &attrNode)
+ return WTFMove(oldAttrNode);
// INUSE_ATTRIBUTE_ERR: 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 (attrNode->ownerElement()) {
- ec = INUSE_ATTRIBUTE_ERR;
- return 0;
- }
+ if (attrNode.ownerElement() && attrNode.ownerElement() != this)
+ return Exception { INUSE_ATTRIBUTE_ERR };
+ {
+ NoEventDispatchAssertion assertNoEventDispatch;
synchronizeAllAttributes();
- UniqueElementData& elementData = ensureUniqueElementData();
+ }
- unsigned index = elementData.findAttributeIndexByNameForAttributeNode(attrNode, shouldIgnoreAttributeCase(*this));
- if (index != ElementData::attributeNotFound) {
+ auto& elementData = ensureUniqueElementData();
+
+ auto existingAttributeIndex = elementData.findAttributeIndexByName(attrNode.localName(), shouldIgnoreAttributeCase(*this));
+
+ // Attr::value() will return its 'm_standaloneValue' member any time its Element is set to nullptr. We need to cache this value
+ // before making changes to attrNode's Element connections.
+ auto attrNodeValue = attrNode.value();
+
+ if (existingAttributeIndex == ElementData::attributeNotFound) {
+ attachAttributeNodeIfNeeded(attrNode);
+ setAttributeInternal(elementData.findAttributeIndexByName(attrNode.qualifiedName()), attrNode.qualifiedName(), attrNodeValue, NotInSynchronizationOfLazyAttribute);
+ } else {
+ const Attribute& attribute = attributeAt(existingAttributeIndex);
if (oldAttrNode)
- detachAttrNodeFromElementWithValue(oldAttrNode.get(), elementData.attributeAt(index).value());
+ detachAttrNodeFromElementWithValue(oldAttrNode.get(), attribute.value());
else
- oldAttrNode = Attr::create(document(), attrNode->qualifiedName(), elementData.attributeAt(index).value());
- }
+ oldAttrNode = Attr::create(document(), attrNode.qualifiedName(), attribute.value());
- setAttributeInternal(index, attrNode->qualifiedName(), attrNode->value(), NotInSynchronizationOfLazyAttribute);
+ attachAttributeNodeIfNeeded(attrNode);
- attrNode->attachToElement(this);
- treeScope().adoptIfNeeded(attrNode);
- ensureAttrNodeListForElement(this).append(attrNode);
+ if (attribute.name().matches(attrNode.qualifiedName()))
+ setAttributeInternal(existingAttributeIndex, attrNode.qualifiedName(), attrNodeValue, NotInSynchronizationOfLazyAttribute);
+ else {
+ removeAttributeInternal(existingAttributeIndex, NotInSynchronizationOfLazyAttribute);
+ setAttributeInternal(ensureUniqueElementData().findAttributeIndexByName(attrNode.qualifiedName()), attrNode.qualifiedName(), attrNodeValue, NotInSynchronizationOfLazyAttribute);
+ }
+ }
- return oldAttrNode.release();
+ return WTFMove(oldAttrNode);
}
-PassRefPtr<Attr> Element::setAttributeNodeNS(Attr* attr, ExceptionCode& ec)
+ExceptionOr<RefPtr<Attr>> Element::setAttributeNodeNS(Attr& attrNode)
{
- return setAttributeNode(attr, ec);
-}
+ RefPtr<Attr> oldAttrNode = attrIfExists(attrNode.qualifiedName());
+ if (oldAttrNode.get() == &attrNode)
+ return WTFMove(oldAttrNode);
-PassRefPtr<Attr> Element::removeAttributeNode(Attr* attr, ExceptionCode& ec)
-{
- if (!attr) {
- ec = TYPE_MISMATCH_ERR;
- return 0;
- }
- if (attr->ownerElement() != this) {
- ec = NOT_FOUND_ERR;
- return 0;
- }
+ // INUSE_ATTRIBUTE_ERR: 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 (attrNode.ownerElement() && attrNode.ownerElement() != this)
+ return Exception { INUSE_ATTRIBUTE_ERR };
- ASSERT(&document() == &attr->document());
+ unsigned index = 0;
+
+ // Attr::value() will return its 'm_standaloneValue' member any time its Element is set to nullptr. We need to cache this value
+ // before making changes to attrNode's Element connections.
+ auto attrNodeValue = attrNode.value();
- synchronizeAttribute(attr->qualifiedName());
+ {
+ NoEventDispatchAssertion assertNoEventDispatch;
+ synchronizeAllAttributes();
+ auto& elementData = ensureUniqueElementData();
- unsigned index = elementData()->findAttributeIndexByNameForAttributeNode(attr);
- if (index == ElementData::attributeNotFound) {
- ec = NOT_FOUND_ERR;
- return 0;
+ index = elementData.findAttributeIndexByName(attrNode.qualifiedName());
+
+ if (index != ElementData::attributeNotFound) {
+ if (oldAttrNode)
+ detachAttrNodeFromElementWithValue(oldAttrNode.get(), elementData.attributeAt(index).value());
+ else
+ oldAttrNode = Attr::create(document(), attrNode.qualifiedName(), elementData.attributeAt(index).value());
+ }
}
- RefPtr<Attr> attrNode = attr;
- detachAttrNodeFromElementWithValue(attr, elementData()->attributeAt(index).value());
- removeAttributeInternal(index, NotInSynchronizationOfLazyAttribute);
- return attrNode.release();
+ attachAttributeNodeIfNeeded(attrNode);
+ setAttributeInternal(index, attrNode.qualifiedName(), attrNodeValue, NotInSynchronizationOfLazyAttribute);
+
+ return WTFMove(oldAttrNode);
}
-bool Element::parseAttributeName(QualifiedName& out, const AtomicString& namespaceURI, const AtomicString& qualifiedName, ExceptionCode& ec)
+ExceptionOr<Ref<Attr>> Element::removeAttributeNode(Attr& attr)
{
- String prefix, localName;
- if (!Document::parseQualifiedName(qualifiedName, prefix, localName, ec))
- return false;
- ASSERT(!ec);
+ if (attr.ownerElement() != this)
+ return Exception { NOT_FOUND_ERR };
- QualifiedName qName(prefix, localName, namespaceURI);
+ ASSERT(&document() == &attr.document());
- if (!Document::hasValidNamespaceForAttributes(qName)) {
- ec = NAMESPACE_ERR;
- return false;
- }
+ synchronizeAllAttributes();
- out = qName;
- return true;
+ if (!m_elementData)
+ return Exception { NOT_FOUND_ERR };
+
+ auto existingAttributeIndex = m_elementData->findAttributeIndexByName(attr.qualifiedName());
+ if (existingAttributeIndex == ElementData::attributeNotFound)
+ return Exception { NOT_FOUND_ERR };
+
+ Ref<Attr> oldAttrNode { attr };
+
+ detachAttrNodeFromElementWithValue(&attr, m_elementData->attributeAt(existingAttributeIndex).value());
+ removeAttributeInternal(existingAttributeIndex, NotInSynchronizationOfLazyAttribute);
+
+ return WTFMove(oldAttrNode);
}
-void Element::setAttributeNS(const AtomicString& namespaceURI, const AtomicString& qualifiedName, const AtomicString& value, ExceptionCode& ec)
+ExceptionOr<QualifiedName> Element::parseAttributeName(const AtomicString& namespaceURI, const AtomicString& qualifiedName)
{
- QualifiedName parsedName = anyName;
- if (!parseAttributeName(parsedName, namespaceURI, qualifiedName, ec))
- return;
- setAttribute(parsedName, value);
+ auto parseResult = Document::parseQualifiedName(namespaceURI, qualifiedName);
+ if (parseResult.hasException())
+ return parseResult.releaseException();
+ QualifiedName parsedAttributeName { parseResult.releaseReturnValue() };
+ if (!Document::hasValidNamespaceForAttributes(parsedAttributeName))
+ return Exception { NAMESPACE_ERR };
+ return WTFMove(parsedAttributeName);
+}
+
+ExceptionOr<void> Element::setAttributeNS(const AtomicString& namespaceURI, const AtomicString& qualifiedName, const AtomicString& value)
+{
+ auto result = parseAttributeName(namespaceURI, qualifiedName);
+ if (result.hasException())
+ return result.releaseException();
+ setAttribute(result.releaseReturnValue(), value);
+ return { };
}
void Element::removeAttributeInternal(unsigned index, SynchronizationOfLazyAttribute inSynchronizationOfLazyAttribute)
@@ -1854,62 +2272,74 @@ void Element::removeAttributeInternal(unsigned index, SynchronizationOfLazyAttri
QualifiedName name = elementData.attributeAt(index).name();
AtomicString valueBeingRemoved = elementData.attributeAt(index).value();
- if (!inSynchronizationOfLazyAttribute) {
- if (!valueBeingRemoved.isNull())
- willModifyAttribute(name, valueBeingRemoved, nullAtom);
- }
-
if (RefPtr<Attr> attrNode = attrIfExists(name))
detachAttrNodeFromElementWithValue(attrNode.get(), elementData.attributeAt(index).value());
- elementData.removeAttribute(index);
+ if (inSynchronizationOfLazyAttribute) {
+ elementData.removeAttribute(index);
+ return;
+ }
+
+ if (!valueBeingRemoved.isNull())
+ willModifyAttribute(name, valueBeingRemoved, nullAtom);
- if (!inSynchronizationOfLazyAttribute)
- didRemoveAttribute(name, valueBeingRemoved);
+ {
+ Style::AttributeChangeInvalidation styleInvalidation(*this, name, valueBeingRemoved, nullAtom);
+ elementData.removeAttribute(index);
+ }
+
+ didRemoveAttribute(name, valueBeingRemoved);
}
void Element::addAttributeInternal(const QualifiedName& name, const AtomicString& value, SynchronizationOfLazyAttribute inSynchronizationOfLazyAttribute)
{
- if (!inSynchronizationOfLazyAttribute)
- willModifyAttribute(name, nullAtom, value);
- ensureUniqueElementData().addAttribute(name, value);
- if (!inSynchronizationOfLazyAttribute)
- didAddAttribute(name, value);
+ if (inSynchronizationOfLazyAttribute) {
+ ensureUniqueElementData().addAttribute(name, value);
+ return;
+ }
+
+ willModifyAttribute(name, nullAtom, value);
+ {
+ Style::AttributeChangeInvalidation styleInvalidation(*this, name, nullAtom, value);
+ ensureUniqueElementData().addAttribute(name, value);
+ }
+ didAddAttribute(name, value);
}
-void Element::removeAttribute(const AtomicString& name)
+bool Element::removeAttribute(const AtomicString& name)
{
if (!elementData())
- return;
+ return false;
- AtomicString localName = shouldIgnoreAttributeCase(*this) ? name.lower() : name;
+ AtomicString localName = shouldIgnoreAttributeCase(*this) ? name.convertToASCIILowercase() : name;
unsigned index = elementData()->findAttributeIndexByName(localName, false);
if (index == ElementData::attributeNotFound) {
- if (UNLIKELY(localName == styleAttr) && elementData()->styleAttributeIsDirty() && isStyledElement())
- toStyledElement(this)->removeAllInlineStyleProperties();
- return;
+ if (UNLIKELY(localName == styleAttr) && elementData()->styleAttributeIsDirty() && is<StyledElement>(*this))
+ downcast<StyledElement>(*this).removeAllInlineStyleProperties();
+ return false;
}
removeAttributeInternal(index, NotInSynchronizationOfLazyAttribute);
+ return true;
}
-void Element::removeAttributeNS(const AtomicString& namespaceURI, const AtomicString& localName)
+bool Element::removeAttributeNS(const AtomicString& namespaceURI, const AtomicString& localName)
{
- removeAttribute(QualifiedName(nullAtom, localName, namespaceURI));
+ return removeAttribute(QualifiedName(nullAtom, localName, namespaceURI));
}
-PassRefPtr<Attr> Element::getAttributeNode(const AtomicString& localName)
+RefPtr<Attr> Element::getAttributeNode(const AtomicString& localName)
{
if (!elementData())
- return 0;
+ return nullptr;
synchronizeAttribute(localName);
const Attribute* attribute = elementData()->findAttributeByName(localName, shouldIgnoreAttributeCase(*this));
if (!attribute)
- return 0;
+ return nullptr;
return ensureAttr(attribute->name());
}
-PassRefPtr<Attr> Element::getAttributeNodeNS(const AtomicString& namespaceURI, const AtomicString& localName)
+RefPtr<Attr> Element::getAttributeNodeNS(const AtomicString& namespaceURI, const AtomicString& localName)
{
if (!elementData())
return 0;
@@ -1926,7 +2356,7 @@ bool Element::hasAttribute(const AtomicString& localName) const
if (!elementData())
return false;
synchronizeAttribute(localName);
- return elementData()->findAttributeByName(shouldIgnoreAttributeCase(*this) ? localName.lower() : localName, false);
+ return elementData()->findAttributeByName(localName, shouldIgnoreAttributeCase(*this));
}
bool Element::hasAttributeNS(const AtomicString& namespaceURI, const AtomicString& localName) const
@@ -1938,18 +2368,22 @@ bool Element::hasAttributeNS(const AtomicString& namespaceURI, const AtomicStrin
return elementData()->findAttributeByName(qName);
}
-CSSStyleDeclaration *Element::style()
+CSSStyleDeclaration* Element::cssomStyle()
{
- return 0;
+ return nullptr;
}
void Element::focus(bool restorePreviousSelection, FocusDirection direction)
{
- if (!inDocument())
+ if (!isConnected())
return;
- if (document().focusedElement() == this)
+ if (document().focusedElement() == this) {
+ if (document().page())
+ document().page()->chrome().client().elementDidRefocus(*this);
+
return;
+ }
// If the stylesheets have already been loaded we can reliably check isFocusable.
// If not, we continue and set the focused node on the focus controller below so
@@ -1969,7 +2403,7 @@ void Element::focus(bool restorePreviousSelection, FocusDirection direction)
// If a focus event handler changes the focus to a different node it
// does not make sense to continue and update appearence.
protect = this;
- if (!page->focusController().setFocusedElement(this, document().frame(), direction))
+ if (!page->focusController().setFocusedElement(this, *document().frame(), direction))
return;
}
@@ -1982,20 +2416,18 @@ void Element::focus(bool restorePreviousSelection, FocusDirection direction)
}
cancelFocusAppearanceUpdate();
+
+ SelectionRevealMode revealMode = SelectionRevealMode::Reveal;
#if PLATFORM(IOS)
// Focusing a form element triggers animation in UIKit to scroll to the right position.
// Calling updateFocusAppearance() would generate an unnecessary call to ScrollView::setScrollPosition(),
// which would jump us around during this animation. See <rdar://problem/6699741>.
- FrameView* view = document().view();
- bool isFormControl = view && isFormControlElement();
+ bool isFormControl = is<HTMLFormControlElement>(*this);
if (isFormControl)
- view->setProhibitsScrolling(true);
-#endif
- updateFocusAppearance(restorePreviousSelection);
-#if PLATFORM(IOS)
- if (isFormControl)
- view->setProhibitsScrolling(false);
+ revealMode = SelectionRevealMode::RevealUpToMainFrame;
#endif
+
+ updateFocusAppearance(restorePreviousSelection ? SelectionRestorationMode::Restore : SelectionRestorationMode::SetDefault, revealMode);
}
void Element::updateFocusAppearanceAfterAttachIfNeeded()
@@ -2006,76 +2438,167 @@ void Element::updateFocusAppearanceAfterAttachIfNeeded()
if (!data->needsFocusAppearanceUpdateSoonAfterAttach())
return;
if (isFocusable() && document().focusedElement() == this)
- document().updateFocusAppearanceSoon(false /* don't restore selection */);
+ document().updateFocusAppearanceSoon(SelectionRestorationMode::SetDefault);
data->setNeedsFocusAppearanceUpdateSoonAfterAttach(false);
}
-void Element::updateFocusAppearance(bool /*restorePreviousSelection*/)
+void Element::updateFocusAppearance(SelectionRestorationMode, SelectionRevealMode revealMode)
{
if (isRootEditableElement()) {
- Frame* frame = document().frame();
+ // Keep frame alive in this method, since setSelection() may release the last reference to |frame|.
+ RefPtr<Frame> frame = document().frame();
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().rootEditableElement())
+ if (this == frame->selection().selection().rootEditableElement())
return;
// FIXME: We should restore the previous selection if there is one.
VisibleSelection newSelection = VisibleSelection(firstPositionInOrBeforeNode(this), DOWNSTREAM);
if (frame->selection().shouldChangeSelection(newSelection)) {
- frame->selection().setSelection(newSelection);
- frame->selection().revealSelection();
+ frame->selection().setSelection(newSelection, FrameSelection::defaultSetSelectionOptions(), Element::defaultFocusTextStateChangeIntent());
+ frame->selection().revealSelection(revealMode);
}
- } else if (renderer() && !renderer()->isWidget())
- renderer()->scrollRectToVisible(boundingBox());
+ } else if (renderer() && !renderer()->isWidget()) {
+ bool insideFixed;
+ LayoutRect absoluteBounds = renderer()->absoluteAnchorRect(&insideFixed);
+ renderer()->scrollRectToVisible(revealMode, absoluteBounds, insideFixed);
+ }
}
void Element::blur()
{
cancelFocusAppearanceUpdate();
- if (treeScope().focusedElement() == this) {
+ if (treeScope().focusedElementInScope() == this) {
if (Frame* frame = document().frame())
- frame->page()->focusController().setFocusedElement(0, frame);
+ frame->page()->focusController().setFocusedElement(nullptr, *frame);
else
- document().setFocusedElement(0);
+ document().setFocusedElement(nullptr);
}
}
-void Element::dispatchFocusInEvent(const AtomicString& eventType, PassRefPtr<Element> oldFocusedElement)
+void Element::dispatchFocusInEvent(const AtomicString& eventType, RefPtr<Element>&& oldFocusedElement)
{
- ASSERT(!NoEventDispatchAssertion::isEventDispatchForbidden());
+ ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventAllowedInMainThread());
ASSERT(eventType == eventNames().focusinEvent || eventType == eventNames().DOMFocusInEvent);
- dispatchScopedEvent(FocusEvent::create(eventType, true, false, document().defaultView(), 0, oldFocusedElement));
+ dispatchScopedEvent(FocusEvent::create(eventType, true, false, document().defaultView(), 0, WTFMove(oldFocusedElement)));
}
-void Element::dispatchFocusOutEvent(const AtomicString& eventType, PassRefPtr<Element> newFocusedElement)
+void Element::dispatchFocusOutEvent(const AtomicString& eventType, RefPtr<Element>&& newFocusedElement)
{
- ASSERT(!NoEventDispatchAssertion::isEventDispatchForbidden());
+ ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventAllowedInMainThread());
ASSERT(eventType == eventNames().focusoutEvent || eventType == eventNames().DOMFocusOutEvent);
- dispatchScopedEvent(FocusEvent::create(eventType, true, false, document().defaultView(), 0, newFocusedElement));
+ dispatchScopedEvent(FocusEvent::create(eventType, true, false, document().defaultView(), 0, WTFMove(newFocusedElement)));
}
-void Element::dispatchFocusEvent(PassRefPtr<Element> oldFocusedElement, FocusDirection)
+void Element::dispatchFocusEvent(RefPtr<Element>&& oldFocusedElement, FocusDirection)
{
if (document().page())
- document().page()->chrome().client().elementDidFocus(this);
+ document().page()->chrome().client().elementDidFocus(*this);
- RefPtr<FocusEvent> event = FocusEvent::create(eventNames().focusEvent, false, false, document().defaultView(), 0, oldFocusedElement);
- EventDispatcher::dispatchEvent(this, event.release());
+ EventDispatcher::dispatchEvent(*this, FocusEvent::create(eventNames().focusEvent, false, false, document().defaultView(), 0, WTFMove(oldFocusedElement)));
}
-void Element::dispatchBlurEvent(PassRefPtr<Element> newFocusedElement)
+void Element::dispatchBlurEvent(RefPtr<Element>&& newFocusedElement)
{
if (document().page())
- document().page()->chrome().client().elementDidBlur(this);
+ document().page()->chrome().client().elementDidBlur(*this);
+
+ EventDispatcher::dispatchEvent(*this, FocusEvent::create(eventNames().blurEvent, false, false, document().defaultView(), 0, WTFMove(newFocusedElement)));
+}
+
+bool Element::dispatchMouseForceWillBegin()
+{
+#if ENABLE(MOUSE_FORCE_EVENTS)
+ if (!document().hasListenerType(Document::FORCEWILLBEGIN_LISTENER))
+ return false;
+
+ Frame* frame = document().frame();
+ if (!frame)
+ return false;
+
+ PlatformMouseEvent platformMouseEvent { frame->eventHandler().lastKnownMousePosition(), frame->eventHandler().lastKnownMouseGlobalPosition(), NoButton, PlatformEvent::NoType, 1, false, false, false, false, WTF::currentTime(), ForceAtClick, NoTap };
+ auto mouseForceWillBeginEvent = MouseEvent::create(eventNames().webkitmouseforcewillbeginEvent, document().defaultView(), platformMouseEvent, 0, nullptr);
+ mouseForceWillBeginEvent->setTarget(this);
+ dispatchEvent(mouseForceWillBeginEvent);
+
+ if (mouseForceWillBeginEvent->defaultHandled() || mouseForceWillBeginEvent->defaultPrevented())
+ return true;
+#endif
+
+ return false;
+}
+
+ExceptionOr<void> Element::mergeWithNextTextNode(Text& node)
+{
+ auto* next = node.nextSibling();
+ if (!is<Text>(next))
+ return { };
+ Ref<Text> textNext { downcast<Text>(*next) };
+ node.appendData(textNext->data());
+ return textNext->remove();
+}
+
+String Element::innerHTML() const
+{
+ return createMarkup(*this, ChildrenOnly);
+}
+
+String Element::outerHTML() const
+{
+ return createMarkup(*this);
+}
+
+ExceptionOr<void> Element::setOuterHTML(const String& html)
+{
+ auto* parentElement = this->parentElement();
+ if (!is<HTMLElement>(parentElement))
+ return Exception { NO_MODIFICATION_ALLOWED_ERR };
- RefPtr<FocusEvent> event = FocusEvent::create(eventNames().blurEvent, false, false, document().defaultView(), 0, newFocusedElement);
- EventDispatcher::dispatchEvent(this, event.release());
+ Ref<HTMLElement> parent = downcast<HTMLElement>(*parentElement);
+ RefPtr<Node> prev = previousSibling();
+ RefPtr<Node> next = nextSibling();
+
+ auto fragment = createFragmentForInnerOuterHTML(parent, html, AllowScriptingContent);
+ if (fragment.hasException())
+ return fragment.releaseException();
+
+ auto replaceResult = parent->replaceChild(fragment.releaseReturnValue().get(), *this);
+ if (replaceResult.hasException())
+ return replaceResult.releaseException();
+
+ RefPtr<Node> node = next ? next->previousSibling() : nullptr;
+ if (is<Text>(node.get())) {
+ auto result = mergeWithNextTextNode(downcast<Text>(*node));
+ if (result.hasException())
+ return result.releaseException();
+ }
+ if (is<Text>(prev.get())) {
+ auto result = mergeWithNextTextNode(downcast<Text>(*prev));
+ if (result.hasException())
+ return result.releaseException();
+ }
+ return { };
}
+ExceptionOr<void> Element::setInnerHTML(const String& html)
+{
+ auto fragment = createFragmentForInnerOuterHTML(*this, html, AllowScriptingContent);
+ if (fragment.hasException())
+ return fragment.releaseException();
+
+ ContainerNode* container;
+ if (!is<HTMLTemplateElement>(*this))
+ container = this;
+ else
+ container = &downcast<HTMLTemplateElement>(*this).content();
+
+ return replaceChildrenWithFragment(*container, fragment.releaseReturnValue());
+}
+
String Element::innerText()
{
// We need to update layout, since plainText uses line boxes in the render tree.
@@ -2084,7 +2607,7 @@ String Element::innerText()
if (!renderer())
return textContent(true);
- return plainText(rangeOfContents(*this).get());
+ return plainText(rangeOfContents(*this).ptr());
}
String Element::outerText()
@@ -2104,12 +2627,12 @@ String Element::title() const
const AtomicString& Element::pseudo() const
{
- return getAttribute(pseudoAttr);
+ return attributeWithoutSynchronization(pseudoAttr);
}
void Element::setPseudo(const AtomicString& value)
{
- setAttribute(pseudoAttr, value);
+ setAttributeWithoutSynchronization(pseudoAttr, value);
}
LayoutSize Element::minimumSizeForResizing() const
@@ -2124,69 +2647,114 @@ void Element::setMinimumSizeForResizing(const LayoutSize& size)
ensureElementRareData().setMinimumSizeForResizing(size);
}
-static PseudoElement* beforeOrAfterPseudoElement(Element* host, PseudoId pseudoElementSpecifier)
+void Element::willBecomeFullscreenElement()
+{
+ for (auto& child : descendantsOfType<Element>(*this))
+ child.ancestorWillEnterFullscreen();
+}
+
+static PseudoElement* beforeOrAfterPseudoElement(Element& host, PseudoId pseudoElementSpecifier)
{
switch (pseudoElementSpecifier) {
case BEFORE:
- return host->beforePseudoElement();
+ return host.beforePseudoElement();
case AFTER:
- return host->afterPseudoElement();
+ return host.afterPseudoElement();
default:
- return 0;
+ return nullptr;
}
}
-RenderStyle* Element::computedStyle(PseudoId pseudoElementSpecifier)
+const RenderStyle* Element::existingComputedStyle()
{
- if (PseudoElement* pseudoElement = beforeOrAfterPseudoElement(this, pseudoElementSpecifier))
- return pseudoElement->computedStyle();
+ if (auto* renderTreeStyle = renderStyle())
+ return renderTreeStyle;
- // FIXME: Find and use the renderer from the pseudo element instead of the actual element so that the 'length'
- // properties, which are only known by the renderer because it did the layout, will be correct and so that the
- // values returned for the ":selection" pseudo-element will be correct.
- if (RenderStyle* usedStyle = renderStyle()) {
- if (pseudoElementSpecifier) {
- RenderStyle* cachedPseudoStyle = usedStyle->getCachedPseudoStyle(pseudoElementSpecifier);
- return cachedPseudoStyle ? cachedPseudoStyle : usedStyle;
+ if (hasRareData())
+ return elementRareData()->computedStyle();
+
+ return nullptr;
+}
+
+const RenderStyle& Element::resolveComputedStyle()
+{
+ ASSERT(isConnected());
+ ASSERT(!existingComputedStyle());
+
+ Deque<Element*, 32> elementsRequiringComputedStyle({ this });
+ const RenderStyle* computedStyle = nullptr;
+
+ // Collect ancestors until we find one that has style.
+ auto composedAncestors = composedTreeAncestors(*this);
+ for (auto& ancestor : composedAncestors) {
+ elementsRequiringComputedStyle.prepend(&ancestor);
+ if (auto* existingStyle = ancestor.existingComputedStyle()) {
+ computedStyle = existingStyle;
+ break;
}
- return usedStyle;
}
- if (!inDocument()) {
- // 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.
+ // Resolve and cache styles starting from the most distant ancestor.
+ for (auto* element : elementsRequiringComputedStyle) {
+ auto style = document().styleForElementIgnoringPendingStylesheets(*element, computedStyle);
+ computedStyle = style.get();
+ ElementRareData& rareData = element->ensureElementRareData();
+ rareData.setComputedStyle(WTFMove(style));
+ }
+
+ return *computedStyle;
+}
+
+const RenderStyle* Element::computedStyle(PseudoId pseudoElementSpecifier)
+{
+ if (!isConnected())
return nullptr;
+
+ if (PseudoElement* pseudoElement = beforeOrAfterPseudoElement(*this, pseudoElementSpecifier))
+ return pseudoElement->computedStyle();
+
+ auto* style = existingComputedStyle();
+ if (!style)
+ style = &resolveComputedStyle();
+
+ if (pseudoElementSpecifier) {
+ if (auto* cachedPseudoStyle = style->getCachedPseudoStyle(pseudoElementSpecifier))
+ return cachedPseudoStyle;
}
- ElementRareData& data = ensureElementRareData();
- if (!data.computedStyle())
- data.setComputedStyle(document().styleForElementIgnoringPendingStylesheets(this));
- return pseudoElementSpecifier ? data.computedStyle()->getCachedPseudoStyle(pseudoElementSpecifier) : data.computedStyle();
+ return style;
}
-void Element::setStyleAffectedByEmpty()
+bool Element::needsStyleInvalidation() const
{
- ensureElementRareData().setStyleAffectedByEmpty(true);
+ if (!inRenderedDocument())
+ return false;
+ if (styleValidity() >= Style::Validity::SubtreeInvalid)
+ return false;
+ if (document().hasPendingForcedStyleRecalc())
+ return false;
+
+ return true;
}
-void Element::setChildrenAffectedByActive()
+void Element::setStyleAffectedByEmpty()
{
- ensureElementRareData().setChildrenAffectedByActive(true);
+ ensureElementRareData().setStyleAffectedByEmpty(true);
}
-void Element::setChildrenAffectedByDrag()
+void Element::setStyleAffectedByFocusWithin()
{
- ensureElementRareData().setChildrenAffectedByDrag(true);
+ ensureElementRareData().setStyleAffectedByFocusWithin(true);
}
-void Element::setChildrenAffectedByDirectAdjacentRules(Element* element)
+void Element::setStyleAffectedByActive()
{
- element->setChildrenAffectedByDirectAdjacentRules();
+ ensureElementRareData().setStyleAffectedByActive(true);
}
-void Element::setChildrenAffectedByForwardPositionalRules(Element* element)
+void Element::setChildrenAffectedByDrag()
{
- element->ensureElementRareData().setChildrenAffectedByForwardPositionalRules(true);
+ ensureElementRareData().setChildrenAffectedByDrag(true);
}
void Element::setChildrenAffectedByBackwardPositionalRules()
@@ -2194,25 +2762,28 @@ void Element::setChildrenAffectedByBackwardPositionalRules()
ensureElementRareData().setChildrenAffectedByBackwardPositionalRules(true);
}
+void Element::setChildrenAffectedByPropertyBasedBackwardPositionalRules()
+{
+ ensureElementRareData().setChildrenAffectedByPropertyBasedBackwardPositionalRules(true);
+}
+
void Element::setChildIndex(unsigned index)
{
ElementRareData& rareData = ensureElementRareData();
- if (RenderStyle* style = renderStyle())
- style->setUnique();
rareData.setChildIndex(index);
}
bool Element::hasFlagsSetDuringStylingOfChildren() const
{
- if (childrenAffectedByHover() || childrenAffectedByFirstChildRules() || childrenAffectedByLastChildRules() || childrenAffectedByDirectAdjacentRules())
+ if (childrenAffectedByHover() || childrenAffectedByFirstChildRules() || childrenAffectedByLastChildRules())
return true;
if (!hasRareData())
return false;
- return rareDataChildrenAffectedByActive()
+ return rareDataStyleAffectedByActive()
|| rareDataChildrenAffectedByDrag()
- || rareDataChildrenAffectedByForwardPositionalRules()
- || rareDataChildrenAffectedByBackwardPositionalRules();
+ || rareDataChildrenAffectedByBackwardPositionalRules()
+ || rareDataChildrenAffectedByPropertyBasedBackwardPositionalRules();
}
bool Element::rareDataStyleAffectedByEmpty() const
@@ -2221,57 +2792,46 @@ bool Element::rareDataStyleAffectedByEmpty() const
return elementRareData()->styleAffectedByEmpty();
}
-bool Element::rareDataChildrenAffectedByActive() const
+bool Element::rareDataStyleAffectedByFocusWithin() const
{
ASSERT(hasRareData());
- return elementRareData()->childrenAffectedByActive();
+ return elementRareData()->styleAffectedByFocusWithin();
}
-bool Element::rareDataChildrenAffectedByDrag() const
+bool Element::rareDataIsNamedFlowContentElement() const
{
ASSERT(hasRareData());
- return elementRareData()->childrenAffectedByDrag();
+ return elementRareData()->isNamedFlowContentElement();
}
-bool Element::rareDataChildrenAffectedByForwardPositionalRules() const
+bool Element::rareDataStyleAffectedByActive() const
{
ASSERT(hasRareData());
- return elementRareData()->childrenAffectedByForwardPositionalRules();
+ return elementRareData()->styleAffectedByActive();
}
-bool Element::rareDataChildrenAffectedByBackwardPositionalRules() const
+bool Element::rareDataChildrenAffectedByDrag() const
{
ASSERT(hasRareData());
- return elementRareData()->childrenAffectedByBackwardPositionalRules();
+ return elementRareData()->childrenAffectedByDrag();
}
-unsigned Element::rareDataChildIndex() const
+bool Element::rareDataChildrenAffectedByBackwardPositionalRules() const
{
ASSERT(hasRareData());
- return elementRareData()->childIndex();
-}
-
-void Element::setIsInCanvasSubtree(bool isInCanvasSubtree)
-{
- ensureElementRareData().setIsInCanvasSubtree(isInCanvasSubtree);
-}
-
-bool Element::isInCanvasSubtree() const
-{
- return hasRareData() && elementRareData()->isInCanvasSubtree();
+ return elementRareData()->childrenAffectedByBackwardPositionalRules();
}
-void Element::setIsInsideRegion(bool value)
+bool Element::rareDataChildrenAffectedByPropertyBasedBackwardPositionalRules() const
{
- if (value == isInsideRegion())
- return;
-
- ensureElementRareData().setIsInsideRegion(value);
+ ASSERT(hasRareData());
+ return elementRareData()->childrenAffectedByPropertyBasedBackwardPositionalRules();
}
-bool Element::isInsideRegion() const
+unsigned Element::rareDataChildIndex() const
{
- return hasRareData() ? elementRareData()->isInsideRegion() : false;
+ ASSERT(hasRareData());
+ return elementRareData()->childIndex();
}
void Element::setRegionOversetState(RegionOversetState state)
@@ -2286,27 +2846,26 @@ RegionOversetState Element::regionOversetState() const
AtomicString Element::computeInheritedLanguage() const
{
- const Node* n = this;
- AtomicString value;
+ if (const ElementData* elementData = this->elementData()) {
+ if (const Attribute* attribute = elementData->findLanguageAttribute())
+ return attribute->value();
+ }
+
// The language property is inherited, so we iterate over the parents to find the first language.
- do {
- if (n->isElementNode()) {
- if (const ElementData* elementData = toElement(n)->elementData()) {
- // Spec: xml:lang takes precedence -- http://www.w3.org/TR/xhtml1/#C_7
- if (const Attribute* attribute = elementData->findAttributeByName(XMLNames::langAttr))
- value = attribute->value();
- else if (const Attribute* attribute = elementData->findAttributeByName(HTMLNames::langAttr))
- value = attribute->value();
+ const Node* currentNode = this;
+ while ((currentNode = currentNode->parentNode())) {
+ if (is<Element>(*currentNode)) {
+ if (const ElementData* elementData = downcast<Element>(*currentNode).elementData()) {
+ if (const Attribute* attribute = elementData->findLanguageAttribute())
+ return attribute->value();
}
- } else if (n->isDocumentNode()) {
+ } else if (is<Document>(*currentNode)) {
// checking the MIME content-language
- value = toDocument(n)->contentLanguage();
+ return downcast<Document>(*currentNode).contentLanguage();
}
+ }
- n = n->parentNode();
- } while (n && value.isNull());
-
- return value;
+ return nullAtom;
}
Locale& Element::locale() const
@@ -2327,7 +2886,7 @@ void Element::normalizeAttributes()
if (!hasAttributes())
return;
- auto* attrNodeList = attrNodeListForElement(this);
+ auto* attrNodeList = attrNodeListForElement(*this);
if (!attrNodeList)
return;
@@ -2341,24 +2900,22 @@ void Element::normalizeAttributes()
PseudoElement* Element::beforePseudoElement() const
{
- return hasRareData() ? elementRareData()->beforePseudoElement() : 0;
+ return hasRareData() ? elementRareData()->beforePseudoElement() : nullptr;
}
PseudoElement* Element::afterPseudoElement() const
{
- return hasRareData() ? elementRareData()->afterPseudoElement() : 0;
+ return hasRareData() ? elementRareData()->afterPseudoElement() : nullptr;
}
-void Element::setBeforePseudoElement(PassRefPtr<PseudoElement> element)
+void Element::setBeforePseudoElement(Ref<PseudoElement>&& element)
{
- ensureElementRareData().setBeforePseudoElement(element);
- resetNeedsNodeRenderingTraversalSlowPath();
+ ensureElementRareData().setBeforePseudoElement(WTFMove(element));
}
-void Element::setAfterPseudoElement(PassRefPtr<PseudoElement> element)
+void Element::setAfterPseudoElement(Ref<PseudoElement>&& element)
{
- ensureElementRareData().setAfterPseudoElement(element);
- resetNeedsNodeRenderingTraversalSlowPath();
+ ensureElementRareData().setAfterPseudoElement(WTFMove(element));
}
static void disconnectPseudoElement(PseudoElement* pseudoElement)
@@ -2366,7 +2923,7 @@ static void disconnectPseudoElement(PseudoElement* pseudoElement)
if (!pseudoElement)
return;
if (pseudoElement->renderer())
- Style::detachRenderTree(*pseudoElement);
+ RenderTreeUpdater::tearDownRenderers(*pseudoElement);
ASSERT(pseudoElement->hostElement());
pseudoElement->clearHostElement();
}
@@ -2377,7 +2934,6 @@ void Element::clearBeforePseudoElement()
return;
disconnectPseudoElement(elementRareData()->beforePseudoElement());
elementRareData()->setBeforePseudoElement(nullptr);
- resetNeedsNodeRenderingTraversalSlowPath();
}
void Element::clearAfterPseudoElement()
@@ -2386,60 +2942,47 @@ void Element::clearAfterPseudoElement()
return;
disconnectPseudoElement(elementRareData()->afterPseudoElement());
elementRareData()->setAfterPseudoElement(nullptr);
- resetNeedsNodeRenderingTraversalSlowPath();
}
-// ElementTraversal API
-Element* Element::firstElementChild() const
+bool Element::matchesValidPseudoClass() const
{
- return ElementTraversal::firstChild(this);
-}
-
-Element* Element::lastElementChild() const
-{
- return ElementTraversal::lastChild(this);
+ return false;
}
-Element* Element::previousElementSibling() const
+bool Element::matchesInvalidPseudoClass() const
{
- return ElementTraversal::previousSibling(this);
+ return false;
}
-Element* Element::nextElementSibling() const
+bool Element::matchesReadWritePseudoClass() const
{
- return ElementTraversal::nextSibling(this);
+ return false;
}
-unsigned Element::childElementCount() const
+bool Element::matchesIndeterminatePseudoClass() const
{
- unsigned count = 0;
- Node* n = firstChild();
- while (n) {
- count += n->isElementNode();
- n = n->nextSibling();
- }
- return count;
+ return shouldAppearIndeterminate();
}
-bool Element::matchesReadOnlyPseudoClass() const
+bool Element::matchesDefaultPseudoClass() const
{
return false;
}
-bool Element::matchesReadWritePseudoClass() const
+ExceptionOr<bool> Element::matches(const String& selector)
{
- return false;
+ auto query = document().selectorQueryForString(selector);
+ if (query.hasException())
+ return query.releaseException();
+ return query.releaseReturnValue().matches(*this);
}
-bool Element::webkitMatchesSelector(const String& selector, ExceptionCode& ec)
+ExceptionOr<Element*> Element::closest(const String& selector)
{
- if (selector.isEmpty()) {
- ec = SYNTAX_ERR;
- return false;
- }
-
- SelectorQuery* selectorQuery = document().selectorQueryCache().add(selector, document(), ec);
- return selectorQuery && selectorQuery->matches(*this);
+ auto query = document().selectorQueryForString(selector);
+ if (query.hasException())
+ return query.releaseException();
+ return query.releaseReturnValue().closest(*this);
}
bool Element::shouldAppearIndeterminate() const
@@ -2447,20 +2990,25 @@ bool Element::shouldAppearIndeterminate() const
return false;
}
-DOMTokenList* Element::classList()
+bool Element::mayCauseRepaintInsideViewport(const IntRect* visibleRect) const
+{
+ return renderer() && renderer()->mayCauseRepaintInsideViewport(visibleRect);
+}
+
+DOMTokenList& Element::classList()
{
ElementRareData& data = ensureElementRareData();
if (!data.classList())
- data.setClassList(std::make_unique<ClassList>(*this));
- return data.classList();
+ data.setClassList(std::make_unique<DOMTokenList>(*this, HTMLNames::classAttr));
+ return *data.classList();
}
-DatasetDOMStringMap* Element::dataset()
+DatasetDOMStringMap& Element::dataset()
{
ElementRareData& data = ensureElementRareData();
if (!data.dataset())
data.setDataset(std::make_unique<DatasetDOMStringMap>(*this));
- return data.dataset();
+ return *data.dataset();
}
URL Element::getURLAttribute(const QualifiedName& name) const
@@ -2490,7 +3038,7 @@ URL Element::getNonEmptyURLAttribute(const QualifiedName& name) const
int Element::getIntegralAttribute(const QualifiedName& attributeName) const
{
- return getAttribute(attributeName).string().toInt();
+ return parseHTMLInteger(getAttribute(attributeName)).value_or(0);
}
void Element::setIntegralAttribute(const QualifiedName& attributeName, int value)
@@ -2500,47 +3048,29 @@ void Element::setIntegralAttribute(const QualifiedName& attributeName, int value
unsigned Element::getUnsignedIntegralAttribute(const QualifiedName& attributeName) const
{
- return getAttribute(attributeName).string().toUInt();
+ return parseHTMLNonNegativeInteger(getAttribute(attributeName)).value_or(0);
}
void Element::setUnsignedIntegralAttribute(const QualifiedName& attributeName, unsigned value)
{
- setAttribute(attributeName, AtomicString::number(value));
+ setAttribute(attributeName, AtomicString::number(limitToOnlyHTMLNonNegative(value)));
}
-#if ENABLE(INDIE_UI)
-void Element::setUIActions(const AtomicString& actions)
-{
- setAttribute(uiactionsAttr, actions);
-}
-
-const AtomicString& Element::UIActions() const
-{
- return getAttribute(uiactionsAttr);
-}
-#endif
-
bool Element::childShouldCreateRenderer(const Node& child) const
{
-#if ENABLE(SVG)
// Only create renderers for SVG elements whose parents are SVG elements, or for proper <svg xmlns="svgNS"> subdocuments.
if (child.isSVGElement()) {
ASSERT(!isSVGElement());
- return child.hasTagName(SVGNames::svgTag) && toSVGElement(child).isValid();
+ const SVGElement& childElement = downcast<SVGElement>(child);
+ return is<SVGSVGElement>(childElement) && childElement.isValid();
}
-#endif
- return ContainerNode::childShouldCreateRenderer(child);
+ return true;
}
#if ENABLE(FULLSCREEN_API)
void Element::webkitRequestFullscreen()
{
- document().requestFullScreenForElement(this, ALLOW_KEYBOARD_INPUT, Document::EnforceIFrameAllowFullScreenRequirement);
-}
-
-void Element::webkitRequestFullScreen(unsigned short flags)
-{
- document().requestFullScreenForElement(this, (flags | LEGACY_MOZILLA_REQUEST), Document::EnforceIFrameAllowFullScreenRequirement);
+ document().requestFullScreenForElement(this, Document::EnforceIFrameAllowFullScreenRequirement);
}
bool Element::containsFullScreenElement() const
@@ -2551,7 +3081,7 @@ bool Element::containsFullScreenElement() const
void Element::setContainsFullScreenElement(bool flag)
{
ensureElementRareData().setContainsFullScreenElement(flag);
- setNeedsStyleRecalc(SyntheticStyleChange);
+ invalidateStyleAndLayerComposition();
}
static Element* parentCrossingFrameBoundaries(Element* element)
@@ -2569,23 +3099,22 @@ void Element::setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(boo
#endif
#if ENABLE(POINTER_LOCK)
-void Element::webkitRequestPointerLock()
+void Element::requestPointerLock()
{
if (document().page())
- document().page()->pointerLockController()->requestPointerLock(this);
+ document().page()->pointerLockController().requestPointerLock(this);
}
#endif
SpellcheckAttributeState Element::spellcheckAttributeState() const
{
- const AtomicString& value = getAttribute(HTMLNames::spellcheckAttr);
- if (value == nullAtom)
+ const AtomicString& value = attributeWithoutSynchronization(HTMLNames::spellcheckAttr);
+ if (value.isNull())
return SpellcheckAttributeDefault;
- if (equalIgnoringCase(value, "true") || equalIgnoringCase(value, ""))
+ if (value.isEmpty() || equalLettersIgnoringASCIICase(value, "true"))
return SpellcheckAttributeTrue;
- if (equalIgnoringCase(value, "false"))
+ if (equalLettersIgnoringASCIICase(value, "false"))
return SpellcheckAttributeFalse;
-
return SpellcheckAttributeDefault;
}
@@ -2605,12 +3134,12 @@ bool Element::isSpellCheckingEnabled() const
return true;
}
-RenderRegion* Element::renderRegion() const
+RenderNamedFlowFragment* Element::renderNamedFlowFragment() const
{
if (renderer() && renderer()->isRenderNamedFlowFragmentContainer())
- return toRenderBlockFlow(renderer())->renderNamedFlowFragment();
+ return downcast<RenderBlockFlow>(*renderer()).renderNamedFlowFragment();
- return 0;
+ return nullptr;
}
#if ENABLE(CSS_REGIONS)
@@ -2625,31 +3154,31 @@ bool Element::shouldMoveToFlowThread(const RenderStyle& styleToUse) const
if (isInShadowTree())
return false;
- if (styleToUse.flowThread().isEmpty())
+ if (!styleToUse.hasFlowInto())
return false;
- return !document().renderView()->flowThreadController().isContentElementRegisteredWithAnyNamedFlow(*this);
+ return true;
}
const AtomicString& Element::webkitRegionOverset() const
{
document().updateLayoutIgnorePendingStylesheets();
- DEFINE_STATIC_LOCAL(AtomicString, undefinedState, ("undefined", AtomicString::ConstructFromLiteral));
- if (!document().cssRegionsEnabled() || !renderRegion())
+ static NeverDestroyed<AtomicString> undefinedState("undefined", AtomicString::ConstructFromLiteral);
+ if (!renderNamedFlowFragment())
return undefinedState;
- switch (renderRegion()->regionOversetState()) {
+ switch (regionOversetState()) {
case RegionFit: {
- DEFINE_STATIC_LOCAL(AtomicString, fitState, ("fit", AtomicString::ConstructFromLiteral));
+ static NeverDestroyed<AtomicString> fitState("fit", AtomicString::ConstructFromLiteral);
return fitState;
}
case RegionEmpty: {
- DEFINE_STATIC_LOCAL(AtomicString, emptyState, ("empty", AtomicString::ConstructFromLiteral));
+ static NeverDestroyed<AtomicString> emptyState("empty", AtomicString::ConstructFromLiteral);
return emptyState;
}
case RegionOverset: {
- DEFINE_STATIC_LOCAL(AtomicString, overflowState, ("overset", AtomicString::ConstructFromLiteral));
+ static NeverDestroyed<AtomicString> overflowState("overset", AtomicString::ConstructFromLiteral);
return overflowState;
}
case RegionUndefined:
@@ -2663,16 +3192,13 @@ const AtomicString& Element::webkitRegionOverset() const
Vector<RefPtr<Range>> Element::webkitGetRegionFlowRanges() const
{
Vector<RefPtr<Range>> rangeObjects;
- if (!document().cssRegionsEnabled())
- return rangeObjects;
-
document().updateLayoutIgnorePendingStylesheets();
- if (renderer() && renderer()->isRenderNamedFlowFragmentContainer()) {
- RenderNamedFlowFragment* region = toRenderBlockFlow(renderer())->renderNamedFlowFragment();
- if (region->isValid())
- region->getRanges(rangeObjects);
+ auto* renderer = this->renderer();
+ if (renderer && renderer->isRenderNamedFlowFragmentContainer()) {
+ auto& namedFlowFragment = *downcast<RenderBlockFlow>(*renderer).renderNamedFlowFragment();
+ if (namedFlowFragment.isValid())
+ namedFlowFragment.getRanges(rangeObjects);
}
-
return rangeObjects;
}
@@ -2684,10 +3210,8 @@ bool Element::fastAttributeLookupAllowed(const QualifiedName& name) const
if (name == HTMLNames::styleAttr)
return false;
-#if ENABLE(SVG)
if (isSVGElement())
- return !toSVGElement(this)->isAnimatableAttribute(name);
-#endif
+ return !downcast<SVGElement>(*this).isAnimatableAttribute(name);
return true;
}
@@ -2710,11 +3234,11 @@ inline void Element::updateName(const AtomicString& oldName, const AtomicString&
updateNameForTreeScope(treeScope(), oldName, newName);
- if (!inDocument())
+ if (!isConnected())
return;
- if (!document().isHTMLDocument())
+ if (!is<HTMLDocument>(document()))
return;
- updateNameForDocument(toHTMLDocument(document()), oldName, newName);
+ updateNameForDocument(downcast<HTMLDocument>(document()), oldName, newName);
}
void Element::updateNameForTreeScope(TreeScope& scope, const AtomicString& oldName, const AtomicString& newName)
@@ -2731,16 +3255,19 @@ void Element::updateNameForDocument(HTMLDocument& document, const AtomicString&
{
ASSERT(oldName != newName);
- if (WindowNameCollection::nodeMatchesIfNameAttributeMatch(this)) {
- const AtomicString& id = WindowNameCollection::nodeMatchesIfIdAttributeMatch(this) ? getIdAttribute() : nullAtom;
+ if (isInShadowTree())
+ return;
+
+ if (WindowNameCollection::elementMatchesIfNameAttributeMatch(*this)) {
+ const AtomicString& id = WindowNameCollection::elementMatchesIfIdAttributeMatch(*this) ? getIdAttribute() : nullAtom;
if (!oldName.isEmpty() && oldName != id)
document.removeWindowNamedItem(*oldName.impl(), *this);
if (!newName.isEmpty() && newName != id)
document.addWindowNamedItem(*newName.impl(), *this);
}
- if (DocumentNameCollection::nodeMatchesIfNameAttributeMatch(this)) {
- const AtomicString& id = DocumentNameCollection::nodeMatchesIfIdAttributeMatch(this) ? getIdAttribute() : nullAtom;
+ if (DocumentNameCollection::elementMatchesIfNameAttributeMatch(*this)) {
+ const AtomicString& id = DocumentNameCollection::elementMatchesIfIdAttributeMatch(*this) ? getIdAttribute() : nullAtom;
if (!oldName.isEmpty() && oldName != id)
document.removeDocumentNamedItem(*oldName.impl(), *this);
if (!newName.isEmpty() && newName != id)
@@ -2758,11 +3285,11 @@ inline void Element::updateId(const AtomicString& oldId, const AtomicString& new
updateIdForTreeScope(treeScope(), oldId, newId, notifyObservers);
- if (!inDocument())
+ if (!isConnected())
return;
- if (!document().isHTMLDocument())
+ if (!is<HTMLDocument>(document()))
return;
- updateIdForDocument(toHTMLDocument(document()), oldId, newId, UpdateHTMLDocumentNamedItemMapsOnlyIfDiffersFromNameAttribute);
+ updateIdForDocument(downcast<HTMLDocument>(document()), oldId, newId, UpdateHTMLDocumentNamedItemMapsOnlyIfDiffersFromNameAttribute);
}
void Element::updateIdForTreeScope(TreeScope& scope, const AtomicString& oldId, const AtomicString& newId, NotifyObservers notifyObservers)
@@ -2778,19 +3305,22 @@ void Element::updateIdForTreeScope(TreeScope& scope, const AtomicString& oldId,
void Element::updateIdForDocument(HTMLDocument& document, const AtomicString& oldId, const AtomicString& newId, HTMLDocumentNamedItemMapsUpdatingCondition condition)
{
- ASSERT(inDocument());
+ ASSERT(isConnected());
ASSERT(oldId != newId);
- if (WindowNameCollection::nodeMatchesIfIdAttributeMatch(this)) {
- const AtomicString& name = condition == UpdateHTMLDocumentNamedItemMapsOnlyIfDiffersFromNameAttribute && WindowNameCollection::nodeMatchesIfNameAttributeMatch(this) ? getNameAttribute() : nullAtom;
+ if (isInShadowTree())
+ return;
+
+ if (WindowNameCollection::elementMatchesIfIdAttributeMatch(*this)) {
+ const AtomicString& name = condition == UpdateHTMLDocumentNamedItemMapsOnlyIfDiffersFromNameAttribute && WindowNameCollection::elementMatchesIfNameAttributeMatch(*this) ? getNameAttribute() : nullAtom;
if (!oldId.isEmpty() && oldId != name)
document.removeWindowNamedItem(*oldId.impl(), *this);
if (!newId.isEmpty() && newId != name)
document.addWindowNamedItem(*newId.impl(), *this);
}
- if (DocumentNameCollection::nodeMatchesIfIdAttributeMatch(this)) {
- const AtomicString& name = condition == UpdateHTMLDocumentNamedItemMapsOnlyIfDiffersFromNameAttribute && DocumentNameCollection::nodeMatchesIfNameAttributeMatch(this) ? getNameAttribute() : nullAtom;
+ if (DocumentNameCollection::elementMatchesIfIdAttributeMatch(*this)) {
+ const AtomicString& name = condition == UpdateHTMLDocumentNamedItemMapsOnlyIfDiffersFromNameAttribute && DocumentNameCollection::elementMatchesIfNameAttributeMatch(*this) ? getNameAttribute() : nullAtom;
if (!oldId.isEmpty() && oldId != name)
document.removeDocumentNamedItem(*oldId.impl(), *this);
if (!newId.isEmpty() && newId != name)
@@ -2802,21 +3332,21 @@ void Element::updateLabel(TreeScope& scope, const AtomicString& oldForAttributeV
{
ASSERT(hasTagName(labelTag));
- if (!inDocument())
+ if (!isConnected())
return;
if (oldForAttributeValue == newForAttributeValue)
return;
if (!oldForAttributeValue.isEmpty())
- scope.removeLabel(*oldForAttributeValue.impl(), *toHTMLLabelElement(this));
+ scope.removeLabel(*oldForAttributeValue.impl(), downcast<HTMLLabelElement>(*this));
if (!newForAttributeValue.isEmpty())
- scope.addLabel(*newForAttributeValue.impl(), *toHTMLLabelElement(this));
+ scope.addLabel(*newForAttributeValue.impl(), downcast<HTMLLabelElement>(*this));
}
void Element::willModifyAttribute(const QualifiedName& name, const AtomicString& oldValue, const AtomicString& newValue)
{
- if (isIdAttributeName(name))
+ if (name == HTMLNames::idAttr)
updateId(oldValue, newValue, NotifyObservers::No); // Will notify observers after the attribute is actually changed.
else if (name == HTMLNames::nameAttr)
updateName(oldValue, newValue);
@@ -2825,92 +3355,69 @@ void Element::willModifyAttribute(const QualifiedName& name, const AtomicString&
updateLabel(treeScope(), oldValue, newValue);
}
- if (oldValue != newValue) {
- auto styleResolver = document().styleResolverIfExists();
- if (styleResolver && styleResolver->hasSelectorForAttribute(name.localName()))
- setNeedsStyleRecalc();
- }
-
- if (OwnPtr<MutationObserverInterestGroup> recipients = MutationObserverInterestGroup::createForAttributesMutation(*this, name))
+ if (auto recipients = MutationObserverInterestGroup::createForAttributesMutation(*this, name))
recipients->enqueueMutationRecord(MutationRecord::createAttributes(*this, name, oldValue));
-#if ENABLE(INSPECTOR)
- InspectorInstrumentation::willModifyDOMAttr(&document(), this, oldValue, newValue);
-#endif
+ InspectorInstrumentation::willModifyDOMAttr(document(), *this, oldValue, newValue);
}
void Element::didAddAttribute(const QualifiedName& name, const AtomicString& value)
{
attributeChanged(name, nullAtom, value);
- InspectorInstrumentation::didModifyDOMAttr(&document(), this, name.localName(), value);
+ InspectorInstrumentation::didModifyDOMAttr(document(), *this, name.localName(), value);
dispatchSubtreeModifiedEvent();
}
void Element::didModifyAttribute(const QualifiedName& name, const AtomicString& oldValue, const AtomicString& newValue)
{
attributeChanged(name, oldValue, newValue);
- InspectorInstrumentation::didModifyDOMAttr(&document(), this, name.localName(), newValue);
+ InspectorInstrumentation::didModifyDOMAttr(document(), *this, name.localName(), newValue);
// Do not dispatch a DOMSubtreeModified event here; see bug 81141.
}
void Element::didRemoveAttribute(const QualifiedName& name, const AtomicString& oldValue)
{
attributeChanged(name, oldValue, nullAtom);
- InspectorInstrumentation::didRemoveDOMAttr(&document(), this, name.localName());
+ InspectorInstrumentation::didRemoveDOMAttr(document(), *this, name.localName());
dispatchSubtreeModifiedEvent();
}
-PassRefPtr<HTMLCollection> Element::ensureCachedHTMLCollection(CollectionType type)
-{
- if (HTMLCollection* collection = cachedHTMLCollection(type))
- return collection;
-
- RefPtr<HTMLCollection> collection;
- if (type == TableRows) {
- return ensureRareData().ensureNodeLists().addCachedCollection<HTMLTableRowsCollection>(toHTMLTableElement(*this), type);
- } else if (type == SelectOptions) {
- return ensureRareData().ensureNodeLists().addCachedCollection<HTMLOptionsCollection>(toHTMLSelectElement(*this), type);
- } else if (type == FormControls) {
- ASSERT(hasTagName(formTag) || hasTagName(fieldsetTag));
- return ensureRareData().ensureNodeLists().addCachedCollection<HTMLFormControlsCollection>(*this, type);
- }
- return ensureRareData().ensureNodeLists().addCachedCollection<HTMLCollection>(*this, type);
-}
-
-HTMLCollection* Element::cachedHTMLCollection(CollectionType type)
+IntPoint Element::savedLayerScrollPosition() const
{
- return hasRareData() && rareData()->nodeLists() ? rareData()->nodeLists()->cachedCollection<HTMLCollection>(type) : 0;
+ return hasRareData() ? elementRareData()->savedLayerScrollPosition() : IntPoint();
}
-IntSize Element::savedLayerScrollOffset() const
+void Element::setSavedLayerScrollPosition(const IntPoint& position)
{
- return hasRareData() ? elementRareData()->savedLayerScrollOffset() : IntSize();
+ if (position.isZero() && !hasRareData())
+ return;
+ ensureElementRareData().setSavedLayerScrollPosition(position);
}
-void Element::setSavedLayerScrollOffset(const IntSize& size)
+RefPtr<Attr> Element::attrIfExists(const AtomicString& localName, bool shouldIgnoreAttributeCase)
{
- if (size.isZero() && !hasRareData())
- return;
- ensureElementRareData().setSavedLayerScrollOffset(size);
+ if (auto* attrNodeList = attrNodeListForElement(*this))
+ return findAttrNodeInList(*attrNodeList, localName, shouldIgnoreAttributeCase);
+ return nullptr;
}
-PassRefPtr<Attr> Element::attrIfExists(const QualifiedName& name)
+RefPtr<Attr> Element::attrIfExists(const QualifiedName& name)
{
- if (AttrNodeList* attrNodeList = attrNodeListForElement(this))
+ if (auto* attrNodeList = attrNodeListForElement(*this))
return findAttrNodeInList(*attrNodeList, name);
- return 0;
+ return nullptr;
}
-PassRefPtr<Attr> Element::ensureAttr(const QualifiedName& name)
+Ref<Attr> Element::ensureAttr(const QualifiedName& name)
{
- AttrNodeList& attrNodeList = ensureAttrNodeListForElement(this);
+ auto& attrNodeList = ensureAttrNodeListForElement(*this);
RefPtr<Attr> attrNode = findAttrNodeInList(attrNodeList, name);
if (!attrNode) {
- attrNode = Attr::create(this, name);
- treeScope().adoptIfNeeded(attrNode.get());
+ attrNode = Attr::create(*this, name);
+ treeScope().adoptIfNeeded(*attrNode);
attrNodeList.append(attrNode);
}
- return attrNode.release();
+ return attrNode.releaseNonNull();
}
void Element::detachAttrNodeFromElementWithValue(Attr* attrNode, const AtomicString& value)
@@ -2918,21 +3425,18 @@ void Element::detachAttrNodeFromElementWithValue(Attr* attrNode, const AtomicStr
ASSERT(hasSyntheticAttrChildNodes());
attrNode->detachFromElementWithValue(value);
- AttrNodeList* attrNodeList = attrNodeListForElement(this);
- for (unsigned i = 0; i < attrNodeList->size(); ++i) {
- if (attrNodeList->at(i)->qualifiedName() == attrNode->qualifiedName()) {
- attrNodeList->remove(i);
- if (attrNodeList->isEmpty())
- removeAttrNodeListForElement(this);
- return;
- }
- }
- ASSERT_NOT_REACHED();
+ auto& attrNodeList = *attrNodeListForElement(*this);
+ bool found = attrNodeList.removeFirstMatching([attrNode](auto& attribute) {
+ return attribute->qualifiedName() == attrNode->qualifiedName();
+ });
+ ASSERT_UNUSED(found, found);
+ if (attrNodeList.isEmpty())
+ removeAttrNodeListForElement(*this);
}
void Element::detachAllAttrNodesFromElement()
{
- AttrNodeList* attrNodeList = attrNodeListForElement(this);
+ auto* attrNodeList = attrNodeListForElement(*this);
ASSERT(attrNodeList);
for (const Attribute& attribute : attributesIterator()) {
@@ -2940,18 +3444,31 @@ void Element::detachAllAttrNodesFromElement()
attrNode->detachFromElementWithValue(attribute.value());
}
- removeAttrNodeListForElement(this);
+ removeAttrNodeListForElement(*this);
}
void Element::resetComputedStyle()
{
if (!hasRareData() || !elementRareData()->computedStyle())
return;
- elementRareData()->resetComputedStyle();
- for (auto& child : descendantsOfType<Element>(*this)) {
- if (child.hasRareData())
- child.elementRareData()->resetComputedStyle();
- }
+
+ auto reset = [](Element& element) {
+ if (!element.hasRareData() || !element.elementRareData()->computedStyle())
+ return;
+ if (element.hasCustomStyleResolveCallbacks())
+ element.willResetComputedStyle();
+ element.elementRareData()->resetComputedStyle();
+ };
+ reset(*this);
+ for (auto& child : descendantsOfType<Element>(*this))
+ reset(child);
+}
+
+void Element::resetStyleRelations()
+{
+ if (!hasRareData())
+ return;
+ elementRareData()->resetStyleRelations();
}
void Element::clearStyleDerivedDataBeforeDetachingRenderer()
@@ -2960,13 +3477,6 @@ void Element::clearStyleDerivedDataBeforeDetachingRenderer()
cancelFocusAppearanceUpdate();
clearBeforePseudoElement();
clearAfterPseudoElement();
- if (!hasRareData())
- return;
- ElementRareData* data = elementRareData();
- data->setIsInCanvasSubtree(false);
- data->resetComputedStyle();
- data->resetDynamicRestyleObservations();
- data->setIsInsideRegion(false);
}
void Element::clearHoverAndActiveStatusBeforeDetachingRenderer()
@@ -2980,10 +3490,9 @@ void Element::clearHoverAndActiveStatusBeforeDetachingRenderer()
document().userActionElements().didDetach(this);
}
-bool Element::willRecalcStyle(Style::Change)
+void Element::willRecalcStyle(Style::Change)
{
ASSERT(hasCustomStyleResolveCallbacks());
- return true;
}
void Element::didRecalcStyle(Style::Change)
@@ -2991,6 +3500,11 @@ void Element::didRecalcStyle(Style::Change)
ASSERT(hasCustomStyleResolveCallbacks());
}
+void Element::willResetComputedStyle()
+{
+ ASSERT(hasCustomStyleResolveCallbacks());
+}
+
void Element::willAttachRenderers()
{
ASSERT(hasCustomStyleResolveCallbacks());
@@ -3011,10 +3525,10 @@ void Element::didDetachRenderers()
ASSERT(hasCustomStyleResolveCallbacks());
}
-PassRefPtr<RenderStyle> Element::customStyleForRenderer()
+std::optional<ElementStyle> Element::resolveCustomStyle(const RenderStyle&, const RenderStyle*)
{
ASSERT(hasCustomStyleResolveCallbacks());
- return 0;
+ return std::nullopt;
}
void Element::cloneAttributesFromElement(const Element& other)
@@ -3024,13 +3538,13 @@ void Element::cloneAttributesFromElement(const Element& other)
other.synchronizeAllAttributes();
if (!other.m_elementData) {
- m_elementData.clear();
+ m_elementData = nullptr;
return;
}
// We can't update window and document's named item maps since the presence of image and object elements depend on other attributes and children.
// Fortunately, those named item maps are only updated when this element is in the document, which should never be the case.
- ASSERT(!inDocument());
+ ASSERT(!isConnected());
const AtomicString& oldID = getIdAttribute();
const AtomicString& newID = other.getIdAttribute();
@@ -3046,10 +3560,10 @@ void Element::cloneAttributesFromElement(const Element& other)
// 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 is no CSSOM wrapper for other's inline style, and there are no presentation attributes.
- if (other.m_elementData->isUnique()
+ if (is<UniqueElementData>(*other.m_elementData)
&& !other.m_elementData->presentationAttributeStyle()
&& (!other.m_elementData->inlineStyle() || !other.m_elementData->inlineStyle()->hasCSSOMWrapper()))
- const_cast<Element&>(other).m_elementData = static_cast<const UniqueElementData*>(other.m_elementData.get())->makeShareableCopy();
+ const_cast<Element&>(other).m_elementData = downcast<UniqueElementData>(*other.m_elementData).makeShareableCopy();
if (!other.m_elementData->isUnique())
m_elementData = other.m_elementData;
@@ -3070,13 +3584,10 @@ void Element::createUniqueElementData()
{
if (!m_elementData)
m_elementData = UniqueElementData::create();
- else {
- ASSERT(!m_elementData->isUnique());
- m_elementData = static_cast<ShareableElementData*>(m_elementData.get())->makeUniqueCopy();
- }
+ else
+ m_elementData = downcast<ShareableElementData>(*m_elementData).makeUniqueCopy();
}
-#if ENABLE(SVG)
bool Element::hasPendingResources() const
{
return hasRareData() && elementRareData()->hasPendingResources();
@@ -3091,6 +3602,143 @@ void Element::clearHasPendingResources()
{
ensureElementRareData().setHasPendingResources(false);
}
-#endif
+
+bool Element::canContainRangeEndPoint() const
+{
+ return !equalLettersIgnoringASCIICase(attributeWithoutSynchronization(roleAttr), "img");
+}
+
+String Element::completeURLsInAttributeValue(const URL& base, const Attribute& attribute) const
+{
+ return URL(base, attribute.value()).string();
+}
+
+ExceptionOr<Node*> Element::insertAdjacent(const String& where, Ref<Node>&& newChild)
+{
+ // In Internet Explorer if the element has no parent and where is "beforeBegin" or "afterEnd",
+ // a document fragment is created and the elements appended in the correct order. This document
+ // fragment isn't returned anywhere.
+ //
+ // This is impossible for us to implement as the DOM tree does not allow for such structures,
+ // Opera also appears to disallow such usage.
+
+ if (equalLettersIgnoringASCIICase(where, "beforebegin")) {
+ auto* parent = this->parentNode();
+ if (!parent)
+ return nullptr;
+ auto result = parent->insertBefore(newChild, this);
+ if (result.hasException())
+ return result.releaseException();
+ return newChild.ptr();
+ }
+
+ if (equalLettersIgnoringASCIICase(where, "afterbegin")) {
+ auto result = insertBefore(newChild, firstChild());
+ if (result.hasException())
+ return result.releaseException();
+ return newChild.ptr();
+ }
+
+ if (equalLettersIgnoringASCIICase(where, "beforeend")) {
+ auto result = appendChild(newChild);
+ if (result.hasException())
+ return result.releaseException();
+ return newChild.ptr();
+ }
+
+ if (equalLettersIgnoringASCIICase(where, "afterend")) {
+ auto* parent = this->parentNode();
+ if (!parent)
+ return nullptr;
+ auto result = parent->insertBefore(newChild, nextSibling());
+ if (result.hasException())
+ return result.releaseException();
+ return newChild.ptr();
+ }
+
+ return Exception { SYNTAX_ERR };
+}
+
+ExceptionOr<Element*> Element::insertAdjacentElement(const String& where, Element& newChild)
+{
+ auto result = insertAdjacent(where, newChild);
+ if (result.hasException())
+ return result.releaseException();
+ return downcast<Element>(result.releaseReturnValue());
+}
+
+// Step 1 of https://w3c.github.io/DOM-Parsing/#dom-element-insertadjacenthtml.
+static ExceptionOr<ContainerNode&> contextNodeForInsertion(const String& where, Element& element)
+{
+ if (equalLettersIgnoringASCIICase(where, "beforebegin") || equalLettersIgnoringASCIICase(where, "afterend")) {
+ auto* parent = element.parentNode();
+ if (!parent || is<Document>(*parent))
+ return Exception { NO_MODIFICATION_ALLOWED_ERR };
+ return *parent;
+ }
+ if (equalLettersIgnoringASCIICase(where, "afterbegin") || equalLettersIgnoringASCIICase(where, "beforeend"))
+ return element;
+ return Exception { SYNTAX_ERR };
+}
+
+// Step 2 of https://w3c.github.io/DOM-Parsing/#dom-element-insertadjacenthtml.
+static ExceptionOr<Ref<Element>> contextElementForInsertion(const String& where, Element& element)
+{
+ auto contextNodeResult = contextNodeForInsertion(where, element);
+ if (contextNodeResult.hasException())
+ return contextNodeResult.releaseException();
+ auto& contextNode = contextNodeResult.releaseReturnValue();
+ if (!is<Element>(contextNode) || (contextNode.document().isHTMLDocument() && is<HTMLHtmlElement>(contextNode)))
+ return Ref<Element> { HTMLBodyElement::create(contextNode.document()) };
+ return Ref<Element> { downcast<Element>(contextNode) };
+}
+
+// https://w3c.github.io/DOM-Parsing/#dom-element-insertadjacenthtml
+ExceptionOr<void> Element::insertAdjacentHTML(const String& where, const String& markup)
+{
+ // Steps 1 and 2.
+ auto contextElement = contextElementForInsertion(where, *this);
+ if (contextElement.hasException())
+ return contextElement.releaseException();
+ // Step 3.
+ auto fragment = createFragmentForInnerOuterHTML(contextElement.releaseReturnValue(), markup, AllowScriptingContent);
+ if (fragment.hasException())
+ return fragment.releaseException();
+ // Step 4.
+ auto result = insertAdjacent(where, fragment.releaseReturnValue());
+ if (result.hasException())
+ return result.releaseException();
+ return { };
+}
+
+ExceptionOr<void> Element::insertAdjacentText(const String& where, const String& text)
+{
+ auto result = insertAdjacent(where, document().createTextNode(text));
+ if (result.hasException())
+ return result.releaseException();
+ return { };
+}
+
+Element* Element::findAnchorElementForLink(String& outAnchorName)
+{
+ if (!isLink())
+ return nullptr;
+
+ const AtomicString& href = attributeWithoutSynchronization(HTMLNames::hrefAttr);
+ if (href.isNull())
+ return nullptr;
+
+ Document& document = this->document();
+ URL url = document.completeURL(href);
+ if (!url.isValid())
+ return nullptr;
+
+ if (url.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(url, document.baseURL())) {
+ outAnchorName = url.fragmentIdentifier();
+ return document.findAnchor(outAnchorName);
+ }
+
+ return nullptr;
+}
} // namespace WebCore