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