DOMTokenList update steps for classList don't follow the spec
[WebKit-https.git] / Source / WebCore / html / DOMTokenList.cpp
1 /*
2  * Copyright (C) 2010 Google Inc. All rights reserved.
3  * Copyright (C) 2015 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, ExceptionCode& ec) const
66 {
67     if (!validateToken(token, ec))
68         return false;
69
70     return m_tokens.contains(token);
71 }
72
73 inline void DOMTokenList::addInternal(const String* tokens, size_t length, ExceptionCode& ec)
74 {
75     // This is usually called with a single token.
76     Vector<AtomicString, 1> uniqueTokens;
77     uniqueTokens.reserveInitialCapacity(length);
78
79     for (size_t i = 0; i < length; ++i) {
80         if (!validateToken(tokens[i], ec))
81             return;
82         if (!m_tokens.contains(tokens[i]) && !uniqueTokens.contains(tokens[i]))
83             uniqueTokens.uncheckedAppend(tokens[i]);
84     }
85
86     if (uniqueTokens.isEmpty())
87         return;
88
89     m_tokens.appendVector(uniqueTokens);
90     updateAfterTokenChange();
91 }
92
93 void DOMTokenList::add(const Vector<String>& tokens, ExceptionCode& ec)
94 {
95     addInternal(tokens.data(), tokens.size(), ec);
96 }
97
98 void DOMTokenList::add(const WTF::AtomicString& token, ExceptionCode& ec)
99 {
100     addInternal(&token.string(), 1, ec);
101 }
102
103 inline void DOMTokenList::removeInternal(const String* tokens, size_t length, ExceptionCode& ec)
104 {
105     if (!validateTokens(tokens, length, ec))
106         return;
107
108     bool didRemoveTokens = false;
109     for (size_t i = 0; i < length; ++i) {
110         if (m_tokens.removeFirst(tokens[i]))
111             didRemoveTokens = true;
112     }
113
114     if (didRemoveTokens)
115         updateAfterTokenChange();
116 }
117
118 void DOMTokenList::remove(const Vector<String>& tokens, ExceptionCode& ec)
119 {
120     removeInternal(tokens.data(), tokens.size(), ec);
121 }
122
123 void DOMTokenList::remove(const WTF::AtomicString& token, ExceptionCode& ec)
124 {
125     removeInternal(&token.string(), 1, ec);
126 }
127
128 bool DOMTokenList::toggle(const AtomicString& token, Optional<bool> force, ExceptionCode& ec)
129 {
130     if (!validateToken(token, ec))
131         return false;
132
133     if (m_tokens.contains(token)) {
134         if (!force.valueOr(false)) {
135             m_tokens.removeFirst(token);
136             updateAfterTokenChange();
137             return false;
138         }
139         return true;
140     }
141
142     if (force && !force.value())
143         return false;
144
145     m_tokens.append(token);
146     updateAfterTokenChange();
147     return true;
148 }
149
150 const AtomicString& DOMTokenList::value() const
151 {
152     if (m_cachedValue.isNull()) {
153         // https://dom.spec.whatwg.org/#concept-ordered-set-serializer
154         StringBuilder builder;
155         for (auto& token : m_tokens) {
156             if (!builder.isEmpty())
157                 builder.append(' ');
158             builder.append(token);
159         }
160         m_cachedValue = builder.toAtomicString();
161         ASSERT(!m_cachedValue.isNull());
162     }
163     return m_cachedValue;
164 }
165
166 void DOMTokenList::setValue(const String& value)
167 {
168     // Clear tokens but not capacity.
169     m_tokens.shrink(0);
170
171     HashSet<AtomicString> addedTokens;
172     // https://dom.spec.whatwg.org/#ordered%20sets
173     for (unsigned start = 0; ; ) {
174         while (start < value.length() && isHTMLSpace(value[start]))
175             ++start;
176         if (start >= value.length())
177             break;
178         unsigned end = start + 1;
179         while (end < value.length() && !isHTMLSpace(value[end]))
180             ++end;
181
182         AtomicString token = value.substring(start, end - start);
183         if (!addedTokens.contains(token)) {
184             m_tokens.append(token);
185             addedTokens.add(token);
186         }
187
188         start = end + 1;
189     }
190
191     m_tokens.shrinkToFit();
192     m_cachedValue = nullAtom;
193 }
194
195 } // namespace WebCore