1 /* 2 * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * (C) 1999 Antti Koivisto (koivisto (at) kde.org) 4 * (C) 2001 Dirk Mueller (mueller (at) kde.org) 5 * Copyright (C) 2004, 2005, 2006, 2007, 2010 Apple Inc. All rights reserved. 6 * (C) 2006 Alexey Proskuryakov (ap (at) nypop.com) 7 * 8 * This library is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU Library General Public 10 * License as published by the Free Software Foundation; either 11 * version 2 of the License, or (at your option) any later version. 12 * 13 * This library is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 * Library General Public License for more details. 17 * 18 * You should have received a copy of the GNU Library General Public License 19 * along with this library; see the file COPYING.LIB. If not, write to 20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 * Boston, MA 02110-1301, USA. 22 * 23 */ 24 25 #include "config.h" 26 #include "core/html/HTMLLabelElement.h" 27 28 #include "core/HTMLNames.h" 29 #include "core/dom/Document.h" 30 #include "core/dom/ElementTraversal.h" 31 #include "core/editing/FrameSelection.h" 32 #include "core/events/MouseEvent.h" 33 #include "core/frame/LocalFrame.h" 34 #include "core/html/FormAssociatedElement.h" 35 #include "core/page/EventHandler.h" 36 37 namespace blink { 38 39 using namespace HTMLNames; 40 41 inline HTMLLabelElement::HTMLLabelElement(Document& document, HTMLFormElement* form) 42 : HTMLElement(labelTag, document) 43 { 44 FormAssociatedElement::associateByParser(form); 45 } 46 47 PassRefPtrWillBeRawPtr<HTMLLabelElement> HTMLLabelElement::create(Document& document, HTMLFormElement* form) 48 { 49 RefPtrWillBeRawPtr<HTMLLabelElement> labelElement = adoptRefWillBeNoop(new HTMLLabelElement(document, form)); 50 return labelElement.release(); 51 } 52 53 bool HTMLLabelElement::rendererIsFocusable() const 54 { 55 HTMLLabelElement* that = const_cast<HTMLLabelElement*>(this); 56 return that->isContentEditable(); 57 } 58 59 LabelableElement* HTMLLabelElement::control() const 60 { 61 const AtomicString& controlId = getAttribute(forAttr); 62 if (controlId.isNull()) { 63 // Search the children and descendants of the label element for a form element. 64 // per http://dev.w3.org/html5/spec/Overview.html#the-label-element 65 // the form element must be "labelable form-associated element". 66 for (LabelableElement* element = Traversal<LabelableElement>::next(*this, this); element; element = Traversal<LabelableElement>::next(*element, this)) { 67 if (element->supportLabels()) 68 return element; 69 } 70 return 0; 71 } 72 73 if (Element* element = treeScope().getElementById(controlId)) { 74 if (isLabelableElement(*element) && toLabelableElement(*element).supportLabels()) 75 return toLabelableElement(element); 76 } 77 78 return 0; 79 } 80 81 HTMLFormElement* HTMLLabelElement::formOwner() const 82 { 83 return FormAssociatedElement::form(); 84 } 85 86 void HTMLLabelElement::setActive(bool down) 87 { 88 if (down == active()) 89 return; 90 91 // Update our status first. 92 HTMLElement::setActive(down); 93 94 // Also update our corresponding control. 95 if (HTMLElement* element = control()) 96 element->setActive(down); 97 } 98 99 void HTMLLabelElement::setHovered(bool over) 100 { 101 if (over == hovered()) 102 return; 103 104 // Update our status first. 105 HTMLElement::setHovered(over); 106 107 // Also update our corresponding control. 108 if (HTMLElement* element = control()) 109 element->setHovered(over); 110 } 111 112 bool HTMLLabelElement::isInteractiveContent() const 113 { 114 return true; 115 } 116 117 bool HTMLLabelElement::isInInteractiveContent(Node* node) const 118 { 119 if (!containsIncludingShadowDOM(node)) 120 return false; 121 while (node && this != node) { 122 if (node->isHTMLElement() && toHTMLElement(node)->isInteractiveContent()) 123 return true; 124 node = node->parentOrShadowHostNode(); 125 } 126 return false; 127 } 128 129 void HTMLLabelElement::defaultEventHandler(Event* evt) 130 { 131 static bool processingClick = false; 132 133 if (evt->type() == EventTypeNames::click && !processingClick) { 134 RefPtrWillBeRawPtr<HTMLElement> element = control(); 135 136 // If we can't find a control or if the control received the click 137 // event, then there's no need for us to do anything. 138 if (!element || (evt->target() && element->containsIncludingShadowDOM(evt->target()->toNode()))) 139 return; 140 141 if (evt->target() && isInInteractiveContent(evt->target()->toNode())) 142 return; 143 144 // Behaviour of label element is as follows: 145 // - If there is double click, two clicks will be passed to control 146 // element. Control element will *not* be focused. 147 // - If there is selection of label element by dragging, no click 148 // event is passed. Also, no focus on control element. 149 // - If there is already a selection on label element and then label 150 // is clicked, then click event is passed to control element and 151 // control element is focused. 152 153 bool isLabelTextSelected = false; 154 155 // If the click is not simulated and the text of the label element 156 // is selected by dragging over it, then return without passing the 157 // click event to control element. 158 // Note: a click event may be not a mouse event if created by 159 // document.createEvent(). 160 if (evt->isMouseEvent() && !toMouseEvent(evt)->isSimulated()) { 161 if (LocalFrame* frame = document().frame()) { 162 // Check if there is a selection and click is not on the 163 // selection. 164 if (frame->selection().isRange() && !frame->eventHandler().mouseDownWasSingleClickInSelection()) 165 isLabelTextSelected = true; 166 // If selection is there and is single click i.e. text is 167 // selected by dragging over label text, then return. 168 // Click count >=2, meaning double click or triple click, 169 // should pass click event to control element. 170 // Only in case of drag, *neither* we pass the click event, 171 // *nor* we focus the control element. 172 if (isLabelTextSelected && frame->eventHandler().clickCount() == 1) 173 return; 174 } 175 } 176 177 processingClick = true; 178 179 document().updateLayoutIgnorePendingStylesheets(); 180 if (element->isMouseFocusable()) { 181 // If the label is *not* selected, or if the click happened on 182 // selection of label, only then focus the control element. 183 // In case of double click or triple click, selection will be there, 184 // so do not focus the control element. 185 if (!isLabelTextSelected) 186 element->focus(true, FocusTypeMouse); 187 } 188 189 // Click the corresponding control. 190 element->dispatchSimulatedClick(evt); 191 192 processingClick = false; 193 194 evt->setDefaultHandled(); 195 } 196 197 HTMLElement::defaultEventHandler(evt); 198 } 199 200 bool HTMLLabelElement::willRespondToMouseClickEvents() 201 { 202 if (control() && control()->willRespondToMouseClickEvents()) 203 return true; 204 205 return HTMLElement::willRespondToMouseClickEvents(); 206 } 207 208 void HTMLLabelElement::focus(bool, FocusType type) 209 { 210 // to match other browsers, always restore previous selection 211 if (HTMLElement* element = control()) 212 element->focus(true, type); 213 if (isFocusable()) 214 HTMLElement::focus(true, type); 215 } 216 217 void HTMLLabelElement::accessKeyAction(bool sendMouseEvents) 218 { 219 if (HTMLElement* element = control()) 220 element->accessKeyAction(sendMouseEvents); 221 else 222 HTMLElement::accessKeyAction(sendMouseEvents); 223 } 224 225 void HTMLLabelElement::updateLabel(TreeScope& scope, const AtomicString& oldForAttributeValue, const AtomicString& newForAttributeValue) 226 { 227 if (!inDocument()) 228 return; 229 230 if (oldForAttributeValue == newForAttributeValue) 231 return; 232 233 if (!oldForAttributeValue.isEmpty()) 234 scope.removeLabel(oldForAttributeValue, this); 235 if (!newForAttributeValue.isEmpty()) 236 scope.addLabel(newForAttributeValue, this); 237 } 238 239 void HTMLLabelElement::attributeWillChange(const QualifiedName& name, const AtomicString& oldValue, const AtomicString& newValue) 240 { 241 if (name == HTMLNames::forAttr) { 242 TreeScope& scope = treeScope(); 243 if (scope.shouldCacheLabelsByForAttribute()) 244 updateLabel(scope, oldValue, newValue); 245 } 246 HTMLElement::attributeWillChange(name, oldValue, newValue); 247 } 248 249 Node::InsertionNotificationRequest HTMLLabelElement::insertedInto(ContainerNode* insertionPoint) 250 { 251 InsertionNotificationRequest result = HTMLElement::insertedInto(insertionPoint); 252 FormAssociatedElement::insertedInto(insertionPoint); 253 if (insertionPoint->isInTreeScope()) { 254 TreeScope& scope = insertionPoint->treeScope(); 255 if (scope == treeScope() && scope.shouldCacheLabelsByForAttribute()) 256 updateLabel(scope, nullAtom, fastGetAttribute(forAttr)); 257 } 258 return result; 259 } 260 261 void HTMLLabelElement::removedFrom(ContainerNode* insertionPoint) 262 { 263 if (insertionPoint->isInTreeScope() && treeScope() == document()) { 264 TreeScope& treeScope = insertionPoint->treeScope(); 265 if (treeScope.shouldCacheLabelsByForAttribute()) 266 updateLabel(treeScope, fastGetAttribute(forAttr), nullAtom); 267 } 268 HTMLElement::removedFrom(insertionPoint); 269 FormAssociatedElement::removedFrom(insertionPoint); 270 } 271 272 void HTMLLabelElement::trace(Visitor* visitor) 273 { 274 HTMLElement::trace(visitor); 275 FormAssociatedElement::trace(visitor); 276 } 277 278 void HTMLLabelElement::parseAttribute(const QualifiedName& attributeName, const AtomicString& attributeValue) 279 { 280 if (attributeName == formAttr) 281 formAttributeChanged(); 282 else 283 HTMLElement::parseAttribute(attributeName, attributeValue); 284 } 285 286 } // namespace 287