void SVGUseElement::buildInstanceTree(SVGElement* target, SVGElementInstance* targetInstance, bool& foundProblem, bool foundUse) { ASSERT(target); ASSERT(targetInstance); // Spec: If the referenced object is itself a 'use', or if there are 'use' subelements within the referenced // object, the instance tree will contain recursive expansion of the indirect references to form a complete tree. bool targetHasUseTag = target->hasTagName(SVGNames::useTag); SVGElement* newTarget = 0; if (targetHasUseTag) { foundProblem = hasCycleUseReferencing(toSVGUseElement(target), targetInstance, newTarget); if (foundProblem) return; // We only need to track first degree <use> dependencies. Indirect references are handled // as the invalidation bubbles up the dependency chain. if (!foundUse) { document().accessSVGExtensions()->addElementReferencingTarget(this, target); foundUse = true; } } else if (isDisallowedElement(*target)) { foundProblem = true; return; } // A general description from the SVG spec, describing what buildInstanceTree() actually does. // // Spec: If the 'use' element references a 'g' which contains two 'rect' elements, then the instance tree // contains three SVGElementInstance objects, a root SVGElementInstance object whose correspondingElement // is the SVGGElement object for the 'g', and then two child SVGElementInstance objects, each of which has // its correspondingElement that is an SVGRectElement object. for (auto& element : childrenOfType<SVGElement>(*target)) { // Skip any non-svg nodes or any disallowed element. if (isDisallowedElement(element)) continue; // Create SVGElementInstance object, for both container/non-container nodes. RefPtr<SVGElementInstance> instance = SVGElementInstance::create(this, 0, &element); SVGElementInstance* instancePtr = instance.get(); targetInstance->appendChild(instance.release()); // Enter recursion, appending new instance tree nodes to the "instance" object. buildInstanceTree(&element, instancePtr, foundProblem, foundUse); if (foundProblem) return; } if (!targetHasUseTag || !newTarget) return; RefPtr<SVGElementInstance> newInstance = SVGElementInstance::create(this, toSVGUseElement(target), newTarget); SVGElementInstance* newInstancePtr = newInstance.get(); targetInstance->appendChild(newInstance.release()); buildInstanceTree(newTarget, newInstancePtr, foundProblem, foundUse); }
void SVGUseElement::expandUseElementsInShadowTree(SVGShadowTreeRootElement* shadowRoot, Node* element) { // Why expand the <use> elements in the shadow tree here, and not just // do this directly in buildShadowTree, if we encounter a <use> element? // // Short answer: Because we may miss to expand some elements. Ie. if a <symbol> // contains <use> tags, we'd miss them. So once we're done with settin' up the // actual shadow tree (after the special case modification for svg/symbol) we have // to walk it completely and expand all <use> elements. if (element->hasTagName(SVGNames::useTag)) { SVGUseElement* use = static_cast<SVGUseElement*>(element); String id = SVGURIReference::getTarget(use->href()); Element* targetElement = document()->getElementById(id); SVGElement* target = 0; if (targetElement && targetElement->isSVGElement()) target = static_cast<SVGElement*>(targetElement); // Don't ASSERT(target) here, it may be "pending", too. // Setup sub-shadow tree root node RefPtr<SVGShadowTreeContainerElement> cloneParent = new SVGShadowTreeContainerElement(document()); // Spec: In the generated content, the 'use' will be replaced by 'g', where all attributes from the // 'use' element except for x, y, width, height and xlink:href are transferred to the generated 'g' element. transferUseAttributesToReplacedElement(use, cloneParent.get()); ExceptionCode ec = 0; if (target && !isDisallowedElement(target)) { RefPtr<Element> newChild = target->cloneElementWithChildren(); // We don't walk the target tree element-by-element, and clone each element, // but instead use cloneElementWithChildren(). This is an optimization for the common // case where <use> doesn't contain disallowed elements (ie. <foreignObject>). // Though if there are disallowed elements in the subtree, we have to remove them. // For instance: <use> on <g> containing <foreignObject> (indirect case). if (subtreeContainsDisallowedElement(newChild.get())) removeDisallowedElementsFromSubtree(newChild.get()); SVGElement* newChildPtr = 0; if (newChild->isSVGElement()) newChildPtr = static_cast<SVGElement*>(newChild.get()); ASSERT(newChildPtr); cloneParent->appendChild(newChild.release(), ec); ASSERT(!ec); } // Replace <use> with referenced content. ASSERT(use->parentNode()); use->parentNode()->replaceChild(cloneParent.release(), use, ec); ASSERT(!ec); // Immediately stop here, and restart expanding. expandUseElementsInShadowTree(shadowRoot, shadowRoot); return; } for (RefPtr<Node> child = element->firstChild(); child; child = child->nextSibling()) expandUseElementsInShadowTree(shadowRoot, child.get()); }
void SVGUseElement::buildShadowTree(SVGShadowTreeRootElement* shadowRoot, SVGElement* target, SVGElementInstance* targetInstance) { // For instance <use> on <foreignObject> (direct case). if (isDisallowedElement(target)) return; RefPtr<Element> newChild = targetInstance->correspondingElement()->cloneElementWithChildren(); // We don't walk the target tree element-by-element, and clone each element, // but instead use cloneElementWithChildren(). This is an optimization for the common // case where <use> doesn't contain disallowed elements (ie. <foreignObject>). // Though if there are disallowed elements in the subtree, we have to remove them. // For instance: <use> on <g> containing <foreignObject> (indirect case). if (subtreeContainsDisallowedElement(newChild.get())) removeDisallowedElementsFromSubtree(newChild.get()); SVGElement* newChildPtr = 0; if (newChild->isSVGElement()) newChildPtr = static_cast<SVGElement*>(newChild.get()); ASSERT(newChildPtr); ExceptionCode ec = 0; shadowRoot->appendChild(newChild.release(), ec); ASSERT(!ec); }
void SVGUseElement::buildInstanceTree(SVGElement* target, SVGElementInstance* targetInstance, bool& foundProblem) { ASSERT(target); ASSERT(targetInstance); // A general description from the SVG spec, describing what buildInstanceTree() actually does. // // Spec: If the 'use' element references a 'g' which contains two 'rect' elements, then the instance tree // contains three SVGElementInstance objects, a root SVGElementInstance object whose correspondingElement // is the SVGGElement object for the 'g', and then two child SVGElementInstance objects, each of which has // its correspondingElement that is an SVGRectElement object. for (Node* node = target->firstChild(); node; node = node->nextSibling()) { SVGElement* element = 0; if (node->isSVGElement()) element = static_cast<SVGElement*>(node); // Skip any non-svg nodes or any disallowed element. if (!element || isDisallowedElement(element)) continue; // Create SVGElementInstance object, for both container/non-container nodes. RefPtr<SVGElementInstance> instance = SVGElementInstance::create(this, element); SVGElementInstance* instancePtr = instance.get(); targetInstance->appendChild(instance.release()); // Enter recursion, appending new instance tree nodes to the "instance" object. buildInstanceTree(element, instancePtr, foundProblem); } // Spec: If the referenced object is itself a 'use', or if there are 'use' subelements within the referenced // object, the instance tree will contain recursive expansion of the indirect references to form a complete tree. if (target->hasTagName(SVGNames::useTag)) handleDeepUseReferencing(static_cast<SVGUseElement*>(target), targetInstance, foundProblem); }
void SVGUseElement::buildShadowTree(SVGElement* target, SVGElementInstance* targetInstance) { // For instance <use> on <foreignObject> (direct case). if (isDisallowedElement(target)) return; RefPtr<Node> newChild = targetInstance->correspondingElement()->cloneNode(true); // We don't walk the target tree element-by-element, and clone each element, // but instead use cloneNode(deep=true). This is an optimization for the common // case where <use> doesn't contain disallowed elements (ie. <foreignObject>). // Though if there are disallowed elements in the subtree, we have to remove them. // For instance: <use> on <g> containing <foreignObject> (indirect case). if (subtreeContainsDisallowedElement(newChild.get())) removeDisallowedElementsFromSubtree(newChild.get()); SVGElement* newChildPtr = 0; if (newChild->isSVGElement()) newChildPtr = static_cast<SVGElement*>(newChild.get()); ASSERT(newChildPtr); ExceptionCode ec = 0; m_shadowTreeRootElement->appendChild(newChild.release(), ec); ASSERT(ec == 0); // Handle use referencing <svg> special case if (target->hasTagName(SVGNames::svgTag)) alterShadowTreeForSVGTag(newChildPtr); }
void SVGUseElement::buildShadowAndInstanceTree(SVGElement* target) { ASSERT(!m_targetElementInstance); ASSERT(!m_needsShadowTreeRecreation); // <use> creates a "user agent" shadow root. Do not build the shadow/instance tree for <use> // elements living in a user agent shadow tree because they will get expanded in a second // pass -- see expandUseElementsInShadowTree(). if (inUseShadowTree()) return; // Do not allow self-referencing. // 'target' may be null, if it's a non SVG namespaced element. if (!target || target == this || isDisallowedElement(target)) return; // Set up root SVG element in shadow tree. RefPtrWillBeRawPtr<Element> newChild = target->cloneElementWithoutChildren(); m_targetElementInstance = toSVGElement(newChild.get()); ShadowRoot* shadowTreeRootElement = userAgentShadowRoot(); shadowTreeRootElement->appendChild(newChild.release()); // Clone the target subtree into the shadow tree, not handling <use> and <symbol> yet. // SVG specification does not say a word about <use> & cycles. My view on this is: just ignore it! // Non-appearing <use> content is easier to debug, then half-appearing content. if (!buildShadowTree(target, m_targetElementInstance.get(), false)) { clearShadowTree(); return; } if (instanceTreeIsLoading(m_targetElementInstance.get())) return; // Assure shadow tree building was successfull ASSERT(m_targetElementInstance); ASSERT(m_targetElementInstance->correspondingUseElement() == this); ASSERT(m_targetElementInstance->correspondingElement() == target); // Expand all <use> elements in the shadow tree. // Expand means: replace the actual <use> element by what it references. if (!expandUseElementsInShadowTree(m_targetElementInstance.get())) { clearShadowTree(); return; } // Expand all <symbol> elements in the shadow tree. // Expand means: replace the actual <symbol> element by the <svg> element. expandSymbolElementsInShadowTree(toSVGElement(shadowTreeRootElement->firstChild())); m_targetElementInstance = toSVGElement(shadowTreeRootElement->firstChild()); transferUseWidthAndHeightIfNeeded(*this, m_targetElementInstance.get(), *m_targetElementInstance->correspondingElement()); ASSERT(m_targetElementInstance->parentNode() == shadowTreeRootElement); // Update relative length information. updateRelativeLengthsInformation(); }
void SVGUseElement::expandUseElementsInShadowTree(Node* element) { // Why expand the <use> elements in the shadow tree here, and not just // do this directly in buildShadowTree, if we encounter a <use> element? // // Short answer: Because we may miss to expand some elements. Ie. if a <symbol> // contains <use> tags, we'd miss them. So once we're done with settin' up the // actual shadow tree (after the special case modification for svg/symbol) we have // to walk it completely and expand all <use> elements. if (element->hasTagName(SVGNames::useTag)) { SVGUseElement* use = toSVGUseElement(element); ASSERT(!use->cachedDocumentIsStillLoading()); ASSERT(referencedDocument()); Element* targetElement = SVGURIReference::targetElementFromIRIString(use->href(), *referencedDocument()); SVGElement* target = 0; if (targetElement && targetElement->isSVGElement()) target = toSVGElement(targetElement); // Don't ASSERT(target) here, it may be "pending", too. // Setup sub-shadow tree root node RefPtr<SVGGElement> cloneParent = SVGGElement::create(SVGNames::gTag, *referencedDocument()); use->cloneChildNodes(cloneParent.get()); // Spec: In the generated content, the 'use' will be replaced by 'g', where all attributes from the // 'use' element except for x, y, width, height and xlink:href are transferred to the generated 'g' element. transferUseAttributesToReplacedElement(use, cloneParent.get()); if (target && !isDisallowedElement(*target)) { RefPtr<Element> newChild = target->cloneElementWithChildren(); ASSERT(newChild->isSVGElement()); cloneParent->appendChild(newChild.release()); } // We don't walk the target tree element-by-element, and clone each element, // but instead use cloneElementWithChildren(). This is an optimization for the common // case where <use> doesn't contain disallowed elements (ie. <foreignObject>). // Though if there are disallowed elements in the subtree, we have to remove them. // For instance: <use> on <g> containing <foreignObject> (indirect case). if (subtreeContainsDisallowedElement(*cloneParent)) removeDisallowedElementsFromSubtree(*cloneParent); RefPtr<Node> replacingElement(cloneParent.get()); // Replace <use> with referenced content. ASSERT(use->parentNode()); use->parentNode()->replaceChild(cloneParent.release(), use); // Expand the siblings because the *element* is replaced and we will // lose the sibling chain when we are back from recursion. element = replacingElement.get(); for (RefPtr<Node> sibling = element->nextSibling(); sibling; sibling = sibling->nextSibling()) expandUseElementsInShadowTree(sibling.get()); } for (RefPtr<Node> child = element->firstChild(); child; child = child->nextSibling()) expandUseElementsInShadowTree(child.get()); }
static bool subtreeContainsDisallowedElement(SVGElement& start) { for (auto& element : descendantsOfType<Element>(start)) { if (isDisallowedElement(element)) return true; } return false; }
static bool subtreeContainsDisallowedElement(Node* start) { if (isDisallowedElement(start)) return true; for (Node* cur = start->firstChild(); cur; cur = cur->nextSibling()) { if (subtreeContainsDisallowedElement(cur)) return true; } return false; }
bool SVGUseElement::buildShadowTree(SVGElement* target, SVGElement* targetInstance, bool foundUse) { ASSERT(target); ASSERT(targetInstance); // Spec: If the referenced object is itself a 'use', or if there are 'use' subelements within the referenced // object, the instance tree will contain recursive expansion of the indirect references to form a complete tree. if (isSVGUseElement(*target)) { // We only need to track first degree <use> dependencies. Indirect references are handled // as the invalidation bubbles up the dependency chain. if (!foundUse && !isStructurallyExternal()) { addReferenceTo(target); foundUse = true; } } else if (isDisallowedElement(target)) { return false; } targetInstance->setCorrespondingElement(target); if (EventTargetData* data = target->eventTargetData()) data->eventListenerMap.copyEventListenersNotCreatedFromMarkupToTarget(targetInstance); for (RefPtrWillBeRawPtr<Node> child = target->firstChild(); child; child = child->nextSibling()) { // Skip any disallowed element. if (isDisallowedElement(child.get())) continue; RefPtrWillBeRawPtr<Node> newChild = child->cloneNode(false); targetInstance->appendChild(newChild.get()); if (newChild->isSVGElement()) { // Enter recursion, appending new instance tree nodes to the "instance" object. if (!buildShadowTree(toSVGElement(child), toSVGElement(newChild), foundUse)) return false; } } return true; }
void SVGUseElement::removeDisallowedElementsFromSubtree(Node* subtree) { ASSERT(!subtree->inDocument()); ExceptionCode ec; Node* node = subtree->firstChild(); while (node) { if (isDisallowedElement(node)) { Node* next = node->traverseNextSibling(subtree); // The subtree is not in document so this won't generate events that could mutate the tree. node->parent()->removeChild(node, ec); node = next; } else node = node->traverseNextNode(subtree); } }
static inline void removeDisallowedElementsFromSubtree(Element& subtree) { ASSERT(!subtree.inDocument()); Element* element = ElementTraversal::firstWithin(subtree); while (element) { if (isDisallowedElement(element)) { Element* next = ElementTraversal::nextSkippingChildren(*element, &subtree); // The subtree is not in document so this won't generate events that could mutate the tree. element->parentNode()->removeChild(element); element = next; } else { element = ElementTraversal::next(*element, &subtree); } } }
static inline void removeDisallowedElementsFromSubtree(SVGElement& subtree) { ASSERT(!subtree.inDocument()); Vector<Element*> toRemove; auto it = descendantsOfType<Element>(subtree).begin(); auto end = descendantsOfType<Element>(subtree).end(); while (it != end) { if (isDisallowedElement(*it)) { toRemove.append(&*it); it.traverseNextSkippingChildren(); continue; } ++it; } // The subtree is not in document so this won't generate events that could mutate the tree. for (unsigned i = 0; i < toRemove.size(); ++i) toRemove[i]->parentNode()->removeChild(toRemove[i]); }
void SVGUseElement::buildShadowTree(SVGElement* target, SVGElementInstance* targetInstance) { ASSERT(target); // FIXME: Don't be a pointer! // For instance <use> on <foreignObject> (direct case). if (isDisallowedElement(*target)) return; RefPtr<SVGElement> newChild = static_pointer_cast<SVGElement>(targetInstance->correspondingElement()->cloneElementWithChildren()); // We don't walk the target tree element-by-element, and clone each element, // but instead use cloneElementWithChildren(). This is an optimization for the common // case where <use> doesn't contain disallowed elements (ie. <foreignObject>). // Though if there are disallowed elements in the subtree, we have to remove them. // For instance: <use> on <g> containing <foreignObject> (indirect case). if (subtreeContainsDisallowedElement(*newChild)) removeDisallowedElementsFromSubtree(*newChild); shadowRoot()->appendChild(newChild.release()); }
bool SVGUseElement::expandUseElementsInShadowTree(SVGElement* element) { ASSERT(element); // Why expand the <use> elements in the shadow tree here, and not just // do this directly in buildShadowTree, if we encounter a <use> element? // // Short answer: Because we may miss to expand some elements. For example, if a <symbol> // contains <use> tags, we'd miss them. So once we're done with setting up the // actual shadow tree (after the special case modification for svg/symbol) we have // to walk it completely and expand all <use> elements. if (isSVGUseElement(*element)) { SVGUseElement* use = toSVGUseElement(element); ASSERT(!use->resourceIsStillLoading()); SVGElement* target = 0; if (hasCycleUseReferencing(toSVGUseElement(use->correspondingElement()), use, target)) return false; if (target && isDisallowedElement(target)) return false; // Don't ASSERT(target) here, it may be "pending", too. // Setup sub-shadow tree root node RefPtrWillBeRawPtr<SVGGElement> cloneParent = SVGGElement::create(referencedScope()->document()); cloneParent->setCorrespondingElement(use->correspondingElement()); // Move already cloned elements to the new <g> element for (RefPtrWillBeRawPtr<Node> child = use->firstChild(); child; ) { RefPtrWillBeRawPtr<Node> nextChild = child->nextSibling(); cloneParent->appendChild(child); child = nextChild.release(); } // Spec: In the generated content, the 'use' will be replaced by 'g', where all attributes from the // 'use' element except for x, y, width, height and xlink:href are transferred to the generated 'g' element. transferUseAttributesToReplacedElement(use, cloneParent.get()); if (target) { RefPtrWillBeRawPtr<Node> newChild = cloneNodeAndAssociate(*target); ASSERT(newChild->isSVGElement()); transferUseWidthAndHeightIfNeeded(*use, toSVGElement(newChild.get()), *target); cloneParent->appendChild(newChild.release()); } // We don't walk the target tree element-by-element, and clone each element, // but instead use cloneElementWithChildren(). This is an optimization for the common // case where <use> doesn't contain disallowed elements (ie. <foreignObject>). // Though if there are disallowed elements in the subtree, we have to remove them. // For instance: <use> on <g> containing <foreignObject> (indirect case). if (subtreeContainsDisallowedElement(cloneParent.get())) removeDisallowedElementsFromSubtree(*cloneParent); RefPtrWillBeRawPtr<SVGElement> replacingElement(cloneParent.get()); // Replace <use> with referenced content. ASSERT(use->parentNode()); use->parentNode()->replaceChild(cloneParent.release(), use); // Expand the siblings because the *element* is replaced and we will // lose the sibling chain when we are back from recursion. element = replacingElement.get(); for (RefPtrWillBeRawPtr<SVGElement> sibling = Traversal<SVGElement>::nextSibling(*element); sibling; sibling = Traversal<SVGElement>::nextSibling(*sibling)) { if (!expandUseElementsInShadowTree(sibling.get())) return false; } } for (RefPtrWillBeRawPtr<SVGElement> child = Traversal<SVGElement>::firstChild(*element); child; child = Traversal<SVGElement>::nextSibling(*child)) { if (!expandUseElementsInShadowTree(child.get())) return false; } return true; }
void SVGUseElement::expandUseElementsInShadowTree(Node* element) { // Why expand the <use> elements in the shadow tree here, and not just // do this directly in buildShadowTree, if we encounter a <use> element? // // Short answer: Because we may miss to expand some elements. Ie. if a <symbol> // contains <use> tags, we'd miss them. So once we're done with settin' up the // actual shadow tree (after the special case modification for svg/symbol) we have // to walk it completely and expand all <use> elements. if (element->hasTagName(SVGNames::useTag)) { SVGUseElement* use = static_cast<SVGUseElement*>(element); String id = SVGURIReference::getTarget(use->href()); Element* targetElement = document()->getElementById(id); SVGElement* target = 0; if (targetElement && targetElement->isSVGElement()) target = static_cast<SVGElement*>(targetElement); // Don't ASSERT(target) here, it may be "pending", too. if (target) { // Setup sub-shadow tree root node RefPtr<SVGElement> cloneParent = new SVGGElement(SVGNames::gTag, document()); // Spec: In the generated content, the 'use' will be replaced by 'g', where all attributes from the // 'use' element except for x, y, width, height and xlink:href are transferred to the generated 'g' element. transferUseAttributesToReplacedElement(use, cloneParent.get()); // Spec: An additional transformation translate(x,y) is appended to the end // (i.e., right-side) of the transform attribute on the generated 'g', where x // and y represent the values of the x and y attributes on the 'use' element. if (use->x().value() != 0.0 || use->y().value() != 0.0) { if (!cloneParent->hasAttribute(SVGNames::transformAttr)) { String transformString = String::format("translate(%f, %f)", use->x().value(), use->y().value()); cloneParent->setAttribute(SVGNames::transformAttr, transformString); } else { String transformString = String::format(" translate(%f, %f)", use->x().value(), use->y().value()); const AtomicString& transformAttribute = cloneParent->getAttribute(SVGNames::transformAttr); cloneParent->setAttribute(SVGNames::transformAttr, transformAttribute + transformString); } } ExceptionCode ec = 0; // For instance <use> on <foreignObject> (direct case). if (isDisallowedElement(target)) { // We still have to setup the <use> replacment (<g>). Otherwhise // associateInstancesWithShadowTreeElements() makes wrong assumptions. // Replace <use> with referenced content. ASSERT(use->parentNode()); use->parentNode()->replaceChild(cloneParent.release(), use, ec); ASSERT(ec == 0); return; } RefPtr<Node> newChild = target->cloneNode(true); // We don't walk the target tree element-by-element, and clone each element, // but instead use cloneNode(deep=true). This is an optimization for the common // case where <use> doesn't contain disallowed elements (ie. <foreignObject>). // Though if there are disallowed elements in the subtree, we have to remove them. // For instance: <use> on <g> containing <foreignObject> (indirect case). if (subtreeContainsDisallowedElement(newChild.get())) removeDisallowedElementsFromSubtree(newChild.get()); SVGElement* newChildPtr = 0; if (newChild->isSVGElement()) newChildPtr = static_cast<SVGElement*>(newChild.get()); ASSERT(newChildPtr); cloneParent->appendChild(newChild.release(), ec); ASSERT(ec == 0); // Replace <use> with referenced content. ASSERT(use->parentNode()); use->parentNode()->replaceChild(cloneParent.release(), use, ec); ASSERT(ec == 0); // Handle use referencing <svg> special case if (target->hasTagName(SVGNames::svgTag)) alterShadowTreeForSVGTag(newChildPtr); // Immediately stop here, and restart expanding. expandUseElementsInShadowTree(m_shadowTreeRootElement.get()); return; } } for (RefPtr<Node> child = element->firstChild(); child; child = child->nextSibling()) expandUseElementsInShadowTree(child.get()); }