Home | History | Annotate | Download | only in html
      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