DOMTokenList.contains() should not throw
[WebKit.git] / Source / WebCore / html / DOMTokenList.cpp
1 /*
2  * Copyright (C) 2010 Google Inc. All rights reserved.
3  * Copyright (C) 2015, 2016 Apple Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17  * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
18  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
21  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "DOMTokenList.h"
28
29 #include "ExceptionCode.h"
30 #include "HTMLParserIdioms.h"
31 #include "SpaceSplitString.h"
32 #include <wtf/HashSet.h>
33 #include <wtf/text/AtomicStringHash.h>
34 #include <wtf/text/StringBuilder.h>
35
36 namespace WebCore {
37
38 bool DOMTokenList::validateToken(const String& token, ExceptionCode& ec)
39 {
40     if (token.isEmpty()) {
41         ec = SYNTAX_ERR;
42         return false;
43     }
44
45     unsigned length = token.length();
46     for (unsigned i = 0; i < length; ++i) {
47         if (isHTMLSpace(token[i])) {
48             ec = INVALID_CHARACTER_ERR;
49             return false;
50         }
51     }
52
53     return true;
54 }
55
56 bool DOMTokenList::validateTokens(const String* tokens, size_t length, ExceptionCode& ec)
57 {
58     for (size_t i = 0; i < length; ++i) {
59         if (!validateToken(tokens[i], ec))
60             return false;
61     }
62     return true;
63 }
64
65 bool DOMTokenList::contains(const AtomicString& token) const
66 {
67     return m_tokens.contains(token);
68 }
69
70 inline void DOMTokenList::addInternal(const String* tokens, size_t length, ExceptionCode& ec)
71 {
72     // This is usually called with a single token.
73     Vector<AtomicString, 1> uniqueTokens;
74     uniqueTokens.reserveInitialCapacity(length);
75
76     for (size_t i = 0; i < length; ++i) {
77         if (!validateToken(tokens[i], ec))
78             return;
79         if (!m_tokens.contains(tokens[i]) && !uniqueTokens.contains(tokens[i]))
80             uniqueTokens.uncheckedAppend(tokens[i]);
81     }
82
83     if (!uniqueTokens.isEmpty())
84         m_tokens.appendVector(uniqueTokens);
85
86     updateAfterTokenChange();
87 }
88
89 void DOMTokenList::add(const Vector<String>& tokens, ExceptionCode& ec)
90 {
91     addInternal(tokens.data(), tokens.size(), ec);
92 }
93
94 void DOMTokenList::add(const WTF::AtomicString& token, ExceptionCode& ec)
95 {
96     addInternal(&token.string(), 1, ec);
97 }
98
99 inline void DOMTokenList::removeInternal(const String* tokens, size_t length, ExceptionCode& ec)
100 {
101     if (!validateTokens(tokens, length, ec))
102         return;
103
104     for (size_t i = 0; i < length; ++i)
105         m_tokens.removeFirst(tokens[i]);
106
107     updateAfterTokenChange();
108 }
109
110 void DOMTokenList::remove(const Vector<String>& tokens, ExceptionCode& ec)
111 {
112     removeInternal(tokens.data(), tokens.size(), ec);
113 }
114
115 void DOMTokenList::remove(const WTF::AtomicString& token, ExceptionCode& ec)
116 {
117     removeInternal(&token.string(), 1, ec);
118 }
119
120 bool DOMTokenList::toggle(const AtomicString& token, Optional<bool> force, ExceptionCode& ec)
121 {
122     if (!validateToken(token, ec))
123         return false;
124
125     if (m_tokens.contains(token)) {
126         if (!force.valueOr(false)) {
127             m_tokens.removeFirst(token);
128             updateAfterTokenChange();
129             return false;
130         }
131         return true;
132     }
133
134     if (force && !force.value())
135         return false;
136
137     m_tokens.append(token);
138     updateAfterTokenChange();
139     return true;
140 }
141
142 const AtomicString& DOMTokenList::value() const
143 {
144     if (m_cachedValue.isNull()) {
145         // https://dom.spec.whatwg.org/#concept-ordered-set-serializer
146         StringBuilder builder;
147         for (auto& token : m_tokens) {
148             if (!builder.isEmpty())
149                 builder.append(' ');
150             builder.append(token);
151         }
152         m_cachedValue = builder.toAtomicString();
153         ASSERT(!m_cachedValue.isNull());
154     }
155     return m_cachedValue;
156 }
157
158 void DOMTokenList::setValue(const String& value)
159 {
160     setValueInternal(value);
161     updateAfterTokenChange();
162 }
163
164 void DOMTokenList::setValueInternal(const WTF::String& value)
165 {
166     // Clear tokens but not capacity.
167     m_tokens.shrink(0);
168
169     HashSet<AtomicString> addedTokens;
170     // https://dom.spec.whatwg.org/#ordered%20sets
171     for (unsigned start = 0; ; ) {
172         while (start < value.length() && isHTMLSpace(value[start]))
173             ++start;
174         if (start >= value.length())
175             break;
176         unsigned end = start + 1;
177         while (end < value.length() && !isHTMLSpace(value[end]))
178             ++end;
179
180         AtomicString token = value.substring(start, end - start);
181         if (!addedTokens.contains(token)) {
182             m_tokens.append(token);
183             addedTokens.add(token);
184         }
185
186         start = end + 1;
187     }
188
189     m_tokens.shrinkToFit();
190     m_cachedValue = nullAtom;
191 }
192
193 } // namespace WebCore