blob: 8f0d4784c760a30e46d0a4b57afad6fd2252be47 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* Copyright (C) 2003, 2004, 2005, 2006, 2007, 2010, 2011, 2012 Apple Inc. All
* rights reserved.
* Copyright (C) 2014 Samsung Electronics. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "third_party/blink/renderer/core/html/forms/html_form_controls_collection.h"
#include "third_party/blink/renderer/bindings/core/v8/radio_node_list_or_element.h"
#include "third_party/blink/renderer/core/frame/use_counter.h"
#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
#include "third_party/blink/renderer/core/html/html_image_element.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/platform/wtf/hash_set.h"
namespace blink {
using namespace HTMLNames;
// Since the collections are to be "live", we have to do the
// calculation every time if anything has changed.
HTMLFormControlsCollection::HTMLFormControlsCollection(
ContainerNode& owner_node)
: HTMLCollection(owner_node, kFormControls, kOverridesItemAfter),
cached_element_(nullptr),
cached_element_offset_in_array_(0) {
DCHECK(IsHTMLFormElement(owner_node));
}
HTMLFormControlsCollection* HTMLFormControlsCollection::Create(
ContainerNode& owner_node,
CollectionType type) {
DCHECK_EQ(type, kFormControls);
return new HTMLFormControlsCollection(owner_node);
}
HTMLFormControlsCollection::~HTMLFormControlsCollection() = default;
const ListedElement::List& HTMLFormControlsCollection::ListedElements() const {
return ToHTMLFormElement(ownerNode()).ListedElements();
}
const HeapVector<Member<HTMLImageElement>>&
HTMLFormControlsCollection::FormImageElements() const {
return ToHTMLFormElement(ownerNode()).ImageElements();
}
static unsigned FindListedElement(const ListedElement::List& listed_elements,
Element* element) {
unsigned i = 0;
for (; i < listed_elements.size(); ++i) {
ListedElement* listed_element = listed_elements[i];
if (listed_element->IsEnumeratable() &&
ToHTMLElement(listed_element) == element)
break;
}
return i;
}
HTMLElement* HTMLFormControlsCollection::VirtualItemAfter(
Element* previous) const {
const ListedElement::List& listed_elements = ListedElements();
unsigned offset;
if (!previous)
offset = 0;
else if (cached_element_ == previous)
offset = cached_element_offset_in_array_ + 1;
else
offset = FindListedElement(listed_elements, previous) + 1;
for (unsigned i = offset; i < listed_elements.size(); ++i) {
ListedElement* listed_element = listed_elements[i];
if (listed_element->IsEnumeratable()) {
cached_element_ = ToHTMLElement(listed_element);
cached_element_offset_in_array_ = i;
return cached_element_;
}
}
return nullptr;
}
void HTMLFormControlsCollection::InvalidateCache(Document* old_document) const {
HTMLCollection::InvalidateCache(old_document);
cached_element_ = nullptr;
cached_element_offset_in_array_ = 0;
}
static HTMLElement* FirstNamedItem(const ListedElement::List& elements_array,
const QualifiedName& attr_name,
const String& name) {
DCHECK(attr_name == idAttr || attr_name == nameAttr);
for (const auto& listed_element : elements_array) {
HTMLElement* element = ToHTMLElement(listed_element);
if (listed_element->IsEnumeratable() &&
element->FastGetAttribute(attr_name) == name)
return element;
}
return nullptr;
}
HTMLElement* HTMLFormControlsCollection::namedItem(
const AtomicString& name) const {
// http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/nameditem.asp
// This method first searches for an object with a matching id
// attribute. If a match is not found, the method then searches for an
// object with a matching name attribute, but only on those elements
// that are allowed a name attribute.
if (HTMLElement* item = FirstNamedItem(ListedElements(), idAttr, name))
return item;
return FirstNamedItem(ListedElements(), nameAttr, name);
}
void HTMLFormControlsCollection::UpdateIdNameCache() const {
if (HasValidIdNameCache())
return;
NamedItemCache* cache = NamedItemCache::Create();
HashSet<StringImpl*> found_input_elements;
for (const auto& listed_element : ListedElements()) {
if (listed_element->IsEnumeratable()) {
HTMLElement* element = ToHTMLElement(listed_element);
const AtomicString& id_attr_val = element->GetIdAttribute();
const AtomicString& name_attr_val = element->GetNameAttribute();
if (!id_attr_val.IsEmpty()) {
cache->AddElementWithId(id_attr_val, element);
found_input_elements.insert(id_attr_val.Impl());
}
if (!name_attr_val.IsEmpty() && id_attr_val != name_attr_val) {
cache->AddElementWithName(name_attr_val, element);
found_input_elements.insert(name_attr_val.Impl());
}
}
}
// HTMLFormControlsCollection doesn't support named getter for IMG
// elements. However we still need to handle IMG elements here because
// HTMLFormElement named getter relies on this.
for (const auto& element : FormImageElements()) {
const AtomicString& id_attr_val = element->GetIdAttribute();
const AtomicString& name_attr_val = element->GetNameAttribute();
if (!id_attr_val.IsEmpty() &&
!found_input_elements.Contains(id_attr_val.Impl()))
cache->AddElementWithId(id_attr_val, element);
if (!name_attr_val.IsEmpty() && id_attr_val != name_attr_val &&
!found_input_elements.Contains(name_attr_val.Impl()))
cache->AddElementWithName(name_attr_val, element);
}
// Set the named item cache last as traversing the tree may cause cache
// invalidation.
SetNamedItemCache(cache);
}
void HTMLFormControlsCollection::namedGetter(
const AtomicString& name,
RadioNodeListOrElement& return_value) {
HeapVector<Member<Element>> named_items;
NamedItems(name, named_items);
if (named_items.IsEmpty())
return;
if (named_items.size() == 1) {
if (!IsHTMLImageElement(*named_items[0]))
return_value.SetElement(named_items.at(0));
return;
}
// This path never returns a RadioNodeList for <img> because
// onlyMatchingImgElements flag is false by default.
return_value.SetRadioNodeList(ownerNode().GetRadioNodeList(name));
}
void HTMLFormControlsCollection::SupportedPropertyNames(Vector<String>& names) {
// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#htmlformcontrolscollection-0:
// The supported property names consist of the non-empty values of all the id
// and name attributes of all the elements represented by the collection, in
// tree order, ignoring later duplicates, with the id of an element preceding
// its name if it contributes both, they differ from each other, and neither
// is the duplicate of an earlier entry.
HashSet<AtomicString> existing_names;
unsigned length = this->length();
for (unsigned i = 0; i < length; ++i) {
HTMLElement* element = item(i);
DCHECK(element);
const AtomicString& id_attribute = element->GetIdAttribute();
if (!id_attribute.IsEmpty()) {
HashSet<AtomicString>::AddResult add_result =
existing_names.insert(id_attribute);
if (add_result.is_new_entry)
names.push_back(id_attribute);
}
const AtomicString& name_attribute = element->GetNameAttribute();
if (!name_attribute.IsEmpty()) {
HashSet<AtomicString>::AddResult add_result =
existing_names.insert(name_attribute);
if (add_result.is_new_entry)
names.push_back(name_attribute);
}
}
}
void HTMLFormControlsCollection::Trace(blink::Visitor* visitor) {
visitor->Trace(cached_element_);
HTMLCollection::Trace(visitor);
}
} // namespace blink