How do you deploy a plugin that depends on javafx? IDEA bundled with JDK does not include native libraries.

I changed the HTML rendering engine in my plugin from JEditorPane to JFXPanel with WebView. It works great BUT IDEA with bundled JDK on the Mac does not include jfxrt.jar and even if I add it to the lib directory in my plugin, this jar depends on libjfxwebkit native library.

IDEA hangs on startup if the plug-in is enabled and the Boot JDK is set to bundled. It does complain that Tool Kit was not found, but it is not clear what the issue is. Took a few "force quits" to figure out what was causing it.

Switching to another jdk 1.8 from Oracle makes everything work.

If I release the plugin as is most users will most likely install and get a hung IDEA wondering what happened. It is a real pain to recover from it. If you have any open projects that load on startup you have to cancel all of them and then disable the plugin and restart. A real pain and not obvious.

Anyone have any ideas how to go about releasing a plugin that depends on jfxrt.jar, that is safe to the users?

7 comments
Comment actions Permalink

The only thing I can think of is on startup the plugin will have to check if a class from web took kit native library can be found and if not then present a sticky notification to the user explaining that the installation directory for jdk 1.8 from Oracle is needed for the plugin to work, and provide a link to Oracle java installation page if they don't already have it installed..

If the directory for jdk is not configured the plugin will keep the sticky notification until it is disabled, uninstalled or properly configured.

Freezing the IDE is not an option. I thought I lost all my configuration, until I figured out how to recover from this.

Any other ideas will be greatly appreciated. I spend many hours getting the WebView implementation polished and it is sweet. I don't want to leave it on the shelf. I think many users will appreciate the upgrade.

Sneak peek at how the HTML is rendered in the new version, compared to GitHub:

Screen Shot 2015-09-06 at 2.24.43 PM.pngScreen Shot 2015-09-06 at 2.25.44 PM copy.png

0
Comment actions Permalink

Hi Vladimir,

I'm not aware of any way to do this reliably. I'd be very interested to know this too.

There are some other options. You could look at https://bitbucket.org/chromiumembedded/java-cef which is an open source project embedding Chromium. I don't know how mature it is, though.

The other option would be to use https://www.teamdev.com/jxbrowser which is a commercial component, but I believe they have a free licence option for open source projects. This is probably the most mature option, I've read some very good things about it but there's not a lot of information out there.

I'm interested in doing something similar, so I'd be very interested in your experiences.

Cheers,
Colin

0
Comment actions Permalink

Hi Colin,

I investigates several options before setting on WebView. LoboEvolution was a contender but its rendering looked too different from standard browsers. Tried SwingBox/CSSBox but its finicky HTML parsing would not render a simple page without modifications. I even looked at jxBrowser but the native library requirement puts it in the same problem box as javafx but with a deployment overhead of 200MB. I just don't see a simple plugin needing a 200MB deplyment package when most of it is not usable on a given platform.

WebView is not a speed demon but the rendering is close enough to chrome. Its quirks and limitations are nothing compared to JEditorPane with HTMLEditorKit and it has good support for CSS and JavaScript. It does have a few quirks that require tweaking the CSS and JavaScript code for it but I was able to do the complete port, learning curve and all, in about 24hrs of effort what weeks of strugling with HTMLEditorKit did not achieve.

I think the solution would be to add the JAVA_HOME/lib/ext to the class path of either my plugin or IntelliJ IDEA. I will experiment in trying to resolve this without having to deploy jfxrt.jar. If the jdk is on the user's machine, the jar is already there except it is not in the default class path.

0
Comment actions Permalink

Actually, some experimentation shows that it is better not to include jfxrt.jar in the deployment because if the native librariy it needs is not there the IDEA hangs. If the jar is missing IDEA just does the usual exception logging which is already an improvement over it hanging and requiring a forced quit.

0
Comment actions Permalink

My solution so far has been to Use Reflection to try and load the new JavaFX WebView based editior and if it fails to load the JEditorPane version so at least the plug in will work in any environment. Which overall is a better solution than two version of the plugin for different systems. I check if jfxrt.jar is available in the lib/ext folder under Java home and add its URL to the class loader.

I added this to my EditorProvider class:

    private static int canLoadFxEditor = 0;     private static Class<?> MultiMarkdownFxPreviewEditor;     private static Constructor<?> classConstructor;     public static FileEditor createEditor(@NotNull Project project, @NotNull VirtualFile file, boolean forRawHtml) {         if (canLoadFxEditor == 0) {             try {                 //return new MultiMarkdownFxPreviewEditor(project, FileDocumentManager.classConstructor().getDocument(file), false);                 String javaHome = System.getProperties().getProperty("java.home");                 String javaVersion = System.getProperties().getProperty("java.version");                 MultiMarkdownPlugin plugin = MultiMarkdownPlugin.getInstance(project);                 ClassLoader classLoader = plugin.getClass().getClassLoader();                 PluginClassLoader pluginClassLoader = (PluginClassLoader) classLoader;                 File jfxrtFile = new File(javaHome + "/lib/ext/jfxrt.jar");                 if (jfxrtFile.exists()) {                     URL[] urls = {new URL("jar:" + jfxrtFile.toURI().toURL() + "!/")};                     classLoader = new URLClassLoader(urls, pluginClassLoader);                 }                 MultiMarkdownFxPreviewEditor = Class.forName("com.vladsch.idea.multimarkdown.editor.MultiMarkdownFxPreviewEditor", true, classLoader);                 classConstructor = MultiMarkdownFxPreviewEditor.getConstructor(Project.class, Document.class, boolean.class);                 canLoadFxEditor = 2;             } catch (ClassNotFoundException e) {                 e.printStackTrace();             } catch (NoSuchMethodException e) {                 e.printStackTrace();             } catch (MalformedURLException e) {                 e.printStackTrace();             }         }         if (canLoadFxEditor == 2) {             try {                 Object fileEditor = classConstructor.newInstance(project, FileDocumentManager.getInstance().getDocument(file), forRawHtml);                 return (FileEditor) fileEditor;             } catch (InvocationTargetException e) {                 e.printStackTrace();             } catch (IllegalAccessException e) {                 e.printStackTrace();             } catch (InstantiationException e) {                 e.printStackTrace();             }         }         // TODO: show notification of the problem and solutions         canLoadFxEditor = 1;         return new MultiMarkdownPreviewEditor(project, FileDocumentManager.getInstance().getDocument(file), forRawHtml);     }

0
Comment actions Permalink

For posterity. If the IDEA is started with a jre that has the jfxrt.jar then plugin will use it. Now there is a bug in PhpStorm 9.5 EAP that only goes through the motions when you change the boot JDK via the change boot jdk action but always boots with the bundled JDK. However, providing a phpstorm.jdk file in the preferences directory (Mac: ~/Library/Preferences/WebIde95) will make it boot with the right JDK.

Simplified to having the plugin class provide the class loader via the method:

    public PluginClassLoader getClassLoader() {         if (myClassLoader == null) {             myClassLoader = (PluginClassLoader) getClass().getClassLoader();             String fileSeparator = System.getProperties().getProperty("file.separator");             String javaHome = System.getProperties().getProperty("java.home");             String javaVersion = System.getProperties().getProperty("java.version");             String fileName = javaHome + fileSeparator + "lib" + fileSeparator + "ext";             File file = new File(fileName);             if (!file.exists()) {                 logger.warn("JavaFX library jfxrt.jar not found in the current jre: " + javaHome + ", version " + javaVersion);                 logger.warn("MultiMarkdown HTML Preview will use a more limited implementation.");             } else {                 logger.info("JavaFX library jfxrt.jar found in the current jre: " + javaHome + ", version " + javaVersion);                 ArrayList<String> libs = new ArrayList<String>(1);                 libs.add(fileName);                 myClassLoader.addLibDirectories(libs);             }         }         return myClassLoader;     }


then the EditorProvider uses it to load the JavaFX based editor:

    @NotNull     public static FileEditor createEditor(@NotNull Project project, @NotNull VirtualFile file, boolean forRawHtml) {         if (canLoadFxEditor == 0) {             try {                 MultiMarkdownPlugin plugin = MultiMarkdownPlugin.getInstance(project);                 PluginClassLoader pluginClassLoader = (PluginClassLoader) plugin.getClassLoader();                 MultiMarkdownFxPreviewEditor = Class.forName("com.vladsch.idea.multimarkdown.editor.MultiMarkdownFxPreviewEditor", true, pluginClassLoader);                 classConstructor = MultiMarkdownFxPreviewEditor.getConstructor(Project.class, Document.class, boolean.class);                 canLoadFxEditor = 2;             } catch (ClassNotFoundException e) {                 logger.error("ClaClassNotFoundException", e);             } catch (NoSuchMethodException e) {                 logger.error("NoSuchMethodException", e);             }         }         if (canLoadFxEditor == 2) {             try {                 Object fileEditor = classConstructor.newInstance(project, FileDocumentManager.getInstance().getDocument(file), forRawHtml);                 return (FileEditor) fileEditor;             } catch (InvocationTargetException e) {                 logger.error("InvocationTargetException", e.getTargetException());             } catch (IllegalAccessException e) {                 logger.error("IllegalAccessException", e);             } catch (InstantiationException e) {                 logger.error("InstantiationException", e);             }         }         // TODO: show notification of the problem and solutions         canLoadFxEditor = 1;         return new MultiMarkdownPreviewEditor(project, FileDocumentManager.getInstance().getDocument(file), forRawHtml);     }

0
Comment actions Permalink

Vladimir Schneider, Colin Fleming

Do you guys have any updates?

It's 2019 alreay and I am also working with webbrowser integration. 

There is an attempt to wrap JCEF into a library https://github.com/CodeBrig/Journey, but it has some issues with Linux stability: https://github.com/CodeBrig/Journey/issues/3

 

0

Please sign in to leave a comment.