Use is<>() / downcast<>() for Element
[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 bool HTMLOptionElement::isFocusable() const
92 {
93     if (!supportsFocus())
94         return false;
95     // Option elements do not have a renderer.
96     auto* style = const_cast<HTMLOptionElement&>(*this).computedStyle();
97     return style && style->display() != NONE;
98 }
99
100 String HTMLOptionElement::text() const
101 {
102     String text;
103
104     // WinIE does not use the label attribute, so as a quirk, we ignore it.
105     if (!document().inQuirksMode())
106         text = fastGetAttribute(labelAttr);
107
108     // FIXME: The following treats an element with the label attribute set to
109     // the empty string the same as an element with no label attribute at all.
110     // Is that correct? If it is, then should the label function work the same way?
111     if (text.isEmpty())
112         text = collectOptionInnerText();
113
114     // FIXME: Is displayStringModifiedByEncoding helpful here?
115     // If it's correct here, then isn't it needed in the value and label functions too?
116     return document().displayStringModifiedByEncoding(text).stripWhiteSpace(isHTMLSpace).simplifyWhiteSpace(isHTMLSpace);
117 }
118
119 void HTMLOptionElement::setText(const String &text, ExceptionCode& ec)
120 {
121     Ref<HTMLOptionElement> protectFromMutationEvents(*this);
122
123     // Changing the text causes a recalc of a select's items, which will reset the selected
124     // index to the first item if the select is single selection with a menu list. We attempt to
125     // preserve the selected item.
126     RefPtr<HTMLSelectElement> select = ownerSelectElement();
127     bool selectIsMenuList = select && select->usesMenuList();
128     int oldSelectedIndex = selectIsMenuList ? select->selectedIndex() : -1;
129
130     // Handle the common special case where there's exactly 1 child node, and it's a text node.
131     Node* child = firstChild();
132     if (child && is<Text>(child) && !child->nextSibling())
133         downcast<Text>(*child).setData(text, ec);
134     else {
135         removeChildren();
136         appendChild(Text::create(document(), text), ec);
137     }
138     
139     if (selectIsMenuList && select->selectedIndex() != oldSelectedIndex)
140         select->setSelectedIndex(oldSelectedIndex);
141 }
142
143 void HTMLOptionElement::accessKeyAction(bool)
144 {
145     HTMLSelectElement* select = ownerSelectElement();
146     if (select)
147         select->accessKeySetSelectedIndex(index());
148 }
149
150 int HTMLOptionElement::index() const
151 {
152     // It would be faster to cache the index, but harder to get it right in all cases.
153
154     HTMLSelectElement* selectElement = ownerSelectElement();
155     if (!selectElement)
156         return 0;
157
158     int optionIndex = 0;
159
160     const Vector<HTMLElement*>& items = selectElement->listItems();
161     size_t length = items.size();
162     for (size_t i = 0; i < length; ++i) {
163         if (!is<HTMLOptionElement>(items[i]))
164             continue;
165         if (items[i] == this)
166             return optionIndex;
167         ++optionIndex;
168     }
169
170     return 0;
171 }
172
173 void HTMLOptionElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
174 {
175 #if ENABLE(DATALIST_ELEMENT)
176     if (name == valueAttr) {
177         if (HTMLDataListElement* dataList = ownerDataListElement())
178             dataList->optionElementChildrenChanged();
179     } else
180 #endif
181     if (name == disabledAttr) {
182         bool oldDisabled = m_disabled;
183         m_disabled = !value.isNull();
184         if (oldDisabled != m_disabled) {
185             didAffectSelector(AffectedSelectorDisabled | AffectedSelectorEnabled);
186             if (renderer() && renderer()->style().hasAppearance())
187                 renderer()->theme().stateChanged(*renderer(), ControlStates::EnabledState);
188         }
189     } else if (name == selectedAttr) {
190         // FIXME: This doesn't match what the HTML specification says.
191         // The specification implies that removing the selected attribute or
192         // changing the value of a selected attribute that is already present
193         // has no effect on whether the element is selected. Further, it seems
194         // that we need to do more than just set m_isSelected to select in that
195         // case; we'd need to do the other work from the setSelected function.
196         m_isSelected = !value.isNull();
197     } else
198         HTMLElement::parseAttribute(name, value);
199 }
200
201 String HTMLOptionElement::value() const
202 {
203     const AtomicString& value = fastGetAttribute(valueAttr);
204     if (!value.isNull())
205         return value;
206     return collectOptionInnerText().stripWhiteSpace(isHTMLSpace).simplifyWhiteSpace(isHTMLSpace);
207 }
208
209 void HTMLOptionElement::setValue(const String& value)
210 {
211     setAttribute(valueAttr, value);
212 }
213
214 bool HTMLOptionElement::selected()
215 {
216     if (HTMLSelectElement* select = ownerSelectElement())
217         select->updateListItemSelectedStates();
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(const ChildChange& change)
245 {
246 #if ENABLE(DATALIST_ELEMENT)
247     if (HTMLDataListElement* dataList = ownerDataListElement())
248         dataList->optionElementChildrenChanged();
249     else
250 #endif
251     if (HTMLSelectElement* select = ownerSelectElement())
252         select->optionElementChildrenChanged();
253     HTMLElement::childrenChanged(change);
254 }
255
256 #if ENABLE(DATALIST_ELEMENT)
257 HTMLDataListElement* HTMLOptionElement::ownerDataListElement() const
258 {
259     for (ContainerNode* parent = parentNode(); parent ; parent = parent->parentNode()) {
260         if (is<HTMLDataListElement>(parent))
261             return downcast<HTMLDataListElement>(parent);
262     }
263     return nullptr;
264 }
265 #endif
266
267 HTMLSelectElement* HTMLOptionElement::ownerSelectElement() const
268 {
269     ContainerNode* select = parentNode();
270     while (select && !is<HTMLSelectElement>(select))
271         select = select->parentNode();
272
273     if (!select)
274         return nullptr;
275
276     return downcast<HTMLSelectElement>(select);
277 }
278
279 String HTMLOptionElement::label() const
280 {
281     const AtomicString& label = fastGetAttribute(labelAttr);
282     if (!label.isNull())
283         return label; 
284     return collectOptionInnerText().stripWhiteSpace(isHTMLSpace).simplifyWhiteSpace(isHTMLSpace);
285 }
286
287 void HTMLOptionElement::setLabel(const String& label)
288 {
289     setAttribute(labelAttr, label);
290 }
291
292 void HTMLOptionElement::willResetComputedStyle()
293 {
294     // FIXME: This is nasty, we ask our owner select to repaint even if the new
295     // style is exactly the same.
296     if (auto select = ownerSelectElement()) {
297         if (auto renderer = select->renderer())
298             renderer->repaint();
299     }
300 }
301
302 String HTMLOptionElement::textIndentedToRespectGroupLabel() const
303 {
304     ContainerNode* parent = parentNode();
305     if (parent && is<HTMLOptGroupElement>(parent))
306         return "    " + text();
307     return text();
308 }
309
310 bool HTMLOptionElement::isDisabledFormControl() const
311 {
312     if (ownElementDisabled())
313         return true;
314
315     if (!parentNode() || !is<HTMLOptGroupElement>(parentNode()))
316         return false;
317
318     return downcast<HTMLOptGroupElement>(*parentNode()).isDisabledFormControl();
319 }
320
321 Node::InsertionNotificationRequest HTMLOptionElement::insertedInto(ContainerNode& insertionPoint)
322 {
323     if (HTMLSelectElement* select = ownerSelectElement()) {
324         select->setRecalcListItems();
325         // Do not call selected() since calling updateListItemSelectedStates()
326         // at this time won't do the right thing. (Why, exactly?)
327         // FIXME: Might be better to call this unconditionally, always passing m_isSelected,
328         // rather than only calling it if we are selected.
329         if (m_isSelected)
330             select->optionSelectionStateChanged(this, true);
331         select->scrollToSelection();
332     }
333
334     return HTMLElement::insertedInto(insertionPoint);
335 }
336
337 String HTMLOptionElement::collectOptionInnerText() const
338 {
339     StringBuilder text;
340     for (Node* node = firstChild(); node; ) {
341         if (is<Text>(node))
342             text.append(node->nodeValue());
343         // Text nodes inside script elements are not part of the option text.
344         if (is<Element>(node) && toScriptElementIfPossible(downcast<Element>(node)))
345             node = NodeTraversal::nextSkippingChildren(node, this);
346         else
347             node = NodeTraversal::next(node, this);
348     }
349     return text.toString();
350 }
351
352 } // namespace