2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * (C) 2001 Dirk Mueller (mueller@kde.org)
5 * (C) 2006 Alexey Proskuryakov (ap@nypop.com)
6 * Copyright (C) 2004, 2005, 2006, 2010 Apple Inc. All rights reserved.
7 * Copyright (C) 2010 Google Inc. All rights reserved.
8 * Copyright (C) 2011 Motorola Mobility, Inc. All rights reserved.
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Library General Public
12 * License as published by the Free Software Foundation; either
13 * version 2 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Library General Public License for more details.
20 * You should have received a copy of the GNU Library General Public License
21 * along with this library; see the file COPYING.LIB. If not, write to
22 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301, USA.
28 #include "HTMLOptionElement.h"
30 #include "Attribute.h"
32 #include "ExceptionCode.h"
33 #include "HTMLDataListElement.h"
34 #include "HTMLNames.h"
35 #include "HTMLParserIdioms.h"
36 #include "HTMLSelectElement.h"
37 #include "NodeRenderStyle.h"
38 #include "NodeRenderingContext.h"
39 #include "RenderMenuList.h"
40 #include "RenderTheme.h"
41 #include "ScriptElement.h"
42 #include "StyleResolver.h"
44 #include <wtf/StdLibExtras.h>
45 #include <wtf/Vector.h>
46 #include <wtf/text/StringBuilder.h>
50 using namespace HTMLNames;
52 HTMLOptionElement::HTMLOptionElement(const QualifiedName& tagName, Document* document)
53 : HTMLElement(tagName, document)
57 ASSERT(hasTagName(optionTag));
58 setHasCustomCallbacks();
61 PassRefPtr<HTMLOptionElement> HTMLOptionElement::create(Document* document)
63 return adoptRef(new HTMLOptionElement(optionTag, document));
66 PassRefPtr<HTMLOptionElement> HTMLOptionElement::create(const QualifiedName& tagName, Document* document)
68 return adoptRef(new HTMLOptionElement(tagName, document));
71 PassRefPtr<HTMLOptionElement> HTMLOptionElement::createForJSConstructor(Document* document, const String& data, const String& value,
72 bool defaultSelected, bool selected, ExceptionCode& ec)
74 RefPtr<HTMLOptionElement> element = adoptRef(new HTMLOptionElement(optionTag, document));
76 RefPtr<Text> text = Text::create(document, data.isNull() ? "" : data);
79 element->appendChild(text.release(), ec);
84 element->setValue(value);
86 element->setAttribute(selectedAttr, emptyAtom);
87 element->setSelected(selected);
89 return element.release();
92 void HTMLOptionElement::attach()
94 HTMLElement::attach();
95 // If after attaching nothing called styleForRenderer() on this node we
96 // manually cache the value. This happens if our parent doesn't have a
97 // renderer like <optgroup> or if it doesn't allow children like <select>.
98 if (!m_style && parentNode()->renderStyle())
99 updateNonRenderStyle();
102 void HTMLOptionElement::detach()
105 HTMLElement::detach();
108 bool HTMLOptionElement::supportsFocus() const
110 return HTMLElement::supportsFocus();
113 bool HTMLOptionElement::isFocusable() const
115 // Option elements do not have a renderer so we check the renderStyle instead.
116 return supportsFocus() && renderStyle() && renderStyle()->display() != NONE;
119 String HTMLOptionElement::text() const
121 Document* document = this->document();
124 // WinIE does not use the label attribute, so as a quirk, we ignore it.
125 if (!document->inQuirksMode())
126 text = fastGetAttribute(labelAttr);
128 // FIXME: The following treats an element with the label attribute set to
129 // the empty string the same as an element with no label attribute at all.
130 // Is that correct? If it is, then should the label function work the same way?
132 text = collectOptionInnerText();
134 // FIXME: Is displayStringModifiedByEncoding helpful here?
135 // If it's correct here, then isn't it needed in the value and label functions too?
136 return document->displayStringModifiedByEncoding(text).stripWhiteSpace(isHTMLSpace).simplifyWhiteSpace(isHTMLSpace);
139 void HTMLOptionElement::setText(const String &text, ExceptionCode& ec)
141 RefPtr<Node> protectFromMutationEvents(this);
143 // Changing the text causes a recalc of a select's items, which will reset the selected
144 // index to the first item if the select is single selection with a menu list. We attempt to
145 // preserve the selected item.
146 RefPtr<HTMLSelectElement> select = ownerSelectElement();
147 bool selectIsMenuList = select && select->usesMenuList();
148 int oldSelectedIndex = selectIsMenuList ? select->selectedIndex() : -1;
150 // Handle the common special case where there's exactly 1 child node, and it's a text node.
151 Node* child = firstChild();
152 if (child && child->isTextNode() && !child->nextSibling())
153 toText(child)->setData(text, ec);
156 appendChild(Text::create(document(), text), ec);
159 if (selectIsMenuList && select->selectedIndex() != oldSelectedIndex)
160 select->setSelectedIndex(oldSelectedIndex);
163 void HTMLOptionElement::accessKeyAction(bool)
165 HTMLSelectElement* select = ownerSelectElement();
167 select->accessKeySetSelectedIndex(index());
170 int HTMLOptionElement::index() const
172 // It would be faster to cache the index, but harder to get it right in all cases.
174 HTMLSelectElement* selectElement = ownerSelectElement();
180 const Vector<HTMLElement*>& items = selectElement->listItems();
181 size_t length = items.size();
182 for (size_t i = 0; i < length; ++i) {
183 if (!items[i]->hasTagName(optionTag))
185 if (items[i] == this)
193 void HTMLOptionElement::parseAttribute(const Attribute& attribute)
195 #if ENABLE(DATALIST_ELEMENT)
196 if (attribute.name() == valueAttr) {
197 if (HTMLDataListElement* dataList = ownerDataListElement())
198 dataList->optionElementChildrenChanged();
201 if (attribute.name() == disabledAttr) {
202 bool oldDisabled = m_disabled;
203 m_disabled = !attribute.isNull();
204 if (oldDisabled != m_disabled) {
205 setNeedsStyleRecalc();
206 invalidateParentDistributionIfNecessary(this, SelectRuleFeatureSet::RuleFeatureDisabled | SelectRuleFeatureSet::RuleFeatureEnabled);
207 if (renderer() && renderer()->style()->hasAppearance())
208 renderer()->theme()->stateChanged(renderer(), EnabledState);
210 } else if (attribute.name() == selectedAttr) {
211 // FIXME: This doesn't match what the HTML specification says.
212 // The specification implies that removing the selected attribute or
213 // changing the value of a selected attribute that is already present
214 // has no effect on whether the element is selected. Further, it seems
215 // that we need to do more than just set m_isSelected to select in that
216 // case; we'd need to do the other work from the setSelected function.
217 m_isSelected = !attribute.isNull();
219 HTMLElement::parseAttribute(attribute);
222 String HTMLOptionElement::value() const
224 const AtomicString& value = fastGetAttribute(valueAttr);
227 return collectOptionInnerText().stripWhiteSpace(isHTMLSpace).simplifyWhiteSpace(isHTMLSpace);
230 void HTMLOptionElement::setValue(const String& value)
232 setAttribute(valueAttr, value);
235 bool HTMLOptionElement::selected()
237 if (HTMLSelectElement* select = ownerSelectElement())
238 select->updateListItemSelectedStates();
242 void HTMLOptionElement::setSelected(bool selected)
244 if (m_isSelected == selected)
247 setSelectedState(selected);
249 if (HTMLSelectElement* select = ownerSelectElement())
250 select->optionSelectionStateChanged(this, selected);
253 void HTMLOptionElement::setSelectedState(bool selected)
255 if (m_isSelected == selected)
258 m_isSelected = selected;
259 setNeedsStyleRecalc();
260 invalidateParentDistributionIfNecessary(this, SelectRuleFeatureSet::RuleFeatureChecked);
262 if (HTMLSelectElement* select = ownerSelectElement())
263 select->invalidateSelectedItems();
266 void HTMLOptionElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
268 #if ENABLE(DATALIST_ELEMENT)
269 if (HTMLDataListElement* dataList = ownerDataListElement())
270 dataList->optionElementChildrenChanged();
273 if (HTMLSelectElement* select = ownerSelectElement())
274 select->optionElementChildrenChanged();
275 HTMLElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
278 #if ENABLE(DATALIST_ELEMENT)
279 HTMLDataListElement* HTMLOptionElement::ownerDataListElement() const
281 for (ContainerNode* parent = parentNode(); parent ; parent = parent->parentNode()) {
282 if (parent->hasTagName(datalistTag))
283 return static_cast<HTMLDataListElement*>(parent);
289 HTMLSelectElement* HTMLOptionElement::ownerSelectElement() const
291 ContainerNode* select = parentNode();
292 while (select && !select->hasTagName(selectTag))
293 select = select->parentNode();
298 return toHTMLSelectElement(select);
301 String HTMLOptionElement::label() const
303 const AtomicString& label = fastGetAttribute(labelAttr);
306 return collectOptionInnerText().stripWhiteSpace(isHTMLSpace).simplifyWhiteSpace(isHTMLSpace);
309 void HTMLOptionElement::setLabel(const String& label)
311 setAttribute(labelAttr, label);
314 void HTMLOptionElement::updateNonRenderStyle()
316 m_style = document()->styleResolver()->styleForElement(this);
319 RenderStyle* HTMLOptionElement::nonRendererStyle() const
321 return m_style.get();
324 PassRefPtr<RenderStyle> HTMLOptionElement::customStyleForRenderer()
326 // styleForRenderer is called whenever a new style should be associated
327 // with an Element so now is a good time to update our cached style.
328 updateNonRenderStyle();
332 void HTMLOptionElement::didRecalcStyle(StyleChange)
334 // FIXME: This is nasty, we ask our owner select to repaint even if the new
335 // style is exactly the same.
336 if (HTMLSelectElement* select = ownerSelectElement()) {
337 if (RenderObject* renderer = select->renderer())
342 String HTMLOptionElement::textIndentedToRespectGroupLabel() const
344 ContainerNode* parent = parentNode();
345 if (parent && parent->hasTagName(optgroupTag))
350 bool HTMLOptionElement::disabled() const
352 if (ownElementDisabled())
355 if (!parentNode() || !parentNode()->isHTMLElement())
358 HTMLElement* parentElement = static_cast<HTMLElement*>(parentNode());
359 return parentElement->hasTagName(optgroupTag) && parentElement->disabled();
362 Node::InsertionNotificationRequest HTMLOptionElement::insertedInto(ContainerNode* insertionPoint)
364 if (HTMLSelectElement* select = ownerSelectElement()) {
365 select->setRecalcListItems();
366 // Do not call selected() since calling updateListItemSelectedStates()
367 // at this time won't do the right thing. (Why, exactly?)
368 // FIXME: Might be better to call this unconditionally, always passing m_isSelected,
369 // rather than only calling it if we are selected.
371 select->optionSelectionStateChanged(this, true);
372 select->scrollToSelection();
375 return HTMLElement::insertedInto(insertionPoint);
378 String HTMLOptionElement::collectOptionInnerText() const
381 for (Node* node = firstChild(); node; ) {
382 if (node->isTextNode())
383 text.append(node->nodeValue());
384 // Text nodes inside script elements are not part of the option text.
385 if (node->isElementNode() && toScriptElement(toElement(node)))
386 node = node->traverseNextSibling(this);
388 node = node->traverseNextNode(this);
390 return text.toString();
395 HTMLOptionElement* toHTMLOptionElement(Node* node)
397 ASSERT(!node || node->hasTagName(optionTag));
398 return static_cast<HTMLOptionElement*>(node);
401 const HTMLOptionElement* toHTMLOptionElement(const Node* node)
403 ASSERT(!node || node->hasTagName(optionTag));
404 return static_cast<const HTMLOptionElement*>(node);