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