![image](https://habrastorage.org/getpro/habr/post_images/bec/4ff/e75/bec4ffe75bb52c331ab8c09ea3af14b6.gif)
(Статья о интеграции браузера Mozilla, как Swing компонента).
Почему плох MozSwing
Нет-нет, не подумайте будто я считаю MozSwing полным отстоем, это громадная работа за которую нужно сказать огромное спасибо (моя работа в разы меньше), но все же меня не устраивает MozSwing и вот почему:
- Поддержка xulrunner`а только версии 1.9 (который, к слову, не поддерживает русские буквы в пути для windows-систем), когда есть версия 1.9.2.
- Огромное количество разрывающих мозг статических классов и методов, из-за чего MozSwing предстает перед нами нерушимой скалой. Для того, чтобы что-либо в нем изменить, приходится менять байт-код уже загруженных классов, потому что наследование в случае MozSwing не работает.
- Невозможность явно переопределить nsIWindowCreator (класс, реализующий этот интерфейс, занимается созданием окон для браузера), из-за чего приходится выдумывать костыли для создания браузеров как вкладок, поскольку MozSwing предпочитает открывать новые странички в новых окнах. Вот, например, так я заменяю nsIWindowCreator после инициализации:
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.
Однако, потенциально здесь скрыта ошибка, поскольку MozSwing любит статические классы, методы, и внутри ссылается на свой nsIWindowCreator. - Некоторые, скажем так, очень сомнительные решения в коде (исходники MozSwing: на всякий случай создается пул в 3 окна, чтобы потом из этого пула взять окно для встройки в него браузера):
/**
* 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.
Особенно пугает число три. Это как бы намекает нам на то, что трех окон в принципе должно хватить… Наверное... - Для включения поддержки контекстных команд (конеткстного меню), приходится менять некоторые методы MozSwing`a. Например:
@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. - Постоянно роняет Sun JVM на некоторых linux системах (ubuntu, openSUSE).
- Запутанность исходников (к слову, скорее это связанно с запутанностью самой технологии XPCOM).
- Нет интеграции с maven.
- Тяжело интегрировать с IoC контейнером.
- Не возмжно создать окно без всяких рюшечек, их можно только спрятать.
JBrowser
Конечно, я не питаю иллюзий — мое решение не идеально: поскольку оно базируется на MozSwing`е, то и переняло множество его болячек. Если хотите, JBrowser это мое переосмысление MozSwing`а в значительной степени более подходящие для реальных систем. Ну, по крайней мере, мне мое API нравится в разы больше (хотя вам оно может совсем не понравиться).
Главной проблемой при знакомстве с MozSwing у меня было то, что в нем нет единой точки входа — создание браузера выполнялось как простое создание компонента (что-то вроде new MozillaWindow, простите, уже точно не вспомню). Да, это в каком-то смысле удобно, пока вам не нужно нечто большее, чем создание просто окна браузера, но как сконфигурировать создаваемый браузер? Один из вариантов — наследоваться от MozSwing-компонентов, лезть внутрь и копать-копать-копать…
Сходу мне было не понятно: как изменить параметры прокси-сервера для браузера? Через некоторое время выяснилось, что есть соответствующий класс MozillaConfig со статическим методом setProxy (или как-то так). О, боже мой, я бы это никогда не узнал, если бы не открыл исходники. В общем, для меня это все не очевидно.
Поэтому JBrowser это некая противоположность (на данный момент не полная) MozSwing в смысле дизайна. В JBrowser есть точка входа — интерфейс BrowserManager. Это самый верхний уровень интеграции xulrunner`а и swing`а. Класс реализующий интерфейс выполняет всю инициализацию, так сказать подготавливает почву для дальнейшей работы. Помимо инициализации, реализующий класс обязан предоставить вам по первому требованию некую реализацию интерфейса BrowserConfig, который позволяет регулировать политику работы всех браузеров (включать/отключать картинки, прокси и др.) Как раз то, что я хотел.
В противоположность верхнему уровню интеграции в JBrowser присутствует самый нижний уровень интеграции — так называемый «компонент-браузер». Любой компонент-браузер реализует интерфейс JBrowserComponent. Это интерфейс представляет собой композит, совмещающий в себе функционал Swing компонента и реализует интерфейс браузера.
/**
* Браузер встроенный в компонент 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.
Есть JBrowserCanvas — базовая реализация этого интерфейса. Это не что иное, как Swing-компонент Canvas c встроенным в него браузером. Другие реализации компонента-браузера почти всегда оборачивают JBrowserCanvas (делегируют вызовы к нему). Например, так делает другой компонент-браузер JBrowserFrame (браузер и JFrame).
Между этими двумя противоположностями существует еще одно звено, которое объединяет все в единое целое — это factory-слой (фактори-слой). После создания верхнего слоя интеграции на основе BrowserManager могут быть созданы многочисленные фактори компонентов-браузеров, реализующие интерфейс ComponentFactory. Нормально, когда приложение содержит несколько таких фактори. Правильно настроенная фактори посредством своих методов создает конкретные реализации компонентов-браузеров. Допустим, я использую в своем приложении следующие фактори: JFrameBrowserFactory (создает браузер как новое окно), JTabbedBrowserFactory (создает браузер как новую вкладку). Благодаря этой схеме становится возможным легко решить проблему кастомизации создаваемых вами компонентов-браузеров.
Таким образом, вот вся цепочка работы с JBrowser: создать BrowserManager (кстати, для этого можно/нужно использовать билдер), создать хотя бы одну фактори для ваших комопонентов-браузеров и наконец, создать браузер с помощью фактори. Вот так выглядит самый простой вариант работы с 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.
Почему фактори? Я считаю (может, я и не прав), что это очень удобно. Я, например, регистрирую свои фактори в IoC-контейнере и легко могу получить к ним доступ практически из любой части приложения, поэтому я могу хоть в самой последней менюшке сделать кнопку, создающую мне новую вкладку-браузер.
Плюс этой архитектуры в том, что вы можете переписать любой из этих трех уровней, и получить по-прежнему эффективно работающею систему.
Фичи, которых не было в MozSwing
Помимо полной переработки интерфейсов, добавились еще вот какие фичи:
- Стало возможным легко получить favIcon открытой страницы (browser.getFavIcon()).
- Стало возможным легко встроить контекстное меню.
- Полная интеграция с maven. В связи с этим JBrowser легко подключить и легко собрать под вашу целевую систему (поменяв профиль в pom.xml). На данный момент поддерживается все то, что поддерживал MozSwing — win, linux, solaris, mac.
Ссылки проекта:
Проект JBrowser (stable)
Проект c множеством примеров (dev)
Как быстро начать работать с JBrowser под Eclipse
Чего не удалось добиться
К сожалению пока Xulrunner 1.9.2 не поддерживается, однако задел есть, и свет в конце туннеля виден.
JWebPane
Надеюсь, он выйдет уже когда-нибудь и спасет мир. Очень жду этот проект.