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