Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "mozilla/ArrayUtils.h"
8 :
9 : #include "mozilla/dom/SVGUseElement.h"
10 : #include "mozilla/dom/SVGUseElementBinding.h"
11 : #include "nsGkAtoms.h"
12 : #include "mozilla/dom/SVGSVGElement.h"
13 : #include "nsIDocument.h"
14 : #include "nsIPresShell.h"
15 : #include "mozilla/dom/Element.h"
16 : #include "nsContentUtils.h"
17 : #include "nsIURI.h"
18 : #include "mozilla/URLExtraData.h"
19 : #include "nsSVGEffects.h"
20 :
21 58 : NS_IMPL_NS_NEW_NAMESPACED_SVG_ELEMENT(Use)
22 :
23 : namespace mozilla {
24 : namespace dom {
25 :
26 : JSObject*
27 0 : SVGUseElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
28 : {
29 0 : return SVGUseElementBinding::Wrap(aCx, this, aGivenProto);
30 : }
31 :
32 : ////////////////////////////////////////////////////////////////////////
33 : // implementation
34 :
35 : nsSVGElement::LengthInfo SVGUseElement::sLengthInfo[4] =
36 : {
37 : { &nsGkAtoms::x, 0, nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER, SVGContentUtils::X },
38 : { &nsGkAtoms::y, 0, nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER, SVGContentUtils::Y },
39 : { &nsGkAtoms::width, 0, nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER, SVGContentUtils::X },
40 : { &nsGkAtoms::height, 0, nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER, SVGContentUtils::Y },
41 : };
42 :
43 : nsSVGElement::StringInfo SVGUseElement::sStringInfo[2] =
44 : {
45 : { &nsGkAtoms::href, kNameSpaceID_None, true },
46 : { &nsGkAtoms::href, kNameSpaceID_XLink, true }
47 : };
48 :
49 : //----------------------------------------------------------------------
50 : // nsISupports methods
51 :
52 : NS_IMPL_CYCLE_COLLECTION_CLASS(SVGUseElement)
53 :
54 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SVGUseElement,
55 : SVGUseElementBase)
56 0 : nsAutoScriptBlocker scriptBlocker;
57 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginal)
58 0 : tmp->DestroyAnonymousContent();
59 0 : tmp->UnlinkSource();
60 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
61 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SVGUseElement,
62 : SVGUseElementBase)
63 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginal)
64 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mClone)
65 0 : tmp->mSource.Traverse(&cb);
66 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
67 :
68 397 : NS_IMPL_ADDREF_INHERITED(SVGUseElement,SVGUseElementBase)
69 318 : NS_IMPL_RELEASE_INHERITED(SVGUseElement,SVGUseElementBase)
70 :
71 243 : NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(SVGUseElement)
72 214 : NS_INTERFACE_TABLE_INHERITED(SVGUseElement, nsIMutationObserver)
73 214 : NS_INTERFACE_TABLE_TAIL_INHERITING(SVGUseElementBase)
74 :
75 : //----------------------------------------------------------------------
76 : // Implementation
77 :
78 29 : SVGUseElement::SVGUseElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
79 29 : : SVGUseElementBase(aNodeInfo), mSource(this)
80 : {
81 29 : }
82 :
83 0 : SVGUseElement::~SVGUseElement()
84 : {
85 0 : UnlinkSource();
86 0 : }
87 :
88 : //----------------------------------------------------------------------
89 : // nsIDOMNode methods
90 :
91 : nsresult
92 0 : SVGUseElement::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult,
93 : bool aPreallocateChildren) const
94 : {
95 0 : *aResult = nullptr;
96 0 : already_AddRefed<mozilla::dom::NodeInfo> ni = RefPtr<mozilla::dom::NodeInfo>(aNodeInfo).forget();
97 0 : SVGUseElement *it = new SVGUseElement(ni);
98 :
99 0 : nsCOMPtr<nsINode> kungFuDeathGrip(it);
100 0 : nsresult rv1 = it->Init();
101 0 : nsresult rv2 = const_cast<SVGUseElement*>(this)->CopyInnerTo(it, aPreallocateChildren);
102 :
103 : // SVGUseElement specific portion - record who we cloned from
104 0 : it->mOriginal = const_cast<SVGUseElement*>(this);
105 :
106 0 : if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2)) {
107 0 : kungFuDeathGrip.swap(*aResult);
108 : }
109 :
110 0 : return NS_FAILED(rv1) ? rv1 : rv2;
111 : }
112 :
113 : already_AddRefed<SVGAnimatedString>
114 0 : SVGUseElement::Href()
115 : {
116 0 : return mStringAttributes[HREF].IsExplicitlySet()
117 : ? mStringAttributes[HREF].ToDOMAnimatedString(this)
118 0 : : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this);
119 : }
120 :
121 : //----------------------------------------------------------------------
122 :
123 : already_AddRefed<SVGAnimatedLength>
124 0 : SVGUseElement::X()
125 : {
126 0 : return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this);
127 : }
128 :
129 : already_AddRefed<SVGAnimatedLength>
130 0 : SVGUseElement::Y()
131 : {
132 0 : return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this);
133 : }
134 :
135 : already_AddRefed<SVGAnimatedLength>
136 0 : SVGUseElement::Width()
137 : {
138 0 : return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this);
139 : }
140 :
141 : already_AddRefed<SVGAnimatedLength>
142 0 : SVGUseElement::Height()
143 : {
144 0 : return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this);
145 : }
146 :
147 : //----------------------------------------------------------------------
148 : // nsIMutationObserver methods
149 :
150 : void
151 0 : SVGUseElement::CharacterDataChanged(nsIDocument *aDocument,
152 : nsIContent *aContent,
153 : CharacterDataChangeInfo* aInfo)
154 : {
155 0 : if (nsContentUtils::IsInSameAnonymousTree(this, aContent)) {
156 0 : TriggerReclone();
157 : }
158 0 : }
159 :
160 : void
161 0 : SVGUseElement::AttributeChanged(nsIDocument* aDocument,
162 : Element* aElement,
163 : int32_t aNameSpaceID,
164 : nsIAtom* aAttribute,
165 : int32_t aModType,
166 : const nsAttrValue* aOldValue)
167 : {
168 0 : if (nsContentUtils::IsInSameAnonymousTree(this, aElement)) {
169 0 : TriggerReclone();
170 : }
171 0 : }
172 :
173 : void
174 0 : SVGUseElement::ContentAppended(nsIDocument *aDocument,
175 : nsIContent *aContainer,
176 : nsIContent *aFirstNewContent,
177 : int32_t aNewIndexInContainer)
178 : {
179 0 : if (nsContentUtils::IsInSameAnonymousTree(this, aContainer)) {
180 0 : TriggerReclone();
181 : }
182 0 : }
183 :
184 : void
185 0 : SVGUseElement::ContentInserted(nsIDocument *aDocument,
186 : nsIContent *aContainer,
187 : nsIContent *aChild,
188 : int32_t aIndexInContainer)
189 : {
190 0 : if (nsContentUtils::IsInSameAnonymousTree(this, aChild)) {
191 0 : TriggerReclone();
192 : }
193 0 : }
194 :
195 : void
196 0 : SVGUseElement::ContentRemoved(nsIDocument *aDocument,
197 : nsIContent *aContainer,
198 : nsIContent *aChild,
199 : int32_t aIndexInContainer,
200 : nsIContent *aPreviousSibling)
201 : {
202 0 : if (nsContentUtils::IsInSameAnonymousTree(this, aChild)) {
203 0 : TriggerReclone();
204 : }
205 0 : }
206 :
207 : void
208 0 : SVGUseElement::NodeWillBeDestroyed(const nsINode *aNode)
209 : {
210 0 : nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
211 0 : UnlinkSource();
212 0 : }
213 :
214 : //----------------------------------------------------------------------
215 :
216 : nsIContent*
217 23 : SVGUseElement::CreateAnonymousContent()
218 : {
219 23 : mClone = nullptr;
220 :
221 23 : if (mSource.get()) {
222 0 : mSource.get()->RemoveMutationObserver(this);
223 : }
224 :
225 23 : LookupHref();
226 23 : nsIContent* targetContent = mSource.get();
227 23 : if (!targetContent)
228 0 : return nullptr;
229 :
230 : // make sure target is valid type for <use>
231 : // QIable nsSVGGraphicsElement would eliminate enumerating all elements
232 23 : if (!targetContent->IsAnyOfSVGElements(nsGkAtoms::svg,
233 : nsGkAtoms::symbol,
234 : nsGkAtoms::g,
235 : nsGkAtoms::path,
236 : nsGkAtoms::text,
237 : nsGkAtoms::rect,
238 : nsGkAtoms::circle,
239 : nsGkAtoms::ellipse,
240 : nsGkAtoms::line,
241 : nsGkAtoms::polyline,
242 : nsGkAtoms::polygon,
243 : nsGkAtoms::image,
244 : nsGkAtoms::use))
245 0 : return nullptr;
246 :
247 : // circular loop detection
248 :
249 : // check 1 - check if we're a document descendent of the target
250 23 : if (nsContentUtils::ContentIsDescendantOf(this, targetContent))
251 0 : return nullptr;
252 :
253 : // check 2 - check if we're a clone, and if we already exist in the hierarchy
254 23 : if (GetParent() && mOriginal) {
255 0 : for (nsCOMPtr<nsIContent> content = GetParent();
256 : content;
257 0 : content = content->GetParent()) {
258 0 : if (content->IsSVGElement(nsGkAtoms::use) &&
259 0 : static_cast<SVGUseElement*>(content.get())->mOriginal == mOriginal) {
260 0 : return nullptr;
261 : }
262 : }
263 : }
264 :
265 46 : nsCOMPtr<nsINode> newnode;
266 : nsNodeInfoManager* nodeInfoManager =
267 23 : targetContent->OwnerDoc() == OwnerDoc() ?
268 23 : nullptr : OwnerDoc()->NodeInfoManager();
269 23 : nsNodeUtils::Clone(targetContent, true, nodeInfoManager, nullptr,
270 46 : getter_AddRefs(newnode));
271 :
272 46 : nsCOMPtr<nsIContent> newcontent = do_QueryInterface(newnode);
273 :
274 23 : if (!newcontent)
275 0 : return nullptr;
276 :
277 23 : if (newcontent->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::symbol)) {
278 0 : nsSVGElement *newElement = static_cast<nsSVGElement*>(newcontent.get());
279 :
280 0 : if (mLengthAttributes[ATTR_WIDTH].IsExplicitlySet())
281 0 : newElement->SetLength(nsGkAtoms::width, mLengthAttributes[ATTR_WIDTH]);
282 0 : if (mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet())
283 0 : newElement->SetLength(nsGkAtoms::height, mLengthAttributes[ATTR_HEIGHT]);
284 : }
285 :
286 : // Store the base URI
287 46 : nsCOMPtr<nsIURI> baseURI = targetContent->GetBaseURI();
288 23 : if (!baseURI) {
289 0 : return nullptr;
290 : }
291 46 : mContentURLData = new URLExtraData(baseURI.forget(),
292 46 : do_AddRef(OwnerDoc()->GetDocumentURI()),
293 69 : do_AddRef(NodePrincipal()));
294 :
295 23 : targetContent->AddMutationObserver(this);
296 23 : mClone = newcontent;
297 :
298 : #ifdef DEBUG
299 : // Our anonymous clone can get restyled by various things
300 : // (e.g. SMIL). Reconstructing its frame is OK, though, because
301 : // it's going to be our _only_ child in the frame tree, so can't get
302 : // mis-ordered with anything.
303 23 : mClone->SetProperty(nsGkAtoms::restylableAnonymousNode,
304 46 : reinterpret_cast<void*>(true));
305 : #endif // DEBUG
306 :
307 23 : return mClone;
308 : }
309 :
310 : nsIURI*
311 0 : SVGUseElement::GetSourceDocURI()
312 : {
313 0 : nsIContent* targetContent = mSource.get();
314 0 : if (!targetContent)
315 0 : return nullptr;
316 :
317 0 : return targetContent->OwnerDoc()->GetDocumentURI();
318 : }
319 :
320 : void
321 0 : SVGUseElement::DestroyAnonymousContent()
322 : {
323 0 : nsContentUtils::DestroyAnonymousContent(&mClone);
324 0 : }
325 :
326 : bool
327 0 : SVGUseElement::OurWidthAndHeightAreUsed() const
328 : {
329 0 : return mClone && mClone->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::symbol);
330 : }
331 :
332 : //----------------------------------------------------------------------
333 : // implementation helpers
334 :
335 : void
336 0 : SVGUseElement::SyncWidthOrHeight(nsIAtom* aName)
337 : {
338 0 : NS_ASSERTION(aName == nsGkAtoms::width || aName == nsGkAtoms::height,
339 : "The clue is in the function name");
340 0 : NS_ASSERTION(OurWidthAndHeightAreUsed(), "Don't call this");
341 :
342 0 : if (OurWidthAndHeightAreUsed()) {
343 0 : nsSVGElement *target = static_cast<nsSVGElement*>(mClone.get());
344 0 : uint32_t index = *sLengthInfo[ATTR_WIDTH].mName == aName ? ATTR_WIDTH : ATTR_HEIGHT;
345 :
346 0 : if (mLengthAttributes[index].IsExplicitlySet()) {
347 0 : target->SetLength(aName, mLengthAttributes[index]);
348 0 : return;
349 : }
350 0 : if (mClone->IsSVGElement(nsGkAtoms::svg)) {
351 : // Our width/height attribute is now no longer explicitly set, so we
352 : // need to revert the clone's width/height to the width/height of the
353 : // content that's being cloned.
354 0 : TriggerReclone();
355 0 : return;
356 : }
357 : // Our width/height attribute is now no longer explicitly set, so we
358 : // need to set the value to 100%
359 : nsSVGLength2 length;
360 : length.Init(SVGContentUtils::XY, 0xff,
361 0 : 100, nsIDOMSVGLength::SVG_LENGTHTYPE_PERCENTAGE);
362 0 : target->SetLength(aName, length);
363 0 : return;
364 : }
365 : }
366 :
367 : void
368 23 : SVGUseElement::LookupHref()
369 : {
370 46 : nsAutoString href;
371 23 : if (mStringAttributes[HREF].IsExplicitlySet()) {
372 2 : mStringAttributes[HREF].GetAnimValue(href, this);
373 : } else {
374 21 : mStringAttributes[XLINK_HREF].GetAnimValue(href, this);
375 : }
376 :
377 23 : if (href.IsEmpty()) {
378 0 : return;
379 : }
380 :
381 : nsCOMPtr<nsIURI> originURI =
382 46 : mOriginal ? mOriginal->GetBaseURI() : GetBaseURI();
383 23 : nsCOMPtr<nsIURI> baseURI = nsContentUtils::IsLocalRefURL(href)
384 69 : ? nsSVGEffects::GetBaseURLForLocalRef(this, originURI)
385 69 : : originURI;
386 :
387 46 : nsCOMPtr<nsIURI> targetURI;
388 46 : nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), href,
389 23 : GetComposedDoc(), baseURI);
390 23 : mSource.Reset(this, targetURI);
391 : }
392 :
393 : void
394 0 : SVGUseElement::TriggerReclone()
395 : {
396 0 : nsIDocument *doc = GetComposedDoc();
397 0 : if (!doc)
398 0 : return;
399 0 : nsIPresShell *presShell = doc->GetShell();
400 0 : if (!presShell)
401 0 : return;
402 0 : presShell->PostRecreateFramesFor(this);
403 : }
404 :
405 : void
406 0 : SVGUseElement::UnlinkSource()
407 : {
408 0 : if (mSource.get()) {
409 0 : mSource.get()->RemoveMutationObserver(this);
410 : }
411 0 : mSource.Unlink();
412 0 : }
413 :
414 : //----------------------------------------------------------------------
415 : // nsSVGElement methods
416 :
417 : /* virtual */ gfxMatrix
418 12 : SVGUseElement::PrependLocalTransformsTo(
419 : const gfxMatrix &aMatrix, SVGTransformTypes aWhich) const
420 : {
421 : // 'transform' attribute:
422 12 : gfxMatrix userToParent;
423 :
424 12 : if (aWhich == eUserSpaceToParent || aWhich == eAllTransforms) {
425 : userToParent = GetUserToParentTransform(mAnimateMotionTransform,
426 12 : mTransforms);
427 12 : if (aWhich == eUserSpaceToParent) {
428 12 : return userToParent * aMatrix;
429 : }
430 : }
431 :
432 : // our 'x' and 'y' attributes:
433 : float x, y;
434 0 : const_cast<SVGUseElement*>(this)->GetAnimatedLengthValues(&x, &y, nullptr);
435 :
436 0 : gfxMatrix childToUser = gfxMatrix::Translation(x, y);
437 :
438 0 : if (aWhich == eAllTransforms) {
439 0 : return childToUser * userToParent * aMatrix;
440 : }
441 :
442 0 : MOZ_ASSERT(aWhich == eChildToUserSpace, "Unknown TransformTypes");
443 :
444 : // The following may look broken because pre-multiplying our eChildToUserSpace
445 : // transform with another matrix without including our eUserSpaceToParent
446 : // transform between the two wouldn't make sense. We don't expect that to
447 : // ever happen though. We get here either when the identity matrix has been
448 : // passed because our caller just wants our eChildToUserSpace transform, or
449 : // when our eUserSpaceToParent transform has already been multiplied into the
450 : // matrix that our caller passes (such as when we're called from PaintSVG).
451 0 : return childToUser * aMatrix;
452 : }
453 :
454 : /* virtual */ bool
455 39 : SVGUseElement::HasValidDimensions() const
456 : {
457 39 : return (!mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() ||
458 117 : mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0) &&
459 39 : (!mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() ||
460 39 : mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0);
461 : }
462 :
463 : nsSVGElement::LengthAttributesInfo
464 89 : SVGUseElement::GetLengthInfo()
465 : {
466 : return LengthAttributesInfo(mLengthAttributes, sLengthInfo,
467 89 : ArrayLength(sLengthInfo));
468 : }
469 :
470 : nsSVGElement::StringAttributesInfo
471 100 : SVGUseElement::GetStringInfo()
472 : {
473 : return StringAttributesInfo(mStringAttributes, sStringInfo,
474 100 : ArrayLength(sStringInfo));
475 : }
476 :
477 : //----------------------------------------------------------------------
478 : // nsIContent methods
479 :
480 : NS_IMETHODIMP_(bool)
481 139 : SVGUseElement::IsAttributeMapped(const nsIAtom* name) const
482 : {
483 : static const MappedAttributeEntry* const map[] = {
484 : sFEFloodMap,
485 : sFiltersMap,
486 : sFontSpecificationMap,
487 : sGradientStopMap,
488 : sLightingEffectsMap,
489 : sMarkersMap,
490 : sTextContentElementsMap,
491 : sViewportsMap
492 : };
493 :
494 278 : return FindAttributeDependence(name, map) ||
495 278 : SVGUseElementBase::IsAttributeMapped(name);
496 : }
497 :
498 : } // namespace dom
499 : } // namespace mozilla
|