1/* 2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org) 3 * (C) 1999 Antti Koivisto (koivisto@kde.org) 4 * (C) 2001 Dirk Mueller (mueller@kde.org) 5 * (C) 2006 Alexey Proskuryakov (ap@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/core/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/dom/shadow/ShadowRoot.h" 38#include "core/html/HTMLDataListElement.h" 39#include "core/html/HTMLOptGroupElement.h" 40#include "core/html/HTMLSelectElement.h" 41#include "core/html/parser/HTMLParserIdioms.h" 42#include "core/rendering/RenderTheme.h" 43#include "wtf/Vector.h" 44#include "wtf/text/StringBuilder.h" 45 46namespace blink { 47 48using namespace HTMLNames; 49 50HTMLOptionElement::HTMLOptionElement(Document& document) 51 : HTMLElement(optionTag, document) 52 , m_disabled(false) 53 , m_isSelected(false) 54{ 55 setHasCustomStyleCallbacks(); 56} 57 58HTMLOptionElement::~HTMLOptionElement() 59{ 60} 61 62PassRefPtrWillBeRawPtr<HTMLOptionElement> HTMLOptionElement::create(Document& document) 63{ 64 RefPtrWillBeRawPtr<HTMLOptionElement> option = adoptRefWillBeNoop(new HTMLOptionElement(document)); 65 option->ensureUserAgentShadowRoot(); 66 return option.release(); 67} 68 69PassRefPtrWillBeRawPtr<HTMLOptionElement> HTMLOptionElement::createForJSConstructor(Document& document, const String& data, const AtomicString& value, 70 bool defaultSelected, bool selected, ExceptionState& exceptionState) 71{ 72 RefPtrWillBeRawPtr<HTMLOptionElement> element = adoptRefWillBeNoop(new HTMLOptionElement(document)); 73 element->ensureUserAgentShadowRoot(); 74 element->appendChild(Text::create(document, data.isNull() ? "" : data), exceptionState); 75 if (exceptionState.hadException()) 76 return nullptr; 77 78 if (!value.isNull()) 79 element->setValue(value); 80 if (defaultSelected) 81 element->setAttribute(selectedAttr, emptyAtom); 82 element->setSelected(selected); 83 84 return element.release(); 85} 86 87void HTMLOptionElement::attach(const AttachContext& context) 88{ 89 AttachContext optionContext(context); 90 if (context.resolvedStyle) { 91 ASSERT(!m_style || m_style == context.resolvedStyle); 92 m_style = context.resolvedStyle; 93 } else { 94 updateNonRenderStyle(); 95 optionContext.resolvedStyle = m_style.get(); 96 } 97 HTMLElement::attach(optionContext); 98} 99 100void HTMLOptionElement::detach(const AttachContext& context) 101{ 102 m_style.clear(); 103 HTMLElement::detach(context); 104} 105 106String HTMLOptionElement::text() const 107{ 108 Document& document = this->document(); 109 String text; 110 111 // WinIE does not use the label attribute, so as a quirk, we ignore it. 112 if (!document.inQuirksMode()) 113 text = fastGetAttribute(labelAttr); 114 115 // FIXME: The following treats an element with the label attribute set to 116 // the empty string the same as an element with no label attribute at all. 117 // Is that correct? If it is, then should the label function work the same way? 118 if (text.isEmpty()) 119 text = collectOptionInnerText(); 120 121 return text.stripWhiteSpace(isHTMLSpace<UChar>).simplifyWhiteSpace(isHTMLSpace<UChar>); 122} 123 124void HTMLOptionElement::setText(const String &text, ExceptionState& exceptionState) 125{ 126 RefPtrWillBeRawPtr<Node> protectFromMutationEvents(this); 127 128 // Changing the text causes a recalc of a select's items, which will reset the selected 129 // index to the first item if the select is single selection with a menu list. We attempt to 130 // preserve the selected item. 131 RefPtrWillBeRawPtr<HTMLSelectElement> select = ownerSelectElement(); 132 bool selectIsMenuList = select && select->usesMenuList(); 133 int oldSelectedIndex = selectIsMenuList ? select->selectedIndex() : -1; 134 135 // Handle the common special case where there's exactly 1 child node, and it's a text node. 136 Node* child = firstChild(); 137 if (child && child->isTextNode() && !child->nextSibling()) 138 toText(child)->setData(text); 139 else { 140 removeChildren(); 141 appendChild(Text::create(document(), text), exceptionState); 142 } 143 144 if (selectIsMenuList && select->selectedIndex() != oldSelectedIndex) 145 select->setSelectedIndex(oldSelectedIndex); 146} 147 148void HTMLOptionElement::accessKeyAction(bool) 149{ 150 if (HTMLSelectElement* select = ownerSelectElement()) 151 select->accessKeySetSelectedIndex(index()); 152} 153 154int 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 WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = selectElement->listItems(); 165 size_t length = items.size(); 166 for (size_t i = 0; i < length; ++i) { 167 if (!isHTMLOptionElement(*items[i])) 168 continue; 169 if (items[i].get() == this) 170 return optionIndex; 171 ++optionIndex; 172 } 173 174 return 0; 175} 176 177void 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 pseudoStateChanged(CSSSelector::PseudoDisabled); 187 pseudoStateChanged(CSSSelector::PseudoEnabled); 188 if (renderer() && renderer()->style()->hasAppearance()) 189 RenderTheme::theme().stateChanged(renderer(), EnabledControlState); 190 } 191 } else if (name == selectedAttr) { 192 if (bool willBeSelected = !value.isNull()) 193 setSelected(willBeSelected); 194 } else if (name == labelAttr) { 195 updateLabel(); 196 } else 197 HTMLElement::parseAttribute(name, value); 198} 199 200String HTMLOptionElement::value() const 201{ 202 const AtomicString& value = fastGetAttribute(valueAttr); 203 if (!value.isNull()) 204 return value; 205 return collectOptionInnerText().stripWhiteSpace(isHTMLSpace<UChar>).simplifyWhiteSpace(isHTMLSpace<UChar>); 206} 207 208void HTMLOptionElement::setValue(const AtomicString& value) 209{ 210 setAttribute(valueAttr, value); 211} 212 213bool HTMLOptionElement::selected() const 214{ 215 if (HTMLSelectElement* select = ownerSelectElement()) { 216 // If a stylesheet contains option:checked selectors, this function is 217 // called during parsing. updateListItemSelectedStates() is O(N) where N 218 // is the number of option elements, so the <select> parsing would be 219 // O(N^2) without the isFinishedParsingChildren check. Also, 220 // updateListItemSelectedStates() determines default selection, and we'd 221 // like to avoid to determine default selection with incomplete option 222 // list. 223 if (!select->isFinishedParsingChildren()) 224 return m_isSelected; 225 select->updateListItemSelectedStates(); 226 } 227 return m_isSelected; 228} 229 230void HTMLOptionElement::setSelected(bool selected) 231{ 232 if (m_isSelected == selected) 233 return; 234 235 setSelectedState(selected); 236 237 if (HTMLSelectElement* select = ownerSelectElement()) 238 select->optionSelectionStateChanged(this, selected); 239} 240 241void HTMLOptionElement::setSelectedState(bool selected) 242{ 243 if (m_isSelected == selected) 244 return; 245 246 m_isSelected = selected; 247 pseudoStateChanged(CSSSelector::PseudoChecked); 248 249 if (HTMLSelectElement* select = ownerSelectElement()) 250 select->invalidateSelectedItems(); 251} 252 253void HTMLOptionElement::childrenChanged(const ChildrenChange& change) 254{ 255 if (HTMLDataListElement* dataList = ownerDataListElement()) 256 dataList->optionElementChildrenChanged(); 257 else if (HTMLSelectElement* select = ownerSelectElement()) 258 select->optionElementChildrenChanged(); 259 updateLabel(); 260 HTMLElement::childrenChanged(change); 261} 262 263HTMLDataListElement* HTMLOptionElement::ownerDataListElement() const 264{ 265 return Traversal<HTMLDataListElement>::firstAncestor(*this); 266} 267 268HTMLSelectElement* HTMLOptionElement::ownerSelectElement() const 269{ 270 return Traversal<HTMLSelectElement>::firstAncestor(*this); 271} 272 273String HTMLOptionElement::label() const 274{ 275 const AtomicString& label = fastGetAttribute(labelAttr); 276 if (!label.isNull()) 277 return label; 278 return collectOptionInnerText().stripWhiteSpace(isHTMLSpace<UChar>).simplifyWhiteSpace(isHTMLSpace<UChar>); 279} 280 281void HTMLOptionElement::setLabel(const AtomicString& label) 282{ 283 setAttribute(labelAttr, label); 284} 285 286void HTMLOptionElement::updateNonRenderStyle() 287{ 288 m_style = originalStyleForRenderer(); 289 if (HTMLSelectElement* select = ownerSelectElement()) 290 select->updateListOnRenderer(); 291} 292 293RenderStyle* HTMLOptionElement::nonRendererStyle() const 294{ 295 return m_style.get(); 296} 297 298PassRefPtr<RenderStyle> HTMLOptionElement::customStyleForRenderer() 299{ 300 updateNonRenderStyle(); 301 return m_style; 302} 303 304void HTMLOptionElement::didRecalcStyle(StyleRecalcChange change) 305{ 306 if (change == NoChange) 307 return; 308 309 // FIXME: We ask our owner select to repaint regardless of which property changed. 310 if (HTMLSelectElement* select = ownerSelectElement()) { 311 if (RenderObject* renderer = select->renderer()) 312 renderer->setShouldDoFullPaintInvalidation(true); 313 } 314} 315 316String HTMLOptionElement::textIndentedToRespectGroupLabel() const 317{ 318 ContainerNode* parent = parentNode(); 319 if (parent && isHTMLOptGroupElement(*parent)) 320 return " " + text(); 321 return text(); 322} 323 324bool HTMLOptionElement::isDisabledFormControl() const 325{ 326 if (ownElementDisabled()) 327 return true; 328 if (Element* parent = parentElement()) 329 return isHTMLOptGroupElement(*parent) && parent->isDisabledFormControl(); 330 return false; 331} 332 333Node::InsertionNotificationRequest HTMLOptionElement::insertedInto(ContainerNode* insertionPoint) 334{ 335 if (HTMLSelectElement* select = ownerSelectElement()) { 336 select->setRecalcListItems(); 337 // Do not call selected() since calling updateListItemSelectedStates() 338 // at this time won't do the right thing. (Why, exactly?) 339 if (m_isSelected) { 340 // FIXME: Might be better to call this unconditionally, always 341 // passing m_isSelected, rather than only calling it if we are 342 // selected. 343 select->optionSelectionStateChanged(this, true); 344 select->scrollToSelection(); 345 } 346 } 347 348 return HTMLElement::insertedInto(insertionPoint); 349} 350 351void HTMLOptionElement::removedFrom(ContainerNode* insertionPoint) 352{ 353 if (HTMLSelectElement* select = Traversal<HTMLSelectElement>::firstAncestorOrSelf(*insertionPoint)) { 354 select->setRecalcListItems(); 355 select->optionRemoved(*this); 356 } 357 HTMLElement::removedFrom(insertionPoint); 358} 359 360String HTMLOptionElement::collectOptionInnerText() const 361{ 362 StringBuilder text; 363 for (Node* node = firstChild(); node; ) { 364 if (node->isTextNode()) 365 text.append(node->nodeValue()); 366 // Text nodes inside script elements are not part of the option text. 367 if (node->isElementNode() && toScriptLoaderIfPossible(toElement(node))) 368 node = NodeTraversal::nextSkippingChildren(*node, this); 369 else 370 node = NodeTraversal::next(*node, this); 371 } 372 return text.toString(); 373} 374 375HTMLFormElement* HTMLOptionElement::form() const 376{ 377 if (HTMLSelectElement* selectElement = ownerSelectElement()) 378 return selectElement->formOwner(); 379 380 return 0; 381} 382 383void HTMLOptionElement::didAddUserAgentShadowRoot(ShadowRoot& root) 384{ 385 updateLabel(); 386} 387 388void HTMLOptionElement::updateLabel() 389{ 390 if (ShadowRoot* root = userAgentShadowRoot()) 391 root->setTextContent(textIndentedToRespectGroupLabel()); 392} 393 394bool HTMLOptionElement::spatialNavigationFocused() const 395{ 396 HTMLSelectElement* select = ownerSelectElement(); 397 if (!select || !select->focused()) 398 return false; 399 return select->spatialNavigationFocusedOption() == this; 400} 401 402bool HTMLOptionElement::isDisplayNone() const 403{ 404 // If m_style is not set, then the node is still unattached. 405 // We have to wait till it gets attached to read the display property. 406 if (!m_style) 407 return false; 408 409 if (m_style->display() != NONE) { 410 Element* parent = parentElement(); 411 ASSERT(parent); 412 if (isHTMLOptGroupElement(*parent)) { 413 RenderStyle* parentStyle = parent->renderStyle() ? parent->renderStyle() : parent->computedStyle(); 414 return !parentStyle || parentStyle->display() == NONE; 415 } 416 } 417 return m_style->display() == NONE; 418} 419 420} // namespace blink 421