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