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