1 /* 2 * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * (C) 1999 Antti Koivisto (koivisto (at) kde.org) 4 * (C) 2001 Dirk Mueller (mueller (at) kde.org) 5 * Copyright (C) 2004, 2005, 2006, 2007, 2009 Apple Inc. All rights reserved. 6 * (C) 2006 Alexey Proskuryakov (ap (at) 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 39 using namespace std; 40 41 namespace WebCore { 42 43 using namespace HTMLNames; 44 45 // Upper limit agreed upon with representatives of Opera and Mozilla. 46 static const unsigned maxSelectItems = 10000; 47 48 HTMLSelectElement::HTMLSelectElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form) 49 : HTMLFormControlElementWithState(tagName, document, form) 50 { 51 ASSERT(hasTagName(selectTag) || hasTagName(keygenTag)); 52 } 53 54 bool 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 61 void HTMLSelectElement::recalcStyle(StyleChange change) 62 { 63 HTMLFormControlElementWithState::recalcStyle(change); 64 } 65 66 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 73 int HTMLSelectElement::selectedIndex() const 74 { 75 return SelectElement::selectedIndex(m_data, this); 76 } 77 78 void HTMLSelectElement::deselectItems(HTMLOptionElement* excludeElement) 79 { 80 SelectElement::deselectItems(m_data, this, excludeElement); 81 } 82 83 void HTMLSelectElement::setSelectedIndex(int optionIndex, bool deselect) 84 { 85 SelectElement::setSelectedIndex(m_data, this, optionIndex, deselect, false, false); 86 } 87 88 void HTMLSelectElement::setSelectedIndexByUser(int optionIndex, bool deselect, bool fireOnChangeNow) 89 { 90 SelectElement::setSelectedIndex(m_data, this, optionIndex, deselect, fireOnChangeNow, true); 91 } 92 93 int HTMLSelectElement::activeSelectionStartListIndex() const 94 { 95 if (m_data.activeSelectionAnchorIndex() >= 0) 96 return m_data.activeSelectionAnchorIndex(); 97 return optionToListIndex(selectedIndex()); 98 } 99 100 int HTMLSelectElement::activeSelectionEndListIndex() const 101 { 102 if (m_data.activeSelectionEndIndex() >= 0) 103 return m_data.activeSelectionEndIndex(); 104 return SelectElement::lastSelectedListIndex(m_data, this); 105 } 106 107 unsigned HTMLSelectElement::length() const 108 { 109 return SelectElement::optionCount(m_data, this); 110 } 111 112 void 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 122 void 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 134 String 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 144 void 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 163 bool HTMLSelectElement::saveFormControlState(String& value) const 164 { 165 return SelectElement::saveFormControlState(m_data, this, value); 166 } 167 168 void HTMLSelectElement::restoreFormControlState(const String& state) 169 { 170 SelectElement::restoreFormControlState(m_data, this, state); 171 } 172 173 void 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 208 bool HTMLSelectElement::isKeyboardFocusable(KeyboardEvent* event) const 209 { 210 if (renderer()) 211 return isFocusable(); 212 return HTMLFormControlElementWithState::isKeyboardFocusable(event); 213 } 214 215 bool HTMLSelectElement::isMouseFocusable() const 216 { 217 if (renderer()) 218 return isFocusable(); 219 return HTMLFormControlElementWithState::isMouseFocusable(); 220 } 221 222 bool HTMLSelectElement::canSelectAll() const 223 { 224 return !m_data.usesMenuList(); 225 } 226 227 void HTMLSelectElement::selectAll() 228 { 229 SelectElement::selectAll(m_data, this); 230 } 231 232 RenderObject* 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 239 bool HTMLSelectElement::appendFormData(FormDataList& list, bool) 240 { 241 return SelectElement::appendFormData(m_data, this, list); 242 } 243 244 int HTMLSelectElement::optionToListIndex(int optionIndex) const 245 { 246 return SelectElement::optionToListIndex(m_data, this, optionIndex); 247 } 248 249 int HTMLSelectElement::listToOptionIndex(int listIndex) const 250 { 251 return SelectElement::listToOptionIndex(m_data, this, listIndex); 252 } 253 254 PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options() 255 { 256 return HTMLOptionsCollection::create(this); 257 } 258 259 void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const 260 { 261 SelectElement::recalcListItems(const_cast<SelectElementData&>(m_data), this, updateSelectedStates); 262 } 263 264 void HTMLSelectElement::recalcListItemsIfNeeded() 265 { 266 if (m_data.shouldRecalcListItems()) 267 recalcListItems(); 268 } 269 270 void 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 279 void HTMLSelectElement::setRecalcListItems() 280 { 281 SelectElement::setRecalcListItems(m_data, this); 282 283 if (!inDocument()) 284 m_collectionInfo.reset(); 285 } 286 287 void HTMLSelectElement::reset() 288 { 289 SelectElement::reset(m_data, this); 290 } 291 292 void HTMLSelectElement::dispatchFocusEvent() 293 { 294 SelectElement::dispatchFocusEvent(m_data, this); 295 HTMLFormControlElementWithState::dispatchFocusEvent(); 296 } 297 298 void HTMLSelectElement::dispatchBlurEvent() 299 { 300 SelectElement::dispatchBlurEvent(m_data, this); 301 HTMLFormControlElementWithState::dispatchBlurEvent(); 302 } 303 304 void 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 312 void HTMLSelectElement::setActiveSelectionAnchorIndex(int index) 313 { 314 SelectElement::setActiveSelectionAnchorIndex(m_data, this, index); 315 } 316 317 void HTMLSelectElement::setActiveSelectionEndIndex(int index) 318 { 319 SelectElement::setActiveSelectionEndIndex(m_data, index); 320 } 321 322 void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions) 323 { 324 SelectElement::updateListBoxSelection(m_data, this, deselectOtherOptions); 325 } 326 327 void HTMLSelectElement::menuListOnChange() 328 { 329 SelectElement::menuListOnChange(m_data, this); 330 } 331 332 void HTMLSelectElement::listBoxOnChange() 333 { 334 SelectElement::listBoxOnChange(m_data, this); 335 } 336 337 void HTMLSelectElement::saveLastSelection() 338 { 339 SelectElement::saveLastSelection(m_data, this); 340 } 341 342 void HTMLSelectElement::accessKeyAction(bool sendToAnyElement) 343 { 344 focus(); 345 dispatchSimulatedClick(0, sendToAnyElement); 346 } 347 348 void HTMLSelectElement::accessKeySetSelectedIndex(int index) 349 { 350 SelectElement::accessKeySetSelectedIndex(m_data, this, index); 351 } 352 353 void HTMLSelectElement::setMultiple(bool multiple) 354 { 355 setAttribute(multipleAttr, multiple ? "" : 0); 356 } 357 358 void HTMLSelectElement::setSize(int size) 359 { 360 setAttribute(sizeAttr, String::number(size)); 361 } 362 363 Node* HTMLSelectElement::namedItem(const AtomicString& name) 364 { 365 return options()->namedItem(name); 366 } 367 368 Node* HTMLSelectElement::item(unsigned index) 369 { 370 return options()->item(index); 371 } 372 373 void 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 396 void 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 425 void HTMLSelectElement::scrollToSelection() 426 { 427 SelectElement::scrollToSelection(m_data, this); 428 } 429 430 void HTMLSelectElement::insertedIntoTree(bool deep) 431 { 432 SelectElement::insertedIntoTree(m_data, this); 433 HTMLFormControlElementWithState::insertedIntoTree(deep); 434 } 435 436 } // namespace 437