bool RenderSVGTransformableContainer::calculateLocalTransform()
{
    SVGGraphicsElement& element = graphicsElement();

    // If we're either the renderer for a <use> element, or for any <g> element inside the shadow
    // tree, that was created during the use/symbol/svg expansion in SVGUseElement. These containers
    // need to respect the translations induced by their corresponding use elements x/y attributes.
    SVGUseElement* useElement = 0;
    if (isSVGUseElement(element))
        useElement = &toSVGUseElement(element);
    else if (element.isInShadowTree() && isSVGGElement(element)) {
        SVGElement* correspondingElement = element.correspondingElement();
        if (correspondingElement && isSVGUseElement(correspondingElement))
            useElement = toSVGUseElement(correspondingElement);
    }

    if (useElement) {
        SVGLengthContext lengthContext(useElement);
        FloatSize translation(useElement->x().value(lengthContext), useElement->y().value(lengthContext));
        if (translation != m_lastTranslation)
            m_needsTransformUpdate = true;
        m_lastTranslation = translation;
    }

    m_didTransformToRootUpdate = m_needsTransformUpdate || SVGRenderSupport::transformToRootChanged(parent());
    if (!m_needsTransformUpdate)
        return false;

    m_localTransform = element.animatedLocalTransform();
    m_localTransform.translate(m_lastTranslation.width(), m_lastTranslation.height());
    m_needsTransformUpdate = false;
    return true;
}
示例#2
0
String SVGElement::title() const
{
    // According to spec, we should not return titles when hovering over root <svg> elements (those
    // <title> elements are the title of the document, not a tooltip) so we instantly return.
    if (isOutermostSVGSVGElement())
        return String();

    // Walk up the tree, to find out whether we're inside a <use> shadow tree, to find the right title.
    if (isInShadowTree()) {
        Element* shadowHostElement = toShadowRoot(treeScope().rootNode()).host();
        // At this time, SVG nodes are not allowed in non-<use> shadow trees, so any shadow root we do
        // have should be a use. The assert and following test is here to catch future shadow DOM changes
        // that do enable SVG in a shadow tree.
        ASSERT(!shadowHostElement || isSVGUseElement(*shadowHostElement));
        if (isSVGUseElement(shadowHostElement)) {
            SVGUseElement& useElement = toSVGUseElement(*shadowHostElement);

            // If the <use> title is not empty we found the title to use.
            String useTitle(useElement.title());
            if (!useTitle.isEmpty())
                return useTitle;
        }
    }

    // If we aren't an instance in a <use> or the <use> title was not found, then find the first
    // <title> child of this element.
    // If a title child was found, return the text contents.
    if (Element* titleElement = Traversal<SVGTitleElement>::firstChild(*this))
        return titleElement->innerText();

    // Otherwise return a null/empty string.
    return String();
}
void SVGElement::buildPendingResourcesIfNeeded()
{
    Document& document = this->document();
    if (!needsPendingResourceHandling() || !inDocument() || inUseShadowTree())
        return;

    SVGDocumentExtensions& extensions = document.accessSVGExtensions();
    AtomicString resourceId = getIdAttribute();
    if (!extensions.hasPendingResource(resourceId))
        return;

    // Mark pending resources as pending for removal.
    extensions.markPendingResourcesForRemoval(resourceId);

    // Rebuild pending resources for each client of a pending resource that is being removed.
    while (Element* clientElement = extensions.removeElementFromPendingResourcesForRemoval(resourceId)) {
        ASSERT(clientElement->hasPendingResources());
        if (clientElement->hasPendingResources()) {
            // FIXME: Ideally we'd always resolve pending resources async instead of inside
            // insertedInto and svgAttributeChanged. For now we only do it for <use> since
            // that would stamp out DOM.
            if (isSVGUseElement(clientElement))
                toSVGUseElement(clientElement)->invalidateShadowTree();
            else
                clientElement->buildPendingResource();
            extensions.clearHasPendingResourcesIfPossible(clientElement);
        }
    }
}
bool LayoutSVGResourceClipper::hitTestClipContent(const FloatRect& objectBoundingBox, const FloatPoint& nodeAtPoint)
{
    FloatPoint point = nodeAtPoint;
    if (!SVGLayoutSupport::pointInClippingArea(this, point))
        return false;

    if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
        AffineTransform transform;
        transform.translate(objectBoundingBox.x(), objectBoundingBox.y());
        transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
        point = transform.inverse().mapPoint(point);
    }

    AffineTransform animatedLocalTransform = toSVGClipPathElement(element())->calculateAnimatedLocalTransform();
    if (!animatedLocalTransform.isInvertible())
        return false;

    point = animatedLocalTransform.inverse().mapPoint(point);

    for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
        LayoutObject* layoutObject = childElement->layoutObject();
        if (!layoutObject)
            continue;
        if (!layoutObject->isSVGShape() && !layoutObject->isSVGText() && !isSVGUseElement(*childElement))
            continue;
        IntPoint hitPoint;
        HitTestResult result(HitTestRequest::SVGClipContent, hitPoint);
        if (layoutObject->nodeAtFloatPoint(result, point, HitTestForeground))
            return true;
    }

    return false;
}
// One of the element types that can cause graphics to be drawn onto the target canvas.
// Specifically: circle, ellipse, image, line, path, polygon, polyline, rect, text and use.
static bool isIntersectionOrEnclosureTarget(LayoutObject* layoutObject)
{
    return layoutObject->isSVGShape()
        || layoutObject->isSVGText()
        || layoutObject->isSVGImage()
        || isSVGUseElement(*layoutObject->node());
}
SVGUseElement* SVGElement::correspondingUseElement() const
{
    if (ShadowRoot* root = containingShadowRoot()) {
        if (isSVGUseElement(root->host()) && (root->type() == ShadowRootType::UserAgent))
            return toSVGUseElement(root->host());
    }
    return nullptr;
}
bool SVGUseElement::instanceTreeIsLoading(const SVGElement* targetInstance)
{
    for (const SVGElement* element = targetInstance; element; element = Traversal<SVGElement>::next(*element, targetInstance)) {
        if (isSVGUseElement(*element) && toSVGUseElement(*element).resourceIsStillLoading())
            return true;
    }
    return false;
}
void RenderSVGResourceClipper::createDisplayList(GraphicsContext* context,
    const AffineTransform& contentTransformation)
{
    ASSERT(context);
    ASSERT(frame());

    // Using strokeBoundingBox (instead of paintInvalidationRectInLocalCoordinates) to avoid the intersection
    // with local clips/mask, which may yield incorrect results when mixing objectBoundingBox and
    // userSpaceOnUse units (http://crbug.com/294900).
    FloatRect bounds = strokeBoundingBox();
    context->beginRecording(bounds);

    // Switch to a paint behavior where all children of this <clipPath> will be rendered using special constraints:
    // - fill-opacity/stroke-opacity/opacity set to 1
    // - masker/filter not applied when rendering the children
    // - fill is set to the initial fill paint server (solid, black)
    // - stroke is set to the initial stroke paint server (none)
    PaintBehavior oldBehavior = frame()->view()->paintBehavior();
    frame()->view()->setPaintBehavior(oldBehavior | PaintBehaviorRenderingSVGMask);

    for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
        RenderObject* renderer = childElement->renderer();
        if (!renderer)
            continue;

        RenderStyle* style = renderer->style();
        if (!style || style->display() == NONE || style->visibility() != VISIBLE)
            continue;

        WindRule newClipRule = style->svgStyle().clipRule();
        bool isUseElement = isSVGUseElement(*childElement);
        if (isUseElement) {
            SVGUseElement& useElement = toSVGUseElement(*childElement);
            renderer = useElement.rendererClipChild();
            if (!renderer)
                continue;
            if (!useElement.hasAttribute(SVGNames::clip_ruleAttr))
                newClipRule = renderer->style()->svgStyle().clipRule();
        }

        // Only shapes, paths and texts are allowed for clipping.
        if (!renderer->isSVGShape() && !renderer->isSVGText())
            continue;

        context->setFillRule(newClipRule);

        if (isUseElement)
            renderer = childElement->renderer();

        SVGRenderingContext::renderSubtree(context, renderer, contentTransformation);
    }

    frame()->view()->setPaintBehavior(oldBehavior);

    m_clipContentDisplayList = context->endRecording();
}
bool LayoutSVGTransformableContainer::calculateLocalTransform()
{
    SVGGraphicsElement* element = toSVGGraphicsElement(this->element());
    ASSERT(element);

    // If we're either the layoutObject for a <use> element, or for any <g> element inside the shadow
    // tree, that was created during the use/symbol/svg expansion in SVGUseElement. These containers
    // need to respect the translations induced by their corresponding use elements x/y attributes.
    SVGUseElement* useElement = nullptr;
    if (isSVGUseElement(*element)) {
        useElement = toSVGUseElement(element);
    } else if (isSVGGElement(*element) && toSVGGElement(element)->inUseShadowTree()) {
        SVGElement* correspondingElement = element->correspondingElement();
        if (isSVGUseElement(correspondingElement))
            useElement = toSVGUseElement(correspondingElement);
    }

    if (useElement) {
        SVGLengthContext lengthContext(useElement);
        FloatSize translation(
            useElement->x()->currentValue()->value(lengthContext),
            useElement->y()->currentValue()->value(lengthContext));
        if (translation != m_additionalTranslation)
            m_needsTransformUpdate = true;
        m_additionalTranslation = translation;
    }

    m_didTransformToRootUpdate = m_needsTransformUpdate || SVGLayoutSupport::transformToRootChanged(parent());
    if (!m_needsTransformUpdate)
        return false;

    m_localTransform = element->calculateAnimatedLocalTransform();
    m_localTransform.translate(m_additionalTranslation.width(), m_additionalTranslation.height());
    m_needsTransformUpdate = false;
    return true;
}
void LayoutSVGResourceClipper::calculateClipContentPaintInvalidationRect()
{
    // This is a rough heuristic to appraise the clip size and doesn't consider clip on clip.
    for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
        LayoutObject* layoutObject = childElement->layoutObject();
        if (!layoutObject)
            continue;
        if (!layoutObject->isSVGShape() && !layoutObject->isSVGText() && !isSVGUseElement(*childElement))
            continue;
        const ComputedStyle* style = layoutObject->style();
        if (!style || style->display() == NONE || style->visibility() != VISIBLE)
            continue;
        m_clipBoundaries.unite(layoutObject->localToParentTransform().mapRect(layoutObject->paintInvalidationRectInLocalCoordinates()));
    }
    m_clipBoundaries = toSVGClipPathElement(element())->calculateAnimatedLocalTransform().mapRect(m_clipBoundaries);
}
示例#11
0
EventTarget* EventPath::eventTargetRespectingTargetRules(Node* referenceNode)
{
    ASSERT(referenceNode);

    if (referenceNode->isPseudoElement())
        return referenceNode->parentNode();

    if (!usesDeprecatedSVGUseTreeEventRules(referenceNode))
        return referenceNode;

    // Spec: The event handling for the non-exposed tree works as if the referenced element had been textually included
    // as a deeply cloned child of the 'use' element, except that events are dispatched to the SVGElementInstance objects.
    Node& rootNode = referenceNode->treeScope().rootNode();
    Element* shadowHostElement = rootNode.isShadowRoot() ? toShadowRoot(rootNode).host() : 0;
    // At this time, SVG nodes are not supported in non-<use> shadow trees.
    if (!isSVGUseElement(shadowHostElement))
        return referenceNode;
    SVGUseElement& useElement = toSVGUseElement(*shadowHostElement);
    if (SVGElementInstance* instance = useElement.instanceForShadowTreeElement(referenceNode))
        return instance;

    return referenceNode;
}
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;
}
bool LayoutSVGResourceClipper::calculateClipContentPathIfNeeded()
{
    if (!m_clipContentPath.isEmpty())
        return true;

    // If the current clip-path gets clipped itself, we have to fallback to masking.
    if (style()->svgStyle().hasClipper())
        return false;

    unsigned opCount = 0;
    bool usingBuilder = false;
    SkOpBuilder clipPathBuilder;

    for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
        LayoutObject* childLayoutObject = childElement->layoutObject();
        if (!childLayoutObject)
            continue;
        // Only shapes or paths are supported for direct clipping. We need to fallback to masking for texts.
        if (childLayoutObject->isSVGText()) {
            m_clipContentPath.clear();
            return false;
        }
        if (!childElement->isSVGGraphicsElement())
            continue;

        const ComputedStyle* style = childLayoutObject->style();
        if (!style || style->display() == NONE || style->visibility() != VISIBLE)
            continue;

        // Current shape in clip-path gets clipped too. Fallback to masking.
        if (style->svgStyle().hasClipper()) {
            m_clipContentPath.clear();
            return false;
        }

        // First clip shape.
        if (m_clipContentPath.isEmpty()) {
            if (isSVGGeometryElement(childElement))
                toSVGGeometryElement(childElement)->toClipPath(m_clipContentPath);
            else if (isSVGUseElement(childElement))
                toSVGUseElement(childElement)->toClipPath(m_clipContentPath);

            continue;
        }

        // Multiple shapes require PathOps. In some degenerate cases PathOps can exhibit quadratic
        // behavior, so we cap the number of ops to a reasonable count.
        const unsigned kMaxOps = 42;
        if (!RuntimeEnabledFeatures::pathOpsSVGClippingEnabled() || ++opCount > kMaxOps) {
            m_clipContentPath.clear();
            return false;
        }

        // Second clip shape => start using the builder.
        if (!usingBuilder) {
            clipPathBuilder.add(m_clipContentPath.skPath(), kUnion_SkPathOp);
            usingBuilder = true;
        }

        Path subPath;
        if (isSVGGeometryElement(childElement))
            toSVGGeometryElement(childElement)->toClipPath(subPath);
        else if (isSVGUseElement(childElement))
            toSVGUseElement(childElement)->toClipPath(subPath);

        clipPathBuilder.add(subPath.skPath(), kUnion_SkPathOp);
    }

    if (usingBuilder) {
        SkPath resolvedPath;
        clipPathBuilder.resolve(&resolvedPath);
        m_clipContentPath = resolvedPath;
    }

    return true;
}
PassRefPtr<const SkPicture> LayoutSVGResourceClipper::createContentPicture(AffineTransform& contentTransformation, const FloatRect& targetBoundingBox,
    GraphicsContext& context)
{
    ASSERT(frame());

    if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
        contentTransformation.translate(targetBoundingBox.x(), targetBoundingBox.y());
        contentTransformation.scaleNonUniform(targetBoundingBox.width(), targetBoundingBox.height());
    }

    if (m_clipContentPicture)
        return m_clipContentPicture;

    SubtreeContentTransformScope contentTransformScope(contentTransformation);

    // Using strokeBoundingBox (instead of paintInvalidationRectInLocalCoordinates) to avoid the intersection
    // with local clips/mask, which may yield incorrect results when mixing objectBoundingBox and
    // userSpaceOnUse units (http://crbug.com/294900).
    FloatRect bounds = strokeBoundingBox();

    SkPictureBuilder pictureBuilder(bounds, nullptr, &context);

    for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
        LayoutObject* layoutObject = childElement->layoutObject();
        if (!layoutObject)
            continue;

        const ComputedStyle* style = layoutObject->style();
        if (!style || style->display() == NONE || style->visibility() != VISIBLE)
            continue;

        bool isUseElement = isSVGUseElement(*childElement);
        if (isUseElement) {
            const SVGGraphicsElement* clippingElement = toSVGUseElement(*childElement).targetGraphicsElementForClipping();
            if (!clippingElement)
                continue;

            layoutObject = clippingElement->layoutObject();
            if (!layoutObject)
                continue;
        }

        // Only shapes, paths and texts are allowed for clipping.
        if (!layoutObject->isSVGShape() && !layoutObject->isSVGText())
            continue;

        if (isUseElement)
            layoutObject = childElement->layoutObject();

        // Switch to a paint behavior where all children of this <clipPath> will be laid out using special constraints:
        // - fill-opacity/stroke-opacity/opacity set to 1
        // - masker/filter not applied when laying out the children
        // - fill is set to the initial fill paint server (solid, black)
        // - stroke is set to the initial stroke paint server (none)
        PaintInfo info(pictureBuilder.context(), LayoutRect::infiniteIntRect(), PaintPhaseForeground, GlobalPaintNormalPhase, PaintLayerPaintingRenderingClipPathAsMask);
        layoutObject->paint(info, IntPoint());
    }

    m_clipContentPicture = pictureBuilder.endRecording();
    return m_clipContentPicture;
}
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;
}