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