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/core/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/dom/shadow/ShadowRoot.h"
     38 #include "core/html/HTMLDataListElement.h"
     39 #include "core/html/HTMLOptGroupElement.h"
     40 #include "core/html/HTMLSelectElement.h"
     41 #include "core/html/parser/HTMLParserIdioms.h"
     42 #include "core/rendering/RenderTheme.h"
     43 #include "wtf/Vector.h"
     44 #include "wtf/text/StringBuilder.h"
     45 
     46 namespace blink {
     47 
     48 using namespace HTMLNames;
     49 
     50 HTMLOptionElement::HTMLOptionElement(Document& document)
     51     : HTMLElement(optionTag, document)
     52     , m_disabled(false)
     53     , m_isSelected(false)
     54 {
     55     setHasCustomStyleCallbacks();
     56 }
     57 
     58 HTMLOptionElement::~HTMLOptionElement()
     59 {
     60 }
     61 
     62 PassRefPtrWillBeRawPtr<HTMLOptionElement> HTMLOptionElement::create(Document& document)
     63 {
     64     RefPtrWillBeRawPtr<HTMLOptionElement> option = adoptRefWillBeNoop(new HTMLOptionElement(document));
     65     option->ensureUserAgentShadowRoot();
     66     return option.release();
     67 }
     68 
     69 PassRefPtrWillBeRawPtr<HTMLOptionElement> HTMLOptionElement::createForJSConstructor(Document& document, const String& data, const AtomicString& value,
     70     bool defaultSelected, bool selected, ExceptionState& exceptionState)
     71 {
     72     RefPtrWillBeRawPtr<HTMLOptionElement> element = adoptRefWillBeNoop(new HTMLOptionElement(document));
     73     element->ensureUserAgentShadowRoot();
     74     element->appendChild(Text::create(document, data.isNull() ? "" : data), exceptionState);
     75     if (exceptionState.hadException())
     76         return nullptr;
     77 
     78     if (!value.isNull())
     79         element->setValue(value);
     80     if (defaultSelected)
     81         element->setAttribute(selectedAttr, emptyAtom);
     82     element->setSelected(selected);
     83 
     84     return element.release();
     85 }
     86 
     87 void HTMLOptionElement::attach(const AttachContext& context)
     88 {
     89     AttachContext optionContext(context);
     90     if (context.resolvedStyle) {
     91         ASSERT(!m_style || m_style == context.resolvedStyle);
     92         m_style = context.resolvedStyle;
     93     } else {
     94         updateNonRenderStyle();
     95         optionContext.resolvedStyle = m_style.get();
     96     }
     97     HTMLElement::attach(optionContext);
     98 }
     99 
    100 void HTMLOptionElement::detach(const AttachContext& context)
    101 {
    102     m_style.clear();
    103     HTMLElement::detach(context);
    104 }
    105 
    106 String HTMLOptionElement::text() const
    107 {
    108     Document& document = this->document();
    109     String text;
    110 
    111     // WinIE does not use the label attribute, so as a quirk, we ignore it.
    112     if (!document.inQuirksMode())
    113         text = fastGetAttribute(labelAttr);
    114 
    115     // FIXME: The following treats an element with the label attribute set to
    116     // the empty string the same as an element with no label attribute at all.
    117     // Is that correct? If it is, then should the label function work the same way?
    118     if (text.isEmpty())
    119         text = collectOptionInnerText();
    120 
    121     return text.stripWhiteSpace(isHTMLSpace<UChar>).simplifyWhiteSpace(isHTMLSpace<UChar>);
    122 }
    123 
    124 void HTMLOptionElement::setText(const String &text, ExceptionState& exceptionState)
    125 {
    126     RefPtrWillBeRawPtr<Node> protectFromMutationEvents(this);
    127 
    128     // Changing the text causes a recalc of a select's items, which will reset the selected
    129     // index to the first item if the select is single selection with a menu list. We attempt to
    130     // preserve the selected item.
    131     RefPtrWillBeRawPtr<HTMLSelectElement> select = ownerSelectElement();
    132     bool selectIsMenuList = select && select->usesMenuList();
    133     int oldSelectedIndex = selectIsMenuList ? select->selectedIndex() : -1;
    134 
    135     // Handle the common special case where there's exactly 1 child node, and it's a text node.
    136     Node* child = firstChild();
    137     if (child && child->isTextNode() && !child->nextSibling())
    138         toText(child)->setData(text);
    139     else {
    140         removeChildren();
    141         appendChild(Text::create(document(), text), exceptionState);
    142     }
    143 
    144     if (selectIsMenuList && select->selectedIndex() != oldSelectedIndex)
    145         select->setSelectedIndex(oldSelectedIndex);
    146 }
    147 
    148 void HTMLOptionElement::accessKeyAction(bool)
    149 {
    150     if (HTMLSelectElement* select = ownerSelectElement())
    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 WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = selectElement->listItems();
    165     size_t length = items.size();
    166     for (size_t i = 0; i < length; ++i) {
    167         if (!isHTMLOptionElement(*items[i]))
    168             continue;
    169         if (items[i].get() == 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             pseudoStateChanged(CSSSelector::PseudoDisabled);
    187             pseudoStateChanged(CSSSelector::PseudoEnabled);
    188             if (renderer() && renderer()->style()->hasAppearance())
    189                 RenderTheme::theme().stateChanged(renderer(), EnabledControlState);
    190         }
    191     } else if (name == selectedAttr) {
    192         if (bool willBeSelected = !value.isNull())
    193             setSelected(willBeSelected);
    194     } else if (name == labelAttr) {
    195         updateLabel();
    196     } else
    197         HTMLElement::parseAttribute(name, value);
    198 }
    199 
    200 String HTMLOptionElement::value() const
    201 {
    202     const AtomicString& value = fastGetAttribute(valueAttr);
    203     if (!value.isNull())
    204         return value;
    205     return collectOptionInnerText().stripWhiteSpace(isHTMLSpace<UChar>).simplifyWhiteSpace(isHTMLSpace<UChar>);
    206 }
    207 
    208 void HTMLOptionElement::setValue(const AtomicString& value)
    209 {
    210     setAttribute(valueAttr, value);
    211 }
    212 
    213 bool HTMLOptionElement::selected() const
    214 {
    215     if (HTMLSelectElement* select = ownerSelectElement()) {
    216         // If a stylesheet contains option:checked selectors, this function is
    217         // called during parsing. updateListItemSelectedStates() is O(N) where N
    218         // is the number of option elements, so the <select> parsing would be
    219         // O(N^2) without the isFinishedParsingChildren check. Also,
    220         // updateListItemSelectedStates() determines default selection, and we'd
    221         // like to avoid to determine default selection with incomplete option
    222         // list.
    223         if (!select->isFinishedParsingChildren())
    224             return m_isSelected;
    225         select->updateListItemSelectedStates();
    226     }
    227     return m_isSelected;
    228 }
    229 
    230 void HTMLOptionElement::setSelected(bool selected)
    231 {
    232     if (m_isSelected == selected)
    233         return;
    234 
    235     setSelectedState(selected);
    236 
    237     if (HTMLSelectElement* select = ownerSelectElement())
    238         select->optionSelectionStateChanged(this, selected);
    239 }
    240 
    241 void HTMLOptionElement::setSelectedState(bool selected)
    242 {
    243     if (m_isSelected == selected)
    244         return;
    245 
    246     m_isSelected = selected;
    247     pseudoStateChanged(CSSSelector::PseudoChecked);
    248 
    249     if (HTMLSelectElement* select = ownerSelectElement())
    250         select->invalidateSelectedItems();
    251 }
    252 
    253 void HTMLOptionElement::childrenChanged(const ChildrenChange& change)
    254 {
    255     if (HTMLDataListElement* dataList = ownerDataListElement())
    256         dataList->optionElementChildrenChanged();
    257     else if (HTMLSelectElement* select = ownerSelectElement())
    258         select->optionElementChildrenChanged();
    259     updateLabel();
    260     HTMLElement::childrenChanged(change);
    261 }
    262 
    263 HTMLDataListElement* HTMLOptionElement::ownerDataListElement() const
    264 {
    265     return Traversal<HTMLDataListElement>::firstAncestor(*this);
    266 }
    267 
    268 HTMLSelectElement* HTMLOptionElement::ownerSelectElement() const
    269 {
    270     return Traversal<HTMLSelectElement>::firstAncestor(*this);
    271 }
    272 
    273 String HTMLOptionElement::label() const
    274 {
    275     const AtomicString& label = fastGetAttribute(labelAttr);
    276     if (!label.isNull())
    277         return label;
    278     return collectOptionInnerText().stripWhiteSpace(isHTMLSpace<UChar>).simplifyWhiteSpace(isHTMLSpace<UChar>);
    279 }
    280 
    281 void HTMLOptionElement::setLabel(const AtomicString& label)
    282 {
    283     setAttribute(labelAttr, label);
    284 }
    285 
    286 void HTMLOptionElement::updateNonRenderStyle()
    287 {
    288     m_style = originalStyleForRenderer();
    289     if (HTMLSelectElement* select = ownerSelectElement())
    290         select->updateListOnRenderer();
    291 }
    292 
    293 RenderStyle* HTMLOptionElement::nonRendererStyle() const
    294 {
    295     return m_style.get();
    296 }
    297 
    298 PassRefPtr<RenderStyle> HTMLOptionElement::customStyleForRenderer()
    299 {
    300     updateNonRenderStyle();
    301     return m_style;
    302 }
    303 
    304 void HTMLOptionElement::didRecalcStyle(StyleRecalcChange change)
    305 {
    306     if (change == NoChange)
    307         return;
    308 
    309     // FIXME: We ask our owner select to repaint regardless of which property changed.
    310     if (HTMLSelectElement* select = ownerSelectElement()) {
    311         if (RenderObject* renderer = select->renderer())
    312             renderer->setShouldDoFullPaintInvalidation(true);
    313     }
    314 }
    315 
    316 String HTMLOptionElement::textIndentedToRespectGroupLabel() const
    317 {
    318     ContainerNode* parent = parentNode();
    319     if (parent && isHTMLOptGroupElement(*parent))
    320         return "    " + text();
    321     return text();
    322 }
    323 
    324 bool HTMLOptionElement::isDisabledFormControl() const
    325 {
    326     if (ownElementDisabled())
    327         return true;
    328     if (Element* parent = parentElement())
    329         return isHTMLOptGroupElement(*parent) && parent->isDisabledFormControl();
    330     return false;
    331 }
    332 
    333 Node::InsertionNotificationRequest HTMLOptionElement::insertedInto(ContainerNode* insertionPoint)
    334 {
    335     if (HTMLSelectElement* select = ownerSelectElement()) {
    336         select->setRecalcListItems();
    337         // Do not call selected() since calling updateListItemSelectedStates()
    338         // at this time won't do the right thing. (Why, exactly?)
    339         if (m_isSelected) {
    340             // FIXME: Might be better to call this unconditionally, always
    341             // passing m_isSelected, rather than only calling it if we are
    342             // selected.
    343             select->optionSelectionStateChanged(this, true);
    344             select->scrollToSelection();
    345         }
    346     }
    347 
    348     return HTMLElement::insertedInto(insertionPoint);
    349 }
    350 
    351 void HTMLOptionElement::removedFrom(ContainerNode* insertionPoint)
    352 {
    353     if (HTMLSelectElement* select = Traversal<HTMLSelectElement>::firstAncestorOrSelf(*insertionPoint)) {
    354         select->setRecalcListItems();
    355         select->optionRemoved(*this);
    356     }
    357     HTMLElement::removedFrom(insertionPoint);
    358 }
    359 
    360 String HTMLOptionElement::collectOptionInnerText() const
    361 {
    362     StringBuilder text;
    363     for (Node* node = firstChild(); node; ) {
    364         if (node->isTextNode())
    365             text.append(node->nodeValue());
    366         // Text nodes inside script elements are not part of the option text.
    367         if (node->isElementNode() && toScriptLoaderIfPossible(toElement(node)))
    368             node = NodeTraversal::nextSkippingChildren(*node, this);
    369         else
    370             node = NodeTraversal::next(*node, this);
    371     }
    372     return text.toString();
    373 }
    374 
    375 HTMLFormElement* HTMLOptionElement::form() const
    376 {
    377     if (HTMLSelectElement* selectElement = ownerSelectElement())
    378         return selectElement->formOwner();
    379 
    380     return 0;
    381 }
    382 
    383 void HTMLOptionElement::didAddUserAgentShadowRoot(ShadowRoot& root)
    384 {
    385     updateLabel();
    386 }
    387 
    388 void HTMLOptionElement::updateLabel()
    389 {
    390     if (ShadowRoot* root = userAgentShadowRoot())
    391         root->setTextContent(textIndentedToRespectGroupLabel());
    392 }
    393 
    394 bool HTMLOptionElement::spatialNavigationFocused() const
    395 {
    396     HTMLSelectElement* select = ownerSelectElement();
    397     if (!select || !select->focused())
    398         return false;
    399     return select->spatialNavigationFocusedOption() == this;
    400 }
    401 
    402 bool HTMLOptionElement::isDisplayNone() const
    403 {
    404     // If m_style is not set, then the node is still unattached.
    405     // We have to wait till it gets attached to read the display property.
    406     if (!m_style)
    407         return false;
    408 
    409     if (m_style->display() != NONE) {
    410         Element* parent = parentElement();
    411         ASSERT(parent);
    412         if (isHTMLOptGroupElement(*parent)) {
    413             RenderStyle* parentStyle = parent->renderStyle() ? parent->renderStyle() : parent->computedStyle();
    414             return !parentStyle || parentStyle->display() == NONE;
    415         }
    416     }
    417     return m_style->display() == NONE;
    418 }
    419 
    420 } // namespace blink
    421