Skip to content

Instantly share code, notes, and snippets.

@caprica
Created October 8, 2013 19:57
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save caprica/6890618 to your computer and use it in GitHub Desktop.
Save caprica/6890618 to your computer and use it in GitHub Desktop.
Embed an SWT Webkit Browser component inside a Swing JPanel, with non-crashing clean-up.
/*
* This class is made available under the Apache License, Version 2.0.
*
* See http://www.apache.org/licenses/LICENSE-2.0.txt
*
* Author: Mark Lee
*
* (C)2013 Caprica Software (http://www.capricasoftware.co.uk)
*/
package uk.co.caprica.swt.browser;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.JFrame;
import javax.swing.JPanel;
import org.eclipse.swt.SWT;
import org.eclipse.swt.awt.SWT_AWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
/**
* Implementation of an AWT {@link Canvas} that embeds an SWT {@link Browser} component.
* <p>
* With contemporary versions of SWT, the Webkit browser is the default implementation.
* <p>
* To embed an SWT component inside of a Swing component there are a number of important
* considerations (all of which comprise this implementation):
* <ul>
* <li>A background thread must be created to process the SWT event dispatch loop.</li>
* <li>The browser component can not be created until after the hosting Swing component (e.g. the
* JFrame) has been made visible - usually right after <code>frame.setVisible(true).</code></li>
* <li>To cleanly dispose the native browser component, it is necessary to perform that clean
* shutdown from inside a {@link WindowListener#windowClosing(WindowEvent)} implementation in
* a listener registered on the hosting JFrame.</li>
* <li>On Linux, the <code>sun.awt.xembedserver</code> system property must be set.</li>
* </ul>
*/
public final class SwtBrowserCanvas extends Canvas {
/**
* Required for Linux, harmless for other OS.
* <p>
* <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=161911">SWT Component Not Displayed Bug</a>
*/
static {
System.setProperty("sun.awt.xembedserver", "true");
}
/**
* SWT browser component reference.
*/
private final AtomicReference<Browser> browserReference = new AtomicReference<>();
/**
* SWT event dispatch thread reference.
*/
private final AtomicReference<SwtThread> swtThreadReference = new AtomicReference<>();
/**
* Get the native browser instance.
*
* @return browser, may be <code>null</code>
*/
public Browser getBrowser() {
return browserReference.get();
}
/**
* Navigate to a URL.
*
* @param url URL
*/
public void setUrl(final String url) {
// This action must be executed on the SWT thread
getBrowser().getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
getBrowser().setUrl(url);
}
});
}
/**
* Create the browser canvas component.
* <p>
* This must be called <strong>after</strong> the parent application Frame is made visible -
* usually directly after <code>frame.setVisible(true)</code>.
* <p>
* This method creates the background thread, which in turn creates the SWT components and
* handles the SWT event dispatch loop.
* <p>
* This method will block (for a very short time) until that thread has successfully created
* the native browser component (or an error occurs).
*
* @return <code>true</code> if the browser component was successfully created; <code>false if it was not</code/
*/
public boolean initialise() {
CountDownLatch browserCreatedLatch = new CountDownLatch(1);
SwtThread swtThread = new SwtThread(browserCreatedLatch);
swtThreadReference.set(swtThread);
swtThread.start();
boolean result;
try {
browserCreatedLatch.await();
result = browserReference.get() != null;
}
catch (InterruptedException e) {
e.printStackTrace();
result = false;
}
return result;
}
/**
* Dispose the browser canvas component.
* <p>
* This should be called from a {@link WindowListener#windowClosing(WindowEvent)} implementation.
*/
public void dispose() {
browserReference.set(null);
SwtThread swtThread = swtThreadReference.getAndSet(null);
if (swtThread != null) {
swtThread.interrupt();
}
}
/**
* Implementation of a thread that creates the browser component and then implements an event
* dispatch loop for SWT.
*/
private class SwtThread extends Thread {
/**
* Initialisation latch.
*/
private final CountDownLatch browserCreatedLatch;
/**
* Create a thread.
*
* @param browserCreatedLatch initialisation latch.
*/
private SwtThread(CountDownLatch browserCreatedLatch) {
this.browserCreatedLatch = browserCreatedLatch;
}
@Override
public void run() {
// First prepare the SWT components...
Display display;
Shell shell;
try {
display = new Display();
shell = SWT_AWT.new_Shell(display, SwtBrowserCanvas.this);
shell.setLayout(new FillLayout());
browserReference.set(new Browser(shell, SWT.NONE));
}
catch (Exception e) {
e.printStackTrace();
return;
}
finally {
// Guarantee the count-down so as not to block the caller, even in case of error -
// there is a theoretical (rare) chance of failure to initialise the SWT components
browserCreatedLatch.countDown();
}
// Execute the SWT event dispatch loop...
try {
shell.open();
while (!isInterrupted() && !shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
browserReference.set(null);
shell.dispose();
display.dispose();
}
catch (Exception e) {
e.printStackTrace();
interrupt();
}
}
}
/**
* Example implementation.
*
* @param args command-line arguments (unused)
*/
public static void main(String[] args) {
final SwtBrowserCanvas browserCanvas = new SwtBrowserCanvas();
JPanel contentPane = new JPanel();
contentPane.setLayout(new BorderLayout());
contentPane.add(browserCanvas, BorderLayout.CENTER);
JFrame frame = new JFrame("SWT Browser Embedded in JPanel");
frame.setBounds(100, 100, 1200, 800);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(contentPane);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
// Dispose of the native component cleanly
browserCanvas.dispose();
}
});
frame.setVisible(true);
// Initialise the native browser component, and if successful...
if (browserCanvas.initialise()) {
// ...navigate to the desired URL
browserCanvas.setUrl("http://www.capricasoftware.co.uk");
}
else {
System.out.println("Failed to initialise browser");
}
}
}
@Skinner927
Copy link

Having trouble on Mac, even when starting with -XstartOnFirstThread

***WARNING: Display must be created on main thread due to Cocoa restrictions.
org.eclipse.swt.SWTException: Invalid thread access
Failed to initialise browser
    at org.eclipse.swt.SWT.error(Unknown Source)
    at org.eclipse.swt.SWT.error(Unknown Source)
    at org.eclipse.swt.SWT.error(Unknown Source)
    at org.eclipse.swt.widgets.Display.error(Unknown Source)
    at org.eclipse.swt.widgets.Display.createDisplay(Unknown Source)
    at org.eclipse.swt.widgets.Display.create(Unknown Source)
    at org.eclipse.swt.graphics.Device.<init>(Unknown Source)
    at org.eclipse.swt.widgets.Display.<init>(Unknown Source)
    at org.eclipse.swt.widgets.Display.<init>(Unknown Source)
    at com.caci.cat4a.presentation.SwtBrowserCanvas$SwtThread.run(SwtBrowserCanvas.java:163)

@ciaranj
Copy link

ciaranj commented Aug 7, 2014

Thank you, also used this code here: https://gist.github.com/caprica/6890618

@Berry90
Copy link

Berry90 commented Jul 14, 2015

When linking to a Flash Game (e.g.: browserCanvas.setUrl("http://www.netzwelt.de/assets/arcade/brickbreakAE.swf"); , this game just react on the first click. No further actions inside the flash animation are possible.
I use Win 7 with JRE 8.45 32-bit.
Any Solutions?

@ivancabaleiros
Copy link

Hi! I'm trying embeding the SwtBrowserCanvas component in a SwingNode of JavaFX and I can't do it.
My source code is:

final SwingNode swingNode = new SwingNode();
final JPanel jpanel= new JPanel();
SwingUtilities.invokeLater(new Runnable() {
@OverRide
public void run() {
SwtBrowserCanvas browserCanvas = new SwtBrowserCanvas();
browserCanvas.setBounds(0, 0, 400, 400);
jpanel.setLayout(new FlowLayout());
jpanel.add(browserCanvas, BorderLayout.CENTER);
swingNode.setContent(jpanel);
if (browserCanvas.initialise()) {
browserCanvas
.setUrl("file:///home/ivan/Escritorio/index.html");
}
}
});

The error log is:

A fatal error has been detected by the Java Runtime Environment:
SIGSEGV (0xb) at pc=0x00007f4762642414, pid=8124, tid=139942718076672
JRE version: Java(TM) SE Runtime Environment (8.0_74-b02) (build 1.8.0_74-b02)
Java VM: Java HotSpot(TM) 64-Bit Server VM (25.74-b02 mixed mode linux-amd64 compressed oops)
Problematic frame:
C [libpthread.so.0+0xa414] pthread_mutex_lock+0x4
Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again`

Any Solution?

Thank.

Sorry for the comment format, I don't know how to format the source code

@borgogelli
Copy link

For me, error on swtThread.start()
java.lang.IllegalArgumentException: Argument not valid [peer not created]

@caprica
Copy link
Author

caprica commented Sep 10, 2021

For code archaeologists, this gist was written in October 2013 and was correct at the time. It's now at time of writing this comment eight years on from that. Little wonder it doesn't work anymore.
The chances of me going back to SWT now are slim-to-none, although it does I believe remain one viable avenue to getting heavyweight Java+LibVLC working on macOS.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment