JBrowser: MozSwing Reincarnation

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

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

    Why MozSwing

    is bad No, no, don’t think that I think MozSwing sucks, it’s a huge job for which I have to thank you so much (my work is much smaller), but MozSwing doesn’t like me, and here’s why:
    1. Support for xulrunner `but only version 1.9 (which, by the way, does not support Russian letters in the path for windows-systems), when there is version 1.9.2.
    2. A huge number of static classes and methods tearing the brain, which is why MozSwing appears before us as an indestructible rock. In order to change something in it, you have to change the bytecode of already loaded classes, because inheritance in the case of MozSwing does not work.
    3. The inability to explicitly override nsIWindowCreator (the class that implements this interface creates browser windows), which makes it necessary to invent crutches to create browsers as tabs, because MozSwing prefers to open new pages in new windows. Here, for example, 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, a potential error is hidden here, because MozSwing loves static classes, methods, and internally refers to its nsIWindowCreator.
    4. Some, let's say, very dubious decisions in the code (MozSwing sources: just in case, a pool of 3 windows is created, so that later we can take a window from this pool to embed a browser in 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 precreatedWins = new LinkedList();

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

          while (precreatedWins.size()
            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.

      Particularly scary is the number three. This seems to hint to us that three windows should be enough in principle ... Probably ...
    5. To enable support for contextual commands (context menu), some of MozSwing's methods have to be changed. For instance:
      @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. The complexity of the source (by the way, rather, this is due to the complexity of the XPCOM technology itself).
    8. No integration with maven.
    9. Hard to integrate with IoC container.
    10. It is not possible to create a window without any ruffles, they can only be hidden.


    JBrowser

    Of course, I have no illusions - my solution is not perfect: since it is based on MozSwing, it took over a lot of its sores. If you want, JBrowser is my reimagining of MozSwing, which is much more suitable for real systems. Well, at least I like my API many times more (although you may not like it at all).

    The main problem when I became acquainted with MozSwing I had was that it did not have a single entry point - the creation of the browser was carried out as a simple creation of a component (something like new MozillaWindow, sorry, I can’t remember exactly). Yes, it’s convenient in a way, as long as you don’t need more than just creating a browser window, but how do you configure your browser? One of the options is to inherit from MozSwing components, climb inside and dig-dig-dig ...

    I didn’t understand right away: how to change the proxy server settings for the browser? After some time, it turned out that there is a corresponding MozillaConfig class with the static setProxy method (or something like that). Oh my God, I would never know if I hadn’t opened the source. In general, for me this 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 highest level of integration between xulrunner and swing. The class that implements the interface performs all initialization, so to speak, paves the way for further work. In addition to initialization, the implementing class is obliged to provide you, upon first request, with a certain implementation of the BrowserConfig interface , which allows you to adjust the operation 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 “browser component”. Any browser component implements an interfaceJBrowserComponent . This interface is a composite, combining the functionality of the Swing component and implements the browser interface .

    /**
    * Браузер встроенный в компонент Swing // Browser embedded in swing component
    * @author caiiiycuk
    */
    public interface JBrowserComponent 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 Canvas Swing component with a browser built into it. Other browser component implementations almost always wrap a JBrowserCanvas (delegate calls to it). For example, another JBrowserFrame browser component (browser and JFrame) does this.

    Between these two opposites, there is another link that combines everything into a single whole - this is a factory layer (factor layer). After creating the top integration layer based on the BrowserManager, numerous factors of browser components can be created that implement the ComponentFactory interface. It’s normal when the application contains several such factors. A 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 customizing the browser components you create.

    Thus, here is the whole chain of work with JBrowser: create a BrowserManager (by the way, you can / need to use a builder for this ), create at least one factor for your browser components, and finally, create a browser using factor. Here 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 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 factors? I think (maybe I'm wrong) that this 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 in the very last menu make a button that creates a new browser tab for me.
    The advantage of this architecture is that you can rewrite any of these three levels, and get a still efficient system.

    Features that were not in MozSwing
    In addition to a complete redesign of the interfaces, some more features were added:

    • It became possible to easily get the favIcon of the open page (browser.getFavIcon ()).
    • It became possible to easily embed a context menu.
    • Full integration with maven. In this regard, JBrowser is easy to connect and easy to assemble for your target system (by changing the profile in pom.xml). At the moment, everything that MozSwing supports is supported - win, linux, solaris, mac.

    Project links: JBrowser
    project (stable)
    Project with many examples (dev)
    How to quickly start working with JBrowser under Eclipse

    What could not be achieved

    Unfortunately, Xulrunner 1.9.2 is not supported yet, but there is a catch, and the light at the end of the tunnel is visible.

    JWebPane

    I hope it comes out someday and saves the world. Really looking forward to this project.

    Also popular now: