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 "HTMLParserIdioms.h"
36#include "HTMLSelectElement.h"
37#include "NodeRenderStyle.h"
38#include "NodeRenderingContext.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/StdLibExtras.h>
46#include <wtf/Vector.h>
47#include <wtf/text/StringBuilder.h>
48
49namespace WebCore {
50
51using namespace HTMLNames;
52
53HTMLOptionElement::HTMLOptionElement(const QualifiedName& tagName, Document* document)
54    : HTMLElement(tagName, document)
55    , m_disabled(false)
56    , m_isSelected(false)
57{
58    ASSERT(hasTagName(optionTag));
59    setHasCustomStyleCallbacks();
60}
61
62PassRefPtr<HTMLOptionElement> HTMLOptionElement::create(Document* document)
63{
64    return adoptRef(new HTMLOptionElement(optionTag, document));
65}
66
67PassRefPtr<HTMLOptionElement> HTMLOptionElement::create(const QualifiedName& tagName, Document* document)
68{
69    return adoptRef(new HTMLOptionElement(tagName, document));
70}
71
72PassRefPtr<HTMLOptionElement> HTMLOptionElement::createForJSConstructor(Document* document, const String& data, const String& value,
73        bool defaultSelected, bool selected, ExceptionCode& ec)
74{
75    RefPtr<HTMLOptionElement> element = adoptRef(new HTMLOptionElement(optionTag, document));
76
77    RefPtr<Text> text = Text::create(document, data.isNull() ? "" : data);
78
79    ec = 0;
80    element->appendChild(text.release(), ec);
81    if (ec)
82        return 0;
83
84    if (!value.isNull())
85        element->setValue(value);
86    if (defaultSelected)
87        element->setAttribute(selectedAttr, emptyAtom);
88    element->setSelected(selected);
89
90    return element.release();
91}
92
93void HTMLOptionElement::attach(const AttachContext& context)
94{
95    HTMLElement::attach(context);
96    // If after attaching nothing called styleForRenderer() on this node we
97    // manually cache the value. This happens if our parent doesn't have a
98    // renderer like <optgroup> or if it doesn't allow children like <select>.
99    if (!m_style && parentNode()->renderStyle())
100        updateNonRenderStyle();
101}
102
103void HTMLOptionElement::detach(const AttachContext& context)
104{
105    m_style.clear();
106    HTMLElement::detach(context);
107}
108
109bool HTMLOptionElement::supportsFocus() const
110{
111    return HTMLElement::supportsFocus();
112}
113
114bool HTMLOptionElement::isFocusable() const
115{
116    // Option elements do not have a renderer so we check the renderStyle instead.
117    return supportsFocus() && renderStyle() && renderStyle()->display() != NONE;
118}
119
120String HTMLOptionElement::text() const
121{
122    Document* document = this->document();
123    String text;
124
125    // WinIE does not use the label attribute, so as a quirk, we ignore it.
126    if (!document->inQuirksMode())
127        text = fastGetAttribute(labelAttr);
128
129    // FIXME: The following treats an element with the label attribute set to
130    // the empty string the same as an element with no label attribute at all.
131    // Is that correct? If it is, then should the label function work the same way?
132    if (text.isEmpty())
133        text = collectOptionInnerText();
134
135    // FIXME: Is displayStringModifiedByEncoding helpful here?
136    // If it's correct here, then isn't it needed in the value and label functions too?
137    return document->displayStringModifiedByEncoding(text).stripWhiteSpace(isHTMLSpace).simplifyWhiteSpace(isHTMLSpace);
138}
139
140void HTMLOptionElement::setText(const String &text, ExceptionCode& ec)
141{
142    RefPtr<Node> protectFromMutationEvents(this);
143
144    // Changing the text causes a recalc of a select's items, which will reset the selected
145    // index to the first item if the select is single selection with a menu list. We attempt to
146    // preserve the selected item.
147    RefPtr<HTMLSelectElement> select = ownerSelectElement();
148    bool selectIsMenuList = select && select->usesMenuList();
149    int oldSelectedIndex = selectIsMenuList ? select->selectedIndex() : -1;
150
151    // Handle the common special case where there's exactly 1 child node, and it's a text node.
152    Node* child = firstChild();
153    if (child && child->isTextNode() && !child->nextSibling())
154        toText(child)->setData(text, ec);
155    else {
156        removeChildren();
157        appendChild(Text::create(document(), text), ec);
158    }
159
160    if (selectIsMenuList && select->selectedIndex() != oldSelectedIndex)
161        select->setSelectedIndex(oldSelectedIndex);
162}
163
164void HTMLOptionElement::accessKeyAction(bool)
165{
166    HTMLSelectElement* select = ownerSelectElement();
167    if (select)
168        select->accessKeySetSelectedIndex(index());
169}
170
171int HTMLOptionElement::index() const
172{
173    // It would be faster to cache the index, but harder to get it right in all cases.
174
175    HTMLSelectElement* selectElement = ownerSelectElement();
176    if (!selectElement)
177        return 0;
178
179    int optionIndex = 0;
180
181    const Vector<HTMLElement*>& items = selectElement->listItems();
182    size_t length = items.size();
183    for (size_t i = 0; i < length; ++i) {
184        if (!items[i]->hasTagName(optionTag))
185            continue;
186        if (items[i] == this)
187            return optionIndex;
188        ++optionIndex;
189    }
190
191    return 0;
192}
193
194void HTMLOptionElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
195{
196#if ENABLE(DATALIST_ELEMENT)
197    if (name == valueAttr) {
198        if (HTMLDataListElement* dataList = ownerDataListElement())
199            dataList->optionElementChildrenChanged();
200    } else
201#endif
202    if (name == disabledAttr) {
203        bool oldDisabled = m_disabled;
204        m_disabled = !value.isNull();
205        if (oldDisabled != m_disabled) {
206            didAffectSelector(AffectedSelectorDisabled | AffectedSelectorEnabled);
207            if (renderer() && renderer()->style()->hasAppearance())
208                renderer()->theme()->stateChanged(renderer(), EnabledState);
209        }
210    } else if (name == selectedAttr) {
211        // FIXME: This doesn't match what the HTML specification says.
212        // The specification implies that removing the selected attribute or
213        // changing the value of a selected attribute that is already present
214        // has no effect on whether the element is selected. Further, it seems
215        // that we need to do more than just set m_isSelected to select in that
216        // case; we'd need to do the other work from the setSelected function.
217        m_isSelected = !value.isNull();
218    } else
219        HTMLElement::parseAttribute(name, value);
220}
221
222String HTMLOptionElement::value() const
223{
224    const AtomicString& value = fastGetAttribute(valueAttr);
225    if (!value.isNull())
226        return value;
227    return collectOptionInnerText().stripWhiteSpace(isHTMLSpace).simplifyWhiteSpace(isHTMLSpace);
228}
229
230void HTMLOptionElement::setValue(const String& value)
231{
232    setAttribute(valueAttr, value);
233}
234
235bool HTMLOptionElement::selected()
236{
237    if (HTMLSelectElement* select = ownerSelectElement())
238        select->updateListItemSelectedStates();
239    return m_isSelected;
240}
241
242void HTMLOptionElement::setSelected(bool selected)
243{
244    if (m_isSelected == selected)
245        return;
246
247    setSelectedState(selected);
248
249    if (HTMLSelectElement* select = ownerSelectElement())
250        select->optionSelectionStateChanged(this, selected);
251}
252
253void HTMLOptionElement::setSelectedState(bool selected)
254{
255    if (m_isSelected == selected)
256        return;
257
258    m_isSelected = selected;
259    didAffectSelector(AffectedSelectorChecked);
260
261    if (HTMLSelectElement* select = ownerSelectElement())
262        select->invalidateSelectedItems();
263}
264
265void HTMLOptionElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
266{
267#if ENABLE(DATALIST_ELEMENT)
268    if (HTMLDataListElement* dataList = ownerDataListElement())
269        dataList->optionElementChildrenChanged();
270    else
271#endif
272    if (HTMLSelectElement* select = ownerSelectElement())
273        select->optionElementChildrenChanged();
274    HTMLElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
275}
276
277#if ENABLE(DATALIST_ELEMENT)
278HTMLDataListElement* HTMLOptionElement::ownerDataListElement() const
279{
280    for (ContainerNode* parent = parentNode(); parent ; parent = parent->parentNode()) {
281        if (parent->hasTagName(datalistTag))
282            return static_cast<HTMLDataListElement*>(parent);
283    }
284    return 0;
285}
286#endif
287
288HTMLSelectElement* HTMLOptionElement::ownerSelectElement() const
289{
290    ContainerNode* select = parentNode();
291    while (select && !select->hasTagName(selectTag))
292        select = select->parentNode();
293
294    if (!select)
295        return 0;
296
297    return toHTMLSelectElement(select);
298}
299
300String HTMLOptionElement::label() const
301{
302    const AtomicString& label = fastGetAttribute(labelAttr);
303    if (!label.isNull())
304        return label;
305    return collectOptionInnerText().stripWhiteSpace(isHTMLSpace).simplifyWhiteSpace(isHTMLSpace);
306}
307
308void HTMLOptionElement::setLabel(const String& label)
309{
310    setAttribute(labelAttr, label);
311}
312
313void HTMLOptionElement::updateNonRenderStyle()
314{
315    m_style = document()->ensureStyleResolver()->styleForElement(this);
316}
317
318RenderStyle* HTMLOptionElement::nonRendererStyle() const
319{
320    return m_style.get();
321}
322
323PassRefPtr<RenderStyle> HTMLOptionElement::customStyleForRenderer()
324{
325    // styleForRenderer is called whenever a new style should be associated
326    // with an Element so now is a good time to update our cached style.
327    updateNonRenderStyle();
328    return m_style;
329}
330
331void HTMLOptionElement::didRecalcStyle(StyleChange)
332{
333    // FIXME: This is nasty, we ask our owner select to repaint even if the new
334    // style is exactly the same.
335    if (HTMLSelectElement* select = ownerSelectElement()) {
336        if (RenderObject* renderer = select->renderer())
337            renderer->repaint();
338    }
339}
340
341String HTMLOptionElement::textIndentedToRespectGroupLabel() const
342{
343    ContainerNode* parent = parentNode();
344    if (parent && parent->hasTagName(optgroupTag))
345        return "    " + text();
346    return text();
347}
348
349bool HTMLOptionElement::isDisabledFormControl() const
350{
351    if (ownElementDisabled())
352        return true;
353
354    if (!parentNode() || !parentNode()->isHTMLElement())
355        return false;
356
357    HTMLElement* parentElement = static_cast<HTMLElement*>(parentNode());
358    return parentElement->hasTagName(optgroupTag) && parentElement->isDisabledFormControl();
359}
360
361Node::InsertionNotificationRequest HTMLOptionElement::insertedInto(ContainerNode* insertionPoint)
362{
363    if (HTMLSelectElement* select = ownerSelectElement()) {
364        select->setRecalcListItems();
365        // Do not call selected() since calling updateListItemSelectedStates()
366        // at this time won't do the right thing. (Why, exactly?)
367        // FIXME: Might be better to call this unconditionally, always passing m_isSelected,
368        // rather than only calling it if we are selected.
369        if (m_isSelected)
370            select->optionSelectionStateChanged(this, true);
371        select->scrollToSelection();
372    }
373
374    return HTMLElement::insertedInto(insertionPoint);
375}
376
377String HTMLOptionElement::collectOptionInnerText() const
378{
379    StringBuilder text;
380    for (Node* node = firstChild(); node; ) {
381        if (node->isTextNode())
382            text.append(node->nodeValue());
383        // Text nodes inside script elements are not part of the option text.
384        if (node->isElementNode() && toScriptElementIfPossible(toElement(node)))
385            node = NodeTraversal::nextSkippingChildren(node, this);
386        else
387            node = NodeTraversal::next(node, this);
388    }
389    return text.toString();
390}
391
392#ifndef NDEBUG
393
394HTMLOptionElement* toHTMLOptionElement(Node* node)
395{
396    ASSERT_WITH_SECURITY_IMPLICATION(!node || node->hasTagName(optionTag));
397    return static_cast<HTMLOptionElement*>(node);
398}
399
400const HTMLOptionElement* toHTMLOptionElement(const Node* node)
401{
402    ASSERT_WITH_SECURITY_IMPLICATION(!node || node->hasTagName(optionTag));
403    return static_cast<const HTMLOptionElement*>(node);
404}
405
406#endif
407
408} // namespace
409