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 : /*
8 : * Implementation of the |attributes| property of DOM Core's Element object.
9 : */
10 :
11 : #include "nsDOMAttributeMap.h"
12 :
13 : #include "mozilla/MemoryReporting.h"
14 : #include "mozilla/dom/Attr.h"
15 : #include "mozilla/dom/Element.h"
16 : #include "mozilla/dom/NamedNodeMapBinding.h"
17 : #include "mozilla/dom/NodeInfoInlines.h"
18 : #include "nsAttrName.h"
19 : #include "nsContentUtils.h"
20 : #include "nsError.h"
21 : #include "nsIContentInlines.h"
22 : #include "nsIDocument.h"
23 : #include "nsNameSpaceManager.h"
24 : #include "nsNodeInfoManager.h"
25 : #include "nsUnicharUtils.h"
26 : #include "nsWrapperCacheInlines.h"
27 :
28 : using namespace mozilla;
29 : using namespace mozilla::dom;
30 :
31 : //----------------------------------------------------------------------
32 :
33 0 : nsDOMAttributeMap::nsDOMAttributeMap(Element* aContent)
34 0 : : mContent(aContent)
35 : {
36 : // We don't add a reference to our content. If it goes away,
37 : // we'll be told to drop our reference
38 0 : }
39 :
40 0 : nsDOMAttributeMap::~nsDOMAttributeMap()
41 : {
42 0 : DropReference();
43 0 : }
44 :
45 : void
46 0 : nsDOMAttributeMap::DropReference()
47 : {
48 0 : for (auto iter = mAttributeCache.Iter(); !iter.Done(); iter.Next()) {
49 0 : iter.Data()->SetMap(nullptr);
50 0 : iter.Remove();
51 : }
52 0 : mContent = nullptr;
53 0 : }
54 :
55 : NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMAttributeMap)
56 :
57 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMAttributeMap)
58 0 : tmp->DropReference();
59 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
60 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mContent)
61 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
62 :
63 :
64 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMAttributeMap)
65 0 : for (auto iter = tmp->mAttributeCache.Iter(); !iter.Done(); iter.Next()) {
66 0 : cb.NoteXPCOMChild(static_cast<nsINode*>(iter.Data().get()));
67 : }
68 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent)
69 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
70 :
71 0 : NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsDOMAttributeMap)
72 :
73 0 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsDOMAttributeMap)
74 0 : if (tmp->HasKnownLiveWrapper()) {
75 0 : if (tmp->mContent) {
76 : // The map owns the element so we can mark it when the
77 : // map itself is certainly alive.
78 0 : mozilla::dom::FragmentOrElement::MarkNodeChildren(tmp->mContent);
79 : }
80 0 : return true;
81 : }
82 0 : if (tmp->mContent &&
83 0 : mozilla::dom::FragmentOrElement::CanSkip(tmp->mContent, true)) {
84 0 : return true;
85 : }
86 0 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
87 :
88 0 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsDOMAttributeMap)
89 0 : return tmp->HasKnownLiveWrapperAndDoesNotNeedTracing(tmp);
90 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
91 :
92 0 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsDOMAttributeMap)
93 0 : return tmp->HasKnownLiveWrapper();
94 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
95 :
96 : // QueryInterface implementation for nsDOMAttributeMap
97 0 : NS_INTERFACE_TABLE_HEAD(nsDOMAttributeMap)
98 0 : NS_INTERFACE_TABLE(nsDOMAttributeMap, nsIDOMMozNamedAttrMap)
99 0 : NS_INTERFACE_TABLE_TO_MAP_SEGUE
100 0 : NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
101 0 : NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsDOMAttributeMap)
102 0 : NS_INTERFACE_MAP_END
103 :
104 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMAttributeMap)
105 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMAttributeMap)
106 :
107 : nsresult
108 0 : nsDOMAttributeMap::SetOwnerDocument(nsIDocument* aDocument)
109 : {
110 0 : for (auto iter = mAttributeCache.Iter(); !iter.Done(); iter.Next()) {
111 0 : nsresult rv = iter.Data()->SetOwnerDocument(aDocument);
112 0 : NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
113 : }
114 0 : return NS_OK;
115 : }
116 :
117 : void
118 0 : nsDOMAttributeMap::DropAttribute(int32_t aNamespaceID, nsIAtom* aLocalName)
119 : {
120 0 : nsAttrKey attr(aNamespaceID, aLocalName);
121 0 : if (auto entry = mAttributeCache.Lookup(attr)) {
122 0 : entry.Data()->SetMap(nullptr); // break link to map
123 0 : entry.Remove();
124 : }
125 0 : }
126 :
127 : Attr*
128 0 : nsDOMAttributeMap::GetAttribute(mozilla::dom::NodeInfo* aNodeInfo)
129 : {
130 0 : NS_ASSERTION(aNodeInfo, "GetAttribute() called with aNodeInfo == nullptr!");
131 :
132 0 : nsAttrKey attr(aNodeInfo->NamespaceID(), aNodeInfo->NameAtom());
133 :
134 0 : RefPtr<Attr>& entryValue = mAttributeCache.GetOrInsert(attr);
135 0 : Attr* node = entryValue;
136 0 : if (!node) {
137 : // Newly inserted entry!
138 0 : RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
139 0 : entryValue = new Attr(this, ni.forget(), EmptyString());
140 0 : node = entryValue;
141 : }
142 :
143 0 : return node;
144 : }
145 :
146 : Attr*
147 0 : nsDOMAttributeMap::NamedGetter(const nsAString& aAttrName, bool& aFound)
148 : {
149 0 : aFound = false;
150 0 : NS_ENSURE_TRUE(mContent, nullptr);
151 :
152 0 : RefPtr<mozilla::dom::NodeInfo> ni = mContent->GetExistingAttrNameFromQName(aAttrName);
153 0 : if (!ni) {
154 0 : return nullptr;
155 : }
156 :
157 0 : aFound = true;
158 0 : return GetAttribute(ni);
159 : }
160 :
161 : void
162 0 : nsDOMAttributeMap::GetSupportedNames(nsTArray<nsString>& aNames)
163 : {
164 : // For HTML elements in HTML documents, only include names that are still the
165 : // same after ASCII-lowercasing, since our named getter will end up
166 : // ASCII-lowercasing the given string.
167 : bool lowercaseNamesOnly =
168 0 : mContent->IsHTMLElement() && mContent->IsInHTMLDocument();
169 :
170 0 : const uint32_t count = mContent->GetAttrCount();
171 0 : bool seenNonAtomName = false;
172 0 : for (uint32_t i = 0; i < count; i++) {
173 0 : const nsAttrName* name = mContent->GetAttrNameAt(i);
174 0 : seenNonAtomName = seenNonAtomName || !name->IsAtom();
175 0 : nsString qualifiedName;
176 0 : name->GetQualifiedName(qualifiedName);
177 :
178 0 : if (lowercaseNamesOnly &&
179 0 : nsContentUtils::StringContainsASCIIUpper(qualifiedName)) {
180 0 : continue;
181 : }
182 :
183 : // Omit duplicates. We only need to do this check if we've seen a non-atom
184 : // name, because that's the only way we can have two identical qualified
185 : // names.
186 0 : if (seenNonAtomName && aNames.Contains(qualifiedName)) {
187 0 : continue;
188 : }
189 :
190 0 : aNames.AppendElement(qualifiedName);
191 : }
192 0 : }
193 :
194 : Attr*
195 0 : nsDOMAttributeMap::GetNamedItem(const nsAString& aAttrName)
196 : {
197 : bool dummy;
198 0 : return NamedGetter(aAttrName, dummy);
199 : }
200 :
201 : NS_IMETHODIMP
202 0 : nsDOMAttributeMap::GetNamedItem(const nsAString& aAttrName,
203 : nsIDOMAttr** aAttribute)
204 : {
205 0 : NS_ENSURE_ARG_POINTER(aAttribute);
206 :
207 0 : NS_IF_ADDREF(*aAttribute = GetNamedItem(aAttrName));
208 :
209 0 : return NS_OK;
210 : }
211 :
212 : NS_IMETHODIMP
213 0 : nsDOMAttributeMap::SetNamedItem(nsIDOMAttr* aAttr, nsIDOMAttr** aReturn)
214 : {
215 0 : Attr* attribute = static_cast<Attr*>(aAttr);
216 0 : NS_ENSURE_ARG(attribute);
217 :
218 0 : ErrorResult rv;
219 0 : *aReturn = SetNamedItemNS(*attribute, rv).take();
220 0 : return rv.StealNSResult();
221 : }
222 :
223 : NS_IMETHODIMP
224 0 : nsDOMAttributeMap::SetNamedItemNS(nsIDOMAttr* aAttr, nsIDOMAttr** aReturn)
225 : {
226 0 : Attr* attribute = static_cast<Attr*>(aAttr);
227 0 : NS_ENSURE_ARG(attribute);
228 :
229 0 : ErrorResult rv;
230 0 : *aReturn = SetNamedItemNS(*attribute, rv).take();
231 0 : return rv.StealNSResult();
232 : }
233 :
234 : already_AddRefed<Attr>
235 0 : nsDOMAttributeMap::SetNamedItemNS(Attr& aAttr, ErrorResult& aError)
236 : {
237 0 : NS_ENSURE_TRUE(mContent, nullptr);
238 :
239 : // XXX should check same-origin between mContent and aAttr however
240 : // nsContentUtils::CheckSameOrigin can't deal with attributenodes yet
241 :
242 : // Check that attribute is not owned by somebody else
243 0 : nsDOMAttributeMap* owner = aAttr.GetMap();
244 0 : if (owner) {
245 0 : if (owner != this) {
246 0 : aError.Throw(NS_ERROR_DOM_INUSE_ATTRIBUTE_ERR);
247 0 : return nullptr;
248 : }
249 :
250 : // setting a preexisting attribute is a no-op, just return the same
251 : // node.
252 0 : RefPtr<Attr> attribute = &aAttr;
253 0 : return attribute.forget();
254 : }
255 :
256 : nsresult rv;
257 0 : if (mContent->OwnerDoc() != aAttr.OwnerDoc()) {
258 : DebugOnly<void*> adoptedNode =
259 0 : mContent->OwnerDoc()->AdoptNode(aAttr, aError);
260 0 : if (aError.Failed()) {
261 0 : return nullptr;
262 : }
263 :
264 0 : NS_ASSERTION(adoptedNode == &aAttr, "Uh, adopt node changed nodes?");
265 : }
266 :
267 : // Get nodeinfo and preexisting attribute (if it exists)
268 0 : RefPtr<NodeInfo> oldNi;
269 :
270 0 : uint32_t i, count = mContent->GetAttrCount();
271 0 : for (i = 0; i < count; ++i) {
272 0 : const nsAttrName* name = mContent->GetAttrNameAt(i);
273 0 : int32_t attrNS = name->NamespaceID();
274 0 : nsIAtom* nameAtom = name->LocalName();
275 :
276 : // we're purposefully ignoring the prefix.
277 0 : if (aAttr.NodeInfo()->Equals(nameAtom, attrNS)) {
278 0 : oldNi = mContent->NodeInfo()->NodeInfoManager()->
279 0 : GetNodeInfo(nameAtom, name->GetPrefix(), aAttr.NodeInfo()->NamespaceID(),
280 0 : nsIDOMNode::ATTRIBUTE_NODE);
281 0 : break;
282 : }
283 : }
284 :
285 0 : RefPtr<Attr> oldAttr;
286 :
287 0 : if (oldNi) {
288 0 : oldAttr = GetAttribute(oldNi);
289 :
290 0 : if (oldAttr == &aAttr) {
291 0 : return oldAttr.forget();
292 : }
293 :
294 0 : if (oldAttr) {
295 : // Just remove it from our hashtable. This has no side-effects, so we
296 : // don't have to recheck anything after we do it. Then we'll add our new
297 : // Attr to the hashtable and do the actual attr set on the element. This
298 : // will make the whole thing look like a single attribute mutation (with
299 : // the new attr node in place) as opposed to a removal and addition.
300 0 : DropAttribute(oldNi->NamespaceID(), oldNi->NameAtom());
301 : }
302 : }
303 :
304 0 : nsAutoString value;
305 0 : aAttr.GetValue(value);
306 :
307 0 : RefPtr<NodeInfo> ni = aAttr.NodeInfo();
308 :
309 : // Add the new attribute to the attribute map before updating
310 : // its value in the element. @see bug 364413.
311 0 : nsAttrKey attrkey(ni->NamespaceID(), ni->NameAtom());
312 0 : mAttributeCache.Put(attrkey, &aAttr);
313 0 : aAttr.SetMap(this);
314 :
315 0 : rv = mContent->SetAttr(ni->NamespaceID(), ni->NameAtom(),
316 0 : ni->GetPrefixAtom(), value, true);
317 0 : if (NS_FAILED(rv)) {
318 0 : DropAttribute(ni->NamespaceID(), ni->NameAtom());
319 0 : aError.Throw(rv);
320 0 : return nullptr;
321 : }
322 :
323 0 : return oldAttr.forget();
324 : }
325 :
326 : already_AddRefed<Attr>
327 0 : nsDOMAttributeMap::RemoveNamedItem(NodeInfo* aNodeInfo, ErrorResult& aError)
328 : {
329 0 : RefPtr<Attr> attribute = GetAttribute(aNodeInfo);
330 : // This removes the attribute node from the attribute map.
331 0 : aError = mContent->UnsetAttr(aNodeInfo->NamespaceID(), aNodeInfo->NameAtom(), true);
332 0 : return attribute.forget();
333 : }
334 :
335 : NS_IMETHODIMP
336 0 : nsDOMAttributeMap::RemoveNamedItem(const nsAString& aName,
337 : nsIDOMAttr** aReturn)
338 : {
339 0 : NS_ENSURE_ARG_POINTER(aReturn);
340 :
341 0 : ErrorResult rv;
342 0 : *aReturn = RemoveNamedItem(aName, rv).take();
343 0 : return rv.StealNSResult();
344 : }
345 :
346 : already_AddRefed<Attr>
347 0 : nsDOMAttributeMap::RemoveNamedItem(const nsAString& aName, ErrorResult& aError)
348 : {
349 0 : if (!mContent) {
350 0 : aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
351 0 : return nullptr;
352 : }
353 :
354 0 : RefPtr<mozilla::dom::NodeInfo> ni = mContent->GetExistingAttrNameFromQName(aName);
355 0 : if (!ni) {
356 0 : aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
357 0 : return nullptr;
358 : }
359 :
360 0 : return RemoveNamedItem(ni, aError);
361 : }
362 :
363 :
364 : Attr*
365 0 : nsDOMAttributeMap::IndexedGetter(uint32_t aIndex, bool& aFound)
366 : {
367 0 : aFound = false;
368 0 : NS_ENSURE_TRUE(mContent, nullptr);
369 :
370 0 : const nsAttrName* name = mContent->GetAttrNameAt(aIndex);
371 0 : NS_ENSURE_TRUE(name, nullptr);
372 :
373 0 : aFound = true;
374 : // Don't use the nodeinfo even if one exists since it can have the wrong
375 : // owner document.
376 0 : RefPtr<mozilla::dom::NodeInfo> ni = mContent->NodeInfo()->NodeInfoManager()->
377 0 : GetNodeInfo(name->LocalName(), name->GetPrefix(), name->NamespaceID(),
378 0 : nsIDOMNode::ATTRIBUTE_NODE);
379 0 : return GetAttribute(ni);
380 : }
381 :
382 : Attr*
383 0 : nsDOMAttributeMap::Item(uint32_t aIndex)
384 : {
385 : bool dummy;
386 0 : return IndexedGetter(aIndex, dummy);
387 : }
388 :
389 : NS_IMETHODIMP
390 0 : nsDOMAttributeMap::Item(uint32_t aIndex, nsIDOMAttr** aReturn)
391 : {
392 0 : NS_IF_ADDREF(*aReturn = Item(aIndex));
393 0 : return NS_OK;
394 : }
395 :
396 : uint32_t
397 0 : nsDOMAttributeMap::Length() const
398 : {
399 0 : NS_ENSURE_TRUE(mContent, 0);
400 :
401 0 : return mContent->GetAttrCount();
402 : }
403 :
404 : nsresult
405 0 : nsDOMAttributeMap::GetLength(uint32_t *aLength)
406 : {
407 0 : NS_ENSURE_ARG_POINTER(aLength);
408 0 : *aLength = Length();
409 0 : return NS_OK;
410 : }
411 :
412 : NS_IMETHODIMP
413 0 : nsDOMAttributeMap::GetNamedItemNS(const nsAString& aNamespaceURI,
414 : const nsAString& aLocalName,
415 : nsIDOMAttr** aReturn)
416 : {
417 0 : NS_IF_ADDREF(*aReturn = GetNamedItemNS(aNamespaceURI, aLocalName));
418 0 : return NS_OK;
419 : }
420 :
421 : Attr*
422 0 : nsDOMAttributeMap::GetNamedItemNS(const nsAString& aNamespaceURI,
423 : const nsAString& aLocalName)
424 : {
425 0 : RefPtr<mozilla::dom::NodeInfo> ni = GetAttrNodeInfo(aNamespaceURI, aLocalName);
426 0 : if (!ni) {
427 0 : return nullptr;
428 : }
429 :
430 0 : return GetAttribute(ni);
431 : }
432 :
433 : already_AddRefed<mozilla::dom::NodeInfo>
434 0 : nsDOMAttributeMap::GetAttrNodeInfo(const nsAString& aNamespaceURI,
435 : const nsAString& aLocalName)
436 : {
437 0 : if (!mContent) {
438 0 : return nullptr;
439 : }
440 :
441 0 : int32_t nameSpaceID = kNameSpaceID_None;
442 :
443 0 : if (!aNamespaceURI.IsEmpty()) {
444 : nameSpaceID =
445 0 : nsContentUtils::NameSpaceManager()->GetNameSpaceID(aNamespaceURI,
446 0 : nsContentUtils::IsChromeDoc(mContent->OwnerDoc()));
447 :
448 0 : if (nameSpaceID == kNameSpaceID_Unknown) {
449 0 : return nullptr;
450 : }
451 : }
452 :
453 0 : uint32_t i, count = mContent->GetAttrCount();
454 0 : for (i = 0; i < count; ++i) {
455 0 : const nsAttrName* name = mContent->GetAttrNameAt(i);
456 0 : int32_t attrNS = name->NamespaceID();
457 0 : nsIAtom* nameAtom = name->LocalName();
458 :
459 : // we're purposefully ignoring the prefix.
460 0 : if (nameSpaceID == attrNS &&
461 0 : nameAtom->Equals(aLocalName)) {
462 0 : RefPtr<mozilla::dom::NodeInfo> ni;
463 0 : ni = mContent->NodeInfo()->NodeInfoManager()->
464 0 : GetNodeInfo(nameAtom, name->GetPrefix(), nameSpaceID,
465 0 : nsIDOMNode::ATTRIBUTE_NODE);
466 :
467 0 : return ni.forget();
468 : }
469 : }
470 :
471 0 : return nullptr;
472 : }
473 :
474 : NS_IMETHODIMP
475 0 : nsDOMAttributeMap::RemoveNamedItemNS(const nsAString& aNamespaceURI,
476 : const nsAString& aLocalName,
477 : nsIDOMAttr** aReturn)
478 : {
479 0 : NS_ENSURE_ARG_POINTER(aReturn);
480 0 : ErrorResult rv;
481 0 : *aReturn = RemoveNamedItemNS(aNamespaceURI, aLocalName, rv).take();
482 0 : return rv.StealNSResult();
483 : }
484 :
485 : already_AddRefed<Attr>
486 0 : nsDOMAttributeMap::RemoveNamedItemNS(const nsAString& aNamespaceURI,
487 : const nsAString& aLocalName,
488 : ErrorResult& aError)
489 : {
490 0 : RefPtr<mozilla::dom::NodeInfo> ni = GetAttrNodeInfo(aNamespaceURI, aLocalName);
491 0 : if (!ni) {
492 0 : aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
493 0 : return nullptr;
494 : }
495 :
496 0 : return RemoveNamedItem(ni, aError);
497 : }
498 :
499 : uint32_t
500 0 : nsDOMAttributeMap::Count() const
501 : {
502 0 : return mAttributeCache.Count();
503 : }
504 :
505 : size_t
506 0 : nsDOMAttributeMap::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
507 : {
508 0 : size_t n = aMallocSizeOf(this);
509 :
510 0 : n += mAttributeCache.ShallowSizeOfExcludingThis(aMallocSizeOf);
511 0 : for (auto iter = mAttributeCache.ConstIter(); !iter.Done(); iter.Next()) {
512 0 : n += aMallocSizeOf(iter.Data().get());
513 : }
514 :
515 : // NB: mContent is non-owning and thus not counted.
516 0 : return n;
517 : }
518 :
519 : /* virtual */ JSObject*
520 0 : nsDOMAttributeMap::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
521 : {
522 0 : return NamedNodeMapBinding::Wrap(aCx, this, aGivenProto);
523 : }
|