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