OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2010 Google Inc. All rights reserved. | 2 * Copyright (C) 2010 Google Inc. All rights reserved. |
3 * | 3 * |
4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
5 * modification, are permitted provided that the following conditions | 5 * modification, are permitted provided that the following conditions |
6 * are met: | 6 * are met: |
7 * 1. Redistributions of source code must retain the above copyright | 7 * 1. Redistributions of source code must retain the above copyright |
8 * notice, this list of conditions and the following disclaimer. | 8 * notice, this list of conditions and the following disclaimer. |
9 * 2. Redistributions in binary form must reproduce the above copyright | 9 * 2. Redistributions in binary form must reproduce the above copyright |
10 * notice, this list of conditions and the following disclaimer in the | 10 * notice, this list of conditions and the following disclaimer in the |
(...skipping 13 matching lines...) Expand all Loading... |
24 | 24 |
25 #include "core/dom/DOMTokenList.h" | 25 #include "core/dom/DOMTokenList.h" |
26 | 26 |
27 #include "bindings/core/v8/ExceptionState.h" | 27 #include "bindings/core/v8/ExceptionState.h" |
28 #include "core/dom/ExceptionCode.h" | 28 #include "core/dom/ExceptionCode.h" |
29 #include "core/html/parser/HTMLParserIdioms.h" | 29 #include "core/html/parser/HTMLParserIdioms.h" |
30 #include "platform/wtf/text/StringBuilder.h" | 30 #include "platform/wtf/text/StringBuilder.h" |
31 | 31 |
32 namespace blink { | 32 namespace blink { |
33 | 33 |
| 34 // This implements the common part of the following operations: |
| 35 // https://dom.spec.whatwg.org/#dom-domtokenlist-add |
| 36 // https://dom.spec.whatwg.org/#dom-domtokenlist-remove |
| 37 // https://dom.spec.whatwg.org/#dom-domtokenlist-toggle |
| 38 // https://dom.spec.whatwg.org/#dom-domtokenlist-replace |
34 bool DOMTokenList::ValidateToken(const String& token, | 39 bool DOMTokenList::ValidateToken(const String& token, |
35 ExceptionState& exception_state) const { | 40 ExceptionState& exception_state) const { |
| 41 // 1. If token is the empty string, then throw a SyntaxError. |
36 if (token.IsEmpty()) { | 42 if (token.IsEmpty()) { |
37 exception_state.ThrowDOMException(kSyntaxError, | 43 exception_state.ThrowDOMException(kSyntaxError, |
38 "The token provided must not be empty."); | 44 "The token provided must not be empty."); |
39 return false; | 45 return false; |
40 } | 46 } |
41 | 47 |
| 48 // 2. If token contains any ASCII whitespace, then throw an |
| 49 // InvalidCharacterError. |
42 if (token.Find(IsHTMLSpace) != kNotFound) { | 50 if (token.Find(IsHTMLSpace) != kNotFound) { |
43 exception_state.ThrowDOMException(kInvalidCharacterError, | 51 exception_state.ThrowDOMException(kInvalidCharacterError, |
44 "The token provided ('" + token + | 52 "The token provided ('" + token + |
45 "') contains HTML space characters, " | 53 "') contains HTML space characters, " |
46 "which are not valid in tokens."); | 54 "which are not valid in tokens."); |
47 return false; | 55 return false; |
48 } | 56 } |
49 | 57 |
50 return true; | 58 return true; |
51 } | 59 } |
(...skipping 20 matching lines...) Expand all Loading... |
72 return ContainsInternal(token); | 80 return ContainsInternal(token); |
73 } | 81 } |
74 | 82 |
75 void DOMTokenList::add(const AtomicString& token, | 83 void DOMTokenList::add(const AtomicString& token, |
76 ExceptionState& exception_state) { | 84 ExceptionState& exception_state) { |
77 Vector<String> tokens; | 85 Vector<String> tokens; |
78 tokens.push_back(token.GetString()); | 86 tokens.push_back(token.GetString()); |
79 add(tokens, exception_state); | 87 add(tokens, exception_state); |
80 } | 88 } |
81 | 89 |
| 90 // https://dom.spec.whatwg.org/#dom-domtokenlist-add |
82 // Optimally, this should take a Vector<AtomicString> const ref in argument but | 91 // Optimally, this should take a Vector<AtomicString> const ref in argument but |
83 // the bindings generator does not handle that. | 92 // the bindings generator does not handle that. |
84 void DOMTokenList::add(const Vector<String>& tokens, | 93 void DOMTokenList::add(const Vector<String>& tokens, |
85 ExceptionState& exception_state) { | 94 ExceptionState& exception_state) { |
86 Vector<String> filtered_tokens; | 95 if (!ValidateTokens(tokens, exception_state)) |
87 filtered_tokens.ReserveCapacity(tokens.size()); | 96 return; |
88 for (const auto& token : tokens) { | |
89 if (!ValidateToken(token, exception_state)) | |
90 return; | |
91 if (ContainsInternal(AtomicString(token))) | |
92 continue; | |
93 if (filtered_tokens.Contains(token)) | |
94 continue; | |
95 filtered_tokens.push_back(token); | |
96 } | |
97 | 97 |
98 if (!filtered_tokens.IsEmpty()) | 98 setValue(AddTokens(tokens)); |
99 setValue(AddTokens(value(), filtered_tokens)); | |
100 } | 99 } |
101 | 100 |
102 void DOMTokenList::remove(const AtomicString& token, | 101 void DOMTokenList::remove(const AtomicString& token, |
103 ExceptionState& exception_state) { | 102 ExceptionState& exception_state) { |
104 Vector<String> tokens; | 103 Vector<String> tokens; |
105 tokens.push_back(token.GetString()); | 104 tokens.push_back(token.GetString()); |
106 remove(tokens, exception_state); | 105 remove(tokens, exception_state); |
107 } | 106 } |
108 | 107 |
| 108 // https://dom.spec.whatwg.org/#dom-domtokenlist-remove |
109 // Optimally, this should take a Vector<AtomicString> const ref in argument but | 109 // Optimally, this should take a Vector<AtomicString> const ref in argument but |
110 // the bindings generator does not handle that. | 110 // the bindings generator does not handle that. |
111 void DOMTokenList::remove(const Vector<String>& tokens, | 111 void DOMTokenList::remove(const Vector<String>& tokens, |
112 ExceptionState& exception_state) { | 112 ExceptionState& exception_state) { |
113 if (!ValidateTokens(tokens, exception_state)) | 113 if (!ValidateTokens(tokens, exception_state)) |
114 return; | 114 return; |
115 | 115 |
116 // Check using containsInternal first since it is a lot faster than going | 116 // TODO(tkent): This null check doesn't conform to the DOM specification. |
117 // through the string character by character. | 117 // See https://github.com/whatwg/dom/issues/462 |
118 bool found = false; | 118 if (value().IsNull()) |
119 for (const auto& token : tokens) { | 119 return; |
120 if (ContainsInternal(AtomicString(token))) { | 120 setValue(RemoveTokens(tokens)); |
121 found = true; | |
122 break; | |
123 } | |
124 } | |
125 | |
126 setValue(found ? RemoveTokens(value(), tokens) : value()); | |
127 } | 121 } |
128 | 122 |
129 bool DOMTokenList::toggle(const AtomicString& token, | 123 bool DOMTokenList::toggle(const AtomicString& token, |
130 ExceptionState& exception_state) { | 124 ExceptionState& exception_state) { |
131 if (!ValidateToken(token, exception_state)) | 125 if (!ValidateToken(token, exception_state)) |
132 return false; | 126 return false; |
133 | 127 |
134 if (ContainsInternal(token)) { | 128 if (ContainsInternal(token)) { |
135 RemoveInternal(token); | 129 RemoveInternal(token); |
136 return false; | 130 return false; |
(...skipping 16 matching lines...) Expand all Loading... |
153 return force; | 147 return force; |
154 } | 148 } |
155 | 149 |
156 bool DOMTokenList::supports(const AtomicString& token, | 150 bool DOMTokenList::supports(const AtomicString& token, |
157 ExceptionState& exception_state) { | 151 ExceptionState& exception_state) { |
158 return ValidateTokenValue(token, exception_state); | 152 return ValidateTokenValue(token, exception_state); |
159 } | 153 } |
160 | 154 |
161 void DOMTokenList::AddInternal(const AtomicString& token) { | 155 void DOMTokenList::AddInternal(const AtomicString& token) { |
162 if (!ContainsInternal(token)) | 156 if (!ContainsInternal(token)) |
163 setValue(AddToken(value(), token)); | 157 setValue(AddToken(token)); |
164 } | 158 } |
165 | 159 |
166 void DOMTokenList::RemoveInternal(const AtomicString& token) { | 160 void DOMTokenList::RemoveInternal(const AtomicString& token) { |
167 // Check using contains first since it uses AtomicString comparisons instead | 161 // Check using contains first since it uses AtomicString comparisons instead |
168 // of character by character testing. | 162 // of character by character testing. |
169 if (!ContainsInternal(token)) | 163 if (!ContainsInternal(token)) |
170 return; | 164 return; |
171 setValue(RemoveToken(value(), token)); | 165 setValue(RemoveToken(token)); |
172 } | 166 } |
173 | 167 |
174 AtomicString DOMTokenList::AddToken(const AtomicString& input, | 168 AtomicString DOMTokenList::AddToken(const AtomicString& token) { |
175 const AtomicString& token) { | |
176 Vector<String> tokens; | 169 Vector<String> tokens; |
177 tokens.push_back(token.GetString()); | 170 tokens.push_back(token.GetString()); |
178 return AddTokens(input, tokens); | 171 return AddTokens(tokens); |
179 } | 172 } |
180 | 173 |
| 174 // https://dom.spec.whatwg.org/#dom-domtokenlist-add |
181 // This returns an AtomicString because it is always passed as argument to | 175 // This returns an AtomicString because it is always passed as argument to |
182 // setValue() and setValue() takes an AtomicString in argument. | 176 // setValue() and setValue() takes an AtomicString in argument. |
183 AtomicString DOMTokenList::AddTokens(const AtomicString& input, | 177 AtomicString DOMTokenList::AddTokens(const Vector<String>& tokens) { |
184 const Vector<String>& tokens) { | 178 SpaceSplitString& token_set = MutableSet(); |
185 bool needs_space = false; | 179 // 2. For each token in tokens, append token to context object’s token set. |
| 180 for (const auto& token : tokens) |
| 181 token_set.Add(AtomicString(token)); |
| 182 // 3. Run the update steps. |
| 183 return SerializeSet(token_set); |
| 184 } |
186 | 185 |
| 186 AtomicString DOMTokenList::RemoveToken(const AtomicString& token) { |
| 187 Vector<String> tokens; |
| 188 tokens.push_back(token.GetString()); |
| 189 return RemoveTokens(tokens); |
| 190 } |
| 191 |
| 192 // https://dom.spec.whatwg.org/#dom-domtokenlist-remove |
| 193 // This returns an AtomicString because it is always passed as argument to |
| 194 // setValue() and setValue() takes an AtomicString in argument. |
| 195 AtomicString DOMTokenList::RemoveTokens(const Vector<String>& tokens) { |
| 196 SpaceSplitString& token_set = MutableSet(); |
| 197 // 2. For each token in tokens, remove token from context object’s token set. |
| 198 for (const auto& token : tokens) |
| 199 token_set.Remove(AtomicString(token)); |
| 200 // 3. Run the update steps. |
| 201 return SerializeSet(token_set); |
| 202 } |
| 203 |
| 204 // https://dom.spec.whatwg.org/#concept-ordered-set-serializer |
| 205 // The ordered set serializer takes a set and returns the concatenation of the |
| 206 // strings in set, separated from each other by U+0020, if set is non-empty, and |
| 207 // the empty string otherwise. |
| 208 AtomicString DOMTokenList::SerializeSet(const SpaceSplitString& token_set) { |
| 209 size_t size = token_set.size(); |
| 210 if (size == 0) |
| 211 return g_empty_atom; |
| 212 if (size == 1) |
| 213 return token_set[0]; |
187 StringBuilder builder; | 214 StringBuilder builder; |
188 if (!input.IsEmpty()) { | 215 builder.Append(token_set[0]); |
189 builder.Append(input); | 216 for (size_t i = 1; i < size; ++i) { |
190 needs_space = !IsHTMLSpace<UChar>(input[input.length() - 1]); | 217 builder.Append(' '); |
| 218 builder.Append(token_set[i]); |
191 } | 219 } |
192 | |
193 for (const auto& token : tokens) { | |
194 if (needs_space) | |
195 builder.Append(' '); | |
196 builder.Append(token); | |
197 needs_space = true; | |
198 } | |
199 | |
200 return builder.ToAtomicString(); | 220 return builder.ToAtomicString(); |
201 } | 221 } |
202 | 222 |
203 AtomicString DOMTokenList::RemoveToken(const AtomicString& input, | |
204 const AtomicString& token) { | |
205 Vector<String> tokens; | |
206 tokens.push_back(token.GetString()); | |
207 return RemoveTokens(input, tokens); | |
208 } | |
209 | |
210 // This returns an AtomicString because it is always passed as argument to | |
211 // setValue() and setValue() takes an AtomicString in argument. | |
212 AtomicString DOMTokenList::RemoveTokens(const AtomicString& input, | |
213 const Vector<String>& tokens) { | |
214 // Algorithm defined at | |
215 // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyn
taxes.html#remove-a-token-from-a-string | |
216 // New spec is at https://dom.spec.whatwg.org/#remove-a-token-from-a-string | |
217 | |
218 unsigned input_length = input.length(); | |
219 StringBuilder output; // 3 | |
220 output.ReserveCapacity(input_length); | |
221 unsigned position = 0; // 4 | |
222 | |
223 // Step 5 | |
224 while (position < input_length) { | |
225 if (IsHTMLSpace<UChar>(input[position])) { // 6 | |
226 position++; | |
227 continue; // 6.3 | |
228 } | |
229 | |
230 // Step 7 | |
231 StringBuilder token_builder; | |
232 while (position < input_length && IsNotHTMLSpace<UChar>(input[position])) | |
233 token_builder.Append(input[position++]); | |
234 | |
235 // Step 8 | |
236 String token = token_builder.ToString(); | |
237 if (tokens.Contains(token)) { | |
238 // Step 8.1 | |
239 while (position < input_length && IsHTMLSpace<UChar>(input[position])) | |
240 ++position; | |
241 | |
242 // Step 8.2 | |
243 size_t j = output.length(); | |
244 while (j > 0 && IsHTMLSpace<UChar>(output[j - 1])) | |
245 --j; | |
246 output.Resize(j); | |
247 } else { | |
248 output.Append(token); // Step 9 | |
249 } | |
250 | |
251 if (position < input_length && !output.IsEmpty()) | |
252 output.Append(' '); | |
253 } | |
254 | |
255 size_t j = output.length(); | |
256 if (j > 0 && IsHTMLSpace<UChar>(output[j - 1])) | |
257 output.Resize(j - 1); | |
258 | |
259 return output.ToAtomicString(); | |
260 } | |
261 | |
262 void DOMTokenList::setValue(const AtomicString& value) { | 223 void DOMTokenList::setValue(const AtomicString& value) { |
263 bool value_changed = value_ != value; | 224 bool value_changed = value_ != value; |
264 value_ = value; | 225 value_ = value; |
265 if (value_changed) | 226 if (value_changed) |
266 tokens_.Set(value, SpaceSplitString::kShouldNotFoldCase); | 227 tokens_.Set(value, SpaceSplitString::kShouldNotFoldCase); |
267 if (observer_) | 228 if (observer_) |
268 observer_->ValueWasSet(); | 229 observer_->ValueWasSet(); |
269 } | 230 } |
270 | 231 |
271 bool DOMTokenList::ContainsInternal(const AtomicString& token) const { | 232 bool DOMTokenList::ContainsInternal(const AtomicString& token) const { |
272 return tokens_.Contains(token); | 233 return tokens_.Contains(token); |
273 } | 234 } |
274 | 235 |
275 const AtomicString DOMTokenList::item(unsigned index) const { | 236 const AtomicString DOMTokenList::item(unsigned index) const { |
276 if (index >= length()) | 237 if (index >= length()) |
277 return AtomicString(); | 238 return AtomicString(); |
278 return tokens_[index]; | 239 return tokens_[index]; |
279 } | 240 } |
280 | 241 |
281 } // namespace blink | 242 } // namespace blink |
OLD | NEW |