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 "mozilla/dom/Document.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
) : mContent(aContent
) {
34 // We don't add a reference to our content. If it goes away,
35 // we'll be told to drop our reference
38 nsDOMAttributeMap::~nsDOMAttributeMap() { DropReference(); }
40 void nsDOMAttributeMap::DropReference() {
41 for (auto iter
= mAttributeCache
.Iter(); !iter
.Done(); iter
.Next()) {
42 iter
.Data()->SetMap(nullptr);
48 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsDOMAttributeMap
)
50 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMAttributeMap
)
52 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
53 NS_IMPL_CYCLE_COLLECTION_UNLINK(mContent
)
54 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
56 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMAttributeMap
)
57 for (const auto& entry
: tmp
->mAttributeCache
) {
58 cb
.NoteXPCOMChild(static_cast<nsINode
*>(entry
.GetWeak()));
60 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent
)
61 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
63 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsDOMAttributeMap
)
64 if (tmp
->HasKnownLiveWrapper()) {
66 // The map owns the element so we can mark it when the
67 // map itself is certainly alive.
68 mozilla::dom::FragmentOrElement::MarkNodeChildren(tmp
->mContent
);
73 mozilla::dom::FragmentOrElement::CanSkip(tmp
->mContent
, true)) {
76 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
78 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsDOMAttributeMap
)
79 return tmp
->HasKnownLiveWrapperAndDoesNotNeedTracing(tmp
);
80 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
82 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsDOMAttributeMap
)
83 return tmp
->HasKnownLiveWrapper();
84 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
86 // QueryInterface implementation for nsDOMAttributeMap
88 NS_INTERFACE_MAP_BEGIN(nsDOMAttributeMap
)
89 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
90 NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsDOMAttributeMap
)
91 NS_INTERFACE_MAP_ENTRY(nsISupports
)
94 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMAttributeMap
)
95 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMAttributeMap
)
97 nsresult
nsDOMAttributeMap::SetOwnerDocument(Document
* aDocument
) {
98 for (const auto& entry
: mAttributeCache
.Values()) {
99 nsresult rv
= entry
->SetOwnerDocument(aDocument
);
100 NS_ENSURE_SUCCESS(rv
, NS_ERROR_FAILURE
);
105 void nsDOMAttributeMap::DropAttribute(int32_t aNamespaceID
,
106 nsAtom
* aLocalName
) {
107 nsAttrKey
attr(aNamespaceID
, aLocalName
);
108 if (auto entry
= mAttributeCache
.Lookup(attr
)) {
109 entry
.Data()->SetMap(nullptr); // break link to map
114 Attr
* nsDOMAttributeMap::GetAttribute(mozilla::dom::NodeInfo
* aNodeInfo
) {
115 NS_ASSERTION(aNodeInfo
, "GetAttribute() called with aNodeInfo == nullptr!");
117 nsAttrKey
attr(aNodeInfo
->NamespaceID(), aNodeInfo
->NameAtom());
119 return mAttributeCache
.LookupOrInsertWith(attr
, [&] {
120 // Newly inserted entry!
121 RefPtr
<mozilla::dom::NodeInfo
> ni
= aNodeInfo
;
122 auto* nim
= ni
->NodeInfoManager();
123 return new (nim
) Attr(this, ni
.forget(), u
""_ns
);
127 Attr
* nsDOMAttributeMap::NamedGetter(const nsAString
& aAttrName
, bool& aFound
) {
129 NS_ENSURE_TRUE(mContent
, nullptr);
131 RefPtr
<mozilla::dom::NodeInfo
> ni
=
132 mContent
->GetExistingAttrNameFromQName(aAttrName
);
138 return GetAttribute(ni
);
141 void nsDOMAttributeMap::GetSupportedNames(nsTArray
<nsString
>& aNames
) {
142 // For HTML elements in HTML documents, only include names that are still the
143 // same after ASCII-lowercasing, since our named getter will end up
144 // ASCII-lowercasing the given string.
145 bool lowercaseNamesOnly
=
146 mContent
->IsHTMLElement() && mContent
->IsInHTMLDocument();
148 const uint32_t count
= mContent
->GetAttrCount();
149 bool seenNonAtomName
= false;
150 for (uint32_t i
= 0; i
< count
; i
++) {
151 const nsAttrName
* name
= mContent
->GetAttrNameAt(i
);
152 seenNonAtomName
= seenNonAtomName
|| !name
->IsAtom();
153 nsString qualifiedName
;
154 name
->GetQualifiedName(qualifiedName
);
156 if (lowercaseNamesOnly
&&
157 nsContentUtils::StringContainsASCIIUpper(qualifiedName
)) {
161 // Omit duplicates. We only need to do this check if we've seen a non-atom
162 // name, because that's the only way we can have two identical qualified
164 if (seenNonAtomName
&& aNames
.Contains(qualifiedName
)) {
168 aNames
.AppendElement(qualifiedName
);
172 Attr
* nsDOMAttributeMap::GetNamedItem(const nsAString
& aAttrName
) {
174 return NamedGetter(aAttrName
, dummy
);
177 already_AddRefed
<Attr
> nsDOMAttributeMap::SetNamedItemNS(Attr
& aAttr
,
178 ErrorResult
& aError
) {
179 NS_ENSURE_TRUE(mContent
, nullptr);
181 // XXX should check same-origin between mContent and aAttr however
182 // nsContentUtils::CheckSameOrigin can't deal with attributenodes yet
184 // Check that attribute is not owned by somebody else
185 nsDOMAttributeMap
* owner
= aAttr
.GetMap();
188 aError
.Throw(NS_ERROR_DOM_INUSE_ATTRIBUTE_ERR
);
192 // setting a preexisting attribute is a no-op, just return the same
194 RefPtr
<Attr
> attribute
= &aAttr
;
195 return attribute
.forget();
199 if (mContent
->OwnerDoc() != aAttr
.OwnerDoc()) {
200 DebugOnly
<void*> adoptedNode
=
201 mContent
->OwnerDoc()->AdoptNode(aAttr
, aError
);
202 if (aError
.Failed()) {
206 NS_ASSERTION(adoptedNode
== &aAttr
, "Uh, adopt node changed nodes?");
209 // Get nodeinfo and preexisting attribute (if it exists)
210 RefPtr
<NodeInfo
> oldNi
;
212 uint32_t i
, count
= mContent
->GetAttrCount();
213 for (i
= 0; i
< count
; ++i
) {
214 const nsAttrName
* name
= mContent
->GetAttrNameAt(i
);
215 int32_t attrNS
= name
->NamespaceID();
216 nsAtom
* nameAtom
= name
->LocalName();
218 // we're purposefully ignoring the prefix.
219 if (aAttr
.NodeInfo()->Equals(nameAtom
, attrNS
)) {
220 oldNi
= mContent
->NodeInfo()->NodeInfoManager()->GetNodeInfo(
221 nameAtom
, name
->GetPrefix(), aAttr
.NodeInfo()->NamespaceID(),
222 nsINode::ATTRIBUTE_NODE
);
227 RefPtr
<Attr
> oldAttr
;
230 oldAttr
= GetAttribute(oldNi
);
232 if (oldAttr
== &aAttr
) {
233 return oldAttr
.forget();
237 // Just remove it from our hashtable. This has no side-effects, so we
238 // don't have to recheck anything after we do it. Then we'll add our new
239 // Attr to the hashtable and do the actual attr set on the element. This
240 // will make the whole thing look like a single attribute mutation (with
241 // the new attr node in place) as opposed to a removal and addition.
242 DropAttribute(oldNi
->NamespaceID(), oldNi
->NameAtom());
247 aAttr
.GetValue(value
);
249 RefPtr
<NodeInfo
> ni
= aAttr
.NodeInfo();
251 // Add the new attribute to the attribute map before updating
252 // its value in the element. @see bug 364413.
253 nsAttrKey
attrkey(ni
->NamespaceID(), ni
->NameAtom());
254 mAttributeCache
.InsertOrUpdate(attrkey
, RefPtr
{&aAttr
});
257 rv
= mContent
->SetAttr(ni
->NamespaceID(), ni
->NameAtom(), ni
->GetPrefixAtom(),
260 DropAttribute(ni
->NamespaceID(), ni
->NameAtom());
265 return oldAttr
.forget();
268 already_AddRefed
<Attr
> nsDOMAttributeMap::RemoveNamedItem(NodeInfo
* aNodeInfo
,
269 ErrorResult
& aError
) {
270 RefPtr
<Attr
> attribute
= GetAttribute(aNodeInfo
);
271 // This removes the attribute node from the attribute map.
272 aError
= mContent
->UnsetAttr(aNodeInfo
->NamespaceID(), aNodeInfo
->NameAtom(),
274 return attribute
.forget();
277 already_AddRefed
<Attr
> nsDOMAttributeMap::RemoveNamedItem(
278 const nsAString
& aName
, ErrorResult
& aError
) {
280 aError
.Throw(NS_ERROR_DOM_NOT_FOUND_ERR
);
284 RefPtr
<mozilla::dom::NodeInfo
> ni
=
285 mContent
->GetExistingAttrNameFromQName(aName
);
287 aError
.Throw(NS_ERROR_DOM_NOT_FOUND_ERR
);
291 return RemoveNamedItem(ni
, aError
);
294 Attr
* nsDOMAttributeMap::IndexedGetter(uint32_t aIndex
, bool& aFound
) {
296 NS_ENSURE_TRUE(mContent
, nullptr);
298 const nsAttrName
* name
= mContent
->GetAttrNameAt(aIndex
);
299 NS_ENSURE_TRUE(name
, nullptr);
302 // Don't use the nodeinfo even if one exists since it can have the wrong
304 RefPtr
<mozilla::dom::NodeInfo
> ni
=
305 mContent
->NodeInfo()->NodeInfoManager()->GetNodeInfo(
306 name
->LocalName(), name
->GetPrefix(), name
->NamespaceID(),
307 nsINode::ATTRIBUTE_NODE
);
308 return GetAttribute(ni
);
311 Attr
* nsDOMAttributeMap::Item(uint32_t aIndex
) {
313 return IndexedGetter(aIndex
, dummy
);
316 uint32_t nsDOMAttributeMap::Length() const {
317 NS_ENSURE_TRUE(mContent
, 0);
319 return mContent
->GetAttrCount();
322 Attr
* nsDOMAttributeMap::GetNamedItemNS(const nsAString
& aNamespaceURI
,
323 const nsAString
& aLocalName
) {
324 RefPtr
<mozilla::dom::NodeInfo
> ni
=
325 GetAttrNodeInfo(aNamespaceURI
, aLocalName
);
330 return GetAttribute(ni
);
333 already_AddRefed
<mozilla::dom::NodeInfo
> nsDOMAttributeMap::GetAttrNodeInfo(
334 const nsAString
& aNamespaceURI
, const nsAString
& aLocalName
) {
339 int32_t nameSpaceID
= kNameSpaceID_None
;
341 if (!aNamespaceURI
.IsEmpty()) {
342 nameSpaceID
= nsNameSpaceManager::GetInstance()->GetNameSpaceID(
343 aNamespaceURI
, nsContentUtils::IsChromeDoc(mContent
->OwnerDoc()));
345 if (nameSpaceID
== kNameSpaceID_Unknown
) {
350 uint32_t i
, count
= mContent
->GetAttrCount();
351 for (i
= 0; i
< count
; ++i
) {
352 const nsAttrName
* name
= mContent
->GetAttrNameAt(i
);
353 int32_t attrNS
= name
->NamespaceID();
354 nsAtom
* nameAtom
= name
->LocalName();
356 // we're purposefully ignoring the prefix.
357 if (nameSpaceID
== attrNS
&& nameAtom
->Equals(aLocalName
)) {
358 RefPtr
<mozilla::dom::NodeInfo
> ni
;
359 ni
= mContent
->NodeInfo()->NodeInfoManager()->GetNodeInfo(
360 nameAtom
, name
->GetPrefix(), nameSpaceID
, nsINode::ATTRIBUTE_NODE
);
369 already_AddRefed
<Attr
> nsDOMAttributeMap::RemoveNamedItemNS(
370 const nsAString
& aNamespaceURI
, const nsAString
& aLocalName
,
371 ErrorResult
& aError
) {
372 RefPtr
<mozilla::dom::NodeInfo
> ni
=
373 GetAttrNodeInfo(aNamespaceURI
, aLocalName
);
375 aError
.Throw(NS_ERROR_DOM_NOT_FOUND_ERR
);
379 return RemoveNamedItem(ni
, aError
);
382 uint32_t nsDOMAttributeMap::Count() const { return mAttributeCache
.Count(); }
384 size_t nsDOMAttributeMap::SizeOfIncludingThis(
385 MallocSizeOf aMallocSizeOf
) const {
386 size_t n
= aMallocSizeOf(this);
388 n
+= mAttributeCache
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
389 for (const auto& entry
: mAttributeCache
) {
390 n
+= aMallocSizeOf(entry
.GetWeak());
393 // NB: mContent is non-owning and thus not counted.
398 JSObject
* nsDOMAttributeMap::WrapObject(JSContext
* aCx
,
399 JS::Handle
<JSObject
*> aGivenProto
) {
400 return NamedNodeMap_Binding::Wrap(aCx
, this, aGivenProto
);
403 DocGroup
* nsDOMAttributeMap::GetDocGroup() const {
404 return mContent
? mContent
->OwnerDoc()->GetDocGroup() : nullptr;