Skip to content

Commit b17cd3f

Browse files
committed
Edge: Implement mouse-related event listener support
- MouseListener up down doubleClick - MouseMoveListener move - MouseTrackListener exit enter - MouseWheelListener scroll - DragDetectListener dragDetected The implementation is JavaScript-based by attaching listeners to the DOM for all relevant events and forwarding them to the WebView via window.chrome.webview.postMessage(). The event handling is analogous to IE's IE#handleDOMEvent(org.eclipse.swt.ole.win32.OleEvent). Note: Since the implementation is JavaScript-based, this requires JavaScript to be enabled on the Browser instance. When JavaScript is disabled, we keep using the timer-based fallback implemenation introduced in #1551. As JavaScript is only truly enabled *after* navigation has finished, we also keep using this workaround when the Browser is first instantiated and before the first page has loaded. This change also fixes the jsEnabled flag lifecycle to reflect this fact. This resolves #2164 as long as JavaScript is enabled.
1 parent e2ac7f1 commit b17cd3f

File tree

5 files changed

+213
-9
lines changed

5 files changed

+213
-9
lines changed

bundles/org.eclipse.swt/Eclipse SWT Browser/common/org/eclipse/swt/browser/Browser.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.util.*;
1717

1818
import org.eclipse.swt.*;
19+
import org.eclipse.swt.events.*;
1920
import org.eclipse.swt.program.*;
2021
import org.eclipse.swt.widgets.*;
2122

@@ -1106,6 +1107,11 @@ public void removeVisibilityWindowListener (VisibilityWindowListener listener) {
11061107
* Sets whether javascript will be allowed to run in pages subsequently
11071108
* viewed in the receiver. Note that setting this value does not affect
11081109
* the running of javascript in the current page.
1110+
* <p>
1111+
* Note: When using the {@link SWT#EDGE} browser on Windows disabling javascript
1112+
* has certain side effects, e.g. proper support for {@link MouseEvent} depends
1113+
* on it and, when disabled, a limited timer-based fallback implementation is
1114+
* used.
11091115
*
11101116
* @param enabled the receiver's new javascript enabled state
11111117
*

bundles/org.eclipse.swt/Eclipse SWT Browser/common/org/eclipse/swt/browser/WebBrowser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ abstract class WebBrowser {
3030
StatusTextListener[] statusTextListeners = new StatusTextListener[0];
3131
TitleListener[] titleListeners = new TitleListener[0];
3232
VisibilityWindowListener[] visibilityWindowListeners = new VisibilityWindowListener[0];
33-
boolean jsEnabledOnNextPage = true, jsEnabled = true;
33+
boolean jsEnabledOnNextPage = true, jsEnabled = false;
3434
int nextFunctionIndex = 1;
3535
Object evaluateResult;
3636

bundles/org.eclipse.swt/Eclipse SWT Browser/win32/org/eclipse/swt/browser/Edge.java

Lines changed: 203 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.concurrent.*;
2525
import java.util.concurrent.atomic.*;
2626
import java.util.function.*;
27+
import java.util.stream.*;
2728

2829
import org.eclipse.swt.*;
2930
import org.eclipse.swt.graphics.*;
@@ -91,6 +92,30 @@ public WebViewEnvironment(ICoreWebView2Environment environment) {
9192
private static record CursorPosition(Point location, boolean isInsideBrowser) {};
9293
private CursorPosition previousCursorPosition = new CursorPosition(new Point(0, 0), false);
9394

95+
private static final String WEBMESSAGE_KIND_MOUSE_RELATED_DOM_EVENT = "mouse-related-dom-event";
96+
private static final String EVENT_MOUSEDOWN = "mousedown"; //$NON-NLS-1$
97+
private static final String EVENT_MOUSEUP = "mouseup"; //$NON-NLS-1$
98+
private static final String EVENT_MOUSEMOVE = "mousemove"; //$NON-NLS-1$
99+
private static final String EVENT_MOUSEOVER = "mouseover"; //$NON-NLS-1$
100+
private static final String EVENT_MOUSEOUT = "mouseout"; //$NON-NLS-1$
101+
private static final String EVENT_DRAGSTART = "dragstart"; //$NON-NLS-1$
102+
private static final String EVENT_DRAGEND = "dragend"; //$NON-NLS-1$
103+
private static final String EVENT_DOUBLECLICK = "dblclick"; //$NON-NLS-1$
104+
private static final String EVENT_MOUSEWHEEL = "wheel"; //$NON-NLS-1$
105+
private static final Set<String> MOUSE_RELATED_DOM_EVENTS = Set.of( //
106+
EVENT_MOUSEDOWN, EVENT_MOUSEUP, //
107+
EVENT_MOUSEMOVE, //
108+
EVENT_MOUSEOVER, EVENT_MOUSEOUT, //
109+
EVENT_DRAGSTART, EVENT_DRAGEND, //
110+
EVENT_DOUBLECLICK, //
111+
EVENT_MOUSEWHEEL//
112+
);
113+
private static record MouseRelatedDomEvent(String eventType, boolean altKey, boolean ctrlKey, boolean shiftKey,
114+
long clientX, long clientY, long button, boolean fromElementSet, boolean toElementSet, long deltaY) {
115+
}
116+
private int lastMouseMoveX;
117+
private int lastMouseMoveY;
118+
94119
static {
95120
NativeClearSessions = () -> {
96121
ICoreWebView2CookieManager manager = getCookieManager();
@@ -775,6 +800,9 @@ void setupBrowser(int hr, long pv) {
775800
handler = newCallback(this::handleSourceChanged);
776801
webView.add_SourceChanged(handler, token);
777802
handler.Release();
803+
handler = newCallback(this::handleWebMessageReceived);
804+
webView.add_WebMessageReceived(handler, token);
805+
handler.Release();
778806
handler = newCallback(this::handleMoveFocusRequested);
779807
controller.add_MoveFocusRequested(handler, token);
780808
handler.Release();
@@ -814,7 +842,7 @@ void setupBrowser(int hr, long pv) {
814842
browser.addListener(SWT.FocusIn, this::browserFocusIn);
815843
browser.addListener(SWT.Resize, this::browserResize);
816844
browser.addListener(SWT.Move, this::browserMove);
817-
scheduleMouseMovementHandling();
845+
scheduleMouseMovementHandlingIfNeeded();
818846

819847
// Sometimes when the shell of the browser is opened before the browser is
820848
// initialized, nothing is drawn on the shell. We need browserResize to force
@@ -881,15 +909,21 @@ void browserResize(Event event) {
881909
controller.put_IsVisible(true);
882910
}
883911

884-
private void scheduleMouseMovementHandling() {
912+
private void scheduleMouseMovementHandlingIfNeeded() {
913+
if (jsEnabled) {
914+
return;
915+
}
885916
browser.getDisplay().timerExec(100, () -> {
886917
if (browser.isDisposed()) {
887918
return;
888919
}
889920
if (browser.isVisible() && hasDisplayFocus()) {
890921
handleMouseMovement();
891922
}
892-
scheduleMouseMovementHandling();
923+
if (jsEnabled) {
924+
return;
925+
}
926+
scheduleMouseMovementHandlingIfNeeded();
893927
});
894928
}
895929

@@ -969,6 +1003,14 @@ public boolean execute(String script) {
9691003
// Feature in WebView2. ExecuteScript works regardless of IsScriptEnabled setting.
9701004
// Disallow programmatic execution manually.
9711005
if (!jsEnabled) return false;
1006+
return executeInternal(script);
1007+
}
1008+
1009+
/**
1010+
* Unconditional script execution, bypassing {@link WebBrowser#jsEnabled} flag /
1011+
* {@link Browser#setJavascriptEnabled(boolean)}.
1012+
*/
1013+
private boolean executeInternal(String script) {
9721014
IUnknown completion = newCallback((long result, long json) -> COM.S_OK);
9731015
int hr = webViewProvider.getWebView(true).ExecuteScript(stringToWstr(script), completion);
9741016
completion.Release();
@@ -1098,8 +1140,7 @@ int handleNavigationStarting(long pView, long pArgs, boolean top) {
10981140
// will be eventually cleared again in handleNavigationCompleted().
10991141
navigations.put(pNavId[0], event);
11001142
if (event.doit) {
1101-
jsEnabled = jsEnabledOnNextPage;
1102-
settings.put_IsScriptEnabled(jsEnabled);
1143+
settings.put_IsScriptEnabled(jsEnabledOnNextPage);
11031144
// Register browser functions in the new document.
11041145
if (!functions.isEmpty()) {
11051146
StringBuilder sb = new StringBuilder();
@@ -1201,6 +1242,29 @@ int handleDOMContentLoaded(long pView, long pArgs) {
12011242
sendProgressCompleted();
12021243
}
12031244
}
1245+
executeInternal(
1246+
"""
1247+
const events = [%%events%%];
1248+
events.forEach(eventType => {
1249+
window.addEventListener(eventType, function(event) {
1250+
window.chrome.webview.postMessage([
1251+
'%%webmessagekind%%',
1252+
eventType,
1253+
event.altKey,
1254+
event.ctrlKey,
1255+
event.shiftKey,
1256+
event.clientX,
1257+
event.clientY,
1258+
event.button,
1259+
"fromElement" in event && event.fromElement != null,
1260+
"toElement" in event && event.toElement != null,
1261+
"deltaY" in event ? event.deltaY : 0,
1262+
]);
1263+
});
1264+
});
1265+
""" //
1266+
.replace("%%events%%", MOUSE_RELATED_DOM_EVENTS.stream().map(x -> "'" + x + "'").collect(Collectors.joining(", "))) //
1267+
.replace("%%webmessagekind%%", WEBMESSAGE_KIND_MOUSE_RELATED_DOM_EVENT));
12041268
return COM.S_OK;
12051269
}
12061270

@@ -1323,6 +1387,10 @@ int handleNavigationCompleted(long pView, long pArgs, boolean top) {
13231387
// ProgressListener.completed from here.
13241388
sendProgressCompleted();
13251389
}
1390+
if (top) {
1391+
jsEnabled = jsEnabledOnNextPage;
1392+
scheduleMouseMovementHandlingIfNeeded();
1393+
}
13261394
int[] pIsSuccess = new int[1];
13271395
args.get_IsSuccess(pIsSuccess);
13281396
if (pIsSuccess[0] != 0) {
@@ -1524,6 +1592,136 @@ int handleMoveFocusRequested(long pView, long pArgs) {
15241592
return COM.S_OK;
15251593
}
15261594

1595+
int handleWebMessageReceived(long pView, long pArgs) {
1596+
ICoreWebView2WebMessageReceivedEventArgs args = new ICoreWebView2WebMessageReceivedEventArgs(pArgs);
1597+
long[] ppszWebMessageJson = new long[1];
1598+
int hr = args.get_WebMessageAsJson(ppszWebMessageJson);
1599+
if (hr != COM.S_OK) return hr;
1600+
try {
1601+
String webMessageJson = wstrToString(ppszWebMessageJson[0], true);
1602+
Object[] data = (Object[]) JSON.parse(webMessageJson);
1603+
if (WEBMESSAGE_KIND_MOUSE_RELATED_DOM_EVENT.equals(data[0])) {
1604+
MouseRelatedDomEvent mouseRelatedDomEvent = new MouseRelatedDomEvent( //
1605+
(String) data[1], //
1606+
(boolean) data[2], //
1607+
(boolean) data[3], //
1608+
(boolean) data[4], //
1609+
Math.round((double) data[5]), //
1610+
Math.round((double) data[6]), //
1611+
Math.round((double) data[7]), //
1612+
(boolean) data[8], //
1613+
(boolean) data[9], //
1614+
Math.round((double) data[10]) //
1615+
);
1616+
handleMouseRelatedDomEvent(mouseRelatedDomEvent);
1617+
}
1618+
} catch (Exception e) {
1619+
System.err.println(e);
1620+
}
1621+
return COM.S_OK;
1622+
}
1623+
1624+
/**
1625+
* Insipired by the mouse-event related parts of
1626+
* {@link IE#handleDOMEvent(org.eclipse.swt.ole.win32.OleEvent)}
1627+
*/
1628+
private void handleMouseRelatedDomEvent(MouseRelatedDomEvent domEvent) {
1629+
String eventType = domEvent.eventType();
1630+
1631+
/*
1632+
* Feature in Edge. MouseOver/MouseOut events are fired any time the mouse enters
1633+
* or exits any element within the Browser. To ensure that SWT events are only
1634+
* fired for mouse movements into or out of the Browser, do not fire an event if
1635+
* the element being exited (on MouseOver) or entered (on MouseExit) is within
1636+
* the Browser.
1637+
*/
1638+
if (eventType.equals(EVENT_MOUSEOVER)) {
1639+
if (domEvent.fromElementSet()) {
1640+
return;
1641+
}
1642+
}
1643+
if (eventType.equals(EVENT_MOUSEOUT)) {
1644+
if (domEvent.toElementSet()) {
1645+
return;
1646+
}
1647+
}
1648+
1649+
int mask = 0;
1650+
Event newEvent = new Event();
1651+
newEvent.widget = browser;
1652+
newEvent.x = (int) domEvent.clientX(); newEvent.y = (int) domEvent.clientY();
1653+
if (domEvent.ctrlKey()) mask |= SWT.CTRL;
1654+
if (domEvent.altKey()) mask |= SWT.ALT;
1655+
if (domEvent.shiftKey()) mask |= SWT.SHIFT;
1656+
newEvent.stateMask = mask;
1657+
1658+
int button = (int) domEvent.button();
1659+
switch (button) {
1660+
case 1: button = 1; break;
1661+
case 2: button = 3; break;
1662+
case 4: button = 2; break;
1663+
}
1664+
1665+
if (eventType.equals(EVENT_MOUSEDOWN)) {
1666+
newEvent.type = SWT.MouseDown;
1667+
newEvent.button = button;
1668+
newEvent.count = 1;
1669+
} else if (eventType.equals(EVENT_MOUSEUP) || eventType.equals(EVENT_DRAGEND)) {
1670+
newEvent.type = SWT.MouseUp;
1671+
newEvent.button = button != 0 ? button : 1; /* button assumed to be 1 for dragends */
1672+
newEvent.count = 1;
1673+
switch (newEvent.button) {
1674+
case 1: newEvent.stateMask |= SWT.BUTTON1; break;
1675+
case 2: newEvent.stateMask |= SWT.BUTTON2; break;
1676+
case 3: newEvent.stateMask |= SWT.BUTTON3; break;
1677+
case 4: newEvent.stateMask |= SWT.BUTTON4; break;
1678+
case 5: newEvent.stateMask |= SWT.BUTTON5; break;
1679+
}
1680+
} else if (eventType.equals(EVENT_MOUSEWHEEL)) {
1681+
newEvent.type = SWT.MouseWheel;
1682+
// Chromium/Edge uses deltaMode DOM_DELTA_PIXEL which
1683+
// - has a different sign than the legacy MouseWheelEvent wheelDelta
1684+
// - depends on the zoom of the browser
1685+
// https://github.com/w3c/uievents/issues/181
1686+
// The literal value of deltaY is therefore useless.
1687+
// Instead, we simply use the sign for the direction and combine
1688+
// it with the internal hard-coded value of '3 lines'.
1689+
newEvent.count = domEvent.deltaY() > 0 ? -3 : 3;
1690+
} else if (eventType.equals(EVENT_MOUSEMOVE)) {
1691+
/*
1692+
* Feature in Edge. Spurious and redundant mousemove events are often received. The workaround
1693+
* is to not fire MouseMove events whose x and y values match the last MouseMove.
1694+
*/
1695+
if (newEvent.x == lastMouseMoveX && newEvent.y == lastMouseMoveY) {
1696+
return;
1697+
}
1698+
newEvent.type = SWT.MouseMove;
1699+
lastMouseMoveX = newEvent.x; lastMouseMoveY = newEvent.y;
1700+
} else if (eventType.equals(EVENT_MOUSEOVER)) {
1701+
newEvent.type = SWT.MouseEnter;
1702+
} else if (eventType.equals(EVENT_MOUSEOUT)) {
1703+
newEvent.type = SWT.MouseExit;
1704+
} else if (eventType.equals(EVENT_DRAGSTART)) {
1705+
newEvent.type = SWT.DragDetect;
1706+
newEvent.button = 1; /* button assumed to be 1 for dragstarts */
1707+
newEvent.stateMask |= SWT.BUTTON1;
1708+
}
1709+
1710+
browser.notifyListeners(newEvent.type, newEvent);
1711+
1712+
if (eventType.equals(EVENT_DOUBLECLICK)) {
1713+
newEvent = new Event ();
1714+
newEvent.widget = browser;
1715+
newEvent.type = SWT.MouseDoubleClick;
1716+
newEvent.x = (int) domEvent.clientX(); newEvent.y = (int) domEvent.clientY();
1717+
newEvent.stateMask = mask;
1718+
newEvent.type = SWT.MouseDoubleClick;
1719+
newEvent.button = 1; /* dblclick only comes for button 1 and does not set the button property */
1720+
newEvent.count = 2;
1721+
browser.notifyListeners (newEvent.type, newEvent);
1722+
}
1723+
}
1724+
15271725
@Override
15281726
public boolean isBackEnabled() {
15291727
int[] pval = new int[1];

bundles/org.eclipse.swt/Eclipse SWT Browser/win32/org/eclipse/swt/browser/WebSite.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@ int ProcessUrlAction(long pwszUrl, int dwAction, long pPolicy, int cbPolicy, lon
594594
}
595595
if (dwAction == IE.URLACTION_SCRIPT_RUN) {
596596
IE browser = (IE)((Browser)getParent ().getParent ()).webBrowser;
597-
policy = browser.jsEnabled ? IE.URLPOLICY_ALLOW : IE.URLPOLICY_DISALLOW;
597+
policy = browser.jsEnabledOnNextPage ? IE.URLPOLICY_ALLOW : IE.URLPOLICY_DISALLOW;
598598
}
599599

600600
if (policy == IE.INET_E_DEFAULT_ACTION) return IE.INET_E_DEFAULT_ACTION;

bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/ole/win32/ICoreWebView2.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ public int PostWebMessageAsJson(char[] webMessageAsJson) {
7979
return COM.VtblCall(32, address, webMessageAsJson);
8080
}
8181

82-
public int add_WebMessageReceived(long handler, long[] token) {
83-
return COM.VtblCall(34, address, handler, token);
82+
public int add_WebMessageReceived(IUnknown eventHandler, long[] token) {
83+
return COM.VtblCall(34, address, eventHandler.address, token);
8484
}
8585

8686
public int get_CanGoBack(int[] canGoBack) {

0 commit comments

Comments
 (0)