Как-то мы обсуждали десктопные приложения nw.js. Всё хорошо, но необходимость распространять весь движок браузера (который с течением времени и добавлением новых фич меньше не становится и сейчас весит сжатый 30МБ) — удручает.
А что если сделать модуль для node.js, умеющий показывать UI в системном браузере?
Под катом расскажу о попытке — как оно, стоит ли усилий, можно ли использовать, и что в результате получилось.

Dicslaimer


В основном проект был сделан скорее как proof-of-concept: поиграться, посмотреть, проверить, как оно. Сделал на нём 3-4 заказа в стиле «хочу маленькое приложение со встроенным webview» или «хочу обёртку для сайта в окне десктоп-приложения». Может быть, кому-то мой опыт окажется полезен, поэтому выложил в паблик. Код на гитхабе. Подробно описывать нюансы кода не буду, цель — не описать код, а скорее подумать над пригодностью результата.

Как это работает


Приложение статически собирается с node.js со своей точкой входа. При запуске в ноду добавляется нативный модуль с логикой отображения окон, и управление передаётся _thirdPartyMain.js (в ноде есть такая возможность). Оттуда загружается пользовательский main.js, где подключается модуль ui. Модуль стартует http-сервер, открывает окно с заданным конфигом и направляет webview на адрес встроенного сервера, который отдаёт пользовательский контент. Окно генерирует события (перемещение, сворачивание, ...) и умеет выполнять методы (например, закрыться).

WebView


Под винды вставляется ActiveX-ный WebBrowser (это тот же MSIE, но без тубларов, адресных строк итд). По умолчанию он показывает много ненужных диалогов (которые отключаются странным образом), но в целом работает как хочется. IE, обновляется неважно, а поддержать XP надо было, поэтому для старых версий Windows предусмотрена возможность скачать вебкит (chrome embedded framework). При необходимости, если пользователь не против, приложение скачивает библиотеки, распаковывает их и работает уже с вебкитом.
Под маком всё более прозрачно, WebKit WebView по сравнению с IE в интеграции намного проще и понянтее; под линуксом есть webkit-gtk.

Упаковка приложений


Нужно было, чтобы приложения паковались в один EXE. Самым подходящим форматом оказался ZIP: он читается с конца, позволяя дописывать в исполняемый файл что угодно, и распаковывать его из себя; по этому принципу и работают SFX. Для использование из node.js, можно повесить хук на обращения к fs и перенаправить запросы к файлам, которые есть в архиве, к виртуальной файловой системе вместо чтения с диска.

Взаимодействие с браузером


IE и WebKit предлагают способы взаимодействия как из C++ в JS, так и наоборот. В IE javascript-овый объект, в том числе и функция, представляет собой объект IDispatch. Если проверить typeof такого объекта, javascript-движок отдаст unknown, это так называемые host objects (использование таких результатов оператора typeof в стандарте ES6 уже не рекомендуется, но раньше про нестандартные типы ничего не было сказано). Что-то вызвать из C++ можно, обратившись к объекту window.external, при условии, что хост реализовал этот метод.
В вебките аналогично, можно добавить свойство window как объект с методми, доступными для вызова.

Взаимодействие с node.js


В ноде процесс создания плагинов прекрасно документирован. Аналогично создаются и встроенные аддоны, но регистрируются другим макросом. После создания класса аддона, он наследуются от EventEmitter-а и умеет генерировать события, получив ссылку на метод emit.
Node.js намеренно живёт в отдельном треде, чтобы длительные операции не блокировали UI, поэтому для отправки сообщения в ноду и синхронизации используется библиотека UV.

Что получилось хорошо


Размер приложения


Он составляет 2-3 МБ (незапакованного 7). В принципе можно сделать сборку и меньшего размера, если надо только открыть страничку в приложении, без node.js (вобщем-то ради размера всё и делалось).

Время запуска


Т.к. в приложении не используются дополнительные фреймворки, запускается оно быстро в среднем 1 секунду (про время первого запуска см. ниже); добавив профилирование, можно убедиться, что основное время уходит на инициализацию браузера.

Проблемы


Первый запуск


Windows оптимизирует запуск приложений и компонентов, которые вы используете чаще. Если IE пользуются редко, время первого запуска приложения составляет где-то 3..10 секунд, в зависимости от операционки и компьютера.

Версии браузеров


Очень часто IE используют как инструмент для скачивания браузера и не обновляют вообще никогда (хотя с последними версиями Windows ситуация улучшается). IE нельзя обновить принудительно (показать диалог обновите IE из приложения равносильно самоубийству: обновление IE сложное, долгое, часто требует перезагрузки — нельзя так издеваться над пользователем, он просто найдёт другое приложение).
Версии браузеров привязаны к операционке: для Windows XP нет IE10, для OS X Lion нет Safari 8.

Контексты javascript


Объекты node.js нельзя использовать в браузере и наоборот, взаимодействие происходит или как в классическом клиент-серверном приложении (xhr), или способом, предоставленным программой (postMessage в окне или ноде) — т.е. как в web worker, передать можно только простые данные. Можно было сделать context bridge, но это во-первых сложно и бажно, во-вторых недальновидно, потому что ES6 уже почти тут, а что делать, например, с объектом-прокси в IE10 — неизвестно.

Что есть


Есть сборка под win/mac/linux, которую в принципе можно использовать, кастомизировать итд. Я и использую её для создания кастомизированных «приложений-браузеров», когда это бывает вдруг кому-то надо. Она относительно стабильна, но до ума и релизного качества я её не доводил, потому что…

Выводы


Приложения скачиваются редко, интернет сейчас достаточно быстрый (и в случае установки приложений, EDGE действительно можно пренебречь, т.к. приложение — это не сайт, его не будут устаналивать в дороге), нерешаемые проблемы в использовании системного браузера присутствуют. В большинстве случаев сейчас игра не стоит свеч и использовать вебкит кажется более правильным. Если цель — создать маленькое приложение в основном под новые операционки — а может быть, можно и так.

Ссылки


Комментарии (16)


  1. naum
    21.06.2015 22:07

    Приложение запаковано UPX — некоторые антивирусы могут ругаться на него. Можно не паковать приложение и получить размер 6-7МБ.
    Ахаха, убили на прочь, а на хрена в 2015 году использовать UPX или любой другой упаковщик исполняемых файлов? В чем этот магический профит? Пробовали бенчмарить без него?

    В целом — круто, спасибо, ждал когда кто-то что-то подобное реализует рано или поздно, тут действительно большой простор для фантазий.


    1. Antelle Автор
      21.06.2015 22:19

      > в 2015 году UPX
      Согласен, тем более что приложения в виде «голого» exe сейчас никто не распространяет, всегда или паковщик из инсталлятора, или архив. Заказчик хотел upx, так он там и остался, как-то не подумал удалить его, спасибо что напомнили, выпилю, профита 0. Бенчмаркил и без него, и с ним, конечно. upx и другие пакеры замедляют выполнение на 200-500 мс.


  1. Igogo2012
    21.06.2015 22:07

    Как-то раз пробовал писать на nw.js там была проблема которую я на тот момент не смог решить и отказался от данного решения.
    Проблема заключалась в том что мне нужно было в контенте приложения выводить гиперссылки которые должны открываться дефолтным браузером ОС, но так как там все приложение по сути и есть браузером с веб приложением то все гиперссылки открывались в том же окне…
    Как у Вас обстоят дела с таким явлением, можно ли заставить приложение открывать ссылки в дефолтном браузере которым пользуется текущий пользователь системы?


    1. Antelle Автор
      21.06.2015 22:22

      Можно. Вроде как, и в nw.js тоже можно. На ссылку повесить обработчик, в обработчике вызвать нодовский spawn.


    1. tsmar
      21.06.2015 22:46
      +3

      если использовать
      var g = require('nw.gui');
      то потом вот так легко открываем в дефолтном браузере ссылку
      g.Shell.openExternal('https://google.com');


      1. Igogo2012
        24.06.2015 11:21

        Спасибо!


  1. Bytexpert
    22.06.2015 00:13
    +1

    А как решали проблему c настройками IE? Отключенный показ картинок и отключенный JS?

    Я делал подобное только под Windows и использовал встроенный JavaScript из WSH, он работал в потоке приложения как основная логика, а с браузером взаимодействовал через COM-объекты проброшенные в браузер. Ноду не использовал, у меня была своя библиотека объектов доступная из скрипта для работы с сетью, zip-ами, xml, графикой и т.п. с файловой системой работал через объекты WSH. Даже приложение написал рабочее pingxpert.com Но у Вас решение более универсальное.


    1. Antelle Автор
      22.06.2015 00:38

      IE позволяет приложению задать путь к настройкам реестра через GetOptionKeyPath (и GetOverrideKeyPath). Если реализовать его, контрол будет с дефолтовыми настройками. Там всякие настройки запрета показа отладчиков итд. (а если честно отключённые js не проверяли, посмотрю, может и нужен более хитрый метод со своим security manager-ом).


      1. Bytexpert
        22.06.2015 01:06

        Да я использовал этот метод и security manager, но пользователи всё-равно частенько жалуются на неработоспособность программы, может тоже где-то недотестил и сам накосячил. А ещё большая проблема с отображением контента, если старые версии IE, то Bootstrap уже нельзя использовать. Поэтому WebKit мне в этом плане надёжнее кажется. А в передаче контекста в IE мне конечно проще было значительно, я просто отдавал объекты JavaScript-а основной логики прямо в браузер через SetExternalDispatch и получал в браузере весь нужный функционал.


        1. Antelle Автор
          22.06.2015 07:52

          Вот примерно к такому выводу я и пришёл, поигравшись со встроенными браузерами: в большинстве случаев вебкит показалось использовать более целесообразным.


  1. some_x
    22.06.2015 08:39

    Экономия в 30 мб, в десктопном приложении действительно стоит таких финтов ушами?


    1. Antelle Автор
      22.06.2015 08:57
      +1

      Не думаю (о чём и написал в «проблемах» и «выводах»). Скажу даже больше: если бы работа над проектом не была оплачена, я бы не стал это делать. А так, мне давно было интересно, а что же с таким подходом получится — вот довелось попробовать.