Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / html / forms / RadioInputType.cpp
1 /*
2  * Copyright (C) 2005, 2011 Apple Inc. All rights reserved.
3  * Copyright (C) 2010 Google Inc. All rights reserved.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public License
16  * along with this library; see the file COPYING.LIB.  If not, write to
17  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  *
20  */
21
22 #include "config.h"
23 #include "core/html/forms/RadioInputType.h"
24
25 #include "core/HTMLNames.h"
26 #include "core/InputTypeNames.h"
27 #include "core/dom/Document.h"
28 #include "core/dom/ElementTraversal.h"
29 #include "core/events/KeyboardEvent.h"
30 #include "core/events/MouseEvent.h"
31 #include "core/html/HTMLInputElement.h"
32 #include "core/page/SpatialNavigation.h"
33 #include "platform/text/PlatformLocale.h"
34 #include "wtf/PassOwnPtr.h"
35
36 namespace blink {
37
38 namespace {
39
40 HTMLElement* nextElement(const HTMLElement& element, HTMLFormElement* stayWithin, bool forward)
41 {
42     return forward ? Traversal<HTMLElement>::next(element, (Node* )stayWithin) : Traversal<HTMLElement>::previous(element, (Node* )stayWithin);
43 }
44
45 } // namespace
46
47 using namespace HTMLNames;
48
49 PassRefPtrWillBeRawPtr<InputType> RadioInputType::create(HTMLInputElement& element)
50 {
51     return adoptRefWillBeNoop(new RadioInputType(element));
52 }
53
54 const AtomicString& RadioInputType::formControlType() const
55 {
56     return InputTypeNames::radio;
57 }
58
59 bool RadioInputType::valueMissing(const String&) const
60 {
61     return element().isInRequiredRadioButtonGroup() && !element().checkedRadioButtonForGroup();
62 }
63
64 String RadioInputType::valueMissingText() const
65 {
66     return locale().queryString(blink::WebLocalizedString::ValidationValueMissingForRadio);
67 }
68
69 void RadioInputType::handleClickEvent(MouseEvent* event)
70 {
71     event->setDefaultHandled();
72 }
73
74 HTMLInputElement* RadioInputType::findNextFocusableRadioButtonInGroup(HTMLInputElement* currentElement, bool forward)
75 {
76     HTMLElement* htmlElement;
77     for (htmlElement = nextElement(*currentElement, element().form(), forward); htmlElement; htmlElement = nextElement(*htmlElement, element().form(), forward)) {
78         if (!isHTMLInputElement(*htmlElement))
79             continue;
80         HTMLInputElement* inputElement = toHTMLInputElement(htmlElement);
81         if (element().form() == inputElement->form() && inputElement->type() == InputTypeNames::radio && inputElement->name() == element().name() && inputElement->isFocusable())
82             return inputElement;
83     }
84     return nullptr;
85 }
86
87 void RadioInputType::handleKeydownEvent(KeyboardEvent* event)
88 {
89     BaseCheckableInputType::handleKeydownEvent(event);
90     if (event->defaultHandled())
91         return;
92     const String& key = event->keyIdentifier();
93     if (key != "Up" && key != "Down" && key != "Left" && key != "Right")
94         return;
95
96     // Left and up mean "previous radio button".
97     // Right and down mean "next radio button".
98     // Tested in WinIE, and even for RTL, left still means previous radio button
99     // (and so moves to the right). Seems strange, but we'll match it. However,
100     // when using Spatial Navigation, we need to be able to navigate without
101     // changing the selection.
102     Document& document = element().document();
103     if (isSpatialNavigationEnabled(document.frame()))
104         return;
105     bool forward = (key == "Down" || key == "Right");
106
107     // We can only stay within the form's children if the form hasn't been demoted to a leaf because
108     // of malformed HTML.
109     HTMLInputElement* inputElement = findNextFocusableRadioButtonInGroup(toHTMLInputElement(&element()), forward);
110     if (!inputElement) {
111         // Traverse in reverse direction till last or first radio button
112         forward = !(forward);
113         HTMLInputElement* nextInputElement = findNextFocusableRadioButtonInGroup(toHTMLInputElement(&element()), forward);
114         while (nextInputElement) {
115             inputElement = nextInputElement;
116             nextInputElement = findNextFocusableRadioButtonInGroup(nextInputElement, forward);
117         }
118     }
119     if (inputElement) {
120         RefPtrWillBeRawPtr<HTMLInputElement> protector(inputElement);
121         document.setFocusedElement(inputElement);
122         inputElement->dispatchSimulatedClick(event, SendNoEvents);
123         event->setDefaultHandled();
124         return;
125     }
126 }
127
128 void RadioInputType::handleKeyupEvent(KeyboardEvent* event)
129 {
130     const String& key = event->keyIdentifier();
131     if (key != "U+0020")
132         return;
133     // If an unselected radio is tabbed into (because the entire group has nothing
134     // checked, or because of some explicit .focus() call), then allow space to check it.
135     if (element().checked())
136         return;
137     dispatchSimulatedClickIfActive(event);
138 }
139
140 bool RadioInputType::isKeyboardFocusable() const
141 {
142     if (!InputType::isKeyboardFocusable())
143         return false;
144
145     // When using Spatial Navigation, every radio button should be focusable.
146     if (isSpatialNavigationEnabled(element().document().frame()))
147         return true;
148
149     // Never allow keyboard tabbing to leave you in the same radio group. Always
150     // skip any other elements in the group.
151     Element* currentFocusedElement = element().document().focusedElement();
152     if (isHTMLInputElement(currentFocusedElement)) {
153         HTMLInputElement& focusedInput = toHTMLInputElement(*currentFocusedElement);
154         if (focusedInput.type() == InputTypeNames::radio && focusedInput.form() == element().form() && focusedInput.name() == element().name())
155             return false;
156     }
157
158     // Allow keyboard focus if we're checked or if nothing in the group is checked.
159     return element().checked() || !element().checkedRadioButtonForGroup();
160 }
161
162 bool RadioInputType::shouldSendChangeEventAfterCheckedChanged()
163 {
164     // Don't send a change event for a radio button that's getting unchecked.
165     // This was done to match the behavior of other browsers.
166     return element().checked();
167 }
168
169 PassOwnPtrWillBeRawPtr<ClickHandlingState> RadioInputType::willDispatchClick()
170 {
171     // An event handler can use preventDefault or "return false" to reverse the selection we do here.
172     // The ClickHandlingState object contains what we need to undo what we did here in didDispatchClick.
173
174     // We want radio groups to end up in sane states, i.e., to have something checked.
175     // Therefore if nothing is currently selected, we won't allow the upcoming action to be "undone", since
176     // we want some object in the radio group to actually get selected.
177
178     OwnPtrWillBeRawPtr<ClickHandlingState> state = adoptPtrWillBeNoop(new ClickHandlingState);
179
180     state->checked = element().checked();
181     state->checkedRadioButton = element().checkedRadioButtonForGroup();
182     element().setChecked(true, DispatchChangeEvent);
183
184     return state.release();
185 }
186
187 void RadioInputType::didDispatchClick(Event* event, const ClickHandlingState& state)
188 {
189     if (event->defaultPrevented() || event->defaultHandled()) {
190         // Restore the original selected radio button if possible.
191         // Make sure it is still a radio button and only do the restoration if it still belongs to our group.
192         HTMLInputElement* checkedRadioButton = state.checkedRadioButton.get();
193         if (!checkedRadioButton)
194             element().setChecked(false);
195         else if (checkedRadioButton->type() == InputTypeNames::radio
196             && checkedRadioButton->form() == element().form()
197             && checkedRadioButton->name() == element().name())
198             checkedRadioButton->setChecked(true);
199     }
200
201     // The work we did in willDispatchClick was default handling.
202     event->setDefaultHandled();
203 }
204
205 bool RadioInputType::shouldAppearIndeterminate() const
206 {
207     return !element().checkedRadioButtonForGroup();
208 }
209
210 } // namespace blink