20

I'm using both JavaFX and the javascript engine inside JavaFX WebEngine to develop an application. I'd like to get feedback from javascript for debugging purposes. What happens to the console output inside the WebEngine? Is there any way I can access it, or redirect to System.out in java?

Artjom B.
  • 61,146
  • 24
  • 125
  • 222

3 Answers3

27

The following code redirects console.log() to JavaBridge.log():

import netscape.javascript.JSObject;

[...]

public class JavaBridge
{
    public void log(String text)
    {
        System.out.println(text);
    }
}

// Maintain a strong reference to prevent garbage collection:
// https://bugs.openjdk.java.net/browse/JDK-8154127
private final JavaBridge bridge = new JavaBridge();

[...]

webEngine.getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) ->
{
    JSObject window = (JSObject) webEngine.executeScript("window");
    window.setMember("java", bridge);
    webEngine.executeScript("console.log = function(message)\n" +
        "{\n" +
        "    java.log(message);\n" +
        "};");
});
Gili
  • 86,244
  • 97
  • 390
  • 689
  • what is "window" here? – NaveenBharadwaj May 19 '15 at 06:45
  • @Florian can you please help here? I'm getting a null pointer exception if i declare window as JSObject – NaveenBharadwaj May 19 '15 at 08:58
  • @naveenbharadwaj you need to post a separate question. – Gili May 19 '15 at 12:27
  • 4
    Doesn't seem to be working. My calls to `console.log()` isn't being picked up for some reason. – tyteen4a03 Mar 20 '16 at 09:23
  • 2
    Not sure if we need to do this every time state changes - perhaps just when `newValue == State.SCHEDULED` or something? otherwise we are overriding `console.log` multiple times for no reason, in the same request. – SleepyFox89 Oct 02 '17 at 12:45
  • 2
    If this silently fails for you like it did for me, one thing to try is initializing `bridge` in a persistent scope (e.g., as an member variable of an object, instead of a local variable). I think my bridge was garbage collected before it could be called from JavaScript (also see https://stackoverflow.com/a/41903154 for details about `setMember` and garbage collection) – Andrew Head Jan 11 '19 at 00:31
  • 1
    @AndrewHead I fixed the answer. Thank you for pointing his out! – Gili Jan 11 '19 at 13:37
  • @SleepyFox89 State.SCHEDULED doesn't work for me, it fails when I load a second page in the same WebView. However, State.SUCCEEDED works. – Guillaume F. Dec 28 '19 at 19:49
  • I was stumped since `import netscape.javascript.JSObject;` is deprecated in Java 9 and `import jdk.nashorn.api.scripting.JSObject;` is deprecated in Java 15, with no replacements mentioned anywhere - I am using Java 20. Finally discovered that if you add `requires jdk.jsobject;` to your `src/main/java/module-info.java` then you can access `import netscape.javascript.JSObject;`. Not sure if this is kosher but it works. – abulka Jul 07 '23 at 06:02
13

You can just add message listener to see what's happening in your output. You don't have to inject js bridge with redefinition of functions like console.log for every single loaded page

WebConsoleListener.setDefaultListener((webView, message, lineNumber, sourceId) -> {
    System.out.println(message + "[at " + lineNumber + "]");
});
dzikoysk
  • 1,560
  • 1
  • 15
  • 27
  • Yes, but you don't get the ability to expand and view the arguments passed to console methods or jump to source like you get from the debugger console. – vladsch Mar 04 '18 at 00:45
  • 1
    He just asked about getting output from console.log, so it's better alternative to Green Man's answer. Dedicated debugging tools belong to another case and it is not always helping - for example if we want to catch output in our final distributed app. – dzikoysk Mar 04 '18 at 00:55
  • 3
    Sadly WebConsoleListener is not available in recent versions of Java. – Guillaume F. Dec 27 '19 at 19:38
  • @GuillaumeF. package com.sun.javafx.webkit; but it is not event from java, I'm using the lastest javaFX version, it is here – FARS May 05 '23 at 22:46
  • Avoid! It uses internal API which might be removed in any release. – Lii Jun 01 '23 at 07:23
6

I like to go the other direction. We use log4j so I created a javascript wrapper like the following:

module.exports = {

    levels:[ "ALL", "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"],

    level:"INFO",

    error:function(msg){
      if(this.isErrorEnabled()){
        console.error(msg)
      }
    },
    warn:function(msg){
      if(this.isWarnEnabled()){
        console.warn(msg)
      }
    },
    info:function(msg){
      if(this.isInfoEnabled()){
        console.log("INFO: "+msg)
      }
    },
    debug:function(msg){
      if(this.isDebugEnabled()){
        console.log("DEBUG: "+msg)
      }
    },
    trace:function(msg){
      if(this.isTraceEnabled()){
        console.log("TRACE: "+msg)
      }
    },

    isErrorEnabled:function(){
      return this.isEnabled("ERROR");
    },
    isWarnEnabled:function(){
      return this.isEnabled("WARN");
    },
    isInfoEnabled:function(){
      return this.isEnabled("INFO");
    },
    isDebugEnabled:function(){
      return this.isEnabled("DEBUG");
    },
    isTraceEnabled:function(){
      return this.isEnabled("TRACE");
    },
    isEnabled:function(statementLevel){
      return this.levels.indexOf(this.level)<=this.levels.indexOf(statementLevel);
    }
  }

Then at the beginning of the javascript I check to see if the log is present and set it:

if(window.log == undefined){
  window.log = require("./utils/log4j-wrapper")
  window.log.level = "INFO"
}

And that way if you set the Log4j logger directly on the engine before you even load the url like:

WebEngine webEngine = webView.getEngine()
JSObject win = (JSObject) webEngine.executeScript("window")
win.setMember("log", log)  //log being the java log4j logger

This way I can get logging in if I am opening directly in a browser or it is being run from a WebView in a JavaFX program. And has the added benefit of having levels to the logging in javascript that match your packages of the WebView controller. Just an alternative for larger javascript views.

Tim Overly
  • 425
  • 4
  • 10