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