1 | /* |
2 | * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
3 | * (C) 1999 Antti Koivisto (koivisto@kde.org) |
4 | * (C) 2001 Peter Kelly (pmk@post.com) |
5 | * (C) 2001 Dirk Mueller (mueller@kde.org) |
6 | * (C) 2007 David Smith (catfish.man@gmail.com) |
7 | * Copyright (C) 2004-2017 Apple Inc. All rights reserved. |
8 | * (C) 2007 Eric Seidel (eric@webkit.org) |
9 | * |
10 | * This library is free software; you can redistribute it and/or |
11 | * modify it under the terms of the GNU Library General Public |
12 | * License as published by the Free Software Foundation; either |
13 | * version 2 of the License, or (at your option) any later version. |
14 | * |
15 | * This library is distributed in the hope that it will be useful, |
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
18 | * Library General Public License for more details. |
19 | * |
20 | * You should have received a copy of the GNU Library General Public License |
21 | * along with this library; see the file COPYING.LIB. If not, write to |
22 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
23 | * Boston, MA 02110-1301, USA. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "Element.h" |
28 | |
29 | #include "AXObjectCache.h" |
30 | #include "Attr.h" |
31 | #include "AttributeChangeInvalidation.h" |
32 | #include "CSSAnimationController.h" |
33 | #include "CSSParser.h" |
34 | #include "Chrome.h" |
35 | #include "ChromeClient.h" |
36 | #include "ClassChangeInvalidation.h" |
37 | #include "ComposedTreeAncestorIterator.h" |
38 | #include "ContainerNodeAlgorithms.h" |
39 | #include "CustomElementReactionQueue.h" |
40 | #include "CustomElementRegistry.h" |
41 | #include "DOMRect.h" |
42 | #include "DOMRectList.h" |
43 | #include "DOMTokenList.h" |
44 | #include "DOMWindow.h" |
45 | #include "DocumentSharedObjectPool.h" |
46 | #include "DocumentTimeline.h" |
47 | #include "Editing.h" |
48 | #include "ElementIterator.h" |
49 | #include "ElementRareData.h" |
50 | #include "EventDispatcher.h" |
51 | #include "EventHandler.h" |
52 | #include "EventNames.h" |
53 | #include "FocusController.h" |
54 | #include "FocusEvent.h" |
55 | #include "Frame.h" |
56 | #include "FrameSelection.h" |
57 | #include "FrameView.h" |
58 | #include "FullscreenManager.h" |
59 | #include "HTMLBodyElement.h" |
60 | #include "HTMLCanvasElement.h" |
61 | #include "HTMLCollection.h" |
62 | #include "HTMLDocument.h" |
63 | #include "HTMLHtmlElement.h" |
64 | #include "HTMLLabelElement.h" |
65 | #include "HTMLNameCollection.h" |
66 | #include "HTMLObjectElement.h" |
67 | #include "HTMLOptGroupElement.h" |
68 | #include "HTMLOptionElement.h" |
69 | #include "HTMLParserIdioms.h" |
70 | #include "HTMLSelectElement.h" |
71 | #include "HTMLTemplateElement.h" |
72 | #include "IdChangeInvalidation.h" |
73 | #include "IdTargetObserverRegistry.h" |
74 | #include "InspectorInstrumentation.h" |
75 | #include "JSLazyEventListener.h" |
76 | #include "KeyboardEvent.h" |
77 | #include "KeyframeEffect.h" |
78 | #include "MutationObserverInterestGroup.h" |
79 | #include "MutationRecord.h" |
80 | #include "NodeRenderStyle.h" |
81 | #include "PlatformWheelEvent.h" |
82 | #include "PointerCaptureController.h" |
83 | #include "PointerEvent.h" |
84 | #include "PointerLockController.h" |
85 | #include "RenderFragmentContainer.h" |
86 | #include "RenderLayer.h" |
87 | #include "RenderLayerBacking.h" |
88 | #include "RenderLayerCompositor.h" |
89 | #include "RenderListBox.h" |
90 | #include "RenderTheme.h" |
91 | #include "RenderTreeUpdater.h" |
92 | #include "RenderView.h" |
93 | #include "RenderWidget.h" |
94 | #include "RuntimeEnabledFeatures.h" |
95 | #include "SVGDocumentExtensions.h" |
96 | #include "SVGElement.h" |
97 | #include "SVGNames.h" |
98 | #include "SVGSVGElement.h" |
99 | #include "ScriptDisallowedScope.h" |
100 | #include "ScrollIntoViewOptions.h" |
101 | #include "ScrollLatchingState.h" |
102 | #include "SelectorQuery.h" |
103 | #include "Settings.h" |
104 | #include "SimulatedClick.h" |
105 | #include "SlotAssignment.h" |
106 | #include "StyleProperties.h" |
107 | #include "StyleResolver.h" |
108 | #include "StyleScope.h" |
109 | #include "StyleTreeResolver.h" |
110 | #include "TextIterator.h" |
111 | #include "TouchAction.h" |
112 | #include "VoidCallback.h" |
113 | #include "WebAnimation.h" |
114 | #include "WheelEvent.h" |
115 | #include "XLinkNames.h" |
116 | #include "XMLNSNames.h" |
117 | #include "XMLNames.h" |
118 | #include "markup.h" |
119 | #include <wtf/IsoMallocInlines.h> |
120 | #include <wtf/NeverDestroyed.h> |
121 | #include <wtf/text/CString.h> |
122 | |
123 | namespace WebCore { |
124 | |
125 | WTF_MAKE_ISO_ALLOCATED_IMPL(Element); |
126 | |
127 | using namespace HTMLNames; |
128 | using namespace XMLNames; |
129 | |
130 | static HashMap<Element*, Vector<RefPtr<Attr>>>& attrNodeListMap() |
131 | { |
132 | static NeverDestroyed<HashMap<Element*, Vector<RefPtr<Attr>>>> map; |
133 | return map; |
134 | } |
135 | |
136 | static Vector<RefPtr<Attr>>* attrNodeListForElement(Element& element) |
137 | { |
138 | if (!element.hasSyntheticAttrChildNodes()) |
139 | return nullptr; |
140 | ASSERT(attrNodeListMap().contains(&element)); |
141 | return &attrNodeListMap().find(&element)->value; |
142 | } |
143 | |
144 | static Vector<RefPtr<Attr>>& ensureAttrNodeListForElement(Element& element) |
145 | { |
146 | if (element.hasSyntheticAttrChildNodes()) { |
147 | ASSERT(attrNodeListMap().contains(&element)); |
148 | return attrNodeListMap().find(&element)->value; |
149 | } |
150 | ASSERT(!attrNodeListMap().contains(&element)); |
151 | element.setHasSyntheticAttrChildNodes(true); |
152 | return attrNodeListMap().add(&element, Vector<RefPtr<Attr>>()).iterator->value; |
153 | } |
154 | |
155 | static void removeAttrNodeListForElement(Element& element) |
156 | { |
157 | ASSERT(element.hasSyntheticAttrChildNodes()); |
158 | ASSERT(attrNodeListMap().contains(&element)); |
159 | attrNodeListMap().remove(&element); |
160 | element.setHasSyntheticAttrChildNodes(false); |
161 | } |
162 | |
163 | static Attr* findAttrNodeInList(Vector<RefPtr<Attr>>& attrNodeList, const QualifiedName& name) |
164 | { |
165 | for (auto& node : attrNodeList) { |
166 | if (node->qualifiedName().matches(name)) |
167 | return node.get(); |
168 | } |
169 | return nullptr; |
170 | } |
171 | |
172 | static Attr* findAttrNodeInList(Vector<RefPtr<Attr>>& attrNodeList, const AtomString& localName, bool shouldIgnoreAttributeCase) |
173 | { |
174 | const AtomString& caseAdjustedName = shouldIgnoreAttributeCase ? localName.convertToASCIILowercase() : localName; |
175 | for (auto& node : attrNodeList) { |
176 | if (node->qualifiedName().localName() == caseAdjustedName) |
177 | return node.get(); |
178 | } |
179 | return nullptr; |
180 | } |
181 | |
182 | Ref<Element> Element::create(const QualifiedName& tagName, Document& document) |
183 | { |
184 | return adoptRef(*new Element(tagName, document, CreateElement)); |
185 | } |
186 | |
187 | Element::Element(const QualifiedName& tagName, Document& document, ConstructionType type) |
188 | : ContainerNode(document, type) |
189 | , m_tagName(tagName) |
190 | { |
191 | } |
192 | |
193 | Element::~Element() |
194 | { |
195 | ASSERT(!beforePseudoElement()); |
196 | ASSERT(!afterPseudoElement()); |
197 | |
198 | #if ENABLE(INTERSECTION_OBSERVER) |
199 | disconnectFromIntersectionObservers(); |
200 | #endif |
201 | |
202 | #if ENABLE(RESIZE_OBSERVER) |
203 | disconnectFromResizeObservers(); |
204 | #endif |
205 | |
206 | removeShadowRoot(); |
207 | |
208 | if (hasSyntheticAttrChildNodes()) |
209 | detachAllAttrNodesFromElement(); |
210 | |
211 | #if ENABLE(CSS_TYPED_OM) |
212 | if (hasRareData()) { |
213 | if (auto* map = elementRareData()->attributeStyleMap()) |
214 | map->clearElement(); |
215 | } |
216 | #endif |
217 | |
218 | if (hasPendingResources()) { |
219 | document().accessSVGExtensions().removeElementFromPendingResources(*this); |
220 | ASSERT(!hasPendingResources()); |
221 | } |
222 | } |
223 | |
224 | inline ElementRareData* Element::elementRareData() const |
225 | { |
226 | ASSERT_WITH_SECURITY_IMPLICATION(hasRareData()); |
227 | return static_cast<ElementRareData*>(rareData()); |
228 | } |
229 | |
230 | inline ElementRareData& Element::ensureElementRareData() |
231 | { |
232 | return static_cast<ElementRareData&>(ensureRareData()); |
233 | } |
234 | |
235 | void Element::clearTabIndexExplicitlyIfNeeded() |
236 | { |
237 | if (hasRareData()) |
238 | elementRareData()->clearTabIndexExplicitly(); |
239 | } |
240 | |
241 | void Element::setTabIndexExplicitly(int tabIndex) |
242 | { |
243 | ensureElementRareData().setTabIndexExplicitly(tabIndex); |
244 | } |
245 | |
246 | bool Element::tabIndexSetExplicitly() const |
247 | { |
248 | return hasRareData() && elementRareData()->tabIndexSetExplicitly(); |
249 | } |
250 | |
251 | bool Element::supportsFocus() const |
252 | { |
253 | return tabIndexSetExplicitly(); |
254 | } |
255 | |
256 | RefPtr<Element> Element::focusDelegate() |
257 | { |
258 | return this; |
259 | } |
260 | |
261 | int Element::tabIndex() const |
262 | { |
263 | return hasRareData() ? elementRareData()->tabIndex() : 0; |
264 | } |
265 | |
266 | void Element::setTabIndex(int value) |
267 | { |
268 | setIntegralAttribute(tabindexAttr, value); |
269 | } |
270 | |
271 | bool Element::isKeyboardFocusable(KeyboardEvent*) const |
272 | { |
273 | return isFocusable() && tabIndex() >= 0; |
274 | } |
275 | |
276 | bool Element::isMouseFocusable() const |
277 | { |
278 | return isFocusable(); |
279 | } |
280 | |
281 | bool Element::shouldUseInputMethod() |
282 | { |
283 | return computeEditability(UserSelectAllIsAlwaysNonEditable, ShouldUpdateStyle::Update) != Editability::ReadOnly; |
284 | } |
285 | |
286 | static bool isForceEvent(const PlatformMouseEvent& platformEvent) |
287 | { |
288 | return platformEvent.type() == PlatformEvent::MouseForceChanged || platformEvent.type() == PlatformEvent::MouseForceDown || platformEvent.type() == PlatformEvent::MouseForceUp; |
289 | } |
290 | |
291 | #if ENABLE(POINTER_EVENTS) && !ENABLE(TOUCH_EVENTS) |
292 | static bool isCompatibilityMouseEvent(const MouseEvent& mouseEvent) |
293 | { |
294 | // https://www.w3.org/TR/pointerevents/#compatibility-mapping-with-mouse-events |
295 | const auto& type = mouseEvent.type(); |
296 | return type != eventNames().clickEvent && type != eventNames().mouseoverEvent && type != eventNames().mouseoutEvent && type != eventNames().mouseenterEvent && type != eventNames().mouseleaveEvent; |
297 | } |
298 | #endif |
299 | |
300 | bool Element::dispatchMouseEvent(const PlatformMouseEvent& platformEvent, const AtomString& eventType, int detail, Element* relatedTarget) |
301 | { |
302 | if (isDisabledFormControl()) |
303 | return false; |
304 | |
305 | if (isForceEvent(platformEvent) && !document().hasListenerTypeForEventType(platformEvent.type())) |
306 | return false; |
307 | |
308 | Ref<MouseEvent> mouseEvent = MouseEvent::create(eventType, document().windowProxy(), platformEvent, detail, relatedTarget); |
309 | |
310 | if (mouseEvent->type().isEmpty()) |
311 | return true; // Shouldn't happen. |
312 | |
313 | bool didNotSwallowEvent = true; |
314 | |
315 | #if ENABLE(POINTER_EVENTS) |
316 | if (RuntimeEnabledFeatures::sharedFeatures().pointerEventsEnabled()) { |
317 | if (auto* page = document().page()) { |
318 | auto& pointerCaptureController = page->pointerCaptureController(); |
319 | #if ENABLE(TOUCH_EVENTS) |
320 | if (mouseEvent->type() != eventNames().clickEvent && pointerCaptureController.preventsCompatibilityMouseEventsForIdentifier(platformEvent.pointerId())) |
321 | return false; |
322 | #else |
323 | if (auto pointerEvent = pointerCaptureController.pointerEventForMouseEvent(mouseEvent)) { |
324 | pointerCaptureController.dispatchEvent(*pointerEvent, this); |
325 | if (isCompatibilityMouseEvent(mouseEvent) && pointerCaptureController.preventsCompatibilityMouseEventsForIdentifier(pointerEvent->pointerId())) |
326 | return false; |
327 | if (pointerEvent->defaultPrevented() || pointerEvent->defaultHandled()) { |
328 | didNotSwallowEvent = false; |
329 | if (pointerEvent->type() == eventNames().pointerdownEvent) |
330 | return false; |
331 | } |
332 | } |
333 | #endif |
334 | } |
335 | } |
336 | #endif |
337 | |
338 | ASSERT(!mouseEvent->target() || mouseEvent->target() != relatedTarget); |
339 | dispatchEvent(mouseEvent); |
340 | if (mouseEvent->defaultPrevented() || mouseEvent->defaultHandled()) |
341 | didNotSwallowEvent = false; |
342 | |
343 | if (mouseEvent->type() == eventNames().clickEvent && mouseEvent->detail() == 2) { |
344 | // Special case: If it's a double click event, we also send the dblclick event. This is not part |
345 | // of the DOM specs, but is used for compatibility with the ondblclick="" attribute. This is treated |
346 | // as a separate event in other DOM-compliant browsers like Firefox, and so we do the same. |
347 | // FIXME: Is it okay that mouseEvent may have been mutated by scripts via initMouseEvent in dispatchEvent above? |
348 | Ref<MouseEvent> doubleClickEvent = MouseEvent::create(eventNames().dblclickEvent, |
349 | mouseEvent->bubbles() ? Event::CanBubble::Yes : Event::CanBubble::No, |
350 | mouseEvent->cancelable() ? Event::IsCancelable::Yes : Event::IsCancelable::No, |
351 | Event::IsComposed::Yes, |
352 | mouseEvent->view(), mouseEvent->detail(), |
353 | mouseEvent->screenX(), mouseEvent->screenY(), mouseEvent->clientX(), mouseEvent->clientY(), |
354 | mouseEvent->modifierKeys(), mouseEvent->button(), mouseEvent->buttons(), mouseEvent->syntheticClickType(), relatedTarget); |
355 | |
356 | if (mouseEvent->defaultHandled()) |
357 | doubleClickEvent->setDefaultHandled(); |
358 | |
359 | dispatchEvent(doubleClickEvent); |
360 | if (doubleClickEvent->defaultHandled() || doubleClickEvent->defaultPrevented()) |
361 | return false; |
362 | } |
363 | return didNotSwallowEvent; |
364 | } |
365 | |
366 | bool Element::dispatchWheelEvent(const PlatformWheelEvent& platformEvent) |
367 | { |
368 | auto event = WheelEvent::create(platformEvent, document().windowProxy()); |
369 | |
370 | // Events with no deltas are important because they convey platform information about scroll gestures |
371 | // and momentum beginning or ending. However, those events should not be sent to the DOM since some |
372 | // websites will break. They need to be dispatched because dispatching them will call into the default |
373 | // event handler, and our platform code will correctly handle the phase changes. Calling stopPropogation() |
374 | // will prevent the event from being sent to the DOM, but will still call the default event handler. |
375 | // FIXME: Move this logic into WheelEvent::create. |
376 | if (!platformEvent.deltaX() && !platformEvent.deltaY()) |
377 | event->stopPropagation(); |
378 | |
379 | dispatchEvent(event); |
380 | return !event->defaultPrevented() && !event->defaultHandled(); |
381 | } |
382 | |
383 | bool Element::dispatchKeyEvent(const PlatformKeyboardEvent& platformEvent) |
384 | { |
385 | auto event = KeyboardEvent::create(platformEvent, document().windowProxy()); |
386 | |
387 | if (Frame* frame = document().frame()) { |
388 | if (frame->eventHandler().accessibilityPreventsEventPropagation(event)) |
389 | event->stopPropagation(); |
390 | } |
391 | |
392 | dispatchEvent(event); |
393 | return !event->defaultPrevented() && !event->defaultHandled(); |
394 | } |
395 | |
396 | void Element::dispatchSimulatedClick(Event* underlyingEvent, SimulatedClickMouseEventOptions eventOptions, SimulatedClickVisualOptions visualOptions) |
397 | { |
398 | simulateClick(*this, underlyingEvent, eventOptions, visualOptions, SimulatedClickSource::UserAgent); |
399 | } |
400 | |
401 | Ref<Node> Element::cloneNodeInternal(Document& targetDocument, CloningOperation type) |
402 | { |
403 | switch (type) { |
404 | case CloningOperation::OnlySelf: |
405 | case CloningOperation::SelfWithTemplateContent: |
406 | return cloneElementWithoutChildren(targetDocument); |
407 | case CloningOperation::Everything: |
408 | break; |
409 | } |
410 | return cloneElementWithChildren(targetDocument); |
411 | } |
412 | |
413 | Ref<Element> Element::cloneElementWithChildren(Document& targetDocument) |
414 | { |
415 | Ref<Element> clone = cloneElementWithoutChildren(targetDocument); |
416 | cloneChildNodes(clone); |
417 | return clone; |
418 | } |
419 | |
420 | Ref<Element> Element::cloneElementWithoutChildren(Document& targetDocument) |
421 | { |
422 | Ref<Element> clone = cloneElementWithoutAttributesAndChildren(targetDocument); |
423 | |
424 | // This will catch HTML elements in the wrong namespace that are not correctly copied. |
425 | // This is a sanity check as HTML overloads some of the DOM methods. |
426 | ASSERT(isHTMLElement() == clone->isHTMLElement()); |
427 | |
428 | clone->cloneDataFromElement(*this); |
429 | return clone; |
430 | } |
431 | |
432 | Ref<Element> Element::cloneElementWithoutAttributesAndChildren(Document& targetDocument) |
433 | { |
434 | return targetDocument.createElement(tagQName(), false); |
435 | } |
436 | |
437 | Ref<Attr> Element::detachAttribute(unsigned index) |
438 | { |
439 | ASSERT(elementData()); |
440 | |
441 | const Attribute& attribute = elementData()->attributeAt(index); |
442 | |
443 | RefPtr<Attr> attrNode = attrIfExists(attribute.name()); |
444 | if (attrNode) |
445 | detachAttrNodeFromElementWithValue(attrNode.get(), attribute.value()); |
446 | else |
447 | attrNode = Attr::create(document(), attribute.name(), attribute.value()); |
448 | |
449 | removeAttributeInternal(index, NotInSynchronizationOfLazyAttribute); |
450 | return attrNode.releaseNonNull(); |
451 | } |
452 | |
453 | bool Element::removeAttribute(const QualifiedName& name) |
454 | { |
455 | if (!elementData()) |
456 | return false; |
457 | |
458 | unsigned index = elementData()->findAttributeIndexByName(name); |
459 | if (index == ElementData::attributeNotFound) |
460 | return false; |
461 | |
462 | removeAttributeInternal(index, NotInSynchronizationOfLazyAttribute); |
463 | return true; |
464 | } |
465 | |
466 | void Element::setBooleanAttribute(const QualifiedName& name, bool value) |
467 | { |
468 | if (value) |
469 | setAttribute(name, emptyAtom()); |
470 | else |
471 | removeAttribute(name); |
472 | } |
473 | |
474 | NamedNodeMap& Element::attributes() const |
475 | { |
476 | ElementRareData& rareData = const_cast<Element*>(this)->ensureElementRareData(); |
477 | if (NamedNodeMap* attributeMap = rareData.attributeMap()) |
478 | return *attributeMap; |
479 | |
480 | rareData.setAttributeMap(std::make_unique<NamedNodeMap>(const_cast<Element&>(*this))); |
481 | return *rareData.attributeMap(); |
482 | } |
483 | |
484 | Node::NodeType Element::nodeType() const |
485 | { |
486 | return ELEMENT_NODE; |
487 | } |
488 | |
489 | bool Element::hasAttribute(const QualifiedName& name) const |
490 | { |
491 | return hasAttributeNS(name.namespaceURI(), name.localName()); |
492 | } |
493 | |
494 | void Element::synchronizeAllAttributes() const |
495 | { |
496 | if (!elementData()) |
497 | return; |
498 | if (elementData()->styleAttributeIsDirty()) { |
499 | ASSERT(isStyledElement()); |
500 | static_cast<const StyledElement*>(this)->synchronizeStyleAttributeInternal(); |
501 | } |
502 | |
503 | if (isSVGElement()) |
504 | downcast<SVGElement>(const_cast<Element&>(*this)).synchronizeAllAttributes(); |
505 | } |
506 | |
507 | ALWAYS_INLINE void Element::synchronizeAttribute(const QualifiedName& name) const |
508 | { |
509 | if (!elementData()) |
510 | return; |
511 | if (UNLIKELY(name == styleAttr && elementData()->styleAttributeIsDirty())) { |
512 | ASSERT_WITH_SECURITY_IMPLICATION(isStyledElement()); |
513 | static_cast<const StyledElement*>(this)->synchronizeStyleAttributeInternal(); |
514 | return; |
515 | } |
516 | |
517 | if (isSVGElement()) |
518 | downcast<SVGElement>(const_cast<Element&>(*this)).synchronizeAttribute(name); |
519 | } |
520 | |
521 | static ALWAYS_INLINE bool isStyleAttribute(const Element& element, const AtomString& attributeLocalName) |
522 | { |
523 | if (shouldIgnoreAttributeCase(element)) |
524 | return equalLettersIgnoringASCIICase(attributeLocalName, "style" ); |
525 | return attributeLocalName == styleAttr->localName(); |
526 | } |
527 | |
528 | ALWAYS_INLINE void Element::synchronizeAttribute(const AtomString& localName) const |
529 | { |
530 | // This version of synchronizeAttribute() is streamlined for the case where you don't have a full QualifiedName, |
531 | // e.g when called from DOM API. |
532 | if (!elementData()) |
533 | return; |
534 | if (elementData()->styleAttributeIsDirty() && isStyleAttribute(*this, localName)) { |
535 | ASSERT_WITH_SECURITY_IMPLICATION(isStyledElement()); |
536 | static_cast<const StyledElement*>(this)->synchronizeStyleAttributeInternal(); |
537 | return; |
538 | } |
539 | |
540 | if (isSVGElement()) |
541 | downcast<SVGElement>(const_cast<Element&>(*this)).synchronizeAttribute(QualifiedName(nullAtom(), localName, nullAtom())); |
542 | } |
543 | |
544 | const AtomString& Element::getAttribute(const QualifiedName& name) const |
545 | { |
546 | if (!elementData()) |
547 | return nullAtom(); |
548 | synchronizeAttribute(name); |
549 | if (const Attribute* attribute = findAttributeByName(name)) |
550 | return attribute->value(); |
551 | return nullAtom(); |
552 | } |
553 | |
554 | Vector<String> Element::getAttributeNames() const |
555 | { |
556 | Vector<String> attributesVector; |
557 | if (!hasAttributes()) |
558 | return attributesVector; |
559 | |
560 | auto attributes = attributesIterator(); |
561 | attributesVector.reserveInitialCapacity(attributes.attributeCount()); |
562 | for (auto& attribute : attributes) |
563 | attributesVector.uncheckedAppend(attribute.name().toString()); |
564 | return attributesVector; |
565 | } |
566 | |
567 | bool Element::isFocusable() const |
568 | { |
569 | if (!isConnected() || !supportsFocus()) |
570 | return false; |
571 | |
572 | if (!renderer()) { |
573 | // If the node is in a display:none tree it might say it needs style recalc but |
574 | // the whole document is actually up to date. |
575 | // FIXME: We should be able to assert !needsStyleRecalc() || !document().childNeedsStyleRecalc() |
576 | // but it hits too frequently on websites like Gmail and Microsoft Exchange. |
577 | |
578 | // Elements in canvas fallback content are not rendered, but they are allowed to be |
579 | // focusable as long as their canvas is displayed and visible. |
580 | if (auto* canvas = ancestorsOfType<HTMLCanvasElement>(*this).first()) |
581 | return canvas->renderer() && canvas->renderer()->style().visibility() == Visibility::Visible; |
582 | } |
583 | |
584 | // FIXME: Even if we are not visible, we might have a child that is visible. |
585 | // Hyatt wants to fix that some day with a "has visible content" flag or the like. |
586 | if (!renderer() || renderer()->style().visibility() != Visibility::Visible) |
587 | return false; |
588 | |
589 | return true; |
590 | } |
591 | |
592 | bool Element::isUserActionElementInActiveChain() const |
593 | { |
594 | ASSERT(isUserActionElement()); |
595 | return document().userActionElements().isInActiveChain(*this); |
596 | } |
597 | |
598 | bool Element::isUserActionElementActive() const |
599 | { |
600 | ASSERT(isUserActionElement()); |
601 | return document().userActionElements().isActive(*this); |
602 | } |
603 | |
604 | bool Element::isUserActionElementFocused() const |
605 | { |
606 | ASSERT(isUserActionElement()); |
607 | return document().userActionElements().isFocused(*this); |
608 | } |
609 | |
610 | bool Element::isUserActionElementHovered() const |
611 | { |
612 | ASSERT(isUserActionElement()); |
613 | return document().userActionElements().isHovered(*this); |
614 | } |
615 | |
616 | void Element::setActive(bool flag, bool pause) |
617 | { |
618 | if (flag == active()) |
619 | return; |
620 | |
621 | document().userActionElements().setActive(*this, flag); |
622 | |
623 | auto* renderStyle = renderOrDisplayContentsStyle(); |
624 | bool reactsToPress = (renderStyle && renderStyle->affectedByActive()) || styleAffectedByActive(); |
625 | if (reactsToPress) |
626 | invalidateStyleForSubtree(); |
627 | |
628 | if (!renderer()) |
629 | return; |
630 | |
631 | if (renderer()->style().hasAppearance() && renderer()->theme().stateChanged(*renderer(), ControlStates::PressedState)) |
632 | reactsToPress = true; |
633 | |
634 | // The rest of this function implements a feature that only works if the |
635 | // platform supports immediate invalidations on the ChromeClient, so bail if |
636 | // that isn't supported. |
637 | if (!document().page()->chrome().client().supportsImmediateInvalidation()) |
638 | return; |
639 | |
640 | if (reactsToPress && pause) { |
641 | // The delay here is subtle. It relies on an assumption, namely that the amount of time it takes |
642 | // to repaint the "down" state of the control is about the same time as it would take to repaint the |
643 | // "up" state. Once you assume this, you can just delay for 100ms - that time (assuming that after you |
644 | // leave this method, it will be about that long before the flush of the up state happens again). |
645 | #ifdef HAVE_FUNC_USLEEP |
646 | MonotonicTime startTime = MonotonicTime::now(); |
647 | #endif |
648 | |
649 | document().updateStyleIfNeeded(); |
650 | |
651 | // Do an immediate repaint. |
652 | if (renderer()) |
653 | renderer()->repaint(); |
654 | |
655 | // FIXME: Come up with a less ridiculous way of doing this. |
656 | #ifdef HAVE_FUNC_USLEEP |
657 | // Now pause for a small amount of time (1/10th of a second from before we repainted in the pressed state) |
658 | Seconds remainingTime = 100_ms - (MonotonicTime::now() - startTime); |
659 | if (remainingTime > 0_s) |
660 | usleep(static_cast<useconds_t>(remainingTime.microseconds())); |
661 | #endif |
662 | } |
663 | } |
664 | |
665 | void Element::setFocus(bool flag) |
666 | { |
667 | if (flag == focused()) |
668 | return; |
669 | |
670 | document().userActionElements().setFocused(*this, flag); |
671 | invalidateStyleForSubtree(); |
672 | |
673 | for (Element* element = this; element; element = element->parentElementInComposedTree()) |
674 | element->setHasFocusWithin(flag); |
675 | } |
676 | |
677 | void Element::setHovered(bool flag) |
678 | { |
679 | if (flag == hovered()) |
680 | return; |
681 | |
682 | document().userActionElements().setHovered(*this, flag); |
683 | |
684 | auto* style = renderOrDisplayContentsStyle(); |
685 | if (style && (style->affectedByHover() || childrenAffectedByHover())) |
686 | invalidateStyleForSubtree(); |
687 | |
688 | if (!renderer()) { |
689 | // When setting hover to false, the style needs to be recalc'd even when |
690 | // there's no renderer (imagine setting display:none in the :hover class, |
691 | // if a nil renderer would prevent this element from recalculating its |
692 | // style, it would never go back to its normal style and remain |
693 | // stuck in its hovered style). |
694 | if (!flag && !style) |
695 | invalidateStyleForSubtree(); |
696 | |
697 | return; |
698 | } |
699 | |
700 | if (style->hasAppearance()) |
701 | renderer()->theme().stateChanged(*renderer(), ControlStates::HoverState); |
702 | } |
703 | |
704 | // FIXME(webkit.org/b/161611): Take into account orientation/direction. |
705 | inline ScrollAlignment toScrollAlignment(Optional<ScrollLogicalPosition> position, bool isVertical) |
706 | { |
707 | switch (position.valueOr(isVertical ? ScrollLogicalPosition::Start : ScrollLogicalPosition::Nearest)) { |
708 | case ScrollLogicalPosition::Start: |
709 | return isVertical ? ScrollAlignment::alignTopAlways : ScrollAlignment::alignLeftAlways; |
710 | case ScrollLogicalPosition::Center: |
711 | return ScrollAlignment::alignCenterAlways; |
712 | case ScrollLogicalPosition::End: |
713 | return isVertical ? ScrollAlignment::alignBottomAlways : ScrollAlignment::alignRightAlways; |
714 | case ScrollLogicalPosition::Nearest: |
715 | return ScrollAlignment::alignToEdgeIfNeeded; |
716 | default: |
717 | ASSERT_NOT_REACHED(); |
718 | return ScrollAlignment::alignToEdgeIfNeeded; |
719 | } |
720 | } |
721 | |
722 | void Element::scrollIntoView(Optional<Variant<bool, ScrollIntoViewOptions>>&& arg) |
723 | { |
724 | document().updateLayoutIgnorePendingStylesheets(); |
725 | |
726 | if (!renderer()) |
727 | return; |
728 | |
729 | bool insideFixed; |
730 | LayoutRect absoluteBounds = renderer()->absoluteAnchorRect(&insideFixed); |
731 | |
732 | // FIXME(webkit.org/b/188043): Support ScrollBehavior. |
733 | ScrollIntoViewOptions options; |
734 | if (arg) { |
735 | auto value = arg.value(); |
736 | if (WTF::holds_alternative<ScrollIntoViewOptions>(value)) |
737 | options = WTF::get<ScrollIntoViewOptions>(value); |
738 | else if (!WTF::get<bool>(value)) |
739 | options.blockPosition = ScrollLogicalPosition::End; |
740 | } |
741 | |
742 | ScrollAlignment alignX = toScrollAlignment(options.inlinePosition, false); |
743 | ScrollAlignment alignY = toScrollAlignment(options.blockPosition, true); |
744 | renderer()->scrollRectToVisible(absoluteBounds, insideFixed, { SelectionRevealMode::Reveal, alignX, alignY, ShouldAllowCrossOriginScrolling::No }); |
745 | } |
746 | |
747 | void Element::scrollIntoView(bool alignToTop) |
748 | { |
749 | document().updateLayoutIgnorePendingStylesheets(); |
750 | |
751 | if (!renderer()) |
752 | return; |
753 | |
754 | bool insideFixed; |
755 | LayoutRect absoluteBounds = renderer()->absoluteAnchorRect(&insideFixed); |
756 | // Align to the top / bottom and to the closest edge. |
757 | if (alignToTop) |
758 | renderer()->scrollRectToVisible(absoluteBounds, insideFixed, { SelectionRevealMode::Reveal, ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignTopAlways, ShouldAllowCrossOriginScrolling::No }); |
759 | else |
760 | renderer()->scrollRectToVisible(absoluteBounds, insideFixed, { SelectionRevealMode::Reveal, ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignBottomAlways, ShouldAllowCrossOriginScrolling::No }); |
761 | } |
762 | |
763 | void Element::scrollIntoViewIfNeeded(bool centerIfNeeded) |
764 | { |
765 | document().updateLayoutIgnorePendingStylesheets(); |
766 | |
767 | if (!renderer()) |
768 | return; |
769 | |
770 | bool insideFixed; |
771 | LayoutRect absoluteBounds = renderer()->absoluteAnchorRect(&insideFixed); |
772 | if (centerIfNeeded) |
773 | renderer()->scrollRectToVisible(absoluteBounds, insideFixed, { SelectionRevealMode::Reveal, ScrollAlignment::alignCenterIfNeeded, ScrollAlignment::alignCenterIfNeeded, ShouldAllowCrossOriginScrolling::No }); |
774 | else |
775 | renderer()->scrollRectToVisible(absoluteBounds, insideFixed, { SelectionRevealMode::Reveal, ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignToEdgeIfNeeded, ShouldAllowCrossOriginScrolling::No }); |
776 | } |
777 | |
778 | void Element::scrollIntoViewIfNotVisible(bool centerIfNotVisible) |
779 | { |
780 | document().updateLayoutIgnorePendingStylesheets(); |
781 | |
782 | if (!renderer()) |
783 | return; |
784 | |
785 | bool insideFixed; |
786 | LayoutRect absoluteBounds = renderer()->absoluteAnchorRect(&insideFixed); |
787 | if (centerIfNotVisible) |
788 | renderer()->scrollRectToVisible(absoluteBounds, insideFixed, { SelectionRevealMode::Reveal, ScrollAlignment::alignCenterIfNotVisible, ScrollAlignment::alignCenterIfNotVisible, ShouldAllowCrossOriginScrolling::No }); |
789 | else |
790 | renderer()->scrollRectToVisible(absoluteBounds, insideFixed, { SelectionRevealMode::Reveal, ScrollAlignment::alignToEdgeIfNotVisible, ScrollAlignment::alignToEdgeIfNotVisible, ShouldAllowCrossOriginScrolling::No }); |
791 | } |
792 | |
793 | void Element::scrollBy(const ScrollToOptions& options) |
794 | { |
795 | ScrollToOptions scrollToOptions = normalizeNonFiniteCoordinatesOrFallBackTo(options, 0, 0); |
796 | scrollToOptions.left.value() += scrollLeft(); |
797 | scrollToOptions.top.value() += scrollTop(); |
798 | scrollTo(scrollToOptions); |
799 | } |
800 | |
801 | void Element::scrollBy(double x, double y) |
802 | { |
803 | scrollBy({ x, y }); |
804 | } |
805 | |
806 | void Element::scrollTo(const ScrollToOptions& options, ScrollClamping clamping) |
807 | { |
808 | if (!document().settings().CSSOMViewScrollingAPIEnabled()) { |
809 | // If the element is the root element and document is in quirks mode, terminate these steps. |
810 | // Note that WebKit always uses quirks mode document scrolling behavior. See Document::scrollingElement(). |
811 | if (this == document().documentElement()) |
812 | return; |
813 | } |
814 | |
815 | document().updateLayoutIgnorePendingStylesheets(); |
816 | |
817 | if (document().scrollingElement() == this) { |
818 | // If the element is the scrolling element and is not potentially scrollable, |
819 | // invoke scroll() on window with options as the only argument, and terminate these steps. |
820 | // FIXME: Scrolling an independently scrollable body is broken: webkit.org/b/161612. |
821 | auto window = makeRefPtr(document().domWindow()); |
822 | if (!window) |
823 | return; |
824 | |
825 | window->scrollTo(options); |
826 | return; |
827 | } |
828 | |
829 | // If the element does not have any associated CSS layout box, the element has no associated scrolling box, |
830 | // or the element has no overflow, terminate these steps. |
831 | RenderBox* renderer = renderBox(); |
832 | if (!renderer || !renderer->hasOverflowClip()) |
833 | return; |
834 | |
835 | ScrollToOptions scrollToOptions = normalizeNonFiniteCoordinatesOrFallBackTo(options, |
836 | adjustForAbsoluteZoom(renderer->scrollLeft(), *renderer), |
837 | adjustForAbsoluteZoom(renderer->scrollTop(), *renderer) |
838 | ); |
839 | renderer->setScrollLeft(clampToInteger(scrollToOptions.left.value() * renderer->style().effectiveZoom()), ScrollType::Programmatic, clamping); |
840 | renderer->setScrollTop(clampToInteger(scrollToOptions.top.value() * renderer->style().effectiveZoom()), ScrollType::Programmatic, clamping); |
841 | } |
842 | |
843 | void Element::scrollTo(double x, double y) |
844 | { |
845 | scrollTo({ x, y }); |
846 | } |
847 | |
848 | void Element::scrollByUnits(int units, ScrollGranularity granularity) |
849 | { |
850 | document().updateLayoutIgnorePendingStylesheets(); |
851 | |
852 | auto* renderer = this->renderer(); |
853 | if (!renderer) |
854 | return; |
855 | |
856 | if (!renderer->hasOverflowClip()) |
857 | return; |
858 | |
859 | ScrollDirection direction = ScrollDown; |
860 | if (units < 0) { |
861 | direction = ScrollUp; |
862 | units = -units; |
863 | } |
864 | Element* stopElement = this; |
865 | downcast<RenderBox>(*renderer).scroll(direction, granularity, units, &stopElement); |
866 | } |
867 | |
868 | void Element::scrollByLines(int lines) |
869 | { |
870 | scrollByUnits(lines, ScrollByLine); |
871 | } |
872 | |
873 | void Element::scrollByPages(int pages) |
874 | { |
875 | scrollByUnits(pages, ScrollByPage); |
876 | } |
877 | |
878 | static double localZoomForRenderer(const RenderElement& renderer) |
879 | { |
880 | // FIXME: This does the wrong thing if two opposing zooms are in effect and canceled each |
881 | // other out, but the alternative is that we'd have to crawl up the whole render tree every |
882 | // time (or store an additional bit in the RenderStyle to indicate that a zoom was specified). |
883 | double zoomFactor = 1; |
884 | if (renderer.style().effectiveZoom() != 1) { |
885 | // Need to find the nearest enclosing RenderElement that set up |
886 | // a differing zoom, and then we divide our result by it to eliminate the zoom. |
887 | const RenderElement* prev = &renderer; |
888 | for (RenderElement* curr = prev->parent(); curr; curr = curr->parent()) { |
889 | if (curr->style().effectiveZoom() != prev->style().effectiveZoom()) { |
890 | zoomFactor = prev->style().zoom(); |
891 | break; |
892 | } |
893 | prev = curr; |
894 | } |
895 | if (prev->isRenderView()) |
896 | zoomFactor = prev->style().zoom(); |
897 | } |
898 | return zoomFactor; |
899 | } |
900 | |
901 | static double adjustForLocalZoom(LayoutUnit value, const RenderElement& renderer, double& zoomFactor) |
902 | { |
903 | zoomFactor = localZoomForRenderer(renderer); |
904 | if (zoomFactor == 1) |
905 | return value.toDouble(); |
906 | return value.toDouble() / zoomFactor; |
907 | } |
908 | |
909 | static int adjustContentsScrollPositionOrSizeForZoom(int value, const Frame& frame) |
910 | { |
911 | double zoomFactor = frame.pageZoomFactor() * frame.frameScaleFactor(); |
912 | if (zoomFactor == 1) |
913 | return value; |
914 | // FIXME (webkit.org/b/189397): Why can't we just ceil/floor? |
915 | // Needed because of truncation (rather than rounding) when scaling up. |
916 | if (zoomFactor > 1) |
917 | value++; |
918 | return static_cast<int>(value / zoomFactor); |
919 | } |
920 | |
921 | enum LegacyCSSOMElementMetricsRoundingStrategy { Round, Floor }; |
922 | |
923 | static bool subpixelMetricsEnabled(const Document& document) |
924 | { |
925 | return document.settings().subpixelCSSOMElementMetricsEnabled(); |
926 | } |
927 | |
928 | static double convertToNonSubpixelValueIfNeeded(double value, const Document& document, LegacyCSSOMElementMetricsRoundingStrategy roundStrategy = Round) |
929 | { |
930 | return subpixelMetricsEnabled(document) ? value : roundStrategy == Round ? round(value) : floor(value); |
931 | } |
932 | |
933 | static double adjustOffsetForZoomAndSubpixelLayout(RenderBoxModelObject* renderer, const LayoutUnit& offset) |
934 | { |
935 | LayoutUnit offsetLeft = subpixelMetricsEnabled(renderer->document()) ? offset : LayoutUnit(roundToInt(offset)); |
936 | double zoomFactor = 1; |
937 | double offsetLeftAdjustedWithZoom = adjustForLocalZoom(offsetLeft, *renderer, zoomFactor); |
938 | return convertToNonSubpixelValueIfNeeded(offsetLeftAdjustedWithZoom, renderer->document(), zoomFactor == 1 ? Floor : Round); |
939 | } |
940 | |
941 | static HashSet<TreeScope*> collectAncestorTreeScopeAsHashSet(Node& node) |
942 | { |
943 | HashSet<TreeScope*> ancestors; |
944 | for (auto* currentScope = &node.treeScope(); currentScope; currentScope = currentScope->parentTreeScope()) |
945 | ancestors.add(currentScope); |
946 | return ancestors; |
947 | } |
948 | |
949 | double Element::offsetLeftForBindings() |
950 | { |
951 | auto offset = offsetLeft(); |
952 | |
953 | auto parent = makeRefPtr(offsetParent()); |
954 | if (!parent || !parent->isInShadowTree()) |
955 | return offset; |
956 | |
957 | ASSERT(&parent->document() == &document()); |
958 | if (&parent->treeScope() == &treeScope()) |
959 | return offset; |
960 | |
961 | auto ancestorTreeScopes = collectAncestorTreeScopeAsHashSet(*this); |
962 | while (parent && !ancestorTreeScopes.contains(&parent->treeScope())) { |
963 | offset += parent->offsetLeft(); |
964 | parent = parent->offsetParent(); |
965 | } |
966 | |
967 | return offset; |
968 | } |
969 | |
970 | double Element::offsetLeft() |
971 | { |
972 | document().updateLayoutIgnorePendingStylesheets(); |
973 | if (RenderBoxModelObject* renderer = renderBoxModelObject()) |
974 | return adjustOffsetForZoomAndSubpixelLayout(renderer, renderer->offsetLeft()); |
975 | return 0; |
976 | } |
977 | |
978 | double Element::offsetTopForBindings() |
979 | { |
980 | auto offset = offsetTop(); |
981 | |
982 | auto parent = makeRefPtr(offsetParent()); |
983 | if (!parent || !parent->isInShadowTree()) |
984 | return offset; |
985 | |
986 | ASSERT(&parent->document() == &document()); |
987 | if (&parent->treeScope() == &treeScope()) |
988 | return offset; |
989 | |
990 | auto ancestorTreeScopes = collectAncestorTreeScopeAsHashSet(*this); |
991 | while (parent && !ancestorTreeScopes.contains(&parent->treeScope())) { |
992 | offset += parent->offsetTop(); |
993 | parent = parent->offsetParent(); |
994 | } |
995 | |
996 | return offset; |
997 | } |
998 | |
999 | double Element::offsetTop() |
1000 | { |
1001 | document().updateLayoutIgnorePendingStylesheets(); |
1002 | if (RenderBoxModelObject* renderer = renderBoxModelObject()) |
1003 | return adjustOffsetForZoomAndSubpixelLayout(renderer, renderer->offsetTop()); |
1004 | return 0; |
1005 | } |
1006 | |
1007 | double Element::offsetWidth() |
1008 | { |
1009 | document().updateLayoutIfDimensionsOutOfDate(*this, WidthDimensionsCheck); |
1010 | if (RenderBoxModelObject* renderer = renderBoxModelObject()) { |
1011 | LayoutUnit offsetWidth = subpixelMetricsEnabled(renderer->document()) ? renderer->offsetWidth() : LayoutUnit(roundToInt(renderer->offsetWidth())); |
1012 | return convertToNonSubpixelValueIfNeeded(adjustLayoutUnitForAbsoluteZoom(offsetWidth, *renderer).toDouble(), renderer->document()); |
1013 | } |
1014 | return 0; |
1015 | } |
1016 | |
1017 | double Element::offsetHeight() |
1018 | { |
1019 | document().updateLayoutIfDimensionsOutOfDate(*this, HeightDimensionsCheck); |
1020 | if (RenderBoxModelObject* renderer = renderBoxModelObject()) { |
1021 | LayoutUnit offsetHeight = subpixelMetricsEnabled(renderer->document()) ? renderer->offsetHeight() : LayoutUnit(roundToInt(renderer->offsetHeight())); |
1022 | return convertToNonSubpixelValueIfNeeded(adjustLayoutUnitForAbsoluteZoom(offsetHeight, *renderer).toDouble(), renderer->document()); |
1023 | } |
1024 | return 0; |
1025 | } |
1026 | |
1027 | Element* Element::offsetParentForBindings() |
1028 | { |
1029 | Element* element = offsetParent(); |
1030 | if (!element || !element->isInShadowTree()) |
1031 | return element; |
1032 | while (element && !isDescendantOrShadowDescendantOf(&element->rootNode())) |
1033 | element = element->offsetParent(); |
1034 | return element; |
1035 | } |
1036 | |
1037 | Element* Element::offsetParent() |
1038 | { |
1039 | document().updateLayoutIgnorePendingStylesheets(); |
1040 | auto renderer = this->renderer(); |
1041 | if (!renderer) |
1042 | return nullptr; |
1043 | auto offsetParent = renderer->offsetParent(); |
1044 | if (!offsetParent) |
1045 | return nullptr; |
1046 | return offsetParent->element(); |
1047 | } |
1048 | |
1049 | double Element::clientLeft() |
1050 | { |
1051 | document().updateLayoutIgnorePendingStylesheets(); |
1052 | |
1053 | if (auto* renderer = renderBox()) { |
1054 | LayoutUnit clientLeft = subpixelMetricsEnabled(renderer->document()) ? renderer->clientLeft() : LayoutUnit(roundToInt(renderer->clientLeft())); |
1055 | return convertToNonSubpixelValueIfNeeded(adjustLayoutUnitForAbsoluteZoom(clientLeft, *renderer).toDouble(), renderer->document()); |
1056 | } |
1057 | return 0; |
1058 | } |
1059 | |
1060 | double Element::clientTop() |
1061 | { |
1062 | document().updateLayoutIgnorePendingStylesheets(); |
1063 | |
1064 | if (auto* renderer = renderBox()) { |
1065 | LayoutUnit clientTop = subpixelMetricsEnabled(renderer->document()) ? renderer->clientTop() : LayoutUnit(roundToInt(renderer->clientTop())); |
1066 | return convertToNonSubpixelValueIfNeeded(adjustLayoutUnitForAbsoluteZoom(clientTop, *renderer).toDouble(), renderer->document()); |
1067 | } |
1068 | return 0; |
1069 | } |
1070 | |
1071 | double Element::clientWidth() |
1072 | { |
1073 | document().updateLayoutIfDimensionsOutOfDate(*this, WidthDimensionsCheck); |
1074 | |
1075 | if (!document().hasLivingRenderTree()) |
1076 | return 0; |
1077 | |
1078 | RenderView& renderView = *document().renderView(); |
1079 | |
1080 | // When in strict mode, clientWidth for the document element should return the width of the containing frame. |
1081 | // When in quirks mode, clientWidth for the body element should return the width of the containing frame. |
1082 | bool inQuirksMode = document().inQuirksMode(); |
1083 | if ((!inQuirksMode && document().documentElement() == this) || (inQuirksMode && isHTMLElement() && document().bodyOrFrameset() == this)) |
1084 | return adjustForAbsoluteZoom(renderView.frameView().layoutWidth(), renderView); |
1085 | |
1086 | if (RenderBox* renderer = renderBox()) { |
1087 | LayoutUnit clientWidth = subpixelMetricsEnabled(renderer->document()) ? renderer->clientWidth() : LayoutUnit(roundToInt(renderer->clientWidth())); |
1088 | return convertToNonSubpixelValueIfNeeded(adjustLayoutUnitForAbsoluteZoom(clientWidth, *renderer).toDouble(), renderer->document()); |
1089 | } |
1090 | return 0; |
1091 | } |
1092 | |
1093 | double Element::clientHeight() |
1094 | { |
1095 | document().updateLayoutIfDimensionsOutOfDate(*this, HeightDimensionsCheck); |
1096 | if (!document().hasLivingRenderTree()) |
1097 | return 0; |
1098 | |
1099 | RenderView& renderView = *document().renderView(); |
1100 | |
1101 | // When in strict mode, clientHeight for the document element should return the height of the containing frame. |
1102 | // When in quirks mode, clientHeight for the body element should return the height of the containing frame. |
1103 | bool inQuirksMode = document().inQuirksMode(); |
1104 | if ((!inQuirksMode && document().documentElement() == this) || (inQuirksMode && isHTMLElement() && document().bodyOrFrameset() == this)) |
1105 | return adjustForAbsoluteZoom(renderView.frameView().layoutHeight(), renderView); |
1106 | |
1107 | if (RenderBox* renderer = renderBox()) { |
1108 | LayoutUnit clientHeight = subpixelMetricsEnabled(renderer->document()) ? renderer->clientHeight() : LayoutUnit(roundToInt(renderer->clientHeight())); |
1109 | return convertToNonSubpixelValueIfNeeded(adjustLayoutUnitForAbsoluteZoom(clientHeight, *renderer).toDouble(), renderer->document()); |
1110 | } |
1111 | return 0; |
1112 | } |
1113 | |
1114 | ALWAYS_INLINE Frame* Element::documentFrameWithNonNullView() const |
1115 | { |
1116 | auto* frame = document().frame(); |
1117 | return frame && frame->view() ? frame : nullptr; |
1118 | } |
1119 | |
1120 | int Element::scrollLeft() |
1121 | { |
1122 | document().updateLayoutIgnorePendingStylesheets(); |
1123 | |
1124 | if (document().scrollingElement() == this) { |
1125 | if (auto* frame = documentFrameWithNonNullView()) |
1126 | return adjustContentsScrollPositionOrSizeForZoom(frame->view()->contentsScrollPosition().x(), *frame); |
1127 | return 0; |
1128 | } |
1129 | |
1130 | if (auto* renderer = renderBox()) |
1131 | return adjustForAbsoluteZoom(renderer->scrollLeft(), *renderer); |
1132 | return 0; |
1133 | } |
1134 | |
1135 | int Element::scrollTop() |
1136 | { |
1137 | document().updateLayoutIgnorePendingStylesheets(); |
1138 | |
1139 | if (document().scrollingElement() == this) { |
1140 | if (auto* frame = documentFrameWithNonNullView()) |
1141 | return adjustContentsScrollPositionOrSizeForZoom(frame->view()->contentsScrollPosition().y(), *frame); |
1142 | return 0; |
1143 | } |
1144 | |
1145 | if (RenderBox* renderer = renderBox()) |
1146 | return adjustForAbsoluteZoom(renderer->scrollTop(), *renderer); |
1147 | return 0; |
1148 | } |
1149 | |
1150 | void Element::setScrollLeft(int newLeft) |
1151 | { |
1152 | document().updateLayoutIgnorePendingStylesheets(); |
1153 | |
1154 | if (document().scrollingElement() == this) { |
1155 | if (auto* frame = documentFrameWithNonNullView()) |
1156 | frame->view()->setScrollPosition(IntPoint(static_cast<int>(newLeft * frame->pageZoomFactor() * frame->frameScaleFactor()), frame->view()->scrollY())); |
1157 | return; |
1158 | } |
1159 | |
1160 | if (auto* renderer = renderBox()) { |
1161 | renderer->setScrollLeft(static_cast<int>(newLeft * renderer->style().effectiveZoom()), ScrollType::Programmatic); |
1162 | if (auto* scrollableArea = renderer->layer()) |
1163 | scrollableArea->setScrollShouldClearLatchedState(true); |
1164 | } |
1165 | } |
1166 | |
1167 | void Element::setScrollTop(int newTop) |
1168 | { |
1169 | document().updateLayoutIgnorePendingStylesheets(); |
1170 | |
1171 | if (document().scrollingElement() == this) { |
1172 | if (auto* frame = documentFrameWithNonNullView()) |
1173 | frame->view()->setScrollPosition(IntPoint(frame->view()->scrollX(), static_cast<int>(newTop * frame->pageZoomFactor() * frame->frameScaleFactor()))); |
1174 | return; |
1175 | } |
1176 | |
1177 | if (auto* renderer = renderBox()) { |
1178 | renderer->setScrollTop(static_cast<int>(newTop * renderer->style().effectiveZoom()), ScrollType::Programmatic); |
1179 | if (auto* scrollableArea = renderer->layer()) |
1180 | scrollableArea->setScrollShouldClearLatchedState(true); |
1181 | } |
1182 | } |
1183 | |
1184 | int Element::scrollWidth() |
1185 | { |
1186 | document().updateLayoutIfDimensionsOutOfDate(*this, WidthDimensionsCheck); |
1187 | |
1188 | if (document().scrollingElement() == this) { |
1189 | // FIXME (webkit.org/b/182289): updateLayoutIfDimensionsOutOfDate seems to ignore zoom level change. |
1190 | document().updateLayoutIgnorePendingStylesheets(); |
1191 | if (auto* frame = documentFrameWithNonNullView()) |
1192 | return adjustContentsScrollPositionOrSizeForZoom(frame->view()->contentsWidth(), *frame); |
1193 | return 0; |
1194 | } |
1195 | |
1196 | if (auto* renderer = renderBox()) |
1197 | return adjustForAbsoluteZoom(renderer->scrollWidth(), *renderer); |
1198 | return 0; |
1199 | } |
1200 | |
1201 | int Element::scrollHeight() |
1202 | { |
1203 | document().updateLayoutIfDimensionsOutOfDate(*this, HeightDimensionsCheck); |
1204 | |
1205 | if (document().scrollingElement() == this) { |
1206 | // FIXME (webkit.org/b/182289): updateLayoutIfDimensionsOutOfDate seems to ignore zoom level change. |
1207 | document().updateLayoutIgnorePendingStylesheets(); |
1208 | if (auto* frame = documentFrameWithNonNullView()) |
1209 | return adjustContentsScrollPositionOrSizeForZoom(frame->view()->contentsHeight(), *frame); |
1210 | return 0; |
1211 | } |
1212 | |
1213 | if (auto* renderer = renderBox()) |
1214 | return adjustForAbsoluteZoom(renderer->scrollHeight(), *renderer); |
1215 | return 0; |
1216 | } |
1217 | |
1218 | IntRect Element::boundsInRootViewSpace() |
1219 | { |
1220 | document().updateLayoutIgnorePendingStylesheets(); |
1221 | |
1222 | FrameView* view = document().view(); |
1223 | if (!view) |
1224 | return IntRect(); |
1225 | |
1226 | Vector<FloatQuad> quads; |
1227 | |
1228 | if (isSVGElement() && renderer()) { |
1229 | // Get the bounding rectangle from the SVG model. |
1230 | SVGElement& svgElement = downcast<SVGElement>(*this); |
1231 | FloatRect localRect; |
1232 | if (svgElement.getBoundingBox(localRect)) |
1233 | quads.append(renderer()->localToAbsoluteQuad(localRect)); |
1234 | } else { |
1235 | // Get the bounding rectangle from the box model. |
1236 | if (renderBoxModelObject()) |
1237 | renderBoxModelObject()->absoluteQuads(quads); |
1238 | } |
1239 | |
1240 | if (quads.isEmpty()) |
1241 | return IntRect(); |
1242 | |
1243 | IntRect result = quads[0].enclosingBoundingBox(); |
1244 | for (size_t i = 1; i < quads.size(); ++i) |
1245 | result.unite(quads[i].enclosingBoundingBox()); |
1246 | |
1247 | result = view->contentsToRootView(result); |
1248 | return result; |
1249 | } |
1250 | |
1251 | static bool layoutOverflowRectContainsAllDescendants(const RenderBox& renderBox) |
1252 | { |
1253 | if (renderBox.isRenderView()) |
1254 | return true; |
1255 | |
1256 | if (!renderBox.element()) |
1257 | return false; |
1258 | |
1259 | // If there are any position:fixed inside of us, game over. |
1260 | if (auto* viewPositionedObjects = renderBox.view().positionedObjects()) { |
1261 | for (auto* positionedBox : *viewPositionedObjects) { |
1262 | if (positionedBox == &renderBox) |
1263 | continue; |
1264 | if (positionedBox->isFixedPositioned() && renderBox.element()->contains(positionedBox->element())) |
1265 | return false; |
1266 | } |
1267 | } |
1268 | |
1269 | if (renderBox.canContainAbsolutelyPositionedObjects()) { |
1270 | // Our layout overflow will include all descendant positioned elements. |
1271 | return true; |
1272 | } |
1273 | |
1274 | // This renderer may have positioned descendants whose containing block is some ancestor. |
1275 | if (auto* containingBlock = renderBox.containingBlockForAbsolutePosition()) { |
1276 | if (auto* positionedObjects = containingBlock->positionedObjects()) { |
1277 | for (auto* positionedBox : *positionedObjects) { |
1278 | if (positionedBox == &renderBox) |
1279 | continue; |
1280 | if (renderBox.element()->contains(positionedBox->element())) |
1281 | return false; |
1282 | } |
1283 | } |
1284 | } |
1285 | return false; |
1286 | } |
1287 | |
1288 | LayoutRect Element::absoluteEventBounds(bool& boundsIncludeAllDescendantElements, bool& includesFixedPositionElements) |
1289 | { |
1290 | boundsIncludeAllDescendantElements = false; |
1291 | includesFixedPositionElements = false; |
1292 | |
1293 | if (!renderer()) |
1294 | return LayoutRect(); |
1295 | |
1296 | LayoutRect result; |
1297 | if (isSVGElement()) { |
1298 | // Get the bounding rectangle from the SVG model. |
1299 | SVGElement& svgElement = downcast<SVGElement>(*this); |
1300 | FloatRect localRect; |
1301 | if (svgElement.getBoundingBox(localRect, SVGLocatable::DisallowStyleUpdate)) |
1302 | result = LayoutRect(renderer()->localToAbsoluteQuad(localRect, UseTransforms, &includesFixedPositionElements).boundingBox()); |
1303 | } else { |
1304 | auto* renderer = this->renderer(); |
1305 | if (is<RenderBox>(renderer)) { |
1306 | auto& box = downcast<RenderBox>(*renderer); |
1307 | |
1308 | bool computedBounds = false; |
1309 | |
1310 | if (RenderFragmentedFlow* fragmentedFlow = box.enclosingFragmentedFlow()) { |
1311 | bool wasFixed = false; |
1312 | Vector<FloatQuad> quads; |
1313 | FloatRect localRect(0, 0, box.width(), box.height()); |
1314 | if (fragmentedFlow->absoluteQuadsForBox(quads, &wasFixed, &box, localRect.y(), localRect.maxY())) { |
1315 | FloatRect quadBounds = quads[0].boundingBox(); |
1316 | for (size_t i = 1; i < quads.size(); ++i) |
1317 | quadBounds.unite(quads[i].boundingBox()); |
1318 | |
1319 | result = LayoutRect(quadBounds); |
1320 | computedBounds = true; |
1321 | } else { |
1322 | // Probably columns. Just return the bounds of the multicol block for now. |
1323 | // FIXME: this doesn't handle nested columns. |
1324 | RenderElement* multicolContainer = fragmentedFlow->parent(); |
1325 | if (multicolContainer && is<RenderBox>(multicolContainer)) { |
1326 | auto overflowRect = downcast<RenderBox>(*multicolContainer).layoutOverflowRect(); |
1327 | result = LayoutRect(multicolContainer->localToAbsoluteQuad(FloatRect(overflowRect), UseTransforms, &includesFixedPositionElements).boundingBox()); |
1328 | computedBounds = true; |
1329 | } |
1330 | } |
1331 | } |
1332 | |
1333 | if (!computedBounds) { |
1334 | LayoutRect overflowRect = box.layoutOverflowRect(); |
1335 | result = LayoutRect(box.localToAbsoluteQuad(FloatRect(overflowRect), UseTransforms, &includesFixedPositionElements).boundingBox()); |
1336 | boundsIncludeAllDescendantElements = layoutOverflowRectContainsAllDescendants(box); |
1337 | } |
1338 | } else |
1339 | result = LayoutRect(renderer->absoluteBoundingBoxRect(true /* useTransforms */, &includesFixedPositionElements)); |
1340 | } |
1341 | |
1342 | return result; |
1343 | } |
1344 | |
1345 | LayoutRect Element::absoluteEventBoundsOfElementAndDescendants(bool& includesFixedPositionElements) |
1346 | { |
1347 | bool boundsIncludeDescendants; |
1348 | LayoutRect result = absoluteEventBounds(boundsIncludeDescendants, includesFixedPositionElements); |
1349 | if (boundsIncludeDescendants) |
1350 | return result; |
1351 | |
1352 | for (auto& child : childrenOfType<Element>(*this)) { |
1353 | bool includesFixedPosition = false; |
1354 | LayoutRect childBounds = child.absoluteEventBoundsOfElementAndDescendants(includesFixedPosition); |
1355 | includesFixedPositionElements |= includesFixedPosition; |
1356 | result.unite(childBounds); |
1357 | } |
1358 | |
1359 | return result; |
1360 | } |
1361 | |
1362 | LayoutRect Element::absoluteEventHandlerBounds(bool& includesFixedPositionElements) |
1363 | { |
1364 | // This is not web-exposed, so don't call the FOUC-inducing updateLayoutIgnorePendingStylesheets(). |
1365 | FrameView* frameView = document().view(); |
1366 | if (!frameView) |
1367 | return LayoutRect(); |
1368 | |
1369 | return absoluteEventBoundsOfElementAndDescendants(includesFixedPositionElements); |
1370 | } |
1371 | |
1372 | static Optional<std::pair<RenderObject*, LayoutRect>> listBoxElementBoundingBox(Element& element) |
1373 | { |
1374 | HTMLSelectElement* selectElement; |
1375 | bool isGroup; |
1376 | if (is<HTMLOptionElement>(element)) { |
1377 | selectElement = downcast<HTMLOptionElement>(element).ownerSelectElement(); |
1378 | isGroup = false; |
1379 | } else if (is<HTMLOptGroupElement>(element)) { |
1380 | selectElement = downcast<HTMLOptGroupElement>(element).ownerSelectElement(); |
1381 | isGroup = true; |
1382 | } else |
1383 | return WTF::nullopt; |
1384 | |
1385 | if (!selectElement || !selectElement->renderer() || !is<RenderListBox>(selectElement->renderer())) |
1386 | return WTF::nullopt; |
1387 | |
1388 | auto& renderer = downcast<RenderListBox>(*selectElement->renderer()); |
1389 | Optional<LayoutRect> boundingBox; |
1390 | int optionIndex = 0; |
1391 | for (auto* item : selectElement->listItems()) { |
1392 | if (item == &element) { |
1393 | LayoutPoint additionOffset; |
1394 | boundingBox = renderer.itemBoundingBoxRect(additionOffset, optionIndex); |
1395 | if (!isGroup) |
1396 | break; |
1397 | } else if (isGroup && boundingBox) { |
1398 | if (item->parentNode() != &element) |
1399 | break; |
1400 | LayoutPoint additionOffset; |
1401 | boundingBox->setHeight(boundingBox->height() + renderer.itemBoundingBoxRect(additionOffset, optionIndex).height()); |
1402 | } |
1403 | ++optionIndex; |
1404 | } |
1405 | |
1406 | if (!boundingBox) |
1407 | return WTF::nullopt; |
1408 | |
1409 | return std::pair<RenderObject*, LayoutRect> { &renderer, boundingBox.value() }; |
1410 | } |
1411 | |
1412 | Ref<DOMRectList> Element::getClientRects() |
1413 | { |
1414 | document().updateLayoutIgnorePendingStylesheets(); |
1415 | |
1416 | RenderObject* renderer = this->renderer(); |
1417 | Vector<FloatQuad> quads; |
1418 | |
1419 | if (auto pair = listBoxElementBoundingBox(*this)) { |
1420 | renderer = pair.value().first; |
1421 | quads.append(renderer->localToAbsoluteQuad(FloatQuad { pair.value().second })); |
1422 | } else if (auto* renderBoxModelObject = this->renderBoxModelObject()) |
1423 | renderBoxModelObject->absoluteQuads(quads); |
1424 | |
1425 | // FIXME: Handle SVG elements. |
1426 | // FIXME: Handle table/inline-table with a caption. |
1427 | |
1428 | if (quads.isEmpty()) |
1429 | return DOMRectList::create(); |
1430 | |
1431 | document().convertAbsoluteToClientQuads(quads, renderer->style()); |
1432 | return DOMRectList::create(quads); |
1433 | } |
1434 | |
1435 | Optional<std::pair<RenderObject*, FloatRect>> Element::boundingAbsoluteRectWithoutLayout() |
1436 | { |
1437 | RenderObject* renderer = this->renderer(); |
1438 | Vector<FloatQuad> quads; |
1439 | if (isSVGElement() && renderer && !renderer->isSVGRoot()) { |
1440 | // Get the bounding rectangle from the SVG model. |
1441 | SVGElement& svgElement = downcast<SVGElement>(*this); |
1442 | FloatRect localRect; |
1443 | if (svgElement.getBoundingBox(localRect)) |
1444 | quads.append(renderer->localToAbsoluteQuad(localRect)); |
1445 | } else if (auto pair = listBoxElementBoundingBox(*this)) { |
1446 | renderer = pair.value().first; |
1447 | quads.append(renderer->localToAbsoluteQuad(FloatQuad { pair.value().second })); |
1448 | } else if (auto* renderBoxModelObject = this->renderBoxModelObject()) |
1449 | renderBoxModelObject->absoluteQuads(quads); |
1450 | |
1451 | if (quads.isEmpty()) |
1452 | return WTF::nullopt; |
1453 | |
1454 | FloatRect result = quads[0].boundingBox(); |
1455 | for (size_t i = 1; i < quads.size(); ++i) |
1456 | result.unite(quads[i].boundingBox()); |
1457 | |
1458 | return std::make_pair(renderer, result); |
1459 | } |
1460 | |
1461 | FloatRect Element::boundingClientRect() |
1462 | { |
1463 | document().updateLayoutIgnorePendingStylesheets(); |
1464 | auto pair = boundingAbsoluteRectWithoutLayout(); |
1465 | if (!pair) |
1466 | return { }; |
1467 | RenderObject* renderer = pair->first; |
1468 | FloatRect result = pair->second; |
1469 | document().convertAbsoluteToClientRect(result, renderer->style()); |
1470 | return result; |
1471 | } |
1472 | |
1473 | Ref<DOMRect> Element::getBoundingClientRect() |
1474 | { |
1475 | return DOMRect::create(boundingClientRect()); |
1476 | } |
1477 | |
1478 | // Note that this is not web-exposed, and does not use the same coordinate system as getBoundingClientRect() and friends. |
1479 | IntRect Element::clientRect() const |
1480 | { |
1481 | if (RenderObject* renderer = this->renderer()) |
1482 | return document().view()->contentsToRootView(renderer->absoluteBoundingBoxRect()); |
1483 | return IntRect(); |
1484 | } |
1485 | |
1486 | IntRect Element::screenRect() const |
1487 | { |
1488 | if (RenderObject* renderer = this->renderer()) |
1489 | return document().view()->contentsToScreen(renderer->absoluteBoundingBoxRect()); |
1490 | return IntRect(); |
1491 | } |
1492 | |
1493 | const AtomString& Element::getAttribute(const AtomString& qualifiedName) const |
1494 | { |
1495 | if (!elementData()) |
1496 | return nullAtom(); |
1497 | synchronizeAttribute(qualifiedName); |
1498 | if (const Attribute* attribute = elementData()->findAttributeByName(qualifiedName, shouldIgnoreAttributeCase(*this))) |
1499 | return attribute->value(); |
1500 | return nullAtom(); |
1501 | } |
1502 | |
1503 | const AtomString& Element::getAttributeNS(const AtomString& namespaceURI, const AtomString& localName) const |
1504 | { |
1505 | return getAttribute(QualifiedName(nullAtom(), localName, namespaceURI)); |
1506 | } |
1507 | |
1508 | // https://dom.spec.whatwg.org/#dom-element-toggleattribute |
1509 | ExceptionOr<bool> Element::toggleAttribute(const AtomString& qualifiedName, Optional<bool> force) |
1510 | { |
1511 | if (!Document::isValidName(qualifiedName)) |
1512 | return Exception { InvalidCharacterError }; |
1513 | |
1514 | synchronizeAttribute(qualifiedName); |
1515 | |
1516 | auto caseAdjustedQualifiedName = shouldIgnoreAttributeCase(*this) ? qualifiedName.convertToASCIILowercase() : qualifiedName; |
1517 | unsigned index = elementData() ? elementData()->findAttributeIndexByName(caseAdjustedQualifiedName, false) : ElementData::attributeNotFound; |
1518 | if (index == ElementData::attributeNotFound) { |
1519 | if (!force || *force) { |
1520 | setAttributeInternal(index, QualifiedName { nullAtom(), caseAdjustedQualifiedName, nullAtom() }, emptyString(), NotInSynchronizationOfLazyAttribute); |
1521 | return true; |
1522 | } |
1523 | return false; |
1524 | } |
1525 | |
1526 | if (!force || !*force) { |
1527 | removeAttributeInternal(index, NotInSynchronizationOfLazyAttribute); |
1528 | return false; |
1529 | } |
1530 | return true; |
1531 | } |
1532 | |
1533 | ExceptionOr<void> Element::setAttribute(const AtomString& qualifiedName, const AtomString& value) |
1534 | { |
1535 | if (!Document::isValidName(qualifiedName)) |
1536 | return Exception { InvalidCharacterError }; |
1537 | |
1538 | synchronizeAttribute(qualifiedName); |
1539 | auto caseAdjustedQualifiedName = shouldIgnoreAttributeCase(*this) ? qualifiedName.convertToASCIILowercase() : qualifiedName; |
1540 | unsigned index = elementData() ? elementData()->findAttributeIndexByName(caseAdjustedQualifiedName, false) : ElementData::attributeNotFound; |
1541 | auto name = index != ElementData::attributeNotFound ? attributeAt(index).name() : QualifiedName { nullAtom(), caseAdjustedQualifiedName, nullAtom() }; |
1542 | setAttributeInternal(index, name, value, NotInSynchronizationOfLazyAttribute); |
1543 | |
1544 | return { }; |
1545 | } |
1546 | |
1547 | void Element::setAttribute(const QualifiedName& name, const AtomString& value) |
1548 | { |
1549 | synchronizeAttribute(name); |
1550 | unsigned index = elementData() ? elementData()->findAttributeIndexByName(name) : ElementData::attributeNotFound; |
1551 | setAttributeInternal(index, name, value, NotInSynchronizationOfLazyAttribute); |
1552 | } |
1553 | |
1554 | void Element::setAttributeWithoutSynchronization(const QualifiedName& name, const AtomString& value) |
1555 | { |
1556 | unsigned index = elementData() ? elementData()->findAttributeIndexByName(name) : ElementData::attributeNotFound; |
1557 | setAttributeInternal(index, name, value, NotInSynchronizationOfLazyAttribute); |
1558 | } |
1559 | |
1560 | void Element::setSynchronizedLazyAttribute(const QualifiedName& name, const AtomString& value) |
1561 | { |
1562 | unsigned index = elementData() ? elementData()->findAttributeIndexByName(name) : ElementData::attributeNotFound; |
1563 | setAttributeInternal(index, name, value, InSynchronizationOfLazyAttribute); |
1564 | } |
1565 | |
1566 | inline void Element::setAttributeInternal(unsigned index, const QualifiedName& name, const AtomString& newValue, SynchronizationOfLazyAttribute inSynchronizationOfLazyAttribute) |
1567 | { |
1568 | if (newValue.isNull()) { |
1569 | if (index != ElementData::attributeNotFound) |
1570 | removeAttributeInternal(index, inSynchronizationOfLazyAttribute); |
1571 | return; |
1572 | } |
1573 | |
1574 | if (index == ElementData::attributeNotFound) { |
1575 | addAttributeInternal(name, newValue, inSynchronizationOfLazyAttribute); |
1576 | return; |
1577 | } |
1578 | |
1579 | if (inSynchronizationOfLazyAttribute) { |
1580 | ensureUniqueElementData().attributeAt(index).setValue(newValue); |
1581 | return; |
1582 | } |
1583 | |
1584 | const Attribute& attribute = attributeAt(index); |
1585 | QualifiedName attributeName = attribute.name(); |
1586 | AtomString oldValue = attribute.value(); |
1587 | |
1588 | willModifyAttribute(attributeName, oldValue, newValue); |
1589 | |
1590 | if (newValue != oldValue) { |
1591 | Style::AttributeChangeInvalidation styleInvalidation(*this, name, oldValue, newValue); |
1592 | ensureUniqueElementData().attributeAt(index).setValue(newValue); |
1593 | } |
1594 | |
1595 | didModifyAttribute(attributeName, oldValue, newValue); |
1596 | } |
1597 | |
1598 | static inline AtomString makeIdForStyleResolution(const AtomString& value, bool inQuirksMode) |
1599 | { |
1600 | if (inQuirksMode) |
1601 | return value.convertToASCIILowercase(); |
1602 | return value; |
1603 | } |
1604 | |
1605 | void Element::attributeChanged(const QualifiedName& name, const AtomString& oldValue, const AtomString& newValue, AttributeModificationReason) |
1606 | { |
1607 | bool valueIsSameAsBefore = oldValue == newValue; |
1608 | |
1609 | if (!valueIsSameAsBefore) { |
1610 | if (name == HTMLNames::accesskeyAttr) |
1611 | document().invalidateAccessKeyCache(); |
1612 | else if (name == HTMLNames::classAttr) |
1613 | classAttributeChanged(newValue); |
1614 | else if (name == HTMLNames::idAttr) { |
1615 | AtomString oldId = elementData()->idForStyleResolution(); |
1616 | AtomString newId = makeIdForStyleResolution(newValue, document().inQuirksMode()); |
1617 | if (newId != oldId) { |
1618 | Style::IdChangeInvalidation styleInvalidation(*this, oldId, newId); |
1619 | elementData()->setIdForStyleResolution(newId); |
1620 | } |
1621 | |
1622 | if (!oldValue.isEmpty()) |
1623 | treeScope().idTargetObserverRegistry().notifyObservers(*oldValue.impl()); |
1624 | if (!newValue.isEmpty()) |
1625 | treeScope().idTargetObserverRegistry().notifyObservers(*newValue.impl()); |
1626 | } else if (name == HTMLNames::nameAttr) |
1627 | elementData()->setHasNameAttribute(!newValue.isNull()); |
1628 | else if (name == HTMLNames::pseudoAttr) { |
1629 | if (needsStyleInvalidation() && isInShadowTree()) |
1630 | invalidateStyleForSubtree(); |
1631 | } else if (name == HTMLNames::slotAttr) { |
1632 | if (auto* parent = parentElement()) { |
1633 | if (auto* shadowRoot = parent->shadowRoot()) |
1634 | shadowRoot->hostChildElementDidChangeSlotAttribute(*this, oldValue, newValue); |
1635 | } |
1636 | } |
1637 | } |
1638 | |
1639 | parseAttribute(name, newValue); |
1640 | |
1641 | document().incDOMTreeVersion(); |
1642 | |
1643 | if (UNLIKELY(isDefinedCustomElement())) |
1644 | CustomElementReactionQueue::enqueueAttributeChangedCallbackIfNeeded(*this, name, oldValue, newValue); |
1645 | |
1646 | if (valueIsSameAsBefore) |
1647 | return; |
1648 | |
1649 | invalidateNodeListAndCollectionCachesInAncestorsForAttribute(name); |
1650 | |
1651 | if (AXObjectCache* cache = document().existingAXObjectCache()) |
1652 | cache->deferAttributeChangeIfNeeded(name, this); |
1653 | } |
1654 | |
1655 | template <typename CharacterType> |
1656 | static inline bool classStringHasClassName(const CharacterType* characters, unsigned length) |
1657 | { |
1658 | ASSERT(length > 0); |
1659 | |
1660 | unsigned i = 0; |
1661 | do { |
1662 | if (isNotHTMLSpace(characters[i])) |
1663 | break; |
1664 | ++i; |
1665 | } while (i < length); |
1666 | |
1667 | return i < length; |
1668 | } |
1669 | |
1670 | static inline bool classStringHasClassName(const AtomString& newClassString) |
1671 | { |
1672 | unsigned length = newClassString.length(); |
1673 | |
1674 | if (!length) |
1675 | return false; |
1676 | |
1677 | if (newClassString.is8Bit()) |
1678 | return classStringHasClassName(newClassString.characters8(), length); |
1679 | return classStringHasClassName(newClassString.characters16(), length); |
1680 | } |
1681 | |
1682 | void Element::classAttributeChanged(const AtomString& newClassString) |
1683 | { |
1684 | // Note: We'll need ElementData, but it doesn't have to be UniqueElementData. |
1685 | if (!elementData()) |
1686 | ensureUniqueElementData(); |
1687 | |
1688 | bool shouldFoldCase = document().inQuirksMode(); |
1689 | bool newStringHasClasses = classStringHasClassName(newClassString); |
1690 | |
1691 | auto oldClassNames = elementData()->classNames(); |
1692 | auto newClassNames = newStringHasClasses ? SpaceSplitString(newClassString, shouldFoldCase) : SpaceSplitString(); |
1693 | { |
1694 | Style::ClassChangeInvalidation styleInvalidation(*this, oldClassNames, newClassNames); |
1695 | elementData()->setClassNames(newClassNames); |
1696 | } |
1697 | |
1698 | if (hasRareData()) { |
1699 | if (auto* classList = elementRareData()->classList()) |
1700 | classList->associatedAttributeValueChanged(newClassString); |
1701 | } |
1702 | } |
1703 | |
1704 | URL Element::absoluteLinkURL() const |
1705 | { |
1706 | if (!isLink()) |
1707 | return URL(); |
1708 | |
1709 | AtomString linkAttribute; |
1710 | if (hasTagName(SVGNames::aTag)) |
1711 | linkAttribute = getAttribute(SVGNames::hrefAttr, XLinkNames::hrefAttr); |
1712 | else |
1713 | linkAttribute = getAttribute(HTMLNames::hrefAttr); |
1714 | |
1715 | if (linkAttribute.isEmpty()) |
1716 | return URL(); |
1717 | |
1718 | return document().completeURL(stripLeadingAndTrailingHTMLSpaces(linkAttribute)); |
1719 | } |
1720 | |
1721 | #if ENABLE(TOUCH_EVENTS) |
1722 | bool Element::allowsDoubleTapGesture() const |
1723 | { |
1724 | #if ENABLE(POINTER_EVENTS) |
1725 | if (renderStyle() && renderStyle()->touchActions() != TouchAction::Auto) |
1726 | return false; |
1727 | #endif |
1728 | |
1729 | Element* parent = parentElement(); |
1730 | return !parent || parent->allowsDoubleTapGesture(); |
1731 | } |
1732 | #endif |
1733 | |
1734 | StyleResolver& Element::styleResolver() |
1735 | { |
1736 | if (auto* shadowRoot = containingShadowRoot()) |
1737 | return shadowRoot->styleScope().resolver(); |
1738 | |
1739 | return document().styleScope().resolver(); |
1740 | } |
1741 | |
1742 | ElementStyle Element::resolveStyle(const RenderStyle* parentStyle) |
1743 | { |
1744 | return styleResolver().styleForElement(*this, parentStyle); |
1745 | } |
1746 | |
1747 | static void invalidateForSiblingCombinators(Element* sibling) |
1748 | { |
1749 | for (; sibling; sibling = sibling->nextElementSibling()) { |
1750 | if (sibling->styleIsAffectedByPreviousSibling()) |
1751 | sibling->invalidateStyleInternal(); |
1752 | if (sibling->descendantsAffectedByPreviousSibling()) { |
1753 | for (auto* siblingChild = sibling->firstElementChild(); siblingChild; siblingChild = siblingChild->nextElementSibling()) |
1754 | siblingChild->invalidateStyleForSubtreeInternal(); |
1755 | } |
1756 | if (!sibling->affectsNextSiblingElementStyle()) |
1757 | return; |
1758 | } |
1759 | } |
1760 | |
1761 | static void invalidateSiblingsIfNeeded(Element& element) |
1762 | { |
1763 | if (!element.affectsNextSiblingElementStyle()) |
1764 | return; |
1765 | auto* parent = element.parentElement(); |
1766 | if (parent && parent->styleValidity() >= Style::Validity::SubtreeInvalid) |
1767 | return; |
1768 | |
1769 | invalidateForSiblingCombinators(element.nextElementSibling()); |
1770 | } |
1771 | |
1772 | void Element::invalidateStyle() |
1773 | { |
1774 | Node::invalidateStyle(Style::Validity::ElementInvalid); |
1775 | invalidateSiblingsIfNeeded(*this); |
1776 | } |
1777 | |
1778 | void Element::invalidateStyleAndLayerComposition() |
1779 | { |
1780 | Node::invalidateStyle(Style::Validity::ElementInvalid, Style::InvalidationMode::RecompositeLayer); |
1781 | invalidateSiblingsIfNeeded(*this); |
1782 | } |
1783 | |
1784 | void Element::invalidateStyleForSubtree() |
1785 | { |
1786 | Node::invalidateStyle(Style::Validity::SubtreeInvalid); |
1787 | invalidateSiblingsIfNeeded(*this); |
1788 | } |
1789 | |
1790 | void Element::invalidateStyleAndRenderersForSubtree() |
1791 | { |
1792 | Node::invalidateStyle(Style::Validity::SubtreeAndRenderersInvalid); |
1793 | invalidateSiblingsIfNeeded(*this); |
1794 | } |
1795 | |
1796 | void Element::invalidateStyleInternal() |
1797 | { |
1798 | Node::invalidateStyle(Style::Validity::ElementInvalid); |
1799 | } |
1800 | |
1801 | void Element::invalidateStyleForSubtreeInternal() |
1802 | { |
1803 | Node::invalidateStyle(Style::Validity::SubtreeInvalid); |
1804 | } |
1805 | |
1806 | bool Element::hasDisplayContents() const |
1807 | { |
1808 | if (!hasRareData()) |
1809 | return false; |
1810 | |
1811 | const RenderStyle* style = elementRareData()->computedStyle(); |
1812 | return style && style->display() == DisplayType::Contents; |
1813 | } |
1814 | |
1815 | void Element::storeDisplayContentsStyle(std::unique_ptr<RenderStyle> style) |
1816 | { |
1817 | ASSERT(style && style->display() == DisplayType::Contents); |
1818 | ASSERT(!renderer() || isPseudoElement()); |
1819 | ensureElementRareData().setComputedStyle(WTFMove(style)); |
1820 | } |
1821 | |
1822 | // Returns true is the given attribute is an event handler. |
1823 | // We consider an event handler any attribute that begins with "on". |
1824 | // It is a simple solution that has the advantage of not requiring any |
1825 | // code or configuration change if a new event handler is defined. |
1826 | |
1827 | bool Element::isEventHandlerAttribute(const Attribute& attribute) const |
1828 | { |
1829 | return attribute.name().namespaceURI().isNull() && attribute.name().localName().startsWith("on" ); |
1830 | } |
1831 | |
1832 | bool Element::isJavaScriptURLAttribute(const Attribute& attribute) const |
1833 | { |
1834 | return isURLAttribute(attribute) && WTF::protocolIsJavaScript(stripLeadingAndTrailingHTMLSpaces(attribute.value())); |
1835 | } |
1836 | |
1837 | void Element::stripScriptingAttributes(Vector<Attribute>& attributeVector) const |
1838 | { |
1839 | attributeVector.removeAllMatching([this](auto& attribute) -> bool { |
1840 | return this->isEventHandlerAttribute(attribute) |
1841 | || this->isJavaScriptURLAttribute(attribute) |
1842 | || this->isHTMLContentAttribute(attribute); |
1843 | }); |
1844 | } |
1845 | |
1846 | void Element::parserSetAttributes(const Vector<Attribute>& attributeVector) |
1847 | { |
1848 | ASSERT(!isConnected()); |
1849 | ASSERT(!parentNode()); |
1850 | ASSERT(!m_elementData); |
1851 | |
1852 | if (!attributeVector.isEmpty()) { |
1853 | if (document().sharedObjectPool()) |
1854 | m_elementData = document().sharedObjectPool()->cachedShareableElementDataWithAttributes(attributeVector); |
1855 | else |
1856 | m_elementData = ShareableElementData::createWithAttributes(attributeVector); |
1857 | |
1858 | } |
1859 | |
1860 | parserDidSetAttributes(); |
1861 | |
1862 | // Use attributeVector instead of m_elementData because attributeChanged might modify m_elementData. |
1863 | for (const auto& attribute : attributeVector) |
1864 | attributeChanged(attribute.name(), nullAtom(), attribute.value(), ModifiedDirectly); |
1865 | } |
1866 | |
1867 | void Element::parserDidSetAttributes() |
1868 | { |
1869 | } |
1870 | |
1871 | void Element::didMoveToNewDocument(Document& oldDocument, Document& newDocument) |
1872 | { |
1873 | ASSERT_WITH_SECURITY_IMPLICATION(&document() == &newDocument); |
1874 | |
1875 | if (oldDocument.inQuirksMode() != document().inQuirksMode()) { |
1876 | // ElementData::m_classNames or ElementData::m_idForStyleResolution need to be updated with the right case. |
1877 | if (hasID()) |
1878 | attributeChanged(idAttr, nullAtom(), getIdAttribute()); |
1879 | if (hasClass()) |
1880 | attributeChanged(classAttr, nullAtom(), getAttribute(classAttr)); |
1881 | } |
1882 | |
1883 | if (UNLIKELY(isDefinedCustomElement())) |
1884 | CustomElementReactionQueue::enqueueAdoptedCallbackIfNeeded(*this, oldDocument, newDocument); |
1885 | |
1886 | #if ENABLE(INTERSECTION_OBSERVER) |
1887 | if (auto* observerData = intersectionObserverData()) { |
1888 | for (const auto& observer : observerData->observers) { |
1889 | if (observer->hasObservationTargets()) { |
1890 | oldDocument.removeIntersectionObserver(*observer); |
1891 | newDocument.addIntersectionObserver(*observer); |
1892 | } |
1893 | } |
1894 | } |
1895 | #endif |
1896 | } |
1897 | |
1898 | bool Element::hasAttributes() const |
1899 | { |
1900 | synchronizeAllAttributes(); |
1901 | return elementData() && elementData()->length(); |
1902 | } |
1903 | |
1904 | bool Element::hasEquivalentAttributes(const Element& other) const |
1905 | { |
1906 | synchronizeAllAttributes(); |
1907 | other.synchronizeAllAttributes(); |
1908 | if (elementData() == other.elementData()) |
1909 | return true; |
1910 | if (elementData()) |
1911 | return elementData()->isEquivalent(other.elementData()); |
1912 | if (other.elementData()) |
1913 | return other.elementData()->isEquivalent(elementData()); |
1914 | return true; |
1915 | } |
1916 | |
1917 | String Element::nodeName() const |
1918 | { |
1919 | return m_tagName.toString(); |
1920 | } |
1921 | |
1922 | String Element::nodeNamePreservingCase() const |
1923 | { |
1924 | return m_tagName.toString(); |
1925 | } |
1926 | |
1927 | ExceptionOr<void> Element::setPrefix(const AtomString& prefix) |
1928 | { |
1929 | auto result = checkSetPrefix(prefix); |
1930 | if (result.hasException()) |
1931 | return result.releaseException(); |
1932 | |
1933 | m_tagName.setPrefix(prefix.isEmpty() ? nullAtom() : prefix); |
1934 | return { }; |
1935 | } |
1936 | |
1937 | const AtomString& Element::imageSourceURL() const |
1938 | { |
1939 | return attributeWithoutSynchronization(srcAttr); |
1940 | } |
1941 | |
1942 | bool Element::rendererIsNeeded(const RenderStyle& style) |
1943 | { |
1944 | return style.display() != DisplayType::None && style.display() != DisplayType::Contents; |
1945 | } |
1946 | |
1947 | RenderPtr<RenderElement> Element::createElementRenderer(RenderStyle&& style, const RenderTreePosition&) |
1948 | { |
1949 | return RenderElement::createFor(*this, WTFMove(style)); |
1950 | } |
1951 | |
1952 | Node::InsertedIntoAncestorResult Element::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree) |
1953 | { |
1954 | ContainerNode::insertedIntoAncestor(insertionType, parentOfInsertedTree); |
1955 | |
1956 | #if ENABLE(FULLSCREEN_API) |
1957 | if (containsFullScreenElement() && parentElement() && !parentElement()->containsFullScreenElement()) |
1958 | setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(true); |
1959 | #endif |
1960 | |
1961 | if (parentNode() == &parentOfInsertedTree) { |
1962 | if (auto* shadowRoot = parentNode()->shadowRoot()) |
1963 | shadowRoot->hostChildElementDidChange(*this); |
1964 | } |
1965 | |
1966 | if (!parentOfInsertedTree.isInTreeScope()) |
1967 | return InsertedIntoAncestorResult::Done; |
1968 | |
1969 | bool becomeConnected = insertionType.connectedToDocument; |
1970 | TreeScope* newScope = &parentOfInsertedTree.treeScope(); |
1971 | HTMLDocument* newDocument = becomeConnected && is<HTMLDocument>(newScope->documentScope()) ? &downcast<HTMLDocument>(newScope->documentScope()) : nullptr; |
1972 | if (!insertionType.treeScopeChanged) |
1973 | newScope = nullptr; |
1974 | |
1975 | const AtomString& idValue = getIdAttribute(); |
1976 | if (!idValue.isNull()) { |
1977 | if (newScope) |
1978 | updateIdForTreeScope(*newScope, nullAtom(), idValue); |
1979 | if (newDocument) |
1980 | updateIdForDocument(*newDocument, nullAtom(), idValue, AlwaysUpdateHTMLDocumentNamedItemMaps); |
1981 | } |
1982 | |
1983 | const AtomString& nameValue = getNameAttribute(); |
1984 | if (!nameValue.isNull()) { |
1985 | if (newScope) |
1986 | updateNameForTreeScope(*newScope, nullAtom(), nameValue); |
1987 | if (newDocument) |
1988 | updateNameForDocument(*newDocument, nullAtom(), nameValue); |
1989 | } |
1990 | |
1991 | if (newScope && hasTagName(labelTag)) { |
1992 | if (newScope->shouldCacheLabelsByForAttribute()) |
1993 | updateLabel(*newScope, nullAtom(), attributeWithoutSynchronization(forAttr)); |
1994 | } |
1995 | |
1996 | if (becomeConnected) { |
1997 | if (UNLIKELY(isCustomElementUpgradeCandidate())) { |
1998 | ASSERT(isConnected()); |
1999 | CustomElementReactionQueue::enqueueElementUpgradeIfDefined(*this); |
2000 | } |
2001 | if (UNLIKELY(isDefinedCustomElement())) |
2002 | CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded(*this); |
2003 | } |
2004 | |
2005 | if (UNLIKELY(hasTagName(articleTag) && newDocument)) |
2006 | newDocument->registerArticleElement(*this); |
2007 | |
2008 | return InsertedIntoAncestorResult::Done; |
2009 | } |
2010 | |
2011 | void Element::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree) |
2012 | { |
2013 | #if ENABLE(FULLSCREEN_API) |
2014 | if (containsFullScreenElement()) |
2015 | setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false); |
2016 | #endif |
2017 | #if ENABLE(POINTER_LOCK) |
2018 | if (document().page()) |
2019 | document().page()->pointerLockController().elementRemoved(*this); |
2020 | #endif |
2021 | #if ENABLE(POINTER_EVENTS) |
2022 | if (document().page() && RuntimeEnabledFeatures::sharedFeatures().pointerEventsEnabled()) |
2023 | document().page()->pointerCaptureController().elementWasRemoved(*this); |
2024 | #endif |
2025 | |
2026 | setSavedLayerScrollPosition(ScrollPosition()); |
2027 | |
2028 | if (oldParentOfRemovedTree.isInTreeScope()) { |
2029 | TreeScope* oldScope = &oldParentOfRemovedTree.treeScope(); |
2030 | Document* oldDocument = removalType.disconnectedFromDocument ? &oldScope->documentScope() : nullptr; |
2031 | HTMLDocument* oldHTMLDocument = oldDocument && is<HTMLDocument>(*oldDocument) ? &downcast<HTMLDocument>(*oldDocument) : nullptr; |
2032 | if (!removalType.treeScopeChanged) |
2033 | oldScope = nullptr; |
2034 | |
2035 | const AtomString& idValue = getIdAttribute(); |
2036 | if (!idValue.isNull()) { |
2037 | if (oldScope) |
2038 | updateIdForTreeScope(*oldScope, idValue, nullAtom()); |
2039 | if (oldHTMLDocument) |
2040 | updateIdForDocument(*oldHTMLDocument, idValue, nullAtom(), AlwaysUpdateHTMLDocumentNamedItemMaps); |
2041 | } |
2042 | |
2043 | const AtomString& nameValue = getNameAttribute(); |
2044 | if (!nameValue.isNull()) { |
2045 | if (oldScope) |
2046 | updateNameForTreeScope(*oldScope, nameValue, nullAtom()); |
2047 | if (oldHTMLDocument) |
2048 | updateNameForDocument(*oldHTMLDocument, nameValue, nullAtom()); |
2049 | } |
2050 | |
2051 | if (oldScope && hasTagName(labelTag)) { |
2052 | if (oldScope->shouldCacheLabelsByForAttribute()) |
2053 | updateLabel(*oldScope, attributeWithoutSynchronization(forAttr), nullAtom()); |
2054 | } |
2055 | |
2056 | if (oldDocument) { |
2057 | if (oldDocument->cssTarget() == this) |
2058 | oldDocument->setCSSTarget(nullptr); |
2059 | if (UNLIKELY(hasTagName(articleTag))) |
2060 | oldDocument->unregisterArticleElement(*this); |
2061 | } |
2062 | |
2063 | if (removalType.disconnectedFromDocument && UNLIKELY(isDefinedCustomElement())) |
2064 | CustomElementReactionQueue::enqueueDisconnectedCallbackIfNeeded(*this); |
2065 | } |
2066 | |
2067 | if (!parentNode()) { |
2068 | if (auto* shadowRoot = oldParentOfRemovedTree.shadowRoot()) |
2069 | shadowRoot->hostChildElementDidChange(*this); |
2070 | } |
2071 | |
2072 | clearBeforePseudoElement(); |
2073 | clearAfterPseudoElement(); |
2074 | |
2075 | ContainerNode::removedFromAncestor(removalType, oldParentOfRemovedTree); |
2076 | |
2077 | if (hasPendingResources()) |
2078 | document().accessSVGExtensions().removeElementFromPendingResources(*this); |
2079 | |
2080 | RefPtr<Frame> frame = document().frame(); |
2081 | if (auto* timeline = document().existingTimeline()) |
2082 | timeline->elementWasRemoved(*this); |
2083 | if (frame) |
2084 | frame->animation().cancelAnimations(*this); |
2085 | |
2086 | #if PLATFORM(MAC) |
2087 | if (frame && frame->page()) |
2088 | frame->page()->removeLatchingStateForTarget(*this); |
2089 | #endif |
2090 | |
2091 | if (hasRareData() && elementRareData()->hasElementIdentifier()) { |
2092 | document().identifiedElementWasRemovedFromDocument(*this); |
2093 | elementRareData()->setHasElementIdentifier(false); |
2094 | } |
2095 | } |
2096 | |
2097 | ShadowRoot* Element::shadowRoot() const |
2098 | { |
2099 | return hasRareData() ? elementRareData()->shadowRoot() : nullptr; |
2100 | } |
2101 | |
2102 | void Element::addShadowRoot(Ref<ShadowRoot>&& newShadowRoot) |
2103 | { |
2104 | ASSERT(!newShadowRoot->hasChildNodes()); |
2105 | ASSERT(!shadowRoot()); |
2106 | |
2107 | ShadowRoot& shadowRoot = newShadowRoot; |
2108 | { |
2109 | ScriptDisallowedScope::InMainThread scriptDisallowedScope; |
2110 | if (renderer()) |
2111 | RenderTreeUpdater::tearDownRenderers(*this); |
2112 | |
2113 | ensureElementRareData().setShadowRoot(WTFMove(newShadowRoot)); |
2114 | |
2115 | shadowRoot.setHost(this); |
2116 | shadowRoot.setParentTreeScope(treeScope()); |
2117 | |
2118 | #if !ASSERT_DISABLED |
2119 | ASSERT(notifyChildNodeInserted(*this, shadowRoot).isEmpty()); |
2120 | #else |
2121 | notifyChildNodeInserted(*this, shadowRoot); |
2122 | #endif |
2123 | |
2124 | invalidateStyleAndRenderersForSubtree(); |
2125 | } |
2126 | |
2127 | if (shadowRoot.mode() == ShadowRootMode::UserAgent) |
2128 | didAddUserAgentShadowRoot(shadowRoot); |
2129 | |
2130 | InspectorInstrumentation::didPushShadowRoot(*this, shadowRoot); |
2131 | } |
2132 | |
2133 | void Element::removeShadowRoot() |
2134 | { |
2135 | RefPtr<ShadowRoot> oldRoot = shadowRoot(); |
2136 | if (!oldRoot) |
2137 | return; |
2138 | |
2139 | InspectorInstrumentation::willPopShadowRoot(*this, *oldRoot); |
2140 | document().adjustFocusedNodeOnNodeRemoval(*oldRoot); |
2141 | |
2142 | ASSERT(!oldRoot->renderer()); |
2143 | |
2144 | elementRareData()->clearShadowRoot(); |
2145 | |
2146 | oldRoot->setHost(nullptr); |
2147 | oldRoot->setParentTreeScope(document()); |
2148 | } |
2149 | |
2150 | static bool canAttachAuthorShadowRoot(const Element& element) |
2151 | { |
2152 | static NeverDestroyed<HashSet<AtomString>> tagNames = [] { |
2153 | static const HTMLQualifiedName* const tagList[] = { |
2154 | &articleTag.get(), |
2155 | &asideTag.get(), |
2156 | &blockquoteTag.get(), |
2157 | &bodyTag.get(), |
2158 | &divTag.get(), |
2159 | &footerTag.get(), |
2160 | &h1Tag.get(), |
2161 | &h2Tag.get(), |
2162 | &h3Tag.get(), |
2163 | &h4Tag.get(), |
2164 | &h5Tag.get(), |
2165 | &h6Tag.get(), |
2166 | &headerTag.get(), |
2167 | &navTag.get(), |
2168 | &pTag.get(), |
2169 | §ionTag.get(), |
2170 | &spanTag.get() |
2171 | }; |
2172 | HashSet<AtomString> set; |
2173 | for (auto& name : tagList) |
2174 | set.add(name->localName()); |
2175 | return set; |
2176 | }(); |
2177 | |
2178 | if (!is<HTMLElement>(element)) |
2179 | return false; |
2180 | |
2181 | const auto& localName = element.localName(); |
2182 | return tagNames.get().contains(localName) || Document::validateCustomElementName(localName) == CustomElementNameValidationStatus::Valid; |
2183 | } |
2184 | |
2185 | ExceptionOr<ShadowRoot&> Element::attachShadow(const ShadowRootInit& init) |
2186 | { |
2187 | if (!canAttachAuthorShadowRoot(*this)) |
2188 | return Exception { NotSupportedError }; |
2189 | if (shadowRoot()) |
2190 | return Exception { InvalidStateError }; |
2191 | if (init.mode == ShadowRootMode::UserAgent) |
2192 | return Exception { TypeError }; |
2193 | auto shadow = ShadowRoot::create(document(), init.mode); |
2194 | auto& result = shadow.get(); |
2195 | addShadowRoot(WTFMove(shadow)); |
2196 | return result; |
2197 | } |
2198 | |
2199 | ShadowRoot* Element::shadowRootForBindings(JSC::ExecState& state) const |
2200 | { |
2201 | auto* shadow = shadowRoot(); |
2202 | if (!shadow) |
2203 | return nullptr; |
2204 | if (shadow->mode() == ShadowRootMode::Open) |
2205 | return shadow; |
2206 | if (JSC::jsCast<JSDOMGlobalObject*>(state.lexicalGlobalObject())->world().shadowRootIsAlwaysOpen()) |
2207 | return shadow; |
2208 | return nullptr; |
2209 | } |
2210 | |
2211 | RefPtr<ShadowRoot> Element::userAgentShadowRoot() const |
2212 | { |
2213 | ASSERT(!shadowRoot() || shadowRoot()->mode() == ShadowRootMode::UserAgent); |
2214 | return shadowRoot(); |
2215 | } |
2216 | |
2217 | ShadowRoot& Element::ensureUserAgentShadowRoot() |
2218 | { |
2219 | if (auto shadow = userAgentShadowRoot()) |
2220 | return *shadow; |
2221 | auto newShadow = ShadowRoot::create(document(), ShadowRootMode::UserAgent); |
2222 | ShadowRoot& shadow = newShadow; |
2223 | addShadowRoot(WTFMove(newShadow)); |
2224 | return shadow; |
2225 | } |
2226 | |
2227 | void Element::setIsDefinedCustomElement(JSCustomElementInterface& elementInterface) |
2228 | { |
2229 | clearFlag(IsEditingTextOrUndefinedCustomElementFlag); |
2230 | setFlag(IsCustomElement); |
2231 | auto& data = ensureElementRareData(); |
2232 | if (!data.customElementReactionQueue()) |
2233 | data.setCustomElementReactionQueue(std::make_unique<CustomElementReactionQueue>(elementInterface)); |
2234 | invalidateStyleForSubtree(); |
2235 | InspectorInstrumentation::didChangeCustomElementState(*this); |
2236 | } |
2237 | |
2238 | void Element::setIsFailedCustomElement(JSCustomElementInterface&) |
2239 | { |
2240 | ASSERT(isUndefinedCustomElement()); |
2241 | ASSERT(getFlag(IsEditingTextOrUndefinedCustomElementFlag)); |
2242 | clearFlag(IsCustomElement); |
2243 | |
2244 | if (hasRareData()) { |
2245 | // Clear the queue instead of deleting it since this function can be called inside CustomElementReactionQueue::invokeAll during upgrades. |
2246 | if (auto* queue = elementRareData()->customElementReactionQueue()) |
2247 | queue->clear(); |
2248 | } |
2249 | InspectorInstrumentation::didChangeCustomElementState(*this); |
2250 | } |
2251 | |
2252 | void Element::setIsCustomElementUpgradeCandidate() |
2253 | { |
2254 | ASSERT(!getFlag(IsCustomElement)); |
2255 | setFlag(IsCustomElement); |
2256 | setFlag(IsEditingTextOrUndefinedCustomElementFlag); |
2257 | InspectorInstrumentation::didChangeCustomElementState(*this); |
2258 | } |
2259 | |
2260 | void Element::enqueueToUpgrade(JSCustomElementInterface& elementInterface) |
2261 | { |
2262 | ASSERT(!isDefinedCustomElement() && !isFailedCustomElement()); |
2263 | setFlag(IsCustomElement); |
2264 | setFlag(IsEditingTextOrUndefinedCustomElementFlag); |
2265 | InspectorInstrumentation::didChangeCustomElementState(*this); |
2266 | |
2267 | auto& data = ensureElementRareData(); |
2268 | bool alreadyScheduledToUpgrade = data.customElementReactionQueue(); |
2269 | if (!alreadyScheduledToUpgrade) |
2270 | data.setCustomElementReactionQueue(std::make_unique<CustomElementReactionQueue>(elementInterface)); |
2271 | data.customElementReactionQueue()->enqueueElementUpgrade(*this, alreadyScheduledToUpgrade); |
2272 | } |
2273 | |
2274 | CustomElementReactionQueue* Element::reactionQueue() const |
2275 | { |
2276 | ASSERT(isDefinedCustomElement() || isCustomElementUpgradeCandidate()); |
2277 | if (!hasRareData()) |
2278 | return nullptr; |
2279 | return elementRareData()->customElementReactionQueue(); |
2280 | } |
2281 | |
2282 | const AtomString& Element::shadowPseudoId() const |
2283 | { |
2284 | return pseudo(); |
2285 | } |
2286 | |
2287 | bool Element::childTypeAllowed(NodeType type) const |
2288 | { |
2289 | switch (type) { |
2290 | case ELEMENT_NODE: |
2291 | case TEXT_NODE: |
2292 | case COMMENT_NODE: |
2293 | case PROCESSING_INSTRUCTION_NODE: |
2294 | case CDATA_SECTION_NODE: |
2295 | return true; |
2296 | default: |
2297 | break; |
2298 | } |
2299 | return false; |
2300 | } |
2301 | |
2302 | static void checkForEmptyStyleChange(Element& element) |
2303 | { |
2304 | if (element.styleAffectedByEmpty()) { |
2305 | auto* style = element.renderStyle(); |
2306 | if (!style || (!style->emptyState() || element.hasChildNodes())) |
2307 | element.invalidateStyleForSubtree(); |
2308 | } |
2309 | } |
2310 | |
2311 | |
2312 | static void invalidateForForwardPositionalRules(Element& parent, Element* elementAfterChange) |
2313 | { |
2314 | bool childrenAffected = parent.childrenAffectedByForwardPositionalRules(); |
2315 | bool descendantsAffected = parent.descendantsAffectedByForwardPositionalRules(); |
2316 | |
2317 | if (!childrenAffected && !descendantsAffected) |
2318 | return; |
2319 | |
2320 | for (auto* sibling = elementAfterChange; sibling; sibling = sibling->nextElementSibling()) { |
2321 | if (childrenAffected) |
2322 | sibling->invalidateStyleInternal(); |
2323 | if (descendantsAffected) { |
2324 | for (auto* siblingChild = sibling->firstElementChild(); siblingChild; siblingChild = siblingChild->nextElementSibling()) |
2325 | siblingChild->invalidateStyleForSubtreeInternal(); |
2326 | } |
2327 | } |
2328 | } |
2329 | |
2330 | static void invalidateForBackwardPositionalRules(Element& parent, Element* elementBeforeChange) |
2331 | { |
2332 | bool childrenAffected = parent.childrenAffectedByBackwardPositionalRules(); |
2333 | bool descendantsAffected = parent.descendantsAffectedByBackwardPositionalRules(); |
2334 | |
2335 | if (!childrenAffected && !descendantsAffected) |
2336 | return; |
2337 | |
2338 | for (auto* sibling = elementBeforeChange; sibling; sibling = sibling->previousElementSibling()) { |
2339 | if (childrenAffected) |
2340 | sibling->invalidateStyleInternal(); |
2341 | if (descendantsAffected) { |
2342 | for (auto* siblingChild = sibling->firstElementChild(); siblingChild; siblingChild = siblingChild->nextElementSibling()) |
2343 | siblingChild->invalidateStyleForSubtreeInternal(); |
2344 | } |
2345 | } |
2346 | } |
2347 | |
2348 | enum SiblingCheckType { FinishedParsingChildren, SiblingElementRemoved, Other }; |
2349 | |
2350 | static void checkForSiblingStyleChanges(Element& parent, SiblingCheckType checkType, Element* elementBeforeChange, Element* elementAfterChange) |
2351 | { |
2352 | // :empty selector. |
2353 | checkForEmptyStyleChange(parent); |
2354 | |
2355 | if (parent.styleValidity() >= Style::Validity::SubtreeInvalid) |
2356 | return; |
2357 | |
2358 | // :first-child. In the parser callback case, we don't have to check anything, since we were right the first time. |
2359 | // In the DOM case, we only need to do something if |afterChange| is not 0. |
2360 | // |afterChange| is 0 in the parser case, so it works out that we'll skip this block. |
2361 | if (parent.childrenAffectedByFirstChildRules() && elementAfterChange) { |
2362 | // Find our new first child. |
2363 | RefPtr<Element> newFirstElement = ElementTraversal::firstChild(parent); |
2364 | // Find the first element node following |afterChange| |
2365 | |
2366 | // This is the insert/append case. |
2367 | if (newFirstElement != elementAfterChange) { |
2368 | auto* style = elementAfterChange->renderStyle(); |
2369 | if (!style || style->firstChildState()) |
2370 | elementAfterChange->invalidateStyleForSubtreeInternal(); |
2371 | } |
2372 | |
2373 | // We also have to handle node removal. |
2374 | if (checkType == SiblingElementRemoved && newFirstElement == elementAfterChange && newFirstElement) { |
2375 | auto* style = newFirstElement->renderStyle(); |
2376 | if (!style || !style->firstChildState()) |
2377 | newFirstElement->invalidateStyleForSubtreeInternal(); |
2378 | } |
2379 | } |
2380 | |
2381 | // :last-child. In the parser callback case, we don't have to check anything, since we were right the first time. |
2382 | // In the DOM case, we only need to do something if |afterChange| is not 0. |
2383 | if (parent.childrenAffectedByLastChildRules() && elementBeforeChange) { |
2384 | // Find our new last child. |
2385 | RefPtr<Element> newLastElement = ElementTraversal::lastChild(parent); |
2386 | |
2387 | if (newLastElement != elementBeforeChange) { |
2388 | auto* style = elementBeforeChange->renderStyle(); |
2389 | if (!style || style->lastChildState()) |
2390 | elementBeforeChange->invalidateStyleForSubtreeInternal(); |
2391 | } |
2392 | |
2393 | // 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 |
2394 | // to match now. |
2395 | if ((checkType == SiblingElementRemoved || checkType == FinishedParsingChildren) && newLastElement == elementBeforeChange && newLastElement) { |
2396 | auto* style = newLastElement->renderStyle(); |
2397 | if (!style || !style->lastChildState()) |
2398 | newLastElement->invalidateStyleForSubtreeInternal(); |
2399 | } |
2400 | } |
2401 | |
2402 | invalidateForSiblingCombinators(elementAfterChange); |
2403 | |
2404 | invalidateForForwardPositionalRules(parent, elementAfterChange); |
2405 | invalidateForBackwardPositionalRules(parent, elementBeforeChange); |
2406 | } |
2407 | |
2408 | void Element::childrenChanged(const ChildChange& change) |
2409 | { |
2410 | ContainerNode::childrenChanged(change); |
2411 | if (change.source == ChildChangeSource::Parser) |
2412 | checkForEmptyStyleChange(*this); |
2413 | else { |
2414 | SiblingCheckType checkType = change.type == ElementRemoved ? SiblingElementRemoved : Other; |
2415 | checkForSiblingStyleChanges(*this, checkType, change.previousSiblingElement, change.nextSiblingElement); |
2416 | } |
2417 | |
2418 | if (ShadowRoot* shadowRoot = this->shadowRoot()) { |
2419 | switch (change.type) { |
2420 | case ElementInserted: |
2421 | case ElementRemoved: |
2422 | // For elements, we notify shadowRoot in Element::insertedIntoAncestor and Element::removedFromAncestor. |
2423 | break; |
2424 | case AllChildrenRemoved: |
2425 | case AllChildrenReplaced: |
2426 | shadowRoot->didRemoveAllChildrenOfShadowHost(); |
2427 | break; |
2428 | case TextInserted: |
2429 | case TextRemoved: |
2430 | case TextChanged: |
2431 | shadowRoot->didChangeDefaultSlot(); |
2432 | break; |
2433 | case NonContentsChildInserted: |
2434 | case NonContentsChildRemoved: |
2435 | break; |
2436 | } |
2437 | } |
2438 | } |
2439 | |
2440 | void Element::setAttributeEventListener(const AtomString& eventType, const QualifiedName& attributeName, const AtomString& attributeValue) |
2441 | { |
2442 | setAttributeEventListener(eventType, JSLazyEventListener::create(*this, attributeName, attributeValue), mainThreadNormalWorld()); |
2443 | } |
2444 | |
2445 | void Element::removeAllEventListeners() |
2446 | { |
2447 | ContainerNode::removeAllEventListeners(); |
2448 | if (ShadowRoot* shadowRoot = this->shadowRoot()) |
2449 | shadowRoot->removeAllEventListeners(); |
2450 | } |
2451 | |
2452 | void Element::beginParsingChildren() |
2453 | { |
2454 | clearIsParsingChildrenFinished(); |
2455 | } |
2456 | |
2457 | void Element::finishParsingChildren() |
2458 | { |
2459 | ContainerNode::finishParsingChildren(); |
2460 | setIsParsingChildrenFinished(); |
2461 | checkForSiblingStyleChanges(*this, FinishedParsingChildren, ElementTraversal::lastChild(*this), nullptr); |
2462 | } |
2463 | |
2464 | #if ENABLE(TREE_DEBUGGING) |
2465 | void Element::formatForDebugger(char* buffer, unsigned length) const |
2466 | { |
2467 | StringBuilder result; |
2468 | String s; |
2469 | |
2470 | result.append(nodeName()); |
2471 | |
2472 | s = getIdAttribute(); |
2473 | if (s.length() > 0) { |
2474 | if (result.length() > 0) |
2475 | result.appendLiteral("; " ); |
2476 | result.appendLiteral("id=" ); |
2477 | result.append(s); |
2478 | } |
2479 | |
2480 | s = getAttribute(classAttr); |
2481 | if (s.length() > 0) { |
2482 | if (result.length() > 0) |
2483 | result.appendLiteral("; " ); |
2484 | result.appendLiteral("class=" ); |
2485 | result.append(s); |
2486 | } |
2487 | |
2488 | strncpy(buffer, result.toString().utf8().data(), length - 1); |
2489 | } |
2490 | #endif |
2491 | |
2492 | const Vector<RefPtr<Attr>>& Element::attrNodeList() |
2493 | { |
2494 | ASSERT(hasSyntheticAttrChildNodes()); |
2495 | return *attrNodeListForElement(*this); |
2496 | } |
2497 | |
2498 | void Element::attachAttributeNodeIfNeeded(Attr& attrNode) |
2499 | { |
2500 | ASSERT(!attrNode.ownerElement() || attrNode.ownerElement() == this); |
2501 | if (attrNode.ownerElement() == this) |
2502 | return; |
2503 | |
2504 | ScriptDisallowedScope::InMainThread scriptDisallowedScope; |
2505 | |
2506 | attrNode.attachToElement(*this); |
2507 | ensureAttrNodeListForElement(*this).append(&attrNode); |
2508 | } |
2509 | |
2510 | ExceptionOr<RefPtr<Attr>> Element::setAttributeNode(Attr& attrNode) |
2511 | { |
2512 | RefPtr<Attr> oldAttrNode = attrIfExists(attrNode.localName(), shouldIgnoreAttributeCase(*this)); |
2513 | if (oldAttrNode.get() == &attrNode) |
2514 | return oldAttrNode; |
2515 | |
2516 | // InUseAttributeError: Raised if node is an Attr that is already an attribute of another Element object. |
2517 | // The DOM user must explicitly clone Attr nodes to re-use them in other elements. |
2518 | if (attrNode.ownerElement() && attrNode.ownerElement() != this) |
2519 | return Exception { InUseAttributeError }; |
2520 | |
2521 | { |
2522 | ScriptDisallowedScope::InMainThread scriptDisallowedScope; |
2523 | synchronizeAllAttributes(); |
2524 | } |
2525 | |
2526 | auto& elementData = ensureUniqueElementData(); |
2527 | |
2528 | auto existingAttributeIndex = elementData.findAttributeIndexByName(attrNode.localName(), shouldIgnoreAttributeCase(*this)); |
2529 | |
2530 | // Attr::value() will return its 'm_standaloneValue' member any time its Element is set to nullptr. We need to cache this value |
2531 | // before making changes to attrNode's Element connections. |
2532 | auto attrNodeValue = attrNode.value(); |
2533 | |
2534 | if (existingAttributeIndex == ElementData::attributeNotFound) { |
2535 | attachAttributeNodeIfNeeded(attrNode); |
2536 | setAttributeInternal(elementData.findAttributeIndexByName(attrNode.qualifiedName()), attrNode.qualifiedName(), attrNodeValue, NotInSynchronizationOfLazyAttribute); |
2537 | } else { |
2538 | const Attribute& attribute = attributeAt(existingAttributeIndex); |
2539 | if (oldAttrNode) |
2540 | detachAttrNodeFromElementWithValue(oldAttrNode.get(), attribute.value()); |
2541 | else |
2542 | oldAttrNode = Attr::create(document(), attrNode.qualifiedName(), attribute.value()); |
2543 | |
2544 | attachAttributeNodeIfNeeded(attrNode); |
2545 | |
2546 | if (attribute.name().matches(attrNode.qualifiedName())) |
2547 | setAttributeInternal(existingAttributeIndex, attrNode.qualifiedName(), attrNodeValue, NotInSynchronizationOfLazyAttribute); |
2548 | else { |
2549 | removeAttributeInternal(existingAttributeIndex, NotInSynchronizationOfLazyAttribute); |
2550 | setAttributeInternal(ensureUniqueElementData().findAttributeIndexByName(attrNode.qualifiedName()), attrNode.qualifiedName(), attrNodeValue, NotInSynchronizationOfLazyAttribute); |
2551 | } |
2552 | } |
2553 | |
2554 | return oldAttrNode; |
2555 | } |
2556 | |
2557 | ExceptionOr<RefPtr<Attr>> Element::setAttributeNodeNS(Attr& attrNode) |
2558 | { |
2559 | RefPtr<Attr> oldAttrNode = attrIfExists(attrNode.qualifiedName()); |
2560 | if (oldAttrNode.get() == &attrNode) |
2561 | return oldAttrNode; |
2562 | |
2563 | // InUseAttributeError: Raised if node is an Attr that is already an attribute of another Element object. |
2564 | // The DOM user must explicitly clone Attr nodes to re-use them in other elements. |
2565 | if (attrNode.ownerElement() && attrNode.ownerElement() != this) |
2566 | return Exception { InUseAttributeError }; |
2567 | |
2568 | // Attr::value() will return its 'm_standaloneValue' member any time its Element is set to nullptr. We need to cache this value |
2569 | // before making changes to attrNode's Element connections. |
2570 | auto attrNodeValue = attrNode.value(); |
2571 | unsigned index = 0; |
2572 | { |
2573 | ScriptDisallowedScope::InMainThread scriptDisallowedScope; |
2574 | synchronizeAllAttributes(); |
2575 | auto& elementData = ensureUniqueElementData(); |
2576 | |
2577 | index = elementData.findAttributeIndexByName(attrNode.qualifiedName()); |
2578 | |
2579 | if (index != ElementData::attributeNotFound) { |
2580 | if (oldAttrNode) |
2581 | detachAttrNodeFromElementWithValue(oldAttrNode.get(), elementData.attributeAt(index).value()); |
2582 | else |
2583 | oldAttrNode = Attr::create(document(), attrNode.qualifiedName(), elementData.attributeAt(index).value()); |
2584 | } |
2585 | } |
2586 | |
2587 | attachAttributeNodeIfNeeded(attrNode); |
2588 | setAttributeInternal(index, attrNode.qualifiedName(), attrNodeValue, NotInSynchronizationOfLazyAttribute); |
2589 | |
2590 | return oldAttrNode; |
2591 | } |
2592 | |
2593 | ExceptionOr<Ref<Attr>> Element::removeAttributeNode(Attr& attr) |
2594 | { |
2595 | if (attr.ownerElement() != this) |
2596 | return Exception { NotFoundError }; |
2597 | |
2598 | ASSERT(&document() == &attr.document()); |
2599 | |
2600 | synchronizeAllAttributes(); |
2601 | |
2602 | if (!m_elementData) |
2603 | return Exception { NotFoundError }; |
2604 | |
2605 | auto existingAttributeIndex = m_elementData->findAttributeIndexByName(attr.qualifiedName()); |
2606 | if (existingAttributeIndex == ElementData::attributeNotFound) |
2607 | return Exception { NotFoundError }; |
2608 | |
2609 | Ref<Attr> oldAttrNode { attr }; |
2610 | |
2611 | detachAttrNodeFromElementWithValue(&attr, m_elementData->attributeAt(existingAttributeIndex).value()); |
2612 | removeAttributeInternal(existingAttributeIndex, NotInSynchronizationOfLazyAttribute); |
2613 | |
2614 | return oldAttrNode; |
2615 | } |
2616 | |
2617 | ExceptionOr<QualifiedName> Element::parseAttributeName(const AtomString& namespaceURI, const AtomString& qualifiedName) |
2618 | { |
2619 | auto parseResult = Document::parseQualifiedName(namespaceURI, qualifiedName); |
2620 | if (parseResult.hasException()) |
2621 | return parseResult.releaseException(); |
2622 | QualifiedName parsedAttributeName { parseResult.releaseReturnValue() }; |
2623 | if (!Document::hasValidNamespaceForAttributes(parsedAttributeName)) |
2624 | return Exception { NamespaceError }; |
2625 | return parsedAttributeName; |
2626 | } |
2627 | |
2628 | ExceptionOr<void> Element::setAttributeNS(const AtomString& namespaceURI, const AtomString& qualifiedName, const AtomString& value) |
2629 | { |
2630 | auto result = parseAttributeName(namespaceURI, qualifiedName); |
2631 | if (result.hasException()) |
2632 | return result.releaseException(); |
2633 | setAttribute(result.releaseReturnValue(), value); |
2634 | return { }; |
2635 | } |
2636 | |
2637 | void Element::removeAttributeInternal(unsigned index, SynchronizationOfLazyAttribute inSynchronizationOfLazyAttribute) |
2638 | { |
2639 | ASSERT_WITH_SECURITY_IMPLICATION(index < attributeCount()); |
2640 | |
2641 | UniqueElementData& elementData = ensureUniqueElementData(); |
2642 | |
2643 | QualifiedName name = elementData.attributeAt(index).name(); |
2644 | AtomString valueBeingRemoved = elementData.attributeAt(index).value(); |
2645 | |
2646 | if (RefPtr<Attr> attrNode = attrIfExists(name)) |
2647 | detachAttrNodeFromElementWithValue(attrNode.get(), elementData.attributeAt(index).value()); |
2648 | |
2649 | if (inSynchronizationOfLazyAttribute) { |
2650 | elementData.removeAttribute(index); |
2651 | return; |
2652 | } |
2653 | |
2654 | ASSERT(!valueBeingRemoved.isNull()); |
2655 | willModifyAttribute(name, valueBeingRemoved, nullAtom()); |
2656 | { |
2657 | Style::AttributeChangeInvalidation styleInvalidation(*this, name, valueBeingRemoved, nullAtom()); |
2658 | elementData.removeAttribute(index); |
2659 | } |
2660 | |
2661 | didRemoveAttribute(name, valueBeingRemoved); |
2662 | } |
2663 | |
2664 | void Element::addAttributeInternal(const QualifiedName& name, const AtomString& value, SynchronizationOfLazyAttribute inSynchronizationOfLazyAttribute) |
2665 | { |
2666 | if (inSynchronizationOfLazyAttribute) { |
2667 | ensureUniqueElementData().addAttribute(name, value); |
2668 | return; |
2669 | } |
2670 | |
2671 | willModifyAttribute(name, nullAtom(), value); |
2672 | { |
2673 | Style::AttributeChangeInvalidation styleInvalidation(*this, name, nullAtom(), value); |
2674 | ensureUniqueElementData().addAttribute(name, value); |
2675 | } |
2676 | didAddAttribute(name, value); |
2677 | } |
2678 | |
2679 | bool Element::removeAttribute(const AtomString& qualifiedName) |
2680 | { |
2681 | if (!elementData()) |
2682 | return false; |
2683 | |
2684 | AtomString caseAdjustedQualifiedName = shouldIgnoreAttributeCase(*this) ? qualifiedName.convertToASCIILowercase() : qualifiedName; |
2685 | unsigned index = elementData()->findAttributeIndexByName(caseAdjustedQualifiedName, false); |
2686 | if (index == ElementData::attributeNotFound) { |
2687 | if (UNLIKELY(caseAdjustedQualifiedName == styleAttr) && elementData()->styleAttributeIsDirty() && is<StyledElement>(*this)) |
2688 | downcast<StyledElement>(*this).removeAllInlineStyleProperties(); |
2689 | return false; |
2690 | } |
2691 | |
2692 | removeAttributeInternal(index, NotInSynchronizationOfLazyAttribute); |
2693 | return true; |
2694 | } |
2695 | |
2696 | bool Element::removeAttributeNS(const AtomString& namespaceURI, const AtomString& localName) |
2697 | { |
2698 | return removeAttribute(QualifiedName(nullAtom(), localName, namespaceURI)); |
2699 | } |
2700 | |
2701 | RefPtr<Attr> Element::getAttributeNode(const AtomString& qualifiedName) |
2702 | { |
2703 | if (!elementData()) |
2704 | return nullptr; |
2705 | synchronizeAttribute(qualifiedName); |
2706 | const Attribute* attribute = elementData()->findAttributeByName(qualifiedName, shouldIgnoreAttributeCase(*this)); |
2707 | if (!attribute) |
2708 | return nullptr; |
2709 | return ensureAttr(attribute->name()); |
2710 | } |
2711 | |
2712 | RefPtr<Attr> Element::getAttributeNodeNS(const AtomString& namespaceURI, const AtomString& localName) |
2713 | { |
2714 | if (!elementData()) |
2715 | return 0; |
2716 | QualifiedName qName(nullAtom(), localName, namespaceURI); |
2717 | synchronizeAttribute(qName); |
2718 | const Attribute* attribute = elementData()->findAttributeByName(qName); |
2719 | if (!attribute) |
2720 | return 0; |
2721 | return ensureAttr(attribute->name()); |
2722 | } |
2723 | |
2724 | bool Element::hasAttribute(const AtomString& qualifiedName) const |
2725 | { |
2726 | if (!elementData()) |
2727 | return false; |
2728 | synchronizeAttribute(qualifiedName); |
2729 | return elementData()->findAttributeByName(qualifiedName, shouldIgnoreAttributeCase(*this)); |
2730 | } |
2731 | |
2732 | bool Element::hasAttributeNS(const AtomString& namespaceURI, const AtomString& localName) const |
2733 | { |
2734 | if (!elementData()) |
2735 | return false; |
2736 | QualifiedName qName(nullAtom(), localName, namespaceURI); |
2737 | synchronizeAttribute(qName); |
2738 | return elementData()->findAttributeByName(qName); |
2739 | } |
2740 | |
2741 | void Element::focus(bool restorePreviousSelection, FocusDirection direction) |
2742 | { |
2743 | if (!isConnected()) |
2744 | return; |
2745 | |
2746 | if (document().focusedElement() == this) { |
2747 | if (document().page()) |
2748 | document().page()->chrome().client().elementDidRefocus(*this); |
2749 | |
2750 | return; |
2751 | } |
2752 | |
2753 | // If the stylesheets have already been loaded we can reliably check isFocusable. |
2754 | // If not, we continue and set the focused node on the focus controller below so |
2755 | // that it can be updated soon after attach. |
2756 | if (document().haveStylesheetsLoaded()) { |
2757 | document().updateStyleIfNeeded(); |
2758 | if (!isFocusable()) |
2759 | return; |
2760 | } |
2761 | |
2762 | if (!supportsFocus()) |
2763 | return; |
2764 | |
2765 | RefPtr<Node> protect; |
2766 | if (Page* page = document().page()) { |
2767 | // Focus and change event handlers can cause us to lose our last ref. |
2768 | // If a focus event handler changes the focus to a different node it |
2769 | // does not make sense to continue and update appearence. |
2770 | protect = this; |
2771 | if (!page->focusController().setFocusedElement(this, *document().frame(), direction)) |
2772 | return; |
2773 | } |
2774 | |
2775 | SelectionRevealMode revealMode = SelectionRevealMode::Reveal; |
2776 | #if PLATFORM(IOS_FAMILY) |
2777 | // Focusing a form element triggers animation in UIKit to scroll to the right position. |
2778 | // Calling updateFocusAppearance() would generate an unnecessary call to ScrollView::setScrollPosition(), |
2779 | // which would jump us around during this animation. See <rdar://problem/6699741>. |
2780 | bool isFormControl = is<HTMLFormControlElement>(*this); |
2781 | if (isFormControl) |
2782 | revealMode = SelectionRevealMode::RevealUpToMainFrame; |
2783 | #endif |
2784 | |
2785 | auto target = focusAppearanceUpdateTarget(); |
2786 | if (!target) |
2787 | return; |
2788 | |
2789 | target->updateFocusAppearance(restorePreviousSelection ? SelectionRestorationMode::Restore : SelectionRestorationMode::SetDefault, revealMode); |
2790 | } |
2791 | |
2792 | RefPtr<Element> Element::focusAppearanceUpdateTarget() |
2793 | { |
2794 | return this; |
2795 | } |
2796 | |
2797 | void Element::updateFocusAppearance(SelectionRestorationMode, SelectionRevealMode revealMode) |
2798 | { |
2799 | if (isRootEditableElement()) { |
2800 | // Keep frame alive in this method, since setSelection() may release the last reference to |frame|. |
2801 | RefPtr<Frame> frame = document().frame(); |
2802 | if (!frame) |
2803 | return; |
2804 | |
2805 | // When focusing an editable element in an iframe, don't reset the selection if it already contains a selection. |
2806 | if (this == frame->selection().selection().rootEditableElement()) |
2807 | return; |
2808 | |
2809 | // FIXME: We should restore the previous selection if there is one. |
2810 | VisibleSelection newSelection = VisibleSelection(firstPositionInOrBeforeNode(this), DOWNSTREAM); |
2811 | |
2812 | if (frame->selection().shouldChangeSelection(newSelection)) { |
2813 | frame->selection().setSelection(newSelection, FrameSelection::defaultSetSelectionOptions(), Element::defaultFocusTextStateChangeIntent()); |
2814 | frame->selection().revealSelection(revealMode); |
2815 | return; |
2816 | } |
2817 | } |
2818 | |
2819 | if (RefPtr<FrameView> view = document().view()) |
2820 | view->scheduleScrollToFocusedElement(revealMode); |
2821 | } |
2822 | |
2823 | void Element::blur() |
2824 | { |
2825 | if (treeScope().focusedElementInScope() == this) { |
2826 | if (Frame* frame = document().frame()) |
2827 | frame->page()->focusController().setFocusedElement(nullptr, *frame); |
2828 | else |
2829 | document().setFocusedElement(nullptr); |
2830 | } |
2831 | } |
2832 | |
2833 | void Element::dispatchFocusInEvent(const AtomString& eventType, RefPtr<Element>&& oldFocusedElement) |
2834 | { |
2835 | ASSERT_WITH_SECURITY_IMPLICATION(ScriptDisallowedScope::InMainThread::isScriptAllowed()); |
2836 | ASSERT(eventType == eventNames().focusinEvent || eventType == eventNames().DOMFocusInEvent); |
2837 | dispatchScopedEvent(FocusEvent::create(eventType, Event::CanBubble::Yes, Event::IsCancelable::No, document().windowProxy(), 0, WTFMove(oldFocusedElement))); |
2838 | } |
2839 | |
2840 | void Element::dispatchFocusOutEvent(const AtomString& eventType, RefPtr<Element>&& newFocusedElement) |
2841 | { |
2842 | ASSERT_WITH_SECURITY_IMPLICATION(ScriptDisallowedScope::InMainThread::isScriptAllowed()); |
2843 | ASSERT(eventType == eventNames().focusoutEvent || eventType == eventNames().DOMFocusOutEvent); |
2844 | dispatchScopedEvent(FocusEvent::create(eventType, Event::CanBubble::Yes, Event::IsCancelable::No, document().windowProxy(), 0, WTFMove(newFocusedElement))); |
2845 | } |
2846 | |
2847 | void Element::dispatchFocusEvent(RefPtr<Element>&& oldFocusedElement, FocusDirection) |
2848 | { |
2849 | if (auto* page = document().page()) |
2850 | page->chrome().client().elementDidFocus(*this); |
2851 | dispatchEvent(FocusEvent::create(eventNames().focusEvent, Event::CanBubble::No, Event::IsCancelable::No, document().windowProxy(), 0, WTFMove(oldFocusedElement))); |
2852 | } |
2853 | |
2854 | void Element::dispatchBlurEvent(RefPtr<Element>&& newFocusedElement) |
2855 | { |
2856 | if (auto* page = document().page()) |
2857 | page->chrome().client().elementDidBlur(*this); |
2858 | dispatchEvent(FocusEvent::create(eventNames().blurEvent, Event::CanBubble::No, Event::IsCancelable::No, document().windowProxy(), 0, WTFMove(newFocusedElement))); |
2859 | } |
2860 | |
2861 | void Element::dispatchWebKitImageReadyEventForTesting() |
2862 | { |
2863 | if (document().settings().webkitImageReadyEventEnabled()) |
2864 | dispatchEvent(Event::create("webkitImageFrameReady" , Event::CanBubble::Yes, Event::IsCancelable::Yes)); |
2865 | } |
2866 | |
2867 | bool Element::dispatchMouseForceWillBegin() |
2868 | { |
2869 | #if ENABLE(MOUSE_FORCE_EVENTS) |
2870 | if (!document().hasListenerType(Document::FORCEWILLBEGIN_LISTENER)) |
2871 | return false; |
2872 | |
2873 | Frame* frame = document().frame(); |
2874 | if (!frame) |
2875 | return false; |
2876 | |
2877 | PlatformMouseEvent platformMouseEvent { frame->eventHandler().lastKnownMousePosition(), frame->eventHandler().lastKnownMouseGlobalPosition(), NoButton, PlatformEvent::NoType, 1, false, false, false, false, WallTime::now(), ForceAtClick, NoTap }; |
2878 | auto mouseForceWillBeginEvent = MouseEvent::create(eventNames().webkitmouseforcewillbeginEvent, document().windowProxy(), platformMouseEvent, 0, nullptr); |
2879 | mouseForceWillBeginEvent->setTarget(this); |
2880 | dispatchEvent(mouseForceWillBeginEvent); |
2881 | |
2882 | if (mouseForceWillBeginEvent->defaultHandled() || mouseForceWillBeginEvent->defaultPrevented()) |
2883 | return true; |
2884 | #endif |
2885 | |
2886 | return false; |
2887 | } |
2888 | |
2889 | ExceptionOr<void> Element::mergeWithNextTextNode(Text& node) |
2890 | { |
2891 | auto* next = node.nextSibling(); |
2892 | if (!is<Text>(next)) |
2893 | return { }; |
2894 | Ref<Text> textNext { downcast<Text>(*next) }; |
2895 | node.appendData(textNext->data()); |
2896 | return textNext->remove(); |
2897 | } |
2898 | |
2899 | String Element::innerHTML() const |
2900 | { |
2901 | return serializeFragment(*this, SerializedNodes::SubtreesOfChildren); |
2902 | } |
2903 | |
2904 | String Element::outerHTML() const |
2905 | { |
2906 | return serializeFragment(*this, SerializedNodes::SubtreeIncludingNode); |
2907 | } |
2908 | |
2909 | ExceptionOr<void> Element::setOuterHTML(const String& html) |
2910 | { |
2911 | auto* parentElement = this->parentElement(); |
2912 | if (!is<HTMLElement>(parentElement)) |
2913 | return Exception { NoModificationAllowedError }; |
2914 | |
2915 | Ref<HTMLElement> parent = downcast<HTMLElement>(*parentElement); |
2916 | RefPtr<Node> prev = previousSibling(); |
2917 | RefPtr<Node> next = nextSibling(); |
2918 | |
2919 | auto fragment = createFragmentForInnerOuterHTML(parent, html, AllowScriptingContent); |
2920 | if (fragment.hasException()) |
2921 | return fragment.releaseException(); |
2922 | |
2923 | auto replaceResult = parent->replaceChild(fragment.releaseReturnValue().get(), *this); |
2924 | if (replaceResult.hasException()) |
2925 | return replaceResult.releaseException(); |
2926 | |
2927 | RefPtr<Node> node = next ? next->previousSibling() : nullptr; |
2928 | if (is<Text>(node)) { |
2929 | auto result = mergeWithNextTextNode(downcast<Text>(*node)); |
2930 | if (result.hasException()) |
2931 | return result.releaseException(); |
2932 | } |
2933 | if (is<Text>(prev)) { |
2934 | auto result = mergeWithNextTextNode(downcast<Text>(*prev)); |
2935 | if (result.hasException()) |
2936 | return result.releaseException(); |
2937 | } |
2938 | return { }; |
2939 | } |
2940 | |
2941 | |
2942 | ExceptionOr<void> Element::setInnerHTML(const String& html) |
2943 | { |
2944 | auto fragment = createFragmentForInnerOuterHTML(*this, html, AllowScriptingContent); |
2945 | if (fragment.hasException()) |
2946 | return fragment.releaseException(); |
2947 | |
2948 | ContainerNode* container; |
2949 | if (!is<HTMLTemplateElement>(*this)) |
2950 | container = this; |
2951 | else |
2952 | container = &downcast<HTMLTemplateElement>(*this).content(); |
2953 | |
2954 | return replaceChildrenWithFragment(*container, fragment.releaseReturnValue()); |
2955 | } |
2956 | |
2957 | String Element::innerText() |
2958 | { |
2959 | // We need to update layout, since plainText uses line boxes in the render tree. |
2960 | document().updateLayoutIgnorePendingStylesheets(); |
2961 | |
2962 | if (!renderer()) |
2963 | return textContent(true); |
2964 | |
2965 | return plainText(rangeOfContents(*this).ptr()); |
2966 | } |
2967 | |
2968 | String Element::outerText() |
2969 | { |
2970 | // Getting outerText is the same as getting innerText, only |
2971 | // setting is different. You would think this should get the plain |
2972 | // text for the outer range, but this is wrong, <br> for instance |
2973 | // would return different values for inner and outer text by such |
2974 | // a rule, but it doesn't in WinIE, and we want to match that. |
2975 | return innerText(); |
2976 | } |
2977 | |
2978 | String Element::title() const |
2979 | { |
2980 | return String(); |
2981 | } |
2982 | |
2983 | const AtomString& Element::pseudo() const |
2984 | { |
2985 | return attributeWithoutSynchronization(pseudoAttr); |
2986 | } |
2987 | |
2988 | void Element::setPseudo(const AtomString& value) |
2989 | { |
2990 | setAttributeWithoutSynchronization(pseudoAttr, value); |
2991 | } |
2992 | |
2993 | LayoutSize Element::minimumSizeForResizing() const |
2994 | { |
2995 | return hasRareData() ? elementRareData()->minimumSizeForResizing() : defaultMinimumSizeForResizing(); |
2996 | } |
2997 | |
2998 | void Element::setMinimumSizeForResizing(const LayoutSize& size) |
2999 | { |
3000 | if (!hasRareData() && size == defaultMinimumSizeForResizing()) |
3001 | return; |
3002 | ensureElementRareData().setMinimumSizeForResizing(size); |
3003 | } |
3004 | |
3005 | void Element::willBecomeFullscreenElement() |
3006 | { |
3007 | for (auto& child : descendantsOfType<Element>(*this)) |
3008 | child.ancestorWillEnterFullscreen(); |
3009 | } |
3010 | |
3011 | static PseudoElement* beforeOrAfterPseudoElement(Element& host, PseudoId pseudoElementSpecifier) |
3012 | { |
3013 | switch (pseudoElementSpecifier) { |
3014 | case PseudoId::Before: |
3015 | return host.beforePseudoElement(); |
3016 | case PseudoId::After: |
3017 | return host.afterPseudoElement(); |
3018 | default: |
3019 | return nullptr; |
3020 | } |
3021 | } |
3022 | |
3023 | const RenderStyle* Element::existingComputedStyle() const |
3024 | { |
3025 | if (hasRareData()) { |
3026 | if (auto* style = elementRareData()->computedStyle()) |
3027 | return style; |
3028 | } |
3029 | |
3030 | return renderStyle(); |
3031 | } |
3032 | |
3033 | const RenderStyle* Element::renderOrDisplayContentsStyle() const |
3034 | { |
3035 | if (auto* style = renderStyle()) |
3036 | return style; |
3037 | |
3038 | if (!hasRareData()) |
3039 | return nullptr; |
3040 | auto* style = elementRareData()->computedStyle(); |
3041 | if (style && style->display() == DisplayType::Contents) |
3042 | return style; |
3043 | |
3044 | return nullptr; |
3045 | } |
3046 | |
3047 | const RenderStyle& Element::resolveComputedStyle() |
3048 | { |
3049 | ASSERT(isConnected()); |
3050 | ASSERT(!existingComputedStyle()); |
3051 | |
3052 | Deque<RefPtr<Element>, 32> elementsRequiringComputedStyle({ this }); |
3053 | const RenderStyle* computedStyle = nullptr; |
3054 | |
3055 | // Collect ancestors until we find one that has style. |
3056 | auto composedAncestors = composedTreeAncestors(*this); |
3057 | for (auto& ancestor : composedAncestors) { |
3058 | if (auto* existingStyle = ancestor.existingComputedStyle()) { |
3059 | computedStyle = existingStyle; |
3060 | break; |
3061 | } |
3062 | elementsRequiringComputedStyle.prepend(&ancestor); |
3063 | } |
3064 | |
3065 | // Resolve and cache styles starting from the most distant ancestor. |
3066 | for (auto& element : elementsRequiringComputedStyle) { |
3067 | auto style = document().styleForElementIgnoringPendingStylesheets(*element, computedStyle); |
3068 | computedStyle = style.get(); |
3069 | ElementRareData& rareData = element->ensureElementRareData(); |
3070 | rareData.setComputedStyle(WTFMove(style)); |
3071 | } |
3072 | |
3073 | return *computedStyle; |
3074 | } |
3075 | |
3076 | const RenderStyle& Element::resolvePseudoElementStyle(PseudoId pseudoElementSpecifier) |
3077 | { |
3078 | ASSERT(!isPseudoElement()); |
3079 | |
3080 | auto* parentStyle = existingComputedStyle(); |
3081 | ASSERT(parentStyle); |
3082 | ASSERT(!parentStyle->getCachedPseudoStyle(pseudoElementSpecifier)); |
3083 | |
3084 | auto style = document().styleForElementIgnoringPendingStylesheets(*this, parentStyle, pseudoElementSpecifier); |
3085 | if (!style) { |
3086 | style = RenderStyle::createPtr(); |
3087 | style->inheritFrom(*parentStyle); |
3088 | style->setStyleType(pseudoElementSpecifier); |
3089 | } |
3090 | |
3091 | auto* computedStyle = style.get(); |
3092 | const_cast<RenderStyle*>(parentStyle)->addCachedPseudoStyle(WTFMove(style)); |
3093 | return *computedStyle; |
3094 | } |
3095 | |
3096 | const RenderStyle* Element::computedStyle(PseudoId pseudoElementSpecifier) |
3097 | { |
3098 | if (!isConnected()) |
3099 | return nullptr; |
3100 | |
3101 | if (PseudoElement* pseudoElement = beforeOrAfterPseudoElement(*this, pseudoElementSpecifier)) |
3102 | return pseudoElement->computedStyle(); |
3103 | |
3104 | auto* style = existingComputedStyle(); |
3105 | if (!style) |
3106 | style = &resolveComputedStyle(); |
3107 | |
3108 | if (pseudoElementSpecifier != PseudoId::None) { |
3109 | if (auto* cachedPseudoStyle = style->getCachedPseudoStyle(pseudoElementSpecifier)) |
3110 | return cachedPseudoStyle; |
3111 | return &resolvePseudoElementStyle(pseudoElementSpecifier); |
3112 | } |
3113 | |
3114 | return style; |
3115 | } |
3116 | |
3117 | bool Element::needsStyleInvalidation() const |
3118 | { |
3119 | if (!inRenderedDocument()) |
3120 | return false; |
3121 | if (styleValidity() >= Style::Validity::SubtreeInvalid) |
3122 | return false; |
3123 | if (document().hasPendingFullStyleRebuild()) |
3124 | return false; |
3125 | |
3126 | return true; |
3127 | } |
3128 | |
3129 | void Element::setStyleAffectedByEmpty() |
3130 | { |
3131 | ensureElementRareData().setStyleAffectedByEmpty(true); |
3132 | } |
3133 | |
3134 | void Element::setStyleAffectedByFocusWithin() |
3135 | { |
3136 | ensureElementRareData().setStyleAffectedByFocusWithin(true); |
3137 | } |
3138 | |
3139 | void Element::setStyleAffectedByActive() |
3140 | { |
3141 | ensureElementRareData().setStyleAffectedByActive(true); |
3142 | } |
3143 | |
3144 | void Element::setChildrenAffectedByDrag() |
3145 | { |
3146 | ensureElementRareData().setChildrenAffectedByDrag(true); |
3147 | } |
3148 | |
3149 | void Element::setChildrenAffectedByForwardPositionalRules() |
3150 | { |
3151 | ensureElementRareData().setChildrenAffectedByForwardPositionalRules(true); |
3152 | } |
3153 | |
3154 | void Element::setDescendantsAffectedByForwardPositionalRules() |
3155 | { |
3156 | ensureElementRareData().setDescendantsAffectedByForwardPositionalRules(true); |
3157 | } |
3158 | |
3159 | void Element::setChildrenAffectedByBackwardPositionalRules() |
3160 | { |
3161 | ensureElementRareData().setChildrenAffectedByBackwardPositionalRules(true); |
3162 | } |
3163 | |
3164 | void Element::setDescendantsAffectedByBackwardPositionalRules() |
3165 | { |
3166 | ensureElementRareData().setDescendantsAffectedByBackwardPositionalRules(true); |
3167 | } |
3168 | |
3169 | void Element::setChildrenAffectedByPropertyBasedBackwardPositionalRules() |
3170 | { |
3171 | ensureElementRareData().setChildrenAffectedByPropertyBasedBackwardPositionalRules(true); |
3172 | } |
3173 | |
3174 | void Element::setChildIndex(unsigned index) |
3175 | { |
3176 | ElementRareData& rareData = ensureElementRareData(); |
3177 | rareData.setChildIndex(index); |
3178 | } |
3179 | |
3180 | bool Element::hasFlagsSetDuringStylingOfChildren() const |
3181 | { |
3182 | if (childrenAffectedByHover() || childrenAffectedByFirstChildRules() || childrenAffectedByLastChildRules()) |
3183 | return true; |
3184 | |
3185 | if (!hasRareData()) |
3186 | return false; |
3187 | return rareDataStyleAffectedByActive() |
3188 | || rareDataChildrenAffectedByDrag() |
3189 | || rareDataChildrenAffectedByForwardPositionalRules() |
3190 | || rareDataDescendantsAffectedByForwardPositionalRules() |
3191 | || rareDataChildrenAffectedByBackwardPositionalRules() |
3192 | || rareDataDescendantsAffectedByBackwardPositionalRules() |
3193 | || rareDataChildrenAffectedByPropertyBasedBackwardPositionalRules(); |
3194 | } |
3195 | |
3196 | bool Element::rareDataStyleAffectedByEmpty() const |
3197 | { |
3198 | ASSERT(hasRareData()); |
3199 | return elementRareData()->styleAffectedByEmpty(); |
3200 | } |
3201 | |
3202 | bool Element::rareDataStyleAffectedByFocusWithin() const |
3203 | { |
3204 | ASSERT(hasRareData()); |
3205 | return elementRareData()->styleAffectedByFocusWithin(); |
3206 | } |
3207 | |
3208 | bool Element::rareDataStyleAffectedByActive() const |
3209 | { |
3210 | ASSERT(hasRareData()); |
3211 | return elementRareData()->styleAffectedByActive(); |
3212 | } |
3213 | |
3214 | bool Element::rareDataChildrenAffectedByDrag() const |
3215 | { |
3216 | ASSERT(hasRareData()); |
3217 | return elementRareData()->childrenAffectedByDrag(); |
3218 | } |
3219 | |
3220 | bool Element::rareDataChildrenAffectedByForwardPositionalRules() const |
3221 | { |
3222 | ASSERT(hasRareData()); |
3223 | return elementRareData()->childrenAffectedByForwardPositionalRules(); |
3224 | } |
3225 | |
3226 | bool Element::rareDataDescendantsAffectedByForwardPositionalRules() const |
3227 | { |
3228 | ASSERT(hasRareData()); |
3229 | return elementRareData()->descendantsAffectedByForwardPositionalRules(); |
3230 | } |
3231 | |
3232 | bool Element::rareDataChildrenAffectedByBackwardPositionalRules() const |
3233 | { |
3234 | ASSERT(hasRareData()); |
3235 | return elementRareData()->childrenAffectedByBackwardPositionalRules(); |
3236 | } |
3237 | |
3238 | bool Element::rareDataDescendantsAffectedByBackwardPositionalRules() const |
3239 | { |
3240 | ASSERT(hasRareData()); |
3241 | return elementRareData()->descendantsAffectedByBackwardPositionalRules(); |
3242 | } |
3243 | |
3244 | bool Element::rareDataChildrenAffectedByPropertyBasedBackwardPositionalRules() const |
3245 | { |
3246 | ASSERT(hasRareData()); |
3247 | return elementRareData()->childrenAffectedByPropertyBasedBackwardPositionalRules(); |
3248 | } |
3249 | |
3250 | unsigned Element::rareDataChildIndex() const |
3251 | { |
3252 | ASSERT(hasRareData()); |
3253 | return elementRareData()->childIndex(); |
3254 | } |
3255 | |
3256 | AtomString Element::computeInheritedLanguage() const |
3257 | { |
3258 | if (const ElementData* elementData = this->elementData()) { |
3259 | if (const Attribute* attribute = elementData->findLanguageAttribute()) |
3260 | return attribute->value(); |
3261 | } |
3262 | |
3263 | // The language property is inherited, so we iterate over the parents to find the first language. |
3264 | const Node* currentNode = this; |
3265 | while ((currentNode = currentNode->parentNode())) { |
3266 | if (is<Element>(*currentNode)) { |
3267 | if (const ElementData* elementData = downcast<Element>(*currentNode).elementData()) { |
3268 | if (const Attribute* attribute = elementData->findLanguageAttribute()) |
3269 | return attribute->value(); |
3270 | } |
3271 | } else if (is<Document>(*currentNode)) { |
3272 | // checking the MIME content-language |
3273 | return downcast<Document>(*currentNode).contentLanguage(); |
3274 | } |
3275 | } |
3276 | |
3277 | return nullAtom(); |
3278 | } |
3279 | |
3280 | Locale& Element::locale() const |
3281 | { |
3282 | return document().getCachedLocale(computeInheritedLanguage()); |
3283 | } |
3284 | |
3285 | void Element::normalizeAttributes() |
3286 | { |
3287 | if (!hasAttributes()) |
3288 | return; |
3289 | |
3290 | auto* attrNodeList = attrNodeListForElement(*this); |
3291 | if (!attrNodeList) |
3292 | return; |
3293 | |
3294 | // Copy the Attr Vector because Node::normalize() can fire synchronous JS |
3295 | // events (e.g. DOMSubtreeModified) and a JS listener could add / remove |
3296 | // attributes while we are iterating. |
3297 | auto copyOfAttrNodeList = *attrNodeList; |
3298 | for (auto& attrNode : copyOfAttrNodeList) |
3299 | attrNode->normalize(); |
3300 | } |
3301 | |
3302 | PseudoElement* Element::beforePseudoElement() const |
3303 | { |
3304 | return hasRareData() ? elementRareData()->beforePseudoElement() : nullptr; |
3305 | } |
3306 | |
3307 | PseudoElement* Element::afterPseudoElement() const |
3308 | { |
3309 | return hasRareData() ? elementRareData()->afterPseudoElement() : nullptr; |
3310 | } |
3311 | |
3312 | void Element::setBeforePseudoElement(Ref<PseudoElement>&& element) |
3313 | { |
3314 | ensureElementRareData().setBeforePseudoElement(WTFMove(element)); |
3315 | } |
3316 | |
3317 | void Element::setAfterPseudoElement(Ref<PseudoElement>&& element) |
3318 | { |
3319 | ensureElementRareData().setAfterPseudoElement(WTFMove(element)); |
3320 | } |
3321 | |
3322 | static void disconnectPseudoElement(PseudoElement* pseudoElement) |
3323 | { |
3324 | if (!pseudoElement) |
3325 | return; |
3326 | ASSERT(!pseudoElement->renderer()); |
3327 | ASSERT(pseudoElement->hostElement()); |
3328 | pseudoElement->clearHostElement(); |
3329 | } |
3330 | |
3331 | void Element::clearBeforePseudoElement() |
3332 | { |
3333 | if (!hasRareData()) |
3334 | return; |
3335 | disconnectPseudoElement(elementRareData()->beforePseudoElement()); |
3336 | elementRareData()->setBeforePseudoElement(nullptr); |
3337 | } |
3338 | |
3339 | void Element::clearAfterPseudoElement() |
3340 | { |
3341 | if (!hasRareData()) |
3342 | return; |
3343 | disconnectPseudoElement(elementRareData()->afterPseudoElement()); |
3344 | elementRareData()->setAfterPseudoElement(nullptr); |
3345 | } |
3346 | |
3347 | bool Element::matchesValidPseudoClass() const |
3348 | { |
3349 | return false; |
3350 | } |
3351 | |
3352 | bool Element::matchesInvalidPseudoClass() const |
3353 | { |
3354 | return false; |
3355 | } |
3356 | |
3357 | bool Element::matchesReadWritePseudoClass() const |
3358 | { |
3359 | return false; |
3360 | } |
3361 | |
3362 | bool Element::matchesIndeterminatePseudoClass() const |
3363 | { |
3364 | return shouldAppearIndeterminate(); |
3365 | } |
3366 | |
3367 | bool Element::matchesDefaultPseudoClass() const |
3368 | { |
3369 | return false; |
3370 | } |
3371 | |
3372 | ExceptionOr<bool> Element::matches(const String& selector) |
3373 | { |
3374 | auto query = document().selectorQueryForString(selector); |
3375 | if (query.hasException()) |
3376 | return query.releaseException(); |
3377 | return query.releaseReturnValue().matches(*this); |
3378 | } |
3379 | |
3380 | ExceptionOr<Element*> Element::closest(const String& selector) |
3381 | { |
3382 | auto query = document().selectorQueryForString(selector); |
3383 | if (query.hasException()) |
3384 | return query.releaseException(); |
3385 | return query.releaseReturnValue().closest(*this); |
3386 | } |
3387 | |
3388 | bool Element::shouldAppearIndeterminate() const |
3389 | { |
3390 | return false; |
3391 | } |
3392 | |
3393 | bool Element::mayCauseRepaintInsideViewport(const IntRect* visibleRect) const |
3394 | { |
3395 | return renderer() && renderer()->mayCauseRepaintInsideViewport(visibleRect); |
3396 | } |
3397 | |
3398 | DOMTokenList& Element::classList() |
3399 | { |
3400 | ElementRareData& data = ensureElementRareData(); |
3401 | if (!data.classList()) |
3402 | data.setClassList(std::make_unique<DOMTokenList>(*this, HTMLNames::classAttr)); |
3403 | return *data.classList(); |
3404 | } |
3405 | |
3406 | DatasetDOMStringMap& Element::dataset() |
3407 | { |
3408 | ElementRareData& data = ensureElementRareData(); |
3409 | if (!data.dataset()) |
3410 | data.setDataset(std::make_unique<DatasetDOMStringMap>(*this)); |
3411 | return *data.dataset(); |
3412 | } |
3413 | |
3414 | URL Element::getURLAttribute(const QualifiedName& name) const |
3415 | { |
3416 | #if !ASSERT_DISABLED |
3417 | if (elementData()) { |
3418 | if (const Attribute* attribute = findAttributeByName(name)) |
3419 | ASSERT(isURLAttribute(*attribute)); |
3420 | } |
3421 | #endif |
3422 | return document().completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(name))); |
3423 | } |
3424 | |
3425 | URL Element::getNonEmptyURLAttribute(const QualifiedName& name) const |
3426 | { |
3427 | #if !ASSERT_DISABLED |
3428 | if (elementData()) { |
3429 | if (const Attribute* attribute = findAttributeByName(name)) |
3430 | ASSERT(isURLAttribute(*attribute)); |
3431 | } |
3432 | #endif |
3433 | String value = stripLeadingAndTrailingHTMLSpaces(getAttribute(name)); |
3434 | if (value.isEmpty()) |
3435 | return URL(); |
3436 | return document().completeURL(value); |
3437 | } |
3438 | |
3439 | int Element::getIntegralAttribute(const QualifiedName& attributeName) const |
3440 | { |
3441 | return parseHTMLInteger(getAttribute(attributeName)).value_or(0); |
3442 | } |
3443 | |
3444 | void Element::setIntegralAttribute(const QualifiedName& attributeName, int value) |
3445 | { |
3446 | setAttribute(attributeName, AtomString::number(value)); |
3447 | } |
3448 | |
3449 | unsigned Element::getUnsignedIntegralAttribute(const QualifiedName& attributeName) const |
3450 | { |
3451 | return parseHTMLNonNegativeInteger(getAttribute(attributeName)).value_or(0); |
3452 | } |
3453 | |
3454 | void Element::setUnsignedIntegralAttribute(const QualifiedName& attributeName, unsigned value) |
3455 | { |
3456 | setAttribute(attributeName, AtomString::number(limitToOnlyHTMLNonNegative(value))); |
3457 | } |
3458 | |
3459 | bool Element::childShouldCreateRenderer(const Node& child) const |
3460 | { |
3461 | // Only create renderers for SVG elements whose parents are SVG elements, or for proper <svg xmlns="svgNS"> subdocuments. |
3462 | if (child.isSVGElement()) { |
3463 | ASSERT(!isSVGElement()); |
3464 | const SVGElement& childElement = downcast<SVGElement>(child); |
3465 | return is<SVGSVGElement>(childElement) && childElement.isValid(); |
3466 | } |
3467 | return true; |
3468 | } |
3469 | |
3470 | #if ENABLE(FULLSCREEN_API) |
3471 | static Element* parentCrossingFrameBoundaries(const Element* element) |
3472 | { |
3473 | ASSERT(element); |
3474 | if (auto* parent = element->parentElementInComposedTree()) |
3475 | return parent; |
3476 | return element->document().ownerElement(); |
3477 | } |
3478 | |
3479 | void Element::webkitRequestFullscreen() |
3480 | { |
3481 | document().fullscreenManager().requestFullscreenForElement(this, FullscreenManager::EnforceIFrameAllowFullscreenRequirement); |
3482 | } |
3483 | |
3484 | bool Element::containsFullScreenElement() const |
3485 | { |
3486 | return hasRareData() && elementRareData()->containsFullScreenElement(); |
3487 | } |
3488 | |
3489 | void Element::setContainsFullScreenElement(bool flag) |
3490 | { |
3491 | ensureElementRareData().setContainsFullScreenElement(flag); |
3492 | invalidateStyleAndLayerComposition(); |
3493 | } |
3494 | |
3495 | void Element::setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(bool flag) |
3496 | { |
3497 | Element* element = this; |
3498 | while ((element = parentCrossingFrameBoundaries(element))) |
3499 | element->setContainsFullScreenElement(flag); |
3500 | } |
3501 | #endif |
3502 | |
3503 | #if ENABLE(POINTER_EVENTS) |
3504 | ExceptionOr<void> Element::setPointerCapture(int32_t pointerId) |
3505 | { |
3506 | if (document().page()) |
3507 | return document().page()->pointerCaptureController().setPointerCapture(this, pointerId); |
3508 | return { }; |
3509 | } |
3510 | |
3511 | ExceptionOr<void> Element::releasePointerCapture(int32_t pointerId) |
3512 | { |
3513 | if (document().page()) |
3514 | return document().page()->pointerCaptureController().releasePointerCapture(this, pointerId); |
3515 | return { }; |
3516 | } |
3517 | |
3518 | bool Element::hasPointerCapture(int32_t pointerId) |
3519 | { |
3520 | if (document().page()) |
3521 | return document().page()->pointerCaptureController().hasPointerCapture(this, pointerId); |
3522 | return false; |
3523 | } |
3524 | #endif |
3525 | |
3526 | #if ENABLE(POINTER_LOCK) |
3527 | void Element::requestPointerLock() |
3528 | { |
3529 | if (document().page()) |
3530 | document().page()->pointerLockController().requestPointerLock(this); |
3531 | } |
3532 | #endif |
3533 | |
3534 | #if ENABLE(INTERSECTION_OBSERVER) |
3535 | void Element::disconnectFromIntersectionObservers() |
3536 | { |
3537 | auto* observerData = intersectionObserverData(); |
3538 | if (!observerData) |
3539 | return; |
3540 | |
3541 | for (const auto& registration : observerData->registrations) |
3542 | registration.observer->targetDestroyed(*this); |
3543 | observerData->registrations.clear(); |
3544 | |
3545 | for (const auto& observer : observerData->observers) |
3546 | observer->rootDestroyed(); |
3547 | observerData->observers.clear(); |
3548 | } |
3549 | |
3550 | IntersectionObserverData& Element::ensureIntersectionObserverData() |
3551 | { |
3552 | auto& rareData = ensureElementRareData(); |
3553 | if (!rareData.intersectionObserverData()) |
3554 | rareData.setIntersectionObserverData(std::make_unique<IntersectionObserverData>()); |
3555 | return *rareData.intersectionObserverData(); |
3556 | } |
3557 | |
3558 | IntersectionObserverData* Element::intersectionObserverData() |
3559 | { |
3560 | return hasRareData() ? elementRareData()->intersectionObserverData() : nullptr; |
3561 | } |
3562 | #endif |
3563 | |
3564 | #if ENABLE(RESIZE_OBSERVER) |
3565 | void Element::disconnectFromResizeObservers() |
3566 | { |
3567 | auto* observerData = resizeObserverData(); |
3568 | if (!observerData) |
3569 | return; |
3570 | |
3571 | for (const auto& observer : observerData->observers) |
3572 | observer->targetDestroyed(*this); |
3573 | observerData->observers.clear(); |
3574 | } |
3575 | |
3576 | ResizeObserverData& Element::ensureResizeObserverData() |
3577 | { |
3578 | auto& rareData = ensureElementRareData(); |
3579 | if (!rareData.resizeObserverData()) |
3580 | rareData.setResizeObserverData(std::make_unique<ResizeObserverData>()); |
3581 | return *rareData.resizeObserverData(); |
3582 | } |
3583 | |
3584 | ResizeObserverData* Element::resizeObserverData() |
3585 | { |
3586 | return hasRareData() ? elementRareData()->resizeObserverData() : nullptr; |
3587 | } |
3588 | #endif |
3589 | |
3590 | SpellcheckAttributeState Element::spellcheckAttributeState() const |
3591 | { |
3592 | const AtomString& value = attributeWithoutSynchronization(HTMLNames::spellcheckAttr); |
3593 | if (value.isNull()) |
3594 | return SpellcheckAttributeDefault; |
3595 | if (value.isEmpty() || equalLettersIgnoringASCIICase(value, "true" )) |
3596 | return SpellcheckAttributeTrue; |
3597 | if (equalLettersIgnoringASCIICase(value, "false" )) |
3598 | return SpellcheckAttributeFalse; |
3599 | return SpellcheckAttributeDefault; |
3600 | } |
3601 | |
3602 | bool Element::isSpellCheckingEnabled() const |
3603 | { |
3604 | for (const Element* element = this; element; element = element->parentOrShadowHostElement()) { |
3605 | switch (element->spellcheckAttributeState()) { |
3606 | case SpellcheckAttributeTrue: |
3607 | return true; |
3608 | case SpellcheckAttributeFalse: |
3609 | return false; |
3610 | case SpellcheckAttributeDefault: |
3611 | break; |
3612 | } |
3613 | } |
3614 | |
3615 | return true; |
3616 | } |
3617 | |
3618 | #ifndef NDEBUG |
3619 | bool Element::fastAttributeLookupAllowed(const QualifiedName& name) const |
3620 | { |
3621 | if (name == HTMLNames::styleAttr) |
3622 | return false; |
3623 | |
3624 | if (isSVGElement()) |
3625 | return !downcast<SVGElement>(*this).isAnimatedPropertyAttribute(name); |
3626 | |
3627 | return true; |
3628 | } |
3629 | #endif |
3630 | |
3631 | #if DUMP_NODE_STATISTICS |
3632 | bool Element::hasNamedNodeMap() const |
3633 | { |
3634 | return hasRareData() && elementRareData()->attributeMap(); |
3635 | } |
3636 | #endif |
3637 | |
3638 | inline void Element::updateName(const AtomString& oldName, const AtomString& newName) |
3639 | { |
3640 | if (!isInTreeScope()) |
3641 | return; |
3642 | |
3643 | if (oldName == newName) |
3644 | return; |
3645 | |
3646 | updateNameForTreeScope(treeScope(), oldName, newName); |
3647 | |
3648 | if (!isConnected()) |
3649 | return; |
3650 | if (!is<HTMLDocument>(document())) |
3651 | return; |
3652 | updateNameForDocument(downcast<HTMLDocument>(document()), oldName, newName); |
3653 | } |
3654 | |
3655 | void Element::updateNameForTreeScope(TreeScope& scope, const AtomString& oldName, const AtomString& newName) |
3656 | { |
3657 | ASSERT(oldName != newName); |
3658 | |
3659 | if (!oldName.isEmpty()) |
3660 | scope.removeElementByName(*oldName.impl(), *this); |
3661 | if (!newName.isEmpty()) |
3662 | scope.addElementByName(*newName.impl(), *this); |
3663 | } |
3664 | |
3665 | void Element::updateNameForDocument(HTMLDocument& document, const AtomString& oldName, const AtomString& newName) |
3666 | { |
3667 | ASSERT(oldName != newName); |
3668 | |
3669 | if (isInShadowTree()) |
3670 | return; |
3671 | |
3672 | if (WindowNameCollection::elementMatchesIfNameAttributeMatch(*this)) { |
3673 | const AtomString& id = WindowNameCollection::elementMatchesIfIdAttributeMatch(*this) ? getIdAttribute() : nullAtom(); |
3674 | if (!oldName.isEmpty() && oldName != id) |
3675 | document.removeWindowNamedItem(*oldName.impl(), *this); |
3676 | if (!newName.isEmpty() && newName != id) |
3677 | document.addWindowNamedItem(*newName.impl(), *this); |
3678 | } |
3679 | |
3680 | if (DocumentNameCollection::elementMatchesIfNameAttributeMatch(*this)) { |
3681 | const AtomString& id = DocumentNameCollection::elementMatchesIfIdAttributeMatch(*this) ? getIdAttribute() : nullAtom(); |
3682 | if (!oldName.isEmpty() && oldName != id) |
3683 | document.removeDocumentNamedItem(*oldName.impl(), *this); |
3684 | if (!newName.isEmpty() && newName != id) |
3685 | document.addDocumentNamedItem(*newName.impl(), *this); |
3686 | } |
3687 | } |
3688 | |
3689 | inline void Element::updateId(const AtomString& oldId, const AtomString& newId, NotifyObservers notifyObservers) |
3690 | { |
3691 | if (!isInTreeScope()) |
3692 | return; |
3693 | |
3694 | if (oldId == newId) |
3695 | return; |
3696 | |
3697 | updateIdForTreeScope(treeScope(), oldId, newId, notifyObservers); |
3698 | |
3699 | if (!isConnected()) |
3700 | return; |
3701 | if (!is<HTMLDocument>(document())) |
3702 | return; |
3703 | updateIdForDocument(downcast<HTMLDocument>(document()), oldId, newId, UpdateHTMLDocumentNamedItemMapsOnlyIfDiffersFromNameAttribute); |
3704 | } |
3705 | |
3706 | void Element::updateIdForTreeScope(TreeScope& scope, const AtomString& oldId, const AtomString& newId, NotifyObservers notifyObservers) |
3707 | { |
3708 | ASSERT(isInTreeScope()); |
3709 | ASSERT(oldId != newId); |
3710 | |
3711 | if (!oldId.isEmpty()) |
3712 | scope.removeElementById(*oldId.impl(), *this, notifyObservers == NotifyObservers::Yes); |
3713 | if (!newId.isEmpty()) |
3714 | scope.addElementById(*newId.impl(), *this, notifyObservers == NotifyObservers::Yes); |
3715 | } |
3716 | |
3717 | void Element::updateIdForDocument(HTMLDocument& document, const AtomString& oldId, const AtomString& newId, HTMLDocumentNamedItemMapsUpdatingCondition condition) |
3718 | { |
3719 | ASSERT(isConnected()); |
3720 | ASSERT(oldId != newId); |
3721 | |
3722 | if (isInShadowTree()) |
3723 | return; |
3724 | |
3725 | if (WindowNameCollection::elementMatchesIfIdAttributeMatch(*this)) { |
3726 | const AtomString& name = condition == UpdateHTMLDocumentNamedItemMapsOnlyIfDiffersFromNameAttribute && WindowNameCollection::elementMatchesIfNameAttributeMatch(*this) ? getNameAttribute() : nullAtom(); |
3727 | if (!oldId.isEmpty() && oldId != name) |
3728 | document.removeWindowNamedItem(*oldId.impl(), *this); |
3729 | if (!newId.isEmpty() && newId != name) |
3730 | document.addWindowNamedItem(*newId.impl(), *this); |
3731 | } |
3732 | |
3733 | if (DocumentNameCollection::elementMatchesIfIdAttributeMatch(*this)) { |
3734 | const AtomString& name = condition == UpdateHTMLDocumentNamedItemMapsOnlyIfDiffersFromNameAttribute && DocumentNameCollection::elementMatchesIfNameAttributeMatch(*this) ? getNameAttribute() : nullAtom(); |
3735 | if (!oldId.isEmpty() && oldId != name) |
3736 | document.removeDocumentNamedItem(*oldId.impl(), *this); |
3737 | if (!newId.isEmpty() && newId != name) |
3738 | document.addDocumentNamedItem(*newId.impl(), *this); |
3739 | } |
3740 | } |
3741 | |
3742 | void Element::updateLabel(TreeScope& scope, const AtomString& oldForAttributeValue, const AtomString& newForAttributeValue) |
3743 | { |
3744 | ASSERT(hasTagName(labelTag)); |
3745 | |
3746 | if (!isConnected()) |
3747 | return; |
3748 | |
3749 | if (oldForAttributeValue == newForAttributeValue) |
3750 | return; |
3751 | |
3752 | if (!oldForAttributeValue.isEmpty()) |
3753 | scope.removeLabel(*oldForAttributeValue.impl(), downcast<HTMLLabelElement>(*this)); |
3754 | if (!newForAttributeValue.isEmpty()) |
3755 | scope.addLabel(*newForAttributeValue.impl(), downcast<HTMLLabelElement>(*this)); |
3756 | } |
3757 | |
3758 | void Element::willModifyAttribute(const QualifiedName& name, const AtomString& oldValue, const AtomString& newValue) |
3759 | { |
3760 | if (name == HTMLNames::idAttr) |
3761 | updateId(oldValue, newValue, NotifyObservers::No); // Will notify observers after the attribute is actually changed. |
3762 | else if (name == HTMLNames::nameAttr) |
3763 | updateName(oldValue, newValue); |
3764 | else if (name == HTMLNames::forAttr && hasTagName(labelTag)) { |
3765 | if (treeScope().shouldCacheLabelsByForAttribute()) |
3766 | updateLabel(treeScope(), oldValue, newValue); |
3767 | } |
3768 | |
3769 | if (auto recipients = MutationObserverInterestGroup::createForAttributesMutation(*this, name)) |
3770 | recipients->enqueueMutationRecord(MutationRecord::createAttributes(*this, name, oldValue)); |
3771 | |
3772 | InspectorInstrumentation::willModifyDOMAttr(document(), *this, oldValue, newValue); |
3773 | } |
3774 | |
3775 | void Element::didAddAttribute(const QualifiedName& name, const AtomString& value) |
3776 | { |
3777 | attributeChanged(name, nullAtom(), value); |
3778 | InspectorInstrumentation::didModifyDOMAttr(document(), *this, name.localName(), value); |
3779 | dispatchSubtreeModifiedEvent(); |
3780 | } |
3781 | |
3782 | void Element::didModifyAttribute(const QualifiedName& name, const AtomString& oldValue, const AtomString& newValue) |
3783 | { |
3784 | attributeChanged(name, oldValue, newValue); |
3785 | InspectorInstrumentation::didModifyDOMAttr(document(), *this, name.localName(), newValue); |
3786 | // Do not dispatch a DOMSubtreeModified event here; see bug 81141. |
3787 | } |
3788 | |
3789 | void Element::didRemoveAttribute(const QualifiedName& name, const AtomString& oldValue) |
3790 | { |
3791 | attributeChanged(name, oldValue, nullAtom()); |
3792 | InspectorInstrumentation::didRemoveDOMAttr(document(), *this, name.localName()); |
3793 | dispatchSubtreeModifiedEvent(); |
3794 | } |
3795 | |
3796 | IntPoint Element::savedLayerScrollPosition() const |
3797 | { |
3798 | return hasRareData() ? elementRareData()->savedLayerScrollPosition() : IntPoint(); |
3799 | } |
3800 | |
3801 | void Element::setSavedLayerScrollPosition(const IntPoint& position) |
3802 | { |
3803 | if (position.isZero() && !hasRareData()) |
3804 | return; |
3805 | ensureElementRareData().setSavedLayerScrollPosition(position); |
3806 | } |
3807 | |
3808 | RefPtr<Attr> Element::attrIfExists(const AtomString& localName, bool shouldIgnoreAttributeCase) |
3809 | { |
3810 | if (auto* attrNodeList = attrNodeListForElement(*this)) |
3811 | return findAttrNodeInList(*attrNodeList, localName, shouldIgnoreAttributeCase); |
3812 | return nullptr; |
3813 | } |
3814 | |
3815 | RefPtr<Attr> Element::attrIfExists(const QualifiedName& name) |
3816 | { |
3817 | if (auto* attrNodeList = attrNodeListForElement(*this)) |
3818 | return findAttrNodeInList(*attrNodeList, name); |
3819 | return nullptr; |
3820 | } |
3821 | |
3822 | Ref<Attr> Element::ensureAttr(const QualifiedName& name) |
3823 | { |
3824 | auto& attrNodeList = ensureAttrNodeListForElement(*this); |
3825 | RefPtr<Attr> attrNode = findAttrNodeInList(attrNodeList, name); |
3826 | if (!attrNode) { |
3827 | attrNode = Attr::create(*this, name); |
3828 | attrNode->setTreeScopeRecursively(treeScope()); |
3829 | attrNodeList.append(attrNode); |
3830 | } |
3831 | return attrNode.releaseNonNull(); |
3832 | } |
3833 | |
3834 | void Element::detachAttrNodeFromElementWithValue(Attr* attrNode, const AtomString& value) |
3835 | { |
3836 | ASSERT(hasSyntheticAttrChildNodes()); |
3837 | attrNode->detachFromElementWithValue(value); |
3838 | |
3839 | auto& attrNodeList = *attrNodeListForElement(*this); |
3840 | bool found = attrNodeList.removeFirstMatching([attrNode](auto& attribute) { |
3841 | return attribute->qualifiedName() == attrNode->qualifiedName(); |
3842 | }); |
3843 | ASSERT_UNUSED(found, found); |
3844 | if (attrNodeList.isEmpty()) |
3845 | removeAttrNodeListForElement(*this); |
3846 | } |
3847 | |
3848 | void Element::detachAllAttrNodesFromElement() |
3849 | { |
3850 | auto* attrNodeList = attrNodeListForElement(*this); |
3851 | ASSERT(attrNodeList); |
3852 | |
3853 | for (const Attribute& attribute : attributesIterator()) { |
3854 | if (RefPtr<Attr> attrNode = findAttrNodeInList(*attrNodeList, attribute.name())) |
3855 | attrNode->detachFromElementWithValue(attribute.value()); |
3856 | } |
3857 | |
3858 | removeAttrNodeListForElement(*this); |
3859 | } |
3860 | |
3861 | void Element::resetComputedStyle() |
3862 | { |
3863 | if (!hasRareData() || !elementRareData()->computedStyle()) |
3864 | return; |
3865 | |
3866 | auto reset = [](Element& element) { |
3867 | if (!element.hasRareData() || !element.elementRareData()->computedStyle()) |
3868 | return; |
3869 | if (element.hasCustomStyleResolveCallbacks()) |
3870 | element.willResetComputedStyle(); |
3871 | element.elementRareData()->resetComputedStyle(); |
3872 | }; |
3873 | reset(*this); |
3874 | for (auto& child : descendantsOfType<Element>(*this)) |
3875 | reset(child); |
3876 | } |
3877 | |
3878 | void Element::resetStyleRelations() |
3879 | { |
3880 | if (!hasRareData()) |
3881 | return; |
3882 | elementRareData()->resetStyleRelations(); |
3883 | } |
3884 | |
3885 | void Element::clearHoverAndActiveStatusBeforeDetachingRenderer() |
3886 | { |
3887 | if (!isUserActionElement()) |
3888 | return; |
3889 | if (hovered()) |
3890 | document().hoveredElementDidDetach(*this); |
3891 | if (isInActiveChain()) |
3892 | document().elementInActiveChainDidDetach(*this); |
3893 | document().userActionElements().clearActiveAndHovered(*this); |
3894 | } |
3895 | |
3896 | void Element::willRecalcStyle(Style::Change) |
3897 | { |
3898 | ASSERT(hasCustomStyleResolveCallbacks()); |
3899 | } |
3900 | |
3901 | void Element::didRecalcStyle(Style::Change) |
3902 | { |
3903 | ASSERT(hasCustomStyleResolveCallbacks()); |
3904 | } |
3905 | |
3906 | void Element::willResetComputedStyle() |
3907 | { |
3908 | ASSERT(hasCustomStyleResolveCallbacks()); |
3909 | } |
3910 | |
3911 | void Element::willAttachRenderers() |
3912 | { |
3913 | ASSERT(hasCustomStyleResolveCallbacks()); |
3914 | } |
3915 | |
3916 | void Element::didAttachRenderers() |
3917 | { |
3918 | ASSERT(hasCustomStyleResolveCallbacks()); |
3919 | } |
3920 | |
3921 | void Element::willDetachRenderers() |
3922 | { |
3923 | ASSERT(hasCustomStyleResolveCallbacks()); |
3924 | } |
3925 | |
3926 | void Element::didDetachRenderers() |
3927 | { |
3928 | ASSERT(hasCustomStyleResolveCallbacks()); |
3929 | } |
3930 | |
3931 | Optional<ElementStyle> Element::resolveCustomStyle(const RenderStyle&, const RenderStyle*) |
3932 | { |
3933 | ASSERT(hasCustomStyleResolveCallbacks()); |
3934 | return WTF::nullopt; |
3935 | } |
3936 | |
3937 | void Element::cloneAttributesFromElement(const Element& other) |
3938 | { |
3939 | if (hasSyntheticAttrChildNodes()) |
3940 | detachAllAttrNodesFromElement(); |
3941 | |
3942 | other.synchronizeAllAttributes(); |
3943 | if (!other.m_elementData) { |
3944 | m_elementData = nullptr; |
3945 | return; |
3946 | } |
3947 | |
3948 | // 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. |
3949 | // Fortunately, those named item maps are only updated when this element is in the document, which should never be the case. |
3950 | ASSERT(!isConnected()); |
3951 | |
3952 | const AtomString& oldID = getIdAttribute(); |
3953 | const AtomString& newID = other.getIdAttribute(); |
3954 | |
3955 | if (!oldID.isNull() || !newID.isNull()) |
3956 | updateId(oldID, newID, NotifyObservers::No); // Will notify observers after the attribute is actually changed. |
3957 | |
3958 | const AtomString& oldName = getNameAttribute(); |
3959 | const AtomString& newName = other.getNameAttribute(); |
3960 | |
3961 | if (!oldName.isNull() || !newName.isNull()) |
3962 | updateName(oldName, newName); |
3963 | |
3964 | // If 'other' has a mutable ElementData, convert it to an immutable one so we can share it between both elements. |
3965 | // We can only do this if there is no CSSOM wrapper for other's inline style, and there are no presentation attributes. |
3966 | if (is<UniqueElementData>(*other.m_elementData) |
3967 | && !other.m_elementData->presentationAttributeStyle() |
3968 | && (!other.m_elementData->inlineStyle() || !other.m_elementData->inlineStyle()->hasCSSOMWrapper())) |
3969 | const_cast<Element&>(other).m_elementData = downcast<UniqueElementData>(*other.m_elementData).makeShareableCopy(); |
3970 | |
3971 | if (!other.m_elementData->isUnique()) |
3972 | m_elementData = other.m_elementData; |
3973 | else |
3974 | m_elementData = other.m_elementData->makeUniqueCopy(); |
3975 | |
3976 | for (const Attribute& attribute : attributesIterator()) |
3977 | attributeChanged(attribute.name(), nullAtom(), attribute.value(), ModifiedByCloning); |
3978 | } |
3979 | |
3980 | void Element::cloneDataFromElement(const Element& other) |
3981 | { |
3982 | cloneAttributesFromElement(other); |
3983 | copyNonAttributePropertiesFromElement(other); |
3984 | } |
3985 | |
3986 | void Element::createUniqueElementData() |
3987 | { |
3988 | if (!m_elementData) |
3989 | m_elementData = UniqueElementData::create(); |
3990 | else |
3991 | m_elementData = downcast<ShareableElementData>(*m_elementData).makeUniqueCopy(); |
3992 | } |
3993 | |
3994 | bool Element::hasPendingResources() const |
3995 | { |
3996 | return hasRareData() && elementRareData()->hasPendingResources(); |
3997 | } |
3998 | |
3999 | void Element::setHasPendingResources() |
4000 | { |
4001 | ensureElementRareData().setHasPendingResources(true); |
4002 | } |
4003 | |
4004 | void Element::clearHasPendingResources() |
4005 | { |
4006 | if (!hasRareData()) |
4007 | return; |
4008 | elementRareData()->setHasPendingResources(false); |
4009 | } |
4010 | |
4011 | bool Element::hasCSSAnimation() const |
4012 | { |
4013 | return hasRareData() && elementRareData()->hasCSSAnimation(); |
4014 | } |
4015 | |
4016 | void Element::setHasCSSAnimation() |
4017 | { |
4018 | ensureElementRareData().setHasCSSAnimation(true); |
4019 | } |
4020 | |
4021 | void Element::clearHasCSSAnimation() |
4022 | { |
4023 | if (!hasRareData()) |
4024 | return; |
4025 | elementRareData()->setHasCSSAnimation(false); |
4026 | } |
4027 | |
4028 | bool Element::canContainRangeEndPoint() const |
4029 | { |
4030 | return !equalLettersIgnoringASCIICase(attributeWithoutSynchronization(roleAttr), "img" ); |
4031 | } |
4032 | |
4033 | String Element::completeURLsInAttributeValue(const URL& base, const Attribute& attribute) const |
4034 | { |
4035 | return URL(base, attribute.value()).string(); |
4036 | } |
4037 | |
4038 | ExceptionOr<Node*> Element::insertAdjacent(const String& where, Ref<Node>&& newChild) |
4039 | { |
4040 | // In Internet Explorer if the element has no parent and where is "beforeBegin" or "afterEnd", |
4041 | // a document fragment is created and the elements appended in the correct order. This document |
4042 | // fragment isn't returned anywhere. |
4043 | // |
4044 | // This is impossible for us to implement as the DOM tree does not allow for such structures, |
4045 | // Opera also appears to disallow such usage. |
4046 | |
4047 | if (equalLettersIgnoringASCIICase(where, "beforebegin" )) { |
4048 | auto* parent = this->parentNode(); |
4049 | if (!parent) |
4050 | return nullptr; |
4051 | auto result = parent->insertBefore(newChild, this); |
4052 | if (result.hasException()) |
4053 | return result.releaseException(); |
4054 | return newChild.ptr(); |
4055 | } |
4056 | |
4057 | if (equalLettersIgnoringASCIICase(where, "afterbegin" )) { |
4058 | auto result = insertBefore(newChild, firstChild()); |
4059 | if (result.hasException()) |
4060 | return result.releaseException(); |
4061 | return newChild.ptr(); |
4062 | } |
4063 | |
4064 | if (equalLettersIgnoringASCIICase(where, "beforeend" )) { |
4065 | auto result = appendChild(newChild); |
4066 | if (result.hasException()) |
4067 | return result.releaseException(); |
4068 | return newChild.ptr(); |
4069 | } |
4070 | |
4071 | if (equalLettersIgnoringASCIICase(where, "afterend" )) { |
4072 | auto* parent = this->parentNode(); |
4073 | if (!parent) |
4074 | return nullptr; |
4075 | auto result = parent->insertBefore(newChild, nextSibling()); |
4076 | if (result.hasException()) |
4077 | return result.releaseException(); |
4078 | return newChild.ptr(); |
4079 | } |
4080 | |
4081 | return Exception { SyntaxError }; |
4082 | } |
4083 | |
4084 | ExceptionOr<Element*> Element::insertAdjacentElement(const String& where, Element& newChild) |
4085 | { |
4086 | auto result = insertAdjacent(where, newChild); |
4087 | if (result.hasException()) |
4088 | return result.releaseException(); |
4089 | return downcast<Element>(result.releaseReturnValue()); |
4090 | } |
4091 | |
4092 | // Step 1 of https://w3c.github.io/DOM-Parsing/#dom-element-insertadjacenthtml. |
4093 | static ExceptionOr<ContainerNode&> contextNodeForInsertion(const String& where, Element& element) |
4094 | { |
4095 | if (equalLettersIgnoringASCIICase(where, "beforebegin" ) || equalLettersIgnoringASCIICase(where, "afterend" )) { |
4096 | auto* parent = element.parentNode(); |
4097 | if (!parent || is<Document>(*parent)) |
4098 | return Exception { NoModificationAllowedError }; |
4099 | return *parent; |
4100 | } |
4101 | if (equalLettersIgnoringASCIICase(where, "afterbegin" ) || equalLettersIgnoringASCIICase(where, "beforeend" )) |
4102 | return element; |
4103 | return Exception { SyntaxError }; |
4104 | } |
4105 | |
4106 | // Step 2 of https://w3c.github.io/DOM-Parsing/#dom-element-insertadjacenthtml. |
4107 | static ExceptionOr<Ref<Element>> contextElementForInsertion(const String& where, Element& element) |
4108 | { |
4109 | auto contextNodeResult = contextNodeForInsertion(where, element); |
4110 | if (contextNodeResult.hasException()) |
4111 | return contextNodeResult.releaseException(); |
4112 | auto& contextNode = contextNodeResult.releaseReturnValue(); |
4113 | if (!is<Element>(contextNode) || (contextNode.document().isHTMLDocument() && is<HTMLHtmlElement>(contextNode))) |
4114 | return Ref<Element> { HTMLBodyElement::create(contextNode.document()) }; |
4115 | return Ref<Element> { downcast<Element>(contextNode) }; |
4116 | } |
4117 | |
4118 | // https://w3c.github.io/DOM-Parsing/#dom-element-insertadjacenthtml |
4119 | ExceptionOr<void> Element::insertAdjacentHTML(const String& where, const String& markup, NodeVector* addedNodes) |
4120 | { |
4121 | // Steps 1 and 2. |
4122 | auto contextElement = contextElementForInsertion(where, *this); |
4123 | if (contextElement.hasException()) |
4124 | return contextElement.releaseException(); |
4125 | // Step 3. |
4126 | auto fragment = createFragmentForInnerOuterHTML(contextElement.releaseReturnValue(), markup, AllowScriptingContent); |
4127 | if (fragment.hasException()) |
4128 | return fragment.releaseException(); |
4129 | |
4130 | if (UNLIKELY(addedNodes)) { |
4131 | // Must be called before insertAdjacent, as otherwise the children of fragment will be moved |
4132 | // to their new parent and will be harder to keep track of. |
4133 | *addedNodes = collectChildNodes(fragment.returnValue()); |
4134 | } |
4135 | |
4136 | // Step 4. |
4137 | auto result = insertAdjacent(where, fragment.releaseReturnValue()); |
4138 | if (result.hasException()) |
4139 | return result.releaseException(); |
4140 | return { }; |
4141 | } |
4142 | |
4143 | ExceptionOr<void> Element::insertAdjacentHTML(const String& where, const String& markup) |
4144 | { |
4145 | return insertAdjacentHTML(where, markup, nullptr); |
4146 | } |
4147 | |
4148 | ExceptionOr<void> Element::insertAdjacentText(const String& where, const String& text) |
4149 | { |
4150 | auto result = insertAdjacent(where, document().createTextNode(text)); |
4151 | if (result.hasException()) |
4152 | return result.releaseException(); |
4153 | return { }; |
4154 | } |
4155 | |
4156 | Element* Element::findAnchorElementForLink(String& outAnchorName) |
4157 | { |
4158 | if (!isLink()) |
4159 | return nullptr; |
4160 | |
4161 | const AtomString& href = attributeWithoutSynchronization(HTMLNames::hrefAttr); |
4162 | if (href.isNull()) |
4163 | return nullptr; |
4164 | |
4165 | Document& document = this->document(); |
4166 | URL url = document.completeURL(href); |
4167 | if (!url.isValid()) |
4168 | return nullptr; |
4169 | |
4170 | if (url.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(url, document.baseURL())) { |
4171 | outAnchorName = url.fragmentIdentifier(); |
4172 | return document.findAnchor(outAnchorName); |
4173 | } |
4174 | |
4175 | return nullptr; |
4176 | } |
4177 | |
4178 | ExceptionOr<Ref<WebAnimation>> Element::animate(JSC::ExecState& state, JSC::Strong<JSC::JSObject>&& keyframes, Optional<Variant<double, KeyframeAnimationOptions>>&& options) |
4179 | { |
4180 | String id = "" ; |
4181 | Optional<Variant<double, KeyframeEffectOptions>> keyframeEffectOptions; |
4182 | if (options) { |
4183 | auto optionsValue = options.value(); |
4184 | Variant<double, KeyframeEffectOptions> keyframeEffectOptionsVariant; |
4185 | if (WTF::holds_alternative<double>(optionsValue)) |
4186 | keyframeEffectOptionsVariant = WTF::get<double>(optionsValue); |
4187 | else { |
4188 | auto keyframeEffectOptions = WTF::get<KeyframeAnimationOptions>(optionsValue); |
4189 | id = keyframeEffectOptions.id; |
4190 | keyframeEffectOptionsVariant = WTFMove(keyframeEffectOptions); |
4191 | } |
4192 | keyframeEffectOptions = keyframeEffectOptionsVariant; |
4193 | } |
4194 | |
4195 | auto keyframeEffectResult = KeyframeEffect::create(state, this, WTFMove(keyframes), WTFMove(keyframeEffectOptions)); |
4196 | if (keyframeEffectResult.hasException()) |
4197 | return keyframeEffectResult.releaseException(); |
4198 | |
4199 | auto animation = WebAnimation::create(document(), &keyframeEffectResult.returnValue().get()); |
4200 | animation->setId(id); |
4201 | |
4202 | auto animationPlayResult = animation->play(); |
4203 | if (animationPlayResult.hasException()) |
4204 | return animationPlayResult.releaseException(); |
4205 | |
4206 | return animation; |
4207 | } |
4208 | |
4209 | Vector<RefPtr<WebAnimation>> Element::getAnimations() |
4210 | { |
4211 | // FIXME: Filter and order the list as specified (webkit.org/b/179535). |
4212 | |
4213 | // For the list of animations to be current, we need to account for any pending CSS changes, |
4214 | // such as updates to CSS Animations and CSS Transitions. |
4215 | // FIXME: We might be able to use ComputedStyleExtractor which is more optimized. |
4216 | document().updateStyleIfNeeded(); |
4217 | |
4218 | Vector<RefPtr<WebAnimation>> animations; |
4219 | if (auto timeline = document().existingTimeline()) { |
4220 | for (auto& animation : timeline->animationsForElement(*this, AnimationTimeline::Ordering::Sorted)) { |
4221 | if (animation->isRelevant()) |
4222 | animations.append(animation); |
4223 | } |
4224 | } |
4225 | return animations; |
4226 | } |
4227 | |
4228 | ElementIdentifier Element::createElementIdentifier() |
4229 | { |
4230 | auto& rareData = ensureElementRareData(); |
4231 | ASSERT(!rareData.hasElementIdentifier()); |
4232 | |
4233 | rareData.setHasElementIdentifier(true); |
4234 | return ElementIdentifier::generate(); |
4235 | } |
4236 | |
4237 | #if ENABLE(CSS_TYPED_OM) |
4238 | StylePropertyMap* Element::attributeStyleMap() |
4239 | { |
4240 | if (!hasRareData()) |
4241 | return nullptr; |
4242 | return elementRareData()->attributeStyleMap(); |
4243 | } |
4244 | |
4245 | void Element::setAttributeStyleMap(Ref<StylePropertyMap>&& map) |
4246 | { |
4247 | ensureElementRareData().setAttributeStyleMap(WTFMove(map)); |
4248 | } |
4249 | #endif |
4250 | |
4251 | #if ENABLE(POINTER_EVENTS) |
4252 | OptionSet<TouchAction> Element::computedTouchActions() const |
4253 | { |
4254 | if (auto* style = renderOrDisplayContentsStyle()) |
4255 | return style->effectiveTouchActions(); |
4256 | |
4257 | return TouchAction::Auto; |
4258 | } |
4259 | |
4260 | #if ENABLE(OVERFLOW_SCROLLING_TOUCH) |
4261 | ScrollingNodeID Element::nearestScrollingNodeIDUsingTouchOverflowScrolling() const |
4262 | { |
4263 | if (!renderer()) |
4264 | return 0; |
4265 | |
4266 | // We are not interested in the root, so check that we also have a valid parent. |
4267 | for (auto* layer = renderer()->enclosingLayer(); layer && layer->parent(); layer = layer->parent()) { |
4268 | if (layer->isComposited()) { |
4269 | if (auto scrollingNodeID = layer->backing()->scrollingNodeIDForRole(ScrollCoordinationRole::Scrolling)) |
4270 | return scrollingNodeID; |
4271 | } |
4272 | } |
4273 | |
4274 | return 0; |
4275 | } |
4276 | #endif |
4277 | #endif |
4278 | |
4279 | } // namespace WebCore |
4280 | |