Awesomium для C++

в 10:27, , рубрики: Awesomium, c++, chromium, браузеры, метки: , ,

Awesomium для C++ Awesomium — это библиотека, для интеграции браузера на базе Chromium в своё приложение. Вся прелесть Awesomium состоит в том, что его можно интегрировать в приложение практически любого типа (есть примеры интеграции в 3D игры на базе Unity3D), он обладает широким набором возможностей для разработчика и, честно говоря, с ним просто приятно работать.

Но, к сожалению, библиотека имеет довольно скромную документацию, практически отсутствуют примеры и комюнити (это касается разработки под C++, дотнетчикам повезло больше). Некоторые моменты мне приходилось по крупицам собирать с разных источников, а к другим понимание приходило после серии проб и ошибок.

И вот накопив некоторые знания я решил ими поделится. Уверен, что я не первый и не последний, кто будет проходить этот путь.

Сразу хочу предупредить: возможно местами я не правильно понял или недопонял, не стоит судить строго, просто наведите на правильный путь, буду очень признателен.

Оконный и безоконный Awesomium

Прежде всего стоит сказать, что приложения с использованием Awesomium могут быть 2 типов:

  • Offscreen — без отображения окна браузера (весь рендеринг и работа скриптов на странице происходит в фоне и не отображается пользователю) либо с отображением браузера, но его отрисовкой и обработкой сообщений занимается программист
  • Windowed — Awesomium «цепляется» к какому-то окну и отображается на нем. Отрисовка, обработка сообщений мыши и клавиатуры осуществляется средствами библиотеки

Первый интересен тем, что позволяет работать с браузером без окна. Можно даже работать с консольного приложения, например, для создания скриншотов страниц. На этом типе мы останавливаться не будем, потому что по нему есть хорошие примеры и я с ним фактически не работал. Интересовал меня второй режим — Windowed.

Windowed Awesomium используется когда вам нужен полноценный браузер в оконном приложении. В этом случае библиотека позаботится об основном — взаимодействии с пользователем. Но не стоит думать, что добавив пару строчек в программу вы получите полноценный браузер, это не так. Львиная часть работы лежит на плечах программистов, например: диалоги загрузки/заливки файлов (да в принципе все диалоги), всплывающие меню, выпадающие списки, создание новых вкладок и т.д.

Структура библиотеки

Структура библиотеки очень простая (правда сперва она была совсем не очевидная):

Основой браузера выступает ядро (WebCore). Каждое приложение может иметь только одно ядро и это в свою очередь накладывает некоторые ограничения. Например, если вы хотите, чтобы какая-то вкладка имела отменный от других User Agent — то у вас ничего не получится, так как значение User Agent хранится в настройках ядра.

Следующим в иерархии выступает WebView — это окно браузера. При создании каждого WebView — запускается отдельный процесс Chromium. Когда вам WebView больше не нужен — не забываем подчищать за ним, иначе процесс будет висеть до тех пор, пока мы не уничтожим WebCore.

Каждый WebView может использовать сессию (WebSession). Сессия — это пользовательские данные (кукисы, кэшь, сертификаты, локальные БД и т.д.). Одна WebSession может подключена к разным WebView. Так же в WebSession хранятся важные настройки работы браузеров (тех, которые используют данную сессию), например прокси.

Ну и последнее (из того, с чем мне пришлось работать) это Listeners — интерфейсы для взаимодействия с браузером. Listeners бывает несколько и каждый из них решает определенные задачи. Например в WebViewListener::Dialog содержится реализация методов для работы с диалоговыми окнами, а в WebViewListener::Download — методы информировании о ходе загрузки файлов.

Рождение

С теорией покончено, переходим к практике. Прежде всего рассмотрим процесс создания и инициализации ядра и вкладок браузера в Windowed режиме.

Прежде всего необходимо инициализировать ядро:

WebCore* core = 0;
WebConfig config;
core = WebCore::Initialize(config);

Далее создаём сессию (и для примера, в настройках сессии прописываем используемый прокси)

WebPreferences prefs;
prefs.proxy_config = WSLit("198.1.99.26:3128");
WebSession * session = core->CreateWebSession(WSLit(""), prefs);

Ну и конечно же создаём объект WebView.

WebView* view = core->CreateWebView(WIN_WIDTH, WIN_HEIGHT, session, kWebViewType_Window);
view->set_parent_window(hwnd);

На последнем шаге остановимся подробнее:

  • Прежде всего стоит заметить, что указывать начальную ширину и высоту окна браузера обязательно. Их позднее можно изменить (например при изменении размеров родительского окна)
  • Третьим параметром выступает сессия. Сессию в ходе работы вкладки менять нельзя, но доступ к ней остаётся, так что теоретически (я не проверял) можно менять настройки и они будут вступать в силу
  • Последний параметр метода создания вкладки браузера есть WebViewType. Он может быть либо kWebViewType_Offscreen (по умолчанию), либо kWebViewType_Window. Мы указываем второй, так как создаём окконый браузер
  • Обязательным шагом после создания WebView с типом kWebViewType_Window есть указание handle родительского окна. Handle не обязательно должен быть окна того же процесса, в котором используется Awesomium, у меня это к примеру handle панели в Delphi программе и всё прекрасно работает

Жизнь

В зависимости от того, для каких целей используется Awesomium — его «жизнь» может значительным образом отличатся. Я лишь остановлюсь на основных моментах при работе с Windowed типом вкладок.

Во-первых, не смотря на то, что Windowed Awesomium частично живет своей жизнь, его всё же необходимо периодически подталкивать работать. Подталкиванием занимается метод ядра Update. Рекомендуется вызывать его в таймере, например так:

// Create our WebCore Update timer, renders at a max of 66 FPS
UINT myTimer = SetTimer ( hwnd, 0, 15, NULL );

...

case WM_TIMER: 
{
	if (core)
	{
		core->Update();
	}
}

...

Так же обновления ядра рекомендуется делать после уничтожения WebView.

Во-вторых, если вы хотите, чтобы окно браузера меняло свои размеры вместе с родительским окном — необходимо обрабатывать сообщение WM_SIZE родительского окна и сообщать новые размеры браузеру. Это делается довольно просто:

case WM_SIZE:
{
	if (view)
	{
		int nWidth = LOWORD(lParam);  // ширина рабочей области 
		int nHeight = HIWORD(lParam); // высота рабочей области 
		view->Resize(nWidth, nHeight);
	}
}

Ну и в третьих, для полноценной работы браузера нужно реализовать интерфейсы Listeners и подключить их к каждому из WebView. С реализацией интерфейсов проблем быть не должно, они отлично документированы. Все необходимые подсказки можно найти в файле WebViewListener.h. Ну а подключать их совсем не сложно:

DialogListener * dialog_lis = new DialogListener();
view->set_dialog_listener(dialog_lis);

Как я уже говорил, при работе в Windowed режиме о многом побеспокоится библиотека, это: отрисовка загруженной странице на родительском окне, обработка нажатия клавиш клавиатуры и мыши, изменения курсоров мышки и т.д.

Ну и в завершение этой главы — небольшой пример открытия страницы и ожидания её загрузки:

// Открываем страницу
view->LoadURL(WebURL(WSLit("http://google.com/")));

// Ожидаем завершения загрузки
while(view->IsLoading())
{
    Sleep(50);
    core->Update();  
}

//  Переводим фокус (иначе действия пользователя не будут обрабатываться)
view->Focus();

Смерть

С уборкой после работы возникли наибольшие проблемы. Информации по этому поводу в официальной документации и в Google для работы в Windowed режиме чуть больше чем ноль. Методом проб и ошибок я дошел до следующего:

1. Перед тем, как уничтожать WebView — сначала нужно уничтожить родительское окно (обратный порядок приведет к краху приложения).

2. Если вы хотите, чтобы данные сессии сохранились (если это не in-memmory сессия) — нужно вызвать метод Release для экземпляра сессии.

3. WebView уничтожается с помощью вызова метода Destroy (и никак иначе)

4. Ядро уничтожается с помощью вызова метода Shutdown, который кстати умеет подчищать и за неверно уничтоженными WebView (но лучше всё таки контролировать самому этот процесс)

Примерно, это выглядит следующим образом:

// Где-то тут уничтожаем родительское окно
...

// Останавливаем загрузку страницы (рекомендуется так же дождаться полной остановки, так как некоторые жалуются на ошибку, если этого не сделать)
view->Stop();  

// Сохраняем данные сессии на диск
view->session()->Release();

// Уничтожаем WebView (в этот момент процесс Chromium для этой вкладки изчезает из списка процессов)
view->Destroy();

// Обновляем ядро (не знаю на сколько обязательно это делать, но в документации есть такие рекомендации)
core->Update();

// Уничтожаем ядро
core->Shutdown();

Уничтожать ядро необходимо только перед завершением работы программы. Если вам нужно просто закрыть одну вкладку браузера, то и удалять нужно только её и WebView, который находился на этой вкладке.

Полезные ссылки

При изучении Awesomium мне очень сильно помогли комментарии в хидер файлах, которые находятся в папке Awesomium SDK1.7.1.0includeAwesomium и следующие страницы интернета:

wiki.awesomium.com/general-use/ — скромные примеры использования от создателей Awesomium. Там же есть описание того, как создавать преокт с использованием этой библиотеки и какие файлы необходимы для работы приложения

awesomium.com/docs/1_7_0/cpp_api/index.html#intro_sec — официальная документация (по сути дублирует комментарии в подключаемых хидер файлах)

gist.github.com/khrona — наработки пользователя khrona на github. Он очень сильно мне помог создать первое тестовое приложение, в частности этот пример Example of using Awesomium r148 API with GDI/WinForms/WinAPI. Правда он пытается сделать в Offscreen режиме подобие Windowed режима, из-за чего имеет скудный функционал (например не работает скрол и курсор мышки не меняется при наведении на ссылку), а так же много избыточного когда.

answers.awesomium.com/index.html — ответы на вопросы пользователей. К сожалению далеко не все вопросы получают ответы, так что полезной информации там не очень много, но она есть

Автор: xpert13

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js