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