FULL PRODUCT VERSION :
ADDITIONAL OS VERSION INFORMATION :
Windows 10
EXTRA RELEVANT SYSTEM CONFIGURATION :
- tested on MacOS to verify that it's Windows specific
A DESCRIPTION OF THE PROBLEM :
The attached sample shows that the webview invokes the onContextMenu mouse event in JavaScript and falsely reports metaKey=true.
Running the same HTML in a pure JavaFX web view (not embedded in Swing) does not show this anomaly and correctly reports no modifier. So it looks like somewhere in the Swing/JavaFX embedding something goes in the weeds.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
- run the attached sample in Windows
- right-click on the yellow box to see what modifier keys are reported to JavaScript
- compare with Mac OS or other OS
- use the "contextmenu.html" down in the source code to test with pure JavaFX WebView, like from the Oracle sample, or with plain web browsers
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
expecting "metaKey=false" if no modifier key is pressed while performing mouse right-click
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.awt.Dimension;
import java.awt.Point;
import java.lang.reflect.Method;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.event.Event;
import javafx.event.EventDispatchChain;
import javafx.event.EventDispatcher;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class JavaFX {
private final static int WIDTH = 500;
private final static int HEIGHT = 500;
/* Create a JFrame with a JButton and a JFXPanel containing the WebView. */
private static void initAndShowGUI() {
// This method is invoked on Swing thread
JFrame frame = new JFrame(javafx());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//frame.getContentPane().setLayout(null); // do the layout manually
final JFXPanel fxPanel = new JFXPanel();
frame.add(fxPanel);
frame.setVisible(true);
fxPanel.setLocation(new Point(100,100));
//fxPanel.setSize(new Dimension(WIDTH,HEIGHT));
frame.getContentPane().setPreferredSize(new Dimension(WIDTH,HEIGHT));
frame.pack();
//frame.setResizable(false);
Platform.runLater(new Runnable() { // this will run initFX as JavaFX-Thread
@Override
public void run() {
initFX(fxPanel);
}
});
}
/* Creates a WebView and loads a test page. */
private static void initFX(final JFXPanel fxPanel) {
Group group = new Group();
Scene scene = new Scene(group);
fxPanel.setScene(scene);
WebView webView = new WebView();
webView.setMinSize(WIDTH,HEIGHT);
webView.setMaxSize(WIDTH,HEIGHT);
group.getChildren().add(webView);
WebEngine webEngine = webView.getEngine();
//uncomment these two lines for a hacky workaround
//webView.setEventDispatcher(new MyEventDispatcher(webView.getEventDispatcher()));
String content =
"<html><head><script>"
+"function registerContextMenu(){"
+" var myDiv=document.getElementById(\"myYellowDiv\");"
+" myDiv.addEventListener(\"contextmenu\",function (mouseevent){"
+" myDiv.innerHTML=\"RightClick Ctrl-Key:\"+mouseevent.ctrlKey+\" Meta-Key:\"+mouseevent.metaKey;"
+" return true;"
+" });}"
+"</script></head>"
+"<body onLoad=\"registerContextMenu();\">"
+"<div onContextMenu=\"return false;\" id=\"myYellowDiv\" style=\"width:400px; height:200px; background-color:yellow\">right-click here...</div>"
+"</body></html>";
webEngine.loadContent(content);
}
public static String javafx()
{
String javafx;
try
{
Class<?> c = Class.forName("com.sun.javafx.runtime.VersionInfo");
Method m = c.getMethod("getRuntimeVersion", new Class[]{});
javafx = (String)m.invoke(c);
}
catch (Exception e)
{
javafx = "";
}
return javafx;
}
public static void main(final String[] args) {
System.out.println("javax.runtime.version="+javafx());
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
initAndShowGUI();
}
});
}
/**
*
* alternate event dispatcher to work around the problem
*
*/
public class MyEventDispatcher implements EventDispatcher {
private EventDispatcher originalDispatcher;
public MyEventDispatcher(EventDispatcher originalDispatcher) {
this.originalDispatcher = originalDispatcher;
}
@Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
if (event instanceof MouseEvent) {
MouseEvent mouseEvent = (MouseEvent) event;
if (MouseButton.SECONDARY == mouseEvent.getButton() && mouseEvent.isMetaDown() ) {
// if SECONDARY mouse button has MetaDown
// the underlying Web Page will not get the CONTEXTMENU event
// so removing the isMetaDown() by firing a corrected event
MouseEvent new_event = new MouseEvent(
mouseEvent.getEventType(),
mouseEvent.getX(),
mouseEvent.getY(),
mouseEvent.getScreenX(),
mouseEvent.getScreenY()+100,
mouseEvent.getButton(),
mouseEvent.getClickCount(),
mouseEvent.isShiftDown(),
mouseEvent.isControlDown(),
mouseEvent.isAltDown(),
false,
mouseEvent.isPrimaryButtonDown(),
mouseEvent.isMiddleButtonDown(),
mouseEvent.isSecondaryButtonDown(),
mouseEvent.isSynthesized(),
mouseEvent.isPopupTrigger(),
mouseEvent.isStillSincePress(),
mouseEvent.getPickResult()
);
event.consume();
Event.fireEvent(mouseEvent.getTarget(), new_event);
}
}
return originalDispatcher.dispatchEvent(event, tail);
}
}
}
/*
------------contextmenu.html for standalone use------------------------
<html>
<head>
<script>
function registerContextMenu(){
var myDiv=document.getElementById("myYellowDiv");
console.log(myDiv);
myDiv.addEventListener("contextmenu",function (mouseevent){
myDiv.innerHTML="RightClick Ctrl-Key:"+mouseevent.ctrlKey+" Meta-Key:"+mouseevent.metaKey;
return true;
});
}
</script>
</head>
<body onLoad="registerContextMenu();">
<div onContextMenu="return false;" id="myYellowDiv" style="width:400px; height:200px; background-color:yellow">right-click here...</div>
</body>
</html>
*/
---------- END SOURCE ----------
ADDITIONAL OS VERSION INFORMATION :
Windows 10
EXTRA RELEVANT SYSTEM CONFIGURATION :
- tested on MacOS to verify that it's Windows specific
A DESCRIPTION OF THE PROBLEM :
The attached sample shows that the webview invokes the onContextMenu mouse event in JavaScript and falsely reports metaKey=true.
Running the same HTML in a pure JavaFX web view (not embedded in Swing) does not show this anomaly and correctly reports no modifier. So it looks like somewhere in the Swing/JavaFX embedding something goes in the weeds.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
- run the attached sample in Windows
- right-click on the yellow box to see what modifier keys are reported to JavaScript
- compare with Mac OS or other OS
- use the "contextmenu.html" down in the source code to test with pure JavaFX WebView, like from the Oracle sample, or with plain web browsers
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
expecting "metaKey=false" if no modifier key is pressed while performing mouse right-click
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.awt.Dimension;
import java.awt.Point;
import java.lang.reflect.Method;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.event.Event;
import javafx.event.EventDispatchChain;
import javafx.event.EventDispatcher;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class JavaFX {
private final static int WIDTH = 500;
private final static int HEIGHT = 500;
/* Create a JFrame with a JButton and a JFXPanel containing the WebView. */
private static void initAndShowGUI() {
// This method is invoked on Swing thread
JFrame frame = new JFrame(javafx());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//frame.getContentPane().setLayout(null); // do the layout manually
final JFXPanel fxPanel = new JFXPanel();
frame.add(fxPanel);
frame.setVisible(true);
fxPanel.setLocation(new Point(100,100));
//fxPanel.setSize(new Dimension(WIDTH,HEIGHT));
frame.getContentPane().setPreferredSize(new Dimension(WIDTH,HEIGHT));
frame.pack();
//frame.setResizable(false);
Platform.runLater(new Runnable() { // this will run initFX as JavaFX-Thread
@Override
public void run() {
initFX(fxPanel);
}
});
}
/* Creates a WebView and loads a test page. */
private static void initFX(final JFXPanel fxPanel) {
Group group = new Group();
Scene scene = new Scene(group);
fxPanel.setScene(scene);
WebView webView = new WebView();
webView.setMinSize(WIDTH,HEIGHT);
webView.setMaxSize(WIDTH,HEIGHT);
group.getChildren().add(webView);
WebEngine webEngine = webView.getEngine();
//uncomment these two lines for a hacky workaround
//webView.setEventDispatcher(new MyEventDispatcher(webView.getEventDispatcher()));
String content =
"<html><head><script>"
+"function registerContextMenu(){"
+" var myDiv=document.getElementById(\"myYellowDiv\");"
+" myDiv.addEventListener(\"contextmenu\",function (mouseevent){"
+" myDiv.innerHTML=\"RightClick Ctrl-Key:\"+mouseevent.ctrlKey+\" Meta-Key:\"+mouseevent.metaKey;"
+" return true;"
+" });}"
+"</script></head>"
+"<body onLoad=\"registerContextMenu();\">"
+"<div onContextMenu=\"return false;\" id=\"myYellowDiv\" style=\"width:400px; height:200px; background-color:yellow\">right-click here...</div>"
+"</body></html>";
webEngine.loadContent(content);
}
public static String javafx()
{
String javafx;
try
{
Class<?> c = Class.forName("com.sun.javafx.runtime.VersionInfo");
Method m = c.getMethod("getRuntimeVersion", new Class[]{});
javafx = (String)m.invoke(c);
}
catch (Exception e)
{
javafx = "";
}
return javafx;
}
public static void main(final String[] args) {
System.out.println("javax.runtime.version="+javafx());
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
initAndShowGUI();
}
});
}
/**
*
* alternate event dispatcher to work around the problem
*
*/
public class MyEventDispatcher implements EventDispatcher {
private EventDispatcher originalDispatcher;
public MyEventDispatcher(EventDispatcher originalDispatcher) {
this.originalDispatcher = originalDispatcher;
}
@Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
if (event instanceof MouseEvent) {
MouseEvent mouseEvent = (MouseEvent) event;
if (MouseButton.SECONDARY == mouseEvent.getButton() && mouseEvent.isMetaDown() ) {
// if SECONDARY mouse button has MetaDown
// the underlying Web Page will not get the CONTEXTMENU event
// so removing the isMetaDown() by firing a corrected event
MouseEvent new_event = new MouseEvent(
mouseEvent.getEventType(),
mouseEvent.getX(),
mouseEvent.getY(),
mouseEvent.getScreenX(),
mouseEvent.getScreenY()+100,
mouseEvent.getButton(),
mouseEvent.getClickCount(),
mouseEvent.isShiftDown(),
mouseEvent.isControlDown(),
mouseEvent.isAltDown(),
false,
mouseEvent.isPrimaryButtonDown(),
mouseEvent.isMiddleButtonDown(),
mouseEvent.isSecondaryButtonDown(),
mouseEvent.isSynthesized(),
mouseEvent.isPopupTrigger(),
mouseEvent.isStillSincePress(),
mouseEvent.getPickResult()
);
event.consume();
Event.fireEvent(mouseEvent.getTarget(), new_event);
}
}
return originalDispatcher.dispatchEvent(event, tail);
}
}
}
/*
------------contextmenu.html for standalone use------------------------
<html>
<head>
<script>
function registerContextMenu(){
var myDiv=document.getElementById("myYellowDiv");
console.log(myDiv);
myDiv.addEventListener("contextmenu",function (mouseevent){
myDiv.innerHTML="RightClick Ctrl-Key:"+mouseevent.ctrlKey+" Meta-Key:"+mouseevent.metaKey;
return true;
});
}
</script>
</head>
<body onLoad="registerContextMenu();">
<div onContextMenu="return false;" id="myYellowDiv" style="width:400px; height:200px; background-color:yellow">right-click here...</div>
</body>
</html>
*/
---------- END SOURCE ----------