1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 * Implementation of HTML <label> elements.
10 #include "HTMLLabelElement.h"
11 #include "mozilla/EventDispatcher.h"
12 #include "mozilla/MouseEvents.h"
13 #include "mozilla/dom/HTMLLabelElementBinding.h"
14 #include "mozilla/dom/MouseEventBinding.h"
15 #include "nsFocusManager.h"
17 #include "nsContentUtils.h"
18 #include "nsQueryObject.h"
19 #include "mozilla/dom/ShadowRoot.h"
21 // construction, destruction
23 NS_IMPL_NS_NEW_HTML_ELEMENT(Label
)
25 namespace mozilla::dom
{
27 HTMLLabelElement::~HTMLLabelElement() = default;
29 JSObject
* HTMLLabelElement::WrapNode(JSContext
* aCx
,
30 JS::Handle
<JSObject
*> aGivenProto
) {
31 return HTMLLabelElement_Binding::Wrap(aCx
, this, aGivenProto
);
34 // nsIDOMHTMLLabelElement
36 NS_IMPL_ELEMENT_CLONE(HTMLLabelElement
)
38 HTMLFormElement
* HTMLLabelElement::GetForm() const {
39 nsGenericHTMLElement
* control
= GetControl();
44 // Not all labeled things have a form association. Stick to the ones that do.
45 nsCOMPtr
<nsIFormControl
> formControl
= do_QueryObject(control
);
50 return formControl
->GetForm();
53 void HTMLLabelElement::Focus(const FocusOptions
& aOptions
,
54 const CallerType aCallerType
,
55 ErrorResult
& aError
) {
57 nsIFrame
* frame
= GetPrimaryFrame(FlushType::Frames
);
58 if (frame
&& frame
->IsFocusable()) {
59 return nsGenericHTMLElement::Focus(aOptions
, aCallerType
, aError
);
63 if (RefPtr
<Element
> elem
= GetLabeledElement()) {
64 return elem
->Focus(aOptions
, aCallerType
, aError
);
68 nsresult
HTMLLabelElement::PostHandleEvent(EventChainPostVisitor
& aVisitor
) {
69 WidgetMouseEvent
* mouseEvent
= aVisitor
.mEvent
->AsMouseEvent();
71 (!(mouseEvent
&& mouseEvent
->IsLeftClickEvent()) &&
72 aVisitor
.mEvent
->mMessage
!= eMouseDown
) ||
73 aVisitor
.mEventStatus
== nsEventStatus_eConsumeNoDefault
||
74 !aVisitor
.mPresContext
||
75 // Don't handle the event if it's already been handled by another label
76 aVisitor
.mEvent
->mFlags
.mMultipleActionsPrevented
) {
80 nsCOMPtr
<Element
> target
=
81 do_QueryInterface(aVisitor
.mEvent
->GetOriginalDOMEventTarget());
82 if (nsContentUtils::IsInInteractiveHTMLContent(target
, this)) {
86 // Strong ref because event dispatch is going to happen.
87 RefPtr
<Element
> content
= GetLabeledElement();
89 if (!content
|| content
->IsDisabled()) {
93 mHandlingEvent
= true;
94 switch (aVisitor
.mEvent
->mMessage
) {
96 if (mouseEvent
->mButton
== MouseButton::ePrimary
) {
97 // We reset the mouse-down point on every event because there is
98 // no guarantee we will reach the eMouseClick code below.
99 LayoutDeviceIntPoint
* curPoint
=
100 new LayoutDeviceIntPoint(mouseEvent
->mRefPoint
);
101 SetProperty(nsGkAtoms::labelMouseDownPtProperty
,
102 static_cast<void*>(curPoint
),
103 nsINode::DeleteProperty
<LayoutDeviceIntPoint
>);
108 if (mouseEvent
->IsLeftClickEvent()) {
109 LayoutDeviceIntPoint
* mouseDownPoint
=
110 static_cast<LayoutDeviceIntPoint
*>(
111 GetProperty(nsGkAtoms::labelMouseDownPtProperty
));
113 bool dragSelect
= false;
114 if (mouseDownPoint
) {
115 LayoutDeviceIntPoint dragDistance
= *mouseDownPoint
;
116 RemoveProperty(nsGkAtoms::labelMouseDownPtProperty
);
118 dragDistance
-= mouseEvent
->mRefPoint
;
119 const int CLICK_DISTANCE
= 2;
120 dragSelect
= dragDistance
.x
> CLICK_DISTANCE
||
121 dragDistance
.x
< -CLICK_DISTANCE
||
122 dragDistance
.y
> CLICK_DISTANCE
||
123 dragDistance
.y
< -CLICK_DISTANCE
;
125 // Don't click the for-content if we did drag-select text or if we
126 // have a kbd modifier (which adjusts a selection).
127 if (dragSelect
|| mouseEvent
->IsShift() || mouseEvent
->IsControl() ||
128 mouseEvent
->IsAlt() || mouseEvent
->IsMeta()) {
131 // Only set focus on the first click of multiple clicks to prevent
132 // to prevent immediate de-focus.
133 if (mouseEvent
->mClickCount
<= 1) {
134 if (RefPtr
<nsFocusManager
> fm
= nsFocusManager::GetFocusManager()) {
135 // Use FLAG_BYMOVEFOCUS here so that the label is scrolled to.
136 // Also, within HTMLInputElement::PostHandleEvent, inputs will
137 // be selected only when focused via a key or when the navigation
138 // flag is used and we want to select the text on label clicks as
140 // If the label has been clicked by the user, we also want to
141 // pass FLAG_BYMOUSE so that we get correct focus ring behavior,
142 // but we don't want to pass FLAG_BYMOUSE if this click event was
143 // caused by the user pressing an accesskey.
144 bool byMouse
= (mouseEvent
->mInputSource
!=
145 MouseEvent_Binding::MOZ_SOURCE_KEYBOARD
);
146 bool byTouch
= (mouseEvent
->mInputSource
==
147 MouseEvent_Binding::MOZ_SOURCE_TOUCH
);
148 fm
->SetFocus(content
,
149 nsIFocusManager::FLAG_BYMOVEFOCUS
|
150 (byMouse
? nsIFocusManager::FLAG_BYMOUSE
: 0) |
151 (byTouch
? nsIFocusManager::FLAG_BYTOUCH
: 0));
154 // Dispatch a new click event to |content|
155 // (For compatibility with IE, we do only left click. If
156 // we wanted to interpret the HTML spec very narrowly, we
157 // would do nothing. If we wanted to do something
158 // sensible, we might send more events through like
159 // this.) See bug 7554, bug 49897, and bug 96813.
160 nsEventStatus status
= aVisitor
.mEventStatus
;
161 // Ok to use aVisitor.mEvent as parameter because DispatchClickEvent
162 // will actually create a new event.
163 EventFlags eventFlags
;
164 eventFlags
.mMultipleActionsPrevented
= true;
165 DispatchClickEvent(aVisitor
.mPresContext
, mouseEvent
, content
, false,
166 &eventFlags
, &status
);
167 // Do we care about the status this returned? I don't think we do...
168 // Don't run another <label> off of this click
169 mouseEvent
->mFlags
.mMultipleActionsPrevented
= true;
176 mHandlingEvent
= false;
180 Result
<bool, nsresult
> HTMLLabelElement::PerformAccesskey(
181 bool aKeyCausesActivation
, bool aIsTrustedEvent
) {
182 if (!aKeyCausesActivation
) {
183 RefPtr
<Element
> element
= GetLabeledElement();
185 return element
->PerformAccesskey(aKeyCausesActivation
, aIsTrustedEvent
);
187 return Err(NS_ERROR_ABORT
);
190 RefPtr
<nsPresContext
> presContext
= GetPresContext(eForUncomposedDoc
);
192 return Err(NS_ERROR_UNEXPECTED
);
195 // Click on it if the users prefs indicate to do so.
196 AutoHandlingUserInputStatePusher
userInputStatePusher(aIsTrustedEvent
);
197 AutoPopupStatePusher
popupStatePusher(
198 aIsTrustedEvent
? PopupBlocker::openAllowed
: PopupBlocker::openAbused
);
199 DispatchSimulatedClick(this, aIsTrustedEvent
, presContext
);
201 // XXXedgar, do we need to check whether the focus is really changed?
205 nsGenericHTMLElement
* HTMLLabelElement::GetLabeledElement() const {
206 nsAutoString elementId
;
208 if (!GetAttr(nsGkAtoms::_for
, elementId
)) {
209 // No @for, so we are a label for our first form control element.
210 // Do a depth-first traversal to look for the first form control element.
211 return GetFirstLabelableDescendant();
214 // We have a @for. The id has to be linked to an element in the same tree
215 // and this element should be a labelable form control.
216 Element
* element
= nullptr;
218 if (ShadowRoot
* shadowRoot
= GetContainingShadow()) {
219 element
= shadowRoot
->GetElementById(elementId
);
220 } else if (Document
* doc
= GetUncomposedDoc()) {
221 element
= doc
->GetElementById(elementId
);
224 nsContentUtils::MatchElementId(SubtreeRoot()->AsContent(), elementId
);
227 if (element
&& element
->IsLabelable()) {
228 return static_cast<nsGenericHTMLElement
*>(element
);
234 nsGenericHTMLElement
* HTMLLabelElement::GetFirstLabelableDescendant() const {
235 for (nsIContent
* cur
= nsINode::GetFirstChild(); cur
;
236 cur
= cur
->GetNextNode(this)) {
237 Element
* element
= Element::FromNode(cur
);
238 if (element
&& element
->IsLabelable()) {
239 return static_cast<nsGenericHTMLElement
*>(element
);
246 } // namespace mozilla::dom