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