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 #include "nsDOMStringMap.h"
8
9 #include "jsapi.h"
10 #include "nsError.h"
11 #include "nsGenericHTMLElement.h"
12 #include "nsContentUtils.h"
13 #include "mozilla/dom/DOMStringMapBinding.h"
14 #include "mozilla/dom/MutationEventBinding.h"
15
16 using namespace mozilla;
17 using namespace mozilla::dom;
18
19 NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMStringMap)
20
21 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMStringMap)
22 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElement)
23 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
24
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.
28 if (tmp->mElement) {
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;
33 }
34 tmp->mExpandoAndGeneration.OwnerUnlinked();
35 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
36
NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsDOMStringMap)37 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsDOMStringMap)
38
39 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMStringMap)
40 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
41 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
42 NS_INTERFACE_MAP_ENTRY(nsISupports)
43 NS_INTERFACE_MAP_END
44
45 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMStringMap)
46 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMStringMap)
47
48 nsDOMStringMap::nsDOMStringMap(Element* aElement)
49 : mElement(aElement), mRemovingProp(false) {
50 mElement->AddMutationObserver(this);
51 }
52
~nsDOMStringMap()53 nsDOMStringMap::~nsDOMStringMap() {
54 // Check if element still exists, may have been unlinked by cycle collector.
55 if (mElement) {
56 // Call back to element to null out weak reference to this object.
57 mElement->ClearDataset();
58 mElement->RemoveMutationObserver(this);
59 }
60 }
61
GetDocGroup() const62 DocGroup* nsDOMStringMap::GetDocGroup() const {
63 return mElement ? mElement->GetDocGroup() : nullptr;
64 }
65
66 /* virtual */
WrapObject(JSContext * cx,JS::Handle<JSObject * > aGivenProto)67 JSObject* nsDOMStringMap::WrapObject(JSContext* cx,
68 JS::Handle<JSObject*> aGivenProto) {
69 return DOMStringMap_Binding::Wrap(cx, this, aGivenProto);
70 }
71
NamedGetter(const nsAString & aProp,bool & found,DOMString & aResult) const72 void nsDOMStringMap::NamedGetter(const nsAString& aProp, bool& found,
73 DOMString& aResult) const {
74 nsAutoString attr;
75
76 if (!DataPropToAttr(aProp, attr)) {
77 found = false;
78 return;
79 }
80
81 found = mElement->GetAttr(attr, aResult);
82 }
83
NamedSetter(const nsAString & aProp,const nsAString & aValue,ErrorResult & rv)84 void nsDOMStringMap::NamedSetter(const nsAString& aProp,
85 const nsAString& aValue, ErrorResult& rv) {
86 nsAutoString attr;
87 if (!DataPropToAttr(aProp, attr)) {
88 rv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
89 return;
90 }
91
92 nsresult res = nsContentUtils::CheckQName(attr, false);
93 if (NS_FAILED(res)) {
94 rv.Throw(res);
95 return;
96 }
97
98 RefPtr<nsAtom> attrAtom = NS_Atomize(attr);
99 MOZ_ASSERT(attrAtom, "Should be infallible");
100
101 res = mElement->SetAttr(kNameSpaceID_None, attrAtom, aValue, true);
102 if (NS_FAILED(res)) {
103 rv.Throw(res);
104 }
105 }
106
NamedDeleter(const nsAString & aProp,bool & found)107 void nsDOMStringMap::NamedDeleter(const nsAString& aProp, bool& found) {
108 // Currently removing property, attribute is already removed.
109 if (mRemovingProp) {
110 found = false;
111 return;
112 }
113
114 nsAutoString attr;
115 if (!DataPropToAttr(aProp, attr)) {
116 found = false;
117 return;
118 }
119
120 RefPtr<nsAtom> attrAtom = NS_Atomize(attr);
121 MOZ_ASSERT(attrAtom, "Should be infallible");
122
123 found = mElement->HasAttr(kNameSpaceID_None, attrAtom);
124
125 if (found) {
126 mRemovingProp = true;
127 mElement->UnsetAttr(kNameSpaceID_None, attrAtom, true);
128 mRemovingProp = false;
129 }
130 }
131
GetSupportedNames(nsTArray<nsString> & aNames)132 void nsDOMStringMap::GetSupportedNames(nsTArray<nsString>& aNames) {
133 uint32_t attrCount = mElement->GetAttrCount();
134
135 // Iterate through all the attributes and add property
136 // names corresponding to data attributes to return array.
137 for (uint32_t i = 0; i < attrCount; ++i) {
138 const nsAttrName* attrName = mElement->GetAttrNameAt(i);
139 // Skip the ones that are not in the null namespace
140 if (attrName->NamespaceID() != kNameSpaceID_None) {
141 continue;
142 }
143
144 nsAutoString prop;
145 if (!AttrToDataProp(nsDependentAtomString(attrName->LocalName()), prop)) {
146 continue;
147 }
148
149 aNames.AppendElement(prop);
150 }
151 }
152
153 /**
154 * Converts a dataset property name to the corresponding data attribute name.
155 * (ex. aBigFish to data-a-big-fish).
156 */
DataPropToAttr(const nsAString & aProp,nsAutoString & aResult)157 bool nsDOMStringMap::DataPropToAttr(const nsAString& aProp,
158 nsAutoString& aResult) {
159 // aResult is an autostring, so don't worry about setting its capacity:
160 // SetCapacity is slow even when it's a no-op and we already have enough
161 // storage there for most cases, probably.
162 aResult.AppendLiteral("data-");
163
164 // Iterate property by character to form attribute name.
165 // Return syntax error if there is a sequence of "-" followed by a character
166 // in the range "a" to "z".
167 // Replace capital characters with "-" followed by lower case character.
168 // Otherwise, simply append character to attribute name.
169 const char16_t* start = aProp.BeginReading();
170 const char16_t* end = aProp.EndReading();
171 const char16_t* cur = start;
172 for (; cur < end; ++cur) {
173 const char16_t* next = cur + 1;
174 if (char16_t('-') == *cur && next < end && char16_t('a') <= *next &&
175 *next <= char16_t('z')) {
176 // Syntax error if character following "-" is in range "a" to "z".
177 return false;
178 }
179
180 if (char16_t('A') <= *cur && *cur <= char16_t('Z')) {
181 // Append the characters in the range [start, cur)
182 aResult.Append(start, cur - start);
183 // Uncamel-case characters in the range of "A" to "Z".
184 aResult.Append(char16_t('-'));
185 aResult.Append(*cur - 'A' + 'a');
186 start = next; // We've already appended the thing at *cur
187 }
188 }
189
190 aResult.Append(start, cur - start);
191
192 return true;
193 }
194
195 /**
196 * Converts a data attribute name to the corresponding dataset property name.
197 * (ex. data-a-big-fish to aBigFish).
198 */
AttrToDataProp(const nsAString & aAttr,nsAutoString & aResult)199 bool nsDOMStringMap::AttrToDataProp(const nsAString& aAttr,
200 nsAutoString& aResult) {
201 // If the attribute name does not begin with "data-" then it can not be
202 // a data attribute.
203 if (!StringBeginsWith(aAttr, u"data-"_ns)) {
204 return false;
205 }
206
207 // Start reading attribute from first character after "data-".
208 const char16_t* cur = aAttr.BeginReading() + 5;
209 const char16_t* end = aAttr.EndReading();
210
211 // Don't try to mess with aResult's capacity: the probably-no-op SetCapacity()
212 // call is not that fast.
213
214 // Iterate through attrName by character to form property name.
215 // If there is a sequence of "-" followed by a character in the range "a" to
216 // "z" then replace with upper case letter.
217 // Otherwise append character to property name.
218 for (; cur < end; ++cur) {
219 const char16_t* next = cur + 1;
220 if (char16_t('-') == *cur && next < end && char16_t('a') <= *next &&
221 *next <= char16_t('z')) {
222 // Upper case the lower case letters that follow a "-".
223 aResult.Append(*next - 'a' + 'A');
224 // Consume character to account for "-" character.
225 ++cur;
226 } else {
227 // Simply append character if camel case is not necessary.
228 aResult.Append(*cur);
229 }
230 }
231
232 return true;
233 }
234
AttributeChanged(Element * aElement,int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType,const nsAttrValue * aOldValue)235 void nsDOMStringMap::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
236 nsAtom* aAttribute, int32_t aModType,
237 const nsAttrValue* aOldValue) {
238 if ((aModType == MutationEvent_Binding::ADDITION ||
239 aModType == MutationEvent_Binding::REMOVAL) &&
240 aNameSpaceID == kNameSpaceID_None &&
241 StringBeginsWith(nsDependentAtomString(aAttribute), u"data-"_ns)) {
242 ++mExpandoAndGeneration.generation;
243 }
244 }
245