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