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 * (C) 2006 Alexey Proskuryakov (ap (at) nypop.com) 6 * Copyright (C) 2004, 2005, 2006, 2010 Apple Inc. All rights reserved. 7 * Copyright (C) 2010 Google Inc. All rights reserved. 8 * Copyright (C) 2011 Motorola Mobility, 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 "core/html/HTMLOptionElement.h" 29 30 #include "HTMLNames.h" 31 #include "bindings/v8/ExceptionState.h" 32 #include "core/dom/Document.h" 33 #include "core/dom/NodeRenderStyle.h" 34 #include "core/dom/NodeTraversal.h" 35 #include "core/dom/ScriptLoader.h" 36 #include "core/dom/Text.h" 37 #include "core/html/HTMLDataListElement.h" 38 #include "core/html/HTMLOptGroupElement.h" 39 #include "core/html/HTMLSelectElement.h" 40 #include "core/html/parser/HTMLParserIdioms.h" 41 #include "core/rendering/RenderTheme.h" 42 #include "wtf/Vector.h" 43 #include "wtf/text/StringBuilder.h" 44 45 namespace WebCore { 46 47 using namespace HTMLNames; 48 49 HTMLOptionElement::HTMLOptionElement(Document& document) 50 : HTMLElement(optionTag, document) 51 , m_disabled(false) 52 , m_isSelected(false) 53 { 54 setHasCustomStyleCallbacks(); 55 ScriptWrappable::init(this); 56 } 57 58 PassRefPtr<HTMLOptionElement> HTMLOptionElement::create(Document& document) 59 { 60 return adoptRef(new HTMLOptionElement(document)); 61 } 62 63 PassRefPtr<HTMLOptionElement> HTMLOptionElement::createForJSConstructor(Document& document, const String& data, const AtomicString& value, 64 bool defaultSelected, bool selected, ExceptionState& exceptionState) 65 { 66 RefPtr<HTMLOptionElement> element = adoptRef(new HTMLOptionElement(document)); 67 68 RefPtr<Text> text = Text::create(document, data.isNull() ? "" : data); 69 70 element->appendChild(text.release(), exceptionState); 71 if (exceptionState.hadException()) 72 return 0; 73 74 if (!value.isNull()) 75 element->setValue(value); 76 if (defaultSelected) 77 element->setAttribute(selectedAttr, emptyAtom); 78 element->setSelected(selected); 79 80 return element.release(); 81 } 82 83 void HTMLOptionElement::attach(const AttachContext& context) 84 { 85 HTMLElement::attach(context); 86 // If after attaching nothing called styleForRenderer() on this node we 87 // manually cache the value. This happens if our parent doesn't have a 88 // renderer like <optgroup> or if it doesn't allow children like <select>. 89 if (!m_style && parentNode()->renderStyle()) 90 updateNonRenderStyle(); 91 } 92 93 void HTMLOptionElement::detach(const AttachContext& context) 94 { 95 m_style.clear(); 96 HTMLElement::detach(context); 97 } 98 99 bool HTMLOptionElement::rendererIsFocusable() const 100 { 101 // Option elements do not have a renderer so we check the renderStyle instead. 102 return renderStyle() && renderStyle()->display() != NONE; 103 } 104 105 String HTMLOptionElement::text() const 106 { 107 Document& document = this->document(); 108 String text; 109 110 // WinIE does not use the label attribute, so as a quirk, we ignore it. 111 if (!document.inQuirksMode()) 112 text = fastGetAttribute(labelAttr); 113 114 // FIXME: The following treats an element with the label attribute set to 115 // the empty string the same as an element with no label attribute at all. 116 // Is that correct? If it is, then should the label function work the same way? 117 if (text.isEmpty()) 118 text = collectOptionInnerText(); 119 120 return text.stripWhiteSpace(isHTMLSpace<UChar>).simplifyWhiteSpace(isHTMLSpace<UChar>); 121 } 122 123 void HTMLOptionElement::setText(const String &text, ExceptionState& exceptionState) 124 { 125 RefPtr<Node> protectFromMutationEvents(this); 126 127 // Changing the text causes a recalc of a select's items, which will reset the selected 128 // index to the first item if the select is single selection with a menu list. We attempt to 129 // preserve the selected item. 130 RefPtr<HTMLSelectElement> select = ownerSelectElement(); 131 bool selectIsMenuList = select && select->usesMenuList(); 132 int oldSelectedIndex = selectIsMenuList ? select->selectedIndex() : -1; 133 134 // Handle the common special case where there's exactly 1 child node, and it's a text node. 135 Node* child = firstChild(); 136 if (child && child->isTextNode() && !child->nextSibling()) 137 toText(child)->setData(text); 138 else { 139 removeChildren(); 140 appendChild(Text::create(document(), text), exceptionState); 141 } 142 143 if (selectIsMenuList && select->selectedIndex() != oldSelectedIndex) 144 select->setSelectedIndex(oldSelectedIndex); 145 } 146 147 void HTMLOptionElement::accessKeyAction(bool) 148 { 149 HTMLSelectElement* select = ownerSelectElement(); 150 if (select) 151 select->accessKeySetSelectedIndex(index()); 152 } 153 154 int HTMLOptionElement::index() const 155 { 156 // It would be faster to cache the index, but harder to get it right in all cases. 157 158 HTMLSelectElement* selectElement = ownerSelectElement(); 159 if (!selectElement) 160 return 0; 161 162 int optionIndex = 0; 163 164 const Vector<HTMLElement*>& items = selectElement->listItems(); 165 size_t length = items.size(); 166 for (size_t i = 0; i < length; ++i) { 167 if (!items[i]->hasTagName(optionTag)) 168 continue; 169 if (items[i] == this) 170 return optionIndex; 171 ++optionIndex; 172 } 173 174 return 0; 175 } 176 177 void HTMLOptionElement::parseAttribute(const QualifiedName& name, const AtomicString& value) 178 { 179 if (name == valueAttr) { 180 if (HTMLDataListElement* dataList = ownerDataListElement()) 181 dataList->optionElementChildrenChanged(); 182 } else if (name == disabledAttr) { 183 bool oldDisabled = m_disabled; 184 m_disabled = !value.isNull(); 185 if (oldDisabled != m_disabled) { 186 didAffectSelector(AffectedSelectorDisabled | AffectedSelectorEnabled); 187 if (renderer() && renderer()->style()->hasAppearance()) 188 RenderTheme::theme().stateChanged(renderer(), EnabledState); 189 } 190 } else if (name == selectedAttr) { 191 if (bool willBeSelected = !value.isNull()) 192 setSelected(willBeSelected); 193 } else 194 HTMLElement::parseAttribute(name, value); 195 } 196 197 String HTMLOptionElement::value() const 198 { 199 const AtomicString& value = fastGetAttribute(valueAttr); 200 if (!value.isNull()) 201 return value; 202 return collectOptionInnerText().stripWhiteSpace(isHTMLSpace<UChar>).simplifyWhiteSpace(isHTMLSpace<UChar>); 203 } 204 205 void HTMLOptionElement::setValue(const AtomicString& value) 206 { 207 setAttribute(valueAttr, value); 208 } 209 210 bool HTMLOptionElement::selected() 211 { 212 if (HTMLSelectElement* select = ownerSelectElement()) { 213 // If a stylesheet contains option:checked selectors, this function is 214 // called during parsing. updateListItemSelectedStates() is O(N) where N 215 // is the number of option elements, so the <select> parsing would be 216 // O(N^2) without isParsingInProgress check. Also, 217 // updateListItemSelectedStates() determines default selection, and we'd 218 // like to avoid to determine default selection with incomplete option 219 // list. 220 if (select->isParsingInProgress()) 221 return m_isSelected; 222 select->updateListItemSelectedStates(); 223 } 224 return m_isSelected; 225 } 226 227 void HTMLOptionElement::setSelected(bool selected) 228 { 229 if (m_isSelected == selected) 230 return; 231 232 setSelectedState(selected); 233 234 if (HTMLSelectElement* select = ownerSelectElement()) 235 select->optionSelectionStateChanged(this, selected); 236 } 237 238 void HTMLOptionElement::setSelectedState(bool selected) 239 { 240 if (m_isSelected == selected) 241 return; 242 243 m_isSelected = selected; 244 didAffectSelector(AffectedSelectorChecked); 245 246 if (HTMLSelectElement* select = ownerSelectElement()) 247 select->invalidateSelectedItems(); 248 } 249 250 void HTMLOptionElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta) 251 { 252 if (HTMLDataListElement* dataList = ownerDataListElement()) 253 dataList->optionElementChildrenChanged(); 254 else if (HTMLSelectElement* select = ownerSelectElement()) 255 select->optionElementChildrenChanged(); 256 HTMLElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta); 257 } 258 259 HTMLDataListElement* HTMLOptionElement::ownerDataListElement() const 260 { 261 for (ContainerNode* parent = parentNode(); parent ; parent = parent->parentNode()) { 262 if (parent->hasTagName(datalistTag)) 263 return toHTMLDataListElement(parent); 264 } 265 return 0; 266 } 267 268 HTMLSelectElement* HTMLOptionElement::ownerSelectElement() const 269 { 270 ContainerNode* select = parentNode(); 271 while (select && !select->hasTagName(selectTag)) 272 select = select->parentNode(); 273 274 if (!select) 275 return 0; 276 277 return toHTMLSelectElement(select); 278 } 279 280 String HTMLOptionElement::label() const 281 { 282 const AtomicString& label = fastGetAttribute(labelAttr); 283 if (!label.isNull()) 284 return label; 285 return collectOptionInnerText().stripWhiteSpace(isHTMLSpace<UChar>).simplifyWhiteSpace(isHTMLSpace<UChar>); 286 } 287 288 void HTMLOptionElement::setLabel(const AtomicString& label) 289 { 290 setAttribute(labelAttr, label); 291 } 292 293 void HTMLOptionElement::updateNonRenderStyle() 294 { 295 m_style = originalStyleForRenderer(); 296 } 297 298 RenderStyle* HTMLOptionElement::nonRendererStyle() const 299 { 300 return m_style.get(); 301 } 302 303 PassRefPtr<RenderStyle> HTMLOptionElement::customStyleForRenderer() 304 { 305 // styleForRenderer is called whenever a new style should be associated 306 // with an Element so now is a good time to update our cached style. 307 updateNonRenderStyle(); 308 return m_style; 309 } 310 311 void HTMLOptionElement::didRecalcStyle(StyleRecalcChange) 312 { 313 // FIXME: This is nasty, we ask our owner select to repaint even if the new 314 // style is exactly the same. 315 if (HTMLSelectElement* select = ownerSelectElement()) { 316 if (RenderObject* renderer = select->renderer()) 317 renderer->repaint(); 318 } 319 } 320 321 String HTMLOptionElement::textIndentedToRespectGroupLabel() const 322 { 323 ContainerNode* parent = parentNode(); 324 if (parent && isHTMLOptGroupElement(parent)) 325 return " " + text(); 326 return text(); 327 } 328 329 bool HTMLOptionElement::isDisabledFormControl() const 330 { 331 if (ownElementDisabled()) 332 return true; 333 if (Element* parent = parentElement()) 334 return isHTMLOptGroupElement(parent) && parent->isDisabledFormControl(); 335 return false; 336 } 337 338 Node::InsertionNotificationRequest HTMLOptionElement::insertedInto(ContainerNode* insertionPoint) 339 { 340 if (HTMLSelectElement* select = ownerSelectElement()) { 341 select->setRecalcListItems(); 342 // Do not call selected() since calling updateListItemSelectedStates() 343 // at this time won't do the right thing. (Why, exactly?) 344 // FIXME: Might be better to call this unconditionally, always passing m_isSelected, 345 // rather than only calling it if we are selected. 346 if (m_isSelected) 347 select->optionSelectionStateChanged(this, true); 348 select->scrollToSelection(); 349 } 350 351 return HTMLElement::insertedInto(insertionPoint); 352 } 353 354 String HTMLOptionElement::collectOptionInnerText() const 355 { 356 StringBuilder text; 357 for (Node* node = firstChild(); node; ) { 358 if (node->isTextNode()) 359 text.append(node->nodeValue()); 360 // Text nodes inside script elements are not part of the option text. 361 if (node->isElementNode() && toScriptLoaderIfPossible(toElement(node))) 362 node = NodeTraversal::nextSkippingChildren(*node, this); 363 else 364 node = NodeTraversal::next(*node, this); 365 } 366 return text.toString(); 367 } 368 369 HTMLFormElement* HTMLOptionElement::form() const 370 { 371 HTMLSelectElement* selectElement = ownerSelectElement(); 372 if (!selectElement) 373 return 0; 374 375 return selectElement->formOwner(); 376 } 377 378 } // namespace WebCore 379