| // Copyright 2014 The Cobalt Authors. 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 |