| /* |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * Copyright (C) 2006-2025 Apple Inc. All rights reserved. |
| * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org> |
| * Copyright (C) 2007 Rob Buis <buis@kde.org> |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "TreeScope.h" |
| |
| #include "Attr.h" |
| #include "CSSStyleSheet.h" |
| #include "CSSStyleSheetObservableArray.h" |
| #include "ContainerNodeInlines.h" |
| #include "CustomElementRegistry.h" |
| #include "DocumentPage.h" |
| #include "DocumentView.h" |
| #include "FocusController.h" |
| #include "FrameDestructionObserverInlines.h" |
| #include "FrameInlines.h" |
| #include "HTMLAnchorElement.h" |
| #include "HTMLFrameOwnerElement.h" |
| #include "HTMLImageElement.h" |
| #include "HTMLLabelElement.h" |
| #include "HTMLMapElement.h" |
| #include "HitTestResult.h" |
| #include "IdTargetObserverRegistry.h" |
| #include "JSObservableArray.h" |
| #include "LegacyRenderSVGResourceContainer.h" |
| #include "LocalDOMWindow.h" |
| #include "LocalFrame.h" |
| #include "LocalFrameView.h" |
| #include "NodeRareData.h" |
| #include "Page.h" |
| #include "PointerLockController.h" |
| #include "PseudoElement.h" |
| #include "RadioButtonGroups.h" |
| #include "RenderView.h" |
| #include "SVGElement.h" |
| #include "Settings.h" |
| #include "ShadowRoot.h" |
| #include "TreeScopeInlines.h" |
| #include "TreeScopeOrderedMap.h" |
| #include "TypedElementDescendantIteratorInlines.h" |
| #include <algorithm> |
| #include <wtf/RobinHoodHashMap.h> |
| #include <wtf/text/AtomStringHash.h> |
| #include <wtf/text/CString.h> |
| |
| namespace WebCore { |
| |
| struct SameSizeAsTreeScope { |
| void* pointers[13]; |
| }; |
| |
| static_assert(sizeof(TreeScope) == sizeof(SameSizeAsTreeScope), "treescope should stay small"); |
| |
| using namespace HTMLNames; |
| |
| using WeakSVGElementSet = WeakHashSet<SVGElement, WeakPtrImplWithEventTargetData>; |
| struct SVGResourcesMap { |
| WTF_MAKE_NONCOPYABLE(SVGResourcesMap); |
| WTF_DEPRECATED_MAKE_STRUCT_FAST_ALLOCATED(SVGResourcesMap); |
| SVGResourcesMap() = default; |
| |
| MemoryCompactRobinHoodHashMap<AtomString, WeakSVGElementSet> pendingResources; |
| MemoryCompactRobinHoodHashMap<AtomString, WeakSVGElementSet> pendingResourcesForRemoval; |
| MemoryCompactRobinHoodHashMap<AtomString, SingleThreadWeakPtr<LegacyRenderSVGResourceContainer>> legacyResources; |
| }; |
| |
| TreeScope::TreeScope(ShadowRoot& shadowRoot, Document& document, RefPtr<CustomElementRegistry>&& registry) |
| : m_rootNode(shadowRoot) |
| , m_documentScope(document) |
| , m_parentTreeScope(&document) |
| , m_customElementRegistry(WTF::move(registry)) |
| { |
| shadowRoot.setTreeScope(*this); |
| } |
| |
| TreeScope::TreeScope(Document& document) |
| : m_rootNode(document) |
| , m_documentScope(document) |
| , m_parentTreeScope(nullptr) |
| { |
| document.setTreeScope(*this); |
| } |
| |
| TreeScope::~TreeScope() = default; |
| |
| void TreeScope::ref() const |
| { |
| if (auto* document = dynamicDowncast<Document>(m_rootNode.get())) |
| document->ref(); |
| else |
| downcast<ShadowRoot>(m_rootNode.get()).ref(); |
| } |
| |
| void TreeScope::deref() const |
| { |
| if (auto* document = dynamicDowncast<Document>(m_rootNode.get())) |
| document->deref(); |
| else |
| downcast<ShadowRoot>(m_rootNode.get()).deref(); |
| } |
| |
| IdTargetObserverRegistry& TreeScope::ensureIdTargetObserverRegistry() |
| { |
| if (!m_idTargetObserverRegistry) |
| m_idTargetObserverRegistry = makeUnique<IdTargetObserverRegistry>(); |
| return *m_idTargetObserverRegistry; |
| } |
| |
| void TreeScope::destroyTreeScopeData() |
| { |
| m_elementsById = nullptr; |
| m_elementsByName = nullptr; |
| m_imageMapsByName = nullptr; |
| m_imagesByUsemap = nullptr; |
| m_labelsByForAttribute = nullptr; |
| } |
| |
| void TreeScope::setParentTreeScope(TreeScope& newParentScope) |
| { |
| // A document node cannot be re-parented. |
| ASSERT(!m_rootNode->isDocumentNode()); |
| |
| m_parentTreeScope = &newParentScope; |
| setDocumentScope(newParentScope.documentScope()); |
| } |
| |
| void TreeScope::setCustomElementRegistry(RefPtr<CustomElementRegistry>&& registry) |
| { |
| m_customElementRegistry = WTF::move(registry); |
| } |
| |
| RefPtr<Element> TreeScope::getElementById(const AtomString& elementId) const |
| { |
| if (elementId.isEmpty()) |
| return nullptr; |
| if (!m_elementsById) |
| return nullptr; |
| return m_elementsById->getElementById(elementId, *this); |
| } |
| |
| RefPtr<Element> TreeScope::getElementById(const String& elementId) const |
| { |
| if (!m_elementsById) |
| return nullptr; |
| |
| if (auto atomElementId = elementId.toExistingAtomString(); !atomElementId.isNull()) |
| return m_elementsById->getElementById(atomElementId, *this); |
| |
| return nullptr; |
| } |
| |
| RefPtr<Element> TreeScope::getElementById(StringView elementId) const |
| { |
| if (!m_elementsById) |
| return nullptr; |
| |
| if (auto atomElementId = elementId.toExistingAtomString(); !atomElementId.isNull()) |
| return m_elementsById->getElementById(atomElementId, *this); |
| |
| return nullptr; |
| } |
| |
| RefPtr<Element> TreeScope::elementByIdResolvingReferenceTarget(const AtomString& elementId) const |
| { |
| RefPtr elementForId = getElementById(elementId); |
| return elementForId ? elementForId->resolveReferenceTarget() : nullptr; |
| } |
| |
| const Vector<WeakRef<Element, WeakPtrImplWithEventTargetData>>* TreeScope::getAllElementsById(const AtomString& elementId) const |
| { |
| if (elementId.isEmpty()) |
| return nullptr; |
| if (!m_elementsById) |
| return nullptr; |
| return m_elementsById->getAllElementsById(elementId, *this); |
| } |
| |
| void TreeScope::addElementById(const AtomString& elementId, Element& element, bool notifyObservers) |
| { |
| if (!m_elementsById) |
| m_elementsById = makeUnique<TreeScopeOrderedMap>(); |
| m_elementsById->add(elementId, element, *this); |
| if (m_idTargetObserverRegistry && notifyObservers) |
| m_idTargetObserverRegistry->notifyObservers(element, elementId); |
| } |
| |
| void TreeScope::removeElementById(const AtomString& elementId, Element& element, bool notifyObservers) |
| { |
| if (!m_elementsById) |
| return; |
| m_elementsById->remove(elementId, element); |
| if (m_idTargetObserverRegistry && notifyObservers) |
| m_idTargetObserverRegistry->notifyObservers(element, elementId); |
| } |
| |
| RefPtr<Element> TreeScope::getElementByName(const AtomString& name) const |
| { |
| if (name.isEmpty()) |
| return nullptr; |
| if (!m_elementsByName) |
| return nullptr; |
| return m_elementsByName->getElementByName(name, *this); |
| } |
| |
| void TreeScope::addElementByName(const AtomString& name, Element& element) |
| { |
| if (!m_elementsByName) |
| m_elementsByName = makeUnique<TreeScopeOrderedMap>(); |
| m_elementsByName->add(name, element, *this); |
| } |
| |
| void TreeScope::removeElementByName(const AtomString& name, Element& element) |
| { |
| if (!m_elementsByName) |
| return; |
| m_elementsByName->remove(name, element); |
| } |
| |
| Ref<Node> TreeScope::retargetToScope(Node& node) const |
| { |
| Ref scope = node.treeScope(); |
| if (this == scope.ptr() || !node.isInShadowTree()) [[likely]] |
| return node; |
| ASSERT(is<ShadowRoot>(scope->rootNode())); |
| |
| Vector<TreeScope*, 8> nodeTreeScopes; |
| for (auto* currentScope = scope.ptr(); currentScope; currentScope = currentScope->parentTreeScope()) |
| nodeTreeScopes.append(currentScope); |
| ASSERT(nodeTreeScopes.size() >= 2); |
| |
| Vector<const TreeScope*, 8> ancestorScopes; |
| for (auto* currentScope = this; currentScope; currentScope = currentScope->parentTreeScope()) |
| ancestorScopes.append(currentScope); |
| |
| auto i = nodeTreeScopes.size(); |
| auto j = ancestorScopes.size(); |
| while (i > 0 && j > 0 && nodeTreeScopes[i - 1] == ancestorScopes[j - 1]) { |
| --i; |
| --j; |
| } |
| |
| bool nodeIsInOuterTreeScope = !i; |
| if (nodeIsInOuterTreeScope) |
| return node; |
| |
| auto& shadowRootInLowestCommonTreeScope = downcast<ShadowRoot>(nodeTreeScopes[i - 1]->rootNode()); |
| return *shadowRootInLowestCommonTreeScope.host(); |
| } |
| |
| Node* TreeScope::ancestorNodeInThisScope(SUPPRESS_UNCHECKED_ARG Node* node) const |
| { |
| for (; node; node = node->shadowHost()) { |
| if (&node->treeScope() == this) |
| return node; |
| if (!node->isInShadowTree()) |
| return nullptr; |
| } |
| return nullptr; |
| } |
| |
| Element* TreeScope::ancestorElementInThisScope(SUPPRESS_UNCHECKED_ARG Element* element) const |
| { |
| for (; element; element = element->shadowHost()) { |
| if (&element->treeScope() == this) |
| return element; |
| if (!element->isInShadowTree()) |
| return nullptr; |
| } |
| return nullptr; |
| } |
| |
| void TreeScope::addImageMap(HTMLMapElement& imageMap) |
| { |
| auto name = imageMap.getName(); |
| if (name.isNull()) |
| return; |
| if (!m_imageMapsByName) |
| m_imageMapsByName = makeUnique<TreeScopeOrderedMap>(); |
| m_imageMapsByName->add(name, imageMap, *this); |
| } |
| |
| void TreeScope::removeImageMap(HTMLMapElement& imageMap) |
| { |
| if (!m_imageMapsByName) |
| return; |
| auto name = imageMap.getName(); |
| if (name.isNull()) |
| return; |
| m_imageMapsByName->remove(name, imageMap); |
| } |
| |
| RefPtr<HTMLMapElement> TreeScope::getImageMap(const AtomString& name) const |
| { |
| if (!m_imageMapsByName || name.isNull()) |
| return nullptr; |
| return m_imageMapsByName->getElementByMapName(name, *this); |
| } |
| |
| void TreeScope::addImageElementByUsemap(const AtomString& name, HTMLImageElement& element) |
| { |
| if (!m_imagesByUsemap) |
| m_imagesByUsemap = makeUnique<TreeScopeOrderedMap>(); |
| return m_imagesByUsemap->add(name, element, *this); |
| } |
| |
| void TreeScope::removeImageElementByUsemap(const AtomString& name, HTMLImageElement& element) |
| { |
| if (!m_imagesByUsemap) |
| return; |
| m_imagesByUsemap->remove(name, element); |
| } |
| |
| RefPtr<HTMLImageElement> TreeScope::imageElementByUsemap(const AtomString& name) const |
| { |
| if (!m_imagesByUsemap) |
| return nullptr; |
| return m_imagesByUsemap->getElementByUsemap(name, *this); |
| } |
| |
| void TreeScope::addLabel(const AtomString& forAttributeValue, HTMLLabelElement& element) |
| { |
| ASSERT(m_labelsByForAttribute); |
| m_labelsByForAttribute->add(forAttributeValue, element, *this); |
| } |
| |
| void TreeScope::removeLabel(const AtomString& forAttributeValue, HTMLLabelElement& element) |
| { |
| ASSERT(m_labelsByForAttribute); |
| m_labelsByForAttribute->remove(forAttributeValue, element); |
| } |
| |
| const Vector<WeakRef<Element, WeakPtrImplWithEventTargetData>>* TreeScope::labelElementsForId(const AtomString& forAttributeValue) |
| { |
| if (forAttributeValue.isEmpty()) |
| return nullptr; |
| |
| if (!m_labelsByForAttribute) { |
| // Populate the map on first access. |
| m_labelsByForAttribute = makeUnique<TreeScopeOrderedMap>(); |
| |
| for (Ref label : descendantsOfType<HTMLLabelElement>(m_rootNode.get())) { |
| const AtomString& forValue = label->attributeWithoutSynchronization(forAttr); |
| if (!forValue.isEmpty()) |
| addLabel(forValue, label); |
| } |
| } |
| |
| return m_labelsByForAttribute->getElementsByLabelForAttribute(forAttributeValue, *this); |
| } |
| |
| static std::optional<LayoutPoint> absolutePointIfNotClipped(Document& document, const LayoutPoint& clientPoint) |
| { |
| if (!document.frame() || !document.view()) |
| return std::nullopt; |
| |
| const auto& settings = document.frame()->settings(); |
| if (settings.visualViewportEnabled() && settings.clientCoordinatesRelativeToLayoutViewport()) { |
| document.updateLayout(); |
| if (!document.view() || !document.hasLivingRenderTree()) |
| return std::nullopt; |
| RefPtr view = document.view(); |
| FloatPoint layoutViewportPoint = view->clientToLayoutViewportPoint(clientPoint); |
| FloatRect layoutViewportBounds({ }, view->layoutViewportRect().size()); |
| if (!layoutViewportBounds.contains(layoutViewportPoint)) |
| return std::nullopt; |
| return LayoutPoint(view->layoutViewportToAbsolutePoint(layoutViewportPoint)); |
| } |
| |
| RefPtr frame = document.frame(); |
| CheckedPtr view = document.view(); |
| float scaleFactor = frame->pageZoomFactor() * frame->frameScaleFactor(); |
| |
| LayoutPoint absolutePoint = clientPoint; |
| absolutePoint.scale(scaleFactor); |
| absolutePoint.moveBy(view->contentsScrollPosition()); |
| |
| LayoutRect visibleRect; |
| #if PLATFORM(IOS_FAMILY) |
| visibleRect = view->unobscuredContentRect(); |
| #else |
| visibleRect = view->visibleContentRect(); |
| #endif |
| if (visibleRect.contains(absolutePoint)) |
| return absolutePoint; |
| return std::nullopt; |
| } |
| |
| RefPtr<Node> TreeScope::nodeFromPoint(const LayoutPoint& clientPoint, LayoutPoint* localPoint, HitTestSource source) |
| { |
| Ref document = documentScope(); |
| auto absolutePoint = absolutePointIfNotClipped(document, clientPoint); |
| if (!absolutePoint) |
| return nullptr; |
| |
| HitTestResult result(absolutePoint.value()); |
| document->hitTest({ source, HitTestRequest::defaultTypes }, result); |
| if (localPoint) |
| *localPoint = result.localPoint(); |
| return result.innerNode(); |
| } |
| |
| RefPtr<Element> TreeScope::elementFromPoint(double clientX, double clientY, HitTestSource source) |
| { |
| if (!protectedDocumentScope()->hasLivingRenderTree()) |
| return nullptr; |
| |
| auto node = nodeFromPoint(LayoutPoint { clientX, clientY }, nullptr, source); |
| if (!node) |
| return nullptr; |
| |
| node = retargetToScope(*node); |
| while (!is<Element>(*node)) { |
| node = node->parentInComposedTree(); |
| if (!node) |
| break; |
| node = retargetToScope(*node); |
| } |
| |
| return uncheckedDowncast<Element>(WTF::move(node)); |
| } |
| |
| Vector<Ref<Element>> TreeScope::elementsFromPoint(double clientX, double clientY, HitTestSource source) |
| { |
| Vector<Ref<Element>> elements; |
| |
| Ref document = documentScope(); |
| if (!document->hasLivingRenderTree()) |
| return elements; |
| |
| auto absolutePoint = absolutePointIfNotClipped(document, LayoutPoint(clientX, clientY)); |
| if (!absolutePoint) |
| return elements; |
| |
| static constexpr OptionSet hitTypes { |
| HitTestRequest::Type::ReadOnly, |
| HitTestRequest::Type::Active, |
| HitTestRequest::Type::DisallowUserAgentShadowContent, |
| HitTestRequest::Type::CollectMultipleElements, |
| HitTestRequest::Type::IncludeAllElementsUnderPoint |
| }; |
| |
| HitTestResult result { absolutePoint.value() }; |
| document->hitTest({ source, hitTypes }, result); |
| |
| RefPtr<Node> lastNode; |
| auto& nodeSet = result.listBasedTestResult(); |
| elements.reserveInitialCapacity(nodeSet.size()); |
| for (auto& listBasedNode : nodeSet) { |
| RefPtr<Node> node = retargetToScope(listBasedNode); |
| while (!is<Element>(node)) { |
| node = node->parentInComposedTree(); |
| if (!node) |
| break; |
| node = retargetToScope(*node); |
| } |
| |
| if (!node) |
| continue; |
| |
| if (auto pseudoElement = dynamicDowncast<PseudoElement>(*node)) |
| node = pseudoElement->hostElement(); |
| |
| // Prune duplicate entries. A pseudo ::before content above its parent |
| // node should only result in one entry. |
| if (node == lastNode) |
| continue; |
| |
| elements.append(uncheckedDowncast<Element>(*node)); |
| lastNode = node; |
| } |
| |
| if (auto* rootDocument = dynamicDowncast<Document>(m_rootNode.get())) { |
| if (RefPtr rootElement = rootDocument->documentElement()) { |
| if (elements.isEmpty() || elements.last().ptr() != rootElement) |
| elements.append(*rootElement); |
| } |
| } |
| |
| return elements; |
| } |
| |
| // FIXME: Would be nice to change this to take a StringView, since that's what callers have |
| // and there is no particular advantage to already having a String. |
| RefPtr<Element> TreeScope::findAnchor(StringView name) |
| { |
| if (name.isEmpty()) |
| return nullptr; |
| if (RefPtr element = getElementById(name)) |
| return element; |
| Ref rootNode = m_rootNode.get(); |
| for (Ref anchor : descendantsOfType<HTMLAnchorElement>(rootNode)) { |
| if (isMatchingAnchor(anchor, name)) |
| return anchor; |
| } |
| return nullptr; |
| } |
| |
| bool TreeScope::isMatchingAnchor(HTMLAnchorElement& anchor, StringView name) |
| { |
| if (documentScope().inQuirksMode()) { |
| // Quirks mode, ASCII case-insensitive comparison of names. |
| // FIXME: This behavior is not mentioned in the HTML specification. |
| // We should either remove this or get this into the specification. |
| if (equalIgnoringASCIICase(anchor.name(), name)) |
| return true; |
| } else { |
| // Strict mode, names need to match exactly. |
| if (anchor.name() == name) |
| return true; |
| } |
| return false; |
| } |
| |
| static Element* focusedFrameOwnerElement(Frame* focusedFrame, LocalFrame* currentFrame) |
| { |
| for (; focusedFrame; focusedFrame = focusedFrame->tree().parent()) { |
| if (focusedFrame->tree().parent() == currentFrame) |
| return focusedFrame->ownerElement(); |
| } |
| return nullptr; |
| } |
| |
| Element* TreeScope::focusedElementInScope() |
| { |
| Ref document = documentScope(); |
| RefPtr element = document->focusedElement(); |
| |
| if (!element && document->page()) |
| element = focusedFrameOwnerElement(document->page()->focusController().focusedFrame(), document->frame()); |
| |
| return ancestorElementInThisScope(element.get()); |
| } |
| |
| #if ENABLE(POINTER_LOCK) |
| |
| Element* TreeScope::pointerLockElement() const |
| { |
| CheckedRef document = documentScope(); |
| RefPtr page = document->page(); |
| if (!page || page->pointerLockController().lockPending()) |
| return nullptr; |
| RefPtr element = page->pointerLockController().element(); |
| if (!element || &element->document() != document.ptr()) |
| return nullptr; |
| return ancestorElementInThisScope(element.get()); |
| } |
| |
| #endif |
| |
| static void listTreeScopes(Node* node, Vector<TreeScope*, 5>& treeScopes) |
| { |
| while (true) { |
| treeScopes.append(&node->treeScope()); |
| RefPtr ancestor = node->shadowHost(); |
| if (!ancestor) |
| break; |
| SUPPRESS_UNCHECKED_ARG(node) = ancestor.get(); |
| } |
| } |
| |
| TreeScope* commonTreeScope(Node* nodeA, Node* nodeB) |
| { |
| if (!nodeA || !nodeB) |
| return nullptr; |
| |
| if (&nodeA->treeScope() == &nodeB->treeScope()) |
| return &nodeA->treeScope(); |
| |
| Vector<TreeScope*, 5> treeScopesA; |
| listTreeScopes(nodeA, treeScopesA); |
| |
| Vector<TreeScope*, 5> treeScopesB; |
| listTreeScopes(nodeB, treeScopesB); |
| |
| size_t indexA = treeScopesA.size(); |
| size_t indexB = treeScopesB.size(); |
| |
| for (; indexA > 0 && indexB > 0 && treeScopesA[indexA - 1] == treeScopesB[indexB - 1]; --indexA, --indexB) { } |
| |
| // If the nodes had no common tree scope, return immediately. |
| if (indexA == treeScopesA.size()) |
| return nullptr; |
| |
| return treeScopesA[indexA] == treeScopesB[indexB] ? treeScopesA[indexA] : nullptr; |
| } |
| |
| RadioButtonGroups& TreeScope::radioButtonGroups() |
| { |
| if (!m_radioButtonGroups) |
| m_radioButtonGroups = makeUnique<RadioButtonGroups>(); |
| return *m_radioButtonGroups; |
| } |
| |
| CSSStyleSheetObservableArray& TreeScope::ensureAdoptedStyleSheets() |
| { |
| if (!m_adoptedStyleSheets) [[unlikely]] |
| m_adoptedStyleSheets = CSSStyleSheetObservableArray::create(m_rootNode.get()); |
| return *m_adoptedStyleSheets; |
| } |
| |
| std::span<const Ref<CSSStyleSheet>> TreeScope::adoptedStyleSheets() const |
| { |
| return m_adoptedStyleSheets ? m_adoptedStyleSheets->sheets().span() : std::span<const Ref<CSSStyleSheet>> { }; |
| } |
| |
| JSC::JSValue TreeScope::adoptedStyleSheetWrapper(JSDOMGlobalObject& lexicalGlobalObject) |
| { |
| return JSC::JSObservableArray::create(&lexicalGlobalObject, ensureAdoptedStyleSheets()); |
| } |
| |
| ExceptionOr<void> TreeScope::setAdoptedStyleSheets(Vector<Ref<CSSStyleSheet>>&& sheets) |
| { |
| if (!m_adoptedStyleSheets && sheets.isEmpty()) |
| return { }; |
| return ensureAdoptedStyleSheets().setSheets(WTF::move(sheets)); |
| } |
| |
| SVGResourcesMap& TreeScope::svgResourcesMap() const |
| { |
| if (!m_svgResourcesMap) |
| const_cast<TreeScope&>(*this).m_svgResourcesMap = makeUnique<SVGResourcesMap>(); |
| return *m_svgResourcesMap; |
| } |
| |
| void TreeScope::addSVGResource(const AtomString& id, LegacyRenderSVGResourceContainer& resource) |
| { |
| if (id.isEmpty()) |
| return; |
| |
| // Replaces resource if already present, to handle potential id changes |
| svgResourcesMap().legacyResources.set(id, &resource); |
| } |
| |
| void TreeScope::removeSVGResource(const AtomString& id, LegacyRenderSVGResourceContainer& resource) |
| { |
| if (id.isEmpty()) |
| return; |
| |
| auto& resources = svgResourcesMap().legacyResources; |
| auto it = resources.find(id); |
| if (it != resources.end() && it->value == &resource) |
| resources.remove(it); |
| } |
| |
| LegacyRenderSVGResourceContainer* TreeScope::lookupLegacySVGResoureById(const AtomString& id) const |
| { |
| if (id.isEmpty()) |
| return nullptr; |
| |
| if (auto resource = svgResourcesMap().legacyResources.get(id)) |
| return resource; |
| |
| return nullptr; |
| } |
| |
| void TreeScope::addPendingSVGResource(const AtomString& id, SVGElement& element) |
| { |
| if (id.isEmpty()) |
| return; |
| |
| auto result = svgResourcesMap().pendingResources.add(id, WeakHashSet<SVGElement, WeakPtrImplWithEventTargetData> { }); |
| result.iterator->value.add(element); |
| |
| element.setHasPendingResources(); |
| } |
| |
| bool TreeScope::isIdOfPendingSVGResource(const AtomString& id) const |
| { |
| if (id.isEmpty()) |
| return false; |
| |
| return svgResourcesMap().pendingResources.contains(id); |
| } |
| |
| bool TreeScope::isElementWithPendingSVGResources(SVGElement& element) const |
| { |
| // This algorithm takes time proportional to the number of pending resources and need not. |
| // If performance becomes an issue we can keep a counted set of elements and answer the question efficiently. |
| return std::ranges::any_of(svgResourcesMap().pendingResources.values(), [&] (const WeakSVGElementSet& set) { |
| return set.contains(element); |
| }); |
| } |
| |
| bool TreeScope::isPendingSVGResource(SVGElement& element, const AtomString& id) const |
| { |
| if (id.isEmpty()) |
| return false; |
| |
| auto& pendingResources = svgResourcesMap().pendingResources; |
| auto it = pendingResources.find(id); |
| if (it == pendingResources.end()) |
| return false; |
| |
| return it->value.contains(element); |
| } |
| |
| void TreeScope::clearHasPendingSVGResourcesIfPossible(SVGElement& element) |
| { |
| if (!isElementWithPendingSVGResources(element)) |
| element.clearHasPendingResources(); |
| } |
| |
| void TreeScope::removeElementFromPendingSVGResources(SVGElement& element) |
| { |
| if (!svgResourcesMap().pendingResources.isEmpty() && element.hasPendingResources()) { |
| Vector<AtomString> toBeRemoved; |
| for (auto& resource : svgResourcesMap().pendingResources) { |
| auto& elements = resource.value; |
| elements.remove(element); |
| if (elements.isEmptyIgnoringNullReferences()) |
| toBeRemoved.append(resource.key); |
| } |
| |
| clearHasPendingSVGResourcesIfPossible(element); |
| |
| // We use the removePendingResource function here because it deals with set lifetime correctly. |
| for (auto& resource : toBeRemoved) |
| removePendingSVGResource(resource); |
| } |
| |
| if (!svgResourcesMap().pendingResourcesForRemoval.isEmpty()) { |
| Vector<AtomString> toBeRemoved; |
| for (auto& resource : svgResourcesMap().pendingResourcesForRemoval) { |
| auto& elements = resource.value; |
| elements.remove(element); |
| if (elements.isEmptyIgnoringNullReferences()) |
| toBeRemoved.append(resource.key); |
| } |
| |
| // We use m_pendingResourcesForRemoval here because it deals with set lifetime correctly. |
| for (auto& resource : toBeRemoved) |
| svgResourcesMap().pendingResourcesForRemoval.remove(resource); |
| } |
| } |
| |
| WeakHashSet<SVGElement, WeakPtrImplWithEventTargetData> TreeScope::removePendingSVGResource(const AtomString& id) |
| { |
| return svgResourcesMap().pendingResources.take(id); |
| } |
| |
| void TreeScope::markPendingSVGResourcesForRemoval(const AtomString& id) |
| { |
| if (id.isEmpty()) |
| return; |
| |
| ASSERT(!svgResourcesMap().pendingResourcesForRemoval.contains(id)); |
| |
| auto existing = svgResourcesMap().pendingResources.take(id); |
| if (!existing.isEmptyIgnoringNullReferences()) |
| svgResourcesMap().pendingResourcesForRemoval.add(id, WTF::move(existing)); |
| } |
| |
| RefPtr<SVGElement> TreeScope::takeElementFromPendingSVGResourcesForRemovalMap(const AtomString& id) |
| { |
| if (id.isEmpty()) |
| return nullptr; |
| |
| auto it = svgResourcesMap().pendingResourcesForRemoval.find(id); |
| if (it == svgResourcesMap().pendingResourcesForRemoval.end()) |
| return nullptr; |
| |
| auto& resourceSet = it->value; |
| RefPtr firstElement = resourceSet.begin().get(); |
| if (!firstElement) |
| return nullptr; |
| |
| resourceSet.remove(*firstElement); |
| |
| if (resourceSet.isEmptyIgnoringNullReferences()) |
| svgResourcesMap().pendingResourcesForRemoval.remove(id); |
| |
| return firstElement; |
| } |
| |
| Ref<Document> TreeScope::protectedDocumentScope() const |
| { |
| return m_documentScope.get(); |
| } |
| |
| } // namespace WebCore |