blob: 64a7c9538b58e230730ba2161d02a0c64b1b6bd2 [file] [log] [blame]
/*
* Copyright (C) 2004, 2005, 2006, 2007, 2008 Nikolas Zimmermann
* <zimmermann@kde.org>
* Copyright (C) 2004, 2005, 2006, 2007 Rob Buis <buis@kde.org>
* Copyright (C) Research In Motion Limited 2009-2010. All rights reserved.
* Copyright (C) 2011 Torch Mobile (Beijing) Co. Ltd. All rights reserved.
* Copyright (C) 2012 University of Szeged
* Copyright (C) 2012 Renata Hodovan <reni@webkit.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "core/svg/SVGUseElement.h"
#include "core/css/StyleChangeReason.h"
#include "core/dom/Document.h"
#include "core/dom/ElementShadow.h"
#include "core/dom/ElementTraversal.h"
#include "core/dom/IdTargetObserver.h"
#include "core/dom/ShadowRoot.h"
#include "core/dom/events/Event.h"
#include "core/layout/svg/LayoutSVGTransformableContainer.h"
#include "core/svg/SVGGElement.h"
#include "core/svg/SVGLengthContext.h"
#include "core/svg/SVGSVGElement.h"
#include "core/svg/SVGSymbolElement.h"
#include "core/svg/SVGTitleElement.h"
#include "core/svg_names.h"
#include "core/xlink_names.h"
#include "core/xml/parser/XMLDocumentParser.h"
#include "platform/loader/fetch/FetchParameters.h"
#include "platform/loader/fetch/ResourceFetcher.h"
#include "platform/loader/fetch/ResourceLoaderOptions.h"
#include "platform/wtf/Vector.h"
#include "public/platform/TaskType.h"
namespace blink {
inline SVGUseElement::SVGUseElement(Document& document)
: SVGGraphicsElement(SVGNames::useTag, document),
SVGURIReference(this),
x_(SVGAnimatedLength::Create(this,
SVGNames::xAttr,
SVGLength::Create(SVGLengthMode::kWidth),
CSSPropertyX)),
y_(SVGAnimatedLength::Create(this,
SVGNames::yAttr,
SVGLength::Create(SVGLengthMode::kHeight),
CSSPropertyY)),
width_(
SVGAnimatedLength::Create(this,
SVGNames::widthAttr,
SVGLength::Create(SVGLengthMode::kWidth))),
height_(
SVGAnimatedLength::Create(this,
SVGNames::heightAttr,
SVGLength::Create(SVGLengthMode::kHeight))),
element_url_is_local_(true),
have_fired_load_event_(false),
needs_shadow_tree_recreation_(false) {
DCHECK(HasCustomStyleCallbacks());
AddToPropertyMap(x_);
AddToPropertyMap(y_);
AddToPropertyMap(width_);
AddToPropertyMap(height_);
}
SVGUseElement* SVGUseElement::Create(Document& document) {
// Always build a user agent #shadow-root for SVGUseElement.
SVGUseElement* use = new SVGUseElement(document);
use->EnsureShadow().AddShadowRoot(*use, ShadowRootType::kClosed);
return use;
}
SVGUseElement::~SVGUseElement() = default;
void SVGUseElement::Dispose() {
ClearResource();
}
void SVGUseElement::Trace(blink::Visitor* visitor) {
visitor->Trace(x_);
visitor->Trace(y_);
visitor->Trace(width_);
visitor->Trace(height_);
visitor->Trace(target_element_instance_);
visitor->Trace(target_id_observer_);
SVGGraphicsElement::Trace(visitor);
SVGURIReference::Trace(visitor);
ResourceClient::Trace(visitor);
}
#if DCHECK_IS_ON()
static inline bool IsWellFormedDocument(Document* document) {
if (document->IsXMLDocument())
return static_cast<XMLDocumentParser*>(document->Parser())->WellFormed();
return true;
}
#endif
Node::InsertionNotificationRequest SVGUseElement::InsertedInto(
ContainerNode* root_parent) {
// This functions exists to assure assumptions made in the code regarding
// SVGElementInstance creation/destruction are satisfied.
SVGGraphicsElement::InsertedInto(root_parent);
if (!root_parent->isConnected())
return kInsertionDone;
#if DCHECK_IS_ON()
DCHECK(!target_element_instance_ || !IsWellFormedDocument(&GetDocument()));
DCHECK(!HasPendingResources() || !IsWellFormedDocument(&GetDocument()));
#endif
InvalidateShadowTree();
return kInsertionDone;
}
void SVGUseElement::RemovedFrom(ContainerNode* root_parent) {
SVGGraphicsElement::RemovedFrom(root_parent);
if (root_parent->isConnected()) {
ClearResourceReference();
CancelShadowTreeRecreation();
}
}
static void TransferUseWidthAndHeightIfNeeded(
const SVGUseElement& use,
SVGElement& shadow_element,
const SVGElement& original_element) {
DEFINE_STATIC_LOCAL(const AtomicString, hundred_percent_string, ("100%"));
// Use |originalElement| for checking the element type, because we will
// have replaced a <symbol> with an <svg> in the instance tree.
if (IsSVGSymbolElement(original_element)) {
// Spec (<use> on <symbol>): This generated 'svg' will always have
// explicit values for attributes width and height. If attributes
// width and/or height are provided on the 'use' element, then these
// attributes will be transferred to the generated 'svg'. If attributes
// width and/or height are not specified, the generated 'svg' element
// will use values of 100% for these attributes.
shadow_element.setAttribute(
SVGNames::widthAttr,
use.width()->IsSpecified()
? AtomicString(use.width()->CurrentValue()->ValueAsString())
: hundred_percent_string);
shadow_element.setAttribute(
SVGNames::heightAttr,
use.height()->IsSpecified()
? AtomicString(use.height()->CurrentValue()->ValueAsString())
: hundred_percent_string);
} else if (IsSVGSVGElement(original_element)) {
// Spec (<use> on <svg>): If attributes width and/or height are
// provided on the 'use' element, then these values will override the
// corresponding attributes on the 'svg' in the generated tree.
shadow_element.setAttribute(
SVGNames::widthAttr,
use.width()->IsSpecified()
? AtomicString(use.width()->CurrentValue()->ValueAsString())
: original_element.getAttribute(SVGNames::widthAttr));
shadow_element.setAttribute(
SVGNames::heightAttr,
use.height()->IsSpecified()
? AtomicString(use.height()->CurrentValue()->ValueAsString())
: original_element.getAttribute(SVGNames::heightAttr));
}
}
void SVGUseElement::CollectStyleForPresentationAttribute(
const QualifiedName& name,
const AtomicString& value,
MutableCSSPropertyValueSet* style) {
SVGAnimatedPropertyBase* property = PropertyFromAttribute(name);
if (property == x_) {
AddPropertyToPresentationAttributeStyle(style, property->CssPropertyId(),
&x_->CssValue());
} else if (property == y_) {
AddPropertyToPresentationAttributeStyle(style, property->CssPropertyId(),
&y_->CssValue());
} else {
SVGGraphicsElement::CollectStyleForPresentationAttribute(name, value,
style);
}
}
bool SVGUseElement::IsStructurallyExternal() const {
return !element_url_is_local_ &&
!EqualIgnoringFragmentIdentifier(element_url_, GetDocument().Url());
}
void SVGUseElement::UpdateTargetReference() {
const String& url_string = HrefString();
element_url_ = GetDocument().CompleteURL(url_string);
element_url_is_local_ = url_string.StartsWith('#');
if (!IsStructurallyExternal()) {
ClearResource();
return;
}
if (!element_url_.HasFragmentIdentifier() ||
(GetResource() &&
EqualIgnoringFragmentIdentifier(element_url_, GetResource()->Url())))
return;
ResourceLoaderOptions options;
options.initiator_info.name = localName();
FetchParameters params(ResourceRequest(element_url_), options);
DocumentResource::FetchSVGDocument(params, GetDocument().Fetcher(), this);
}
void SVGUseElement::SvgAttributeChanged(const QualifiedName& attr_name) {
if (attr_name == SVGNames::xAttr || attr_name == SVGNames::yAttr ||
attr_name == SVGNames::widthAttr || attr_name == SVGNames::heightAttr) {
SVGElement::InvalidationGuard invalidation_guard(this);
if (attr_name == SVGNames::xAttr || attr_name == SVGNames::yAttr) {
InvalidateSVGPresentationAttributeStyle();
SetNeedsStyleRecalc(
kLocalStyleChange,
StyleChangeReasonForTracing::FromAttribute(attr_name));
}
UpdateRelativeLengthsInformation();
if (target_element_instance_) {
DCHECK(target_element_instance_->CorrespondingElement());
TransferUseWidthAndHeightIfNeeded(
*this, *target_element_instance_,
*target_element_instance_->CorrespondingElement());
}
LayoutObject* object = this->GetLayoutObject();
if (object)
MarkForLayoutAndParentResourceInvalidation(object);
return;
}
if (SVGURIReference::IsKnownAttribute(attr_name)) {
SVGElement::InvalidationGuard invalidation_guard(this);
UpdateTargetReference();
InvalidateShadowTree();
return;
}
SVGGraphicsElement::SvgAttributeChanged(attr_name);
}
static bool IsDisallowedElement(const Element& element) {
// Spec: "Any 'svg', 'symbol', 'g', graphics element or other 'use' is
// potentially a template object that can be re-used (i.e., "instanced") in
// the SVG document via a 'use' element." "Graphics Element" is defined as
// 'circle', 'ellipse', 'image', 'line', 'path', 'polygon', 'polyline',
// 'rect', 'text' Excluded are anything that is used by reference or that only
// make sense to appear once in a document.
if (!element.IsSVGElement())
return true;
DEFINE_STATIC_LOCAL(
HashSet<QualifiedName>, allowed_element_tags,
({
SVGNames::aTag, SVGNames::circleTag, SVGNames::descTag,
SVGNames::ellipseTag, SVGNames::gTag, SVGNames::imageTag,
SVGNames::lineTag, SVGNames::metadataTag, SVGNames::pathTag,
SVGNames::polygonTag, SVGNames::polylineTag, SVGNames::rectTag,
SVGNames::svgTag, SVGNames::switchTag, SVGNames::symbolTag,
SVGNames::textTag, SVGNames::textPathTag, SVGNames::titleTag,
SVGNames::tspanTag, SVGNames::useTag,
}));
return !allowed_element_tags.Contains<SVGAttributeHashTranslator>(
element.TagQName());
}
void SVGUseElement::ScheduleShadowTreeRecreation() {
if (InUseShadowTree())
return;
needs_shadow_tree_recreation_ = true;
GetDocument().ScheduleUseShadowTreeUpdate(*this);
}
void SVGUseElement::CancelShadowTreeRecreation() {
needs_shadow_tree_recreation_ = false;
GetDocument().UnscheduleUseShadowTreeUpdate(*this);
}
void SVGUseElement::ClearInstanceRoot() {
target_element_instance_ = nullptr;
}
void SVGUseElement::ClearResourceReference() {
UnobserveTarget(target_id_observer_);
ClearInstanceRoot();
RemoveAllOutgoingReferences();
}
Element* SVGUseElement::ResolveTargetElement(ObserveBehavior observe_behavior) {
if (!element_url_.HasFragmentIdentifier())
return nullptr;
AtomicString element_identifier(
DecodeURLEscapeSequences(element_url_.FragmentIdentifier()));
if (!IsStructurallyExternal()) {
if (observe_behavior == kDontAddObserver)
return GetTreeScope().getElementById(element_identifier);
return ObserveTarget(
target_id_observer_, GetTreeScope(), element_identifier,
WTF::BindRepeating(&SVGUseElement::InvalidateShadowTree,
WrapWeakPersistent(this)));
}
if (!ResourceIsValid())
return nullptr;
return ToDocumentResource(GetResource())
->GetDocument()
->getElementById(element_identifier);
}
void SVGUseElement::BuildPendingResource() {
if (InUseShadowTree())
return;
// FIXME: We should try to optimize this, to at least allow partial reclones.
UseShadowRoot().RemoveChildren(kOmitSubtreeModifiedEvent);
ClearResourceReference();
CancelShadowTreeRecreation();
if (!isConnected())
return;
Element* target = ResolveTargetElement(kAddObserver);
// TODO(fs): Why would the Element not be "connected" at this point?
if (target && target->isConnected() && target->IsSVGElement()) {
BuildShadowAndInstanceTree(ToSVGElement(*target));
InvalidateDependentShadowTrees();
}
DCHECK(!needs_shadow_tree_recreation_);
}
String SVGUseElement::title() const {
// Find the first <title> child in <use> which doesn't cover shadow tree.
if (Element* title_element = Traversal<SVGTitleElement>::FirstChild(*this))
return title_element->innerText();
// If there is no <title> child in <use>, we lookup first <title> child in
// shadow tree.
if (target_element_instance_) {
if (Element* title_element =
Traversal<SVGTitleElement>::FirstChild(*target_element_instance_))
return title_element->innerText();
}
// Otherwise return a null string.
return String();
}
static void AssociateCorrespondingElements(SVGElement& target_root,
SVGElement& instance_root) {
auto target_range =
Traversal<SVGElement>::InclusiveDescendantsOf(target_root);
auto target_iterator = target_range.begin();
for (SVGElement& instance :
Traversal<SVGElement>::InclusiveDescendantsOf(instance_root)) {
DCHECK(!instance.CorrespondingElement());
instance.SetCorrespondingElement(&*target_iterator);
++target_iterator;
}
DCHECK(!(target_iterator != target_range.end()));
}
// 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).
static inline void RemoveDisallowedElementsFromSubtree(SVGElement& subtree) {
DCHECK(!subtree.isConnected());
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 void MoveChildrenToReplacementElement(ContainerNode& source_root,
ContainerNode& destination_root) {
for (Node* child = source_root.firstChild(); child;) {
Node* next_child = child->nextSibling();
destination_root.AppendChild(child);
child = next_child;
}
}
Element* SVGUseElement::CreateInstanceTree(SVGElement& target_root) const {
Element* instance_root = target_root.CloneElementWithChildren();
DCHECK(instance_root->IsSVGElement());
if (IsSVGSymbolElement(target_root)) {
// Spec: The referenced 'symbol' and its contents are deep-cloned into
// the generated tree, with the exception that the 'symbol' is replaced
// by an 'svg'. This generated 'svg' will always have explicit values
// for attributes width and height. If attributes width and/or height
// are provided on the 'use' element, then these attributes will be
// transferred to the generated 'svg'. If attributes width and/or
// height are not specified, the generated 'svg' element will use
// values of 100% for these attributes.
SVGSVGElement* svg_element =
SVGSVGElement::Create(target_root.GetDocument());
// Transfer all data (attributes, etc.) from the <symbol> to the new
// <svg> element.
svg_element->CloneDataFromElement(*instance_root);
// Move already cloned elements to the new <svg> element.
MoveChildrenToReplacementElement(*instance_root, *svg_element);
instance_root = svg_element;
}
TransferUseWidthAndHeightIfNeeded(*this, ToSVGElement(*instance_root),
target_root);
AssociateCorrespondingElements(target_root, ToSVGElement(*instance_root));
RemoveDisallowedElementsFromSubtree(ToSVGElement(*instance_root));
return instance_root;
}
void SVGUseElement::BuildShadowAndInstanceTree(SVGElement& target) {
DCHECK(!target_element_instance_);
DCHECK(!needs_shadow_tree_recreation_);
// <use> creates a closed shadow root. Do not build the shadow/instance
// tree for <use> elements living in a closed tree because they
// will get expanded in a second pass -- see expandUseElementsInShadowTree().
if (InUseShadowTree())
return;
// Do not allow self-referencing.
if (&target == this || IsDisallowedElement(target))
return;
// Set up root SVG element in shadow tree.
// Clone the target subtree into the shadow tree, not handling <use> and
// <symbol> yet.
Element* instance_root = CreateInstanceTree(target);
target_element_instance_ = ToSVGElement(instance_root);
ShadowRoot& shadow_root = UseShadowRoot();
shadow_root.AppendChild(instance_root);
AddReferencesToFirstDegreeNestedUseElements(target);
if (InstanceTreeIsLoading()) {
CloneNonMarkupEventListeners();
return;
}
// Assure shadow tree building was successful.
DCHECK(target_element_instance_);
DCHECK_EQ(target_element_instance_->CorrespondingUseElement(), this);
DCHECK_EQ(target_element_instance_->CorrespondingElement(), &target);
// Expand all <use> elements in the shadow tree.
// Expand means: replace the actual <use> element by what it references.
if (!ExpandUseElementsInShadowTree()) {
shadow_root.RemoveChildren(kOmitSubtreeModifiedEvent);
ClearResourceReference();
return;
}
// If the instance root was a <use>, it could have been replaced now, so
// reset |m_targetElementInstance|.
target_element_instance_ = ToSVGElementOrDie(shadow_root.firstChild());
DCHECK_EQ(target_element_instance_->parentNode(), shadow_root);
CloneNonMarkupEventListeners();
// Update relative length information.
UpdateRelativeLengthsInformation();
}
LayoutObject* SVGUseElement::CreateLayoutObject(const ComputedStyle& style) {
if (style.Display() == EDisplay::kContents)
return nullptr;
return new LayoutSVGTransformableContainer(this);
}
static bool IsDirectReference(const SVGElement& element) {
return IsSVGPathElement(element) || IsSVGRectElement(element) ||
IsSVGCircleElement(element) || IsSVGEllipseElement(element) ||
IsSVGPolygonElement(element) || IsSVGPolylineElement(element) ||
IsSVGTextElement(element);
}
Path SVGUseElement::ToClipPath() const {
const SVGGraphicsElement* element = VisibleTargetGraphicsElementForClipping();
if (!element || !element->IsSVGGeometryElement())
return Path();
DCHECK(GetLayoutObject());
Path path = ToSVGGeometryElement(*element).ToClipPath();
AffineTransform transform = GetLayoutObject()->LocalSVGTransform();
if (!transform.IsIdentity())
path.Transform(transform);
return path;
}
SVGGraphicsElement* SVGUseElement::VisibleTargetGraphicsElementForClipping()
const {
Node* n = UseShadowRoot().firstChild();
if (!n || !n->IsSVGElement())
return nullptr;
SVGElement& element = ToSVGElement(*n);
if (!element.IsSVGGraphicsElement())
return nullptr;
// Spec: "If a <use> element is a child of a clipPath element, it must
// directly reference <path>, <text> or basic shapes elements. Indirect
// references are an error and the clipPath element must be ignored."
// http://dev.w3.org/fxtf/css-masking-1/#the-clip-path
if (!IsDirectReference(element)) {
// Spec: Indirect references are an error (14.3.5)
return nullptr;
}
return &ToSVGGraphicsElement(element);
}
void SVGUseElement::AddReferencesToFirstDegreeNestedUseElements(
SVGElement& target) {
// Don't track references to external documents.
if (IsStructurallyExternal())
return;
// We only need to track first degree <use> dependencies. Indirect
// references are handled as the invalidation bubbles up the dependency
// chain.
SVGUseElement* use_element =
IsSVGUseElement(target) ? ToSVGUseElement(&target)
: Traversal<SVGUseElement>::FirstWithin(target);
for (; use_element;
use_element = Traversal<SVGUseElement>::NextSkippingChildren(
*use_element, &target))
AddReferenceTo(use_element);
}
void SVGUseElement::CloneNonMarkupEventListeners() {
for (SVGElement& element :
Traversal<SVGElement>::DescendantsOf(UseShadowRoot())) {
if (EventTargetData* data =
element.CorrespondingElement()->GetEventTargetData()) {
data->event_listener_map.CopyEventListenersNotCreatedFromMarkupToTarget(
&element);
}
}
}
bool SVGUseElement::HasCycleUseReferencing(SVGUseElement& use,
const ContainerNode& target_instance,
SVGElement*& new_target) const {
Element* target_element = use.ResolveTargetElement(kDontAddObserver);
new_target = nullptr;
if (target_element && target_element->IsSVGElement())
new_target = ToSVGElement(target_element);
if (!new_target)
return false;
// Shortcut for self-references
if (new_target == this)
return true;
AtomicString target_id = new_target->GetIdAttribute();
ContainerNode* instance = target_instance.parentNode();
while (instance && instance->IsSVGElement()) {
SVGElement* element = ToSVGElement(instance);
if (element->HasID() && element->GetIdAttribute() == target_id &&
element->GetDocument() == new_target->GetDocument())
return true;
instance = instance->parentNode();
}
return false;
}
// 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.
static void RemoveAttributesFromReplacementElement(
SVGElement& replacement_element) {
replacement_element.removeAttribute(SVGNames::xAttr);
replacement_element.removeAttribute(SVGNames::yAttr);
replacement_element.removeAttribute(SVGNames::widthAttr);
replacement_element.removeAttribute(SVGNames::heightAttr);
replacement_element.removeAttribute(SVGNames::hrefAttr);
replacement_element.removeAttribute(XLinkNames::hrefAttr);
}
bool SVGUseElement::ExpandUseElementsInShadowTree() {
// 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.
ShadowRoot& shadow_root = UseShadowRoot();
for (SVGUseElement* use = Traversal<SVGUseElement>::FirstWithin(shadow_root);
use;) {
DCHECK(!use->ResourceIsStillLoading());
SVGUseElement& original_use = ToSVGUseElement(*use->CorrespondingElement());
SVGElement* target = nullptr;
if (HasCycleUseReferencing(original_use, *use, target))
return false;
if (target && IsDisallowedElement(*target))
return false;
// Don't DCHECK(target) here, it may be "pending", too.
// Setup sub-shadow tree root node
SVGGElement* clone_parent = SVGGElement::Create(original_use.GetDocument());
// Transfer all data (attributes, etc.) from <use> to the new <g> element.
clone_parent->CloneDataFromElement(*use);
clone_parent->SetCorrespondingElement(&original_use);
RemoveAttributesFromReplacementElement(*clone_parent);
// Move already cloned elements to the new <g> element.
MoveChildrenToReplacementElement(*use, *clone_parent);
if (target)
clone_parent->AppendChild(use->CreateInstanceTree(*target));
SVGElement* replacing_element(clone_parent);
// Replace <use> with referenced content.
use->parentNode()->ReplaceChild(clone_parent, use);
use = Traversal<SVGUseElement>::Next(*replacing_element, &shadow_root);
}
return true;
}
void SVGUseElement::InvalidateShadowTree() {
if (!InActiveDocument() || needs_shadow_tree_recreation_)
return;
ClearInstanceRoot();
ScheduleShadowTreeRecreation();
InvalidateDependentShadowTrees();
}
void SVGUseElement::InvalidateDependentShadowTrees() {
// Recursively invalidate dependent <use> shadow trees
const HeapHashSet<WeakMember<SVGElement>>& raw_instances =
InstancesForElement();
HeapVector<Member<SVGElement>> instances;
instances.AppendRange(raw_instances.begin(), raw_instances.end());
for (auto& instance : instances) {
if (SVGUseElement* element = instance->CorrespondingUseElement()) {
DCHECK(element->isConnected());
element->InvalidateShadowTree();
}
}
}
bool SVGUseElement::SelfHasRelativeLengths() const {
if (x_->CurrentValue()->IsRelative() || y_->CurrentValue()->IsRelative() ||
width_->CurrentValue()->IsRelative() ||
height_->CurrentValue()->IsRelative())
return true;
if (!target_element_instance_)
return false;
return target_element_instance_->HasRelativeLengths();
}
FloatRect SVGUseElement::GetBBox() {
DCHECK(GetLayoutObject());
LayoutSVGTransformableContainer& transformable_container =
ToLayoutSVGTransformableContainer(*GetLayoutObject());
// Don't apply the additional translation if the oBB is invalid.
if (!transformable_container.IsObjectBoundingBoxValid())
return FloatRect();
// TODO(fs): Preferably this would just use objectBoundingBox() (and hence
// don't need to override SVGGraphicsElement::getBBox at all) and be
// correct without additional work. That will not work out ATM without
// additional quirks. The problem stems from including the additional
// translation directly on the LayoutObject corresponding to the
// SVGUseElement.
FloatRect bbox = transformable_container.ObjectBoundingBox();
bbox.Move(transformable_container.AdditionalTranslation());
return bbox;
}
void SVGUseElement::DispatchPendingEvent() {
DCHECK(IsStructurallyExternal());
DCHECK(have_fired_load_event_);
DispatchEvent(Event::Create(EventTypeNames::load));
}
void SVGUseElement::NotifyFinished(Resource* resource) {
DCHECK_EQ(GetResource(), resource);
if (!isConnected())
return;
InvalidateShadowTree();
if (!ResourceIsValid()) {
DispatchEvent(Event::Create(EventTypeNames::error));
} else if (!resource->WasCanceled()) {
if (have_fired_load_event_)
return;
if (!IsStructurallyExternal())
return;
DCHECK(!have_fired_load_event_);
have_fired_load_event_ = true;
GetDocument()
.GetTaskRunner(TaskType::kDOMManipulation)
->PostTask(FROM_HERE, WTF::Bind(&SVGUseElement::DispatchPendingEvent,
WrapPersistent(this)));
}
}
bool SVGUseElement::ResourceIsStillLoading() const {
return GetResource() && GetResource()->IsLoading();
}
bool SVGUseElement::ResourceIsValid() const {
return GetResource() && GetResource()->IsLoaded() &&
!GetResource()->ErrorOccurred() &&
ToDocumentResource(GetResource())->GetDocument();
}
bool SVGUseElement::InstanceTreeIsLoading() const {
for (const SVGUseElement& use_element :
Traversal<SVGUseElement>::DescendantsOf(UseShadowRoot())) {
if (use_element.ResourceIsStillLoading())
return true;
}
return false;
}
} // namespace blink