📜 ⬆️ ⬇️

JBrowser: MozSwing Reincarnation

image Recall what MozSwing is . MozSwing is the only adequate (in my opinion) free and cross-platform solution for embedding a browser as a component of a swing. But, sadly enough to admit it, the project died at that stage when there were too many mistakes in it. These errors, as well as miscalculations in architecture, do not allow using this solution “as is” for your projects. With an overwhelming desire to fix it, I took up the job and did something for me.

(An article about Mozilla browser integration as a Swing component.)

Why is bad MozSwing

No, no, don’t think that I think MozSwing sucks, this is a huge job for which you need to thank you so much (my work is several times less), but MozSwing doesn’t like me and that's why:
  1. Support for xulrunner `only version 1.9 (which, by the way, does not support Russian letters in transit for windows-systems) when there is version 1.9.2.
  2. A huge number of brain-breaking static classes and methods, which is why MozSwing appears before us an indestructible rock. In order to change anything in it, you have to change the bytecode of the already loaded classes, because inheritance in the case of MozSwing does not work.
  3. The inability to explicitly override nsIWindowCreator (the class implementing this interface is creating windows for the browser), which makes it necessary to invent crutches to create browsers as tabs, since MozSwing prefers to open new pages in new windows. For example, this is how I replace nsIWindowCreator after initialization:
    nsIWindowWatcher winWatcher = XPCOMUtils.getService( "@mozilla.org/embedcomp/window-watcher;1" , nsIWindowWatcher. class ); //$NON-NLS-1$
    winWatcher.setWindowCreator(wndCreator);


    * This source code was highlighted with Source Code Highlighter .

    However, the error is potentially hidden here, because MozSwing loves static classes, methods, and internally refers to its nsIWindowCreator.
  4. Some, let's say, very dubious solutions in the code (MozSwing source code: just in case a 3-window pool is created, so that you can take a window from this pool to embed a browser into it):
    /**
    * When mozilla does a callback to createChromeWindow()
    * we need to create a swing window. But doing this on
    * Swing thread using invokeAndWait sometimes ends
    * with deadlock in AWT.
    * Therefore we keep a list of precreated windows
    * in case we will need them.
    */
    private List <IMozillaWindow> precreatedWins = new LinkedList<IMozillaWindow>();

    public void ensurePrecreatedWindows() {
    ensurePrecreatedWindows(3);
    }
    public void ensurePrecreatedWindows( int winNum) {
    assert !isMozillaThread(); //has to be called from swing

    while (precreatedWins.size()<winNum) {

    if (winFactory== null ) return ;
    IMozillaWindow w = winFactory.create( false );
    if (!(w instanceof Component)) return ;

    // w is instance of something we can work with
    precreatedWins.add(w);
    Component c = (Component)w;
    c.addNotify();
    }
    }


    * This source code was highlighted with Source Code Highlighter .

    The number three is especially scary. It kind of hints us that the three windows should be enough in principle ... Probably ...
  5. To enable support for contextual commands (shortcut menu), some MozSwing'a methods have to be changed. For example:
    @SuppressWarnings( "deprecation" )
    public static void replaceChromeAdapterMethod() {
    try {
    ClassPool classPool = ClassPool.getDefault();
    CtClass ctClass = classPool. get ( "org.mozilla.browser.impl.ChromeAdapter" );
    CtMethod ctMethod = ctClass.getMethod( "queryInterface" , "(Ljava/lang/String;)Lorg/mozilla/interfaces/nsISupports;" );
    ctMethod.setBody( "{ return ru.redstonegroup.geo.gui.components.browser.impl.QueryInterfaceImpl.getInstance().queryInterface(this, $1); }" );
    ctClass.toClass(QueryInterfaceImpl. class .getClassLoader());
    } catch (Throwable e) {
    logger.error(e.getMessage(), e);
    }
    }


    * This source code was highlighted with Source Code Highlighter .
  6. Constantly drops Sun JVM on some linux systems (ubuntu, openSUSE).
  7. Confusion of sources (by the way, rather it is connected with the complexity of the XPCOM technology itself).
  8. No integration with maven.
  9. Difficult to integrate with IoC container.
  10. It is not possible to create a window without any ryushechek, they can only be hidden.

')
Jbrowser

Of course, I have no illusions - my decision is not ideal: since it is based on MozSwing, it took many of his illnesses. If you like, JBrowser is my rethinking of MozSwing ʻand much more suitable for real-world systems. Well, at least, I like my API many times more (although you may not like it at all).

The main problem when meeting MozSwing was that there was no single entry point in it - creating a browser was done as a simple component creation (something like new MozillaWindow, I'm sorry, I don’t remember exactly). Yes, it is in some sense convenient, as long as you do not need anything more than creating just a browser window, but how to configure the browser being created? One option is to inherit from MozSwing-components, climb inside and dig-dig-dig ...

Immediately I was not clear: how to change the proxy server settings for the browser? After some time, it turned out that there is a corresponding MozillaConfig class with a static setProxy method (or something like that). Oh my God, I would never have known if I hadn't opened the source. In general, for me it is all not obvious.

Therefore, JBrowser is a kind of opposite (currently not complete) MozSwing in terms of design. JBrowser has an entry point - the BrowserManager interface. This is the topmost level of xulrunner and swingʻa integration. The class implementing the interface performs all the initialization, as it prepares the ground for further work. In addition to initialization, the implementing class is obliged to provide you on demand with a certain implementation of the BrowserConfig interface, which allows you to adjust the policy of all browsers (enable / disable images, proxies, etc.) Just what I wanted.

In contrast to the upper level of integration, JBrowser has the lowest level of integration, the so-called “component browser”. Any browser component implements the JBrowserComponent interface. This interface is a composite that combines the functionality of a Swing component and implements a browser interface.

/**
* Swing // Browser embedded in swing component
* @author caiiiycuk
*/
public interface JBrowserComponent<T extends Component> extends DisplayableComponent, Browser, NativeBrowser {
/**
* @return See {@link java.awt.Component}
*/
T getComponent();
...
}


* This source code was highlighted with Source Code Highlighter .


There is JBrowserCanvas - the basic implementation of this interface. This is nothing more than a Swing-Canvas component with a browser built into it. Other implementations of the browser component almost always wrap JBrowserCanvas (delegate calls to it). For example, another browser component JBrowserFrame (browser and JFrame) does this.

Between these two opposites, there is another link that unites everything into a single whole - this is a factory-layer (factorial-layer). After creating the top integration layer based on BrowserManager, numerous factors of browser components can be created that implement the ComponentFactory interface. Normally, when an application contains several such factors. Properly configured factor through its methods creates specific implementations of browser components. Suppose I use the following factors in my application: JFrameBrowserFactory (creates a browser as a new window), JTabbedBrowserFactory (creates a browser as a new tab). Thanks to this scheme, it becomes possible to easily solve the problem of customization of browser components you create.

So, here's the whole chain of working with JBrowser: create a BrowserManager (by the way, you can / should use the builder for this), create at least one factor for your browser component and finally, create a browser using factor. This is the simplest way to work with JBrowser:

public class GettingStartedSnippet {

public static void main( String [] args) {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();

JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(( int ) (screenSize.getWidth() * 0.75f),
( int ) (screenSize.getHeight() * 0.75f));
frame.setLocationRelativeTo( null );

BrowserManager browserManager =
new JBrowserBuilder().buildBrowserManager();

JComponentFactory<Canvas> canvasFactory = browserManager.getComponentFactory(JBrowserCanvas. class );
JBrowserComponent<?> browser = canvasFactory.createBrowser();

frame.getContentPane().add(browser.getComponent());
frame.setVisible( true );

browser.setUrl( "http://code.google.com/p/jbrowser/" );
}
}


* This source code was highlighted with Source Code Highlighter .


Why factor? I think (maybe I'm wrong) that it is very convenient. For example, I register my factors in an IoC container and can easily access them from almost any part of the application, so I can at least make a button in the most recent menu that creates a new browser tab for me.
The advantage of this architecture is that you can rewrite any of these three levels, and still get a working system efficiently.

Features that were not in MozSwing
In addition to the complete processing of interfaces, there are some more features added:


Project links:
Project JBrowser (stable)
Project with many examples (dev)
How to get started quickly with JBrowser under Eclipse

What could not be achieved

Unfortunately, while Xulrunner 1.9.2 is not supported, however, there is a reserve, and the light at the end of the tunnel is visible.

Jwebpane

I hope he will come out someday and will save the world. Really looking forward to this project.

Source: https://habr.com/ru/post/91884/


All Articles