| // Copyright 2014 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "cobalt/dom/dom_token_list.h" |
| |
| #include <algorithm> |
| |
| #include "base/string_split.h" |
| #include "cobalt/dom/global_stats.h" |
| #include "nb/memory_scope.h" |
| |
| namespace cobalt { |
| namespace dom { |
| |
| DOMTokenList::DOMTokenList(Element* element, const std::string& attr_name) |
| : element_(element), |
| attr_name_(attr_name), |
| element_node_generation_(Node::kInvalidNodeGeneration) { |
| TRACK_MEMORY_SCOPE("DOM"); |
| DCHECK(element); |
| // The current implementation relies on nodes calling UpdateNodeGeneration() |
| // each time the class is changed. This results in DOMTokenList only working |
| // for class attribute. DOMTokenList is only used by Element::class_list(), |
| // and it is not likely to be used anywhere else. Therefore DCHECK is used to |
| // guarantee attr_name is always "class". |
| DCHECK_EQ(attr_name, "class"); |
| GlobalStats::GetInstance()->Add(this); |
| } |
| |
| // Algorithm for length: |
| // https://www.w3.org/TR/dom/#dom-domtokenlist-length |
| unsigned int DOMTokenList::length() const { |
| // Custom, not in any spec. |
| MaybeRefresh(); |
| |
| return static_cast<unsigned int>(tokens_.size()); |
| } |
| |
| // Algorithm for Item: |
| // https://www.w3.org/TR/dom/#dom-domtokenlist-item |
| base::optional<std::string> DOMTokenList::Item(unsigned int index) const { |
| // Custom, not in any spec. |
| MaybeRefresh(); |
| |
| // 1. If index is equal to or greater than the number of tokens in tokens, |
| // return null. |
| if (index >= tokens_.size()) { |
| return base::nullopt; |
| } |
| |
| // 2. Return the indexth token in tokens. |
| return std::string(tokens_[index].c_str()); |
| } |
| |
| // Algorithm for Contains: |
| // https://www.w3.org/TR/dom/#dom-domtokenlist-contains |
| bool DOMTokenList::Contains(const std::string& token) const { |
| // Custom, not in any spec. |
| MaybeRefresh(); |
| |
| // Cobalt changes the spec processing order to 3, then 1 and 2. The reason for |
| // this is that we're guaranteed that if the token is in the list, it is |
| // valid, and it being in the list is far more likely to be the case than it |
| // being invalid. Given that the two states are mutually exclusive, we're |
| // processing the likeliest path first. |
| |
| // 3. Return true if token is in tokens, and false otherwise. |
| if (std::find(tokens_.begin(), tokens_.end(), token) != tokens_.end()) { |
| return true; |
| } |
| |
| // 1. If token is the empty string, then throw a "SyntaxError" exception. |
| // 2. If token contains any ASCII whitespace, then throw an |
| // "InvalidCharacterError" exception. |
| if (!IsTokenValid(token)) { |
| return false; |
| } |
| |
| return false; |
| } |
| |
| // Algorithm for Add: |
| // https://www.w3.org/TR/dom/#dom-domtokenlist-add |
| void DOMTokenList::Add(const std::vector<std::string>& tokens) { |
| TRACK_MEMORY_SCOPE("DOM"); |
| // Custom, not in any spec. |
| MaybeRefresh(); |
| |
| for (std::vector<std::string>::const_iterator it = tokens.begin(); |
| it != tokens.end(); ++it) { |
| // 1. If token is the empty string, then throw a "SyntaxError" exception. |
| // 2. If token contains any ASCII whitespace, then throw an |
| // "InvalidCharacterError" exception. |
| if (!IsTokenValid(*it)) { |
| return; |
| } |
| } |
| |
| for (std::vector<std::string>::const_iterator it = tokens.begin(); |
| it != tokens.end(); ++it) { |
| // 3. For each token in tokens, in given order, that is not in tokens, |
| // append token to tokens. |
| if (std::find(tokens_.begin(), tokens_.end(), *it) != tokens_.end()) { |
| continue; |
| } |
| tokens_.push_back(base::Token(*it)); |
| } |
| |
| // 4. Run the update steps. |
| RunUpdateSteps(); |
| } |
| |
| // Algorithm for Remove: |
| // https://www.w3.org/TR/dom/#dom-domtokenlist-remove |
| void DOMTokenList::Remove(const std::vector<std::string>& tokens) { |
| // Custom, not in any spec. |
| MaybeRefresh(); |
| |
| for (std::vector<std::string>::const_iterator it = tokens.begin(); |
| it != tokens.end(); ++it) { |
| // 1. If token is the empty string, then throw a "SyntaxError" exception. |
| // 2. If token contains any ASCII whitespace, then throw an |
| // "InvalidCharacterError" exception. |
| if (!IsTokenValid(*it)) { |
| return; |
| } |
| } |
| |
| for (std::vector<std::string>::const_iterator it = tokens.begin(); |
| it != tokens.end(); ++it) { |
| // 3. For each token in tokens, remove token from tokens. |
| tokens_.erase(std::remove(tokens_.begin(), tokens_.end(), *it), |
| tokens_.end()); |
| } |
| |
| // 4. Run the update steps. |
| RunUpdateSteps(); |
| } |
| |
| // Algorithm for AnonymousStringifier: |
| // https://www.w3.org/TR/dom/#dom-domtokenlist-stringifier |
| std::string DOMTokenList::AnonymousStringifier() const { |
| // Custom, not in any spec. |
| MaybeRefresh(); |
| |
| std::string result; |
| for (size_t i = 0; i < tokens_.size(); ++i) { |
| result += tokens_[i].c_str(); |
| result += ' '; |
| } |
| if (!result.empty()) { |
| result.resize(result.size() - 1); |
| } |
| return result; |
| } |
| |
| bool DOMTokenList::ContainsValid(base::Token valid_token) const { |
| MaybeRefresh(); |
| |
| // This version of Contains does not process steps 1 and 2 and requires the |
| // token to be pre-validated. |
| |
| // 1. If token is the empty string, then throw a "SyntaxError" exception. |
| // 2. If token contains any ASCII whitespace, then throw an |
| // "InvalidCharacterError" exception. |
| |
| // 3. Return true if token is in tokens, and false otherwise. |
| if (std::find(tokens_.begin(), tokens_.end(), valid_token) != tokens_.end()) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| const std::vector<base::Token>& DOMTokenList::GetTokens() const { |
| MaybeRefresh(); |
| return tokens_; |
| } |
| |
| void DOMTokenList::TraceMembers(script::Tracer* tracer) { |
| tracer->Trace(element()); |
| } |
| |
| DOMTokenList::~DOMTokenList() { GlobalStats::GetInstance()->Remove(this); } |
| |
| // Algorithm for RunUpdateSteps: |
| // https://www.w3.org/TR/dom/#concept-dtl-update |
| void DOMTokenList::RunUpdateSteps() const { |
| TRACK_MEMORY_SCOPE("DOM"); |
| // 1. If there is no associated attribute (when the object is a |
| // DOMSettableTokenList), terminate these steps. |
| // 2. Set an attribute for the associated element using associated attribute's |
| // local name and the result of running the ordered set serializer for tokens. |
| element_->SetAttribute(attr_name_, AnonymousStringifier()); |
| } |
| |
| bool DOMTokenList::IsTokenValid(const std::string& token) const { |
| if (token.empty()) { |
| // TODO: Throw JS SyntaxError. |
| return false; |
| } |
| if (token.find_first_of(" \n\t\r\f") != std::string::npos) { |
| // TODO: Throw JS InvalidCharacterError. |
| return false; |
| } |
| return true; |
| } |
| |
| void DOMTokenList::MaybeRefresh() const { |
| TRACK_MEMORY_SCOPE("DOM"); |
| if (element_node_generation_ != element_->node_generation()) { |
| element_node_generation_ = element_->node_generation(); |
| std::string attribute = element_->GetAttribute(attr_name_).value_or(""); |
| std::vector<std::string> tokens; |
| tokens.reserve(tokens_.size()); |
| base::SplitStringAlongWhitespace(attribute, &tokens); |
| tokens_.clear(); |
| tokens_.reserve(tokens.size()); |
| for (size_t i = 0; i < tokens.size(); ++i) { |
| tokens_.push_back(base::Token(tokens[i])); |
| } |
| } |
| } |
| |
| } // namespace dom |
| } // namespace cobalt |