79239f43d70034107d2d61f13d3d2e29703a3344
[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 "bindings/v8/ExceptionState.h"
31 #include "core/HTMLNames.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 PassRefPtrWillBeRawPtr<HTMLOptionElement> HTMLOptionElement::create(Document& document)
58 {
59     return adoptRefWillBeNoop(new HTMLOptionElement(document));
60 }
61
62 PassRefPtrWillBeRawPtr<HTMLOptionElement> HTMLOptionElement::createForJSConstructor(Document& document, const String& data, const AtomicString& value,
63     bool defaultSelected, bool selected, ExceptionState& exceptionState)
64 {
65     RefPtrWillBeRawPtr<HTMLOptionElement> element = adoptRefWillBeNoop(new HTMLOptionElement(document));
66     element->appendChild(Text::create(document, data.isNull() ? "" : data), exceptionState);
67     if (exceptionState.hadException())
68         return nullptr;
69
70     if (!value.isNull())
71         element->setValue(value);
72     if (defaultSelected)
73         element->setAttribute(selectedAttr, emptyAtom);
74     element->setSelected(selected);
75
76     return element.release();
77 }
78
79 void HTMLOptionElement::attach(const AttachContext& context)
80 {
81     AttachContext optionContext(context);
82     if (context.resolvedStyle) {
83         ASSERT(!m_style || m_style == context.resolvedStyle);
84         m_style = context.resolvedStyle;
85     } else {
86         updateNonRenderStyle();
87         optionContext.resolvedStyle = m_style.get();
88     }
89     HTMLElement::attach(optionContext);
90 }
91
92 void HTMLOptionElement::detach(const AttachContext& context)
93 {
94     m_style.clear();
95     HTMLElement::detach(context);
96 }
97
98 bool HTMLOptionElement::rendererIsFocusable() const
99 {
100     // Option elements do not have a renderer so we check the renderStyle instead.
101     return renderStyle() && renderStyle()->display() != NONE;
102 }
103
104 String HTMLOptionElement::text() const
105 {
106     Document& document = this->document();
107     String text;
108
109     // WinIE does not use the label attribute, so as a quirk, we ignore it.
110     if (!document.inQuirksMode())
111         text = fastGetAttribute(labelAttr);
112
113     // FIXME: The following treats an element with the label attribute set to
114     // the empty string the same as an element with no label attribute at all.
115     // Is that correct? If it is, then should the label function work the same way?
116     if (text.isEmpty())
117         text = collectOptionInnerText();
118
119     return text.stripWhiteSpace(isHTMLSpace<UChar>).simplifyWhiteSpace(isHTMLSpace<UChar>);
120 }
121
122 void HTMLOptionElement::setText(const String &text, ExceptionState& exceptionState)
123 {
124     RefPtrWillBeRawPtr<Node> protectFromMutationEvents(this);
125
126     // Changing the text causes a recalc of a select's items, which will reset the selected
127     // index to the first item if the select is single selection with a menu list. We attempt to
128     // preserve the selected item.
129     RefPtrWillBeRawPtr<HTMLSelectElement> select = ownerSelectElement();
130     bool selectIsMenuList = select && select->usesMenuList();
131     int oldSelectedIndex = selectIsMenuList ? select->selectedIndex() : -1;
132
133     // Handle the common special case where there's exactly 1 child node, and it's a text node.
134     Node* child = firstChild();
135     if (child && child->isTextNode() && !child->nextSibling())
136         toText(child)->setData(text);
137     else {
138         removeChildren();
139         appendChild(Text::create(document(), text), exceptionState);
140     }
141
142     if (selectIsMenuList && select->selectedIndex() != oldSelectedIndex)
143         select->setSelectedIndex(oldSelectedIndex);
144 }
145
146 void HTMLOptionElement::accessKeyAction(bool)
147 {
148     if (HTMLSelectElement* select = ownerSelectElement())
149         select->accessKeySetSelectedIndex(index());
150 }
151
152 int HTMLOptionElement::index() const
153 {
154     // It would be faster to cache the index, but harder to get it right in all cases.
155
156     HTMLSelectElement* selectElement = ownerSelectElement();
157     if (!selectElement)
158         return 0;
159
160     int optionIndex = 0;
161
162     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = selectElement->listItems();
163     size_t length = items.size();
164     for (size_t i = 0; i < length; ++i) {
165         if (!isHTMLOptionElement(*items[i]))
166             continue;
167         if (items[i].get() == this)
168             return optionIndex;
169         ++optionIndex;
170     }
171
172     return 0;
173 }
174
175 void HTMLOptionElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
176 {
177     if (name == valueAttr) {
178         if (HTMLDataListElement* dataList = ownerDataListElement())
179             dataList->optionElementChildrenChanged();
180     } else if (name == disabledAttr) {
181         bool oldDisabled = m_disabled;
182         m_disabled = !value.isNull();
183         if (oldDisabled != m_disabled) {
184             didAffectSelector(AffectedSelectorDisabled | AffectedSelectorEnabled);
185             if (renderer() && renderer()->style()->hasAppearance())
186                 RenderTheme::theme().stateChanged(renderer(), EnabledControlState);
187         }
188     } else if (name == selectedAttr) {
189         if (bool willBeSelected = !value.isNull())
190             setSelected(willBeSelected);
191     } else
192         HTMLElement::parseAttribute(name, value);
193 }
194
195 String HTMLOptionElement::value() const
196 {
197     const AtomicString& value = fastGetAttribute(valueAttr);
198     if (!value.isNull())
199         return value;
200     return collectOptionInnerText().stripWhiteSpace(isHTMLSpace<UChar>).simplifyWhiteSpace(isHTMLSpace<UChar>);
201 }
202
203 void HTMLOptionElement::setValue(const AtomicString& value)
204 {
205     setAttribute(valueAttr, value);
206 }
207
208 bool HTMLOptionElement::selected() const
209 {
210     if (HTMLSelectElement* select = ownerSelectElement()) {
211         // If a stylesheet contains option:checked selectors, this function is
212         // called during parsing. updateListItemSelectedStates() is O(N) where N
213         // is the number of option elements, so the <select> parsing would be
214         // O(N^2) without the isFinishedParsingChildren check. Also,
215         // updateListItemSelectedStates() determines default selection, and we'd
216         // like to avoid to determine default selection with incomplete option
217         // list.
218         if (!select->isFinishedParsingChildren())
219             return m_isSelected;
220         select->updateListItemSelectedStates();
221     }
222     return m_isSelected;
223 }
224
225 void HTMLOptionElement::setSelected(bool selected)
226 {
227     if (m_isSelected == selected)
228         return;
229
230     setSelectedState(selected);
231
232     if (HTMLSelectElement* select = ownerSelectElement())
233         select->optionSelectionStateChanged(this, selected);
234 }
235
236 void HTMLOptionElement::setSelectedState(bool selected)
237 {
238     if (m_isSelected == selected)
239         return;
240
241     m_isSelected = selected;
242     didAffectSelector(AffectedSelectorChecked);
243
244     if (HTMLSelectElement* select = ownerSelectElement())
245         select->invalidateSelectedItems();
246 }
247
248 void HTMLOptionElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
249 {
250     if (HTMLDataListElement* dataList = ownerDataListElement())
251         dataList->optionElementChildrenChanged();
252     else if (HTMLSelectElement* select = ownerSelectElement())
253         select->optionElementChildrenChanged();
254     HTMLElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
255 }
256
257 HTMLDataListElement* HTMLOptionElement::ownerDataListElement() const
258 {
259     return Traversal<HTMLDataListElement>::firstAncestor(*this);
260 }
261
262 HTMLSelectElement* HTMLOptionElement::ownerSelectElement() const
263 {
264     return Traversal<HTMLSelectElement>::firstAncestor(*this);
265 }
266
267 String HTMLOptionElement::label() const
268 {
269     const AtomicString& label = fastGetAttribute(labelAttr);
270     if (!label.isNull())
271         return label;
272     return collectOptionInnerText().stripWhiteSpace(isHTMLSpace<UChar>).simplifyWhiteSpace(isHTMLSpace<UChar>);
273 }
274
275 void HTMLOptionElement::setLabel(const AtomicString& label)
276 {
277     setAttribute(labelAttr, label);
278 }
279
280 void HTMLOptionElement::updateNonRenderStyle()
281 {
282     bool oldDisplayNoneStatus = isDisplayNone();
283     m_style = originalStyleForRenderer();
284     if (oldDisplayNoneStatus != isDisplayNone()) {
285         if (HTMLSelectElement* select = ownerSelectElement())
286             select->updateListOnRenderer();
287     }
288 }
289
290 RenderStyle* HTMLOptionElement::nonRendererStyle() const
291 {
292     return m_style.get();
293 }
294
295 PassRefPtr<RenderStyle> HTMLOptionElement::customStyleForRenderer()
296 {
297     updateNonRenderStyle();
298     return m_style;
299 }
300
301 void HTMLOptionElement::didRecalcStyle(StyleRecalcChange change)
302 {
303     if (change == NoChange)
304         return;
305
306     // FIXME: We ask our owner select to repaint regardless of which property changed.
307     if (HTMLSelectElement* select = ownerSelectElement()) {
308         if (RenderObject* renderer = select->renderer())
309             renderer->paintInvalidationForWholeRenderer();
310     }
311 }
312
313 String HTMLOptionElement::textIndentedToRespectGroupLabel() const
314 {
315     ContainerNode* parent = parentNode();
316     if (parent && isHTMLOptGroupElement(*parent))
317         return "    " + text();
318     return text();
319 }
320
321 bool HTMLOptionElement::isDisabledFormControl() const
322 {
323     if (ownElementDisabled())
324         return true;
325     if (Element* parent = parentElement())
326         return isHTMLOptGroupElement(*parent) && parent->isDisabledFormControl();
327     return false;
328 }
329
330 Node::InsertionNotificationRequest HTMLOptionElement::insertedInto(ContainerNode* insertionPoint)
331 {
332     if (HTMLSelectElement* select = ownerSelectElement()) {
333         select->setRecalcListItems();
334         // Do not call selected() since calling updateListItemSelectedStates()
335         // at this time won't do the right thing. (Why, exactly?)
336         // FIXME: Might be better to call this unconditionally, always passing m_isSelected,
337         // rather than only calling it if we are selected.
338         if (m_isSelected)
339             select->optionSelectionStateChanged(this, true);
340         select->scrollToSelection();
341     }
342
343     return HTMLElement::insertedInto(insertionPoint);
344 }
345
346 String HTMLOptionElement::collectOptionInnerText() const
347 {
348     StringBuilder text;
349     for (Node* node = firstChild(); node; ) {
350         if (node->isTextNode())
351             text.append(node->nodeValue());
352         // Text nodes inside script elements are not part of the option text.
353         if (node->isElementNode() && toScriptLoaderIfPossible(toElement(node)))
354             node = NodeTraversal::nextSkippingChildren(*node, this);
355         else
356             node = NodeTraversal::next(*node, this);
357     }
358     return text.toString();
359 }
360
361 HTMLFormElement* HTMLOptionElement::form() const
362 {
363     if (HTMLSelectElement* selectElement = ownerSelectElement())
364         return selectElement->formOwner();
365
366     return 0;
367 }
368
369 bool HTMLOptionElement::isDisplayNone() const
370 {
371     ContainerNode* parent = parentNode();
372     // Check for parent optgroup having display NONE
373     if (parent && isHTMLOptGroupElement(*parent)) {
374         if (toHTMLOptGroupElement(*parent).isDisplayNone())
375             return true;
376     }
377     RenderStyle* style = nonRendererStyle();
378     return style && style->display() == NONE;
379 }
380
381 } // namespace WebCore