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