blob: dfb672c1be161f76ecca232734f3f5cb3ad68456 [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 "third_party/blink/renderer/core/svg/svg_use_element.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/renderer/core/css/style_change_reason.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element_traversal.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/dom/id_target_observer.h"
#include "third_party/blink/renderer/core/dom/shadow_root.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_transformable_container.h"
#include "third_party/blink/renderer/core/svg/svg_g_element.h"
#include "third_party/blink/renderer/core/svg/svg_length_context.h"
#include "third_party/blink/renderer/core/svg/svg_svg_element.h"
#include "third_party/blink/renderer/core/svg/svg_symbol_element.h"
#include "third_party/blink/renderer/core/svg/svg_title_element.h"
#include "third_party/blink/renderer/core/svg_names.h"
#include "third_party/blink/renderer/core/xlink_names.h"
#include "third_party/blink/renderer/core/xml/parser/xml_document_parser.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
namespace blink {
inline SVGUseElement::SVGUseElement(Document& document)
: SVGGraphicsElement(svg_names::kUseTag, document),
SVGURIReference(this),
x_(SVGAnimatedLength::Create(this,
svg_names::kXAttr,
SVGLengthMode::kWidth,
SVGLength::Initial::kUnitlessZero,
CSSPropertyX)),
y_(SVGAnimatedLength::Create(this,
svg_names::kYAttr,
SVGLengthMode::kHeight,
SVGLength::Initial::kUnitlessZero,
CSSPropertyY)),
width_(SVGAnimatedLength::Create(this,
svg_names::kWidthAttr,
SVGLengthMode::kWidth,
SVGLength::Initial::kUnitlessZero)),
height_(SVGAnimatedLength::Create(this,
svg_names::kHeightAttr,
SVGLengthMode::kHeight,
SVGLength::Initial::kUnitlessZero)),
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 = MakeGarbageCollected<SVGUseElement>(document);
use->AttachShadowRootInternal(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()));
#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) {
// Use |original_element| for checking the element type, because we will
// have replaced a <symbol> with an <svg> in the instance tree.
if (!IsSVGSymbolElement(original_element) &&
!IsSVGSVGElement(original_element))
return;
// "The width and height properties on the 'use' element override the values
// for the corresponding properties on a referenced 'svg' or 'symbol' element
// when determining the used value for that property on the instance root
// element. However, if the computed value for the property on the 'use'
// element is auto, then the property is computed as normal for the element
// instance. ... Because auto is the initial value, if dimensions are not
// explicitly set on the 'use' element, the values set on the 'svg' or
// 'symbol' will be used as defaults."
// (https://svgwg.org/svg2-draft/struct.html#UseElement)
AtomicString width_value(
use.width()->IsSpecified()
? use.width()->CurrentValue()->ValueAsString()
: original_element.getAttribute(svg_names::kWidthAttr));
shadow_element.setAttribute(svg_names::kWidthAttr, width_value);
AtomicString height_value(
use.height()->IsSpecified()
? use.height()->CurrentValue()->ValueAsString()
: original_element.getAttribute(svg_names::kHeightAttr));
shadow_element.setAttribute(svg_names::kHeightAttr, height_value);
}
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);
params.MutableResourceRequest().SetFetchRequestMode(
network::mojom::FetchRequestMode::kSameOrigin);
DocumentResource::FetchSVGDocument(params, GetDocument().Fetcher(), this);
}
void SVGUseElement::SvgAttributeChanged(const QualifiedName& attr_name) {
if (attr_name == svg_names::kXAttr || attr_name == svg_names::kYAttr ||
attr_name == svg_names::kWidthAttr ||
attr_name == svg_names::kHeightAttr) {
SVGElement::InvalidationGuard invalidation_guard(this);
if (attr_name == svg_names::kXAttr || attr_name == svg_names::kYAttr) {
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());
}
if (LayoutObject* object = GetLayoutObject())
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,
({
svg_names::kATag, svg_names::kCircleTag,
svg_names::kDescTag, svg_names::kEllipseTag,
svg_names::kGTag, svg_names::kImageTag,
svg_names::kLineTag, svg_names::kMetadataTag,
svg_names::kPathTag, svg_names::kPolygonTag,
svg_names::kPolylineTag, svg_names::kRectTag,
svg_names::kSVGTag, svg_names::kSwitchTag,
svg_names::kSymbolTag, svg_names::kTextTag,
svg_names::kTextPathTag, svg_names::kTitleTag,
svg_names::kTSpanTag, svg_names::kUseTag,
}));
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(), DecodeURLMode::kUTF8OrIsomorphic));
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() {
// Do not build the shadow/instance tree for nested <use> elements
// because they will get expanded in a second pass -- see
// ExpandUseElementsInShadowTree().
if (InUseShadowTree())
return;
// FIXME: We should try to optimize this, to at least allow partial reclones.
UseShadowRoot().RemoveChildren(kOmitSubtreeModifiedEvent);
ClearResourceReference();
CancelShadowTreeRecreation();
if (!isConnected())
return;
SVGElement* target = ToSVGElementOrNull(ResolveTargetElement(kAddObserver));
// TODO(fs): Why would the Element not be "connected" at this point?
if (target && target->isConnected()) {
BuildShadowAndInstanceTree(*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.CloneWithChildren();
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 attributes from the <symbol> to the new <svg>
// element.
svg_element->CloneAttributesFrom(*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_);
DCHECK(!InUseShadowTree());
// Do not allow self-referencing.
if (IsDisallowedElement(target) || HasCycleUseReferencing(*this, 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.
ExpandUseElementsInShadowTree();
// 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."
// https://drafts.fxtf.org/css-masking/#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(const ContainerNode& target_instance,
const SVGElement& target) const {
// Shortcut for self-references
if (&target == this)
return true;
AtomicString target_id = target.GetIdAttribute();
ContainerNode* instance = target_instance.ParentOrShadowHostElement();
while (instance && instance->IsSVGElement()) {
SVGElement* element = ToSVGElement(instance);
if (element->HasID() && element->GetIdAttribute() == target_id &&
element->GetDocument() == target.GetDocument())
return true;
instance = instance->ParentOrShadowHostElement();
}
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(svg_names::kXAttr);
replacement_element.removeAttribute(svg_names::kYAttr);
replacement_element.removeAttribute(svg_names::kWidthAttr);
replacement_element.removeAttribute(svg_names::kHeightAttr);
replacement_element.removeAttribute(svg_names::kHrefAttr);
replacement_element.removeAttribute(xlink_names::kHrefAttr);
}
void 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 =
ToSVGElementOrNull(original_use.ResolveTargetElement(kDontAddObserver));
if (target) {
if (IsDisallowedElement(*target) || HasCycleUseReferencing(*use, *target))
return;
}
// 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->CloneAttributesFrom(*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);
}
}
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(event_type_names::kLoad));
}
void SVGUseElement::NotifyFinished(Resource* resource) {
DCHECK_EQ(GetResource(), resource);
if (!isConnected())
return;
InvalidateShadowTree();
if (!ResourceIsValid()) {
DispatchEvent(*Event::Create(event_type_names::kError));
} 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