• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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