diff --git a/bundles/org.eclipse.swt/Eclipse SWT Browser/common/org/eclipse/swt/browser/Browser.java b/bundles/org.eclipse.swt/Eclipse SWT Browser/common/org/eclipse/swt/browser/Browser.java
index 3f4eb80708e..f5b7c794f75 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT Browser/common/org/eclipse/swt/browser/Browser.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT Browser/common/org/eclipse/swt/browser/Browser.java
@@ -16,6 +16,7 @@
import java.util.*;
import org.eclipse.swt.*;
+import org.eclipse.swt.events.*;
import org.eclipse.swt.program.*;
import org.eclipse.swt.widgets.*;
@@ -1106,6 +1107,11 @@ public void removeVisibilityWindowListener (VisibilityWindowListener listener) {
* Sets whether javascript will be allowed to run in pages subsequently
* viewed in the receiver. Note that setting this value does not affect
* the running of javascript in the current page.
+ *
+ * Note: When using the {@link SWT#EDGE} browser on Windows disabling javascript
+ * has certain side effects, e.g. proper support for {@link MouseEvent} depends
+ * on it and, when disabled, a limited timer-based fallback implementation is
+ * used.
*
* @param enabled the receiver's new javascript enabled state
*
diff --git a/bundles/org.eclipse.swt/Eclipse SWT Browser/win32/org/eclipse/swt/browser/Edge.java b/bundles/org.eclipse.swt/Eclipse SWT Browser/win32/org/eclipse/swt/browser/Edge.java
index 49cb5e1b926..d7560f91b9e 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT Browser/win32/org/eclipse/swt/browser/Edge.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT Browser/win32/org/eclipse/swt/browser/Edge.java
@@ -24,6 +24,7 @@
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.function.*;
+import java.util.stream.*;
import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
@@ -88,9 +89,34 @@ public WebViewEnvironment(ICoreWebView2Environment environment) {
private boolean ignoreFocusIn;
private String lastCustomText;
+ private boolean needsMouseMovementFallback = true;
private static record CursorPosition(Point location, boolean isInsideBrowser) {};
private CursorPosition previousCursorPosition = new CursorPosition(new Point(0, 0), false);
+ private static final String WEBMESSAGE_KIND_MOUSE_RELATED_DOM_EVENT = "mouse-related-dom-event";
+ private static final String EVENT_MOUSEDOWN = "mousedown"; //$NON-NLS-1$
+ private static final String EVENT_MOUSEUP = "mouseup"; //$NON-NLS-1$
+ private static final String EVENT_MOUSEMOVE = "mousemove"; //$NON-NLS-1$
+ private static final String EVENT_MOUSEOVER = "mouseover"; //$NON-NLS-1$
+ private static final String EVENT_MOUSEOUT = "mouseout"; //$NON-NLS-1$
+ private static final String EVENT_DRAGSTART = "dragstart"; //$NON-NLS-1$
+ private static final String EVENT_DRAGEND = "dragend"; //$NON-NLS-1$
+ private static final String EVENT_DOUBLECLICK = "dblclick"; //$NON-NLS-1$
+ private static final String EVENT_MOUSEWHEEL = "wheel"; //$NON-NLS-1$
+ private static final Set MOUSE_RELATED_DOM_EVENTS = Set.of( //
+ EVENT_MOUSEDOWN, EVENT_MOUSEUP, //
+ EVENT_MOUSEMOVE, //
+ EVENT_MOUSEOVER, EVENT_MOUSEOUT, //
+ EVENT_DRAGSTART, EVENT_DRAGEND, //
+ EVENT_DOUBLECLICK, //
+ EVENT_MOUSEWHEEL//
+ );
+ private static record MouseRelatedDomEvent(String eventType, boolean altKey, boolean ctrlKey, boolean shiftKey,
+ long clientX, long clientY, long button, boolean fromElementSet, boolean toElementSet, long deltaY) {
+ }
+ private int lastMouseMoveX;
+ private int lastMouseMoveY;
+
static {
NativeClearSessions = () -> {
ICoreWebView2CookieManager manager = getCookieManager();
@@ -775,6 +801,9 @@ void setupBrowser(int hr, long pv) {
handler = newCallback(this::handleSourceChanged);
webView.add_SourceChanged(handler, token);
handler.Release();
+ handler = newCallback(this::handleWebMessageReceived);
+ webView.add_WebMessageReceived(handler, token);
+ handler.Release();
handler = newCallback(this::handleMoveFocusRequested);
controller.add_MoveFocusRequested(handler, token);
handler.Release();
@@ -814,7 +843,7 @@ void setupBrowser(int hr, long pv) {
browser.addListener(SWT.FocusIn, this::browserFocusIn);
browser.addListener(SWT.Resize, this::browserResize);
browser.addListener(SWT.Move, this::browserMove);
- scheduleMouseMovementHandling();
+ scheduleMouseMovementHandlingIfNeeded();
// Sometimes when the shell of the browser is opened before the browser is
// initialized, nothing is drawn on the shell. We need browserResize to force
@@ -881,7 +910,10 @@ void browserResize(Event event) {
controller.put_IsVisible(true);
}
-private void scheduleMouseMovementHandling() {
+private void scheduleMouseMovementHandlingIfNeeded() {
+ if (!needsMouseMovementFallback) {
+ return;
+ }
browser.getDisplay().timerExec(100, () -> {
if (browser.isDisposed()) {
return;
@@ -889,7 +921,10 @@ private void scheduleMouseMovementHandling() {
if (browser.isVisible() && hasDisplayFocus()) {
handleMouseMovement();
}
- scheduleMouseMovementHandling();
+ if (!needsMouseMovementFallback) {
+ return;
+ }
+ scheduleMouseMovementHandlingIfNeeded();
});
}
@@ -969,6 +1004,14 @@ public boolean execute(String script) {
// Feature in WebView2. ExecuteScript works regardless of IsScriptEnabled setting.
// Disallow programmatic execution manually.
if (!jsEnabled) return false;
+ return executeInternal(script);
+}
+
+/**
+ * Unconditional script execution, bypassing {@link WebBrowser#jsEnabled} flag /
+ * {@link Browser#setJavascriptEnabled(boolean)}.
+ */
+private boolean executeInternal(String script) {
IUnknown completion = newCallback((long result, long json) -> COM.S_OK);
int hr = webViewProvider.getWebView(true).ExecuteScript(stringToWstr(script), completion);
completion.Release();
@@ -1098,8 +1141,7 @@ int handleNavigationStarting(long pView, long pArgs, boolean top) {
// will be eventually cleared again in handleNavigationCompleted().
navigations.put(pNavId[0], event);
if (event.doit) {
- jsEnabled = jsEnabledOnNextPage;
- settings.put_IsScriptEnabled(jsEnabled);
+ settings.put_IsScriptEnabled(jsEnabledOnNextPage);
// Register browser functions in the new document.
if (!functions.isEmpty()) {
StringBuilder sb = new StringBuilder();
@@ -1201,6 +1243,29 @@ int handleDOMContentLoaded(long pView, long pArgs) {
sendProgressCompleted();
}
}
+ executeInternal(
+ """
+ const events = [%%events%%];
+ events.forEach(eventType => {
+ window.addEventListener(eventType, function(event) {
+ window.chrome.webview.postMessage([
+ '%%webmessagekind%%',
+ eventType,
+ event.altKey,
+ event.ctrlKey,
+ event.shiftKey,
+ event.clientX,
+ event.clientY,
+ event.button,
+ "fromElement" in event && event.fromElement != null,
+ "toElement" in event && event.toElement != null,
+ "deltaY" in event ? event.deltaY : 0,
+ ]);
+ });
+ });
+ """ //
+ .replace("%%events%%", MOUSE_RELATED_DOM_EVENTS.stream().map(x -> "'" + x + "'").collect(Collectors.joining(", "))) //
+ .replace("%%webmessagekind%%", WEBMESSAGE_KIND_MOUSE_RELATED_DOM_EVENT));
return COM.S_OK;
}
@@ -1323,6 +1388,11 @@ int handleNavigationCompleted(long pView, long pArgs, boolean top) {
// ProgressListener.completed from here.
sendProgressCompleted();
}
+ if (top) {
+ jsEnabled = jsEnabledOnNextPage;
+ needsMouseMovementFallback = !jsEnabled;
+ scheduleMouseMovementHandlingIfNeeded();
+ }
int[] pIsSuccess = new int[1];
args.get_IsSuccess(pIsSuccess);
if (pIsSuccess[0] != 0) {
@@ -1524,6 +1594,136 @@ int handleMoveFocusRequested(long pView, long pArgs) {
return COM.S_OK;
}
+int handleWebMessageReceived(long pView, long pArgs) {
+ ICoreWebView2WebMessageReceivedEventArgs args = new ICoreWebView2WebMessageReceivedEventArgs(pArgs);
+ long[] ppszWebMessageJson = new long[1];
+ int hr = args.get_WebMessageAsJson(ppszWebMessageJson);
+ if (hr != COM.S_OK) return hr;
+ try {
+ String webMessageJson = wstrToString(ppszWebMessageJson[0], true);
+ Object[] data = (Object[]) JSON.parse(webMessageJson);
+ if (WEBMESSAGE_KIND_MOUSE_RELATED_DOM_EVENT.equals(data[0])) {
+ MouseRelatedDomEvent mouseRelatedDomEvent = new MouseRelatedDomEvent( //
+ (String) data[1], //
+ (boolean) data[2], //
+ (boolean) data[3], //
+ (boolean) data[4], //
+ Math.round((double) data[5]), //
+ Math.round((double) data[6]), //
+ Math.round((double) data[7]), //
+ (boolean) data[8], //
+ (boolean) data[9], //
+ Math.round((double) data[10]) //
+ );
+ handleMouseRelatedDomEvent(mouseRelatedDomEvent);
+ }
+ } catch (Exception e) {
+ System.err.println(e);
+ }
+ return COM.S_OK;
+}
+
+/**
+ * Insipired by the mouse-event related parts of
+ * {@link IE#handleDOMEvent(org.eclipse.swt.ole.win32.OleEvent)}
+ */
+private void handleMouseRelatedDomEvent(MouseRelatedDomEvent domEvent) {
+ String eventType = domEvent.eventType();
+
+ /*
+ * Feature in Edge. MouseOver/MouseOut events are fired any time the mouse enters
+ * or exits any element within the Browser. To ensure that SWT events are only
+ * fired for mouse movements into or out of the Browser, do not fire an event if
+ * the element being exited (on MouseOver) or entered (on MouseExit) is within
+ * the Browser.
+ */
+ if (eventType.equals(EVENT_MOUSEOVER)) {
+ if (domEvent.fromElementSet()) {
+ return;
+ }
+ }
+ if (eventType.equals(EVENT_MOUSEOUT)) {
+ if (domEvent.toElementSet()) {
+ return;
+ }
+ }
+
+ int mask = 0;
+ Event newEvent = new Event();
+ newEvent.widget = browser;
+ newEvent.x = (int) domEvent.clientX(); newEvent.y = (int) domEvent.clientY();
+ if (domEvent.ctrlKey()) mask |= SWT.CTRL;
+ if (domEvent.altKey()) mask |= SWT.ALT;
+ if (domEvent.shiftKey()) mask |= SWT.SHIFT;
+ newEvent.stateMask = mask;
+
+ int button = (int) domEvent.button();
+ switch (button) {
+ case 1: button = 1; break;
+ case 2: button = 3; break;
+ case 4: button = 2; break;
+ }
+
+ if (eventType.equals(EVENT_MOUSEDOWN)) {
+ newEvent.type = SWT.MouseDown;
+ newEvent.button = button;
+ newEvent.count = 1;
+ } else if (eventType.equals(EVENT_MOUSEUP) || eventType.equals(EVENT_DRAGEND)) {
+ newEvent.type = SWT.MouseUp;
+ newEvent.button = button != 0 ? button : 1; /* button assumed to be 1 for dragends */
+ newEvent.count = 1;
+ switch (newEvent.button) {
+ case 1: newEvent.stateMask |= SWT.BUTTON1; break;
+ case 2: newEvent.stateMask |= SWT.BUTTON2; break;
+ case 3: newEvent.stateMask |= SWT.BUTTON3; break;
+ case 4: newEvent.stateMask |= SWT.BUTTON4; break;
+ case 5: newEvent.stateMask |= SWT.BUTTON5; break;
+ }
+ } else if (eventType.equals(EVENT_MOUSEWHEEL)) {
+ newEvent.type = SWT.MouseWheel;
+ // Chromium/Edge uses deltaMode DOM_DELTA_PIXEL which
+ // - has a different sign than the legacy MouseWheelEvent wheelDelta
+ // - depends on the zoom of the browser
+ // https://github.com/w3c/uievents/issues/181
+ // The literal value of deltaY is therefore useless.
+ // Instead, we simply use the sign for the direction and combine
+ // it with the internal hard-coded value of '3 lines'.
+ newEvent.count = domEvent.deltaY() > 0 ? -3 : 3;
+ } else if (eventType.equals(EVENT_MOUSEMOVE)) {
+ /*
+ * Feature in Edge. Spurious and redundant mousemove events are often received. The workaround
+ * is to not fire MouseMove events whose x and y values match the last MouseMove.
+ */
+ if (newEvent.x == lastMouseMoveX && newEvent.y == lastMouseMoveY) {
+ return;
+ }
+ newEvent.type = SWT.MouseMove;
+ lastMouseMoveX = newEvent.x; lastMouseMoveY = newEvent.y;
+ } else if (eventType.equals(EVENT_MOUSEOVER)) {
+ newEvent.type = SWT.MouseEnter;
+ } else if (eventType.equals(EVENT_MOUSEOUT)) {
+ newEvent.type = SWT.MouseExit;
+ } else if (eventType.equals(EVENT_DRAGSTART)) {
+ newEvent.type = SWT.DragDetect;
+ newEvent.button = 1; /* button assumed to be 1 for dragstarts */
+ newEvent.stateMask |= SWT.BUTTON1;
+ }
+
+ browser.notifyListeners(newEvent.type, newEvent);
+
+ if (eventType.equals(EVENT_DOUBLECLICK)) {
+ newEvent = new Event ();
+ newEvent.widget = browser;
+ newEvent.type = SWT.MouseDoubleClick;
+ newEvent.x = (int) domEvent.clientX(); newEvent.y = (int) domEvent.clientY();
+ newEvent.stateMask = mask;
+ newEvent.type = SWT.MouseDoubleClick;
+ newEvent.button = 1; /* dblclick only comes for button 1 and does not set the button property */
+ newEvent.count = 2;
+ browser.notifyListeners (newEvent.type, newEvent);
+ }
+}
+
@Override
public boolean isBackEnabled() {
int[] pval = new int[1];
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/ole/win32/ICoreWebView2.java b/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/ole/win32/ICoreWebView2.java
index 77b3c8e5f5b..473a3d981d0 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/ole/win32/ICoreWebView2.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/ole/win32/ICoreWebView2.java
@@ -79,8 +79,8 @@ public int PostWebMessageAsJson(char[] webMessageAsJson) {
return COM.VtblCall(32, address, webMessageAsJson);
}
-public int add_WebMessageReceived(long handler, long[] token) {
- return COM.VtblCall(34, address, handler, token);
+public int add_WebMessageReceived(IUnknown eventHandler, long[] token) {
+ return COM.VtblCall(34, address, eventHandler.address, token);
}
public int get_CanGoBack(int[] canGoBack) {