1 /*
2 * Copyright (c) 2002-2025 Gargoyle Software Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 * https://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 package org.htmlunit.javascript.host.event;
16
17 import static org.htmlunit.javascript.configuration.SupportedBrowser.FF;
18 import static org.htmlunit.javascript.configuration.SupportedBrowser.FF_ESR;
19
20 import java.util.ArrayList;
21
22 import org.htmlunit.corejs.javascript.Context;
23 import org.htmlunit.corejs.javascript.ScriptableObject;
24 import org.htmlunit.html.DomNode;
25 import org.htmlunit.javascript.JavaScriptEngine;
26 import org.htmlunit.javascript.configuration.JsxClass;
27 import org.htmlunit.javascript.configuration.JsxConstant;
28 import org.htmlunit.javascript.configuration.JsxConstructor;
29 import org.htmlunit.javascript.configuration.JsxFunction;
30 import org.htmlunit.javascript.configuration.JsxGetter;
31 import org.htmlunit.javascript.host.html.HTMLElement;
32
33 /**
34 * JavaScript object representing a Mouse Event.
35 * For general information on which properties and functions should be supported, see
36 * <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-MouseEvent">DOM Level 2 Events</a>.
37 *
38 * @author Marc Guillemot
39 * @author Ahmed Ashour
40 * @author Frank Danek
41 * @author Ronald Brill
42 */
43 @JsxClass
44 public class MouseEvent extends UIEvent {
45
46 /** Constant for {@code MOZ_SOURCE_UNKNOWN}. */
47 @JsxConstant({FF, FF_ESR})
48 public static final int MOZ_SOURCE_UNKNOWN = 0;
49 /** Constant for {@code MOZ_SOURCE_MOUSE}. */
50 @JsxConstant({FF, FF_ESR})
51 public static final int MOZ_SOURCE_MOUSE = 1;
52 /** Constant for {@code MOZ_SOURCE_PEN}. */
53 @JsxConstant({FF, FF_ESR})
54 public static final int MOZ_SOURCE_PEN = 2;
55 /** Constant for {@code MOZ_SOURCE_ERASER}. */
56 @JsxConstant({FF, FF_ESR})
57 public static final int MOZ_SOURCE_ERASER = 3;
58 /** Constant for {@code MOZ_SOURCE_CURSOR}. */
59 @JsxConstant({FF, FF_ESR})
60 public static final int MOZ_SOURCE_CURSOR = 4;
61 /** Constant for {@code MOZ_SOURCE_TOUCH}. */
62 @JsxConstant({FF, FF_ESR})
63 public static final int MOZ_SOURCE_TOUCH = 5;
64 /** Constant for {@code MOZ_SOURCE_KEYBOARD}. */
65 @JsxConstant({FF, FF_ESR})
66 public static final int MOZ_SOURCE_KEYBOARD = 6;
67
68 /** The click event type, triggered by {@code onclick} event handlers. */
69 public static final String TYPE_CLICK = "click";
70
71 /** The dblclick event type, triggered by {@code ondblclick} event handlers. */
72 public static final String TYPE_DBL_CLICK = "dblclick";
73
74 /** The mouse over event type, triggered by {@code onmouseover} event handlers. */
75 public static final String TYPE_MOUSE_OVER = "mouseover";
76
77 /** The mouse move event type, triggered by {@code onmousemove} event handlers. */
78 public static final String TYPE_MOUSE_MOVE = "mousemove";
79
80 /** The mouse out event type, triggered by {@code onmouseout} event handlers. */
81 public static final String TYPE_MOUSE_OUT = "mouseout";
82
83 /** The mouse down event type, triggered by {@code onmousedown} event handlers. */
84 public static final String TYPE_MOUSE_DOWN = "mousedown";
85
86 /** The mouse up event type, triggered by {@code onmouseup} event handlers. */
87 public static final String TYPE_MOUSE_UP = "mouseup";
88
89 /** The context menu event type, triggered by {@code oncontextmenu} event handlers. */
90 public static final String TYPE_CONTEXT_MENU = "contextmenu";
91
92 /** The code for left mouse button. */
93 public static final int BUTTON_LEFT = 0;
94
95 /** The code for middle mouse button. */
96 public static final int BUTTON_MIDDLE = 1;
97
98 /** The code for right mouse button. */
99 public static final int BUTTON_RIGHT = 2;
100
101 /** The event's screen coordinates; initially {@code null} and lazily initialized for performance reasons. */
102 private Integer screenX_;
103 private Integer screenY_;
104
105 /** The event's client coordinates; initially {@code null} and lazily initialized for performance reasons. */
106 private Integer clientX_;
107 private Integer clientY_;
108
109 /** The button code according to W3C (0: left button, 1: middle button, 2: right button). */
110 private int button_;
111
112 /** The buttons being depressed (if any) when the mouse event was fired. */
113 private int buttons_;
114
115 /** Switch to disable label handling if we already processing the event triggered from label processing */
116 private boolean processLabelAfterBubbling_ = true;
117
118 /** Whether or not the "meta" key was pressed during the firing of the event. */
119 private boolean metaKey_;
120
121 /**
122 * Used to build the prototype.
123 */
124 public MouseEvent() {
125 super();
126 screenX_ = Integer.valueOf(0);
127 screenY_ = Integer.valueOf(0);
128 setDetail(1);
129 }
130
131 /**
132 * JavaScript constructor.
133 *
134 * @param type the event type
135 * @param details the event details (optional)
136 */
137 @Override
138 @JsxConstructor
139 public void jsConstructor(final String type, final ScriptableObject details) {
140 super.jsConstructor(JavaScriptEngine.toString(type), details);
141 if (details != null && !JavaScriptEngine.isUndefined(details)) {
142 final Object screenX = details.get("screenX", details);
143 if (NOT_FOUND != screenX) {
144 screenX_ = JavaScriptEngine.toInt32(screenX);
145 }
146
147 final Object screenY = details.get("screenY", details);
148 if (NOT_FOUND != screenX) {
149 screenY_ = JavaScriptEngine.toInt32(screenY);
150 }
151
152 final Object clientX = details.get("clientX", details);
153 if (NOT_FOUND != clientX) {
154 clientX_ = JavaScriptEngine.toInt32(clientX);
155 }
156
157 final Object clientY = details.get("clientY", details);
158 if (NOT_FOUND != clientX) {
159 clientY_ = JavaScriptEngine.toInt32(clientY);
160 }
161
162 final Object button = details.get("button", details);
163 if (NOT_FOUND != button) {
164 button_ = JavaScriptEngine.toInt32(button);
165 }
166
167 final Object buttons = details.get("buttons", details);
168 if (NOT_FOUND != buttons) {
169 buttons_ = JavaScriptEngine.toInt32(buttons);
170 }
171
172 setAltKey(JavaScriptEngine.toBoolean(details.get("altKey")));
173 setCtrlKey(JavaScriptEngine.toBoolean(details.get("ctrlKey")));
174 setMetaKey(JavaScriptEngine.toBoolean(details.get("metaKey")));
175 setShiftKey(JavaScriptEngine.toBoolean(details.get("shiftKey")));
176 }
177 }
178
179 /**
180 * Creates a new event instance.
181 * @param domNode the DOM node that triggered the event
182 * @param type the event type
183 * @param shiftKey true if SHIFT is pressed
184 * @param ctrlKey true if CTRL is pressed
185 * @param altKey true if ALT is pressed
186 * @param button the button code, must be {@link #BUTTON_LEFT}, {@link #BUTTON_MIDDLE} or {@link #BUTTON_RIGHT}
187 * @param detail the detail value
188 */
189 public MouseEvent(final DomNode domNode, final String type, final boolean shiftKey,
190 final boolean ctrlKey, final boolean altKey, final int button, final int detail) {
191
192 super(domNode, type);
193 setShiftKey(shiftKey);
194 setCtrlKey(ctrlKey);
195 setAltKey(altKey);
196
197 if (button != BUTTON_LEFT && button != BUTTON_MIDDLE && button != BUTTON_RIGHT) {
198 throw new IllegalArgumentException("Invalid button code: " + button);
199 }
200 button_ = button;
201
202 setDetail(detail);
203 }
204
205 /**
206 * The horizontal coordinate at which the event occurred relative to the DOM implementation's client area.
207 * @return the horizontal coordinate
208 */
209 @JsxGetter
210 public int getClientX() {
211 if (clientX_ == null) {
212 clientX_ = Integer.valueOf(getScreenX());
213 }
214 return clientX_.intValue();
215 }
216
217 /**
218 * Sets the clientX value.
219 * @param value the clientX value
220 */
221 public void setClientX(final int value) {
222 clientX_ = value;
223 }
224
225 /**
226 * The horizontal coordinate at which the event occurred relative to the origin of the screen
227 * coordinate system. The value of this attribute is initialized lazily, in order to optimize
228 * performance (it requires CSS parsing).
229 *
230 * @return the horizontal coordinate
231 */
232 @JsxGetter
233 public int getScreenX() {
234 if (screenX_ == null) {
235 final HTMLElement target = (HTMLElement) getTarget();
236 screenX_ = Integer.valueOf(target.getPosX() + 10);
237 }
238 return screenX_.intValue();
239 }
240
241 /**
242 * Returns the horizontal coordinate of the event relative to whole document.
243 * @return the horizontal coordinate (currently the same as {@link #getScreenX()})
244 * @see <a href="https://developer.mozilla.org/en-US/docs/DOM/event.pageX">Mozilla doc</a>
245 */
246 @JsxGetter
247 public int getPageX() {
248 return getScreenX();
249 }
250
251 /**
252 * The vertical coordinate at which the event occurred relative to the DOM implementation's client area.
253 * @return the horizontal coordinate
254 */
255 @JsxGetter
256 public int getClientY() {
257 if (clientY_ == null) {
258 clientY_ = Integer.valueOf(getScreenY());
259 }
260 return clientY_.intValue();
261 }
262
263 /**
264 * Sets the clientY value.
265 * @param value the clientY value
266 */
267 public void setClientY(final int value) {
268 clientY_ = value;
269 }
270
271 /**
272 * The vertical coordinate at which the event occurred relative to the origin of the screen
273 * coordinate system. The value of this attribute is initialized lazily, in order to optimize
274 * performance (it requires CSS parsing).
275 *
276 * @return the vertical coordinate
277 */
278 @JsxGetter
279 public int getScreenY() {
280 if (screenY_ == null) {
281 final HTMLElement target = (HTMLElement) getTarget();
282 screenY_ = Integer.valueOf(target.getPosY() + 10);
283 }
284 return screenY_.intValue();
285 }
286
287 /**
288 * Returns the vertical coordinate of the event relative to the whole document.
289 * @return the horizontal coordinate (currently the same as {@link #getScreenY()})
290 * @see <a href="https://developer.mozilla.org/en-US/docs/DOM/event.pageY">Mozilla doc</a>
291 */
292 @JsxGetter
293 public int getPageY() {
294 return getScreenY();
295 }
296
297 /**
298 * Gets the button code.
299 * @return the button code
300 */
301 @JsxGetter
302 public int getButton() {
303 return button_;
304 }
305
306 /**
307 * Sets the button code.
308 * @param value the button code
309 */
310 public void setButton(final int value) {
311 button_ = value;
312 }
313
314 /**
315 * Gets the button code.
316 * @return the button code
317 */
318 @JsxGetter
319 public int getButtons() {
320 return buttons_;
321 }
322
323 /**
324 * Sets the button code.
325 * @param value the button code
326 */
327 public void setButtons(final int value) {
328 buttons_ = value;
329 }
330
331 /**
332 * Special for FF (old stuff from Netscape time).
333 * @see <a href="http://unixpapa.com/js/mouse.html">Javascript Madness: Mouse Events</a>
334 * @return the button code
335 */
336 @Override
337 public int getWhich() {
338 return button_ + 1;
339 }
340
341 /**
342 * Implementation of the DOM Level 2 Event method for initializing the mouse event.
343 *
344 * @param type the event type
345 * @param bubbles can the event bubble
346 * @param cancelable can the event be canceled
347 * @param view the view to use for this event
348 * @param detail the detail to set for the event
349 * @param screenX the initial value of screenX
350 * @param screenY the initial value of screenY
351 * @param clientX the initial value of clientX
352 * @param clientY the initial value of clientY
353 * @param ctrlKey is the control key pressed
354 * @param altKey is the alt key pressed
355 * @param shiftKey is the shift key pressed
356 * @param metaKey is the meta key pressed
357 * @param button what mouse button is pressed
358 * @param relatedTarget is there a related target for the event
359 */
360 @JsxFunction
361 public void initMouseEvent(
362 final String type,
363 final boolean bubbles,
364 final boolean cancelable,
365 final Object view,
366 final int detail,
367 final int screenX,
368 final int screenY,
369 final int clientX,
370 final int clientY,
371 final boolean ctrlKey,
372 final boolean altKey,
373 final boolean shiftKey,
374 final boolean metaKey,
375 final int button,
376 final Object relatedTarget) {
377 initUIEvent(type, bubbles, cancelable, view, detail);
378 screenX_ = Integer.valueOf(screenX);
379 screenY_ = Integer.valueOf(screenY);
380 clientX_ = Integer.valueOf(clientX);
381 clientY_ = Integer.valueOf(clientY);
382 setCtrlKey(ctrlKey);
383 setAltKey(altKey);
384 setShiftKey(shiftKey);
385 setMetaKey(metaKey);
386 button_ = button;
387 // Ignore the relatedTarget parameter; we don't support it yet.
388 }
389
390 /**
391 * Returns the mouse event currently firing, or {@code null} if no mouse event is being processed.
392 * @return the mouse event currently firing
393 */
394 @SuppressWarnings("unchecked")
395 public static MouseEvent getCurrentMouseEvent() {
396 final Context context = Context.getCurrentContext();
397 if (context != null) {
398 final ArrayList<Event> events = (ArrayList<Event>) context.getThreadLocal(KEY_CURRENT_EVENT);
399 if (events != null && !events.isEmpty()) {
400 final int lastIdx = events.size() - 1;
401 final Event lastEvent = events.get(lastIdx);
402 if (lastEvent instanceof MouseEvent) {
403 return (MouseEvent) lastEvent;
404 }
405 }
406 }
407 return null;
408 }
409
410 /**
411 * Returns {@code true} if the specified event type should be managed as a mouse event.
412 * @param type the type of event to check
413 * @return {@code true} if the specified event type should be managed as a mouse event
414 */
415 public static boolean isMouseEvent(final String type) {
416 return TYPE_CLICK.equals(type)
417 || TYPE_MOUSE_OVER.equals(type)
418 || TYPE_MOUSE_MOVE.equals(type)
419 || TYPE_MOUSE_OUT.equals(type)
420 || TYPE_MOUSE_DOWN.equals(type)
421 || TYPE_MOUSE_UP.equals(type)
422 || TYPE_CONTEXT_MENU.equals(type);
423 }
424
425 /**
426 * {@inheritDoc} Overridden to modify browser configurations.
427 */
428 @Override
429 @JsxGetter
430 public boolean isAltKey() {
431 return super.isAltKey();
432 }
433
434 /**
435 * {@inheritDoc} Overridden to modify browser configurations.
436 */
437 @Override
438 @JsxGetter
439 public boolean isCtrlKey() {
440 return super.isCtrlKey();
441 }
442
443 /**
444 * {@inheritDoc} Overridden to modify browser configurations.
445 */
446 @Override
447 @JsxGetter
448 public boolean isShiftKey() {
449 return super.isShiftKey();
450 }
451
452 /**
453 * {@inheritDoc} Overridden take care of click events.
454 */
455 @Override
456 public boolean processLabelAfterBubbling() {
457 return MouseEvent.TYPE_CLICK == getType() && processLabelAfterBubbling_;
458 }
459
460 /**
461 * Disable the lable processing if we are already processing one.
462 */
463 public void disableProcessLabelAfterBubbling() {
464 processLabelAfterBubbling_ = false;
465 }
466
467 /**
468 * Returns whether or not the "meta" key was pressed during the event firing.
469 * @return whether or not the "meta" key was pressed during the event firing
470 */
471 @JsxGetter
472 public boolean isMetaKey() {
473 return metaKey_;
474 }
475
476 /**
477 * @param metaKey whether Meta has been pressed during this event or not
478 */
479 protected void setMetaKey(final boolean metaKey) {
480 metaKey_ = metaKey;
481 }
482 }