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 "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
HTMLOptionElement(Document & document)50 HTMLOptionElement::HTMLOptionElement(Document& document)
51 : HTMLElement(optionTag, document)
52 , m_disabled(false)
53 , m_isSelected(false)
54 {
55 setHasCustomStyleCallbacks();
56 }
57
~HTMLOptionElement()58 HTMLOptionElement::~HTMLOptionElement()
59 {
60 }
61
create(Document & document)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
createForJSConstructor(Document & document,const String & data,const AtomicString & value,bool defaultSelected,bool selected,ExceptionState & exceptionState)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
attach(const AttachContext & context)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
detach(const AttachContext & context)100 void HTMLOptionElement::detach(const AttachContext& context)
101 {
102 m_style.clear();
103 HTMLElement::detach(context);
104 }
105
text() const106 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
setText(const String & text,ExceptionState & exceptionState)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
accessKeyAction(bool)148 void HTMLOptionElement::accessKeyAction(bool)
149 {
150 if (HTMLSelectElement* select = ownerSelectElement())
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 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
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 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
value() const200 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
setValue(const AtomicString & value)208 void HTMLOptionElement::setValue(const AtomicString& value)
209 {
210 setAttribute(valueAttr, value);
211 }
212
selected() const213 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
setSelected(bool selected)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
setSelectedState(bool selected)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
childrenChanged(const ChildrenChange & change)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
ownerDataListElement() const263 HTMLDataListElement* HTMLOptionElement::ownerDataListElement() const
264 {
265 return Traversal<HTMLDataListElement>::firstAncestor(*this);
266 }
267
ownerSelectElement() const268 HTMLSelectElement* HTMLOptionElement::ownerSelectElement() const
269 {
270 return Traversal<HTMLSelectElement>::firstAncestor(*this);
271 }
272
label() const273 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
setLabel(const AtomicString & label)281 void HTMLOptionElement::setLabel(const AtomicString& label)
282 {
283 setAttribute(labelAttr, label);
284 }
285
updateNonRenderStyle()286 void HTMLOptionElement::updateNonRenderStyle()
287 {
288 m_style = originalStyleForRenderer();
289 if (HTMLSelectElement* select = ownerSelectElement())
290 select->updateListOnRenderer();
291 }
292
nonRendererStyle() const293 RenderStyle* HTMLOptionElement::nonRendererStyle() const
294 {
295 return m_style.get();
296 }
297
customStyleForRenderer()298 PassRefPtr<RenderStyle> HTMLOptionElement::customStyleForRenderer()
299 {
300 updateNonRenderStyle();
301 return m_style;
302 }
303
didRecalcStyle(StyleRecalcChange change)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
textIndentedToRespectGroupLabel() const316 String HTMLOptionElement::textIndentedToRespectGroupLabel() const
317 {
318 ContainerNode* parent = parentNode();
319 if (parent && isHTMLOptGroupElement(*parent))
320 return " " + text();
321 return text();
322 }
323
isDisabledFormControl() const324 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
insertedInto(ContainerNode * insertionPoint)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
removedFrom(ContainerNode * insertionPoint)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
collectOptionInnerText() const360 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
form() const375 HTMLFormElement* HTMLOptionElement::form() const
376 {
377 if (HTMLSelectElement* selectElement = ownerSelectElement())
378 return selectElement->formOwner();
379
380 return 0;
381 }
382
didAddUserAgentShadowRoot(ShadowRoot & root)383 void HTMLOptionElement::didAddUserAgentShadowRoot(ShadowRoot& root)
384 {
385 updateLabel();
386 }
387
updateLabel()388 void HTMLOptionElement::updateLabel()
389 {
390 if (ShadowRoot* root = userAgentShadowRoot())
391 root->setTextContent(textIndentedToRespectGroupLabel());
392 }
393
spatialNavigationFocused() const394 bool HTMLOptionElement::spatialNavigationFocused() const
395 {
396 HTMLSelectElement* select = ownerSelectElement();
397 if (!select || !select->focused())
398 return false;
399 return select->spatialNavigationFocusedOption() == this;
400 }
401
isDisplayNone() const402 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