Browser integration in Java GUIs

Introduction

One of the challenges I had to face a couple of years ago was to migrate a Java desktop application from 32 bits to 64 bits architecture, and what may have been as simple as using the right JDK, switching to the proper external libraries versions and getting rid of the deprecated code actually had an extra issue. The application embeded a 32 bits PDF reader on a panel, and that limitation had been stopping this migration for a while.

One of the challenges I had to face a couple of years ago was to migrate a Java desktop application from 32 bits to 64 bits architecture, and what may have been as simple as using the right JDK, switching to the proper external libraries versions and getting rid of the deprecated code actually had an extra issue. The application embeded a 32 bits PDF reader on a panel, and that limitation had been stopping this migration for a while.

Going into the code itself, it was using the XULRunner, one of the Firefox engine components, so what it actually did was “using a 32 bits Firefox browser embedded”. It also used Adobe Acrobat generated interactive forms, which actually required Adobe Acrobat Reader itself to be rendered properly and this took Apache PDFBox out of the picture to do this task.

When I looked into the philosophy of running an embed browser I tried to take what happened in my laptop: when I was surfing the net and opening a PDF file my Firefox 32 bits browser, I didn’t use some browser code: I delegated this in the Adobe Acrobat Reader X Plugin for the browser. So, why should not we take this idea one step further, and use the same plugin through an integration? Adobe itself provides this system, so there would be no incompatibility issue, I just had to setup this through the operating system configuration to get a workaround and get the proper version for the machine architecture in which we are currently running the program.

So let’s meet DJNativeSwing, a library based in SWT (Standard Widget Toolkit) which allows us to have our very own embedded browser in our code. SWT is highly recommended library due to its portability, as it accesses to the Native Operating System GUIs, and that is exactly what we require for this kind of issue. It is also the next wrapping level to Java Swing, and actually lighter and faster, and I had already used it back in the day to avoid problems when I had to deal with Macromedia Flash plugin integrations.

As an example of how to configure this, we are going to follow 2 basic steps:

  • Set up a browser tab on a Java swing component.
  • Be able to open a PDF on that said browser tab.

Process

1. Previous set up: the Maven dependecies required

Let’s start with the basics: first of all we will get the maven dependencies.

❕This was the stable version when the post was originally written.

1
2
3
4
5
<dependecy>
<group>chrriis.dj.nativeswing</group>
<artifactId>DJNativeSwing</artifactId>
<version>1.0.2</version>
</dependecy>

2. Building a basic Swing interface

Right after that, we will define the basic window frame, with a button the select the file (via FileChooser) and a panel to show the results.

❕Setting up all the text strings as constants makes them easier to spot them in order to replace them if needed. It’s not really necessary, but doing it improves reusability.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.util.Map;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileNameExtensionFilter;

import nativeSwing.BrowserPanel;
import pdfHandler.PdfReader;
import xmlHandler.XMLHandler;
import chrriis.common.UIUtils;
import chrriis.dj.nativeswing.swtimpl.NativeInterface;

public class DemoPDFRenderLauncher {

private static final String TITLE = "PDF Renderer demo";
private static final String NO_OUTPUT_MESSAGE = "No output available";
private static final String NO_DATA_MESSAGE = "There is no data available from form";
private static final int LENGTH = 800;
private static final int WIDTH = 600;
private static final String FILTER_FILES "PDF files";
private static final String FILE_EXTENSION "pdf";

/**
* The main app window
*/
private JFrame window;
/**
* The path of the file we will open
*/
private String path;
/**
* Button for open file function
*/
private JButton buttonOpen;
/**
* A browser panel
*/
private BrowserPanel browserPanel;

/**
* Constructor method, creates the GUI
*/
public Launcher() {
window = new JFrame(TITLE);
window.getContentPane().setLayout(new BorderLayout());
window.setSize(LENGTH, WIDTH);
window.add(createButtonsPanel(), BorderLayout.NORTH);
window.add(createContentPanel(), BorderLayout.CENTER);
window.setVisible(true);
window.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
NativeInterface.close();
System.exit(0);
}
});
}

/**
* Creates a button panel with the action button: open a file
* @return the buttons panel
*/
private Component createButtonsPanel() {
JPanel panel = new JPanel();
panel.setLayout(new FlowLayout());
buttonOpen = new JButton("Open file");
buttonOpen.addActionListener(new ButtonOpenController());
panel.add(buttonOpen);
return panel;
}

/**
* Creates a panel to render the content
*
* @return the buttons panel
*/
private Component createContentPanel() {
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
JScrollPane scrollPaneText = new JScrollPane(textPanel);
panel.add(scrollPaneText);
//here we will insert the DJNativeSwing panle
browserPanel = new BrowserPanel();
JScrollPane scrollPaneBrowser = new JScrollPane(browserPanel);
panel.add(scrollPaneBrowser);
return panel;
}

/**
* Load button controller, which launches the FileChooser.
*/
private class ButtonOpenController implements ActionListener {
@Override
public void actionPerformed(ActionEvent arg0) {
launchOpenSelectFile();
}
}

/**
* Launches the FileChooser window, and invokes the pdf opener window.
*/
private void launchOpenSelectFile() {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setAcceptAllFileFilterUsed(false);
FileNameExtensionFilter filter = new FileNameExtensionFilter(
FILTER_FILES, FILE_EXTENSION);
fileChooser.addChoosableFileFilter(filter);
if (fileChooser.showOpenDialog(window) == JFileChooser.APPROVE_OPTION) {
path = fileChooser.getSelectedFile().getAbsolutePath();
browserPanel.navigate(path);
}
}

@SuppressWarnings("unused")
public static void main(String[] args) {
UIUtils.setPreferredLookAndFeel();
NativeInterface.open();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Launcher demo = new Launcher();
}
});
}
}

3. Embeding a web browser

Then let’s follow up with browser tab: if we just wanted to create a browser panel it would be as easy as writing the following lines:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import java.awt.BorderLayout;

import javax.swing.BorderFactory;
import javax.swing.JPanel;

import chrriis.dj.nativeswing.swtimpl.components.JWebBrowser;

public class BasicBrowserPanel extends JPanel {

private static final String TITLE = "";

/**
* The browser will be handled in this specific component
*/
private JWebBrowser webBrowser;

/**
* Constructor
*/
public BrowserPanel() {
super(new BorderLayout());
JPanel webBrowserPanel = new JPanel(new BorderLayout());
webBrowserPanel.setBorder(BorderFactory.createTitledBorder(TITLE));
webBrowser = new JWebBrowser();
webBrowser.setBarsVisible(false);
webBrowser.setStatusBarVisible(false);
webBrowserPanel.add(webBrowser, BorderLayout.CENTER);
add(webBrowserPanel, BorderLayout.CENTER);
}

/**
* Initializes the browser and sets a value in the URL storage
* @param path the URL value o file path to open
*/
public void navigate(String path) {
webBrowser.setVisible(true);
webBrowser.navigate(path);
}

/**
* Makes the browser retrieve and render the content from the path previously stored
*/
public String getAddress(){
return webBrowser.getHTMLContent();
}

/**
* Hides the browser controls (forward, back, home buttons...)
*/
public void hideContent() {
webBrowser.setVisible(false);
}
}

This way we are getting a fully functional web browser, very similar to the browser in the Eclipse IDE, but with too many unnecessary functions for what we are trying to do here. Since we are only doing the rendering process by delegating it on Adobe, we can remove the GUI extra elements from this whole custom browsing system and leave the bare panel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import chrriis.dj.nativeswing.swtimpl.NativeInterface;
import chrriis.dj.nativeswing.swtimpl.components.JWebBrowser;

/**
* Allows to launch a JFrame containing an embedded browser.
*/
public class BrowserFrameLauncher {

/**
* Renders the file content on a browser via DJ Native Swing
*
* @param path
* the file url if we pass as a parameter any webpage URL,
* the system would try to render it
* @return a JPanel with a native browser, to render the file content
*/
private Component createBrowserPanel(String path) {
JWebBrowser.useXULRunnerRuntime();
JPanel fileBrowserPanel = new JPanel(new BorderLayout());
final JWebBrowser fileBrowser = new JWebBrowser();
fileBrowser.setBarsVisible(false);
fileBrowser.setStatusBarVisible(false);
fileBrowser.navigate(path);
fileBrowserPanel.add(fileBrowser, BorderLayout.CENTER);
return fileBrowserPanel;
}

}

4. Setting the last piece of the puzzle: getting the PDF itself

Finally let’s make the final touches to open a PDF. What we are actually doing is getting the PDF file path into the browser, so in the end we have a new layer over our old friend XULRunner, but this provides us a way to integrate the plugins via the “right architecture version” SWT library. So as a conclussion we are able to connect to the “right architecture version” plugin, fixing our rendering problem and making us independent from the 32 bits plaform once and for all.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// excerpt from BrowserFrameLauncherPDF.java

private static final String CLOSING_MESSAGE = "Do you really want to close the file?";
private static final String RENDERED_TITLE = "PDF Renderer demo - Embed Browser";

/**
* Opens a file and shows it content in a JFrame.
*
* @param path
* the url of the file to open in a JFrame
*/
public static void openPDF(final String path) {
NativeInterface.open();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
final JFrame frame = new JFrame(RENDERED_TITLE);
frame.setLocation(0, 0);
//we may set up a default size for this test
//frame.setSize(800, 600);
frame.setVisible(true);
frame.add(createBrowserPanel(path));

//a window listener would allow us to control the closing actions
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
int i = JOptionPane.showConfirmDialog(frame,CLOSING_MESSAGE);
if (i == 0) {
NativeInterface.close();
}
}
});
}
});
}

❗️ Please notice the NativeInterface.open() line to make sure about getting the components correctly booted, and the threading of this component in order to avoid other processes interfering with the rendering.