Pass Attribute by const reference as much as possible.
[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 "HTMLNames.h"
34 #include "HTMLParserIdioms.h"
35 #include "HTMLSelectElement.h"
36 #include "NodeRenderStyle.h"
37 #include "NodeRenderingContext.h"
38 #include "RenderMenuList.h"
39 #include "RenderTheme.h"
40 #include "ScriptElement.h"
41 #include "StyleResolver.h"
42 #include "Text.h"
43 #include <wtf/StdLibExtras.h>
44 #include <wtf/Vector.h>
45 #include <wtf/text/StringBuilder.h>
46
47 namespace WebCore {
48
49 using namespace HTMLNames;
50
51 HTMLOptionElement::HTMLOptionElement(const QualifiedName& tagName, Document* document)
52     : HTMLElement(tagName, document)
53     , m_disabled(false)
54     , m_isSelected(false)
55 {
56     ASSERT(hasTagName(optionTag));
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, ExceptionCode& ec)
71 {
72     RefPtr<HTMLOptionElement> element = adoptRef(new HTMLOptionElement(optionTag, document));
73
74     RefPtr<Text> text = Text::create(document, data.isNull() ? "" : data);
75
76     ec = 0;
77     element->appendChild(text.release(), ec);
78     if (ec)
79         return 0;
80
81     if (!value.isNull())
82         element->setValue(value);
83     if (defaultSelected)
84         element->setAttribute(selectedAttr, emptyAtom);
85     element->setSelected(selected);
86
87     return element.release();
88 }
89
90 void HTMLOptionElement::attach()
91 {
92     if (parentNode()->renderStyle())
93         setRenderStyle(styleForRenderer());
94     HTMLElement::attach();
95 }
96
97 void HTMLOptionElement::detach()
98 {
99     m_style.clear();
100     HTMLElement::detach();
101 }
102
103 bool HTMLOptionElement::supportsFocus() const
104 {
105     return HTMLElement::supportsFocus();
106 }
107
108 bool HTMLOptionElement::isFocusable() const
109 {
110     // Option elements do not have a renderer so we check the renderStyle instead.
111     return supportsFocus() && renderStyle() && renderStyle()->display() != NONE;
112 }
113
114 String HTMLOptionElement::text() const
115 {
116     Document* document = this->document();
117     String text;
118
119     // WinIE does not use the label attribute, so as a quirk, we ignore it.
120     if (!document->inQuirksMode())
121         text = fastGetAttribute(labelAttr);
122
123     // FIXME: The following treats an element with the label attribute set to
124     // the empty string the same as an element with no label attribute at all.
125     // Is that correct? If it is, then should the label function work the same way?
126     if (text.isEmpty())
127         text = collectOptionInnerText();
128
129     // FIXME: Is displayStringModifiedByEncoding helpful here?
130     // If it's correct here, then isn't it needed in the value and label functions too?
131     return document->displayStringModifiedByEncoding(text).stripWhiteSpace(isHTMLSpace).simplifyWhiteSpace(isHTMLSpace);
132 }
133
134 void HTMLOptionElement::setText(const String &text, ExceptionCode& ec)
135 {
136     RefPtr<Node> protectFromMutationEvents(this);
137
138     // Changing the text causes a recalc of a select's items, which will reset the selected
139     // index to the first item if the select is single selection with a menu list. We attempt to
140     // preserve the selected item.
141     RefPtr<HTMLSelectElement> select = ownerSelectElement();
142     bool selectIsMenuList = select && select->usesMenuList();
143     int oldSelectedIndex = selectIsMenuList ? select->selectedIndex() : -1;
144
145     // Handle the common special case where there's exactly 1 child node, and it's a text node.
146     Node* child = firstChild();
147     if (child && child->isTextNode() && !child->nextSibling())
148         toText(child)->setData(text, ec);
149     else {
150         removeChildren();
151         appendChild(Text::create(document(), text), ec);
152     }
153     
154     if (selectIsMenuList && select->selectedIndex() != oldSelectedIndex)
155         select->setSelectedIndex(oldSelectedIndex);
156 }
157
158 void HTMLOptionElement::accessKeyAction(bool)
159 {
160     HTMLSelectElement* select = ownerSelectElement();
161     if (select)
162         select->accessKeySetSelectedIndex(index());
163 }
164
165 int HTMLOptionElement::index() const
166 {
167     // It would be faster to cache the index, but harder to get it right in all cases.
168
169     HTMLSelectElement* selectElement = ownerSelectElement();
170     if (!selectElement)
171         return 0;
172
173     int optionIndex = 0;
174
175     const Vector<HTMLElement*>& items = selectElement->listItems();
176     size_t length = items.size();
177     for (size_t i = 0; i < length; ++i) {
178         if (!items[i]->hasTagName(optionTag))
179             continue;
180         if (items[i] == this)
181             return optionIndex;
182         ++optionIndex;
183     }
184
185     return 0;
186 }
187
188 void HTMLOptionElement::parseAttribute(const Attribute& attribute)
189 {
190     if (attribute.name() == disabledAttr) {
191         bool oldDisabled = m_disabled;
192         m_disabled = !attribute.isNull();
193         if (oldDisabled != m_disabled) {
194             setNeedsStyleRecalc();
195             if (renderer() && renderer()->style()->hasAppearance())
196                 renderer()->theme()->stateChanged(renderer(), EnabledState);
197         }
198     } else if (attribute.name() == selectedAttr) {
199         // FIXME: This doesn't match what the HTML specification says.
200         // The specification implies that removing the selected attribute or
201         // changing the value of a selected attribute that is already present
202         // has no effect on whether the element is selected. Further, it seems
203         // that we need to do more than just set m_isSelected to select in that
204         // case; we'd need to do the other work from the setSelected function.
205         m_isSelected = !attribute.isNull();
206     } else
207         HTMLElement::parseAttribute(attribute);
208 }
209
210 String HTMLOptionElement::value() const
211 {
212     const AtomicString& value = fastGetAttribute(valueAttr);
213     if (!value.isNull())
214         return value;
215     return collectOptionInnerText().stripWhiteSpace(isHTMLSpace).simplifyWhiteSpace(isHTMLSpace);
216 }
217
218 void HTMLOptionElement::setValue(const String& value)
219 {
220     setAttribute(valueAttr, value);
221 }
222
223 bool HTMLOptionElement::selected()
224 {
225     if (HTMLSelectElement* select = ownerSelectElement())
226         select->updateListItemSelectedStates();
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     setNeedsStyleRecalc();
248 }
249
250 void HTMLOptionElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
251 {
252     if (HTMLSelectElement* select = ownerSelectElement())
253         select->optionElementChildrenChanged();
254     HTMLElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
255 }
256
257 HTMLSelectElement* HTMLOptionElement::ownerSelectElement() const
258 {
259     ContainerNode* select = parentNode();
260     while (select && !select->hasTagName(selectTag))
261         select = select->parentNode();
262
263     if (!select)
264         return 0;
265
266     return toHTMLSelectElement(select);
267 }
268
269 String HTMLOptionElement::label() const
270 {
271     const AtomicString& label = fastGetAttribute(labelAttr);
272     if (!label.isNull())
273         return label; 
274     return collectOptionInnerText().stripWhiteSpace(isHTMLSpace).simplifyWhiteSpace(isHTMLSpace);
275 }
276
277 void HTMLOptionElement::setLabel(const String& label)
278 {
279     setAttribute(labelAttr, label);
280 }
281
282 void HTMLOptionElement::setRenderStyle(PassRefPtr<RenderStyle> newStyle)
283 {
284     m_style = newStyle;
285     if (HTMLSelectElement* select = ownerSelectElement()) {
286         if (RenderObject* renderer = select->renderer())
287             renderer->repaint();
288     }
289 }
290
291 RenderStyle* HTMLOptionElement::nonRendererRenderStyle() const
292 {
293     return m_style.get();
294 }
295
296 String HTMLOptionElement::textIndentedToRespectGroupLabel() const
297 {
298     ContainerNode* parent = parentNode();
299     if (parent && parent->hasTagName(optgroupTag))
300         return "    " + text();
301     return text();
302 }
303
304 bool HTMLOptionElement::disabled() const
305 {
306     return ownElementDisabled() || (parentNode() && parentNode()->isHTMLElement() && static_cast<HTMLElement*>(parentNode())->disabled());
307 }
308
309 Node::InsertionNotificationRequest HTMLOptionElement::insertedInto(Node* insertionPoint)
310 {
311     if (HTMLSelectElement* select = ownerSelectElement()) {
312         select->setRecalcListItems();
313         // Do not call selected() since calling updateListItemSelectedStates()
314         // at this time won't do the right thing. (Why, exactly?)
315         // FIXME: Might be better to call this unconditionally, always passing m_isSelected,
316         // rather than only calling it if we are selected.
317         if (m_isSelected)
318             select->optionSelectionStateChanged(this, true);
319         select->scrollToSelection();
320     }
321
322     return HTMLElement::insertedInto(insertionPoint);
323 }
324
325 String HTMLOptionElement::collectOptionInnerText() const
326 {
327     StringBuilder text;
328     for (Node* node = firstChild(); node; ) {
329         if (node->isTextNode())
330             text.append(node->nodeValue());
331         // Text nodes inside script elements are not part of the option text.
332         if (node->isElementNode() && toScriptElement(toElement(node)))
333             node = node->traverseNextSibling(this);
334         else
335             node = node->traverseNextNode(this);
336     }
337     return text.toString();
338 }
339
340 #ifndef NDEBUG
341
342 HTMLOptionElement* toHTMLOptionElement(Node* node)
343 {
344     ASSERT(!node || node->hasTagName(optionTag));
345     return static_cast<HTMLOptionElement*>(node);
346 }
347
348 const HTMLOptionElement* toHTMLOptionElement(const Node* node)
349 {
350     ASSERT(!node || node->hasTagName(optionTag));
351     return static_cast<const HTMLOptionElement*>(node);
352 }
353
354 #endif
355
356 } // namespace