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 "HTMLNames.h" 26#include "InputTypeNames.h" 27#include "core/dom/NodeTraversal.h" 28#include "core/events/KeyboardEvent.h" 29#include "core/events/MouseEvent.h" 30#include "core/html/HTMLInputElement.h" 31#include "core/page/SpatialNavigation.h" 32#include "platform/text/PlatformLocale.h" 33#include "wtf/PassOwnPtr.h" 34 35namespace WebCore { 36 37using namespace HTMLNames; 38 39PassRefPtr<InputType> RadioInputType::create(HTMLInputElement& element) 40{ 41 return adoptRef(new RadioInputType(element)); 42} 43 44const AtomicString& RadioInputType::formControlType() const 45{ 46 return InputTypeNames::radio; 47} 48 49bool RadioInputType::valueMissing(const String&) const 50{ 51 return element().isInRequiredRadioButtonGroup() && !element().checkedRadioButtonForGroup(); 52} 53 54String RadioInputType::valueMissingText() const 55{ 56 return locale().queryString(blink::WebLocalizedString::ValidationValueMissingForRadio); 57} 58 59void RadioInputType::handleClickEvent(MouseEvent* event) 60{ 61 event->setDefaultHandled(); 62} 63 64void RadioInputType::handleKeydownEvent(KeyboardEvent* event) 65{ 66 BaseCheckableInputType::handleKeydownEvent(event); 67 if (event->defaultHandled()) 68 return; 69 const String& key = event->keyIdentifier(); 70 if (key != "Up" && key != "Down" && key != "Left" && key != "Right") 71 return; 72 73 // Left and up mean "previous radio button". 74 // Right and down mean "next radio button". 75 // Tested in WinIE, and even for RTL, left still means previous radio button 76 // (and so moves to the right). Seems strange, but we'll match it. However, 77 // when using Spatial Navigation, we need to be able to navigate without 78 // changing the selection. 79 Document& document = element().document(); 80 if (isSpatialNavigationEnabled(document.frame())) 81 return; 82 bool forward = (key == "Down" || key == "Right"); 83 84 // We can only stay within the form's children if the form hasn't been demoted to a leaf because 85 // of malformed HTML. 86 Node* node = &element(); 87 while ((node = (forward ? NodeTraversal::next(*node) : NodeTraversal::previous(*node)))) { 88 // Once we encounter a form element, we know we're through. 89 if (node->hasTagName(formTag)) 90 break; 91 // Look for more radio buttons. 92 if (!node->hasTagName(inputTag)) 93 continue; 94 HTMLInputElement* inputElement = toHTMLInputElement(node); 95 if (inputElement->form() != element().form()) 96 break; 97 if (inputElement->isRadioButton() && inputElement->name() == element().name() && inputElement->isFocusable()) { 98 RefPtr<HTMLInputElement> protector(inputElement); 99 document.setFocusedElement(inputElement); 100 inputElement->dispatchSimulatedClick(event, SendNoEvents); 101 event->setDefaultHandled(); 102 return; 103 } 104 } 105} 106 107void RadioInputType::handleKeyupEvent(KeyboardEvent* event) 108{ 109 const String& key = event->keyIdentifier(); 110 if (key != "U+0020") 111 return; 112 // If an unselected radio is tabbed into (because the entire group has nothing 113 // checked, or because of some explicit .focus() call), then allow space to check it. 114 if (element().checked()) 115 return; 116 dispatchSimulatedClickIfActive(event); 117} 118 119bool RadioInputType::isKeyboardFocusable() const 120{ 121 if (!InputType::isKeyboardFocusable()) 122 return false; 123 124 // When using Spatial Navigation, every radio button should be focusable. 125 if (isSpatialNavigationEnabled(element().document().frame())) 126 return true; 127 128 // Never allow keyboard tabbing to leave you in the same radio group. Always 129 // skip any other elements in the group. 130 Element* currentFocusedElement = element().document().focusedElement(); 131 if (currentFocusedElement && currentFocusedElement->hasTagName(inputTag)) { 132 HTMLInputElement* focusedInput = toHTMLInputElement(currentFocusedElement); 133 if (focusedInput->isRadioButton() && focusedInput->form() == element().form() && focusedInput->name() == element().name()) 134 return false; 135 } 136 137 // Allow keyboard focus if we're checked or if nothing in the group is checked. 138 return element().checked() || !element().checkedRadioButtonForGroup(); 139} 140 141bool RadioInputType::shouldSendChangeEventAfterCheckedChanged() 142{ 143 // Don't send a change event for a radio button that's getting unchecked. 144 // This was done to match the behavior of other browsers. 145 return element().checked(); 146} 147 148PassOwnPtr<ClickHandlingState> RadioInputType::willDispatchClick() 149{ 150 // An event handler can use preventDefault or "return false" to reverse the selection we do here. 151 // The ClickHandlingState object contains what we need to undo what we did here in didDispatchClick. 152 153 // We want radio groups to end up in sane states, i.e., to have something checked. 154 // Therefore if nothing is currently selected, we won't allow the upcoming action to be "undone", since 155 // we want some object in the radio group to actually get selected. 156 157 OwnPtr<ClickHandlingState> state = adoptPtr(new ClickHandlingState); 158 159 state->checked = element().checked(); 160 state->checkedRadioButton = element().checkedRadioButtonForGroup(); 161 element().setChecked(true, DispatchChangeEvent); 162 163 return state.release(); 164} 165 166void RadioInputType::didDispatchClick(Event* event, const ClickHandlingState& state) 167{ 168 if (event->defaultPrevented() || event->defaultHandled()) { 169 // Restore the original selected radio button if possible. 170 // Make sure it is still a radio button and only do the restoration if it still belongs to our group. 171 HTMLInputElement* checkedRadioButton = state.checkedRadioButton.get(); 172 if (checkedRadioButton 173 && checkedRadioButton->isRadioButton() 174 && checkedRadioButton->form() == element().form() 175 && checkedRadioButton->name() == element().name()) { 176 checkedRadioButton->setChecked(true); 177 } 178 } 179 180 // The work we did in willDispatchClick was default handling. 181 event->setDefaultHandled(); 182} 183 184bool RadioInputType::isRadioButton() const 185{ 186 return true; 187} 188 189bool RadioInputType::supportsIndeterminateAppearance() const 190{ 191 return false; 192} 193 194} // namespace WebCore 195