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