1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Mozilla Communicator client code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : *
24 : * Alternatively, the contents of this file may be used under the terms of
25 : * either of the GNU General Public License Version 2 or later (the "GPL"),
26 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 : * in which case the provisions of the GPL or the LGPL are applicable instead
28 : * of those above. If you wish to allow use of your version of this file only
29 : * under the terms of either the GPL or the LGPL, and not to allow others to
30 : * use your version of this file under the terms of the MPL, indicate your
31 : * decision by deleting the provisions above and replace them with the notice
32 : * and other provisions required by the GPL or the LGPL. If you do not delete
33 : * the provisions above, a recipient may use your version of this file under
34 : * the terms of any one of the MPL, the GPL or the LGPL.
35 : *
36 : * ***** END LICENSE BLOCK ***** */
37 :
38 : /**
39 : * Implementation of HTML <label> elements.
40 : */
41 : #include "nsHTMLLabelElement.h"
42 : #include "nsCOMPtr.h"
43 : #include "nsIDOMHTMLFormElement.h"
44 : #include "nsIDOMEventTarget.h"
45 : #include "nsGkAtoms.h"
46 : #include "nsStyleConsts.h"
47 : #include "nsPresContext.h"
48 : #include "nsIFormControl.h"
49 : #include "nsIForm.h"
50 : #include "nsIDOMDocument.h"
51 : #include "nsIDocument.h"
52 : #include "nsGUIEvent.h"
53 : #include "nsEventDispatcher.h"
54 : #include "nsPIDOMWindow.h"
55 : #include "nsFocusManager.h"
56 :
57 : // construction, destruction
58 :
59 : using namespace mozilla::dom;
60 :
61 0 : NS_IMPL_NS_NEW_HTML_ELEMENT(Label)
62 :
63 :
64 0 : nsHTMLLabelElement::nsHTMLLabelElement(already_AddRefed<nsINodeInfo> aNodeInfo)
65 : : nsGenericHTMLFormElement(aNodeInfo)
66 0 : , mHandlingEvent(false)
67 : {
68 0 : }
69 :
70 0 : nsHTMLLabelElement::~nsHTMLLabelElement()
71 : {
72 0 : }
73 :
74 : // nsISupports
75 :
76 :
77 0 : NS_IMPL_ADDREF_INHERITED(nsHTMLLabelElement, nsGenericElement)
78 0 : NS_IMPL_RELEASE_INHERITED(nsHTMLLabelElement, nsGenericElement)
79 :
80 :
81 0 : DOMCI_NODE_DATA(HTMLLabelElement, nsHTMLLabelElement)
82 :
83 : // QueryInterface implementation for nsHTMLLabelElement
84 0 : NS_INTERFACE_TABLE_HEAD(nsHTMLLabelElement)
85 0 : NS_HTML_CONTENT_INTERFACE_TABLE1(nsHTMLLabelElement,
86 : nsIDOMHTMLLabelElement)
87 0 : NS_HTML_CONTENT_INTERFACE_TABLE_TO_MAP_SEGUE(nsHTMLLabelElement,
88 : nsGenericHTMLFormElement)
89 0 : NS_HTML_CONTENT_INTERFACE_TABLE_TAIL_CLASSINFO(HTMLLabelElement)
90 :
91 :
92 : // nsIDOMHTMLLabelElement
93 :
94 :
95 0 : NS_IMPL_ELEMENT_CLONE(nsHTMLLabelElement)
96 :
97 :
98 : NS_IMETHODIMP
99 0 : nsHTMLLabelElement::GetForm(nsIDOMHTMLFormElement** aForm)
100 : {
101 0 : return nsGenericHTMLFormElement::GetForm(aForm);
102 : }
103 :
104 : NS_IMETHODIMP
105 0 : nsHTMLLabelElement::GetControl(nsIDOMHTMLElement** aElement)
106 : {
107 0 : nsCOMPtr<nsIDOMHTMLElement> element = do_QueryInterface(GetLabeledElement());
108 0 : element.forget(aElement);
109 0 : return NS_OK;
110 : }
111 :
112 :
113 0 : NS_IMPL_STRING_ATTR(nsHTMLLabelElement, HtmlFor, _for)
114 :
115 : NS_IMETHODIMP
116 0 : nsHTMLLabelElement::Focus()
117 : {
118 : // retarget the focus method at the for content
119 0 : nsIFocusManager* fm = nsFocusManager::GetFocusManager();
120 0 : if (fm) {
121 0 : nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(GetLabeledElement());
122 0 : if (elem)
123 0 : fm->SetFocus(elem, 0);
124 : }
125 :
126 0 : return NS_OK;
127 : }
128 :
129 : nsresult
130 0 : nsHTMLLabelElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
131 : nsIContent* aBindingParent,
132 : bool aCompileEventHandlers)
133 : {
134 : return nsGenericHTMLFormElement::BindToTree(aDocument, aParent,
135 : aBindingParent,
136 0 : aCompileEventHandlers);
137 : }
138 :
139 : void
140 0 : nsHTMLLabelElement::UnbindFromTree(bool aDeep, bool aNullParent)
141 : {
142 0 : nsGenericHTMLFormElement::UnbindFromTree(aDeep, aNullParent);
143 0 : }
144 :
145 : static bool
146 0 : EventTargetIn(nsEvent *aEvent, nsIContent *aChild, nsIContent *aStop)
147 : {
148 0 : nsCOMPtr<nsIContent> c = do_QueryInterface(aEvent->target);
149 0 : nsIContent *content = c;
150 0 : while (content) {
151 0 : if (content == aChild) {
152 0 : return true;
153 : }
154 :
155 0 : if (content == aStop) {
156 0 : break;
157 : }
158 :
159 0 : content = content->GetParent();
160 : }
161 0 : return false;
162 : }
163 :
164 : static void
165 0 : DestroyMouseDownPoint(void * /*aObject*/,
166 : nsIAtom * /*aPropertyName*/,
167 : void * aPropertyValue,
168 : void * /*aData*/)
169 : {
170 0 : nsIntPoint* pt = static_cast<nsIntPoint*>(aPropertyValue);
171 : delete pt;
172 0 : }
173 :
174 : nsresult
175 0 : nsHTMLLabelElement::PostHandleEvent(nsEventChainPostVisitor& aVisitor)
176 : {
177 0 : if (mHandlingEvent ||
178 0 : (!NS_IS_MOUSE_LEFT_CLICK(aVisitor.mEvent) &&
179 : aVisitor.mEvent->message != NS_MOUSE_BUTTON_DOWN) ||
180 : aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault ||
181 0 : !aVisitor.mPresContext ||
182 : // Don't handle the event if it's already been handled by another label
183 : (aVisitor.mEvent->flags & NS_EVENT_FLAG_PREVENT_MULTIPLE_ACTIONS)) {
184 0 : return NS_OK;
185 : }
186 :
187 : // Strong ref because event dispatch is going to happen.
188 0 : nsRefPtr<Element> content = GetLabeledElement();
189 :
190 0 : if (content && !EventTargetIn(aVisitor.mEvent, content, this)) {
191 0 : mHandlingEvent = true;
192 0 : switch (aVisitor.mEvent->message) {
193 : case NS_MOUSE_BUTTON_DOWN:
194 0 : NS_ASSERTION(aVisitor.mEvent->eventStructType == NS_MOUSE_EVENT,
195 : "wrong event struct for event");
196 0 : if (static_cast<nsMouseEvent*>(aVisitor.mEvent)->button ==
197 : nsMouseEvent::eLeftButton) {
198 : // We reset the mouse-down point on every event because there is
199 : // no guarantee we will reach the NS_MOUSE_CLICK code below.
200 0 : nsIntPoint *curPoint = new nsIntPoint(aVisitor.mEvent->refPoint);
201 : SetProperty(nsGkAtoms::labelMouseDownPtProperty,
202 : static_cast<void *>(curPoint),
203 0 : DestroyMouseDownPoint);
204 : }
205 0 : break;
206 :
207 : case NS_MOUSE_CLICK:
208 0 : if (NS_IS_MOUSE_LEFT_CLICK(aVisitor.mEvent)) {
209 : const nsMouseEvent* event =
210 0 : static_cast<const nsMouseEvent*>(aVisitor.mEvent);
211 : nsIntPoint *mouseDownPoint = static_cast<nsIntPoint *>
212 0 : (GetProperty(nsGkAtoms::labelMouseDownPtProperty));
213 :
214 0 : bool dragSelect = false;
215 0 : if (mouseDownPoint) {
216 0 : nsIntPoint dragDistance = *mouseDownPoint;
217 0 : DeleteProperty(nsGkAtoms::labelMouseDownPtProperty);
218 :
219 0 : dragDistance -= aVisitor.mEvent->refPoint;
220 0 : const int CLICK_DISTANCE = 2;
221 : dragSelect = dragDistance.x > CLICK_DISTANCE ||
222 : dragDistance.x < -CLICK_DISTANCE ||
223 : dragDistance.y > CLICK_DISTANCE ||
224 0 : dragDistance.y < -CLICK_DISTANCE;
225 : }
226 :
227 : // Don't click the for-content if we did drag-select text or if we
228 : // have a kbd modifier (which adjusts a selection), or if it's a
229 : // double click (we already forwarded the first click event).
230 0 : if (dragSelect || event->clickCount > 1 ||
231 : event->isShift || event->isControl || event->isAlt ||
232 : event->isMeta) {
233 0 : break;
234 : }
235 :
236 0 : nsIFocusManager* fm = nsFocusManager::GetFocusManager();
237 0 : if (fm) {
238 : // Use FLAG_BYMOVEFOCUS here so that the label is scrolled to.
239 : // Also, within nsHTMLInputElement::PostHandleEvent, inputs will
240 : // be selected only when focused via a key or when the navigation
241 : // flag is used and we want to select the text on label clicks as
242 : // well.
243 0 : nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(content);
244 0 : fm->SetFocus(elem, nsIFocusManager::FLAG_BYMOVEFOCUS);
245 : }
246 :
247 : // Dispatch a new click event to |content|
248 : // (For compatibility with IE, we do only left click. If
249 : // we wanted to interpret the HTML spec very narrowly, we
250 : // would do nothing. If we wanted to do something
251 : // sensible, we might send more events through like
252 : // this.) See bug 7554, bug 49897, and bug 96813.
253 0 : nsEventStatus status = aVisitor.mEventStatus;
254 : // Ok to use aVisitor.mEvent as parameter because DispatchClickEvent
255 : // will actually create a new event.
256 : DispatchClickEvent(aVisitor.mPresContext,
257 : static_cast<nsInputEvent*>(aVisitor.mEvent),
258 : content, false,
259 0 : NS_EVENT_FLAG_PREVENT_MULTIPLE_ACTIONS, &status);
260 : // Do we care about the status this returned? I don't think we do...
261 : // Don't run another <label> off of this click
262 0 : aVisitor.mEvent->flags |= NS_EVENT_FLAG_PREVENT_MULTIPLE_ACTIONS;
263 : }
264 0 : break;
265 : }
266 0 : mHandlingEvent = false;
267 : }
268 0 : return NS_OK;
269 : }
270 :
271 : nsresult
272 0 : nsHTMLLabelElement::Reset()
273 : {
274 0 : return NS_OK;
275 : }
276 :
277 : NS_IMETHODIMP
278 0 : nsHTMLLabelElement::SubmitNamesValues(nsFormSubmission* aFormSubmission)
279 : {
280 0 : return NS_OK;
281 : }
282 :
283 : nsresult
284 0 : nsHTMLLabelElement::SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName, nsIAtom* aPrefix,
285 : const nsAString& aValue, bool aNotify)
286 : {
287 : return nsGenericHTMLFormElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue,
288 0 : aNotify);
289 : }
290 :
291 : nsresult
292 0 : nsHTMLLabelElement::UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttribute,
293 : bool aNotify)
294 : {
295 0 : return nsGenericHTMLFormElement::UnsetAttr(aNameSpaceID, aAttribute, aNotify);
296 : }
297 :
298 : void
299 0 : nsHTMLLabelElement::PerformAccesskey(bool aKeyCausesActivation,
300 : bool aIsTrustedEvent)
301 : {
302 0 : if (!aKeyCausesActivation) {
303 0 : nsRefPtr<Element> element = GetLabeledElement();
304 0 : if (element)
305 0 : element->PerformAccesskey(aKeyCausesActivation, aIsTrustedEvent);
306 : } else {
307 0 : nsPresContext *presContext = GetPresContext();
308 0 : if (!presContext)
309 0 : return;
310 :
311 : // Click on it if the users prefs indicate to do so.
312 : nsMouseEvent event(aIsTrustedEvent, NS_MOUSE_CLICK,
313 0 : nsnull, nsMouseEvent::eReal);
314 0 : event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD;
315 :
316 : nsAutoPopupStatePusher popupStatePusher(aIsTrustedEvent ?
317 0 : openAllowed : openAbused);
318 :
319 : nsEventDispatcher::Dispatch(static_cast<nsIContent*>(this), presContext,
320 0 : &event);
321 : }
322 : }
323 :
324 : Element*
325 0 : nsHTMLLabelElement::GetLabeledElement()
326 : {
327 0 : nsAutoString elementId;
328 :
329 0 : if (!GetAttr(kNameSpaceID_None, nsGkAtoms::_for, elementId)) {
330 : // No @for, so we are a label for our first form control element.
331 : // Do a depth-first traversal to look for the first form control element.
332 0 : return GetFirstDescendantFormControl();
333 : }
334 :
335 : // We have a @for. The id has to be linked to an element in the same document
336 : // and this element should be a labelable form control.
337 0 : nsIDocument* doc = GetCurrentDoc();
338 0 : if (!doc) {
339 0 : return nsnull;
340 : }
341 :
342 0 : Element* element = doc->GetElementById(elementId);
343 0 : if (!element) {
344 0 : return nsnull;
345 : }
346 :
347 0 : nsCOMPtr<nsIFormControl> controlElement = do_QueryInterface(element);
348 0 : if (controlElement && controlElement->IsLabelableControl()) {
349 : // Transfer the reference count of element to the returned value.
350 0 : return element;
351 : }
352 :
353 0 : return nsnull;
354 : }
355 :
356 : Element*
357 0 : nsHTMLLabelElement::GetFirstDescendantFormControl()
358 : {
359 : // Have to cast do disambiguate GetFirstChild from the DOM method of that name
360 0 : for (nsINode* cur = static_cast<nsINode*>(this)->GetFirstChild();
361 : cur;
362 0 : cur = cur->GetNextNode(this)) {
363 0 : nsCOMPtr<nsIFormControl> element = do_QueryInterface(cur);
364 0 : if (element && element->IsLabelableControl()) {
365 0 : NS_ASSERTION(cur->IsElement(), "How did that happen?");
366 0 : return cur->AsElement();
367 : }
368 : }
369 :
370 0 : return nsnull;
371 : }
372 :
|