blob: 37a66ce9cf5e07bb600871c12bfba94b1d27ea75 [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/named_node_map.h"
#include <algorithm>
#include <iterator>
#include "base/optional.h"
#include "cobalt/dom/attr.h"
#include "cobalt/dom/element.h"
#include "cobalt/dom/global_stats.h"
#include "nb/memory_scope.h"
namespace cobalt {
namespace dom {
NamedNodeMap::NamedNodeMap(const scoped_refptr<Element>& element)
: element_(element) {
DCHECK(element_);
GlobalStats::GetInstance()->Add(this);
}
// The length attribute's getter must return the attribute list's size.
// https://dom.spec.whatwg.org/#dom-namednodemap-length
unsigned int NamedNodeMap::length() const {
return static_cast<unsigned int>(element_->attribute_map().size());
}
// Algorithm for Item:
// https://dom.spec.whatwg.org/#dom-namednodemap-item
scoped_refptr<Attr> NamedNodeMap::Item(unsigned int item) {
// 1. If index is equal to or greater than context object's attribute list's
// size, then return null.
if (item >= element_->attribute_map().size()) {
return NULL;
}
// 2. Otherwise, return context object's attribute list[index].
Element::AttributeMap::const_iterator iter =
element_->attribute_map().begin();
std::advance(iter, item);
return GetOrCreateAttr(iter->first);
}
// The getNamedItem(qualifiedName) method, when invoked, must return the result
// of getting an attribute given qualifiedName and element.
// https://dom.spec.whatwg.org/#dom-namednodemap-getnameditem
scoped_refptr<Attr> NamedNodeMap::GetNamedItem(const std::string& name) const {
// 1. if element is in the HTML namespace and its node document is an HTML
// document, then set qualifiedName to qualifiedName in ASCII lowercase.
// 2. Return the first attribute in element's attribute list whose qualified
// name is qualifiedName, and null otherwise.
if (!element_->HasAttribute(name)) {
return NULL;
}
return GetOrCreateAttr(name);
}
// The setNamedItem(attr) and setNamedItemNS(attr) methods, when invoked, must
// return the result of setting an attribute given attr and element.
// https://dom.spec.whatwg.org/#dom-namednodemap-setnameditem
scoped_refptr<Attr> NamedNodeMap::SetNamedItem(
const scoped_refptr<Attr>& attribute) {
// Custom, not in any spec.
if (!attribute) {
// TODO: Throw JS NotFoundError.
return NULL;
}
// To set an attribute given an attr and element, run these steps:
// https://dom.spec.whatwg.org/#concept-element-attributes-set
// 1. If attr's element is neither null nor element, throw an
// InUseAttributeError.
if (attribute->container() && attribute->container() != this) {
// TODO: Throw JS InUseAttributeError.
return NULL;
}
// 2. Let oldAttr be the result of getting an attribute given attr's
// namespace, attr's local name, and element.
// 3. If oldAttr is attr, return attr.
// 4. If oldAttr is non-null, replace it by attr in element.
// 5. Otherwise, append attr to element.
const std::string& name = attribute->name();
scoped_refptr<Attr> old_attr;
if (element_->HasAttribute(name)) {
old_attr = GetOrCreateAttr(name);
if (attribute->value() == old_attr->value()) {
return old_attr;
}
old_attr->set_container(NULL);
}
attribute->set_container(this);
proxy_attributes_[name] = attribute->AsWeakPtr();
// Inform the element about the new attribute. This should trigger a call to
// NamedNodeMap::SetAttributeInternal and continue the update there.
element_->SetAttribute(name, attribute->value());
// 6. Return oldAttr.
return old_attr;
}
// Algorithm for RemoveNamedItem:
// https://dom.spec.whatwg.org/#dom-namednodemap-removenameditem
scoped_refptr<Attr> NamedNodeMap::RemoveNamedItem(const std::string& name) {
// 1. Let attr be the result of removing an attribute given qualifiedName and
// element.
// 2. If attr is null, then throw a NotFoundError.
scoped_refptr<Attr> attr;
if (element_->HasAttribute(name)) {
// Get the previous attribute that was associated with 'name' and detach it
// from NamedNodeMap.
attr = GetOrCreateAttr(name);
attr->set_container(NULL);
} else {
// TODO: Throw JS NotFoundError.
return NULL;
}
// Inform the element about the removed attribute. This should trigger a call
// to NamedNodeMap::RemoveAttributeInternal and continue the update there.
element_->RemoveAttribute(name);
// 3. Return attr.
return attr;
}
bool NamedNodeMap::CanQueryNamedProperty(const std::string& name) const {
return element_->HasAttribute(name);
}
void NamedNodeMap::EnumerateNamedProperties(
script::PropertyEnumerator* enumerator) const {
const Element::AttributeMap& attribute_map = element_->attribute_map();
for (Element::AttributeMap::const_iterator iter = attribute_map.begin();
iter != attribute_map.end(); ++iter) {
enumerator->AddProperty(iter->first);
}
}
void NamedNodeMap::SetAttributeInternal(const std::string& name,
const std::string& value) {
// Update the associated Attr object.
NameToAttrMap::iterator attribute_iter = proxy_attributes_.find(name);
if (attribute_iter != proxy_attributes_.end() && attribute_iter->second) {
attribute_iter->second->SetValueInternal(value);
}
}
void NamedNodeMap::RemoveAttributeInternal(const std::string& name) {
// Make sure to detach the proxy attribute from NamedNodeMap.
NameToAttrMap::iterator iter = proxy_attributes_.find(name);
if (iter != proxy_attributes_.end()) {
if (iter->second) {
iter->second->set_container(NULL);
}
proxy_attributes_.erase(iter);
}
}
scoped_refptr<Element> NamedNodeMap::element() const { return element_; }
void NamedNodeMap::TraceMembers(script::Tracer* tracer) {
tracer->Trace(element_);
tracer->TraceValues(proxy_attributes_);
}
NamedNodeMap::~NamedNodeMap() { GlobalStats::GetInstance()->Remove(this); }
scoped_refptr<Attr> NamedNodeMap::GetOrCreateAttr(
const std::string& name) const {
TRACK_MEMORY_SCOPE("DOM");
NameToAttrMap::iterator iter = proxy_attributes_.find(name);
if (iter != proxy_attributes_.end() && iter->second) {
return iter->second.get();
}
// We don't have an existing Attr object so we create a new once instead.
base::optional<std::string> value = element_->GetAttribute(name);
scoped_refptr<Attr> attribute = new Attr(name, value.value_or(""), this);
proxy_attributes_[name] = attribute->AsWeakPtr();
return attribute;
}
} // namespace dom
} // namespace cobalt