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