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