View Javadoc
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 getMetaKey() {
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 }