/* * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * (C) 2001 Peter Kelly (pmk@post.com) * (C) 2001 Dirk Mueller (mueller@kde.org) * (C) 2007 David Smith (catfish.man@gmail.com) * Copyright (C) 2004-2015 Apple Inc. All rights reserved. * (C) 2007 Eric Seidel (eric@webkit.org) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config.h" #include "Element.h" #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 "DOMTokenList.h" #include "Dictionary.h" #include "DocumentSharedObjectPool.h" #include "ElementIterator.h" #include "ElementRareData.h" #include "EventDispatcher.h" #include "EventHandler.h" #include "FlowThreadController.h" #include "FocusController.h" #include "FocusEvent.h" #include "FrameSelection.h" #include "FrameView.h" #include "HTMLCanvasElement.h" #include "HTMLCollection.h" #include "HTMLDocument.h" #include "HTMLLabelElement.h" #include "HTMLNameCollection.h" #include "HTMLParserIdioms.h" #include "HTMLSelectElement.h" #include "HTMLTemplateElement.h" #include "IdChangeInvalidation.h" #include "IdTargetObserverRegistry.h" #include "KeyboardEvent.h" #include "MainFrame.h" #include "MutationObserverInterestGroup.h" #include "MutationRecord.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 "RenderView.h" #include "RenderWidget.h" #include "SVGDocumentExtensions.h" #include "SVGElement.h" #include "SVGNames.h" #include "ScrollLatchingState.h" #include "SelectorQuery.h" #include "Settings.h" #include "SimulatedClick.h" #include "StyleProperties.h" #include "StyleResolver.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 "markup.h" #include #include #include #include namespace WebCore { using namespace HTMLNames; using namespace XMLNames; static HashMap>>& attrNodeListMap() { static NeverDestroyed>>> map; return map; } static Vector>* attrNodeListForElement(Element& element) { if (!element.hasSyntheticAttrChildNodes()) return nullptr; ASSERT(attrNodeListMap().contains(&element)); return &attrNodeListMap().find(&element)->value; } static Vector>& ensureAttrNodeListForElement(Element& element) { if (element.hasSyntheticAttrChildNodes()) { ASSERT(attrNodeListMap().contains(&element)); return attrNodeListMap().find(&element)->value; } ASSERT(!attrNodeListMap().contains(&element)); element.setHasSyntheticAttrChildNodes(true); return attrNodeListMap().add(&element, Vector>()).iterator->value; } static void removeAttrNodeListForElement(Element& element) { ASSERT(element.hasSyntheticAttrChildNodes()); ASSERT(attrNodeListMap().contains(&element)); attrNodeListMap().remove(&element); element.setHasSyntheticAttrChildNodes(false); } static Attr* findAttrNodeInList(Vector>& attrNodeList, const QualifiedName& name) { for (auto& node : attrNodeList) { if (node->qualifiedName().matches(name)) return node.get(); } return nullptr; } static Attr* findAttrNodeInList(Vector>& attrNodeList, const AtomicString& localName, bool shouldIgnoreAttributeCase) { const AtomicString& caseAdjustedName = shouldIgnoreAttributeCase ? localName.convertToASCIILowercase() : localName; for (auto& node : attrNodeList) { if (node->qualifiedName().localName() == caseAdjustedName) return node.get(); } return nullptr; } Ref Element::create(const QualifiedName& tagName, Document& document) { return adoptRef(*new Element(tagName, document, CreateElement)); } Element::Element(const QualifiedName& tagName, Document& document, ConstructionType type) : ContainerNode(document, type) , m_tagName(tagName) { } Element::~Element() { #ifndef NDEBUG 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 isNamedFlowContentNode flag reset. ASSERT_WITH_SECURITY_IMPLICATION(!isNamedFlowContentNode()); } #endif ASSERT(!beforePseudoElement()); ASSERT(!afterPseudoElement()); removeShadowRoot(); if (hasSyntheticAttrChildNodes()) detachAllAttrNodesFromElement(); if (hasPendingResources()) { document().accessSVGExtensions().removeElementFromPendingResources(this); ASSERT(!hasPendingResources()); } } inline ElementRareData* Element::elementRareData() const { ASSERT_WITH_SECURITY_IMPLICATION(hasRareData()); return static_cast(rareData()); } inline ElementRareData& Element::ensureElementRareData() { return static_cast(ensureRareData()); } void Element::clearTabIndexExplicitlyIfNeeded() { if (hasRareData()) elementRareData()->clearTabIndexExplicitly(); } void Element::setTabIndexExplicitly(short tabIndex) { ensureElementRareData().setTabIndexExplicitly(tabIndex); } bool Element::supportsFocus() const { return hasRareData() && elementRareData()->tabIndexSetExplicitly(); } Element* Element::focusDelegate() { return this; } short Element::tabIndex() const { return hasRareData() ? elementRareData()->tabIndex() : 0; } void Element::setTabIndex(int value) { setIntegralAttribute(tabindexAttr, value); } bool Element::isKeyboardFocusable(KeyboardEvent*) const { return isFocusable() && tabIndex() >= 0; } bool Element::isMouseFocusable() const { return isFocusable(); } bool Element::shouldUseInputMethod() { 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) { if (isDisabledFormControl()) return false; if (isForceEvent(platformEvent) && !document().hasListenerTypeForEventType(platformEvent.type())) return false; Ref mouseEvent = MouseEvent::create(eventType, document().defaultView(), platformEvent, detail, relatedTarget); if (mouseEvent->type().isEmpty()) return true; // Shouldn't happen. ASSERT(!mouseEvent->target() || mouseEvent->target() != relatedTarget); bool didNotSwallowEvent = dispatchEvent(mouseEvent) && !mouseEvent->defaultHandled(); if (mouseEvent->type() == eventNames().clickEvent && mouseEvent->detail() == 2) { // 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. Ref 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); if (mouseEvent->defaultHandled()) doubleClickEvent->setDefaultHandled(); dispatchEvent(doubleClickEvent); if (doubleClickEvent->defaultHandled() || doubleClickEvent->defaultPrevented()) return false; } return didNotSwallowEvent; } bool Element::dispatchWheelEvent(const PlatformWheelEvent& event) { Ref wheelEvent = WheelEvent::create(event, document().defaultView()); // 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(); } bool Element::dispatchKeyEvent(const PlatformKeyboardEvent& platformEvent) { Ref 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) { simulateClick(*this, underlyingEvent, eventOptions, visualOptions, SimulatedClickCreationOptions::FromUserAgent); } void Element::dispatchSimulatedClickForBindings(Event* underlyingEvent) { simulateClick(*this, underlyingEvent, SendNoEvents, DoNotShowPressedLook, SimulatedClickCreationOptions::FromBindings); } Ref Element::cloneNodeInternal(Document& targetDocument, CloningOperation type) { switch (type) { case CloningOperation::OnlySelf: case CloningOperation::SelfWithTemplateContent: return cloneElementWithoutChildren(targetDocument); case CloningOperation::Everything: break; } return cloneElementWithChildren(targetDocument); } Ref Element::cloneElementWithChildren(Document& targetDocument) { Ref clone = cloneElementWithoutChildren(targetDocument); cloneChildNodes(clone); return clone; } Ref Element::cloneElementWithoutChildren(Document& targetDocument) { Ref 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; } Ref Element::cloneElementWithoutAttributesAndChildren(Document& targetDocument) { return targetDocument.createElement(tagQName(), false); } RefPtr Element::detachAttribute(unsigned index) { ASSERT(elementData()); const Attribute& attribute = elementData()->attributeAt(index); RefPtr attrNode = attrIfExists(attribute.name()); if (attrNode) detachAttrNodeFromElementWithValue(attrNode.get(), attribute.value()); else attrNode = Attr::create(document(), attribute.name(), attribute.value()); removeAttributeInternal(index, NotInSynchronizationOfLazyAttribute); return attrNode.release(); } bool Element::removeAttribute(const QualifiedName& name) { if (!elementData()) return false; unsigned index = elementData()->findAttributeIndexByName(name); if (index == ElementData::attributeNotFound) return false; removeAttributeInternal(index, NotInSynchronizationOfLazyAttribute); return true; } void Element::setBooleanAttribute(const QualifiedName& name, bool value) { if (value) setAttribute(name, emptyAtom); else removeAttribute(name); } NamedNodeMap& Element::attributes() const { ElementRareData& rareData = const_cast(this)->ensureElementRareData(); if (NamedNodeMap* attributeMap = rareData.attributeMap()) return *attributeMap; rareData.setAttributeMap(std::make_unique(const_cast(*this))); return *rareData.attributeMap(); } Node::NodeType Element::nodeType() const { return ELEMENT_NODE; } bool Element::hasAttribute(const QualifiedName& name) const { return hasAttributeNS(name.namespaceURI(), name.localName()); } void Element::synchronizeAllAttributes() const { if (!elementData()) return; if (elementData()->styleAttributeIsDirty()) { ASSERT(isStyledElement()); static_cast(this)->synchronizeStyleAttributeInternal(); } if (elementData()->animatedSVGAttributesAreDirty()) { ASSERT(isSVGElement()); downcast(*this).synchronizeAnimatedSVGAttribute(anyQName()); } } ALWAYS_INLINE void Element::synchronizeAttribute(const QualifiedName& name) const { if (!elementData()) return; if (UNLIKELY(name == styleAttr && elementData()->styleAttributeIsDirty())) { ASSERT_WITH_SECURITY_IMPLICATION(isStyledElement()); static_cast(this)->synchronizeStyleAttributeInternal(); return; } if (UNLIKELY(elementData()->animatedSVGAttributesAreDirty())) { ASSERT(isSVGElement()); downcast(*this).synchronizeAnimatedSVGAttribute(name); } } 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() && isStyleAttribute(*this, localName)) { ASSERT_WITH_SECURITY_IMPLICATION(isStyledElement()); static_cast(this)->synchronizeStyleAttributeInternal(); return; } 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()); downcast(*this).synchronizeAnimatedSVGAttribute(QualifiedName(nullAtom, localName, nullAtom)); } } const AtomicString& Element::getAttribute(const QualifiedName& name) const { if (!elementData()) return nullAtom; synchronizeAttribute(name); if (const Attribute* attribute = findAttributeByName(name)) return attribute->value(); return nullAtom; } Vector Element::getAttributeNames() const { Vector 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()) return false; 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(*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. // Hyatt wants to fix that some day with a "has visible content" flag or the like. if (!renderer() || renderer()->style().visibility() != VISIBLE) return false; return true; } bool Element::isUserActionElementInActiveChain() const { ASSERT(isUserActionElement()); return document().userActionElements().isInActiveChain(this); } bool Element::isUserActionElementActive() const { ASSERT(isUserActionElement()); return document().userActionElements().isActive(this); } bool Element::isUserActionElementFocused() const { ASSERT(isUserActionElement()); return document().userActionElements().isFocused(this); } bool Element::isUserActionElementHovered() const { ASSERT(isUserActionElement()); return document().userActionElements().isHovered(this); } void Element::setActive(bool flag, bool pause) { if (flag == active()) return; document().userActionElements().setActive(this, flag); if (!renderer()) return; bool reactsToPress = renderStyle()->affectedByActive() || childrenAffectedByActive(); if (reactsToPress) setNeedsStyleRecalc(); 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 // platform supports immediate invalidations on the ChromeClient, so bail if // that isn't supported. if (!document().page()->chrome().client().supportsImmediateInvalidation()) return; if (reactsToPress && pause) { // The delay here is subtle. It relies on an assumption, namely that the amount of time it takes // to repaint the "down" state of the control is about the same time as it would take to repaint the // "up" state. Once you assume this, you can just delay for 100ms - that time (assuming that after you // leave this method, it will be about that long before the flush of the up state happens again). #ifdef HAVE_FUNC_USLEEP double startTime = monotonicallyIncreasingTime(); #endif document().updateStyleIfNeeded(); // Do an immediate repaint. if (renderer()) renderer()->repaint(); // FIXME: Come up with a less ridiculous way of doing this. #ifdef HAVE_FUNC_USLEEP // Now pause for a small amount of time (1/10th of a second from before we repainted in the pressed state) double remainingTime = 0.1 - (monotonicallyIncreasingTime() - startTime); if (remainingTime > 0) usleep(static_cast(remainingTime * 1000000.0)); #endif } } void Element::setFocus(bool flag) { if (flag == focused()) return; document().userActionElements().setFocused(this, flag); setNeedsStyleRecalc(); } void Element::setHovered(bool flag) { if (flag == hovered()) return; document().userActionElements().setHovered(this, flag); if (!renderer()) { // When setting hover to false, the style needs to be recalc'd even when // there's no renderer (imagine setting display:none in the :hover class, // if a nil renderer would prevent this element from recalculating its // style, it would never go back to its normal style and remain // stuck in its hovered style). if (!flag) setNeedsStyleRecalc(); return; } if (renderer()->style().affectedByHover() || childrenAffectedByHover()) setNeedsStyleRecalc(); if (renderer()->style().hasAppearance()) renderer()->theme().stateChanged(*renderer(), ControlStates::HoverState); } void Element::scrollIntoView(bool alignToTop) { document().updateLayoutIgnorePendingStylesheets(); if (!renderer()) return; LayoutRect bounds = renderer()->anchorRect(); // Align to the top / bottom and to the closest edge. if (alignToTop) renderer()->scrollRectToVisible(bounds, ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignTopAlways); else renderer()->scrollRectToVisible(bounds, ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignBottomAlways); } void Element::scrollIntoViewIfNeeded(bool centerIfNeeded) { document().updateLayoutIgnorePendingStylesheets(); if (!renderer()) return; LayoutRect bounds = renderer()->anchorRect(); if (centerIfNeeded) renderer()->scrollRectToVisible(bounds, ScrollAlignment::alignCenterIfNeeded, ScrollAlignment::alignCenterIfNeeded); else renderer()->scrollRectToVisible(bounds, ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignToEdgeIfNeeded); } void Element::scrollIntoViewIfNotVisible(bool centerIfNotVisible) { document().updateLayoutIgnorePendingStylesheets(); if (!renderer()) return; LayoutRect bounds = renderer()->anchorRect(); if (centerIfNotVisible) renderer()->scrollRectToVisible(bounds, ScrollAlignment::alignCenterIfNotVisible, ScrollAlignment::alignCenterIfNotVisible); else renderer()->scrollRectToVisible(bounds, ScrollAlignment::alignToEdgeIfNotVisible, ScrollAlignment::alignToEdgeIfNotVisible); } void Element::scrollByUnits(int units, ScrollGranularity granularity) { document().updateLayoutIgnorePendingStylesheets(); if (!renderer()) return; if (!renderer()->hasOverflowClip()) return; ScrollDirection direction = ScrollDown; if (units < 0) { direction = ScrollUp; units = -units; } Element* stopElement = this; downcast(*renderer()).scroll(direction, granularity, units, &stopElement); } void Element::scrollByLines(int lines) { scrollByUnits(lines, ScrollByLine); } void Element::scrollByPages(int pages) { scrollByUnits(pages, ScrollByPage); } 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). 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. const RenderElement* prev = &renderer; for (RenderElement* curr = prev->parent(); curr; curr = curr->parent()) { if (curr->style().effectiveZoom() != prev->style().effectiveZoom()) { zoomFactor = prev->style().zoom(); break; } prev = curr; } if (prev->isRenderView()) zoomFactor = prev->style().zoom(); } return zoomFactor; } static double adjustForLocalZoom(LayoutUnit value, const RenderElement& renderer, double& zoomFactor) { zoomFactor = localZoomForRenderer(renderer); if (zoomFactor == 1) return value.toDouble(); return value.toDouble() / zoomFactor; } enum LegacyCSSOMElementMetricsRoundingStrategy { Round, Floor }; static bool subpixelMetricsEnabled(const Document& document) { return document.settings() && document.settings()->subpixelCSSOMElementMetricsEnabled(); } static double convertToNonSubpixelValueIfNeeded(double value, const Document& document, LegacyCSSOMElementMetricsRoundingStrategy roundStrategy = Round) { return subpixelMetricsEnabled(document) ? value : roundStrategy == Round ? round(value) : floor(value); } double Element::offsetLeft() { document().updateLayoutIgnorePendingStylesheets(); 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; } double Element::offsetTop() { document().updateLayoutIgnorePendingStylesheets(); 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; } double Element::offsetWidth() { 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; } double Element::offsetHeight() { 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; } Element* Element::bindingsOffsetParent() { Element* element = offsetParent(); if (!element || !element->isInShadowTree()) return element; return element->containingShadowRoot()->type() == ShadowRoot::Type::UserAgent ? nullptr : element; } Element* Element::offsetParent() { document().updateLayoutIgnorePendingStylesheets(); auto renderer = this->renderer(); if (!renderer) return nullptr; auto offsetParent = renderer->offsetParent(); if (!offsetParent) return nullptr; return offsetParent->element(); } double Element::clientLeft() { document().updateLayoutIgnorePendingStylesheets(); if (RenderBox* renderer = renderBox()) { LayoutUnit clientLeft = subpixelMetricsEnabled(renderer->document()) ? renderer->clientLeft() : LayoutUnit(roundToInt(renderer->clientLeft())); return convertToNonSubpixelValueIfNeeded(adjustLayoutUnitForAbsoluteZoom(clientLeft, *renderer).toDouble(), renderer->document()); } return 0; } double Element::clientTop() { document().updateLayoutIgnorePendingStylesheets(); if (RenderBox* renderer = renderBox()) { LayoutUnit clientTop = subpixelMetricsEnabled(renderer->document()) ? renderer->clientTop() : LayoutUnit(roundToInt(renderer->clientTop())); return convertToNonSubpixelValueIfNeeded(adjustLayoutUnitForAbsoluteZoom(clientTop, *renderer).toDouble(), renderer->document()); } return 0; } double Element::clientWidth() { 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().bodyOrFrameset() == this)) return adjustForAbsoluteZoom(renderView.frameView().layoutWidth(), renderView); 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; } double Element::clientHeight() { 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().bodyOrFrameset() == this)) return adjustForAbsoluteZoom(renderView.frameView().layoutHeight(), renderView); 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() { document().updateLayoutIgnorePendingStylesheets(); if (RenderBox* rend = renderBox()) return adjustForAbsoluteZoom(rend->scrollLeft(), *rend); return 0; } int Element::scrollTop() { document().updateLayoutIgnorePendingStylesheets(); if (RenderBox* rend = renderBox()) return adjustForAbsoluteZoom(rend->scrollTop(), *rend); return 0; } void Element::setScrollLeft(int newLeft) { document().updateLayoutIgnorePendingStylesheets(); if (RenderBox* renderer = renderBox()) { renderer->setScrollLeft(static_cast(newLeft * renderer->style().effectiveZoom())); if (auto* scrollableArea = renderer->layer()) scrollableArea->setScrolledProgrammatically(true); } } void Element::setScrollTop(int newTop) { document().updateLayoutIgnorePendingStylesheets(); if (RenderBox* renderer = renderBox()) { renderer->setScrollTop(static_cast(newTop * renderer->style().effectiveZoom())); if (auto* scrollableArea = renderer->layer()) scrollableArea->setScrolledProgrammatically(true); } } int Element::scrollWidth() { document().updateLayoutIfDimensionsOutOfDate(*this, WidthDimensionsCheck); if (RenderBox* rend = renderBox()) return adjustForAbsoluteZoom(rend->scrollWidth(), *rend); return 0; } int Element::scrollHeight() { document().updateLayoutIfDimensionsOutOfDate(*this, HeightDimensionsCheck); if (RenderBox* rend = renderBox()) return adjustForAbsoluteZoom(rend->scrollHeight(), *rend); return 0; } IntRect Element::boundsInRootViewSpace() { document().updateLayoutIgnorePendingStylesheets(); FrameView* view = document().view(); if (!view) return IntRect(); Vector quads; if (isSVGElement() && renderer()) { // Get the bounding rectangle from the SVG model. SVGElement& svgElement = downcast(*this); FloatRect localRect; if (svgElement.getBoundingBox(localRect)) quads.append(renderer()->localToAbsoluteQuad(localRect)); } else { // Get the bounding rectangle from the box model. if (renderBoxModelObject()) renderBoxModelObject()->absoluteQuads(quads); } if (quads.isEmpty()) return IntRect(); IntRect result = quads[0].enclosingBoundingBox(); for (size_t i = 1; i < quads.size(); ++i) result.unite(quads[i].enclosingBoundingBox()); result = view->contentsToRootView(result); return result; } static bool layoutOverflowRectContainsAllDescendants(const RenderElement& renderer) { if (renderer.isRenderView()) return true; if (!renderer.element()) return false; // If there are any position:fixed inside of us, game over. if (auto viewPositionedObjects = renderer.view().positionedObjects()) { for (RenderBox* it : *viewPositionedObjects) { if (it != &renderer && it->style().position() == FixedPosition && renderer.element()->contains(it->element())) return false; } } if (renderer.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 = containingBlockForAbsolutePosition(&renderer)) { if (auto positionedObjects = containingBlock->positionedObjects()) { for (RenderBox* it : *positionedObjects) { if (it != &renderer && renderer.element()->contains(it->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(*this); FloatRect localRect; if (svgElement.getBoundingBox(localRect, SVGLocatable::DisallowStyleUpdate)) result = LayoutRect(renderer()->localToAbsoluteQuad(localRect, UseTransforms, &includesFixedPositionElements).boundingBox()); } else { if (is(renderer())) { RenderBox& box = *downcast(renderer()); bool computedBounds = false; if (RenderFlowThread* flowThread = box.flowThreadContainingBlock()) { bool wasFixed = false; Vector 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(multicolContainer)) { LayoutRect overflowRect = downcast(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(*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 Element::getClientRects() { document().updateLayoutIgnorePendingStylesheets(); RenderBoxModelObject* renderBoxModelObject = this->renderBoxModelObject(); if (!renderBoxModelObject) return ClientRectList::create(); // FIXME: Handle SVG elements. // FIXME: Handle table/inline-table with a caption. Vector quads; renderBoxModelObject->absoluteQuads(quads); document().adjustFloatQuadsForScrollAndAbsoluteZoomAndFrameScale(quads, renderBoxModelObject->style()); return ClientRectList::create(quads); } Ref Element::getBoundingClientRect() { document().updateLayoutIgnorePendingStylesheets(); Vector quads; if (isSVGElement() && renderer() && !renderer()->isSVGRoot()) { // Get the bounding rectangle from the SVG model. SVGElement& svgElement = downcast(*this); FloatRect localRect; if (svgElement.getBoundingBox(localRect)) quads.append(renderer()->localToAbsoluteQuad(localRect)); } else { // Get the bounding rectangle from the box model. if (renderBoxModelObject()) renderBoxModelObject()->absoluteQuads(quads); } if (quads.isEmpty()) return ClientRect::create(); FloatRect result = quads[0].boundingBox(); for (size_t i = 1; i < quads.size(); ++i) result.unite(quads[i].boundingBox()); document().adjustFloatRectForScrollAndAbsoluteZoomAndFrameScale(result, renderer()->style()); return ClientRect::create(result); } IntRect Element::clientRect() const { if (RenderObject* renderer = this->renderer()) return document().view()->contentsToRootView(renderer->absoluteBoundingBoxRect()); return IntRect(); } IntRect Element::screenRect() const { if (RenderObject* renderer = this->renderer()) return document().view()->contentsToScreen(renderer->absoluteBoundingBoxRect()); return IntRect(); } const AtomicString& Element::getAttribute(const AtomicString& localName) const { if (!elementData()) return nullAtom; synchronizeAttribute(localName); if (const Attribute* attribute = elementData()->findAttributeByName(localName, shouldIgnoreAttributeCase(*this))) return attribute->value(); return nullAtom; } const AtomicString& Element::getAttributeNS(const AtomicString& namespaceURI, const AtomicString& localName) const { return getAttribute(QualifiedName(nullAtom, localName, namespaceURI)); } void Element::setAttribute(const AtomicString& localName, const AtomicString& value, ExceptionCode& ec) { if (!Document::isValidName(localName)) { ec = INVALID_CHARACTER_ERR; return; } synchronizeAttribute(localName); const AtomicString& 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); } void Element::setAttribute(const QualifiedName& name, const AtomicString& value) { synchronizeAttribute(name); unsigned index = elementData() ? elementData()->findAttributeIndexByName(name) : ElementData::attributeNotFound; 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; setAttributeInternal(index, name, value, InSynchronizationOfLazyAttribute); } inline void Element::setAttributeInternal(unsigned index, const QualifiedName& name, const AtomicString& newValue, SynchronizationOfLazyAttribute inSynchronizationOfLazyAttribute) { if (newValue.isNull()) { if (index != ElementData::attributeNotFound) removeAttributeInternal(index, inSynchronizationOfLazyAttribute); return; } if (index == ElementData::attributeNotFound) { addAttributeInternal(name, newValue, inSynchronizationOfLazyAttribute); return; } if (inSynchronizationOfLazyAttribute) { ensureUniqueElementData().attributeAt(index).setValue(newValue); return; } const Attribute& attribute = attributeAt(index); QualifiedName attributeName = attribute.name(); AtomicString oldValue = attribute.value(); willModifyAttribute(attributeName, oldValue, newValue); 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 attrNode = attrIfExists(attributeName)) attrNode->setValue(newValue); else { Style::AttributeChangeInvalidation styleInvalidation(*this, name, oldValue, newValue); ensureUniqueElementData().attributeAt(index).setValue(newValue); } } didModifyAttribute(attributeName, oldValue, newValue); } static inline AtomicString makeIdForStyleResolution(const AtomicString& value, bool inQuirksMode) { if (inQuirksMode) return value.convertToASCIILowercase(); return value; } void Element::attributeChanged(const QualifiedName& name, const AtomicString& oldValue, const AtomicString& newValue, AttributeModificationReason) { bool valueIsSameAsBefore = oldValue == newValue; if (!valueIsSameAsBefore) { if (name == HTMLNames::idAttr) { 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) { 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()) setNeedsStyleRecalc(FullStyleChange); } #if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT) else if (name == HTMLNames::slotAttr) { if (auto* parent = parentElement()) { if (auto* shadowRoot = parent->shadowRoot()) shadowRoot->invalidateSlotAssignments(); } } #endif } parseAttribute(name, newValue); document().incDOMTreeVersion(); if (valueIsSameAsBefore) return; invalidateNodeListAndCollectionCachesInAncestors(&name, this); if (AXObjectCache* cache = document().existingAXObjectCache()) cache->handleAttributeChanged(name, this); } template static inline bool classStringHasClassName(const CharacterType* characters, unsigned length) { ASSERT(length > 0); unsigned i = 0; do { if (isNotHTMLSpace(characters[i])) break; ++i; } while (i < length); return i < length; } static inline bool classStringHasClassName(const AtomicString& newClassString) { unsigned length = newClassString.length(); if (!length) return false; if (newClassString.is8Bit()) return classStringHasClassName(newClassString.characters8(), length); return classStringHasClassName(newClassString.characters16(), length); } void Element::classAttributeChanged(const AtomicString& newClassString) { // Note: We'll need ElementData, but it doesn't have to be UniqueElementData. if (!elementData()) ensureUniqueElementData(); 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); } if (hasRareData()) { if (auto* classList = elementRareData()->classList()) classList->attributeValueChanged(newClassString); } } URL Element::absoluteLinkURL() const { if (!isLink()) return URL(); AtomicString linkAttribute; if (hasTagName(SVGNames::aTag)) linkAttribute = getAttribute(XLinkNames::hrefAttr); else linkAttribute = getAttribute(HTMLNames::hrefAttr); if (linkAttribute.isEmpty()) return URL(); 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->styleResolver(); return document().ensureStyleResolver(); } Ref Element::resolveStyle(RenderStyle* parentStyle) { return styleResolver().styleForElement(*this, parentStyle); } // Returns true is the given attribute is an event handler. // We consider an event handler any attribute that begins with "on". // It is a simple solution that has the advantage of not requiring any // code or configuration change if a new event handler is defined. static inline bool isEventHandlerAttribute(const Attribute& attribute) { return attribute.name().namespaceURI().isNull() && attribute.name().localName().startsWith("on"); } bool Element::isJavaScriptURLAttribute(const Attribute& attribute) const { return isURLAttribute(attribute) && protocolIsJavaScript(stripLeadingAndTrailingHTMLSpaces(attribute.value())); } void Element::stripScriptingAttributes(Vector& 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); } void Element::parserSetAttributes(const Vector& attributeVector) { ASSERT(!inDocument()); ASSERT(!parentNode()); ASSERT(!m_elementData); if (!attributeVector.isEmpty()) { 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 (const auto& attribute : attributeVector) attributeChanged(attribute.name(), nullAtom, attribute.value(), ModifiedDirectly); } void Element::parserDidSetAttributes() { } bool Element::hasAttributes() const { synchronizeAllAttributes(); return elementData() && elementData()->length(); } bool Element::hasEquivalentAttributes(const Element* other) const { synchronizeAllAttributes(); other->synchronizeAllAttributes(); if (elementData() == other->elementData()) return true; if (elementData()) return elementData()->isEquivalent(other->elementData()); if (other->elementData()) return other->elementData()->isEquivalent(elementData()); return true; } String Element::nodeName() const { return m_tagName.toString(); } String Element::nodeNamePreservingCase() const { return m_tagName.toString(); } void Element::setPrefix(const AtomicString& prefix, ExceptionCode& ec) { ec = 0; checkSetPrefix(prefix, ec); if (ec) return; m_tagName.setPrefix(prefix.isEmpty() ? AtomicString() : prefix); } const AtomicString& Element::imageSourceURL() const { return fastGetAttribute(srcAttr); } bool Element::rendererIsNeeded(const RenderStyle& style) { return style.display() != NONE; } RenderPtr Element::createElementRenderer(Ref&& style, const RenderTreePosition&) { 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 // by the time we reach updateId ContainerNode::insertedInto(insertionPoint); ASSERT(!wasInDocument || inDocument()); #if ENABLE(FULLSCREEN_API) if (containsFullScreenElement() && parentElement() && !parentElement()->containsFullScreenElement()) setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(true); #endif if (!insertionPoint.isInTreeScope()) return InsertionDone; // 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() && is(newScope->documentScope()) ? &downcast(newScope->documentScope()) : nullptr; if (newScope != &treeScope()) newScope = nullptr; const AtomicString& idValue = getIdAttribute(); if (!idValue.isNull()) { if (newScope) updateIdForTreeScope(*newScope, nullAtom, idValue); if (newDocument) updateIdForDocument(*newDocument, nullAtom, idValue, AlwaysUpdateHTMLDocumentNamedItemMaps); } const AtomicString& nameValue = getNameAttribute(); if (!nameValue.isNull()) { if (newScope) updateNameForTreeScope(*newScope, nullAtom, nameValue); if (newDocument) updateNameForDocument(*newDocument, nullAtom, nameValue); } if (newScope && hasTagName(labelTag)) { if (newScope->shouldCacheLabelsByForAttribute()) updateLabel(*newScope, nullAtom, fastGetAttribute(forAttr)); } return InsertionDone; } void Element::removedFrom(ContainerNode& insertionPoint) { #if ENABLE(FULLSCREEN_API) if (containsFullScreenElement()) setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false); #endif #if ENABLE(POINTER_LOCK) if (document().page()) document().page()->pointerLockController().elementRemoved(this); #endif setSavedLayerScrollPosition(ScrollPosition()); if (insertionPoint.isInTreeScope()) { TreeScope* oldScope = &insertionPoint.treeScope(); HTMLDocument* oldDocument = inDocument() && is(oldScope->documentScope()) ? &downcast(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()) { if (oldScope) updateIdForTreeScope(*oldScope, idValue, nullAtom); if (oldDocument) updateIdForDocument(*oldDocument, idValue, nullAtom, AlwaysUpdateHTMLDocumentNamedItemMaps); } const AtomicString& nameValue = getNameAttribute(); if (!nameValue.isNull()) { if (oldScope) updateNameForTreeScope(*oldScope, nameValue, nullAtom); if (oldDocument) updateNameForDocument(*oldDocument, nameValue, nullAtom); } if (oldScope && hasTagName(labelTag)) { if (oldScope->shouldCacheLabelsByForAttribute()) updateLabel(*oldScope, fastGetAttribute(forAttr), nullAtom); } } ContainerNode::removedFrom(insertionPoint); if (hasPendingResources()) document().accessSVGExtensions().removeElementFromPendingResources(this); #if PLATFORM(MAC) if (Frame* frame = document().frame()) frame->mainFrame().removeLatchingStateForTarget(*this); #endif } void Element::unregisterNamedFlowContentElement() { if (document().cssRegionsEnabled() && isNamedFlowContentNode() && document().renderView()) document().renderView()->flowThreadController().unregisterNamedFlowContentElement(*this); } ShadowRoot* Element::shadowRoot() const { return hasRareData() ? elementRareData()->shadowRoot() : nullptr; } void Element::addShadowRoot(Ref&& newShadowRoot) { ASSERT(!shadowRoot()); ShadowRoot& shadowRoot = newShadowRoot.get(); ensureElementRareData().setShadowRoot(WTFMove(newShadowRoot)); shadowRoot.setHost(this); shadowRoot.setParentTreeScope(&treeScope()); NodeVector postInsertionNotificationTargets; notifyChildNodeInserted(*this, shadowRoot, postInsertionNotificationTargets); for (auto& target : postInsertionNotificationTargets) target->finishedInsertingSubtree(); setNeedsStyleRecalc(ReconstructRenderTree); InspectorInstrumentation::didPushShadowRoot(*this, shadowRoot); if (shadowRoot.type() == ShadowRoot::Type::UserAgent) didAddUserAgentShadowRoot(&shadowRoot); } void Element::removeShadowRoot() { RefPtr oldRoot = shadowRoot(); if (!oldRoot) return; InspectorInstrumentation::willPopShadowRoot(*this, *oldRoot); document().removeFocusedNodeOfSubtree(oldRoot.get()); ASSERT(!oldRoot->renderer()); elementRareData()->clearShadowRoot(); oldRoot->setHost(nullptr); oldRoot->setParentTreeScope(&document()); notifyChildNodeRemoved(*this, *oldRoot); } RefPtr Element::createShadowRoot(ExceptionCode& ec) { if (alwaysCreateUserAgentShadowRoot()) ensureUserAgentShadowRoot(); ec = HIERARCHY_REQUEST_ERR; return nullptr; } RefPtr Element::attachShadow(const Dictionary& dictionary, ExceptionCode& ec) { String mode; dictionary.get("mode", mode); auto type = ShadowRoot::Type::Closed; if (mode == "open") type = ShadowRoot::Type::Open; else if (mode != "closed") { ec = TypeError; return nullptr; } // FIXME: The current spec allows attachShadow on non-HTML elements. if (!is(this) || downcast(this)->canHaveUserAgentShadowRoot()) { ec = NOT_SUPPORTED_ERR; return nullptr; } if (shadowRoot()) { ec = INVALID_STATE_ERR; return nullptr; } addShadowRoot(ShadowRoot::create(document(), type)); return shadowRoot(); } ShadowRoot* Element::shadowRootForBindings(JSC::ExecState& state) const { ShadowRoot* root = shadowRoot(); if (!root) return nullptr; if (root->type() != ShadowRoot::Type::Open) { if (!JSC::jsCast(state.lexicalGlobalObject())->world().shadowRootIsAlwaysOpen()) return nullptr; } return root; } ShadowRoot* Element::userAgentShadowRoot() const { if (ShadowRoot* shadowRoot = this->shadowRoot()) { ASSERT(shadowRoot->type() == ShadowRoot::Type::UserAgent); return shadowRoot; } return nullptr; } ShadowRoot& Element::ensureUserAgentShadowRoot() { ShadowRoot* shadowRoot = userAgentShadowRoot(); if (!shadowRoot) { addShadowRoot(ShadowRoot::create(document(), ShadowRoot::Type::UserAgent)); shadowRoot = userAgentShadowRoot(); } return *shadowRoot; } const AtomicString& Element::shadowPseudoId() const { return pseudo(); } bool Element::childTypeAllowed(NodeType type) const { switch (type) { case ELEMENT_NODE: case TEXT_NODE: case COMMENT_NODE: case PROCESSING_INSTRUCTION_NODE: case CDATA_SECTION_NODE: return true; default: break; } return false; } static void checkForEmptyStyleChange(Element& element) { if (element.styleAffectedByEmpty()) { RenderStyle* style = element.renderStyle(); if (!style || (!style->emptyState() || element.hasChildNodes())) element.setNeedsStyleRecalc(); } } enum SiblingCheckType { FinishedParsingChildren, SiblingElementRemoved, Other }; static void checkForSiblingStyleChanges(Element& parent, SiblingCheckType checkType, Element* elementBeforeChange, Element* elementAfterChange) { // :empty selector. checkForEmptyStyleChange(parent); if (parent.styleChangeType() >= FullStyleChange) 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) { // 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) { RenderStyle* style = elementAfterChange->renderStyle(); if (!style || style->firstChildState()) elementAfterChange->setNeedsStyleRecalc(); } // We also have to handle node removal. if (checkType == SiblingElementRemoved && newFirstElement == elementAfterChange && newFirstElement) { RenderStyle* style = newFirstElement->renderStyle(); if (!style || !style->firstChildState()) newFirstElement->setNeedsStyleRecalc(); } } // :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) { // Find our new last child. Element* newLastElement = ElementTraversal::lastChild(parent); if (newLastElement != elementBeforeChange) { RenderStyle* style = elementBeforeChange->renderStyle(); if (!style || style->lastChildState()) elementBeforeChange->setNeedsStyleRecalc(); } // 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) { RenderStyle* style = newLastElement->renderStyle(); if (!style || !style->lastChildState()) newLastElement->setNeedsStyleRecalc(); } } if (elementAfterChange) { if (elementAfterChange->styleIsAffectedByPreviousSibling()) elementAfterChange->setNeedsStyleRecalc(); else if (elementAfterChange->affectsNextSiblingElementStyle()) { Element* elementToInvalidate = elementAfterChange; do { elementToInvalidate = elementToInvalidate->nextElementSibling(); } while (elementToInvalidate && !elementToInvalidate->styleIsAffectedByPreviousSibling()); if (elementToInvalidate) elementToInvalidate->setNeedsStyleRecalc(); } } // 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.childrenAffectedByBackwardPositionalRules() && elementBeforeChange) parent.setNeedsStyleRecalc(); } void Element::childrenChanged(const ChildChange& change) { ContainerNode::childrenChanged(change); if (change.source == ChildChangeSourceParser) checkForEmptyStyleChange(*this); else { SiblingCheckType checkType = change.type == ElementRemoved ? SiblingElementRemoved : Other; checkForSiblingStyleChanges(*this, checkType, change.previousSiblingElement, change.nextSiblingElement); } #if ENABLE(SHADOW_DOM) || ENABLE(DETAILS_ELEMENT) if (ShadowRoot* shadowRoot = this->shadowRoot()) { switch (change.type) { case ElementInserted: case ElementRemoved: case AllChildrenRemoved: shadowRoot->invalidateSlotAssignments(); break; case TextInserted: case TextRemoved: case TextChanged: shadowRoot->invalidateDefaultSlotAssignments(); break; case NonContentsChildChanged: break; } } #endif } void Element::removeAllEventListeners() { ContainerNode::removeAllEventListeners(); if (ShadowRoot* shadowRoot = this->shadowRoot()) shadowRoot->removeAllEventListeners(); } void Element::beginParsingChildren() { clearIsParsingChildrenFinished(); } void Element::finishParsingChildren() { ContainerNode::finishParsingChildren(); setIsParsingChildrenFinished(); checkForSiblingStyleChanges(*this, FinishedParsingChildren, ElementTraversal::lastChild(*this), nullptr); } #if ENABLE(TREE_DEBUGGING) void Element::formatForDebugger(char* buffer, unsigned length) const { StringBuilder result; String s; result.append(nodeName()); s = getIdAttribute(); if (s.length() > 0) { if (result.length() > 0) result.appendLiteral("; "); result.appendLiteral("id="); result.append(s); } s = getAttribute(classAttr); if (s.length() > 0) { if (result.length() > 0) result.appendLiteral("; "); result.appendLiteral("class="); result.append(s); } strncpy(buffer, result.toString().utf8().data(), length - 1); } #endif const Vector>& Element::attrNodeList() { ASSERT(hasSyntheticAttrChildNodes()); return *attrNodeListForElement(*this); } RefPtr Element::setAttributeNode(Attr* attrNode, ExceptionCode& ec) { if (!attrNode) { ec = TYPE_MISMATCH_ERR; return nullptr; } RefPtr oldAttrNode = attrIfExists(attrNode->qualifiedName().localName(), shouldIgnoreAttributeCase(*this)); if (oldAttrNode.get() == attrNode) return attrNode; // This Attr is already attached to the element. // 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) { ec = INUSE_ATTRIBUTE_ERR; return nullptr; } synchronizeAllAttributes(); UniqueElementData& elementData = ensureUniqueElementData(); unsigned existingAttributeIndex = elementData.findAttributeIndexByName(attrNode->qualifiedName().localName(), shouldIgnoreAttributeCase(*this)); if (existingAttributeIndex != ElementData::attributeNotFound) { const Attribute& attribute = attributeAt(existingAttributeIndex); if (oldAttrNode) detachAttrNodeFromElementWithValue(oldAttrNode.get(), attribute.value()); else oldAttrNode = Attr::create(document(), attrNode->qualifiedName(), attribute.value()); if (attribute.name().matches(attrNode->qualifiedName())) setAttributeInternal(existingAttributeIndex, attrNode->qualifiedName(), attrNode->value(), NotInSynchronizationOfLazyAttribute); else { removeAttributeInternal(existingAttributeIndex, NotInSynchronizationOfLazyAttribute); unsigned existingAttributeIndexForFullQualifiedName = elementData.findAttributeIndexByName(attrNode->qualifiedName()); setAttributeInternal(existingAttributeIndexForFullQualifiedName, attrNode->qualifiedName(), attrNode->value(), NotInSynchronizationOfLazyAttribute); } } else { unsigned existingAttributeIndexForFullQualifiedName = elementData.findAttributeIndexByName(attrNode->qualifiedName()); setAttributeInternal(existingAttributeIndexForFullQualifiedName, attrNode->qualifiedName(), attrNode->value(), NotInSynchronizationOfLazyAttribute); } if (attrNode->ownerElement() != this) { attrNode->attachToElement(this); treeScope().adoptIfNeeded(attrNode); ensureAttrNodeListForElement(*this).append(attrNode); } return oldAttrNode; } RefPtr Element::setAttributeNodeNS(Attr* attrNode, ExceptionCode& ec) { if (!attrNode) { ec = TYPE_MISMATCH_ERR; return 0; } RefPtr oldAttrNode = attrIfExists(attrNode->qualifiedName()); if (oldAttrNode.get() == attrNode) return attrNode; // This Attr is already attached to the element. // 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) { ec = INUSE_ATTRIBUTE_ERR; return 0; } synchronizeAllAttributes(); UniqueElementData& elementData = ensureUniqueElementData(); unsigned 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()); } setAttributeInternal(index, attrNode->qualifiedName(), attrNode->value(), NotInSynchronizationOfLazyAttribute); attrNode->attachToElement(this); treeScope().adoptIfNeeded(attrNode); ensureAttrNodeListForElement(*this).append(attrNode); return oldAttrNode.release(); } RefPtr Element::removeAttributeNode(Attr* attr, ExceptionCode& ec) { if (!attr) { ec = TYPE_MISMATCH_ERR; return nullptr; } if (attr->ownerElement() != this) { ec = NOT_FOUND_ERR; return nullptr; } ASSERT(&document() == &attr->document()); synchronizeAllAttributes(); if (!m_elementData) { ec = NOT_FOUND_ERR; return nullptr; } unsigned existingAttributeIndex = m_elementData->findAttributeIndexByName(attr->qualifiedName()); if (existingAttributeIndex == ElementData::attributeNotFound) { ec = NOT_FOUND_ERR; return nullptr; } RefPtr attrNode = attr; detachAttrNodeFromElementWithValue(attr, m_elementData->attributeAt(existingAttributeIndex).value()); removeAttributeInternal(existingAttributeIndex, NotInSynchronizationOfLazyAttribute); return attrNode; } bool Element::parseAttributeName(QualifiedName& out, const AtomicString& namespaceURI, const AtomicString& qualifiedName, ExceptionCode& ec) { String prefix, localName; if (!Document::parseQualifiedName(qualifiedName, prefix, localName, ec)) return false; ASSERT(!ec); QualifiedName qName(prefix, localName, namespaceURI); if (!Document::hasValidNamespaceForAttributes(qName)) { ec = NAMESPACE_ERR; return false; } out = qName; return true; } void Element::setAttributeNS(const AtomicString& namespaceURI, const AtomicString& qualifiedName, const AtomicString& value, ExceptionCode& ec) { QualifiedName parsedName = anyName; if (!parseAttributeName(parsedName, namespaceURI, qualifiedName, ec)) return; setAttribute(parsedName, value); } void Element::removeAttributeInternal(unsigned index, SynchronizationOfLazyAttribute inSynchronizationOfLazyAttribute) { ASSERT_WITH_SECURITY_IMPLICATION(index < attributeCount()); UniqueElementData& elementData = ensureUniqueElementData(); QualifiedName name = elementData.attributeAt(index).name(); AtomicString valueBeingRemoved = elementData.attributeAt(index).value(); if (RefPtr attrNode = attrIfExists(name)) detachAttrNodeFromElementWithValue(attrNode.get(), elementData.attributeAt(index).value()); if (inSynchronizationOfLazyAttribute) { elementData.removeAttribute(index); return; } if (!valueBeingRemoved.isNull()) willModifyAttribute(name, valueBeingRemoved, nullAtom); { 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) { ensureUniqueElementData().addAttribute(name, value); return; } willModifyAttribute(name, nullAtom, value); { Style::AttributeChangeInvalidation styleInvalidation(*this, name, nullAtom, value); ensureUniqueElementData().addAttribute(name, value); } didAddAttribute(name, value); } bool Element::removeAttribute(const AtomicString& name) { if (!elementData()) return false; AtomicString localName = shouldIgnoreAttributeCase(*this) ? name.convertToASCIILowercase() : name; unsigned index = elementData()->findAttributeIndexByName(localName, false); if (index == ElementData::attributeNotFound) { if (UNLIKELY(localName == styleAttr) && elementData()->styleAttributeIsDirty() && is(*this)) downcast(*this).removeAllInlineStyleProperties(); return false; } removeAttributeInternal(index, NotInSynchronizationOfLazyAttribute); return true; } bool Element::removeAttributeNS(const AtomicString& namespaceURI, const AtomicString& localName) { return removeAttribute(QualifiedName(nullAtom, localName, namespaceURI)); } RefPtr Element::getAttributeNode(const AtomicString& localName) { if (!elementData()) return nullptr; synchronizeAttribute(localName); const Attribute* attribute = elementData()->findAttributeByName(localName, shouldIgnoreAttributeCase(*this)); if (!attribute) return nullptr; return ensureAttr(attribute->name()); } RefPtr Element::getAttributeNodeNS(const AtomicString& namespaceURI, const AtomicString& localName) { if (!elementData()) return 0; QualifiedName qName(nullAtom, localName, namespaceURI); synchronizeAttribute(qName); const Attribute* attribute = elementData()->findAttributeByName(qName); if (!attribute) return 0; return ensureAttr(attribute->name()); } bool Element::hasAttribute(const AtomicString& localName) const { if (!elementData()) return false; synchronizeAttribute(localName); return elementData()->findAttributeByName(localName, shouldIgnoreAttributeCase(*this)); } bool Element::hasAttributeNS(const AtomicString& namespaceURI, const AtomicString& localName) const { if (!elementData()) return false; QualifiedName qName(nullAtom, localName, namespaceURI); synchronizeAttribute(qName); return elementData()->findAttributeByName(qName); } CSSStyleDeclaration* Element::cssomStyle() { return nullptr; } void Element::focus(bool restorePreviousSelection, FocusDirection direction) { if (!inDocument()) return; 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 // that it can be updated soon after attach. if (document().haveStylesheetsLoaded()) { document().updateLayoutIgnorePendingStylesheets(); if (!isFocusable()) return; } if (!supportsFocus()) return; RefPtr protect; if (Page* page = document().page()) { // Focus and change event handlers can cause us to lose our last ref. // 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)) return; } // Setting the focused node above might have invalidated the layout due to scripts. document().updateLayoutIgnorePendingStylesheets(); if (!isFocusable()) { ensureElementRareData().setNeedsFocusAppearanceUpdateSoonAfterAttach(true); return; } cancelFocusAppearanceUpdate(); #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 . FrameView* view = document().view(); bool isFormControl = view && is(*this); if (isFormControl) view->setProhibitsScrolling(true); #endif updateFocusAppearance(restorePreviousSelection ? SelectionRestorationMode::Restore : SelectionRestorationMode::SetDefault); #if PLATFORM(IOS) if (isFormControl) view->setProhibitsScrolling(false); #endif } void Element::updateFocusAppearanceAfterAttachIfNeeded() { if (!hasRareData()) return; ElementRareData* data = elementRareData(); if (!data->needsFocusAppearanceUpdateSoonAfterAttach()) return; if (isFocusable() && document().focusedElement() == this) document().updateFocusAppearanceSoon(SelectionRestorationMode::SetDefault); data->setNeedsFocusAppearanceUpdateSoonAfterAttach(false); } void Element::updateFocusAppearance(SelectionRestorationMode, SelectionRevealMode revealMode) { if (isRootEditableElement()) { // Keep frame alive in this method, since setSelection() may release the last reference to |frame|. RefPtr 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().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, FrameSelection::defaultSetSelectionOptions(), Element::defaultFocusTextStateChangeIntent()); if (revealMode == SelectionRevealMode::Reveal) frame->selection().revealSelection(); } } else if (renderer() && !renderer()->isWidget() && revealMode == SelectionRevealMode::Reveal) renderer()->scrollRectToVisible(renderer()->anchorRect()); } void Element::blur() { cancelFocusAppearanceUpdate(); if (treeScope().focusedElement() == this) { if (Frame* frame = document().frame()) frame->page()->focusController().setFocusedElement(0, frame); else document().setFocusedElement(nullptr); } } void Element::dispatchFocusInEvent(const AtomicString& eventType, RefPtr&& oldFocusedElement) { ASSERT_WITH_SECURITY_IMPLICATION(!NoEventDispatchAssertion::isEventDispatchForbidden()); ASSERT(eventType == eventNames().focusinEvent || eventType == eventNames().DOMFocusInEvent); dispatchScopedEvent(FocusEvent::create(eventType, true, false, document().defaultView(), 0, WTFMove(oldFocusedElement))); } void Element::dispatchFocusOutEvent(const AtomicString& eventType, RefPtr&& newFocusedElement) { ASSERT_WITH_SECURITY_IMPLICATION(!NoEventDispatchAssertion::isEventDispatchForbidden()); ASSERT(eventType == eventNames().focusoutEvent || eventType == eventNames().DOMFocusOutEvent); dispatchScopedEvent(FocusEvent::create(eventType, true, false, document().defaultView(), 0, WTFMove(newFocusedElement))); } void Element::dispatchFocusEvent(RefPtr&& oldFocusedElement, FocusDirection) { if (document().page()) document().page()->chrome().client().elementDidFocus(this); EventDispatcher::dispatchEvent(this, FocusEvent::create(eventNames().focusEvent, false, false, document().defaultView(), 0, WTFMove(oldFocusedElement))); } void Element::dispatchBlurEvent(RefPtr&& newFocusedElement) { if (document().page()) document().page()->chrome().client().elementDidBlur(this); EventDispatcher::dispatchEvent(this, FocusEvent::create(eventNames().blurEvent, false, false, document().defaultView(), 0, WTFMove(newFocusedElement))); } #if ENABLE(MOUSE_FORCE_EVENTS) bool Element::dispatchMouseForceWillBegin() { 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); Ref mouseForceWillBeginEvent = MouseEvent::create(eventNames().webkitmouseforcewillbeginEvent, document().defaultView(), platformMouseEvent, 0, nullptr); mouseForceWillBeginEvent->setTarget(this); dispatchEvent(mouseForceWillBeginEvent); if (mouseForceWillBeginEvent->defaultHandled() || mouseForceWillBeginEvent->defaultPrevented()) return true; return false; } #else bool Element::dispatchMouseForceWillBegin() { return false; } #endif // #if ENABLE(MOUSE_FORCE_EVENTS) void Element::mergeWithNextTextNode(Text& node, ExceptionCode& ec) { Node* next = node.nextSibling(); if (!is(next)) return; Ref textNode(node); Ref textNext(downcast(*next)); textNode->appendData(textNext->data()); textNext->remove(ec); } String Element::innerHTML() const { return createMarkup(*this, ChildrenOnly); } String Element::outerHTML() const { return createMarkup(*this); } void Element::setOuterHTML(const String& html, ExceptionCode& ec) { Element* p = parentElement(); if (!is(p)) { ec = NO_MODIFICATION_ALLOWED_ERR; return; } RefPtr parent = downcast(p); RefPtr prev = previousSibling(); RefPtr next = nextSibling(); RefPtr fragment = createFragmentForInnerOuterHTML(html, parent.get(), AllowScriptingContent, ec); if (ec) return; parent->replaceChild(fragment.releaseNonNull(), *this, ec); RefPtr node = next ? next->previousSibling() : nullptr; if (!ec && is(node.get())) mergeWithNextTextNode(downcast(*node), ec); if (!ec && is(prev.get())) mergeWithNextTextNode(downcast(*prev), ec); } void Element::setInnerHTML(const String& html, ExceptionCode& ec) { if (RefPtr fragment = createFragmentForInnerOuterHTML(html, this, AllowScriptingContent, ec)) { ContainerNode* container = this; #if ENABLE(TEMPLATE_ELEMENT) if (is(*this)) container = downcast(*this).content(); #endif replaceChildrenWithFragment(*container, fragment.releaseNonNull(), ec); } } String Element::innerText() { // We need to update layout, since plainText uses line boxes in the render tree. document().updateLayoutIgnorePendingStylesheets(); if (!renderer()) return textContent(true); return plainText(rangeOfContents(*this).ptr()); } String Element::outerText() { // Getting outerText is the same as getting innerText, only // setting is different. You would think this should get the plain // text for the outer range, but this is wrong,
for instance // would return different values for inner and outer text by such // a rule, but it doesn't in WinIE, and we want to match that. return innerText(); } String Element::title() const { return String(); } const AtomicString& Element::pseudo() const { return fastGetAttribute(pseudoAttr); } void Element::setPseudo(const AtomicString& value) { setAttributeWithoutSynchronization(pseudoAttr, value); } LayoutSize Element::minimumSizeForResizing() const { return hasRareData() ? elementRareData()->minimumSizeForResizing() : defaultMinimumSizeForResizing(); } void Element::setMinimumSizeForResizing(const LayoutSize& size) { if (!hasRareData() && size == defaultMinimumSizeForResizing()) return; ensureElementRareData().setMinimumSizeForResizing(size); } static PseudoElement* beforeOrAfterPseudoElement(Element& host, PseudoId pseudoElementSpecifier) { switch (pseudoElementSpecifier) { case BEFORE: return host.beforePseudoElement(); case AFTER: return host.afterPseudoElement(); default: return nullptr; } } RenderStyle* Element::existingComputedStyle() { if (auto* renderTreeStyle = renderStyle()) return renderTreeStyle; if (hasRareData()) return elementRareData()->computedStyle(); return nullptr; } RenderStyle& Element::resolveComputedStyle() { ASSERT(inDocument()); ASSERT(!existingComputedStyle()); Deque elementsRequiringComputedStyle({ this }); RenderStyle* computedStyle = nullptr; // Collect ancestors until we find one that has style. auto composedAncestors = composedTreeAncestors(*this); for (auto& ancestor : composedAncestors) { if (!is(ancestor)) break; auto& ancestorElement = downcast(ancestor); elementsRequiringComputedStyle.prepend(&ancestorElement); if (auto* existingStyle = ancestorElement.existingComputedStyle()) { computedStyle = existingStyle; break; } } // Resolve and cache styles starting from the most distant ancestor. for (auto* element : elementsRequiringComputedStyle) { auto style = document().styleForElementIgnoringPendingStylesheets(*element, computedStyle); computedStyle = style.ptr(); ElementRareData& rareData = element->ensureElementRareData(); rareData.setComputedStyle(WTFMove(style)); } return *computedStyle; } RenderStyle* Element::computedStyle(PseudoId pseudoElementSpecifier) { if (PseudoElement* pseudoElement = beforeOrAfterPseudoElement(*this, pseudoElementSpecifier)) return pseudoElement->computedStyle(); auto* style = existingComputedStyle(); if (!style) { if (!inDocument()) return nullptr; style = &resolveComputedStyle(); } if (pseudoElementSpecifier) { if (auto* cachedPseudoStyle = style->getCachedPseudoStyle(pseudoElementSpecifier)) return cachedPseudoStyle; } return style; } bool Element::needsStyleInvalidation() const { if (!inRenderedDocument()) return false; if (styleChangeType() >= FullStyleChange) return false; if (document().hasPendingForcedStyleRecalc()) return false; return true; } void Element::setStyleAffectedByEmpty() { ensureElementRareData().setStyleAffectedByEmpty(true); } void Element::setChildrenAffectedByActive() { ensureElementRareData().setChildrenAffectedByActive(true); } void Element::setChildrenAffectedByDrag() { ensureElementRareData().setChildrenAffectedByDrag(true); } 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()) return true; if (!hasRareData()) return false; return rareDataChildrenAffectedByActive() || rareDataChildrenAffectedByDrag() || rareDataChildrenAffectedByBackwardPositionalRules() || rareDataChildrenAffectedByPropertyBasedBackwardPositionalRules(); } bool Element::rareDataStyleAffectedByEmpty() const { ASSERT(hasRareData()); return elementRareData()->styleAffectedByEmpty(); } bool Element::rareDataChildrenAffectedByActive() const { ASSERT(hasRareData()); return elementRareData()->childrenAffectedByActive(); } bool Element::rareDataChildrenAffectedByDrag() const { ASSERT(hasRareData()); return elementRareData()->childrenAffectedByDrag(); } bool Element::rareDataChildrenAffectedByBackwardPositionalRules() const { ASSERT(hasRareData()); return elementRareData()->childrenAffectedByBackwardPositionalRules(); } bool Element::rareDataChildrenAffectedByPropertyBasedBackwardPositionalRules() const { ASSERT(hasRareData()); return elementRareData()->childrenAffectedByPropertyBasedBackwardPositionalRules(); } unsigned Element::rareDataChildIndex() const { ASSERT(hasRareData()); return elementRareData()->childIndex(); } void Element::setRegionOversetState(RegionOversetState state) { ensureElementRareData().setRegionOversetState(state); } RegionOversetState Element::regionOversetState() const { return hasRareData() ? elementRareData()->regionOversetState() : RegionUndefined; } AtomicString Element::computeInheritedLanguage() const { 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. const Node* currentNode = this; while ((currentNode = currentNode->parentNode())) { if (is(*currentNode)) { if (const ElementData* elementData = downcast(*currentNode).elementData()) { if (const Attribute* attribute = elementData->findLanguageAttribute()) return attribute->value(); } } else if (is(*currentNode)) { // checking the MIME content-language return downcast(*currentNode).contentLanguage(); } } return nullAtom; } Locale& Element::locale() const { return document().getCachedLocale(computeInheritedLanguage()); } void Element::cancelFocusAppearanceUpdate() { if (hasRareData()) elementRareData()->setNeedsFocusAppearanceUpdateSoonAfterAttach(false); if (document().focusedElement() == this) document().cancelFocusAppearanceUpdate(); } void Element::normalizeAttributes() { if (!hasAttributes()) return; auto* attrNodeList = attrNodeListForElement(*this); if (!attrNodeList) return; // Copy the Attr Vector because Node::normalize() can fire synchronous JS // events (e.g. DOMSubtreeModified) and a JS listener could add / remove // attributes while we are iterating. auto copyOfAttrNodeList = *attrNodeList; for (auto& attrNode : copyOfAttrNodeList) attrNode->normalize(); } PseudoElement* Element::beforePseudoElement() const { return hasRareData() ? elementRareData()->beforePseudoElement() : nullptr; } PseudoElement* Element::afterPseudoElement() const { return hasRareData() ? elementRareData()->afterPseudoElement() : nullptr; } void Element::setBeforePseudoElement(Ref&& element) { ensureElementRareData().setBeforePseudoElement(WTFMove(element)); } void Element::setAfterPseudoElement(Ref&& element) { ensureElementRareData().setAfterPseudoElement(WTFMove(element)); } static void disconnectPseudoElement(PseudoElement* pseudoElement) { if (!pseudoElement) return; if (pseudoElement->renderer()) Style::detachRenderTree(*pseudoElement); ASSERT(pseudoElement->hostElement()); pseudoElement->clearHostElement(); } void Element::clearBeforePseudoElement() { if (!hasRareData()) return; disconnectPseudoElement(elementRareData()->beforePseudoElement()); elementRareData()->setBeforePseudoElement(nullptr); } void Element::clearAfterPseudoElement() { if (!hasRareData()) return; disconnectPseudoElement(elementRareData()->afterPseudoElement()); elementRareData()->setAfterPseudoElement(nullptr); } bool Element::matchesReadWritePseudoClass() const { return false; } bool Element::matches(const String& selector, ExceptionCode& ec) { SelectorQuery* selectorQuery = document().selectorQueryForString(selector, ec); return selectorQuery && selectorQuery->matches(*this); } Element* Element::closest(const String& selector, ExceptionCode& ec) { SelectorQuery* selectorQuery = document().selectorQueryForString(selector, ec); if (selectorQuery) return selectorQuery->closest(*this); return nullptr; } bool Element::shouldAppearIndeterminate() const { return false; } 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(*this, HTMLNames::classAttr)); return *data.classList(); } DatasetDOMStringMap& Element::dataset() { ElementRareData& data = ensureElementRareData(); if (!data.dataset()) data.setDataset(std::make_unique(*this)); return *data.dataset(); } URL Element::getURLAttribute(const QualifiedName& name) const { #if !ASSERT_DISABLED if (elementData()) { if (const Attribute* attribute = findAttributeByName(name)) ASSERT(isURLAttribute(*attribute)); } #endif return document().completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(name))); } URL Element::getNonEmptyURLAttribute(const QualifiedName& name) const { #if !ASSERT_DISABLED if (elementData()) { if (const Attribute* attribute = findAttributeByName(name)) ASSERT(isURLAttribute(*attribute)); } #endif String value = stripLeadingAndTrailingHTMLSpaces(getAttribute(name)); if (value.isEmpty()) return URL(); return document().completeURL(value); } int Element::getIntegralAttribute(const QualifiedName& attributeName) const { return getAttribute(attributeName).string().toInt(); } void Element::setIntegralAttribute(const QualifiedName& attributeName, int value) { setAttribute(attributeName, AtomicString::number(value)); } unsigned Element::getUnsignedIntegralAttribute(const QualifiedName& attributeName) const { return getAttribute(attributeName).string().toUInt(); } void Element::setUnsignedIntegralAttribute(const QualifiedName& attributeName, unsigned value) { setAttribute(attributeName, AtomicString::number(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 { // Only create renderers for SVG elements whose parents are SVG elements, or for proper subdocuments. if (child.isSVGElement()) { ASSERT(!isSVGElement()); const SVGElement& childElement = downcast(child); return is(childElement) && childElement.isValid(); } 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); } bool Element::containsFullScreenElement() const { return hasRareData() && elementRareData()->containsFullScreenElement(); } void Element::setContainsFullScreenElement(bool flag) { ensureElementRareData().setContainsFullScreenElement(flag); setNeedsStyleRecalc(SyntheticStyleChange); } static Element* parentCrossingFrameBoundaries(Element* element) { ASSERT(element); return element->parentElement() ? element->parentElement() : element->document().ownerElement(); } void Element::setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(bool flag) { Element* element = this; while ((element = parentCrossingFrameBoundaries(element))) element->setContainsFullScreenElement(flag); } #endif #if ENABLE(POINTER_LOCK) void Element::requestPointerLock() { if (document().page()) document().page()->pointerLockController().requestPointerLock(this); } #endif SpellcheckAttributeState Element::spellcheckAttributeState() const { const AtomicString& value = fastGetAttribute(HTMLNames::spellcheckAttr); if (value.isNull()) return SpellcheckAttributeDefault; if (value.isEmpty() || equalLettersIgnoringASCIICase(value, "true")) return SpellcheckAttributeTrue; if (equalLettersIgnoringASCIICase(value, "false")) return SpellcheckAttributeFalse; return SpellcheckAttributeDefault; } bool Element::isSpellCheckingEnabled() const { for (const Element* element = this; element; element = element->parentOrShadowHostElement()) { switch (element->spellcheckAttributeState()) { case SpellcheckAttributeTrue: return true; case SpellcheckAttributeFalse: return false; case SpellcheckAttributeDefault: break; } } return true; } RenderNamedFlowFragment* Element::renderNamedFlowFragment() const { if (renderer() && renderer()->isRenderNamedFlowFragmentContainer()) return downcast(*renderer()).renderNamedFlowFragment(); return nullptr; } #if ENABLE(CSS_REGIONS) bool Element::shouldMoveToFlowThread(const RenderStyle& styleToUse) const { #if ENABLE(FULLSCREEN_API) if (document().webkitIsFullScreen() && document().webkitCurrentFullScreenElement() == this) return false; #endif if (isInShadowTree()) return false; if (!styleToUse.hasFlowInto()) return false; return true; } const AtomicString& Element::webkitRegionOverset() const { document().updateLayoutIgnorePendingStylesheets(); static NeverDestroyed undefinedState("undefined", AtomicString::ConstructFromLiteral); if (!document().cssRegionsEnabled() || !renderNamedFlowFragment()) return undefinedState; switch (regionOversetState()) { case RegionFit: { static NeverDestroyed fitState("fit", AtomicString::ConstructFromLiteral); return fitState; } case RegionEmpty: { static NeverDestroyed emptyState("empty", AtomicString::ConstructFromLiteral); return emptyState; } case RegionOverset: { static NeverDestroyed overflowState("overset", AtomicString::ConstructFromLiteral); return overflowState; } case RegionUndefined: return undefinedState; } ASSERT_NOT_REACHED(); return undefinedState; } Vector> Element::webkitGetRegionFlowRanges() const { Vector> rangeObjects; if (!document().cssRegionsEnabled()) return rangeObjects; document().updateLayoutIgnorePendingStylesheets(); if (renderer() && renderer()->isRenderNamedFlowFragmentContainer()) { RenderNamedFlowFragment& namedFlowFragment = *downcast(*renderer()).renderNamedFlowFragment(); if (namedFlowFragment.isValid()) namedFlowFragment.getRanges(rangeObjects); } return rangeObjects; } #endif #ifndef NDEBUG bool Element::fastAttributeLookupAllowed(const QualifiedName& name) const { if (name == HTMLNames::styleAttr) return false; if (isSVGElement()) return !downcast(*this).isAnimatableAttribute(name); return true; } #endif #ifdef DUMP_NODE_STATISTICS bool Element::hasNamedNodeMap() const { return hasRareData() && elementRareData()->attributeMap(); } #endif inline void Element::updateName(const AtomicString& oldName, const AtomicString& newName) { if (!isInTreeScope()) return; if (oldName == newName) return; updateNameForTreeScope(treeScope(), oldName, newName); if (!inDocument()) return; if (!is(document())) return; updateNameForDocument(downcast(document()), oldName, newName); } void Element::updateNameForTreeScope(TreeScope& scope, const AtomicString& oldName, const AtomicString& newName) { ASSERT(oldName != newName); if (!oldName.isEmpty()) scope.removeElementByName(*oldName.impl(), *this); if (!newName.isEmpty()) scope.addElementByName(*newName.impl(), *this); } void Element::updateNameForDocument(HTMLDocument& document, const AtomicString& oldName, const AtomicString& newName) { ASSERT(oldName != newName); 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::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) document.addDocumentNamedItem(*newName.impl(), *this); } } inline void Element::updateId(const AtomicString& oldId, const AtomicString& newId, NotifyObservers notifyObservers) { if (!isInTreeScope()) return; if (oldId == newId) return; updateIdForTreeScope(treeScope(), oldId, newId, notifyObservers); if (!inDocument()) return; if (!is(document())) return; updateIdForDocument(downcast(document()), oldId, newId, UpdateHTMLDocumentNamedItemMapsOnlyIfDiffersFromNameAttribute); } void Element::updateIdForTreeScope(TreeScope& scope, const AtomicString& oldId, const AtomicString& newId, NotifyObservers notifyObservers) { ASSERT(isInTreeScope()); ASSERT(oldId != newId); if (!oldId.isEmpty()) scope.removeElementById(*oldId.impl(), *this, notifyObservers == NotifyObservers::Yes); if (!newId.isEmpty()) scope.addElementById(*newId.impl(), *this, notifyObservers == NotifyObservers::Yes); } void Element::updateIdForDocument(HTMLDocument& document, const AtomicString& oldId, const AtomicString& newId, HTMLDocumentNamedItemMapsUpdatingCondition condition) { ASSERT(inDocument()); ASSERT(oldId != newId); 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::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) document.addDocumentNamedItem(*newId.impl(), *this); } } void Element::updateLabel(TreeScope& scope, const AtomicString& oldForAttributeValue, const AtomicString& newForAttributeValue) { ASSERT(hasTagName(labelTag)); if (!inDocument()) return; if (oldForAttributeValue == newForAttributeValue) return; if (!oldForAttributeValue.isEmpty()) scope.removeLabel(*oldForAttributeValue.impl(), downcast(*this)); if (!newForAttributeValue.isEmpty()) scope.addLabel(*newForAttributeValue.impl(), downcast(*this)); } void Element::willModifyAttribute(const QualifiedName& name, const AtomicString& oldValue, const AtomicString& newValue) { 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); else if (name == HTMLNames::forAttr && hasTagName(labelTag)) { if (treeScope().shouldCacheLabelsByForAttribute()) updateLabel(treeScope(), oldValue, newValue); } if (std::unique_ptr recipients = MutationObserverInterestGroup::createForAttributesMutation(*this, name)) recipients->enqueueMutationRecord(MutationRecord::createAttributes(*this, name, oldValue)); 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); dispatchSubtreeModifiedEvent(); } void Element::didModifyAttribute(const QualifiedName& name, const AtomicString& oldValue, const AtomicString& newValue) { attributeChanged(name, oldValue, 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()); dispatchSubtreeModifiedEvent(); } IntPoint Element::savedLayerScrollPosition() const { return hasRareData() ? elementRareData()->savedLayerScrollPosition() : IntPoint(); } void Element::setSavedLayerScrollPosition(const IntPoint& position) { if (position.isZero() && !hasRareData()) return; ensureElementRareData().setSavedLayerScrollPosition(position); } RefPtr Element::attrIfExists(const AtomicString& localName, bool shouldIgnoreAttributeCase) { if (auto* attrNodeList = attrNodeListForElement(*this)) return findAttrNodeInList(*attrNodeList, localName, shouldIgnoreAttributeCase); return nullptr; } RefPtr Element::attrIfExists(const QualifiedName& name) { if (auto* attrNodeList = attrNodeListForElement(*this)) return findAttrNodeInList(*attrNodeList, name); return nullptr; } RefPtr Element::ensureAttr(const QualifiedName& name) { auto& attrNodeList = ensureAttrNodeListForElement(*this); RefPtr attrNode = findAttrNodeInList(attrNodeList, name); if (!attrNode) { attrNode = Attr::create(this, name); treeScope().adoptIfNeeded(attrNode.get()); attrNodeList.append(attrNode); } return attrNode.release(); } void Element::detachAttrNodeFromElementWithValue(Attr* attrNode, const AtomicString& value) { ASSERT(hasSyntheticAttrChildNodes()); attrNode->detachFromElementWithValue(value); auto& attrNodeList = *attrNodeListForElement(*this); bool found = attrNodeList.removeFirstMatching([attrNode] (const RefPtr& attribute) { return attribute->qualifiedName() == attrNode->qualifiedName(); }); ASSERT_UNUSED(found, found); if (attrNodeList.isEmpty()) removeAttrNodeListForElement(*this); } void Element::detachAllAttrNodesFromElement() { auto* attrNodeList = attrNodeListForElement(*this); ASSERT(attrNodeList); for (const Attribute& attribute : attributesIterator()) { if (RefPtr attrNode = findAttrNodeInList(*attrNodeList, attribute.name())) attrNode->detachFromElementWithValue(attribute.value()); } removeAttrNodeListForElement(*this); } void Element::resetComputedStyle() { if (!hasRareData() || !elementRareData()->computedStyle()) return; 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(*this)) reset(child); } void Element::clearStyleDerivedDataBeforeDetachingRenderer() { unregisterNamedFlowContentElement(); cancelFocusAppearanceUpdate(); clearBeforePseudoElement(); clearAfterPseudoElement(); if (!hasRareData()) return; ElementRareData* data = elementRareData(); data->resetComputedStyle(); data->resetDynamicRestyleObservations(); } void Element::clearHoverAndActiveStatusBeforeDetachingRenderer() { if (!isUserActionElement()) return; if (hovered()) document().hoveredElementDidDetach(this); if (inActiveChain()) document().elementInActiveChainDidDetach(this); document().userActionElements().didDetach(this); } bool Element::willRecalcStyle(Style::Change) { ASSERT(hasCustomStyleResolveCallbacks()); return true; } void Element::didRecalcStyle(Style::Change) { ASSERT(hasCustomStyleResolveCallbacks()); } void Element::willResetComputedStyle() { ASSERT(hasCustomStyleResolveCallbacks()); } void Element::willAttachRenderers() { ASSERT(hasCustomStyleResolveCallbacks()); } void Element::didAttachRenderers() { ASSERT(hasCustomStyleResolveCallbacks()); } void Element::willDetachRenderers() { ASSERT(hasCustomStyleResolveCallbacks()); } void Element::didDetachRenderers() { ASSERT(hasCustomStyleResolveCallbacks()); } RefPtr Element::customStyleForRenderer(RenderStyle&) { ASSERT(hasCustomStyleResolveCallbacks()); return nullptr; } void Element::cloneAttributesFromElement(const Element& other) { if (hasSyntheticAttrChildNodes()) detachAllAttrNodesFromElement(); other.synchronizeAllAttributes(); if (!other.m_elementData) { 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()); const AtomicString& oldID = getIdAttribute(); const AtomicString& newID = other.getIdAttribute(); if (!oldID.isNull() || !newID.isNull()) updateId(oldID, newID, NotifyObservers::No); // Will notify observers after the attribute is actually changed. const AtomicString& oldName = getNameAttribute(); const AtomicString& newName = other.getNameAttribute(); if (!oldName.isNull() || !newName.isNull()) updateName(oldName, newName); // 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 (is(*other.m_elementData) && !other.m_elementData->presentationAttributeStyle() && (!other.m_elementData->inlineStyle() || !other.m_elementData->inlineStyle()->hasCSSOMWrapper())) const_cast(other).m_elementData = downcast(*other.m_elementData).makeShareableCopy(); if (!other.m_elementData->isUnique()) m_elementData = other.m_elementData; else m_elementData = other.m_elementData->makeUniqueCopy(); for (const Attribute& attribute : attributesIterator()) attributeChanged(attribute.name(), nullAtom, attribute.value(), ModifiedByCloning); } void Element::cloneDataFromElement(const Element& other) { cloneAttributesFromElement(other); copyNonAttributePropertiesFromElement(other); } void Element::createUniqueElementData() { if (!m_elementData) m_elementData = UniqueElementData::create(); else m_elementData = downcast(*m_elementData).makeUniqueCopy(); } bool Element::hasPendingResources() const { return hasRareData() && elementRareData()->hasPendingResources(); } void Element::setHasPendingResources() { ensureElementRareData().setHasPendingResources(true); } void Element::clearHasPendingResources() { ensureElementRareData().setHasPendingResources(false); } bool Element::canContainRangeEndPoint() const { return !equalLettersIgnoringASCIICase(fastGetAttribute(roleAttr), "img"); } String Element::completeURLsInAttributeValue(const URL& base, const Attribute& attribute) const { return URL(base, attribute.value()).string(); } } // namespace WebCore