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 "HTMLInputElement.h" 27#include "HTMLNames.h" 28#include "InputTypeNames.h" 29#include "KeyboardEvent.h" 30#include "LocalizedStrings.h" 31#include "MouseEvent.h" 32#include "NodeTraversal.h" 33#include "Settings.h" 34#include "SpatialNavigation.h" 35#include <wtf/PassOwnPtr.h> 36 37namespace WebCore { 38 39using namespace HTMLNames; 40 41PassOwnPtr<InputType> RadioInputType::create(HTMLInputElement* element) 42{ 43 return adoptPtr(new RadioInputType(element)); 44} 45 46const AtomicString& RadioInputType::formControlType() const 47{ 48 return InputTypeNames::radio(); 49} 50 51bool RadioInputType::valueMissing(const String&) const 52{ 53 return element()->isInRequiredRadioButtonGroup() && !element()->checkedRadioButtonForGroup(); 54} 55 56String RadioInputType::valueMissingText() const 57{ 58 return validationMessageValueMissingForRadioText(); 59} 60 61void RadioInputType::handleClickEvent(MouseEvent* event) 62{ 63 event->setDefaultHandled(); 64} 65 66void RadioInputType::handleKeydownEvent(KeyboardEvent* event) 67{ 68 BaseCheckableInputType::handleKeydownEvent(event); 69 if (event->defaultHandled()) 70 return; 71 const String& key = event->keyIdentifier(); 72 if (key != "Up" && key != "Down" && key != "Left" && key != "Right") 73 return; 74 75 // Left and up mean "previous radio button". 76 // Right and down mean "next radio button". 77 // Tested in WinIE, and even for RTL, left still means previous radio button (and so moves 78 // to the right). Seems strange, but we'll match it. 79 // However, when using Spatial Navigation, we need to be able to navigate without changing the selection. 80 Document* document = element()->document(); 81 if (isSpatialNavigationEnabled(document->frame())) 82 return; 83 bool forward = (key == "Down" || key == "Right"); 84 85 // We can only stay within the form's children if the form hasn't been demoted to a leaf because 86 // of malformed HTML. 87 Node* node = element(); 88 while ((node = (forward ? NodeTraversal::next(node) : NodeTraversal::previous(node)))) { 89 // Once we encounter a form element, we know we're through. 90 if (node->hasTagName(formTag)) 91 break; 92 // Look for more radio buttons. 93 if (!node->hasTagName(inputTag)) 94 continue; 95 RefPtr<HTMLInputElement> inputElement = static_cast<HTMLInputElement*>(node); 96 if (inputElement->form() != element()->form()) 97 break; 98 if (inputElement->isRadioButton() && inputElement->name() == element()->name() && inputElement->isFocusable()) { 99 document->setFocusedElement(inputElement); 100 inputElement->dispatchSimulatedClick(event, SendNoEvents, DoNotShowPressedLook); 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(KeyboardEvent* event) const 120{ 121 if (!InputType::isKeyboardFocusable(event)) 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* currentFocusedNode = element()->document()->focusedElement(); 131 if (currentFocusedNode && currentFocusedNode->hasTagName(inputTag)) { 132 HTMLInputElement* focusedInput = static_cast<HTMLInputElement*>(currentFocusedNode); 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 162#if PLATFORM(IOS) 163 state->indeterminate = element()->indeterminate(); 164 165 if (element()->indeterminate()) 166 element()->setIndeterminate(false); 167#endif 168 169 element()->setChecked(true, DispatchChangeEvent); 170 171 return state.release(); 172} 173 174void RadioInputType::didDispatchClick(Event* event, const ClickHandlingState& state) 175{ 176 if (event->defaultPrevented() || event->defaultHandled()) { 177 // Restore the original selected radio button if possible. 178 // Make sure it is still a radio button and only do the restoration if it still belongs to our group. 179 HTMLInputElement* checkedRadioButton = state.checkedRadioButton.get(); 180 if (checkedRadioButton 181 && checkedRadioButton->isRadioButton() 182 && checkedRadioButton->form() == element()->form() 183 && checkedRadioButton->name() == element()->name()) { 184 checkedRadioButton->setChecked(true); 185 } 186 187#if PLATFORM(IOS) 188 element()->setIndeterminate(state.indeterminate); 189#endif 190 191 } 192 193 // The work we did in willDispatchClick was default handling. 194 event->setDefaultHandled(); 195} 196 197bool RadioInputType::isRadioButton() const 198{ 199 return true; 200} 201 202bool RadioInputType::supportsIndeterminateAppearance() const 203{ 204#if PLATFORM(IOS) 205 return true; 206#else 207 return false; 208#endif 209} 210 211} // namespace WebCore 212