1/*
2 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
3 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
4 *           (C) 1999 Antti Koivisto (koivisto@kde.org)
5 *           (C) 2001 Dirk Mueller (mueller@kde.org)
6 * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010 Apple Inc. All rights reserved.
7 *           (C) 2006 Alexey Proskuryakov (ap@nypop.com)
8 * Copyright (C) 2010 Google 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 "HTMLSelectElement.h"
29
30#include "AXObjectCache.h"
31#include "Attribute.h"
32#include "EventNames.h"
33#include "HTMLNames.h"
34#include "HTMLOptionElement.h"
35#include "HTMLOptionsCollection.h"
36#include "RenderListBox.h"
37#include "RenderMenuList.h"
38#include "ScriptEventListener.h"
39
40using namespace std;
41
42namespace WebCore {
43
44using namespace HTMLNames;
45
46// Upper limit agreed upon with representatives of Opera and Mozilla.
47static const unsigned maxSelectItems = 10000;
48
49HTMLSelectElement::HTMLSelectElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form)
50    : HTMLFormControlElementWithState(tagName, document, form)
51{
52    ASSERT(hasTagName(selectTag));
53}
54
55PassRefPtr<HTMLSelectElement> HTMLSelectElement::create(const QualifiedName& tagName, Document* document, HTMLFormElement* form)
56{
57    ASSERT(tagName.matches(selectTag));
58    return adoptRef(new HTMLSelectElement(tagName, document, form));
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    setNeedsValidityCheck();
82}
83
84void HTMLSelectElement::setSelectedIndex(int optionIndex, bool deselect)
85{
86    SelectElement::setSelectedIndex(m_data, this, optionIndex, deselect, false, false);
87    setNeedsValidityCheck();
88}
89
90void HTMLSelectElement::setSelectedIndexByUser(int optionIndex, bool deselect, bool fireOnChangeNow, bool allowMultipleSelection)
91{
92    // List box selects can fire onchange events through user interaction, such as
93    // mousedown events. This allows that same behavior programmatically.
94    if (!m_data.usesMenuList()) {
95        updateSelectedState(m_data, this, optionIndex, allowMultipleSelection, false);
96        setNeedsValidityCheck();
97        if (fireOnChangeNow)
98            listBoxOnChange();
99        return;
100    }
101
102    // Bail out if this index is already the selected one, to avoid running unnecessary JavaScript that can mess up
103    // autofill, when there is no actual change (see https://bugs.webkit.org/show_bug.cgi?id=35256 and rdar://7467917 ).
104    // Perhaps this logic could be moved into SelectElement, but some callers of SelectElement::setSelectedIndex()
105    // seem to expect it to fire its change event even when the index was already selected.
106    if (optionIndex == selectedIndex())
107        return;
108
109    SelectElement::setSelectedIndex(m_data, this, optionIndex, deselect, fireOnChangeNow, true);
110    setNeedsValidityCheck();
111}
112
113bool HTMLSelectElement::hasPlaceholderLabelOption() const
114{
115    // The select element has no placeholder label option if it has an attribute "multiple" specified or a display size of non-1.
116    //
117    // The condition "size() > 1" is actually not compliant with the HTML5 spec as of Dec 3, 2010. "size() != 1" is correct.
118    // Using "size() > 1" here because size() may be 0 in WebKit.
119    // See the discussion at https://bugs.webkit.org/show_bug.cgi?id=43887
120    //
121    // "0 size()" happens when an attribute "size" is absent or an invalid size attribute is specified.
122    // In this case, the display size should be assumed as the default.
123    // The default display size is 1 for non-multiple select elements, and 4 for multiple select elements.
124    //
125    // Finally, if size() == 0 and non-multiple, the display size can be assumed as 1.
126    if (multiple() || size() > 1)
127        return false;
128
129    int listIndex = optionToListIndex(0);
130    ASSERT(listIndex >= 0);
131    if (listIndex < 0)
132        return false;
133    HTMLOptionElement* option = static_cast<HTMLOptionElement*>(listItems()[listIndex]);
134    return !option->disabled() && !listIndex && option->value().isEmpty();
135}
136
137bool HTMLSelectElement::valueMissing() const
138{
139    if (!isRequiredFormControl())
140        return false;
141
142    int firstSelectionIndex = selectedIndex();
143
144    // If a non-placeholer label option is selected (firstSelectionIndex > 0), it's not value-missing.
145    return firstSelectionIndex < 0 || (!firstSelectionIndex && hasPlaceholderLabelOption());
146}
147
148void HTMLSelectElement::listBoxSelectItem(int listIndex, bool allowMultiplySelections, bool shift, bool fireOnChangeNow)
149{
150    if (!multiple())
151        setSelectedIndexByUser(listToOptionIndex(listIndex), true, fireOnChangeNow);
152    else {
153        updateSelectedState(m_data, this, listIndex, allowMultiplySelections, shift);
154        setNeedsValidityCheck();
155        if (fireOnChangeNow)
156            listBoxOnChange();
157    }
158}
159
160int HTMLSelectElement::activeSelectionStartListIndex() const
161{
162    if (m_data.activeSelectionAnchorIndex() >= 0)
163        return m_data.activeSelectionAnchorIndex();
164    return optionToListIndex(selectedIndex());
165}
166
167int HTMLSelectElement::activeSelectionEndListIndex() const
168{
169    if (m_data.activeSelectionEndIndex() >= 0)
170        return m_data.activeSelectionEndIndex();
171    return SelectElement::lastSelectedListIndex(m_data, this);
172}
173
174unsigned HTMLSelectElement::length() const
175{
176    return SelectElement::optionCount(m_data, this);
177}
178
179void HTMLSelectElement::add(HTMLElement* element, HTMLElement* before, ExceptionCode& ec)
180{
181    RefPtr<HTMLElement> protectNewChild(element); // make sure the element is ref'd and deref'd so we don't leak it
182
183    if (!element || !(element->hasLocalName(optionTag) || element->hasLocalName(hrTag)))
184        return;
185
186    insertBefore(element, before, ec);
187    setNeedsValidityCheck();
188}
189
190void HTMLSelectElement::remove(int optionIndex)
191{
192    int listIndex = optionToListIndex(optionIndex);
193    if (listIndex < 0)
194        return;
195
196    ExceptionCode ec;
197    listItems()[listIndex]->remove(ec);
198}
199
200void HTMLSelectElement::remove(HTMLOptionElement* option)
201{
202    if (option->ownerSelectElement() != this)
203        return;
204
205    ExceptionCode ec;
206    option->remove(ec);
207}
208
209String HTMLSelectElement::value() const
210{
211    const Vector<Element*>& items = listItems();
212    for (unsigned i = 0; i < items.size(); i++) {
213        if (items[i]->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(items[i])->selected())
214            return static_cast<HTMLOptionElement*>(items[i])->value();
215    }
216    return "";
217}
218
219void HTMLSelectElement::setValue(const String &value)
220{
221    if (value.isNull())
222        return;
223    // find the option with value() matching the given parameter
224    // and make it the current selection.
225    const Vector<Element*>& items = listItems();
226    unsigned optionIndex = 0;
227    for (unsigned i = 0; i < items.size(); i++) {
228        if (items[i]->hasLocalName(optionTag)) {
229            if (static_cast<HTMLOptionElement*>(items[i])->value() == value) {
230                setSelectedIndex(optionIndex, true);
231                return;
232            }
233            optionIndex++;
234        }
235    }
236}
237
238bool HTMLSelectElement::saveFormControlState(String& value) const
239{
240    return SelectElement::saveFormControlState(m_data, this, value);
241}
242
243void HTMLSelectElement::restoreFormControlState(const String& state)
244{
245    SelectElement::restoreFormControlState(m_data, this, state);
246    setNeedsValidityCheck();
247}
248
249void HTMLSelectElement::parseMappedAttribute(Attribute* attr)
250{
251    bool oldUsesMenuList = m_data.usesMenuList();
252    if (attr->name() == sizeAttr) {
253        int oldSize = m_data.size();
254        // Set the attribute value to a number.
255        // This is important since the style rules for this attribute can determine the appearance property.
256        int size = attr->value().toInt();
257        String attrSize = String::number(size);
258        if (attrSize != attr->value())
259            attr->setValue(attrSize);
260        size = max(size, 1);
261
262        // Ensure that we've determined selectedness of the items at least once prior to changing the size.
263        if (oldSize != size)
264            recalcListItemsIfNeeded();
265
266        m_data.setSize(size);
267        setNeedsValidityCheck();
268        if ((oldUsesMenuList != m_data.usesMenuList() || (!oldUsesMenuList && m_data.size() != oldSize)) && attached()) {
269            detach();
270            attach();
271            setRecalcListItems();
272        }
273    } else if (attr->name() == multipleAttr)
274        SelectElement::parseMultipleAttribute(m_data, this, attr);
275    else if (attr->name() == accesskeyAttr) {
276        // FIXME: ignore for the moment
277    } else if (attr->name() == alignAttr) {
278        // Don't map 'align' attribute.  This matches what Firefox, Opera and IE do.
279        // See http://bugs.webkit.org/show_bug.cgi?id=12072
280    } else if (attr->name() == onchangeAttr) {
281        setAttributeEventListener(eventNames().changeEvent, createAttributeEventListener(this, attr));
282    } else
283        HTMLFormControlElementWithState::parseMappedAttribute(attr);
284}
285
286bool HTMLSelectElement::isKeyboardFocusable(KeyboardEvent* event) const
287{
288    if (renderer())
289        return isFocusable();
290    return HTMLFormControlElementWithState::isKeyboardFocusable(event);
291}
292
293bool HTMLSelectElement::isMouseFocusable() const
294{
295    if (renderer())
296        return isFocusable();
297    return HTMLFormControlElementWithState::isMouseFocusable();
298}
299
300bool HTMLSelectElement::canSelectAll() const
301{
302    return !m_data.usesMenuList();
303}
304
305void HTMLSelectElement::selectAll()
306{
307    SelectElement::selectAll(m_data, this);
308    setNeedsValidityCheck();
309}
310
311RenderObject* HTMLSelectElement::createRenderer(RenderArena* arena, RenderStyle*)
312{
313    if (m_data.usesMenuList())
314        return new (arena) RenderMenuList(this);
315    return new (arena) RenderListBox(this);
316}
317
318bool HTMLSelectElement::appendFormData(FormDataList& list, bool)
319{
320    return SelectElement::appendFormData(m_data, this, list);
321}
322
323int HTMLSelectElement::optionToListIndex(int optionIndex) const
324{
325    return SelectElement::optionToListIndex(m_data, this, optionIndex);
326}
327
328int HTMLSelectElement::listToOptionIndex(int listIndex) const
329{
330    return SelectElement::listToOptionIndex(m_data, this, listIndex);
331}
332
333PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options()
334{
335    return HTMLOptionsCollection::create(this);
336}
337
338void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const
339{
340    SelectElement::recalcListItems(const_cast<SelectElementData&>(m_data), this, updateSelectedStates);
341}
342
343void HTMLSelectElement::recalcListItemsIfNeeded()
344{
345    if (m_data.shouldRecalcListItems())
346        recalcListItems();
347}
348
349void HTMLSelectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
350{
351    setRecalcListItems();
352    setNeedsValidityCheck();
353    HTMLFormControlElementWithState::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
354
355    if (AXObjectCache::accessibilityEnabled() && renderer())
356        renderer()->document()->axObjectCache()->childrenChanged(renderer());
357}
358
359void HTMLSelectElement::setRecalcListItems()
360{
361    SelectElement::setRecalcListItems(m_data, this);
362
363    if (!inDocument())
364        m_collectionInfo.reset();
365}
366
367void HTMLSelectElement::reset()
368{
369    SelectElement::reset(m_data, this);
370    setNeedsValidityCheck();
371}
372
373void HTMLSelectElement::dispatchFocusEvent()
374{
375    SelectElement::dispatchFocusEvent(m_data, this);
376    HTMLFormControlElementWithState::dispatchFocusEvent();
377}
378
379void HTMLSelectElement::dispatchBlurEvent()
380{
381    SelectElement::dispatchBlurEvent(m_data, this);
382    HTMLFormControlElementWithState::dispatchBlurEvent();
383}
384
385void HTMLSelectElement::defaultEventHandler(Event* event)
386{
387    SelectElement::defaultEventHandler(m_data, this, event, form());
388    if (event->defaultHandled())
389        return;
390    HTMLFormControlElementWithState::defaultEventHandler(event);
391}
392
393void HTMLSelectElement::setActiveSelectionAnchorIndex(int index)
394{
395    SelectElement::setActiveSelectionAnchorIndex(m_data, this, index);
396}
397
398void HTMLSelectElement::setActiveSelectionEndIndex(int index)
399{
400    SelectElement::setActiveSelectionEndIndex(m_data, index);
401}
402
403void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions)
404{
405    SelectElement::updateListBoxSelection(m_data, this, deselectOtherOptions);
406    setNeedsValidityCheck();
407}
408
409void HTMLSelectElement::menuListOnChange()
410{
411    SelectElement::menuListOnChange(m_data, this);
412}
413
414void HTMLSelectElement::listBoxOnChange()
415{
416    SelectElement::listBoxOnChange(m_data, this);
417}
418
419void HTMLSelectElement::saveLastSelection()
420{
421    SelectElement::saveLastSelection(m_data, this);
422}
423
424void HTMLSelectElement::accessKeyAction(bool sendToAnyElement)
425{
426    focus();
427    dispatchSimulatedClick(0, sendToAnyElement);
428}
429
430void HTMLSelectElement::accessKeySetSelectedIndex(int index)
431{
432    SelectElement::accessKeySetSelectedIndex(m_data, this, index);
433}
434
435void HTMLSelectElement::setMultiple(bool multiple)
436{
437    int oldSelectedIndex = selectedIndex();
438    setAttribute(multipleAttr, multiple ? "" : 0);
439
440    // Restore selectedIndex after changing the multiple flag to preserve
441    // selection as single-line and multi-line has different defaults.
442    setSelectedIndex(oldSelectedIndex);
443}
444
445void HTMLSelectElement::setSize(int size)
446{
447    setAttribute(sizeAttr, String::number(size));
448}
449
450Node* HTMLSelectElement::namedItem(const AtomicString& name)
451{
452    return options()->namedItem(name);
453}
454
455Node* HTMLSelectElement::item(unsigned index)
456{
457    return options()->item(index);
458}
459
460void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionCode& ec)
461{
462    ec = 0;
463    if (index > maxSelectItems - 1)
464        index = maxSelectItems - 1;
465    int diff = index  - length();
466    HTMLElement* before = 0;
467    // out of array bounds ? first insert empty dummies
468    if (diff > 0) {
469        setLength(index, ec);
470        // replace an existing entry ?
471    } else if (diff < 0) {
472        before = toHTMLElement(options()->item(index+1));
473        remove(index);
474    }
475    // finally add the new element
476    if (!ec) {
477        add(option, before, ec);
478        if (diff >= 0 && option->selected())
479            setSelectedIndex(index, !m_data.multiple());
480    }
481}
482
483void HTMLSelectElement::setLength(unsigned newLen, ExceptionCode& ec)
484{
485    ec = 0;
486    if (newLen > maxSelectItems)
487        newLen = maxSelectItems;
488    int diff = length() - newLen;
489
490    if (diff < 0) { // add dummy elements
491        do {
492            RefPtr<Element> option = document()->createElement(optionTag, false);
493            ASSERT(option);
494            add(toHTMLElement(option.get()), 0, ec);
495            if (ec)
496                break;
497        } while (++diff);
498    } else {
499        const Vector<Element*>& items = listItems();
500
501        // Removing children fires mutation events, which might mutate the DOM further, so we first copy out a list
502        // of elements that we intend to remove then attempt to remove them one at a time.
503        Vector<RefPtr<Element> > itemsToRemove;
504        size_t optionIndex = 0;
505        for (size_t i = 0; i < items.size(); ++i) {
506            Element* item = items[i];
507            if (item->hasLocalName(optionTag) && optionIndex++ >= newLen) {
508                ASSERT(item->parentNode());
509                itemsToRemove.append(item);
510            }
511        }
512
513        for (size_t i = 0; i < itemsToRemove.size(); ++i) {
514            Element* item = itemsToRemove[i].get();
515            if (item->parentNode()) {
516                item->parentNode()->removeChild(item, ec);
517            }
518        }
519    }
520    setNeedsValidityCheck();
521}
522
523void HTMLSelectElement::scrollToSelection()
524{
525    SelectElement::scrollToSelection(m_data, this);
526}
527
528void HTMLSelectElement::insertedIntoTree(bool deep)
529{
530    SelectElement::insertedIntoTree(m_data, this);
531    HTMLFormControlElementWithState::insertedIntoTree(deep);
532}
533
534bool HTMLSelectElement::isRequiredFormControl() const
535{
536    return required();
537}
538
539} // namespace
540