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