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