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_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 (auto iter
= tmp
->mAttributeCache
.Iter(); !iter
.Done(); iter
.Next()) {
58 cb
.NoteXPCOMChild(static_cast<nsINode
*>(iter
.Data().get()));
60 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent
)
61 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
63 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsDOMAttributeMap
)
65 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsDOMAttributeMap
)
66 if (tmp
->HasKnownLiveWrapper()) {
68 // The map owns the element so we can mark it when the
69 // map itself is certainly alive.
70 mozilla::dom::FragmentOrElement::MarkNodeChildren(tmp
->mContent
);
75 mozilla::dom::FragmentOrElement::CanSkip(tmp
->mContent
, true)) {
78 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
80 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsDOMAttributeMap
)
81 return tmp
->HasKnownLiveWrapperAndDoesNotNeedTracing(tmp
);
82 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
84 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsDOMAttributeMap
)
85 return tmp
->HasKnownLiveWrapper();
86 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
88 // QueryInterface implementation for nsDOMAttributeMap
90 NS_INTERFACE_MAP_BEGIN(nsDOMAttributeMap
)
91 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
92 NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsDOMAttributeMap
)
93 NS_INTERFACE_MAP_ENTRY(nsISupports
)
96 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMAttributeMap
)
97 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMAttributeMap
)
99 nsresult
nsDOMAttributeMap::SetOwnerDocument(Document
* aDocument
) {
100 for (auto iter
= mAttributeCache
.Iter(); !iter
.Done(); iter
.Next()) {
101 nsresult rv
= iter
.Data()->SetOwnerDocument(aDocument
);
102 NS_ENSURE_SUCCESS(rv
, NS_ERROR_FAILURE
);
107 void nsDOMAttributeMap::DropAttribute(int32_t aNamespaceID
,
108 nsAtom
* aLocalName
) {
109 nsAttrKey
attr(aNamespaceID
, aLocalName
);
110 if (auto entry
= mAttributeCache
.Lookup(attr
)) {
111 entry
.Data()->SetMap(nullptr); // break link to map
116 Attr
* nsDOMAttributeMap::GetAttribute(mozilla::dom::NodeInfo
* aNodeInfo
) {
117 NS_ASSERTION(aNodeInfo
, "GetAttribute() called with aNodeInfo == nullptr!");
119 nsAttrKey
attr(aNodeInfo
->NamespaceID(), aNodeInfo
->NameAtom());
121 RefPtr
<Attr
>& entryValue
= mAttributeCache
.GetOrInsert(attr
);
122 Attr
* node
= entryValue
;
124 // Newly inserted entry!
125 RefPtr
<mozilla::dom::NodeInfo
> ni
= aNodeInfo
;
126 auto* nim
= ni
->NodeInfoManager();
127 entryValue
= new (nim
) Attr(this, ni
.forget(), u
""_ns
);
134 Attr
* nsDOMAttributeMap::NamedGetter(const nsAString
& aAttrName
, bool& aFound
) {
136 NS_ENSURE_TRUE(mContent
, nullptr);
138 RefPtr
<mozilla::dom::NodeInfo
> ni
=
139 mContent
->GetExistingAttrNameFromQName(aAttrName
);
145 return GetAttribute(ni
);
148 void nsDOMAttributeMap::GetSupportedNames(nsTArray
<nsString
>& aNames
) {
149 // For HTML elements in HTML documents, only include names that are still the
150 // same after ASCII-lowercasing, since our named getter will end up
151 // ASCII-lowercasing the given string.
152 bool lowercaseNamesOnly
=
153 mContent
->IsHTMLElement() && mContent
->IsInHTMLDocument();
155 const uint32_t count
= mContent
->GetAttrCount();
156 bool seenNonAtomName
= false;
157 for (uint32_t i
= 0; i
< count
; i
++) {
158 const nsAttrName
* name
= mContent
->GetAttrNameAt(i
);
159 seenNonAtomName
= seenNonAtomName
|| !name
->IsAtom();
160 nsString qualifiedName
;
161 name
->GetQualifiedName(qualifiedName
);
163 if (lowercaseNamesOnly
&&
164 nsContentUtils::StringContainsASCIIUpper(qualifiedName
)) {
168 // Omit duplicates. We only need to do this check if we've seen a non-atom
169 // name, because that's the only way we can have two identical qualified
171 if (seenNonAtomName
&& aNames
.Contains(qualifiedName
)) {
175 aNames
.AppendElement(qualifiedName
);
179 Attr
* nsDOMAttributeMap::GetNamedItem(const nsAString
& aAttrName
) {
181 return NamedGetter(aAttrName
, dummy
);
184 already_AddRefed
<Attr
> nsDOMAttributeMap::SetNamedItemNS(Attr
& aAttr
,
185 ErrorResult
& aError
) {
186 NS_ENSURE_TRUE(mContent
, nullptr);
188 // XXX should check same-origin between mContent and aAttr however
189 // nsContentUtils::CheckSameOrigin can't deal with attributenodes yet
191 // Check that attribute is not owned by somebody else
192 nsDOMAttributeMap
* owner
= aAttr
.GetMap();
195 aError
.Throw(NS_ERROR_DOM_INUSE_ATTRIBUTE_ERR
);
199 // setting a preexisting attribute is a no-op, just return the same
201 RefPtr
<Attr
> attribute
= &aAttr
;
202 return attribute
.forget();
206 if (mContent
->OwnerDoc() != aAttr
.OwnerDoc()) {
207 DebugOnly
<void*> adoptedNode
=
208 mContent
->OwnerDoc()->AdoptNode(aAttr
, aError
);
209 if (aError
.Failed()) {
213 NS_ASSERTION(adoptedNode
== &aAttr
, "Uh, adopt node changed nodes?");
216 // Get nodeinfo and preexisting attribute (if it exists)
217 RefPtr
<NodeInfo
> oldNi
;
219 uint32_t i
, count
= mContent
->GetAttrCount();
220 for (i
= 0; i
< count
; ++i
) {
221 const nsAttrName
* name
= mContent
->GetAttrNameAt(i
);
222 int32_t attrNS
= name
->NamespaceID();
223 nsAtom
* nameAtom
= name
->LocalName();
225 // we're purposefully ignoring the prefix.
226 if (aAttr
.NodeInfo()->Equals(nameAtom
, attrNS
)) {
227 oldNi
= mContent
->NodeInfo()->NodeInfoManager()->GetNodeInfo(
228 nameAtom
, name
->GetPrefix(), aAttr
.NodeInfo()->NamespaceID(),
229 nsINode::ATTRIBUTE_NODE
);
234 RefPtr
<Attr
> oldAttr
;
237 oldAttr
= GetAttribute(oldNi
);
239 if (oldAttr
== &aAttr
) {
240 return oldAttr
.forget();
244 // Just remove it from our hashtable. This has no side-effects, so we
245 // don't have to recheck anything after we do it. Then we'll add our new
246 // Attr to the hashtable and do the actual attr set on the element. This
247 // will make the whole thing look like a single attribute mutation (with
248 // the new attr node in place) as opposed to a removal and addition.
249 DropAttribute(oldNi
->NamespaceID(), oldNi
->NameAtom());
254 aAttr
.GetValue(value
);
256 RefPtr
<NodeInfo
> ni
= aAttr
.NodeInfo();
258 // Add the new attribute to the attribute map before updating
259 // its value in the element. @see bug 364413.
260 nsAttrKey
attrkey(ni
->NamespaceID(), ni
->NameAtom());
261 mAttributeCache
.Put(attrkey
, RefPtr
{&aAttr
});
264 rv
= mContent
->SetAttr(ni
->NamespaceID(), ni
->NameAtom(), ni
->GetPrefixAtom(),
267 DropAttribute(ni
->NamespaceID(), ni
->NameAtom());
272 return oldAttr
.forget();
275 already_AddRefed
<Attr
> nsDOMAttributeMap::RemoveNamedItem(NodeInfo
* aNodeInfo
,
276 ErrorResult
& aError
) {
277 RefPtr
<Attr
> attribute
= GetAttribute(aNodeInfo
);
278 // This removes the attribute node from the attribute map.
279 aError
= mContent
->UnsetAttr(aNodeInfo
->NamespaceID(), aNodeInfo
->NameAtom(),
281 return attribute
.forget();
284 already_AddRefed
<Attr
> nsDOMAttributeMap::RemoveNamedItem(
285 const nsAString
& aName
, ErrorResult
& aError
) {
287 aError
.Throw(NS_ERROR_DOM_NOT_FOUND_ERR
);
291 RefPtr
<mozilla::dom::NodeInfo
> ni
=
292 mContent
->GetExistingAttrNameFromQName(aName
);
294 aError
.Throw(NS_ERROR_DOM_NOT_FOUND_ERR
);
298 return RemoveNamedItem(ni
, aError
);
301 Attr
* nsDOMAttributeMap::IndexedGetter(uint32_t aIndex
, bool& aFound
) {
303 NS_ENSURE_TRUE(mContent
, nullptr);
305 const nsAttrName
* name
= mContent
->GetAttrNameAt(aIndex
);
306 NS_ENSURE_TRUE(name
, nullptr);
309 // Don't use the nodeinfo even if one exists since it can have the wrong
311 RefPtr
<mozilla::dom::NodeInfo
> ni
=
312 mContent
->NodeInfo()->NodeInfoManager()->GetNodeInfo(
313 name
->LocalName(), name
->GetPrefix(), name
->NamespaceID(),
314 nsINode::ATTRIBUTE_NODE
);
315 return GetAttribute(ni
);
318 Attr
* nsDOMAttributeMap::Item(uint32_t aIndex
) {
320 return IndexedGetter(aIndex
, dummy
);
323 uint32_t nsDOMAttributeMap::Length() const {
324 NS_ENSURE_TRUE(mContent
, 0);
326 return mContent
->GetAttrCount();
329 Attr
* nsDOMAttributeMap::GetNamedItemNS(const nsAString
& aNamespaceURI
,
330 const nsAString
& aLocalName
) {
331 RefPtr
<mozilla::dom::NodeInfo
> ni
=
332 GetAttrNodeInfo(aNamespaceURI
, aLocalName
);
337 return GetAttribute(ni
);
340 already_AddRefed
<mozilla::dom::NodeInfo
> nsDOMAttributeMap::GetAttrNodeInfo(
341 const nsAString
& aNamespaceURI
, const nsAString
& aLocalName
) {
346 int32_t nameSpaceID
= kNameSpaceID_None
;
348 if (!aNamespaceURI
.IsEmpty()) {
349 nameSpaceID
= nsContentUtils::NameSpaceManager()->GetNameSpaceID(
350 aNamespaceURI
, nsContentUtils::IsChromeDoc(mContent
->OwnerDoc()));
352 if (nameSpaceID
== kNameSpaceID_Unknown
) {
357 uint32_t i
, count
= mContent
->GetAttrCount();
358 for (i
= 0; i
< count
; ++i
) {
359 const nsAttrName
* name
= mContent
->GetAttrNameAt(i
);
360 int32_t attrNS
= name
->NamespaceID();
361 nsAtom
* nameAtom
= name
->LocalName();
363 // we're purposefully ignoring the prefix.
364 if (nameSpaceID
== attrNS
&& nameAtom
->Equals(aLocalName
)) {
365 RefPtr
<mozilla::dom::NodeInfo
> ni
;
366 ni
= mContent
->NodeInfo()->NodeInfoManager()->GetNodeInfo(
367 nameAtom
, name
->GetPrefix(), nameSpaceID
, nsINode::ATTRIBUTE_NODE
);
376 already_AddRefed
<Attr
> nsDOMAttributeMap::RemoveNamedItemNS(
377 const nsAString
& aNamespaceURI
, const nsAString
& aLocalName
,
378 ErrorResult
& aError
) {
379 RefPtr
<mozilla::dom::NodeInfo
> ni
=
380 GetAttrNodeInfo(aNamespaceURI
, aLocalName
);
382 aError
.Throw(NS_ERROR_DOM_NOT_FOUND_ERR
);
386 return RemoveNamedItem(ni
, aError
);
389 uint32_t nsDOMAttributeMap::Count() const { return mAttributeCache
.Count(); }
391 size_t nsDOMAttributeMap::SizeOfIncludingThis(
392 MallocSizeOf aMallocSizeOf
) const {
393 size_t n
= aMallocSizeOf(this);
395 n
+= mAttributeCache
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
396 for (auto iter
= mAttributeCache
.ConstIter(); !iter
.Done(); iter
.Next()) {
397 n
+= aMallocSizeOf(iter
.Data().get());
400 // NB: mContent is non-owning and thus not counted.
405 JSObject
* nsDOMAttributeMap::WrapObject(JSContext
* aCx
,
406 JS::Handle
<JSObject
*> aGivenProto
) {
407 return NamedNodeMap_Binding::Wrap(aCx
, this, aGivenProto
);
410 DocGroup
* nsDOMAttributeMap::GetDocGroup() const {
411 return mContent
? mContent
->OwnerDoc()->GetDocGroup() : nullptr;