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