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