• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
40 using namespace std;
41 
42 namespace WebCore {
43 
44 using namespace HTMLNames;
45 
46 // Upper limit agreed upon with representatives of Opera and Mozilla.
47 static const unsigned maxSelectItems = 10000;
48 
HTMLSelectElement(const QualifiedName & tagName,Document * document,HTMLFormElement * form)49 HTMLSelectElement::HTMLSelectElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form)
50     : HTMLFormControlElementWithState(tagName, document, form)
51 {
52     ASSERT(hasTagName(selectTag));
53 }
54 
create(const QualifiedName & tagName,Document * document,HTMLFormElement * form)55 PassRefPtr<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 
recalcStyle(StyleChange change)61 void HTMLSelectElement::recalcStyle(StyleChange change)
62 {
63     HTMLFormControlElementWithState::recalcStyle(change);
64 }
65 
formControlType() const66 const 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 
selectedIndex() const73 int HTMLSelectElement::selectedIndex() const
74 {
75     return SelectElement::selectedIndex(m_data, this);
76 }
77 
deselectItems(HTMLOptionElement * excludeElement)78 void HTMLSelectElement::deselectItems(HTMLOptionElement* excludeElement)
79 {
80     SelectElement::deselectItems(m_data, this, excludeElement);
81     setNeedsValidityCheck();
82 }
83 
setSelectedIndex(int optionIndex,bool deselect)84 void HTMLSelectElement::setSelectedIndex(int optionIndex, bool deselect)
85 {
86     SelectElement::setSelectedIndex(m_data, this, optionIndex, deselect, false, false);
87     setNeedsValidityCheck();
88 }
89 
setSelectedIndexByUser(int optionIndex,bool deselect,bool fireOnChangeNow,bool allowMultipleSelection)90 void 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 
hasPlaceholderLabelOption() const113 bool 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 
valueMissing() const137 bool 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 
listBoxSelectItem(int listIndex,bool allowMultiplySelections,bool shift,bool fireOnChangeNow)148 void 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 
activeSelectionStartListIndex() const160 int HTMLSelectElement::activeSelectionStartListIndex() const
161 {
162     if (m_data.activeSelectionAnchorIndex() >= 0)
163         return m_data.activeSelectionAnchorIndex();
164     return optionToListIndex(selectedIndex());
165 }
166 
activeSelectionEndListIndex() const167 int HTMLSelectElement::activeSelectionEndListIndex() const
168 {
169     if (m_data.activeSelectionEndIndex() >= 0)
170         return m_data.activeSelectionEndIndex();
171     return SelectElement::lastSelectedListIndex(m_data, this);
172 }
173 
length() const174 unsigned HTMLSelectElement::length() const
175 {
176     return SelectElement::optionCount(m_data, this);
177 }
178 
add(HTMLElement * element,HTMLElement * before,ExceptionCode & ec)179 void 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 
remove(int optionIndex)190 void 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 
remove(HTMLOptionElement * option)200 void HTMLSelectElement::remove(HTMLOptionElement* option)
201 {
202     if (option->ownerSelectElement() != this)
203         return;
204 
205     ExceptionCode ec;
206     option->remove(ec);
207 }
208 
value() const209 String 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 
setValue(const String & value)219 void 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 
saveFormControlState(String & value) const238 bool HTMLSelectElement::saveFormControlState(String& value) const
239 {
240     return SelectElement::saveFormControlState(m_data, this, value);
241 }
242 
restoreFormControlState(const String & state)243 void HTMLSelectElement::restoreFormControlState(const String& state)
244 {
245     SelectElement::restoreFormControlState(m_data, this, state);
246     setNeedsValidityCheck();
247 }
248 
parseMappedAttribute(Attribute * attr)249 void 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 
isKeyboardFocusable(KeyboardEvent * event) const286 bool HTMLSelectElement::isKeyboardFocusable(KeyboardEvent* event) const
287 {
288     if (renderer())
289         return isFocusable();
290     return HTMLFormControlElementWithState::isKeyboardFocusable(event);
291 }
292 
isMouseFocusable() const293 bool HTMLSelectElement::isMouseFocusable() const
294 {
295     if (renderer())
296         return isFocusable();
297     return HTMLFormControlElementWithState::isMouseFocusable();
298 }
299 
canSelectAll() const300 bool HTMLSelectElement::canSelectAll() const
301 {
302     return !m_data.usesMenuList();
303 }
304 
selectAll()305 void HTMLSelectElement::selectAll()
306 {
307     SelectElement::selectAll(m_data, this);
308     setNeedsValidityCheck();
309 }
310 
createRenderer(RenderArena * arena,RenderStyle *)311 RenderObject* 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 
appendFormData(FormDataList & list,bool)318 bool HTMLSelectElement::appendFormData(FormDataList& list, bool)
319 {
320     return SelectElement::appendFormData(m_data, this, list);
321 }
322 
optionToListIndex(int optionIndex) const323 int HTMLSelectElement::optionToListIndex(int optionIndex) const
324 {
325     return SelectElement::optionToListIndex(m_data, this, optionIndex);
326 }
327 
listToOptionIndex(int listIndex) const328 int HTMLSelectElement::listToOptionIndex(int listIndex) const
329 {
330     return SelectElement::listToOptionIndex(m_data, this, listIndex);
331 }
332 
options()333 PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options()
334 {
335     return HTMLOptionsCollection::create(this);
336 }
337 
recalcListItems(bool updateSelectedStates) const338 void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const
339 {
340     SelectElement::recalcListItems(const_cast<SelectElementData&>(m_data), this, updateSelectedStates);
341 }
342 
recalcListItemsIfNeeded()343 void HTMLSelectElement::recalcListItemsIfNeeded()
344 {
345     if (m_data.shouldRecalcListItems())
346         recalcListItems();
347 }
348 
childrenChanged(bool changedByParser,Node * beforeChange,Node * afterChange,int childCountDelta)349 void 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 
setRecalcListItems()359 void HTMLSelectElement::setRecalcListItems()
360 {
361     SelectElement::setRecalcListItems(m_data, this);
362 
363     if (!inDocument())
364         m_collectionInfo.reset();
365 }
366 
reset()367 void HTMLSelectElement::reset()
368 {
369     SelectElement::reset(m_data, this);
370     setNeedsValidityCheck();
371 }
372 
dispatchFocusEvent()373 void HTMLSelectElement::dispatchFocusEvent()
374 {
375     SelectElement::dispatchFocusEvent(m_data, this);
376     HTMLFormControlElementWithState::dispatchFocusEvent();
377 }
378 
dispatchBlurEvent()379 void HTMLSelectElement::dispatchBlurEvent()
380 {
381     SelectElement::dispatchBlurEvent(m_data, this);
382     HTMLFormControlElementWithState::dispatchBlurEvent();
383 }
384 
defaultEventHandler(Event * event)385 void 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 
setActiveSelectionAnchorIndex(int index)393 void HTMLSelectElement::setActiveSelectionAnchorIndex(int index)
394 {
395     SelectElement::setActiveSelectionAnchorIndex(m_data, this, index);
396 }
397 
setActiveSelectionEndIndex(int index)398 void HTMLSelectElement::setActiveSelectionEndIndex(int index)
399 {
400     SelectElement::setActiveSelectionEndIndex(m_data, index);
401 }
402 
updateListBoxSelection(bool deselectOtherOptions)403 void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions)
404 {
405     SelectElement::updateListBoxSelection(m_data, this, deselectOtherOptions);
406     setNeedsValidityCheck();
407 }
408 
menuListOnChange()409 void HTMLSelectElement::menuListOnChange()
410 {
411     SelectElement::menuListOnChange(m_data, this);
412 }
413 
listBoxOnChange()414 void HTMLSelectElement::listBoxOnChange()
415 {
416     SelectElement::listBoxOnChange(m_data, this);
417 }
418 
saveLastSelection()419 void HTMLSelectElement::saveLastSelection()
420 {
421     SelectElement::saveLastSelection(m_data, this);
422 }
423 
accessKeyAction(bool sendToAnyElement)424 void HTMLSelectElement::accessKeyAction(bool sendToAnyElement)
425 {
426     focus();
427     dispatchSimulatedClick(0, sendToAnyElement);
428 }
429 
accessKeySetSelectedIndex(int index)430 void HTMLSelectElement::accessKeySetSelectedIndex(int index)
431 {
432     SelectElement::accessKeySetSelectedIndex(m_data, this, index);
433 }
434 
setMultiple(bool multiple)435 void 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 
setSize(int size)445 void HTMLSelectElement::setSize(int size)
446 {
447     setAttribute(sizeAttr, String::number(size));
448 }
449 
namedItem(const AtomicString & name)450 Node* HTMLSelectElement::namedItem(const AtomicString& name)
451 {
452     return options()->namedItem(name);
453 }
454 
item(unsigned index)455 Node* HTMLSelectElement::item(unsigned index)
456 {
457     return options()->item(index);
458 }
459 
setOption(unsigned index,HTMLOptionElement * option,ExceptionCode & ec)460 void 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 
setLength(unsigned newLen,ExceptionCode & ec)483 void 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 
scrollToSelection()523 void HTMLSelectElement::scrollToSelection()
524 {
525     SelectElement::scrollToSelection(m_data, this);
526 }
527 
insertedIntoTree(bool deep)528 void HTMLSelectElement::insertedIntoTree(bool deep)
529 {
530     SelectElement::insertedIntoTree(m_data, this);
531     HTMLFormControlElementWithState::insertedIntoTree(deep);
532 }
533 
isRequiredFormControl() const534 bool HTMLSelectElement::isRequiredFormControl() const
535 {
536     return required();
537 }
538 
539 } // namespace
540