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-2017 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 "HTMLLabelElement.h"
27
28#include "Document.h"
29#include "ElementIterator.h"
30#include "Event.h"
31#include "EventNames.h"
32#include "FormAssociatedElement.h"
33#include "HTMLFormControlElement.h"
34#include "HTMLNames.h"
35#include <wtf/IsoMallocInlines.h>
36
37namespace WebCore {
38
39WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLLabelElement);
40
41using namespace HTMLNames;
42
43static LabelableElement* firstElementWithIdIfLabelable(TreeScope& treeScope, const AtomicString& id)
44{
45 auto element = makeRefPtr(treeScope.getElementById(id));
46 if (!is<LabelableElement>(element))
47 return nullptr;
48
49 auto& labelableElement = downcast<LabelableElement>(*element);
50 return labelableElement.supportLabels() ? &labelableElement : nullptr;
51}
52
53inline HTMLLabelElement::HTMLLabelElement(const QualifiedName& tagName, Document& document)
54 : HTMLElement(tagName, document)
55{
56 ASSERT(hasTagName(labelTag));
57}
58
59Ref<HTMLLabelElement> HTMLLabelElement::create(const QualifiedName& tagName, Document& document)
60{
61 return adoptRef(*new HTMLLabelElement(tagName, document));
62}
63
64RefPtr<LabelableElement> HTMLLabelElement::control() const
65{
66 auto& controlId = attributeWithoutSynchronization(forAttr);
67 if (controlId.isNull()) {
68 // Search the children and descendants of the label element for a form element.
69 // per http://dev.w3.org/html5/spec/Overview.html#the-label-element
70 // the form element must be "labelable form-associated element".
71 for (auto& labelableElement : descendantsOfType<LabelableElement>(*this)) {
72 if (labelableElement.supportLabels())
73 return const_cast<LabelableElement*>(&labelableElement);
74 }
75 return nullptr;
76 }
77 return isConnected() ? firstElementWithIdIfLabelable(treeScope(), controlId) : nullptr;
78}
79
80HTMLFormElement* HTMLLabelElement::form() const
81{
82 auto control = this->control();
83 if (!is<HTMLFormControlElement>(control))
84 return nullptr;
85 return downcast<HTMLFormControlElement>(control.get())->form();
86}
87
88void HTMLLabelElement::setActive(bool down, bool pause)
89{
90 if (down == active())
91 return;
92
93 // Update our status first.
94 HTMLElement::setActive(down, pause);
95
96 // Also update our corresponding control.
97 if (auto element = control())
98 element->setActive(down, pause);
99}
100
101void HTMLLabelElement::setHovered(bool over)
102{
103 if (over == hovered())
104 return;
105
106 // Update our status first.
107 HTMLElement::setHovered(over);
108
109 // Also update our corresponding control.
110 if (auto element = control())
111 element->setHovered(over);
112}
113
114void HTMLLabelElement::defaultEventHandler(Event& event)
115{
116 static bool processingClick = false;
117
118 if (event.type() == eventNames().clickEvent && !processingClick) {
119 auto control = this->control();
120
121 // If we can't find a control or if the control received the click
122 // event, then there's no need for us to do anything.
123 if (!control || (is<Node>(event.target()) && control->containsIncludingShadowDOM(&downcast<Node>(*event.target())))) {
124 HTMLElement::defaultEventHandler(event);
125 return;
126 }
127
128 processingClick = true;
129
130 control->dispatchSimulatedClick(&event);
131
132 document().updateLayoutIgnorePendingStylesheets();
133 if (control->isMouseFocusable())
134 control->focus();
135
136 processingClick = false;
137
138 event.setDefaultHandled();
139 }
140
141 HTMLElement::defaultEventHandler(event);
142}
143
144bool HTMLLabelElement::willRespondToMouseClickEvents()
145{
146 auto element = control();
147 return (element && element->willRespondToMouseClickEvents()) || HTMLElement::willRespondToMouseClickEvents();
148}
149
150void HTMLLabelElement::focus(bool restorePreviousSelection, FocusDirection direction)
151{
152 if (document().haveStylesheetsLoaded()) {
153 document().updateLayout();
154 if (isFocusable()) {
155 // The value of restorePreviousSelection is not used for label elements as it doesn't override updateFocusAppearance.
156 Element::focus(restorePreviousSelection, direction);
157 return;
158 }
159 }
160
161 // To match other browsers, always restore previous selection.
162 if (auto element = control())
163 element->focus(true, direction);
164}
165
166void HTMLLabelElement::accessKeyAction(bool sendMouseEvents)
167{
168 if (auto element = control())
169 element->accessKeyAction(sendMouseEvents);
170 else
171 HTMLElement::accessKeyAction(sendMouseEvents);
172}
173
174} // namespace
175