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/. */
8 * Implementation of the |attributes| property of DOM Core's Element object.
11 #include "nsDOMAttributeMap.h"
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"
21 #include "nsIContentInlines.h"
22 #include "nsIDocument.h"
23 #include "nsNameSpaceManager.h"
24 #include "nsNodeInfoManager.h"
25 #include "nsUnicharUtils.h"
26 #include "nsWrapperCacheInlines.h"
28 using namespace mozilla
;
29 using namespace mozilla::dom
;
31 //----------------------------------------------------------------------
33 nsDOMAttributeMap::nsDOMAttributeMap(Element
* aContent
)
36 // We don't add a reference to our content. If it goes away,
37 // we'll be told to drop our reference
41 * Clear map pointer for attributes.
44 RemoveMapRef(nsAttrHashKey::KeyType aKey
, nsRefPtr
<Attr
>& aData
,
47 aData
->SetMap(nullptr);
49 return PL_DHASH_REMOVE
;
52 nsDOMAttributeMap::~nsDOMAttributeMap()
54 if (mAttributeCache
) {
55 mAttributeCache
->Enumerate(RemoveMapRef
, nullptr);
60 nsDOMAttributeMap::DropReference()
62 if (mAttributeCache
) {
63 mAttributeCache
->Enumerate(RemoveMapRef
, nullptr);
68 NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMAttributeMap
)
70 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMAttributeMap
)
72 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
73 NS_IMPL_CYCLE_COLLECTION_UNLINK(mContent
)
74 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
78 TraverseMapEntry(nsAttrHashKey::KeyType aKey
, nsRefPtr
<Attr
>& aData
,
81 nsCycleCollectionTraversalCallback
*cb
=
82 static_cast<nsCycleCollectionTraversalCallback
*>(aUserArg
);
84 cb
->NoteXPCOMChild(static_cast<nsINode
*>(aData
.get()));
89 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMAttributeMap
)
90 if (tmp
->mAttributeCache
) {
91 tmp
->mAttributeCache
->Enumerate(TraverseMapEntry
, &cb
);
93 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
94 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent
)
95 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
97 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsDOMAttributeMap
)
99 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsDOMAttributeMap
)
100 if (tmp
->IsBlack()) {
102 // The map owns the element so we can mark it when the
103 // map itself is certainly alive.
104 mozilla::dom::FragmentOrElement::MarkNodeChildren(tmp
->mContent
);
109 mozilla::dom::FragmentOrElement::CanSkip(tmp
->mContent
, true)) {
112 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
114 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsDOMAttributeMap
)
115 return tmp
->IsBlackAndDoesNotNeedTracing(tmp
);
116 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
118 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsDOMAttributeMap
)
119 return tmp
->IsBlack();
120 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
122 // QueryInterface implementation for nsDOMAttributeMap
123 NS_INTERFACE_TABLE_HEAD(nsDOMAttributeMap
)
124 NS_INTERFACE_TABLE(nsDOMAttributeMap
, nsIDOMMozNamedAttrMap
)
125 NS_INTERFACE_TABLE_TO_MAP_SEGUE
126 NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
127 NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsDOMAttributeMap
)
130 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMAttributeMap
)
131 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMAttributeMap
)
134 SetOwnerDocumentFunc(nsAttrHashKey::KeyType aKey
,
135 nsRefPtr
<Attr
>& aData
,
138 nsresult rv
= aData
->SetOwnerDocument(static_cast<nsIDocument
*>(aUserArg
));
140 return NS_FAILED(rv
) ? PL_DHASH_STOP
: PL_DHASH_NEXT
;
144 nsDOMAttributeMap::SetOwnerDocument(nsIDocument
* aDocument
)
146 if (mAttributeCache
) {
147 uint32_t n
= mAttributeCache
->Enumerate(SetOwnerDocumentFunc
, aDocument
);
148 NS_ENSURE_TRUE(n
== mAttributeCache
->Count(), NS_ERROR_FAILURE
);
154 nsDOMAttributeMap::DropAttribute(int32_t aNamespaceID
, nsIAtom
* aLocalName
)
156 nsAttrKey
attr(aNamespaceID
, aLocalName
);
157 if (mAttributeCache
) {
158 Attr
*node
= mAttributeCache
->GetWeak(attr
);
161 node
->SetMap(nullptr);
164 mAttributeCache
->Remove(attr
);
169 already_AddRefed
<Attr
>
170 nsDOMAttributeMap::RemoveAttribute(mozilla::dom::NodeInfo
* aNodeInfo
)
172 NS_ASSERTION(aNodeInfo
, "RemoveAttribute() called with aNodeInfo == nullptr!");
174 nsAttrKey
attr(aNodeInfo
->NamespaceID(), aNodeInfo
->NameAtom());
177 if (mAttributeCache
&& mAttributeCache
->Get(attr
, getter_AddRefs(node
))) {
179 node
->SetMap(nullptr);
182 mAttributeCache
->Remove(attr
);
185 // As we are removing the attribute we need to set the current value in
186 // the attribute node.
187 mContent
->GetAttr(aNodeInfo
->NamespaceID(), aNodeInfo
->NameAtom(), value
);
188 nsRefPtr
<mozilla::dom::NodeInfo
> ni
= aNodeInfo
;
189 node
= new Attr(nullptr, ni
.forget(), value
, true);
192 return node
.forget();
196 nsDOMAttributeMap::GetAttribute(mozilla::dom::NodeInfo
* aNodeInfo
, bool aNsAware
)
198 NS_ASSERTION(aNodeInfo
, "GetAttribute() called with aNodeInfo == nullptr!");
200 nsAttrKey
attr(aNodeInfo
->NamespaceID(), aNodeInfo
->NameAtom());
202 EnsureAttributeCache();
203 Attr
* node
= mAttributeCache
->GetWeak(attr
);
205 nsRefPtr
<mozilla::dom::NodeInfo
> ni
= aNodeInfo
;
206 nsRefPtr
<Attr
> newAttr
=
207 new Attr(this, ni
.forget(), EmptyString(), aNsAware
);
208 mAttributeCache
->Put(attr
, newAttr
);
216 nsDOMAttributeMap::NamedGetter(const nsAString
& aAttrName
, bool& aFound
)
219 NS_ENSURE_TRUE(mContent
, nullptr);
221 nsRefPtr
<mozilla::dom::NodeInfo
> ni
= mContent
->GetExistingAttrNameFromQName(aAttrName
);
227 return GetAttribute(ni
, false);
231 nsDOMAttributeMap::NameIsEnumerable(const nsAString
& aName
)
237 nsDOMAttributeMap::GetNamedItem(const nsAString
& aAttrName
)
240 return NamedGetter(aAttrName
, dummy
);
244 nsDOMAttributeMap::GetNamedItem(const nsAString
& aAttrName
,
245 nsIDOMAttr
** aAttribute
)
247 NS_ENSURE_ARG_POINTER(aAttribute
);
249 NS_IF_ADDREF(*aAttribute
= GetNamedItem(aAttrName
));
255 nsDOMAttributeMap::EnsureAttributeCache()
257 if (!mAttributeCache
) {
258 mAttributeCache
= MakeUnique
<AttrCache
>();
263 nsDOMAttributeMap::SetNamedItem(nsIDOMAttr
* aAttr
, nsIDOMAttr
** aReturn
)
265 Attr
* attribute
= static_cast<Attr
*>(aAttr
);
266 NS_ENSURE_ARG(attribute
);
269 *aReturn
= SetNamedItem(*attribute
, rv
).take();
270 return rv
.ErrorCode();
274 nsDOMAttributeMap::SetNamedItemNS(nsIDOMAttr
* aAttr
, nsIDOMAttr
** aReturn
)
276 Attr
* attribute
= static_cast<Attr
*>(aAttr
);
277 NS_ENSURE_ARG(attribute
);
280 *aReturn
= SetNamedItemNS(*attribute
, rv
).take();
281 return rv
.ErrorCode();
284 already_AddRefed
<Attr
>
285 nsDOMAttributeMap::SetNamedItemInternal(Attr
& aAttr
,
289 NS_ENSURE_TRUE(mContent
, nullptr);
291 // XXX should check same-origin between mContent and aAttr however
292 // nsContentUtils::CheckSameOrigin can't deal with attributenodes yet
294 // Check that attribute is not owned by somebody else
295 nsDOMAttributeMap
* owner
= aAttr
.GetMap();
298 aError
.Throw(NS_ERROR_DOM_INUSE_ATTRIBUTE_ERR
);
302 // setting a preexisting attribute is a no-op, just return the same
304 nsRefPtr
<Attr
> attribute
= &aAttr
;
305 return attribute
.forget();
309 if (mContent
->OwnerDoc() != aAttr
.OwnerDoc()) {
310 nsCOMPtr
<nsINode
> adoptedNode
=
311 mContent
->OwnerDoc()->AdoptNode(aAttr
, aError
);
312 if (aError
.Failed()) {
316 NS_ASSERTION(adoptedNode
== &aAttr
, "Uh, adopt node changed nodes?");
319 // Get nodeinfo and preexisting attribute (if it exists)
321 nsRefPtr
<mozilla::dom::NodeInfo
> ni
;
326 // Return existing attribute, if present
327 ni
= aAttr
.NodeInfo();
329 if (mContent
->HasAttr(ni
->NamespaceID(), ni
->NameAtom())) {
330 attr
= RemoveAttribute(ni
);
332 } else { // SetNamedItem()
335 // get node-info of old attribute
336 ni
= mContent
->GetExistingAttrNameFromQName(name
);
338 attr
= RemoveAttribute(ni
);
341 if (mContent
->IsInHTMLDocument() &&
342 mContent
->IsHTML()) {
343 nsContentUtils::ASCIIToLower(name
);
346 rv
= mContent
->NodeInfo()->NodeInfoManager()->
347 GetNodeInfo(name
, nullptr, kNameSpaceID_None
,
348 nsIDOMNode::ATTRIBUTE_NODE
, getter_AddRefs(ni
));
353 // value is already empty
358 aAttr
.GetValue(value
);
360 // Add the new attribute to the attribute map before updating
361 // its value in the element. @see bug 364413.
362 nsAttrKey
attrkey(ni
->NamespaceID(), ni
->NameAtom());
363 EnsureAttributeCache();
364 mAttributeCache
->Put(attrkey
, &aAttr
);
367 rv
= mContent
->SetAttr(ni
->NamespaceID(), ni
->NameAtom(),
368 ni
->GetPrefixAtom(), value
, true);
371 DropAttribute(ni
->NamespaceID(), ni
->NameAtom());
374 return attr
.forget();
378 nsDOMAttributeMap::RemoveNamedItem(const nsAString
& aName
,
379 nsIDOMAttr
** aReturn
)
381 NS_ENSURE_ARG_POINTER(aReturn
);
384 *aReturn
= RemoveNamedItem(aName
, rv
).take();
385 return rv
.ErrorCode();
388 already_AddRefed
<Attr
>
389 nsDOMAttributeMap::RemoveNamedItem(const nsAString
& aName
, ErrorResult
& aError
)
392 aError
.Throw(NS_ERROR_DOM_NOT_FOUND_ERR
);
396 nsRefPtr
<mozilla::dom::NodeInfo
> ni
= mContent
->GetExistingAttrNameFromQName(aName
);
398 aError
.Throw(NS_ERROR_DOM_NOT_FOUND_ERR
);
402 nsRefPtr
<Attr
> attribute
= GetAttribute(ni
, true);
404 // This removes the attribute node from the attribute map.
405 aError
= mContent
->UnsetAttr(ni
->NamespaceID(), ni
->NameAtom(), true);
406 return attribute
.forget();
411 nsDOMAttributeMap::IndexedGetter(uint32_t aIndex
, bool& aFound
)
414 NS_ENSURE_TRUE(mContent
, nullptr);
416 const nsAttrName
* name
= mContent
->GetAttrNameAt(aIndex
);
417 NS_ENSURE_TRUE(name
, nullptr);
420 // Don't use the nodeinfo even if one exists since it can have the wrong
422 nsRefPtr
<mozilla::dom::NodeInfo
> ni
= mContent
->NodeInfo()->NodeInfoManager()->
423 GetNodeInfo(name
->LocalName(), name
->GetPrefix(), name
->NamespaceID(),
424 nsIDOMNode::ATTRIBUTE_NODE
);
425 return GetAttribute(ni
, true);
429 nsDOMAttributeMap::Item(uint32_t aIndex
)
432 return IndexedGetter(aIndex
, dummy
);
436 nsDOMAttributeMap::Item(uint32_t aIndex
, nsIDOMAttr
** aReturn
)
438 NS_IF_ADDREF(*aReturn
= Item(aIndex
));
443 nsDOMAttributeMap::Length() const
445 NS_ENSURE_TRUE(mContent
, 0);
447 return mContent
->GetAttrCount();
451 nsDOMAttributeMap::GetLength(uint32_t *aLength
)
453 NS_ENSURE_ARG_POINTER(aLength
);
459 nsDOMAttributeMap::GetNamedItemNS(const nsAString
& aNamespaceURI
,
460 const nsAString
& aLocalName
,
461 nsIDOMAttr
** aReturn
)
463 NS_IF_ADDREF(*aReturn
= GetNamedItemNS(aNamespaceURI
, aLocalName
));
468 nsDOMAttributeMap::GetNamedItemNS(const nsAString
& aNamespaceURI
,
469 const nsAString
& aLocalName
)
471 nsRefPtr
<mozilla::dom::NodeInfo
> ni
= GetAttrNodeInfo(aNamespaceURI
, aLocalName
);
476 return GetAttribute(ni
, true);
479 already_AddRefed
<mozilla::dom::NodeInfo
>
480 nsDOMAttributeMap::GetAttrNodeInfo(const nsAString
& aNamespaceURI
,
481 const nsAString
& aLocalName
)
487 int32_t nameSpaceID
= kNameSpaceID_None
;
489 if (!aNamespaceURI
.IsEmpty()) {
491 nsContentUtils::NameSpaceManager()->GetNameSpaceID(aNamespaceURI
);
493 if (nameSpaceID
== kNameSpaceID_Unknown
) {
498 uint32_t i
, count
= mContent
->GetAttrCount();
499 for (i
= 0; i
< count
; ++i
) {
500 const nsAttrName
* name
= mContent
->GetAttrNameAt(i
);
501 int32_t attrNS
= name
->NamespaceID();
502 nsIAtom
* nameAtom
= name
->LocalName();
504 if (nameSpaceID
== attrNS
&&
505 nameAtom
->Equals(aLocalName
)) {
506 nsRefPtr
<mozilla::dom::NodeInfo
> ni
;
507 ni
= mContent
->NodeInfo()->NodeInfoManager()->
508 GetNodeInfo(nameAtom
, name
->GetPrefix(), nameSpaceID
,
509 nsIDOMNode::ATTRIBUTE_NODE
);
519 nsDOMAttributeMap::RemoveNamedItemNS(const nsAString
& aNamespaceURI
,
520 const nsAString
& aLocalName
,
521 nsIDOMAttr
** aReturn
)
523 NS_ENSURE_ARG_POINTER(aReturn
);
525 *aReturn
= RemoveNamedItemNS(aNamespaceURI
, aLocalName
, rv
).take();
526 return rv
.ErrorCode();
529 already_AddRefed
<Attr
>
530 nsDOMAttributeMap::RemoveNamedItemNS(const nsAString
& aNamespaceURI
,
531 const nsAString
& aLocalName
,
534 nsRefPtr
<mozilla::dom::NodeInfo
> ni
= GetAttrNodeInfo(aNamespaceURI
, aLocalName
);
536 aError
.Throw(NS_ERROR_DOM_NOT_FOUND_ERR
);
540 nsRefPtr
<Attr
> attr
= RemoveAttribute(ni
);
541 mozilla::dom::NodeInfo
* attrNi
= attr
->NodeInfo();
542 mContent
->UnsetAttr(attrNi
->NamespaceID(), attrNi
->NameAtom(), true);
544 return attr
.forget();
548 nsDOMAttributeMap::Count() const
550 return mAttributeCache
? mAttributeCache
->Count() : 0;
554 nsDOMAttributeMap::Enumerate(AttrCache::EnumReadFunction aFunc
,
555 void *aUserArg
) const
557 return mAttributeCache
? mAttributeCache
->EnumerateRead(aFunc
, aUserArg
) : 0;
561 AttrCacheSizeEnumerator(const nsAttrKey
& aKey
,
562 const nsRefPtr
<Attr
>& aValue
,
563 MallocSizeOf aMallocSizeOf
,
566 return aMallocSizeOf(aValue
.get());
570 nsDOMAttributeMap::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const
572 size_t n
= aMallocSizeOf(this);
574 ? mAttributeCache
->SizeOfExcludingThis(AttrCacheSizeEnumerator
,
578 // NB: mContent is non-owning and thus not counted.
582 /* virtual */ JSObject
*
583 nsDOMAttributeMap::WrapObject(JSContext
* aCx
)
585 return NamedNodeMapBinding::Wrap(aCx
, this);