| 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 |