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/. */
6
7 /**
8 * Implementation of HTML <label> elements.
9 */
10 #include "HTMLLabelElement.h"
11 #include "mozilla/EventDispatcher.h"
12 #include "mozilla/MouseEvents.h"
13 #include "mozilla/dom/HTMLLabelElementBinding.h"
14 #include "nsFocusManager.h"
15 #include "nsIDOMMouseEvent.h"
16 #include "nsQueryObject.h"
17
18 // construction, destruction
19
20 NS_IMPL_NS_NEW_HTML_ELEMENT(Label)
21
22 namespace mozilla {
23 namespace dom {
24
~HTMLLabelElement()25 HTMLLabelElement::~HTMLLabelElement()
26 {
27 }
28
29 JSObject*
WrapNode(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)30 HTMLLabelElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
31 {
32 return HTMLLabelElementBinding::Wrap(aCx, this, aGivenProto);
33 }
34
35 // nsISupports
36
NS_IMPL_ISUPPORTS_INHERITED(HTMLLabelElement,nsGenericHTMLElement,nsIDOMHTMLLabelElement)37 NS_IMPL_ISUPPORTS_INHERITED(HTMLLabelElement, nsGenericHTMLElement,
38 nsIDOMHTMLLabelElement)
39
40 // nsIDOMHTMLLabelElement
41
42 NS_IMPL_ELEMENT_CLONE(HTMLLabelElement)
43
44 NS_IMETHODIMP
45 HTMLLabelElement::GetForm(nsIDOMHTMLFormElement** aForm)
46 {
47 RefPtr<nsIDOMHTMLFormElement> form = GetForm();
48 form.forget(aForm);
49 return NS_OK;
50 }
51
52 NS_IMETHODIMP
GetControl(nsIDOMHTMLElement ** aElement)53 HTMLLabelElement::GetControl(nsIDOMHTMLElement** aElement)
54 {
55 nsCOMPtr<nsIDOMHTMLElement> element = do_QueryObject(GetLabeledElement());
56 element.forget(aElement);
57 return NS_OK;
58 }
59
60 NS_IMETHODIMP
SetHtmlFor(const nsAString & aHtmlFor)61 HTMLLabelElement::SetHtmlFor(const nsAString& aHtmlFor)
62 {
63 ErrorResult rv;
64 SetHtmlFor(aHtmlFor, rv);
65 return rv.StealNSResult();
66 }
67
68 NS_IMETHODIMP
GetHtmlFor(nsAString & aHtmlFor)69 HTMLLabelElement::GetHtmlFor(nsAString& aHtmlFor)
70 {
71 nsString htmlFor;
72 GetHtmlFor(htmlFor);
73 aHtmlFor = htmlFor;
74 return NS_OK;
75 }
76
77 HTMLFormElement*
GetForm() const78 HTMLLabelElement::GetForm() const
79 {
80 nsGenericHTMLElement* control = GetControl();
81 if (!control) {
82 return nullptr;
83 }
84
85 // Not all labeled things have a form association. Stick to the ones that do.
86 nsCOMPtr<nsIFormControl> formControl = do_QueryObject(control);
87 if (!formControl) {
88 return nullptr;
89 }
90
91 return static_cast<HTMLFormElement*>(formControl->GetFormElement());
92 }
93
94 void
Focus(ErrorResult & aError)95 HTMLLabelElement::Focus(ErrorResult& aError)
96 {
97 // retarget the focus method at the for content
98 nsIFocusManager* fm = nsFocusManager::GetFocusManager();
99 if (fm) {
100 nsCOMPtr<nsIDOMElement> elem = do_QueryObject(GetLabeledElement());
101 if (elem)
102 fm->SetFocus(elem, 0);
103 }
104 }
105
106 static bool
InInteractiveHTMLContent(nsIContent * aContent,nsIContent * aStop)107 InInteractiveHTMLContent(nsIContent* aContent, nsIContent* aStop)
108 {
109 nsIContent* content = aContent;
110 while (content && content != aStop) {
111 if (content->IsElement() &&
112 content->AsElement()->IsInteractiveHTMLContent(true)) {
113 return true;
114 }
115 content = content->GetParent();
116 }
117 return false;
118 }
119
120 nsresult
PostHandleEvent(EventChainPostVisitor & aVisitor)121 HTMLLabelElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
122 {
123 WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
124 if (mHandlingEvent ||
125 (!(mouseEvent && mouseEvent->IsLeftClickEvent()) &&
126 aVisitor.mEvent->mMessage != eMouseDown) ||
127 aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault ||
128 !aVisitor.mPresContext ||
129 // Don't handle the event if it's already been handled by another label
130 aVisitor.mEvent->mFlags.mMultipleActionsPrevented) {
131 return NS_OK;
132 }
133
134 nsCOMPtr<nsIContent> target = do_QueryInterface(aVisitor.mEvent->mTarget);
135 if (InInteractiveHTMLContent(target, this)) {
136 return NS_OK;
137 }
138
139 // Strong ref because event dispatch is going to happen.
140 RefPtr<Element> content = GetLabeledElement();
141
142 if (content) {
143 mHandlingEvent = true;
144 switch (aVisitor.mEvent->mMessage) {
145 case eMouseDown:
146 if (mouseEvent->button == WidgetMouseEvent::eLeftButton) {
147 // We reset the mouse-down point on every event because there is
148 // no guarantee we will reach the eMouseClick code below.
149 LayoutDeviceIntPoint* curPoint =
150 new LayoutDeviceIntPoint(mouseEvent->mRefPoint);
151 SetProperty(nsGkAtoms::labelMouseDownPtProperty,
152 static_cast<void*>(curPoint),
153 nsINode::DeleteProperty<LayoutDeviceIntPoint>);
154 }
155 break;
156
157 case eMouseClick:
158 if (mouseEvent->IsLeftClickEvent()) {
159 LayoutDeviceIntPoint* mouseDownPoint =
160 static_cast<LayoutDeviceIntPoint*>(
161 GetProperty(nsGkAtoms::labelMouseDownPtProperty));
162
163 bool dragSelect = false;
164 if (mouseDownPoint) {
165 LayoutDeviceIntPoint dragDistance = *mouseDownPoint;
166 DeleteProperty(nsGkAtoms::labelMouseDownPtProperty);
167
168 dragDistance -= mouseEvent->mRefPoint;
169 const int CLICK_DISTANCE = 2;
170 dragSelect = dragDistance.x > CLICK_DISTANCE ||
171 dragDistance.x < -CLICK_DISTANCE ||
172 dragDistance.y > CLICK_DISTANCE ||
173 dragDistance.y < -CLICK_DISTANCE;
174 }
175 // Don't click the for-content if we did drag-select text or if we
176 // have a kbd modifier (which adjusts a selection).
177 if (dragSelect || mouseEvent->IsShift() || mouseEvent->IsControl() ||
178 mouseEvent->IsAlt() || mouseEvent->IsMeta()) {
179 break;
180 }
181 // Only set focus on the first click of multiple clicks to prevent
182 // to prevent immediate de-focus.
183 if (mouseEvent->mClickCount <= 1) {
184 nsIFocusManager* fm = nsFocusManager::GetFocusManager();
185 if (fm) {
186 // Use FLAG_BYMOVEFOCUS here so that the label is scrolled to.
187 // Also, within HTMLInputElement::PostHandleEvent, inputs will
188 // be selected only when focused via a key or when the navigation
189 // flag is used and we want to select the text on label clicks as
190 // well.
191 // If the label has been clicked by the user, we also want to
192 // pass FLAG_BYMOUSE so that we get correct focus ring behavior,
193 // but we don't want to pass FLAG_BYMOUSE if this click event was
194 // caused by the user pressing an accesskey.
195 nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(content);
196 bool byMouse = (mouseEvent->inputSource != nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD);
197 bool byTouch = (mouseEvent->inputSource == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH);
198 fm->SetFocus(elem, nsIFocusManager::FLAG_BYMOVEFOCUS |
199 (byMouse ? nsIFocusManager::FLAG_BYMOUSE : 0) |
200 (byTouch ? nsIFocusManager::FLAG_BYTOUCH : 0));
201 }
202 }
203 // Dispatch a new click event to |content|
204 // (For compatibility with IE, we do only left click. If
205 // we wanted to interpret the HTML spec very narrowly, we
206 // would do nothing. If we wanted to do something
207 // sensible, we might send more events through like
208 // this.) See bug 7554, bug 49897, and bug 96813.
209 nsEventStatus status = aVisitor.mEventStatus;
210 // Ok to use aVisitor.mEvent as parameter because DispatchClickEvent
211 // will actually create a new event.
212 EventFlags eventFlags;
213 eventFlags.mMultipleActionsPrevented = true;
214 DispatchClickEvent(aVisitor.mPresContext, mouseEvent,
215 content, false, &eventFlags, &status);
216 // Do we care about the status this returned? I don't think we do...
217 // Don't run another <label> off of this click
218 mouseEvent->mFlags.mMultipleActionsPrevented = true;
219 }
220 break;
221
222 default:
223 break;
224 }
225 mHandlingEvent = false;
226 }
227 return NS_OK;
228 }
229
230 bool
PerformAccesskey(bool aKeyCausesActivation,bool aIsTrustedEvent)231 HTMLLabelElement::PerformAccesskey(bool aKeyCausesActivation,
232 bool aIsTrustedEvent)
233 {
234 if (!aKeyCausesActivation) {
235 RefPtr<Element> element = GetLabeledElement();
236 if (element) {
237 return element->PerformAccesskey(aKeyCausesActivation, aIsTrustedEvent);
238 }
239 } else {
240 nsPresContext *presContext = GetPresContext(eForUncomposedDoc);
241 if (!presContext) {
242 return false;
243 }
244
245 // Click on it if the users prefs indicate to do so.
246 WidgetMouseEvent event(aIsTrustedEvent, eMouseClick,
247 nullptr, WidgetMouseEvent::eReal);
248 event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD;
249
250 nsAutoPopupStatePusher popupStatePusher(aIsTrustedEvent ?
251 openAllowed : openAbused);
252
253 EventDispatcher::Dispatch(static_cast<nsIContent*>(this), presContext,
254 &event);
255 }
256
257 return aKeyCausesActivation;
258 }
259
260 nsGenericHTMLElement*
GetLabeledElement() const261 HTMLLabelElement::GetLabeledElement() const
262 {
263 nsAutoString elementId;
264
265 if (!GetAttr(kNameSpaceID_None, nsGkAtoms::_for, elementId)) {
266 // No @for, so we are a label for our first form control element.
267 // Do a depth-first traversal to look for the first form control element.
268 return GetFirstLabelableDescendant();
269 }
270
271 // We have a @for. The id has to be linked to an element in the same document
272 // and this element should be a labelable form control.
273 //XXXsmaug It is unclear how this should work in case the element is in
274 // Shadow DOM.
275 // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=26365.
276 nsIDocument* doc = GetUncomposedDoc();
277 if (!doc) {
278 return nullptr;
279 }
280
281 Element* element = doc->GetElementById(elementId);
282 if (element && element->IsLabelable()) {
283 return static_cast<nsGenericHTMLElement*>(element);
284 }
285
286 return nullptr;
287 }
288
289 nsGenericHTMLElement*
GetFirstLabelableDescendant() const290 HTMLLabelElement::GetFirstLabelableDescendant() const
291 {
292 for (nsIContent* cur = nsINode::GetFirstChild(); cur;
293 cur = cur->GetNextNode(this)) {
294 Element* element = cur->IsElement() ? cur->AsElement() : nullptr;
295 if (element && element->IsLabelable()) {
296 return static_cast<nsGenericHTMLElement*>(element);
297 }
298 }
299
300 return nullptr;
301 }
302
303 } // namespace dom
304 } // namespace mozilla
305