Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/svg/SVGUseElement.cpp
Line
Count
Source (jump to first uncovered line)
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
#include "mozilla/ErrorResult.h"
9
10
#include "mozilla/dom/SVGUseElement.h"
11
#include "mozilla/dom/SVGLengthBinding.h"
12
#include "mozilla/dom/SVGUseElementBinding.h"
13
#include "nsGkAtoms.h"
14
#include "mozilla/dom/SVGSVGElement.h"
15
#include "nsIDocument.h"
16
#include "nsIPresShell.h"
17
#include "mozilla/dom/Element.h"
18
#include "nsContentUtils.h"
19
#include "nsIURI.h"
20
#include "mozilla/URLExtraData.h"
21
#include "SVGObserverUtils.h"
22
#include "nsSVGUseFrame.h"
23
#include "mozilla/net/ReferrerPolicy.h"
24
25
NS_IMPL_NS_NEW_NAMESPACED_SVG_ELEMENT(Use)
26
27
namespace mozilla {
28
namespace dom {
29
30
JSObject*
31
SVGUseElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
32
0
{
33
0
  return SVGUseElement_Binding::Wrap(aCx, this, aGivenProto);
34
0
}
35
36
////////////////////////////////////////////////////////////////////////
37
// implementation
38
39
nsSVGElement::LengthInfo SVGUseElement::sLengthInfo[4] =
40
{
41
  { &nsGkAtoms::x, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, SVGContentUtils::X },
42
  { &nsGkAtoms::y, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, SVGContentUtils::Y },
43
  { &nsGkAtoms::width, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, SVGContentUtils::X },
44
  { &nsGkAtoms::height, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, SVGContentUtils::Y },
45
};
46
47
nsSVGElement::StringInfo SVGUseElement::sStringInfo[2] =
48
{
49
  { &nsGkAtoms::href, kNameSpaceID_None, true },
50
  { &nsGkAtoms::href, kNameSpaceID_XLink, true }
51
};
52
53
//----------------------------------------------------------------------
54
// nsISupports methods
55
56
NS_IMPL_CYCLE_COLLECTION_CLASS(SVGUseElement)
57
58
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SVGUseElement,
59
0
                                                SVGUseElementBase)
60
0
  nsAutoScriptBlocker scriptBlocker;
61
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginal)
62
0
  tmp->UnlinkSource();
63
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
64
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SVGUseElement,
65
0
                                                  SVGUseElementBase)
66
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginal)
67
0
  tmp->mReferencedElementTracker.Traverse(&cb);
68
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
69
70
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(SVGUseElement,
71
                                             SVGUseElementBase,
72
                                             nsIMutationObserver)
73
74
//----------------------------------------------------------------------
75
// Implementation
76
77
SVGUseElement::SVGUseElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
78
  : SVGUseElementBase(std::move(aNodeInfo))
79
  , mReferencedElementTracker(this)
80
0
{
81
0
}
82
83
SVGUseElement::~SVGUseElement()
84
0
{
85
0
  UnlinkSource();
86
0
  MOZ_DIAGNOSTIC_ASSERT(
87
0
    !OwnerDoc()->SVGUseElementNeedsShadowTreeUpdate(*this),
88
0
    "Dying without unbinding?"
89
0
  );
90
0
}
91
92
//----------------------------------------------------------------------
93
// nsINode methods
94
95
nsresult
96
SVGUseElement::Clone(dom::NodeInfo* aNodeInfo, nsINode** aResult) const
97
0
{
98
0
  *aResult = nullptr;
99
0
  SVGUseElement *it = new SVGUseElement(do_AddRef(aNodeInfo));
100
0
101
0
  nsCOMPtr<nsINode> kungFuDeathGrip(it);
102
0
  nsresult rv1 = it->Init();
103
0
  nsresult rv2 = const_cast<SVGUseElement*>(this)->CopyInnerTo(it);
104
0
105
0
  // SVGUseElement specific portion - record who we cloned from
106
0
  it->mOriginal = const_cast<SVGUseElement*>(this);
107
0
108
0
  if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2)) {
109
0
    kungFuDeathGrip.swap(*aResult);
110
0
  }
111
0
112
0
  return NS_FAILED(rv1) ? rv1 : rv2;
113
0
}
114
115
nsresult
116
SVGUseElement::BindToTree(nsIDocument* aDocument,
117
                          nsIContent* aParent,
118
                          nsIContent* aBindingParent)
119
0
{
120
0
  nsresult rv = SVGUseElementBase::BindToTree(aDocument, aParent, aBindingParent);
121
0
  NS_ENSURE_SUCCESS(rv, rv);
122
0
123
0
  TriggerReclone();
124
0
  return NS_OK;
125
0
}
126
127
void
128
SVGUseElement::UnbindFromTree(bool aDeep, bool aNullParent)
129
0
{
130
0
  SVGUseElementBase::UnbindFromTree(aDeep, aNullParent);
131
0
  OwnerDoc()->UnscheduleSVGUseElementShadowTreeUpdate(*this);
132
0
}
133
134
already_AddRefed<SVGAnimatedString>
135
SVGUseElement::Href()
136
0
{
137
0
  return mStringAttributes[HREF].IsExplicitlySet()
138
0
         ? mStringAttributes[HREF].ToDOMAnimatedString(this)
139
0
         : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this);
140
0
}
141
142
//----------------------------------------------------------------------
143
144
already_AddRefed<SVGAnimatedLength>
145
SVGUseElement::X()
146
0
{
147
0
  return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this);
148
0
}
149
150
already_AddRefed<SVGAnimatedLength>
151
SVGUseElement::Y()
152
0
{
153
0
  return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this);
154
0
}
155
156
already_AddRefed<SVGAnimatedLength>
157
SVGUseElement::Width()
158
0
{
159
0
  return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this);
160
0
}
161
162
already_AddRefed<SVGAnimatedLength>
163
SVGUseElement::Height()
164
0
{
165
0
  return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this);
166
0
}
167
168
//----------------------------------------------------------------------
169
// nsIMutationObserver methods
170
171
void
172
SVGUseElement::CharacterDataChanged(nsIContent* aContent,
173
                                    const CharacterDataChangeInfo&)
174
0
{
175
0
  if (nsContentUtils::IsInSameAnonymousTree(this, aContent)) {
176
0
    TriggerReclone();
177
0
  }
178
0
}
179
180
void
181
SVGUseElement::AttributeChanged(Element* aElement,
182
                                int32_t aNameSpaceID,
183
                                nsAtom* aAttribute,
184
                                int32_t aModType,
185
                                const nsAttrValue* aOldValue)
186
0
{
187
0
  if (nsContentUtils::IsInSameAnonymousTree(this, aElement)) {
188
0
    TriggerReclone();
189
0
  }
190
0
}
191
192
void
193
SVGUseElement::ContentAppended(nsIContent* aFirstNewContent)
194
0
{
195
0
  // FIXME(emilio, bug 1442336): Why does this check the parent but
196
0
  // ContentInserted the child?
197
0
  if (nsContentUtils::IsInSameAnonymousTree(this, aFirstNewContent->GetParent())) {
198
0
    TriggerReclone();
199
0
  }
200
0
}
201
202
void
203
SVGUseElement::ContentInserted(nsIContent* aChild)
204
0
{
205
0
  // FIXME(emilio, bug 1442336): Why does this check the child but
206
0
  // ContentAppended the parent?
207
0
  if (nsContentUtils::IsInSameAnonymousTree(this, aChild)) {
208
0
    TriggerReclone();
209
0
  }
210
0
}
211
212
void
213
SVGUseElement::ContentRemoved(nsIContent* aChild, nsIContent* aPreviousSibling)
214
0
{
215
0
  if (nsContentUtils::IsInSameAnonymousTree(this, aChild)) {
216
0
    TriggerReclone();
217
0
  }
218
0
}
219
220
void
221
SVGUseElement::NodeWillBeDestroyed(const nsINode *aNode)
222
0
{
223
0
  nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
224
0
  UnlinkSource();
225
0
}
226
227
//----------------------------------------------------------------------
228
229
void
230
SVGUseElement::UpdateShadowTree()
231
0
{
232
0
  MOZ_ASSERT(IsInComposedDoc());
233
0
234
0
  if (mReferencedElementTracker.get()) {
235
0
    mReferencedElementTracker.get()->RemoveMutationObserver(this);
236
0
  }
237
0
238
0
  LookupHref();
239
0
240
0
  RefPtr<ShadowRoot> shadow = GetShadowRoot();
241
0
  if (!shadow) {
242
0
    shadow = AttachShadowWithoutNameChecks(ShadowRootMode::Closed);
243
0
  }
244
0
  MOZ_ASSERT(shadow);
245
0
246
0
  Element* targetElement = mReferencedElementTracker.get();
247
0
  RefPtr<Element> newElement;
248
0
249
0
  auto UpdateShadowTree = mozilla::MakeScopeExit([&]() {
250
0
    nsIContent* firstChild = shadow->GetFirstChild();
251
0
    if (firstChild) {
252
0
      MOZ_ASSERT(!firstChild->GetNextSibling());
253
0
      shadow->RemoveChildNode(firstChild, /* aNotify = */ true);
254
0
    }
255
0
256
0
    if (newElement) {
257
0
      shadow->AppendChildTo(newElement, /* aNotify = */ true);
258
0
    }
259
0
  });
260
0
261
0
  // make sure target is valid type for <use>
262
0
  // QIable nsSVGGraphicsElement would eliminate enumerating all elements
263
0
  if (!targetElement ||
264
0
      !targetElement->IsAnyOfSVGElements(nsGkAtoms::svg,
265
0
                                         nsGkAtoms::symbol,
266
0
                                         nsGkAtoms::g,
267
0
                                         nsGkAtoms::path,
268
0
                                         nsGkAtoms::text,
269
0
                                         nsGkAtoms::rect,
270
0
                                         nsGkAtoms::circle,
271
0
                                         nsGkAtoms::ellipse,
272
0
                                         nsGkAtoms::line,
273
0
                                         nsGkAtoms::polyline,
274
0
                                         nsGkAtoms::polygon,
275
0
                                         nsGkAtoms::image,
276
0
                                         nsGkAtoms::use)) {
277
0
    return;
278
0
  }
279
0
280
0
  // circular loop detection
281
0
282
0
  // check 1 - check if we're a document descendent of the target
283
0
  if (nsContentUtils::ContentIsShadowIncludingDescendantOf(this, targetElement)) {
284
0
    return;
285
0
  }
286
0
287
0
  // check 2 - check if we're a clone, and if we already exist in the hierarchy
288
0
  if (mOriginal) {
289
0
    for (nsINode* parent = GetParentOrHostNode();
290
0
         parent;
291
0
         parent = parent->GetParentOrHostNode()) {
292
0
      SVGUseElement* use = SVGUseElement::FromNode(*parent);
293
0
      if (use && use->mOriginal == mOriginal) {
294
0
        return;
295
0
      }
296
0
    }
297
0
  }
298
0
299
0
  nsCOMPtr<nsIURI> baseURI = targetElement->GetBaseURI();
300
0
  if (!baseURI) {
301
0
    return;
302
0
  }
303
0
304
0
  {
305
0
    nsNodeInfoManager* nodeInfoManager =
306
0
      targetElement->OwnerDoc() == OwnerDoc()
307
0
        ? nullptr
308
0
        : OwnerDoc()->NodeInfoManager();
309
0
310
0
    nsCOMPtr<nsINode> newNode =
311
0
      nsNodeUtils::Clone(targetElement, true, nodeInfoManager, nullptr,
312
0
                         IgnoreErrors());
313
0
    if (!newNode) {
314
0
      return;
315
0
    }
316
0
317
0
    MOZ_ASSERT(newNode->IsElement());
318
0
    newElement = newNode.forget().downcast<Element>();
319
0
  }
320
0
321
0
  if (newElement->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::symbol)) {
322
0
    auto* newSVGElement = static_cast<nsSVGElement*>(newElement.get());
323
0
    if (mLengthAttributes[ATTR_WIDTH].IsExplicitlySet())
324
0
      newSVGElement->SetLength(nsGkAtoms::width, mLengthAttributes[ATTR_WIDTH]);
325
0
    if (mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet())
326
0
      newSVGElement->SetLength(nsGkAtoms::height, mLengthAttributes[ATTR_HEIGHT]);
327
0
  }
328
0
329
0
  // The specs do not say which referrer policy we should use, pass RP_Unset for
330
0
  // now
331
0
  mContentURLData = new URLExtraData(baseURI.forget(),
332
0
                                     do_AddRef(OwnerDoc()->GetDocumentURI()),
333
0
                                     do_AddRef(NodePrincipal()),
334
0
                                     mozilla::net::RP_Unset);
335
0
336
0
  targetElement->AddMutationObserver(this);
337
0
}
338
339
nsIURI*
340
SVGUseElement::GetSourceDocURI()
341
0
{
342
0
  nsIContent* targetElement = mReferencedElementTracker.get();
343
0
  if (!targetElement) {
344
0
    return nullptr;
345
0
  }
346
0
347
0
  return targetElement->OwnerDoc()->GetDocumentURI();
348
0
}
349
350
static nsINode*
351
GetClonedChild(const SVGUseElement& aUseElement)
352
0
{
353
0
  const ShadowRoot* shadow = aUseElement.GetShadowRoot();
354
0
  return shadow ? shadow->GetFirstChild() : nullptr;
355
0
}
356
357
bool
358
SVGUseElement::OurWidthAndHeightAreUsed() const
359
0
{
360
0
  nsINode* clonedChild = GetClonedChild(*this);
361
0
  return clonedChild &&
362
0
    clonedChild->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::symbol);
363
0
}
364
365
//----------------------------------------------------------------------
366
// implementation helpers
367
368
void
369
SVGUseElement::SyncWidthOrHeight(nsAtom* aName)
370
0
{
371
0
  NS_ASSERTION(aName == nsGkAtoms::width || aName == nsGkAtoms::height,
372
0
               "The clue is in the function name");
373
0
  NS_ASSERTION(OurWidthAndHeightAreUsed(), "Don't call this");
374
0
375
0
  if (!OurWidthAndHeightAreUsed()) {
376
0
    return;
377
0
  }
378
0
379
0
  auto* target = nsSVGElement::FromNode(GetClonedChild(*this));
380
0
  uint32_t index = *sLengthInfo[ATTR_WIDTH].mName == aName ? ATTR_WIDTH : ATTR_HEIGHT;
381
0
382
0
  if (mLengthAttributes[index].IsExplicitlySet()) {
383
0
    target->SetLength(aName, mLengthAttributes[index]);
384
0
    return;
385
0
  }
386
0
  if (target->IsSVGElement(nsGkAtoms::svg)) {
387
0
    // Our width/height attribute is now no longer explicitly set, so we
388
0
    // need to revert the clone's width/height to the width/height of the
389
0
    // content that's being cloned.
390
0
    TriggerReclone();
391
0
    return;
392
0
  }
393
0
  // Our width/height attribute is now no longer explicitly set, so we
394
0
  // need to set the value to 100%
395
0
  nsSVGLength2 length;
396
0
  length.Init(SVGContentUtils::XY, 0xff,
397
0
              100, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE);
398
0
  target->SetLength(aName, length);
399
0
}
400
401
void
402
SVGUseElement::LookupHref()
403
0
{
404
0
  nsAutoString href;
405
0
  if (mStringAttributes[HREF].IsExplicitlySet()) {
406
0
    mStringAttributes[HREF].GetAnimValue(href, this);
407
0
  } else {
408
0
    mStringAttributes[XLINK_HREF].GetAnimValue(href, this);
409
0
  }
410
0
411
0
  if (href.IsEmpty()) {
412
0
    return;
413
0
  }
414
0
415
0
  nsCOMPtr<nsIURI> originURI =
416
0
    mOriginal ? mOriginal->GetBaseURI() : GetBaseURI();
417
0
  nsCOMPtr<nsIURI> baseURI = nsContentUtils::IsLocalRefURL(href)
418
0
    ? SVGObserverUtils::GetBaseURLForLocalRef(this, originURI)
419
0
    : originURI;
420
0
421
0
  nsCOMPtr<nsIURI> targetURI;
422
0
  nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), href,
423
0
                                            GetComposedDoc(), baseURI);
424
0
  // Bug 1415044 to investigate which referrer we should use
425
0
  mReferencedElementTracker.Reset(this, targetURI,
426
0
                                  OwnerDoc()->GetDocumentURI(),
427
0
                                  OwnerDoc()->GetReferrerPolicy());
428
0
}
429
430
void
431
SVGUseElement::TriggerReclone()
432
0
{
433
0
  if (nsIDocument* doc = GetComposedDoc()) {
434
0
    doc->ScheduleSVGUseElementShadowTreeUpdate(*this);
435
0
  }
436
0
}
437
438
void
439
SVGUseElement::UnlinkSource()
440
0
{
441
0
  if (mReferencedElementTracker.get()) {
442
0
    mReferencedElementTracker.get()->RemoveMutationObserver(this);
443
0
  }
444
0
  mReferencedElementTracker.Unlink();
445
0
}
446
447
//----------------------------------------------------------------------
448
// nsSVGElement methods
449
450
/* virtual */ gfxMatrix
451
SVGUseElement::PrependLocalTransformsTo(
452
  const gfxMatrix &aMatrix, SVGTransformTypes aWhich) const
453
0
{
454
0
  // 'transform' attribute:
455
0
  gfxMatrix userToParent;
456
0
457
0
  if (aWhich == eUserSpaceToParent || aWhich == eAllTransforms) {
458
0
    userToParent = GetUserToParentTransform(mAnimateMotionTransform,
459
0
                                            mTransforms);
460
0
    if (aWhich == eUserSpaceToParent) {
461
0
      return userToParent * aMatrix;
462
0
    }
463
0
  }
464
0
465
0
  // our 'x' and 'y' attributes:
466
0
  float x, y;
467
0
  const_cast<SVGUseElement*>(this)->GetAnimatedLengthValues(&x, &y, nullptr);
468
0
469
0
  gfxMatrix childToUser = gfxMatrix::Translation(x, y);
470
0
471
0
  if (aWhich == eAllTransforms) {
472
0
    return childToUser * userToParent * aMatrix;
473
0
  }
474
0
475
0
  MOZ_ASSERT(aWhich == eChildToUserSpace, "Unknown TransformTypes");
476
0
477
0
  // The following may look broken because pre-multiplying our eChildToUserSpace
478
0
  // transform with another matrix without including our eUserSpaceToParent
479
0
  // transform between the two wouldn't make sense.  We don't expect that to
480
0
  // ever happen though.  We get here either when the identity matrix has been
481
0
  // passed because our caller just wants our eChildToUserSpace transform, or
482
0
  // when our eUserSpaceToParent transform has already been multiplied into the
483
0
  // matrix that our caller passes (such as when we're called from PaintSVG).
484
0
  return childToUser * aMatrix;
485
0
}
486
487
/* virtual */ bool
488
SVGUseElement::HasValidDimensions() const
489
0
{
490
0
  return (!mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() ||
491
0
           mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0) &&
492
0
         (!mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() ||
493
0
           mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0);
494
0
}
495
496
nsSVGElement::LengthAttributesInfo
497
SVGUseElement::GetLengthInfo()
498
0
{
499
0
  return LengthAttributesInfo(mLengthAttributes, sLengthInfo,
500
0
                              ArrayLength(sLengthInfo));
501
0
}
502
503
nsSVGElement::StringAttributesInfo
504
SVGUseElement::GetStringInfo()
505
0
{
506
0
  return StringAttributesInfo(mStringAttributes, sStringInfo,
507
0
                              ArrayLength(sStringInfo));
508
0
}
509
510
nsSVGUseFrame*
511
SVGUseElement::GetFrame() const
512
0
{
513
0
  nsIFrame* frame = GetPrimaryFrame();
514
0
  MOZ_ASSERT_IF(frame, frame->IsSVGUseFrame());
515
0
  return static_cast<nsSVGUseFrame*>(frame);
516
0
}
517
518
//----------------------------------------------------------------------
519
// nsIContent methods
520
521
NS_IMETHODIMP_(bool)
522
SVGUseElement::IsAttributeMapped(const nsAtom* name) const
523
0
{
524
0
  static const MappedAttributeEntry* const map[] = {
525
0
    sFEFloodMap,
526
0
    sFiltersMap,
527
0
    sFontSpecificationMap,
528
0
    sGradientStopMap,
529
0
    sLightingEffectsMap,
530
0
    sMarkersMap,
531
0
    sTextContentElementsMap,
532
0
    sViewportsMap
533
0
  };
534
0
535
0
  return FindAttributeDependence(name, map) ||
536
0
    SVGUseElementBase::IsAttributeMapped(name);
537
0
}
538
539
} // namespace dom
540
} // namespace mozilla