| /* |
| * 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/dom/xml_document.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/layout/svg/layout_svg_transformable_container.h" |
| #include "third_party/blink/renderer/core/svg/svg_animated_length.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_resource_document_content.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/heap/heap.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 { |
| |
| SVGUseElement::SVGUseElement(Document& document) |
| : SVGGraphicsElement(svg_names::kUseTag, document), |
| SVGURIReference(this), |
| x_(MakeGarbageCollected<SVGAnimatedLength>( |
| this, |
| svg_names::kXAttr, |
| SVGLengthMode::kWidth, |
| SVGLength::Initial::kUnitlessZero, |
| CSSPropertyID::kX)), |
| y_(MakeGarbageCollected<SVGAnimatedLength>( |
| this, |
| svg_names::kYAttr, |
| SVGLengthMode::kHeight, |
| SVGLength::Initial::kUnitlessZero, |
| CSSPropertyID::kY)), |
| width_(MakeGarbageCollected<SVGAnimatedLength>( |
| this, |
| svg_names::kWidthAttr, |
| SVGLengthMode::kWidth, |
| SVGLength::Initial::kUnitlessZero)), |
| height_(MakeGarbageCollected<SVGAnimatedLength>( |
| 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_); |
| |
| AttachShadowRootInternal(ShadowRootType::kClosed); |
| } |
| |
| SVGUseElement::~SVGUseElement() = default; |
| |
| void SVGUseElement::Trace(Visitor* visitor) const { |
| visitor->Trace(document_content_); |
| visitor->Trace(x_); |
| visitor->Trace(y_); |
| visitor->Trace(width_); |
| visitor->Trace(height_); |
| visitor->Trace(target_id_observer_); |
| SVGGraphicsElement::Trace(visitor); |
| SVGURIReference::Trace(visitor); |
| ResourceClient::Trace(visitor); |
| } |
| |
| #if DCHECK_IS_ON() |
| static inline bool IsWellFormedDocument(const Document& document) { |
| if (IsA<XMLDocument>(document)) |
| return static_cast<XMLDocumentParser*>(document.Parser())->WellFormed(); |
| return true; |
| } |
| #endif |
| |
| Node::InsertionNotificationRequest SVGUseElement::InsertedInto( |
| ContainerNode& root_parent) { |
| SVGGraphicsElement::InsertedInto(root_parent); |
| if (root_parent.isConnected()) { |
| InvalidateShadowTree(); |
| #if DCHECK_IS_ON() |
| DCHECK(!InstanceRoot() || !IsWellFormedDocument(GetDocument())); |
| #endif |
| } |
| return kInsertionDone; |
| } |
| |
| void SVGUseElement::RemovedFrom(ContainerNode& root_parent) { |
| SVGGraphicsElement::RemovedFrom(root_parent); |
| if (root_parent.isConnected()) { |
| ClearResourceReference(); |
| CancelShadowTreeRecreation(); |
| } |
| } |
| |
| void SVGUseElement::DidMoveToNewDocument(Document& old_document) { |
| SVGGraphicsElement::DidMoveToNewDocument(old_document); |
| UpdateTargetReference(); |
| } |
| |
| 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 (!IsA<SVGSymbolElement>(original_element) && |
| !IsA<SVGSVGElement>(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() || !GetDocument().IsActive()) { |
| ClearResource(); |
| document_content_ = nullptr; |
| return; |
| } |
| if (!element_url_.HasFragmentIdentifier() || |
| (document_content_ && EqualIgnoringFragmentIdentifier( |
| element_url_, document_content_->Url()))) { |
| return; |
| } |
| |
| auto* context_document = &GetDocument(); |
| if (GetDocument().ImportsController()) { |
| // For @imports from HTML imported Documents, we use the |
| // context document for getting origin and ResourceFetcher to use the |
| // main Document's origin, while using the element document for |
| // CompleteURL() to use imported Documents' base URLs. |
| context_document = |
| To<LocalDOMWindow>(GetDocument().GetExecutionContext())->document(); |
| } |
| |
| ExecutionContext* execution_context = context_document->GetExecutionContext(); |
| ResourceLoaderOptions options(execution_context->GetCurrentWorld()); |
| options.initiator_info.name = localName(); |
| FetchParameters params(ResourceRequest(element_url_), options); |
| document_content_ = |
| SVGResourceDocumentContent::Fetch(params, *context_document, this); |
| } |
| |
| void SVGUseElement::SvgAttributeChanged( |
| const SvgAttributeChangedParams& params) { |
| const QualifiedName& attr_name = params.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 (SVGElement* instance_root = InstanceRoot()) { |
| DCHECK(instance_root->CorrespondingElement()); |
| TransferUseWidthAndHeightIfNeeded(*this, *instance_root, |
| *instance_root->CorrespondingElement()); |
| } |
| |
| if (LayoutObject* object = GetLayoutObject()) |
| MarkForLayoutAndParentResourceInvalidation(*object); |
| return; |
| } |
| |
| if (SVGURIReference::IsKnownAttribute(attr_name)) { |
| SVGElement::InvalidationGuard invalidation_guard(this); |
| UpdateTargetReference(); |
| InvalidateShadowTree(); |
| return; |
| } |
| |
| SVGGraphicsElement::SvgAttributeChanged(params); |
| } |
| |
| 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() { |
| needs_shadow_tree_recreation_ = true; |
| GetDocument().ScheduleUseShadowTreeUpdate(*this); |
| } |
| |
| void SVGUseElement::CancelShadowTreeRecreation() { |
| needs_shadow_tree_recreation_ = false; |
| GetDocument().UnscheduleUseShadowTreeUpdate(*this); |
| } |
| |
| void SVGUseElement::ClearResourceReference() { |
| UnobserveTarget(target_id_observer_); |
| RemoveAllOutgoingReferences(); |
| } |
| |
| Element* SVGUseElement::ResolveTargetElement() { |
| if (!element_url_.HasFragmentIdentifier()) |
| return nullptr; |
| AtomicString element_identifier(DecodeURLEscapeSequences( |
| element_url_.FragmentIdentifier(), DecodeURLMode::kUTF8OrIsomorphic)); |
| |
| if (!IsStructurallyExternal()) { |
| // Only create observers for non-instance use elements. |
| // Instances will be updated by their corresponding elements. |
| if (InUseShadowTree()) { |
| return OriginatingTreeScope().getElementById(element_identifier); |
| } else { |
| return ObserveTarget( |
| target_id_observer_, OriginatingTreeScope(), element_identifier, |
| WTF::BindRepeating(&SVGUseElement::InvalidateTargetReference, |
| WrapWeakPersistent(this))); |
| } |
| } |
| if (!document_content_ || !document_content_->GetDocument()) |
| return nullptr; |
| return document_content_->GetDocument()->getElementById(element_identifier); |
| } |
| |
| SVGElement* SVGUseElement::InstanceRoot() const { |
| if (ShadowTreeRebuildPending()) |
| return nullptr; |
| return To<SVGElement>(UseShadowRoot().firstChild()); |
| } |
| |
| void SVGUseElement::BuildPendingResource() { |
| if (!isConnected()) { |
| DCHECK(!needs_shadow_tree_recreation_); |
| return; // Already replaced by rebuilding ancestor. |
| } |
| CancelShadowTreeRecreation(); |
| |
| // Check if this element is scheduled (by an ancestor) to be replaced. |
| SVGUseElement* ancestor = GeneratingUseElement(); |
| while (ancestor) { |
| if (ancestor->needs_shadow_tree_recreation_) |
| return; |
| ancestor = ancestor->GeneratingUseElement(); |
| } |
| |
| DetachShadowTree(); |
| ClearResourceReference(); |
| |
| if (auto* target = DynamicTo<SVGElement>(ResolveTargetElement())) { |
| DCHECK(target->isConnected()); |
| AttachShadowTree(*target); |
| } |
| 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 (SVGElement* instance_root = InstanceRoot()) { |
| if (Element* title_element = |
| Traversal<SVGTitleElement>::FirstChild(*instance_root)) |
| 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; |
| } |
| } |
| |
| SVGElement* SVGUseElement::CreateInstanceTree(SVGElement& target_root) const { |
| SVGElement* instance_root = &To<SVGElement>(target_root.CloneWithChildren()); |
| if (IsA<SVGSymbolElement>(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. |
| auto* svg_element = |
| MakeGarbageCollected<SVGSVGElement>(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, *instance_root, target_root); |
| AssociateCorrespondingElements(target_root, *instance_root); |
| RemoveDisallowedElementsFromSubtree(*instance_root); |
| return instance_root; |
| } |
| |
| void SVGUseElement::AttachShadowTree(SVGElement& target) { |
| DCHECK(!InstanceRoot()); |
| DCHECK(!needs_shadow_tree_recreation_); |
| |
| // 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. |
| UseShadowRoot().AppendChild(CreateInstanceTree(target)); |
| |
| // Assure shadow tree building was successful. |
| DCHECK(InstanceRoot()); |
| DCHECK_EQ(InstanceRoot()->GeneratingUseElement(), this); |
| DCHECK_EQ(InstanceRoot()->CorrespondingElement(), &target); |
| |
| for (SVGElement& instance : |
| Traversal<SVGElement>::DescendantsOf(UseShadowRoot())) { |
| SVGElement* corresponding_element = instance.CorrespondingElement(); |
| // Transfer non-markup event listeners. |
| if (EventTargetData* data = corresponding_element->GetEventTargetData()) { |
| data->event_listener_map.CopyEventListenersNotCreatedFromMarkupToTarget( |
| &instance); |
| } |
| // Setup the mapping from the corresponding (original) element back to the |
| // instance. |
| corresponding_element->MapInstanceToElement(&instance); |
| } |
| } |
| |
| void SVGUseElement::DetachShadowTree() { |
| ShadowRoot& shadow_root = UseShadowRoot(); |
| // FIXME: We should try to optimize this, to at least allow partial reclones. |
| shadow_root.RemoveChildren(kOmitSubtreeModifiedEvent); |
| } |
| |
| LayoutObject* SVGUseElement::CreateLayoutObject(const ComputedStyle& style, |
| LegacyLayout) { |
| return new LayoutSVGTransformableContainer(this); |
| } |
| |
| static bool IsDirectReference(const SVGElement& element) { |
| return IsA<SVGPathElement>(element) || IsA<SVGRectElement>(element) || |
| IsA<SVGCircleElement>(element) || IsA<SVGEllipseElement>(element) || |
| IsA<SVGPolygonElement>(element) || IsA<SVGPolylineElement>(element) || |
| IsA<SVGTextElement>(element); |
| } |
| |
| Path SVGUseElement::ToClipPath() const { |
| const SVGGraphicsElement* element = VisibleTargetGraphicsElementForClipping(); |
| auto* geometry_element = DynamicTo<SVGGeometryElement>(element); |
| if (!geometry_element) |
| return Path(); |
| |
| DCHECK(GetLayoutObject()); |
| Path path = geometry_element->ToClipPath(); |
| AffineTransform transform = GetLayoutObject()->LocalSVGTransform(); |
| if (!transform.IsIdentity()) |
| path.Transform(transform); |
| return path; |
| } |
| |
| SVGGraphicsElement* SVGUseElement::VisibleTargetGraphicsElementForClipping() |
| const { |
| auto* svg_graphics_element = DynamicTo<SVGGraphicsElement>(InstanceRoot()); |
| if (!svg_graphics_element) |
| 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(*svg_graphics_element)) { |
| // Spec: Indirect references are an error (14.3.5) |
| return nullptr; |
| } |
| |
| return svg_graphics_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(); |
| auto* element = |
| DynamicTo<SVGElement>(target_instance.ParentOrShadowHostElement()); |
| while (element) { |
| if (element->HasID() && element->GetIdAttribute() == target_id && |
| element->GetDocument() == target.GetDocument()) |
| return true; |
| element = DynamicTo<SVGElement>(element->ParentOrShadowHostElement()); |
| } |
| return false; |
| } |
| |
| bool SVGUseElement::ShadowTreeRebuildPending() const { |
| // The shadow tree is torn down lazily, so check if there's a pending rebuild |
| // or if we're disconnected from the document. |
| return !InActiveDocument() || needs_shadow_tree_recreation_; |
| } |
| |
| void SVGUseElement::InvalidateShadowTree() { |
| if (ShadowTreeRebuildPending()) |
| return; |
| ScheduleShadowTreeRecreation(); |
| } |
| |
| void SVGUseElement::InvalidateTargetReference() { |
| InvalidateShadowTree(); |
| for (SVGElement* instance : InstancesForElement()) |
| To<SVGUseElement>(instance)->InvalidateShadowTree(); |
| } |
| |
| bool SVGUseElement::SelfHasRelativeLengths() const { |
| return x_->CurrentValue()->IsRelative() || y_->CurrentValue()->IsRelative() || |
| width_->CurrentValue()->IsRelative() || |
| height_->CurrentValue()->IsRelative(); |
| } |
| |
| FloatRect SVGUseElement::GetBBox() { |
| DCHECK(GetLayoutObject()); |
| auto& transformable_container = |
| To<LayoutSVGTransformableContainer>(*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) { |
| if (!isConnected()) |
| return; |
| |
| InvalidateShadowTree(); |
| if (resource->ErrorOccurred() || !document_content_->GetDocument()) { |
| DispatchEvent(*Event::Create(event_type_names::kError)); |
| } else { |
| 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))); |
| } |
| } |
| |
| String SVGUseElement::DebugName() const { |
| return "SVGUseElement"; |
| } |
| |
| } // namespace blink |