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 "core/html/HTMLOptionElement.h"
30 #include "HTMLNames.h"
31 #include "bindings/v8/ExceptionState.h"
32 #include "core/dom/Document.h"
33 #include "core/dom/NodeRenderStyle.h"
34 #include "core/dom/NodeTraversal.h"
35 #include "core/dom/ScriptLoader.h"
36 #include "core/dom/Text.h"
37 #include "core/html/HTMLDataListElement.h"
38 #include "core/html/HTMLSelectElement.h"
39 #include "core/html/parser/HTMLParserIdioms.h"
40 #include "core/rendering/RenderTheme.h"
41 #include "wtf/Vector.h"
42 #include "wtf/text/StringBuilder.h"
46 using namespace HTMLNames;
48 HTMLOptionElement::HTMLOptionElement(Document& document)
49 : HTMLElement(optionTag, document)
53 setHasCustomStyleCallbacks();
54 ScriptWrappable::init(this);
57 PassRefPtr<HTMLOptionElement> HTMLOptionElement::create(Document& document)
59 return adoptRef(new HTMLOptionElement(document));
62 PassRefPtr<HTMLOptionElement> HTMLOptionElement::createForJSConstructor(Document& document, const String& data, const AtomicString& value,
63 bool defaultSelected, bool selected, ExceptionState& exceptionState)
65 RefPtr<HTMLOptionElement> element = adoptRef(new HTMLOptionElement(document));
67 RefPtr<Text> text = Text::create(document, data.isNull() ? "" : data);
69 element->appendChild(text.release(), exceptionState);
70 if (exceptionState.hadException())
74 element->setValue(value);
76 element->setAttribute(selectedAttr, emptyAtom);
77 element->setSelected(selected);
79 return element.release();
82 void HTMLOptionElement::attach(const AttachContext& context)
84 updateNonRenderStyle();
85 HTMLElement::attach(context);
88 void HTMLOptionElement::detach(const AttachContext& context)
91 HTMLElement::detach(context);
94 bool HTMLOptionElement::rendererIsFocusable() const
96 // Option elements do not have a renderer so we check the renderStyle instead.
97 return renderStyle() && renderStyle()->display() != NONE;
100 String HTMLOptionElement::text() const
102 Document& document = this->document();
105 // WinIE does not use the label attribute, so as a quirk, we ignore it.
106 if (!document.inQuirksMode())
107 text = fastGetAttribute(labelAttr);
109 // FIXME: The following treats an element with the label attribute set to
110 // the empty string the same as an element with no label attribute at all.
111 // Is that correct? If it is, then should the label function work the same way?
113 text = collectOptionInnerText();
115 return text.stripWhiteSpace(isHTMLSpace<UChar>).simplifyWhiteSpace(isHTMLSpace<UChar>);
118 void HTMLOptionElement::setText(const String &text, ExceptionState& exceptionState)
120 RefPtr<Node> protectFromMutationEvents(this);
122 // Changing the text causes a recalc of a select's items, which will reset the selected
123 // index to the first item if the select is single selection with a menu list. We attempt to
124 // preserve the selected item.
125 RefPtr<HTMLSelectElement> select = ownerSelectElement();
126 bool selectIsMenuList = select && select->usesMenuList();
127 int oldSelectedIndex = selectIsMenuList ? select->selectedIndex() : -1;
129 // Handle the common special case where there's exactly 1 child node, and it's a text node.
130 Node* child = firstChild();
131 if (child && child->isTextNode() && !child->nextSibling())
132 toText(child)->setData(text);
135 appendChild(Text::create(document(), text), exceptionState);
138 if (selectIsMenuList && select->selectedIndex() != oldSelectedIndex)
139 select->setSelectedIndex(oldSelectedIndex);
142 void HTMLOptionElement::accessKeyAction(bool)
144 if (HTMLSelectElement* select = ownerSelectElement())
145 select->accessKeySetSelectedIndex(index());
148 int HTMLOptionElement::index() const
150 // It would be faster to cache the index, but harder to get it right in all cases.
152 HTMLSelectElement* selectElement = ownerSelectElement();
158 const Vector<HTMLElement*>& items = selectElement->listItems();
159 size_t length = items.size();
160 for (size_t i = 0; i < length; ++i) {
161 if (!items[i]->hasTagName(optionTag))
163 if (items[i] == this)
171 void HTMLOptionElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
173 if (name == valueAttr) {
174 if (HTMLDataListElement* dataList = ownerDataListElement())
175 dataList->optionElementChildrenChanged();
176 } else if (name == disabledAttr) {
177 bool oldDisabled = m_disabled;
178 m_disabled = !value.isNull();
179 if (oldDisabled != m_disabled) {
180 didAffectSelector(AffectedSelectorDisabled | AffectedSelectorEnabled);
181 if (renderer() && renderer()->style()->hasAppearance())
182 RenderTheme::theme().stateChanged(renderer(), EnabledState);
184 } else if (name == selectedAttr) {
185 if (bool willBeSelected = !value.isNull())
186 setSelected(willBeSelected);
188 HTMLElement::parseAttribute(name, value);
191 String HTMLOptionElement::value() const
193 const AtomicString& value = fastGetAttribute(valueAttr);
196 return collectOptionInnerText().stripWhiteSpace(isHTMLSpace<UChar>).simplifyWhiteSpace(isHTMLSpace<UChar>);
199 void HTMLOptionElement::setValue(const AtomicString& value)
201 setAttribute(valueAttr, value);
204 bool HTMLOptionElement::selected() const
206 if (HTMLSelectElement* select = ownerSelectElement()) {
207 // If a stylesheet contains option:checked selectors, this function is
208 // called during parsing. updateListItemSelectedStates() is O(N) where N
209 // is the number of option elements, so the <select> parsing would be
210 // O(N^2) without the isFinishedParsingChildren check. Also,
211 // updateListItemSelectedStates() determines default selection, and we'd
212 // like to avoid to determine default selection with incomplete option
214 if (!select->isFinishedParsingChildren())
216 select->updateListItemSelectedStates();
221 void HTMLOptionElement::setSelected(bool selected)
223 if (m_isSelected == selected)
226 setSelectedState(selected);
228 if (HTMLSelectElement* select = ownerSelectElement())
229 select->optionSelectionStateChanged(this, selected);
232 void HTMLOptionElement::setSelectedState(bool selected)
234 if (m_isSelected == selected)
237 m_isSelected = selected;
238 didAffectSelector(AffectedSelectorChecked);
240 if (HTMLSelectElement* select = ownerSelectElement())
241 select->invalidateSelectedItems();
244 void HTMLOptionElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
246 if (HTMLDataListElement* dataList = ownerDataListElement())
247 dataList->optionElementChildrenChanged();
248 else if (HTMLSelectElement* select = ownerSelectElement())
249 select->optionElementChildrenChanged();
250 HTMLElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
253 HTMLDataListElement* HTMLOptionElement::ownerDataListElement() const
255 for (ContainerNode* parent = parentNode(); parent ; parent = parent->parentNode()) {
256 if (parent->hasTagName(datalistTag))
257 return toHTMLDataListElement(parent);
262 HTMLSelectElement* HTMLOptionElement::ownerSelectElement() const
264 ContainerNode* select = parentNode();
265 while (select && !select->hasTagName(selectTag))
266 select = select->parentNode();
271 return toHTMLSelectElement(select);
274 String HTMLOptionElement::label() const
276 const AtomicString& label = fastGetAttribute(labelAttr);
279 return collectOptionInnerText().stripWhiteSpace(isHTMLSpace<UChar>).simplifyWhiteSpace(isHTMLSpace<UChar>);
282 void HTMLOptionElement::setLabel(const AtomicString& label)
284 setAttribute(labelAttr, label);
287 void HTMLOptionElement::updateNonRenderStyle()
289 m_style = originalStyleForRenderer();
292 RenderStyle* HTMLOptionElement::nonRendererStyle() const
294 return m_style.get();
297 PassRefPtr<RenderStyle> HTMLOptionElement::customStyleForRenderer()
302 void HTMLOptionElement::willRecalcStyle(StyleRecalcChange change)
304 if (!needsAttach() && (needsStyleRecalc() || change >= Inherit))
305 updateNonRenderStyle();
308 void HTMLOptionElement::didRecalcStyle(StyleRecalcChange)
310 // FIXME: This is nasty, we ask our owner select to repaint even if the new
311 // style is exactly the same.
312 if (HTMLSelectElement* select = ownerSelectElement()) {
313 if (RenderObject* renderer = select->renderer())
318 String HTMLOptionElement::textIndentedToRespectGroupLabel() const
320 ContainerNode* parent = parentNode();
321 if (parent && parent->hasTagName(optgroupTag))
326 bool HTMLOptionElement::isDisabledFormControl() const
328 if (ownElementDisabled())
330 if (Element* parent = parentElement())
331 return parent->hasTagName(optgroupTag) && parent->isDisabledFormControl();
335 Node::InsertionNotificationRequest HTMLOptionElement::insertedInto(ContainerNode* insertionPoint)
337 if (HTMLSelectElement* select = ownerSelectElement()) {
338 select->setRecalcListItems();
339 // Do not call selected() since calling updateListItemSelectedStates()
340 // at this time won't do the right thing. (Why, exactly?)
341 // FIXME: Might be better to call this unconditionally, always passing m_isSelected,
342 // rather than only calling it if we are selected.
344 select->optionSelectionStateChanged(this, true);
345 select->scrollToSelection();
348 return HTMLElement::insertedInto(insertionPoint);
351 String HTMLOptionElement::collectOptionInnerText() const
354 for (Node* node = firstChild(); node; ) {
355 if (node->isTextNode())
356 text.append(node->nodeValue());
357 // Text nodes inside script elements are not part of the option text.
358 if (node->isElementNode() && toScriptLoaderIfPossible(toElement(node)))
359 node = NodeTraversal::nextSkippingChildren(*node, this);
361 node = NodeTraversal::next(*node, this);
363 return text.toString();
366 HTMLFormElement* HTMLOptionElement::form() const
368 if (HTMLSelectElement* selectElement = ownerSelectElement())
369 return selectElement->formOwner();
374 } // namespace WebCore