1/*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4 *           (C) 2001 Dirk Mueller (mueller@kde.org)
5 * Copyright (C) 2004, 2005, 2006, 2007, 2010 Apple Inc. All rights reserved.
6 *           (C) 2006 Alexey Proskuryakov (ap@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
37namespace blink {
38
39using namespace HTMLNames;
40
41inline HTMLLabelElement::HTMLLabelElement(Document& document, HTMLFormElement* form)
42    : HTMLElement(labelTag, document)
43{
44    FormAssociatedElement::associateByParser(form);
45}
46
47PassRefPtrWillBeRawPtr<HTMLLabelElement> HTMLLabelElement::create(Document& document, HTMLFormElement* form)
48{
49    RefPtrWillBeRawPtr<HTMLLabelElement> labelElement = adoptRefWillBeNoop(new HTMLLabelElement(document, form));
50    return labelElement.release();
51}
52
53bool HTMLLabelElement::rendererIsFocusable() const
54{
55    HTMLLabelElement* that = const_cast<HTMLLabelElement*>(this);
56    return that->isContentEditable();
57}
58
59LabelableElement* 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
81HTMLFormElement* HTMLLabelElement::formOwner() const
82{
83    return FormAssociatedElement::form();
84}
85
86void 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
99void 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
112bool HTMLLabelElement::isInteractiveContent() const
113{
114    return true;
115}
116
117bool 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
129void 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
200bool HTMLLabelElement::willRespondToMouseClickEvents()
201{
202    if (control() && control()->willRespondToMouseClickEvents())
203        return true;
204
205    return HTMLElement::willRespondToMouseClickEvents();
206}
207
208void 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
217void HTMLLabelElement::accessKeyAction(bool sendMouseEvents)
218{
219    if (HTMLElement* element = control())
220        element->accessKeyAction(sendMouseEvents);
221    else
222        HTMLElement::accessKeyAction(sendMouseEvents);
223}
224
225void 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
239void 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
249Node::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
261void 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
272void HTMLLabelElement::trace(Visitor* visitor)
273{
274    HTMLElement::trace(visitor);
275    FormAssociatedElement::trace(visitor);
276}
277
278void 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