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