• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "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 
HTMLOptionElement(Document & document)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 
create(Document & document)58 PassRefPtr<HTMLOptionElement> HTMLOptionElement::create(Document& document)
59 {
60     return adoptRef(new HTMLOptionElement(document));
61 }
62 
createForJSConstructor(Document & document,const String & data,const AtomicString & value,bool defaultSelected,bool selected,ExceptionState & exceptionState)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 
attach(const AttachContext & context)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 
detach(const AttachContext & context)93 void HTMLOptionElement::detach(const AttachContext& context)
94 {
95     m_style.clear();
96     HTMLElement::detach(context);
97 }
98 
rendererIsFocusable() const99 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 
text() const105 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 
setText(const String & text,ExceptionState & exceptionState)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 
accessKeyAction(bool)147 void HTMLOptionElement::accessKeyAction(bool)
148 {
149     HTMLSelectElement* select = ownerSelectElement();
150     if (select)
151         select->accessKeySetSelectedIndex(index());
152 }
153 
index() const154 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 
parseAttribute(const QualifiedName & name,const AtomicString & value)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 
value() const197 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 
setValue(const AtomicString & value)205 void HTMLOptionElement::setValue(const AtomicString& value)
206 {
207     setAttribute(valueAttr, value);
208 }
209 
selected()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 
setSelected(bool selected)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 
setSelectedState(bool selected)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 
childrenChanged(bool changedByParser,Node * beforeChange,Node * afterChange,int childCountDelta)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 
ownerDataListElement() const259 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 
ownerSelectElement() const268 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 
label() const280 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 
setLabel(const AtomicString & label)288 void HTMLOptionElement::setLabel(const AtomicString& label)
289 {
290     setAttribute(labelAttr, label);
291 }
292 
updateNonRenderStyle()293 void HTMLOptionElement::updateNonRenderStyle()
294 {
295     m_style = originalStyleForRenderer();
296 }
297 
nonRendererStyle() const298 RenderStyle* HTMLOptionElement::nonRendererStyle() const
299 {
300     return m_style.get();
301 }
302 
customStyleForRenderer()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 
didRecalcStyle(StyleRecalcChange)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 
textIndentedToRespectGroupLabel() const321 String HTMLOptionElement::textIndentedToRespectGroupLabel() const
322 {
323     ContainerNode* parent = parentNode();
324     if (parent && isHTMLOptGroupElement(parent))
325         return "    " + text();
326     return text();
327 }
328 
isDisabledFormControl() const329 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 
insertedInto(ContainerNode * insertionPoint)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 
collectOptionInnerText() const354 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 
form() const369 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