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
47namespace WebCore {
48
49using namespace HTMLNames;
50
51HTMLOptionElement::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
60PassRefPtr<HTMLOptionElement> HTMLOptionElement::create(Document& document)
61{
62    return adoptRef(new HTMLOptionElement(optionTag, document));
63}
64
65PassRefPtr<HTMLOptionElement> HTMLOptionElement::create(const QualifiedName& tagName, Document& document)
66{
67    return adoptRef(new HTMLOptionElement(tagName, document));
68}
69
70PassRefPtr<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
91void 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(*parentNode()->renderStyle());
98}
99
100void HTMLOptionElement::willDetachRenderers()
101{
102    m_style.clear();
103}
104
105bool 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
111String 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
130void 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
154void HTMLOptionElement::accessKeyAction(bool)
155{
156    HTMLSelectElement* select = ownerSelectElement();
157    if (select)
158        select->accessKeySetSelectedIndex(index());
159}
160
161int 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
184void 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(), ControlStates::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
212String 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
220void HTMLOptionElement::setValue(const String& value)
221{
222    setAttribute(valueAttr, value);
223}
224
225bool HTMLOptionElement::selected()
226{
227    if (HTMLSelectElement* select = ownerSelectElement())
228        select->updateListItemSelectedStates();
229    return m_isSelected;
230}
231
232void 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
243void 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
255void 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)
268HTMLDataListElement* 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
278HTMLSelectElement* 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
290String 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
298void HTMLOptionElement::setLabel(const String& label)
299{
300    setAttribute(labelAttr, label);
301}
302
303void HTMLOptionElement::updateNonRenderStyle(RenderStyle& parentStyle)
304{
305    m_style = document().ensureStyleResolver().styleForElement(this, &parentStyle);
306}
307
308RenderStyle* HTMLOptionElement::nonRendererStyle() const
309{
310    return m_style.get();
311}
312
313PassRefPtr<RenderStyle> HTMLOptionElement::customStyleForRenderer(RenderStyle& parentStyle)
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(parentStyle);
318    return m_style;
319}
320
321void 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
331String HTMLOptionElement::textIndentedToRespectGroupLabel() const
332{
333    ContainerNode* parent = parentNode();
334    if (parent && isHTMLOptGroupElement(parent))
335        return "    " + text();
336    return text();
337}
338
339bool 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
351Node::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
367String 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
383