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