1 /*
2 * Copyright (C) 2010 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25 #include "config.h"
26 #include "core/dom/DOMTokenList.h"
27
28 #include "bindings/core/v8/ExceptionState.h"
29 #include "core/dom/ExceptionCode.h"
30 #include "core/html/parser/HTMLParserIdioms.h"
31 #include "wtf/text/StringBuilder.h"
32
33 namespace blink {
34
validateToken(const String & token,ExceptionState & exceptionState)35 bool DOMTokenList::validateToken(const String& token, ExceptionState& exceptionState)
36 {
37 if (token.isEmpty()) {
38 exceptionState.throwDOMException(SyntaxError, "The token provided must not be empty.");
39 return false;
40 }
41
42 if (token.find(isHTMLSpace) != kNotFound) {
43 exceptionState.throwDOMException(InvalidCharacterError, "The token provided ('" + token + "') contains HTML space characters, which are not valid in tokens.");
44 return false;
45 }
46
47 return true;
48 }
49
validateTokens(const Vector<String> & tokens,ExceptionState & exceptionState)50 bool DOMTokenList::validateTokens(const Vector<String>& tokens, ExceptionState& exceptionState)
51 {
52 for (size_t i = 0; i < tokens.size(); ++i) {
53 if (!validateToken(tokens[i], exceptionState))
54 return false;
55 }
56
57 return true;
58 }
59
contains(const AtomicString & token,ExceptionState & exceptionState) const60 bool DOMTokenList::contains(const AtomicString& token, ExceptionState& exceptionState) const
61 {
62 if (!validateToken(token, exceptionState))
63 return false;
64 return containsInternal(token);
65 }
66
add(const AtomicString & token,ExceptionState & exceptionState)67 void DOMTokenList::add(const AtomicString& token, ExceptionState& exceptionState)
68 {
69 Vector<String> tokens;
70 tokens.append(token.string());
71 add(tokens, exceptionState);
72 }
73
74 // Optimally, this should take a Vector<AtomicString> const ref in argument but the
75 // bindings generator does not handle that.
add(const Vector<String> & tokens,ExceptionState & exceptionState)76 void DOMTokenList::add(const Vector<String>& tokens, ExceptionState& exceptionState)
77 {
78 Vector<String> filteredTokens;
79 filteredTokens.reserveCapacity(tokens.size());
80 for (size_t i = 0; i < tokens.size(); ++i) {
81 if (!validateToken(tokens[i], exceptionState))
82 return;
83 if (containsInternal(AtomicString(tokens[i])))
84 continue;
85 if (filteredTokens.contains(tokens[i]))
86 continue;
87 filteredTokens.append(tokens[i]);
88 }
89
90 if (filteredTokens.isEmpty())
91 return;
92
93 setValue(addTokens(value(), filteredTokens));
94 }
95
remove(const AtomicString & token,ExceptionState & exceptionState)96 void DOMTokenList::remove(const AtomicString& token, ExceptionState& exceptionState)
97 {
98 Vector<String> tokens;
99 tokens.append(token.string());
100 remove(tokens, exceptionState);
101 }
102
103 // Optimally, this should take a Vector<AtomicString> const ref in argument but the
104 // bindings generator does not handle that.
remove(const Vector<String> & tokens,ExceptionState & exceptionState)105 void DOMTokenList::remove(const Vector<String>& tokens, ExceptionState& exceptionState)
106 {
107 if (!validateTokens(tokens, exceptionState))
108 return;
109
110 // Check using containsInternal first since it is a lot faster than going
111 // through the string character by character.
112 bool found = false;
113 for (size_t i = 0; i < tokens.size(); ++i) {
114 if (containsInternal(AtomicString(tokens[i]))) {
115 found = true;
116 break;
117 }
118 }
119
120 if (found)
121 setValue(removeTokens(value(), tokens));
122 }
123
toggle(const AtomicString & token,ExceptionState & exceptionState)124 bool DOMTokenList::toggle(const AtomicString& token, ExceptionState& exceptionState)
125 {
126 if (!validateToken(token, exceptionState))
127 return false;
128
129 if (containsInternal(token)) {
130 removeInternal(token);
131 return false;
132 }
133 addInternal(token);
134 return true;
135 }
136
toggle(const AtomicString & token,bool force,ExceptionState & exceptionState)137 bool DOMTokenList::toggle(const AtomicString& token, bool force, ExceptionState& exceptionState)
138 {
139 if (!validateToken(token, exceptionState))
140 return false;
141
142 if (force)
143 addInternal(token);
144 else
145 removeInternal(token);
146
147 return force;
148 }
149
addInternal(const AtomicString & token)150 void DOMTokenList::addInternal(const AtomicString& token)
151 {
152 if (!containsInternal(token))
153 setValue(addToken(value(), token));
154 }
155
removeInternal(const AtomicString & token)156 void DOMTokenList::removeInternal(const AtomicString& token)
157 {
158 // Check using contains first since it uses AtomicString comparisons instead
159 // of character by character testing.
160 if (!containsInternal(token))
161 return;
162 setValue(removeToken(value(), token));
163 }
164
addToken(const AtomicString & input,const AtomicString & token)165 AtomicString DOMTokenList::addToken(const AtomicString& input, const AtomicString& token)
166 {
167 Vector<String> tokens;
168 tokens.append(token.string());
169 return addTokens(input, tokens);
170 }
171
172 // This returns an AtomicString because it is always passed as argument to setValue() and setValue()
173 // takes an AtomicString in argument.
addTokens(const AtomicString & input,const Vector<String> & tokens)174 AtomicString DOMTokenList::addTokens(const AtomicString& input, const Vector<String>& tokens)
175 {
176 bool needsSpace = false;
177
178 StringBuilder builder;
179 if (!input.isEmpty()) {
180 builder.append(input);
181 needsSpace = !isHTMLSpace<UChar>(input[input.length() - 1]);
182 }
183
184 for (size_t i = 0; i < tokens.size(); ++i) {
185 if (needsSpace)
186 builder.append(' ');
187 builder.append(tokens[i]);
188 needsSpace = true;
189 }
190
191 return builder.toAtomicString();
192 }
193
removeToken(const AtomicString & input,const AtomicString & token)194 AtomicString DOMTokenList::removeToken(const AtomicString& input, const AtomicString& token)
195 {
196 Vector<String> tokens;
197 tokens.append(token.string());
198 return removeTokens(input, tokens);
199 }
200
201 // This returns an AtomicString because it is always passed as argument to setValue() and setValue()
202 // takes an AtomicString in argument.
removeTokens(const AtomicString & input,const Vector<String> & tokens)203 AtomicString DOMTokenList::removeTokens(const AtomicString& input, const Vector<String>& tokens)
204 {
205 // Algorithm defined at http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#remove-a-token-from-a-string
206 // New spec is at http://dom.spec.whatwg.org/#remove-a-token-from-a-string
207
208 unsigned inputLength = input.length();
209 StringBuilder output; // 3
210 output.reserveCapacity(inputLength);
211 unsigned position = 0; // 4
212
213 // Step 5
214 while (position < inputLength) {
215 if (isHTMLSpace<UChar>(input[position])) { // 6
216 output.append(input[position++]); // 6.1, 6.2
217 continue; // 6.3
218 }
219
220 // Step 7
221 StringBuilder tokenBuilder;
222 while (position < inputLength && isNotHTMLSpace<UChar>(input[position]))
223 tokenBuilder.append(input[position++]);
224
225 // Step 8
226 String token = tokenBuilder.toString();
227 if (tokens.contains(token)) {
228 // Step 8.1
229 while (position < inputLength && isHTMLSpace<UChar>(input[position]))
230 ++position;
231
232 // Step 8.2
233 size_t j = output.length();
234 while (j > 0 && isHTMLSpace<UChar>(output[j - 1]))
235 --j;
236 output.resize(j);
237
238 // Step 8.3
239 if (position < inputLength && !output.isEmpty())
240 output.append(' ');
241 } else {
242 output.append(token); // Step 9
243 }
244 }
245
246 return output.toAtomicString();
247 }
248
249 } // namespace blink
250