Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / html / HTMLOptionElement.cpp
1 /*
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.
9  *
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.
14  *
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.
19  *
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.
24  *
25  */
26
27 #include "config.h"
28 #include "core/html/HTMLOptionElement.h"
29
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"
43
44 namespace WebCore {
45
46 using namespace HTMLNames;
47
48 HTMLOptionElement::HTMLOptionElement(Document& document)
49     : HTMLElement(optionTag, document)
50     , m_disabled(false)
51     , m_isSelected(false)
52 {
53     setHasCustomStyleCallbacks();
54     ScriptWrappable::init(this);
55 }
56
57 PassRefPtr<HTMLOptionElement> HTMLOptionElement::create(Document& document)
58 {
59     return adoptRef(new HTMLOptionElement(document));
60 }
61
62 PassRefPtr<HTMLOptionElement> HTMLOptionElement::createForJSConstructor(Document& document, const String& data, const AtomicString& value,
63     bool defaultSelected, bool selected, ExceptionState& exceptionState)
64 {
65     RefPtr<HTMLOptionElement> element = adoptRef(new HTMLOptionElement(document));
66
67     RefPtr<Text> text = Text::create(document, data.isNull() ? "" : data);
68
69     element->appendChild(text.release(), exceptionState);
70     if (exceptionState.hadException())
71         return 0;
72
73     if (!value.isNull())
74         element->setValue(value);
75     if (defaultSelected)
76         element->setAttribute(selectedAttr, emptyAtom);
77     element->setSelected(selected);
78
79     return element.release();
80 }
81
82 void HTMLOptionElement::attach(const AttachContext& context)
83 {
84     updateNonRenderStyle();
85     HTMLElement::attach(context);
86 }
87
88 void HTMLOptionElement::detach(const AttachContext& context)
89 {
90     m_style.clear();
91     HTMLElement::detach(context);
92 }
93
94 bool HTMLOptionElement::rendererIsFocusable() const
95 {
96     // Option elements do not have a renderer so we check the renderStyle instead.
97     return renderStyle() && renderStyle()->display() != NONE;
98 }
99
100 String HTMLOptionElement::text() const
101 {
102     Document& document = this->document();
103     String text;
104
105     // WinIE does not use the label attribute, so as a quirk, we ignore it.
106     if (!document.inQuirksMode())
107         text = fastGetAttribute(labelAttr);
108
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?
112     if (text.isEmpty())
113         text = collectOptionInnerText();
114
115     return text.stripWhiteSpace(isHTMLSpace<UChar>).simplifyWhiteSpace(isHTMLSpace<UChar>);
116 }
117
118 void HTMLOptionElement::setText(const String &text, ExceptionState& exceptionState)
119 {
120     RefPtr<Node> protectFromMutationEvents(this);
121
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;
128
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);
133     else {
134         removeChildren();
135         appendChild(Text::create(document(), text), exceptionState);
136     }
137
138     if (selectIsMenuList && select->selectedIndex() != oldSelectedIndex)
139         select->setSelectedIndex(oldSelectedIndex);
140 }
141
142 void HTMLOptionElement::accessKeyAction(bool)
143 {
144     if (HTMLSelectElement* select = ownerSelectElement())
145         select->accessKeySetSelectedIndex(index());
146 }
147
148 int HTMLOptionElement::index() const
149 {
150     // It would be faster to cache the index, but harder to get it right in all cases.
151
152     HTMLSelectElement* selectElement = ownerSelectElement();
153     if (!selectElement)
154         return 0;
155
156     int optionIndex = 0;
157
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))
162             continue;
163         if (items[i] == this)
164             return optionIndex;
165         ++optionIndex;
166     }
167
168     return 0;
169 }
170
171 void HTMLOptionElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
172 {
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);
183         }
184     } else if (name == selectedAttr) {
185         if (bool willBeSelected = !value.isNull())
186             setSelected(willBeSelected);
187     } else
188         HTMLElement::parseAttribute(name, value);
189 }
190
191 String HTMLOptionElement::value() const
192 {
193     const AtomicString& value = fastGetAttribute(valueAttr);
194     if (!value.isNull())
195         return value;
196     return collectOptionInnerText().stripWhiteSpace(isHTMLSpace<UChar>).simplifyWhiteSpace(isHTMLSpace<UChar>);
197 }
198
199 void HTMLOptionElement::setValue(const AtomicString& value)
200 {
201     setAttribute(valueAttr, value);
202 }
203
204 bool HTMLOptionElement::selected() const
205 {
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
213         // list.
214         if (!select->isFinishedParsingChildren())
215             return m_isSelected;
216         select->updateListItemSelectedStates();
217     }
218     return m_isSelected;
219 }
220
221 void HTMLOptionElement::setSelected(bool selected)
222 {
223     if (m_isSelected == selected)
224         return;
225
226     setSelectedState(selected);
227
228     if (HTMLSelectElement* select = ownerSelectElement())
229         select->optionSelectionStateChanged(this, selected);
230 }
231
232 void HTMLOptionElement::setSelectedState(bool selected)
233 {
234     if (m_isSelected == selected)
235         return;
236
237     m_isSelected = selected;
238     didAffectSelector(AffectedSelectorChecked);
239
240     if (HTMLSelectElement* select = ownerSelectElement())
241         select->invalidateSelectedItems();
242 }
243
244 void HTMLOptionElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
245 {
246     if (HTMLDataListElement* dataList = ownerDataListElement())
247         dataList->optionElementChildrenChanged();
248     else if (HTMLSelectElement* select = ownerSelectElement())
249         select->optionElementChildrenChanged();
250     HTMLElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
251 }
252
253 HTMLDataListElement* HTMLOptionElement::ownerDataListElement() const
254 {
255     for (ContainerNode* parent = parentNode(); parent ; parent = parent->parentNode()) {
256         if (parent->hasTagName(datalistTag))
257             return toHTMLDataListElement(parent);
258     }
259     return 0;
260 }
261
262 HTMLSelectElement* HTMLOptionElement::ownerSelectElement() const
263 {
264     ContainerNode* select = parentNode();
265     while (select && !select->hasTagName(selectTag))
266         select = select->parentNode();
267
268     if (!select)
269         return 0;
270
271     return toHTMLSelectElement(select);
272 }
273
274 String HTMLOptionElement::label() const
275 {
276     const AtomicString& label = fastGetAttribute(labelAttr);
277     if (!label.isNull())
278         return label;
279     return collectOptionInnerText().stripWhiteSpace(isHTMLSpace<UChar>).simplifyWhiteSpace(isHTMLSpace<UChar>);
280 }
281
282 void HTMLOptionElement::setLabel(const AtomicString& label)
283 {
284     setAttribute(labelAttr, label);
285 }
286
287 void HTMLOptionElement::updateNonRenderStyle()
288 {
289     m_style = originalStyleForRenderer();
290 }
291
292 RenderStyle* HTMLOptionElement::nonRendererStyle() const
293 {
294     return m_style.get();
295 }
296
297 PassRefPtr<RenderStyle> HTMLOptionElement::customStyleForRenderer()
298 {
299     return m_style;
300 }
301
302 void HTMLOptionElement::willRecalcStyle(StyleRecalcChange change)
303 {
304     if (!needsAttach() && (needsStyleRecalc() || change >= Inherit))
305         updateNonRenderStyle();
306 }
307
308 void HTMLOptionElement::didRecalcStyle(StyleRecalcChange)
309 {
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())
314             renderer->repaint();
315     }
316 }
317
318 String HTMLOptionElement::textIndentedToRespectGroupLabel() const
319 {
320     ContainerNode* parent = parentNode();
321     if (parent && parent->hasTagName(optgroupTag))
322         return "    " + text();
323     return text();
324 }
325
326 bool HTMLOptionElement::isDisabledFormControl() const
327 {
328     if (ownElementDisabled())
329         return true;
330     if (Element* parent = parentElement())
331         return parent->hasTagName(optgroupTag) && parent->isDisabledFormControl();
332     return false;
333 }
334
335 Node::InsertionNotificationRequest HTMLOptionElement::insertedInto(ContainerNode* insertionPoint)
336 {
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.
343         if (m_isSelected)
344             select->optionSelectionStateChanged(this, true);
345         select->scrollToSelection();
346     }
347
348     return HTMLElement::insertedInto(insertionPoint);
349 }
350
351 String HTMLOptionElement::collectOptionInnerText() const
352 {
353     StringBuilder text;
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);
360         else
361             node = NodeTraversal::next(*node, this);
362     }
363     return text.toString();
364 }
365
366 HTMLFormElement* HTMLOptionElement::form() const
367 {
368     if (HTMLSelectElement* selectElement = ownerSelectElement())
369         return selectElement->formOwner();
370
371     return 0;
372 }
373
374 } // namespace WebCore