1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=2 et sw=2 tw=80: */
3 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is mozilla.org code.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * the Mozilla Foundation.
20 : * Portions created by the Initial Developer are Copyright (C) 2011
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * William Chen <wchen@mozilla.com> (Original Author)
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either the GNU General Public License Version 2 or later (the "GPL"), or
28 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "nsDOMStringMap.h"
41 :
42 : #include "nsDOMClassInfoID.h"
43 : #include "nsGenericHTMLElement.h"
44 : #include "nsContentUtils.h"
45 :
46 : DOMCI_DATA(DOMStringMap, nsDOMStringMap)
47 :
48 1464 : NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMStringMap)
49 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMStringMap)
50 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mElement)
51 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
52 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMStringMap)
53 : // Check that mElement exists in case the unlink code is run more than once.
54 0 : if (tmp->mElement) {
55 : // Call back to element to null out weak reference to this object.
56 0 : tmp->mElement->ClearDataset();
57 0 : tmp->mElement = nsnull;
58 : }
59 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
60 :
61 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMStringMap)
62 0 : NS_INTERFACE_MAP_ENTRY(nsIDOMDOMStringMap)
63 0 : NS_INTERFACE_MAP_ENTRY(nsISupports)
64 0 : NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(DOMStringMap)
65 0 : NS_INTERFACE_MAP_END
66 :
67 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMStringMap)
68 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMStringMap)
69 :
70 0 : nsDOMStringMap::nsDOMStringMap(nsGenericHTMLElement* aElement)
71 : : mElement(aElement),
72 0 : mRemovingProp(false)
73 : {
74 0 : }
75 :
76 0 : nsDOMStringMap::~nsDOMStringMap()
77 : {
78 : // Check if element still exists, may have been unlinked by cycle collector.
79 0 : if (mElement) {
80 : // Call back to element to null out weak reference to this object.
81 0 : mElement->ClearDataset();
82 : }
83 0 : }
84 :
85 : class nsDOMStringMapRemoveProp : public nsRunnable {
86 : public:
87 0 : nsDOMStringMapRemoveProp(nsDOMStringMap* aDataset, nsIAtom* aProperty)
88 : : mDataset(aDataset),
89 0 : mProperty(aProperty)
90 : {
91 0 : }
92 :
93 0 : NS_IMETHOD Run()
94 : {
95 0 : return mDataset->RemovePropInternal(mProperty);
96 : }
97 :
98 0 : virtual ~nsDOMStringMapRemoveProp()
99 0 : {
100 0 : }
101 :
102 : protected:
103 : nsRefPtr<nsDOMStringMap> mDataset;
104 : nsCOMPtr<nsIAtom> mProperty;
105 : };
106 :
107 : /* [notxpcom] boolean hasDataAttr (in DOMString prop); */
108 0 : NS_IMETHODIMP_(bool) nsDOMStringMap::HasDataAttr(const nsAString& aProp)
109 : {
110 0 : nsAutoString attr;
111 0 : if (!DataPropToAttr(aProp, attr)) {
112 0 : return false;
113 : }
114 :
115 0 : nsCOMPtr<nsIAtom> attrAtom = do_GetAtom(attr);
116 0 : if (!attrAtom) {
117 0 : return false;
118 : }
119 :
120 0 : return mElement->HasAttr(kNameSpaceID_None, attrAtom);
121 : }
122 :
123 : /* [noscript] DOMString getDataAttr (in DOMString prop); */
124 0 : NS_IMETHODIMP nsDOMStringMap::GetDataAttr(const nsAString& aProp,
125 : nsAString& aResult NS_OUTPARAM)
126 : {
127 0 : nsAutoString attr;
128 :
129 0 : if (!DataPropToAttr(aProp, attr)) {
130 0 : aResult.SetIsVoid(true);
131 0 : return NS_OK;
132 : }
133 :
134 0 : nsCOMPtr<nsIAtom> attrAtom = do_GetAtom(attr);
135 0 : NS_ENSURE_TRUE(attrAtom, NS_ERROR_OUT_OF_MEMORY);
136 :
137 0 : if (!mElement->GetAttr(kNameSpaceID_None, attrAtom, aResult)) {
138 0 : aResult.SetIsVoid(true);
139 0 : return NS_OK;
140 : }
141 :
142 0 : return NS_OK;
143 : }
144 :
145 : /* [noscript] void setDataAttr (in DOMString prop, in DOMString value); */
146 0 : NS_IMETHODIMP nsDOMStringMap::SetDataAttr(const nsAString& aProp,
147 : const nsAString& aValue)
148 : {
149 0 : nsAutoString attr;
150 0 : NS_ENSURE_TRUE(DataPropToAttr(aProp, attr), NS_ERROR_DOM_SYNTAX_ERR);
151 :
152 0 : nsresult rv = nsContentUtils::CheckQName(attr, false);
153 0 : NS_ENSURE_SUCCESS(rv, rv);
154 :
155 0 : nsCOMPtr<nsIAtom> attrAtom = do_GetAtom(attr);
156 0 : NS_ENSURE_TRUE(attrAtom, NS_ERROR_OUT_OF_MEMORY);
157 :
158 0 : return mElement->SetAttr(kNameSpaceID_None, attrAtom, aValue, true);
159 : }
160 :
161 : /* [notxpcom] void removeDataAttr (in DOMString prop); */
162 0 : NS_IMETHODIMP_(void) nsDOMStringMap::RemoveDataAttr(const nsAString& aProp)
163 : {
164 : // Currently removing property, attribute is already removed.
165 0 : if (mRemovingProp) {
166 0 : return;
167 : }
168 :
169 0 : nsAutoString attr;
170 0 : if (!DataPropToAttr(aProp, attr)) {
171 : return;
172 : }
173 :
174 0 : nsCOMPtr<nsIAtom> attrAtom = do_GetAtom(attr);
175 0 : if (!attrAtom) {
176 : return;
177 : }
178 :
179 0 : mElement->UnsetAttr(kNameSpaceID_None, attrAtom, true);
180 : }
181 :
182 0 : nsGenericHTMLElement* nsDOMStringMap::GetElement()
183 : {
184 0 : return mElement;
185 : }
186 :
187 : /* [notxpcom] void removeProp (in nsIAtom attr); */
188 0 : NS_IMETHODIMP_(void) nsDOMStringMap::RemoveProp(nsIAtom* aAttr)
189 : {
190 0 : nsContentUtils::AddScriptRunner(new nsDOMStringMapRemoveProp(this, aAttr));
191 0 : }
192 :
193 0 : nsresult nsDOMStringMap::RemovePropInternal(nsIAtom* aAttr)
194 : {
195 0 : nsAutoString attr;
196 0 : aAttr->ToString(attr);
197 0 : nsAutoString prop;
198 0 : NS_ENSURE_TRUE(AttrToDataProp(attr, prop), NS_OK);
199 :
200 : jsval val;
201 0 : JSContext* cx = nsContentUtils::GetCurrentJSContext();
202 : nsresult rv = nsContentUtils::WrapNative(cx, JS_GetGlobalForScopeChain(cx),
203 0 : this, &val);
204 0 : NS_ENSURE_SUCCESS(rv, rv);
205 :
206 0 : JSAutoEnterCompartment ac;
207 0 : if (!ac.enter(cx, JSVAL_TO_OBJECT(val))) {
208 0 : return NS_ERROR_FAILURE;
209 : }
210 :
211 : // Guard against infinite recursion. Prevents the stack from looking like
212 : // ...
213 : // RemoveProp
214 : // ...
215 : // RemoveDataAttr
216 : // ...
217 : // RemoveProp
218 0 : mRemovingProp = true;
219 : jsval dummy;
220 : JS_DeleteUCProperty2(cx, JSVAL_TO_OBJECT(val), prop.get(), prop.Length(),
221 0 : &dummy);
222 0 : mRemovingProp = false;
223 :
224 0 : return NS_OK;
225 : }
226 :
227 : /**
228 : * Returns a list of dataset properties corresponding to the data
229 : * attributes on the element.
230 : */
231 0 : nsresult nsDOMStringMap::GetDataPropList(nsTArray<nsString>& aResult)
232 : {
233 0 : PRUint32 attrCount = mElement->GetAttrCount();
234 :
235 : // Iterate through all the attributes and add property
236 : // names corresponding to data attributes to return array.
237 0 : for (PRUint32 i = 0; i < attrCount; ++i) {
238 0 : nsAutoString attrString;
239 0 : const nsAttrName* attrName = mElement->GetAttrNameAt(i);
240 0 : attrName->LocalName()->ToString(attrString);
241 :
242 0 : nsAutoString prop;
243 0 : if (!AttrToDataProp(attrString, prop)) {
244 0 : continue;
245 : }
246 :
247 0 : aResult.AppendElement(prop);
248 : }
249 :
250 0 : return NS_OK;
251 : }
252 :
253 : /**
254 : * Converts a dataset property name to the corresponding data attribute name.
255 : * (ex. aBigFish to data-a-big-fish).
256 : */
257 0 : bool nsDOMStringMap::DataPropToAttr(const nsAString& aProp,
258 : nsAString& aResult)
259 : {
260 0 : const PRUnichar* cur = aProp.BeginReading();
261 0 : const PRUnichar* end = aProp.EndReading();
262 :
263 : // String corresponding to the data attribute on the element.
264 0 : nsAutoString attr;
265 : // Length of attr will be at least the length of the property + 5 for "data-".
266 0 : attr.SetCapacity(aProp.Length() + 5);
267 :
268 0 : attr.Append(NS_LITERAL_STRING("data-"));
269 :
270 : // Iterate property by character to form attribute name.
271 : // Return syntax error if there is a sequence of "-" followed by a character
272 : // in the range "a" to "z".
273 : // Replace capital characters with "-" followed by lower case character.
274 : // Otherwise, simply append character to attribute name.
275 0 : for (; cur < end; ++cur) {
276 0 : const PRUnichar* next = cur + 1;
277 0 : if (PRUnichar('-') == *cur && next < end &&
278 : PRUnichar('a') <= *next && *next <= PRUnichar('z')) {
279 : // Syntax error if character following "-" is in range "a" to "z".
280 0 : return false;
281 : }
282 :
283 0 : if (PRUnichar('A') <= *cur && *cur <= PRUnichar('Z')) {
284 : // Uncamel-case characters in the range of "A" to "Z".
285 0 : attr.Append(PRUnichar('-'));
286 0 : attr.Append(*cur - 'A' + 'a');
287 : } else {
288 0 : attr.Append(*cur);
289 : }
290 : }
291 :
292 0 : aResult.Assign(attr);
293 0 : return true;
294 : }
295 :
296 : /**
297 : * Converts a data attribute name to the corresponding dataset property name.
298 : * (ex. data-a-big-fish to aBigFish).
299 : */
300 0 : bool nsDOMStringMap::AttrToDataProp(const nsAString& aAttr,
301 : nsAString& aResult)
302 : {
303 : // If the attribute name does not begin with "data-" then it can not be
304 : // a data attribute.
305 0 : if (!StringBeginsWith(aAttr, NS_LITERAL_STRING("data-"))) {
306 0 : return false;
307 : }
308 :
309 : // Start reading attribute from first character after "data-".
310 0 : const PRUnichar* cur = aAttr.BeginReading() + 5;
311 0 : const PRUnichar* end = aAttr.EndReading();
312 :
313 : // Dataset property name. Ensure that the string is large enough to store
314 : // all the characters in the property name.
315 0 : nsAutoString prop;
316 :
317 : // Iterate through attrName by character to form property name.
318 : // If there is a sequence of "-" followed by a character in the range "a" to
319 : // "z" then replace with upper case letter.
320 : // Otherwise append character to property name.
321 0 : for (; cur < end; ++cur) {
322 0 : const PRUnichar* next = cur + 1;
323 0 : if (PRUnichar('-') == *cur && next < end &&
324 : PRUnichar('a') <= *next && *next <= PRUnichar('z')) {
325 : // Upper case the lower case letters that follow a "-".
326 0 : prop.Append(*next - 'a' + 'A');
327 : // Consume character to account for "-" character.
328 0 : ++cur;
329 : } else {
330 : // Simply append character if camel case is not necessary.
331 0 : prop.Append(*cur);
332 : }
333 : }
334 :
335 0 : aResult.Assign(prop);
336 0 : return true;
337 4392 : }
|