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