diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/dom/Element.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/dom/Element.cpp')
-rw-r--r-- | Source/WebCore/dom/Element.cpp | 2678 |
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, + §ionTag, + &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 |