/src/mozilla-central/dom/svg/SVGAElement.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/dom/SVGAElement.h" |
8 | | |
9 | | #include "mozilla/Attributes.h" |
10 | | #include "mozilla/EventDispatcher.h" |
11 | | #include "mozilla/EventStates.h" |
12 | | #include "mozilla/dom/SVGAElementBinding.h" |
13 | | #include "nsCOMPtr.h" |
14 | | #include "nsContentUtils.h" |
15 | | #include "nsGkAtoms.h" |
16 | | #include "nsSVGString.h" |
17 | | #include "nsIContentInlines.h" |
18 | | #include "nsIURI.h" |
19 | | |
20 | | NS_IMPL_NS_NEW_NAMESPACED_SVG_ELEMENT(A) |
21 | | |
22 | | namespace mozilla { |
23 | | namespace dom { |
24 | | |
25 | | JSObject* |
26 | | SVGAElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) |
27 | 0 | { |
28 | 0 | return SVGAElement_Binding::Wrap(aCx, this, aGivenProto); |
29 | 0 | } |
30 | | |
31 | | nsSVGElement::StringInfo SVGAElement::sStringInfo[3] = |
32 | | { |
33 | | { &nsGkAtoms::href, kNameSpaceID_None, true }, |
34 | | { &nsGkAtoms::href, kNameSpaceID_XLink, true }, |
35 | | { &nsGkAtoms::target, kNameSpaceID_None, true } |
36 | | }; |
37 | | |
38 | | // static |
39 | | const DOMTokenListSupportedToken SVGAElement::sSupportedRelValues[] = { |
40 | | "noreferrer", |
41 | | "noopener", |
42 | | nullptr |
43 | | }; |
44 | | |
45 | | //---------------------------------------------------------------------- |
46 | | // nsISupports methods |
47 | | |
48 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGAElement) |
49 | 0 | NS_INTERFACE_MAP_ENTRY(Link) |
50 | 0 | NS_INTERFACE_MAP_END_INHERITING(SVGAElementBase) |
51 | | |
52 | | NS_IMPL_CYCLE_COLLECTION_INHERITED(SVGAElement, |
53 | | SVGAElementBase, |
54 | | mRelList) |
55 | | |
56 | | NS_IMPL_ADDREF_INHERITED(SVGAElement, SVGAElementBase) |
57 | | NS_IMPL_RELEASE_INHERITED(SVGAElement, SVGAElementBase) |
58 | | |
59 | | //---------------------------------------------------------------------- |
60 | | // Implementation |
61 | | |
62 | | SVGAElement::SVGAElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) |
63 | | : SVGAElementBase(std::move(aNodeInfo)) |
64 | | , Link(this) |
65 | 0 | { |
66 | 0 | } |
67 | | |
68 | | SVGAElement::~SVGAElement() |
69 | 0 | { |
70 | 0 | } |
71 | | |
72 | | already_AddRefed<SVGAnimatedString> |
73 | | SVGAElement::Href() |
74 | 0 | { |
75 | 0 | return mStringAttributes[HREF].IsExplicitlySet() |
76 | 0 | ? mStringAttributes[HREF].ToDOMAnimatedString(this) |
77 | 0 | : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); |
78 | 0 | } |
79 | | |
80 | | //---------------------------------------------------------------------- |
81 | | // Link methods |
82 | | |
83 | | bool |
84 | | SVGAElement::ElementHasHref() const |
85 | 0 | { |
86 | 0 | return mStringAttributes[HREF].IsExplicitlySet() || |
87 | 0 | mStringAttributes[XLINK_HREF].IsExplicitlySet(); |
88 | 0 | } |
89 | | |
90 | | //---------------------------------------------------------------------- |
91 | | // nsINode methods |
92 | | |
93 | | void |
94 | | SVGAElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) |
95 | 0 | { |
96 | 0 | Element::GetEventTargetParent(aVisitor); |
97 | 0 |
|
98 | 0 | GetEventTargetParentForLinks(aVisitor); |
99 | 0 | } |
100 | | |
101 | | nsresult |
102 | | SVGAElement::PostHandleEvent(EventChainPostVisitor& aVisitor) |
103 | 0 | { |
104 | 0 | return PostHandleEventForLinks(aVisitor); |
105 | 0 | } |
106 | | |
107 | | NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGAElement) |
108 | | |
109 | | |
110 | | //---------------------------------------------------------------------- |
111 | | |
112 | | already_AddRefed<SVGAnimatedString> |
113 | | SVGAElement::Target() |
114 | 0 | { |
115 | 0 | return mStringAttributes[TARGET].ToDOMAnimatedString(this); |
116 | 0 | } |
117 | | |
118 | | void |
119 | | SVGAElement::GetDownload(nsAString& aDownload) |
120 | 0 | { |
121 | 0 | GetAttr(nsGkAtoms::download, aDownload); |
122 | 0 | } |
123 | | |
124 | | void |
125 | | SVGAElement::SetDownload(const nsAString& aDownload, ErrorResult& rv) |
126 | 0 | { |
127 | 0 | SetAttr(nsGkAtoms::download, aDownload, rv); |
128 | 0 | } |
129 | | |
130 | | void |
131 | | SVGAElement::GetPing(nsAString& aPing) |
132 | 0 | { |
133 | 0 | GetAttr(nsGkAtoms::ping, aPing); |
134 | 0 | } |
135 | | |
136 | | void |
137 | | SVGAElement::SetPing(const nsAString& aPing, ErrorResult& rv) |
138 | 0 | { |
139 | 0 | SetAttr(nsGkAtoms::ping, aPing, rv); |
140 | 0 | } |
141 | | |
142 | | void |
143 | | SVGAElement::GetRel(nsAString& aRel) |
144 | 0 | { |
145 | 0 | GetAttr(nsGkAtoms::rel, aRel); |
146 | 0 | } |
147 | | |
148 | | void |
149 | | SVGAElement::SetRel(const nsAString& aRel, ErrorResult& rv) |
150 | 0 | { |
151 | 0 | SetAttr(nsGkAtoms::rel, aRel, rv); |
152 | 0 | } |
153 | | |
154 | | void |
155 | | SVGAElement::GetReferrerPolicy(nsAString& aPolicy) |
156 | 0 | { |
157 | 0 | GetEnumAttr(nsGkAtoms::referrerpolicy, EmptyCString().get(), aPolicy); |
158 | 0 | } |
159 | | |
160 | | void |
161 | | SVGAElement::SetReferrerPolicy(const nsAString& aPolicy, |
162 | | mozilla::ErrorResult& rv) |
163 | 0 | { |
164 | 0 | SetAttr(nsGkAtoms::referrerpolicy, aPolicy, rv); |
165 | 0 | } |
166 | | |
167 | | nsDOMTokenList* |
168 | | SVGAElement:: RelList() |
169 | 0 | { |
170 | 0 | if (!mRelList) { |
171 | 0 | mRelList = new nsDOMTokenList(this, nsGkAtoms::rel, sSupportedRelValues); |
172 | 0 | } |
173 | 0 | return mRelList; |
174 | 0 | } |
175 | | |
176 | | void |
177 | | SVGAElement::GetHreflang(nsAString& aHreflang) |
178 | 0 | { |
179 | 0 | GetAttr(nsGkAtoms::hreflang, aHreflang); |
180 | 0 | } |
181 | | |
182 | | void |
183 | | SVGAElement::SetHreflang(const nsAString& aHreflang, mozilla::ErrorResult& rv) |
184 | 0 | { |
185 | 0 | SetAttr(nsGkAtoms::hreflang, aHreflang, rv); |
186 | 0 | } |
187 | | |
188 | | void SVGAElement::GetType(nsAString& aType) |
189 | 0 | { |
190 | 0 | GetAttr(nsGkAtoms::type, aType); |
191 | 0 | } |
192 | | |
193 | | void SVGAElement::SetType(const nsAString& aType, mozilla::ErrorResult& rv) |
194 | 0 | { |
195 | 0 | SetAttr(nsGkAtoms::type, aType, rv); |
196 | 0 | } |
197 | | |
198 | | void SVGAElement::GetText(nsAString& aText, mozilla::ErrorResult& rv) |
199 | 0 | { |
200 | 0 | if (NS_WARN_IF(!nsContentUtils::GetNodeTextContent(this, true, aText, fallible))) { |
201 | 0 | rv.Throw(NS_ERROR_OUT_OF_MEMORY); |
202 | 0 | } |
203 | 0 | } |
204 | | |
205 | | void SVGAElement::SetText(const nsAString& aText, mozilla::ErrorResult& rv) |
206 | 0 | { |
207 | 0 | rv = nsContentUtils::SetNodeTextContent(this, aText, false); |
208 | 0 | } |
209 | | |
210 | | //---------------------------------------------------------------------- |
211 | | // nsIContent methods |
212 | | |
213 | | nsresult |
214 | | SVGAElement::BindToTree(nsIDocument *aDocument, nsIContent *aParent, |
215 | | nsIContent *aBindingParent) |
216 | 0 | { |
217 | 0 | Link::ResetLinkState(false, Link::ElementHasHref()); |
218 | 0 |
|
219 | 0 | nsresult rv = SVGAElementBase::BindToTree(aDocument, aParent, |
220 | 0 | aBindingParent); |
221 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
222 | 0 |
|
223 | 0 | nsIDocument* doc = GetComposedDoc(); |
224 | 0 | if (doc) { |
225 | 0 | doc->RegisterPendingLinkUpdate(this); |
226 | 0 | } |
227 | 0 |
|
228 | 0 | return NS_OK; |
229 | 0 | } |
230 | | |
231 | | void |
232 | | SVGAElement::UnbindFromTree(bool aDeep, bool aNullParent) |
233 | 0 | { |
234 | 0 | // If this link is ever reinserted into a document, it might |
235 | 0 | // be under a different xml:base, so forget the cached state now. |
236 | 0 | Link::ResetLinkState(false, Link::ElementHasHref()); |
237 | 0 |
|
238 | 0 | SVGAElementBase::UnbindFromTree(aDeep, aNullParent); |
239 | 0 | } |
240 | | |
241 | | already_AddRefed<nsIURI> |
242 | | SVGAElement::GetHrefURI() const |
243 | 0 | { |
244 | 0 | nsCOMPtr<nsIURI> hrefURI; |
245 | 0 | return IsLink(getter_AddRefs(hrefURI)) ? hrefURI.forget() : nullptr; |
246 | 0 | } |
247 | | |
248 | | |
249 | | NS_IMETHODIMP_(bool) |
250 | | SVGAElement::IsAttributeMapped(const nsAtom* name) const |
251 | 0 | { |
252 | 0 | static const MappedAttributeEntry* const map[] = { |
253 | 0 | sFEFloodMap, |
254 | 0 | sFiltersMap, |
255 | 0 | sFontSpecificationMap, |
256 | 0 | sGradientStopMap, |
257 | 0 | sLightingEffectsMap, |
258 | 0 | sMarkersMap, |
259 | 0 | sTextContentElementsMap, |
260 | 0 | sViewportsMap |
261 | 0 | }; |
262 | 0 |
|
263 | 0 | return FindAttributeDependence(name, map) || |
264 | 0 | SVGAElementBase::IsAttributeMapped(name); |
265 | 0 | } |
266 | | |
267 | | int32_t |
268 | | SVGAElement::TabIndexDefault() |
269 | 0 | { |
270 | 0 | return 0; |
271 | 0 | } |
272 | | |
273 | | static bool |
274 | | IsNodeInEditableRegion(nsINode* aNode) |
275 | 0 | { |
276 | 0 | while (aNode) { |
277 | 0 | if (aNode->IsEditable()) { |
278 | 0 | return true; |
279 | 0 | } |
280 | 0 | aNode = aNode->GetParent(); |
281 | 0 | } |
282 | 0 | return false; |
283 | 0 | } |
284 | | |
285 | | bool |
286 | | SVGAElement::IsSVGFocusable(bool* aIsFocusable, int32_t* aTabIndex) |
287 | 0 | { |
288 | 0 | if (nsSVGElement::IsSVGFocusable(aIsFocusable, aTabIndex)) { |
289 | 0 | return true; |
290 | 0 | } |
291 | 0 | |
292 | 0 | // cannot focus links if there is no link handler |
293 | 0 | nsIDocument* doc = GetComposedDoc(); |
294 | 0 | if (doc) { |
295 | 0 | nsPresContext* presContext = doc->GetPresContext(); |
296 | 0 | if (presContext && !presContext->GetLinkHandler()) { |
297 | 0 | *aIsFocusable = false; |
298 | 0 | return false; |
299 | 0 | } |
300 | 0 | } |
301 | 0 | |
302 | 0 | // Links that are in an editable region should never be focusable, even if |
303 | 0 | // they are in a contenteditable="false" region. |
304 | 0 | if (IsNodeInEditableRegion(this)) { |
305 | 0 | if (aTabIndex) { |
306 | 0 | *aTabIndex = -1; |
307 | 0 | } |
308 | 0 |
|
309 | 0 | *aIsFocusable = false; |
310 | 0 |
|
311 | 0 | return true; |
312 | 0 | } |
313 | 0 |
|
314 | 0 | if (!HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) { |
315 | 0 | // check whether we're actually a link |
316 | 0 | if (!Link::HasURI()) { |
317 | 0 | // Not tabbable or focusable without href (bug 17605), unless |
318 | 0 | // forced to be via presence of nonnegative tabindex attribute |
319 | 0 | if (aTabIndex) { |
320 | 0 | *aTabIndex = -1; |
321 | 0 | } |
322 | 0 |
|
323 | 0 | *aIsFocusable = false; |
324 | 0 |
|
325 | 0 | return false; |
326 | 0 | } |
327 | 0 | } |
328 | 0 |
|
329 | 0 | if (aTabIndex && (sTabFocusModel & eTabFocus_linksMask) == 0) { |
330 | 0 | *aTabIndex = -1; |
331 | 0 | } |
332 | 0 |
|
333 | 0 | *aIsFocusable = true; |
334 | 0 |
|
335 | 0 | return false; |
336 | 0 | } |
337 | | |
338 | | bool |
339 | | SVGAElement::IsLink(nsIURI** aURI) const |
340 | 0 | { |
341 | 0 | // To be a clickable XLink for styling and interaction purposes, we require: |
342 | 0 | // |
343 | 0 | // xlink:href - must be set |
344 | 0 | // xlink:type - must be unset or set to "" or set to "simple" |
345 | 0 | // xlink:show - must be unset or set to "", "new" or "replace" |
346 | 0 | // xlink:actuate - must be unset or set to "" or "onRequest" |
347 | 0 | // |
348 | 0 | // For any other values, we're either not a *clickable* XLink, or the end |
349 | 0 | // result is poorly specified. Either way, we return false. |
350 | 0 |
|
351 | 0 | static Element::AttrValuesArray sTypeVals[] = |
352 | 0 | { &nsGkAtoms::_empty, &nsGkAtoms::simple, nullptr }; |
353 | 0 |
|
354 | 0 | static Element::AttrValuesArray sShowVals[] = |
355 | 0 | { &nsGkAtoms::_empty, &nsGkAtoms::_new, &nsGkAtoms::replace, nullptr }; |
356 | 0 |
|
357 | 0 | static Element::AttrValuesArray sActuateVals[] = |
358 | 0 | { &nsGkAtoms::_empty, &nsGkAtoms::onRequest, nullptr }; |
359 | 0 |
|
360 | 0 | // Optimization: check for href first for early return |
361 | 0 | bool useBareHref = mStringAttributes[HREF].IsExplicitlySet(); |
362 | 0 |
|
363 | 0 | if ((useBareHref || mStringAttributes[XLINK_HREF].IsExplicitlySet()) && |
364 | 0 | FindAttrValueIn(kNameSpaceID_XLink, nsGkAtoms::type, |
365 | 0 | sTypeVals, eCaseMatters) != |
366 | 0 | Element::ATTR_VALUE_NO_MATCH && |
367 | 0 | FindAttrValueIn(kNameSpaceID_XLink, nsGkAtoms::show, |
368 | 0 | sShowVals, eCaseMatters) != |
369 | 0 | Element::ATTR_VALUE_NO_MATCH && |
370 | 0 | FindAttrValueIn(kNameSpaceID_XLink, nsGkAtoms::actuate, |
371 | 0 | sActuateVals, eCaseMatters) != |
372 | 0 | Element::ATTR_VALUE_NO_MATCH) { |
373 | 0 | nsCOMPtr<nsIURI> baseURI = GetBaseURI(); |
374 | 0 | // Get absolute URI |
375 | 0 | nsAutoString str; |
376 | 0 | const uint8_t idx = useBareHref ? HREF : XLINK_HREF; |
377 | 0 | mStringAttributes[idx].GetAnimValue(str, this); |
378 | 0 | nsContentUtils::NewURIWithDocumentCharset(aURI, str, OwnerDoc(), baseURI); |
379 | 0 | // must promise out param is non-null if we return true |
380 | 0 | return !!*aURI; |
381 | 0 | } |
382 | 0 |
|
383 | 0 | *aURI = nullptr; |
384 | 0 | return false; |
385 | 0 | } |
386 | | |
387 | | void |
388 | | SVGAElement::GetLinkTarget(nsAString& aTarget) |
389 | 0 | { |
390 | 0 | mStringAttributes[TARGET].GetAnimValue(aTarget, this); |
391 | 0 | if (aTarget.IsEmpty()) { |
392 | 0 |
|
393 | 0 | static Element::AttrValuesArray sShowVals[] = |
394 | 0 | { &nsGkAtoms::_new, &nsGkAtoms::replace, nullptr }; |
395 | 0 |
|
396 | 0 | switch (FindAttrValueIn(kNameSpaceID_XLink, nsGkAtoms::show, |
397 | 0 | sShowVals, eCaseMatters)) { |
398 | 0 | case 0: |
399 | 0 | aTarget.AssignLiteral("_blank"); |
400 | 0 | return; |
401 | 0 | case 1: |
402 | 0 | return; |
403 | 0 | } |
404 | 0 | nsIDocument* ownerDoc = OwnerDoc(); |
405 | 0 | if (ownerDoc) { |
406 | 0 | ownerDoc->GetBaseTarget(aTarget); |
407 | 0 | } |
408 | 0 | } |
409 | 0 | } |
410 | | |
411 | | EventStates |
412 | | SVGAElement::IntrinsicState() const |
413 | 0 | { |
414 | 0 | return Link::LinkState() | SVGAElementBase::IntrinsicState(); |
415 | 0 | } |
416 | | |
417 | | nsresult |
418 | | SVGAElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, |
419 | | const nsAttrValue* aValue, |
420 | | const nsAttrValue* aOldValue, |
421 | | nsIPrincipal* aMaybeScriptedPrincipal, |
422 | | bool aNotify) |
423 | 0 | { |
424 | 0 | if (aName == nsGkAtoms::href && |
425 | 0 | (aNameSpaceID == kNameSpaceID_XLink || |
426 | 0 | aNameSpaceID == kNameSpaceID_None)) { |
427 | 0 | // We can't assume that null aValue means we no longer have an href, because |
428 | 0 | // we could be unsetting xlink:href but still have a null-namespace href, or |
429 | 0 | // vice versa. But we can fast-path the case when we _do_ have a new value. |
430 | 0 | Link::ResetLinkState(aNotify, aValue || Link::ElementHasHref()); |
431 | 0 | } |
432 | 0 |
|
433 | 0 | return SVGAElementBase::AfterSetAttr(aNameSpaceID, aName, aValue, aOldValue, |
434 | 0 | aMaybeScriptedPrincipal, aNotify); |
435 | 0 | } |
436 | | |
437 | | //---------------------------------------------------------------------- |
438 | | // nsSVGElement methods |
439 | | |
440 | | nsSVGElement::StringAttributesInfo |
441 | | SVGAElement::GetStringInfo() |
442 | 0 | { |
443 | 0 | return StringAttributesInfo(mStringAttributes, sStringInfo, |
444 | 0 | ArrayLength(sStringInfo)); |
445 | 0 | } |
446 | | |
447 | | } // namespace dom |
448 | | } // namespace mozilla |