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/. */
7 #include "nsDOMStringMap.h"
11 #include "nsGenericHTMLElement.h"
12 #include "nsContentUtils.h"
13 #include "mozilla/dom/DOMStringMapBinding.h"
14 #include "mozilla/dom/MutationEventBinding.h"
16 using namespace mozilla
;
17 using namespace mozilla::dom
;
19 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsDOMStringMap
)
21 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMStringMap
)
22 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElement
)
23 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
25 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMStringMap
)
26 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
27 // Check that mElement exists in case the unlink code is run more than once.
29 // Call back to element to null out weak reference to this object.
30 tmp
->mElement
->ClearDataset();
31 tmp
->mElement
->RemoveMutationObserver(tmp
);
32 tmp
->mElement
= nullptr;
34 tmp
->mExpandoAndGeneration
.OwnerUnlinked();
35 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
37 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMStringMap
)
38 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
39 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver
)
40 NS_INTERFACE_MAP_ENTRY(nsISupports
)
43 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMStringMap
)
44 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMStringMap
)
46 nsDOMStringMap::nsDOMStringMap(Element
* aElement
)
47 : mElement(aElement
), mRemovingProp(false) {
48 mElement
->AddMutationObserver(this);
51 nsDOMStringMap::~nsDOMStringMap() {
52 // Check if element still exists, may have been unlinked by cycle collector.
54 // Call back to element to null out weak reference to this object.
55 mElement
->ClearDataset();
56 mElement
->RemoveMutationObserver(this);
60 DocGroup
* nsDOMStringMap::GetDocGroup() const {
61 return mElement
? mElement
->GetDocGroup() : nullptr;
65 JSObject
* nsDOMStringMap::WrapObject(JSContext
* cx
,
66 JS::Handle
<JSObject
*> aGivenProto
) {
67 return DOMStringMap_Binding::Wrap(cx
, this, aGivenProto
);
70 void nsDOMStringMap::NamedGetter(const nsAString
& aProp
, bool& found
,
71 DOMString
& aResult
) const {
74 if (!DataPropToAttr(aProp
, attr
)) {
79 found
= mElement
->GetAttr(attr
, aResult
);
82 void nsDOMStringMap::NamedSetter(const nsAString
& aProp
,
83 const nsAString
& aValue
, ErrorResult
& rv
) {
85 if (!DataPropToAttr(aProp
, attr
)) {
86 rv
.Throw(NS_ERROR_DOM_SYNTAX_ERR
);
90 nsresult res
= nsContentUtils::CheckQName(attr
, false);
96 RefPtr
<nsAtom
> attrAtom
= NS_Atomize(attr
);
97 MOZ_ASSERT(attrAtom
, "Should be infallible");
99 res
= mElement
->SetAttr(kNameSpaceID_None
, attrAtom
, aValue
, true);
100 if (NS_FAILED(res
)) {
105 void nsDOMStringMap::NamedDeleter(const nsAString
& aProp
, bool& found
) {
106 // Currently removing property, attribute is already removed.
113 if (!DataPropToAttr(aProp
, attr
)) {
118 RefPtr
<nsAtom
> attrAtom
= NS_Atomize(attr
);
119 MOZ_ASSERT(attrAtom
, "Should be infallible");
121 found
= mElement
->HasAttr(attrAtom
);
124 mRemovingProp
= true;
125 mElement
->UnsetAttr(kNameSpaceID_None
, attrAtom
, true);
126 mRemovingProp
= false;
130 void nsDOMStringMap::GetSupportedNames(nsTArray
<nsString
>& aNames
) {
131 uint32_t attrCount
= mElement
->GetAttrCount();
133 // Iterate through all the attributes and add property
134 // names corresponding to data attributes to return array.
135 for (uint32_t i
= 0; i
< attrCount
; ++i
) {
136 const nsAttrName
* attrName
= mElement
->GetAttrNameAt(i
);
137 // Skip the ones that are not in the null namespace
138 if (attrName
->NamespaceID() != kNameSpaceID_None
) {
143 if (!AttrToDataProp(nsDependentAtomString(attrName
->LocalName()), prop
)) {
147 aNames
.AppendElement(prop
);
152 * Converts a dataset property name to the corresponding data attribute name.
153 * (ex. aBigFish to data-a-big-fish).
155 bool nsDOMStringMap::DataPropToAttr(const nsAString
& aProp
,
156 nsAutoString
& aResult
) {
157 // aResult is an autostring, so don't worry about setting its capacity:
158 // SetCapacity is slow even when it's a no-op and we already have enough
159 // storage there for most cases, probably.
160 aResult
.AppendLiteral("data-");
162 // Iterate property by character to form attribute name.
163 // Return syntax error if there is a sequence of "-" followed by a character
164 // in the range "a" to "z".
165 // Replace capital characters with "-" followed by lower case character.
166 // Otherwise, simply append character to attribute name.
167 const char16_t
* start
= aProp
.BeginReading();
168 const char16_t
* end
= aProp
.EndReading();
169 const char16_t
* cur
= start
;
170 for (; cur
< end
; ++cur
) {
171 const char16_t
* next
= cur
+ 1;
172 if (char16_t('-') == *cur
&& next
< end
&& char16_t('a') <= *next
&&
173 *next
<= char16_t('z')) {
174 // Syntax error if character following "-" is in range "a" to "z".
178 if (char16_t('A') <= *cur
&& *cur
<= char16_t('Z')) {
179 // Append the characters in the range [start, cur)
180 aResult
.Append(start
, cur
- start
);
181 // Uncamel-case characters in the range of "A" to "Z".
182 aResult
.Append(char16_t('-'));
183 aResult
.Append(*cur
- 'A' + 'a');
184 start
= next
; // We've already appended the thing at *cur
188 aResult
.Append(start
, cur
- start
);
194 * Converts a data attribute name to the corresponding dataset property name.
195 * (ex. data-a-big-fish to aBigFish).
197 bool nsDOMStringMap::AttrToDataProp(const nsAString
& aAttr
,
198 nsAutoString
& aResult
) {
199 // If the attribute name does not begin with "data-" then it can not be
201 if (!StringBeginsWith(aAttr
, u
"data-"_ns
)) {
205 // Start reading attribute from first character after "data-".
206 const char16_t
* cur
= aAttr
.BeginReading() + 5;
207 const char16_t
* end
= aAttr
.EndReading();
209 // Don't try to mess with aResult's capacity: the probably-no-op SetCapacity()
210 // call is not that fast.
212 // Iterate through attrName by character to form property name.
213 // If there is a sequence of "-" followed by a character in the range "a" to
214 // "z" then replace with upper case letter.
215 // Otherwise append character to property name.
216 for (; cur
< end
; ++cur
) {
217 const char16_t
* next
= cur
+ 1;
218 if (char16_t('-') == *cur
&& next
< end
&& char16_t('a') <= *next
&&
219 *next
<= char16_t('z')) {
220 // Upper case the lower case letters that follow a "-".
221 aResult
.Append(*next
- 'a' + 'A');
222 // Consume character to account for "-" character.
225 // Simply append character if camel case is not necessary.
226 aResult
.Append(*cur
);
233 void nsDOMStringMap::AttributeChanged(Element
* aElement
, int32_t aNameSpaceID
,
234 nsAtom
* aAttribute
, int32_t aModType
,
235 const nsAttrValue
* aOldValue
) {
236 if ((aModType
== MutationEvent_Binding::ADDITION
||
237 aModType
== MutationEvent_Binding::REMOVAL
) &&
238 aNameSpaceID
== kNameSpaceID_None
&&
239 StringBeginsWith(nsDependentAtomString(aAttribute
), u
"data-"_ns
)) {
240 ++mExpandoAndGeneration
.generation
;