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 * Copyright (C) 2004, 2005, 2006, 2007, 2009 Apple Inc. All rights reserved.
6 *           (C) 2006 Alexey Proskuryakov (ap@nypop.com)
7 * Copyright (C) 2010 Google Inc. All rights reserved.
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 * Library General Public License for more details.
18 *
19 * You should have received a copy of the GNU Library General Public License
20 * along with this library; see the file COPYING.LIB.  If not, write to
21 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
23 *
24 */
25
26#include "config.h"
27#include "HTMLSelectElement.h"
28
29#include "AXObjectCache.h"
30#include "EventNames.h"
31#include "HTMLNames.h"
32#include "HTMLOptionElement.h"
33#include "HTMLOptionsCollection.h"
34#include "MappedAttribute.h"
35#include "RenderListBox.h"
36#include "RenderMenuList.h"
37#include "ScriptEventListener.h"
38
39using namespace std;
40
41namespace WebCore {
42
43using namespace HTMLNames;
44
45// Upper limit agreed upon with representatives of Opera and Mozilla.
46static const unsigned maxSelectItems = 10000;
47
48HTMLSelectElement::HTMLSelectElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form)
49    : HTMLFormControlElementWithState(tagName, document, form)
50{
51    ASSERT(hasTagName(selectTag) || hasTagName(keygenTag));
52}
53
54bool HTMLSelectElement::checkDTD(const Node* newChild)
55{
56    // Make sure to keep <optgroup> in sync with this.
57    return newChild->isTextNode() || newChild->hasTagName(optionTag) || newChild->hasTagName(optgroupTag) || newChild->hasTagName(hrTag) ||
58           newChild->hasTagName(scriptTag);
59}
60
61void HTMLSelectElement::recalcStyle(StyleChange change)
62{
63    HTMLFormControlElementWithState::recalcStyle(change);
64}
65
66const AtomicString& HTMLSelectElement::formControlType() const
67{
68    DEFINE_STATIC_LOCAL(const AtomicString, selectMultiple, ("select-multiple"));
69    DEFINE_STATIC_LOCAL(const AtomicString, selectOne, ("select-one"));
70    return m_data.multiple() ? selectMultiple : selectOne;
71}
72
73int HTMLSelectElement::selectedIndex() const
74{
75    return SelectElement::selectedIndex(m_data, this);
76}
77
78void HTMLSelectElement::deselectItems(HTMLOptionElement* excludeElement)
79{
80    SelectElement::deselectItems(m_data, this, excludeElement);
81}
82
83void HTMLSelectElement::setSelectedIndex(int optionIndex, bool deselect)
84{
85    SelectElement::setSelectedIndex(m_data, this, optionIndex, deselect, false, false);
86}
87
88void HTMLSelectElement::setSelectedIndexByUser(int optionIndex, bool deselect, bool fireOnChangeNow)
89{
90    SelectElement::setSelectedIndex(m_data, this, optionIndex, deselect, fireOnChangeNow, true);
91}
92
93int HTMLSelectElement::activeSelectionStartListIndex() const
94{
95    if (m_data.activeSelectionAnchorIndex() >= 0)
96        return m_data.activeSelectionAnchorIndex();
97    return optionToListIndex(selectedIndex());
98}
99
100int HTMLSelectElement::activeSelectionEndListIndex() const
101{
102    if (m_data.activeSelectionEndIndex() >= 0)
103        return m_data.activeSelectionEndIndex();
104    return SelectElement::lastSelectedListIndex(m_data, this);
105}
106
107unsigned HTMLSelectElement::length() const
108{
109    return SelectElement::optionCount(m_data, this);
110}
111
112void HTMLSelectElement::add(HTMLElement *element, HTMLElement *before, ExceptionCode& ec)
113{
114    RefPtr<HTMLElement> protectNewChild(element); // make sure the element is ref'd and deref'd so we don't leak it
115
116    if (!element || !(element->hasLocalName(optionTag) || element->hasLocalName(hrTag)))
117        return;
118
119    insertBefore(element, before, ec);
120}
121
122void HTMLSelectElement::remove(int index)
123{
124    int listIndex = optionToListIndex(index);
125    if (listIndex < 0)
126        return;
127
128    Element* item = listItems()[listIndex];
129    ASSERT(item->parentNode());
130    ExceptionCode ec;
131    item->parentNode()->removeChild(item, ec);
132}
133
134String HTMLSelectElement::value()
135{
136    const Vector<Element*>& items = listItems();
137    for (unsigned i = 0; i < items.size(); i++) {
138        if (items[i]->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(items[i])->selected())
139            return static_cast<HTMLOptionElement*>(items[i])->value();
140    }
141    return "";
142}
143
144void HTMLSelectElement::setValue(const String &value)
145{
146    if (value.isNull())
147        return;
148    // find the option with value() matching the given parameter
149    // and make it the current selection.
150    const Vector<Element*>& items = listItems();
151    unsigned optionIndex = 0;
152    for (unsigned i = 0; i < items.size(); i++) {
153        if (items[i]->hasLocalName(optionTag)) {
154            if (static_cast<HTMLOptionElement*>(items[i])->value() == value) {
155                setSelectedIndex(optionIndex, true);
156                return;
157            }
158            optionIndex++;
159        }
160    }
161}
162
163bool HTMLSelectElement::saveFormControlState(String& value) const
164{
165    return SelectElement::saveFormControlState(m_data, this, value);
166}
167
168void HTMLSelectElement::restoreFormControlState(const String& state)
169{
170    SelectElement::restoreFormControlState(m_data, this, state);
171}
172
173void HTMLSelectElement::parseMappedAttribute(MappedAttribute* attr)
174{
175    bool oldUsesMenuList = m_data.usesMenuList();
176    if (attr->name() == sizeAttr) {
177        int oldSize = m_data.size();
178        // Set the attribute value to a number.
179        // This is important since the style rules for this attribute can determine the appearance property.
180        int size = attr->value().toInt();
181        String attrSize = String::number(size);
182        if (attrSize != attr->value())
183            attr->setValue(attrSize);
184
185        m_data.setSize(max(size, 1));
186        if ((oldUsesMenuList != m_data.usesMenuList() || (!oldUsesMenuList && m_data.size() != oldSize)) && attached()) {
187            detach();
188            attach();
189            setRecalcListItems();
190        }
191    } else if (attr->name() == multipleAttr)
192        SelectElement::parseMultipleAttribute(m_data, this, attr);
193    else if (attr->name() == accesskeyAttr) {
194        // FIXME: ignore for the moment
195    } else if (attr->name() == alignAttr) {
196        // Don't map 'align' attribute.  This matches what Firefox, Opera and IE do.
197        // See http://bugs.webkit.org/show_bug.cgi?id=12072
198    } else if (attr->name() == onfocusAttr) {
199        setAttributeEventListener(eventNames().focusEvent, createAttributeEventListener(this, attr));
200    } else if (attr->name() == onblurAttr) {
201        setAttributeEventListener(eventNames().blurEvent, createAttributeEventListener(this, attr));
202    } else if (attr->name() == onchangeAttr) {
203        setAttributeEventListener(eventNames().changeEvent, createAttributeEventListener(this, attr));
204    } else
205        HTMLFormControlElementWithState::parseMappedAttribute(attr);
206}
207
208bool HTMLSelectElement::isKeyboardFocusable(KeyboardEvent* event) const
209{
210    if (renderer())
211        return isFocusable();
212    return HTMLFormControlElementWithState::isKeyboardFocusable(event);
213}
214
215bool HTMLSelectElement::isMouseFocusable() const
216{
217    if (renderer())
218        return isFocusable();
219    return HTMLFormControlElementWithState::isMouseFocusable();
220}
221
222bool HTMLSelectElement::canSelectAll() const
223{
224    return !m_data.usesMenuList();
225}
226
227void HTMLSelectElement::selectAll()
228{
229    SelectElement::selectAll(m_data, this);
230}
231
232RenderObject* HTMLSelectElement::createRenderer(RenderArena* arena, RenderStyle*)
233{
234    if (m_data.usesMenuList())
235        return new (arena) RenderMenuList(this);
236    return new (arena) RenderListBox(this);
237}
238
239bool HTMLSelectElement::appendFormData(FormDataList& list, bool)
240{
241    return SelectElement::appendFormData(m_data, this, list);
242}
243
244int HTMLSelectElement::optionToListIndex(int optionIndex) const
245{
246    return SelectElement::optionToListIndex(m_data, this, optionIndex);
247}
248
249int HTMLSelectElement::listToOptionIndex(int listIndex) const
250{
251    return SelectElement::listToOptionIndex(m_data, this, listIndex);
252}
253
254PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options()
255{
256    return HTMLOptionsCollection::create(this);
257}
258
259void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const
260{
261    SelectElement::recalcListItems(const_cast<SelectElementData&>(m_data), this, updateSelectedStates);
262}
263
264void HTMLSelectElement::recalcListItemsIfNeeded()
265{
266    if (m_data.shouldRecalcListItems())
267        recalcListItems();
268}
269
270void HTMLSelectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
271{
272    setRecalcListItems();
273    HTMLFormControlElementWithState::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
274
275    if (AXObjectCache::accessibilityEnabled() && renderer())
276        renderer()->document()->axObjectCache()->childrenChanged(renderer());
277}
278
279void HTMLSelectElement::setRecalcListItems()
280{
281    SelectElement::setRecalcListItems(m_data, this);
282
283    if (!inDocument())
284        m_collectionInfo.reset();
285}
286
287void HTMLSelectElement::reset()
288{
289    SelectElement::reset(m_data, this);
290}
291
292void HTMLSelectElement::dispatchFocusEvent()
293{
294    SelectElement::dispatchFocusEvent(m_data, this);
295    HTMLFormControlElementWithState::dispatchFocusEvent();
296}
297
298void HTMLSelectElement::dispatchBlurEvent()
299{
300    SelectElement::dispatchBlurEvent(m_data, this);
301    HTMLFormControlElementWithState::dispatchBlurEvent();
302}
303
304void HTMLSelectElement::defaultEventHandler(Event* event)
305{
306    SelectElement::defaultEventHandler(m_data, this, event, form());
307    if (event->defaultHandled())
308        return;
309    HTMLFormControlElementWithState::defaultEventHandler(event);
310}
311
312void HTMLSelectElement::setActiveSelectionAnchorIndex(int index)
313{
314    SelectElement::setActiveSelectionAnchorIndex(m_data, this, index);
315}
316
317void HTMLSelectElement::setActiveSelectionEndIndex(int index)
318{
319    SelectElement::setActiveSelectionEndIndex(m_data, index);
320}
321
322void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions)
323{
324    SelectElement::updateListBoxSelection(m_data, this, deselectOtherOptions);
325}
326
327void HTMLSelectElement::menuListOnChange()
328{
329    SelectElement::menuListOnChange(m_data, this);
330}
331
332void HTMLSelectElement::listBoxOnChange()
333{
334    SelectElement::listBoxOnChange(m_data, this);
335}
336
337void HTMLSelectElement::saveLastSelection()
338{
339    SelectElement::saveLastSelection(m_data, this);
340}
341
342void HTMLSelectElement::accessKeyAction(bool sendToAnyElement)
343{
344    focus();
345    dispatchSimulatedClick(0, sendToAnyElement);
346}
347
348void HTMLSelectElement::accessKeySetSelectedIndex(int index)
349{
350    SelectElement::accessKeySetSelectedIndex(m_data, this, index);
351}
352
353void HTMLSelectElement::setMultiple(bool multiple)
354{
355    setAttribute(multipleAttr, multiple ? "" : 0);
356}
357
358void HTMLSelectElement::setSize(int size)
359{
360    setAttribute(sizeAttr, String::number(size));
361}
362
363Node* HTMLSelectElement::namedItem(const AtomicString& name)
364{
365    return options()->namedItem(name);
366}
367
368Node* HTMLSelectElement::item(unsigned index)
369{
370    return options()->item(index);
371}
372
373void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionCode& ec)
374{
375    ec = 0;
376    if (index > maxSelectItems - 1)
377        index = maxSelectItems - 1;
378    int diff = index  - length();
379    HTMLElement* before = 0;
380    // out of array bounds ? first insert empty dummies
381    if (diff > 0) {
382        setLength(index, ec);
383        // replace an existing entry ?
384    } else if (diff < 0) {
385        before = static_cast<HTMLElement*>(options()->item(index+1));
386        remove(index);
387    }
388    // finally add the new element
389    if (!ec) {
390        add(option, before, ec);
391        if (diff >= 0 && option->selected())
392            setSelectedIndex(index, !m_data.multiple());
393    }
394}
395
396void HTMLSelectElement::setLength(unsigned newLen, ExceptionCode& ec)
397{
398    ec = 0;
399    if (newLen > maxSelectItems)
400        newLen = maxSelectItems;
401    int diff = length() - newLen;
402
403    if (diff < 0) { // add dummy elements
404        do {
405            RefPtr<Element> option = document()->createElement(optionTag, false);
406            ASSERT(option);
407            add(static_cast<HTMLElement*>(option.get()), 0, ec);
408            if (ec)
409                break;
410        } while (++diff);
411    } else {
412        const Vector<Element*>& items = listItems();
413
414        size_t optionIndex = 0;
415        for (size_t listIndex = 0; listIndex < items.size(); listIndex++) {
416            if (items[listIndex]->hasLocalName(optionTag) && optionIndex++ >= newLen) {
417                Element *item = items[listIndex];
418                ASSERT(item->parentNode());
419                item->parentNode()->removeChild(item, ec);
420            }
421        }
422    }
423}
424
425void HTMLSelectElement::scrollToSelection()
426{
427    SelectElement::scrollToSelection(m_data, this);
428}
429
430void HTMLSelectElement::insertedIntoTree(bool deep)
431{
432    SelectElement::insertedIntoTree(m_data, this);
433    HTMLFormControlElementWithState::insertedIntoTree(deep);
434}
435
436} // namespace
437