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