Page::theme() should return a reference.
[WebKit-https.git] / Source / WebCore / 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 "HTMLOptionElement.h"
29
30 #include "Attribute.h"
31 #include "Document.h"
32 #include "ExceptionCode.h"
33 #include "HTMLDataListElement.h"
34 #include "HTMLNames.h"
35 #include "HTMLOptGroupElement.h"
36 #include "HTMLParserIdioms.h"
37 #include "HTMLSelectElement.h"
38 #include "NodeRenderStyle.h"
39 #include "NodeTraversal.h"
40 #include "RenderMenuList.h"
41 #include "RenderTheme.h"
42 #include "ScriptElement.h"
43 #include "StyleResolver.h"
44 #include "Text.h"
45 #include <wtf/Ref.h>
46
47 namespace WebCore {
48
49 using namespace HTMLNames;
50
51 HTMLOptionElement::HTMLOptionElement(const QualifiedName& tagName, Document& document)
52     : HTMLElement(tagName, document)
53     , m_disabled(false)
54     , m_isSelected(false)
55 {
56     ASSERT(hasTagName(optionTag));
57     setHasCustomStyleResolveCallbacks();
58 }
59
60 PassRefPtr<HTMLOptionElement> HTMLOptionElement::create(Document& document)
61 {
62     return adoptRef(new HTMLOptionElement(optionTag, document));
63 }
64
65 PassRefPtr<HTMLOptionElement> HTMLOptionElement::create(const QualifiedName& tagName, Document& document)
66 {
67     return adoptRef(new HTMLOptionElement(tagName, document));
68 }
69
70 PassRefPtr<HTMLOptionElement> HTMLOptionElement::createForJSConstructor(Document& document, const String& data, const String& value,
71         bool defaultSelected, bool selected, ExceptionCode& ec)
72 {
73     RefPtr<HTMLOptionElement> element = adoptRef(new HTMLOptionElement(optionTag, document));
74
75     RefPtr<Text> text = Text::create(document, data.isNull() ? "" : data);
76
77     ec = 0;
78     element->appendChild(text.release(), ec);
79     if (ec)
80         return 0;
81
82     if (!value.isNull())
83         element->setValue(value);
84     if (defaultSelected)
85         element->setAttribute(selectedAttr, emptyAtom);
86     element->setSelected(selected);
87
88     return element.release();
89 }
90
91 void HTMLOptionElement::didAttachRenderers()
92 {
93     // If after attaching nothing called styleForRenderer() on this node we
94     // manually cache the value. This happens if our parent doesn't have a
95     // renderer like <optgroup> or if it doesn't allow children like <select>.
96     if (!m_style && parentNode()->renderStyle())
97         updateNonRenderStyle();
98 }
99
100 void HTMLOptionElement::willDetachRenderers()
101 {
102     m_style.clear();
103 }
104
105 bool HTMLOptionElement::isFocusable() const
106 {
107     // Option elements do not have a renderer so we check the renderStyle instead.
108     return supportsFocus() && renderStyle() && renderStyle()->display() != NONE;
109 }
110
111 String HTMLOptionElement::text() const
112 {
113     String text;
114
115     // WinIE does not use the label attribute, so as a quirk, we ignore it.
116     if (!document().inQuirksMode())
117         text = fastGetAttribute(labelAttr);
118
119     // FIXME: The following treats an element with the label attribute set to
120     // the empty string the same as an element with no label attribute at all.
121     // Is that correct? If it is, then should the label function work the same way?
122     if (text.isEmpty())
123         text = collectOptionInnerText();
124
125     // FIXME: Is displayStringModifiedByEncoding helpful here?
126     // If it's correct here, then isn't it needed in the value and label functions too?
127     return document().displayStringModifiedByEncoding(text).stripWhiteSpace(isHTMLSpace).simplifyWhiteSpace(isHTMLSpace);
128 }
129
130 void HTMLOptionElement::setText(const String &text, ExceptionCode& ec)
131 {
132     Ref<HTMLOptionElement> protectFromMutationEvents(*this);
133
134     // Changing the text causes a recalc of a select's items, which will reset the selected
135     // index to the first item if the select is single selection with a menu list. We attempt to
136     // preserve the selected item.
137     RefPtr<HTMLSelectElement> select = ownerSelectElement();
138     bool selectIsMenuList = select && select->usesMenuList();
139     int oldSelectedIndex = selectIsMenuList ? select->selectedIndex() : -1;
140
141     // Handle the common special case where there's exactly 1 child node, and it's a text node.
142     Node* child = firstChild();
143     if (child && child->isTextNode() && !child->nextSibling())
144         toText(child)->setData(text, ec);
145     else {
146         removeChildren();
147         appendChild(Text::create(document(), text), ec);
148     }
149     
150     if (selectIsMenuList && select->selectedIndex() != oldSelectedIndex)
151         select->setSelectedIndex(oldSelectedIndex);
152 }
153
154 void HTMLOptionElement::accessKeyAction(bool)
155 {
156     HTMLSelectElement* select = ownerSelectElement();
157     if (select)
158         select->accessKeySetSelectedIndex(index());
159 }
160
161 int HTMLOptionElement::index() const
162 {
163     // It would be faster to cache the index, but harder to get it right in all cases.
164
165     HTMLSelectElement* selectElement = ownerSelectElement();
166     if (!selectElement)
167         return 0;
168
169     int optionIndex = 0;
170
171     const Vector<HTMLElement*>& items = selectElement->listItems();
172     size_t length = items.size();
173     for (size_t i = 0; i < length; ++i) {
174         if (!isHTMLOptionElement(items[i]))
175             continue;
176         if (items[i] == this)
177             return optionIndex;
178         ++optionIndex;
179     }
180
181     return 0;
182 }
183
184 void HTMLOptionElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
185 {
186 #if ENABLE(DATALIST_ELEMENT)
187     if (name == valueAttr) {
188         if (HTMLDataListElement* dataList = ownerDataListElement())
189             dataList->optionElementChildrenChanged();
190     } else
191 #endif
192     if (name == disabledAttr) {
193         bool oldDisabled = m_disabled;
194         m_disabled = !value.isNull();
195         if (oldDisabled != m_disabled) {
196             didAffectSelector(AffectedSelectorDisabled | AffectedSelectorEnabled);
197             if (renderer() && renderer()->style().hasAppearance())
198                 renderer()->theme().stateChanged(renderer(), EnabledState);
199         }
200     } else if (name == selectedAttr) {
201         // FIXME: This doesn't match what the HTML specification says.
202         // The specification implies that removing the selected attribute or
203         // changing the value of a selected attribute that is already present
204         // has no effect on whether the element is selected. Further, it seems
205         // that we need to do more than just set m_isSelected to select in that
206         // case; we'd need to do the other work from the setSelected function.
207         m_isSelected = !value.isNull();
208     } else
209         HTMLElement::parseAttribute(name, value);
210 }
211
212 String HTMLOptionElement::value() const
213 {
214     const AtomicString& value = fastGetAttribute(valueAttr);
215     if (!value.isNull())
216         return value;
217     return collectOptionInnerText().stripWhiteSpace(isHTMLSpace).simplifyWhiteSpace(isHTMLSpace);
218 }
219
220 void HTMLOptionElement::setValue(const String& value)
221 {
222     setAttribute(valueAttr, value);
223 }
224
225 bool HTMLOptionElement::selected()
226 {
227     if (HTMLSelectElement* select = ownerSelectElement())
228         select->updateListItemSelectedStates();
229     return m_isSelected;
230 }
231
232 void HTMLOptionElement::setSelected(bool selected)
233 {
234     if (m_isSelected == selected)
235         return;
236
237     setSelectedState(selected);
238
239     if (HTMLSelectElement* select = ownerSelectElement())
240         select->optionSelectionStateChanged(this, selected);
241 }
242
243 void HTMLOptionElement::setSelectedState(bool selected)
244 {
245     if (m_isSelected == selected)
246         return;
247
248     m_isSelected = selected;
249     didAffectSelector(AffectedSelectorChecked);
250
251     if (HTMLSelectElement* select = ownerSelectElement())
252         select->invalidateSelectedItems();
253 }
254
255 void HTMLOptionElement::childrenChanged(const ChildChange& change)
256 {
257 #if ENABLE(DATALIST_ELEMENT)
258     if (HTMLDataListElement* dataList = ownerDataListElement())
259         dataList->optionElementChildrenChanged();
260     else
261 #endif
262     if (HTMLSelectElement* select = ownerSelectElement())
263         select->optionElementChildrenChanged();
264     HTMLElement::childrenChanged(change);
265 }
266
267 #if ENABLE(DATALIST_ELEMENT)
268 HTMLDataListElement* HTMLOptionElement::ownerDataListElement() const
269 {
270     for (ContainerNode* parent = parentNode(); parent ; parent = parent->parentNode()) {
271         if (parent->hasTagName(datalistTag))
272             return toHTMLDataListElement(parent);
273     }
274     return 0;
275 }
276 #endif
277
278 HTMLSelectElement* HTMLOptionElement::ownerSelectElement() const
279 {
280     ContainerNode* select = parentNode();
281     while (select && !select->hasTagName(selectTag))
282         select = select->parentNode();
283
284     if (!select)
285         return 0;
286
287     return toHTMLSelectElement(select);
288 }
289
290 String HTMLOptionElement::label() const
291 {
292     const AtomicString& label = fastGetAttribute(labelAttr);
293     if (!label.isNull())
294         return label; 
295     return collectOptionInnerText().stripWhiteSpace(isHTMLSpace).simplifyWhiteSpace(isHTMLSpace);
296 }
297
298 void HTMLOptionElement::setLabel(const String& label)
299 {
300     setAttribute(labelAttr, label);
301 }
302
303 void HTMLOptionElement::updateNonRenderStyle()
304 {
305     m_style = document().ensureStyleResolver().styleForElement(this);
306 }
307
308 RenderStyle* HTMLOptionElement::nonRendererStyle() const
309 {
310     return m_style.get();
311 }
312
313 PassRefPtr<RenderStyle> HTMLOptionElement::customStyleForRenderer()
314 {
315     // styleForRenderer is called whenever a new style should be associated
316     // with an Element so now is a good time to update our cached style.
317     updateNonRenderStyle();
318     return m_style;
319 }
320
321 void HTMLOptionElement::didRecalcStyle(Style::Change)
322 {
323     // FIXME: This is nasty, we ask our owner select to repaint even if the new
324     // style is exactly the same.
325     if (auto select = ownerSelectElement()) {
326         if (auto renderer = select->renderer())
327             renderer->repaint();
328     }
329 }
330
331 String HTMLOptionElement::textIndentedToRespectGroupLabel() const
332 {
333     ContainerNode* parent = parentNode();
334     if (parent && isHTMLOptGroupElement(parent))
335         return "    " + text();
336     return text();
337 }
338
339 bool HTMLOptionElement::isDisabledFormControl() const
340 {
341     if (ownElementDisabled())
342         return true;
343
344     if (!parentNode() || !parentNode()->isHTMLElement())
345         return false;
346
347     HTMLElement& parentElement = toHTMLElement(*parentNode());
348     return isHTMLOptGroupElement(parentElement) && parentElement.isDisabledFormControl();
349 }
350
351 Node::InsertionNotificationRequest HTMLOptionElement::insertedInto(ContainerNode& insertionPoint)
352 {
353     if (HTMLSelectElement* select = ownerSelectElement()) {
354         select->setRecalcListItems();
355         // Do not call selected() since calling updateListItemSelectedStates()
356         // at this time won't do the right thing. (Why, exactly?)
357         // FIXME: Might be better to call this unconditionally, always passing m_isSelected,
358         // rather than only calling it if we are selected.
359         if (m_isSelected)
360             select->optionSelectionStateChanged(this, true);
361         select->scrollToSelection();
362     }
363
364     return HTMLElement::insertedInto(insertionPoint);
365 }
366
367 String HTMLOptionElement::collectOptionInnerText() const
368 {
369     StringBuilder text;
370     for (Node* node = firstChild(); node; ) {
371         if (node->isTextNode())
372             text.append(node->nodeValue());
373         // Text nodes inside script elements are not part of the option text.
374         if (node->isElementNode() && toScriptElementIfPossible(toElement(node)))
375             node = NodeTraversal::nextSkippingChildren(node, this);
376         else
377             node = NodeTraversal::next(node, this);
378     }
379     return text.toString();
380 }
381
382 } // namespace