blob: 31ab686cd7bc4c8f11bab5c003250b86c7ccf7aa [file] [log] [blame]
// 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