Chromium Embedded Framework. Взаимодействие с Javascript

Я продолжаю разбираться с Chromium Embedded Framework и мне катастрофически не хватает подробной документации. Большая часть нужных сведений находится в файле cef.h, распечатка которого является моей настольной книгой. В конечном итоге именно в нем я нашел ответы на многие из своих вопросов, но это оказалось совсем непросто.

Вторым, по важности, источником знания является форум CEF, который успешно справляется с ролью указателя: "Копать здесь!"

Кстати, результаты моих раскопок теперь доступны на GitHub

Раньше я уже упоминал, что хочу использовать CEF для интеграции веб-интерфейса в свои desktop-приложения. В связи с этим, один из важных, для меня вопросов является взаисодействие c++ и javascript. Я достаточно успешно решил эту задачу для CHtmlView (IE), но там не обошлось без костылей, использование которых хотелось бы исключить с переходом на Chromium. Например, в IE я не смог найти способа, с помощью которого можно было бы перехватить AJAX-запрос и сформировать ответ на него прямо на c++. Пришлось написать функцию для этого функцию на JS. Забегая вперед скажу, что CEF не разочаровал меня в этом вопросе.

Для начала приведу несколько ссылок, которые помогли мне разобраться во взаимодействии c++ и javascript. Большинство из них ведут на ранее упомянутый форум

How to get the js return value in C++ code?

Changing reponse header to force download

CefContentFilter gets freed too quick?

AJAX with custom scheme

Filtering response content

Issue 241: Filtering response content

Вызов JavaScript функциий из С++

Для вызова функций javascript можно использовать два метода.

1. Использовать функцию ExecuteJavaScript, которая позволит асинхронно выполнить javascript-код. Если передан некорректный код, то узнать об этом в программе будет невозможно. На форуме пишут, что возвращаемое значение вызванной функции можно поучить, зарегестрировав callback-функцию. Я не успел попробовать этот метод, так как мне более интересен синхронный вызов. Выглядеть это может примерно вот так:

  1. CefRefPtr<CefFrame> frame = browser->GetMainFrame();
  2. frame->ExecuteJavaScript("log('process ajax request');", url, 0);
  3. std::string dump;
  4. DumpResponseContents(response, dump);
  5. dump = StringReplace(dump, "'", "\\'");
  6. dump = StringReplace(dump, "\n", "<br>");
  7. dump = StringReplace(dump, "&", "&amp;");
  8. dump = "log('" + dump + "');";
  9. frame->ExecuteJavaScript(dump, url, 0);
  10.  

2. Использовать объект V8 для синхронного вызова функции. Кроме непосредственного получения результата в этом случае возможно использование исключений для обработки ошибок, возникших при выполнении скрипта. В проекте можно посмотреть функцию void ClientHandler::JavaScriptTest(void), которая демонстрирует этот метод на примере вызова функции eval

  1. CefRefPtr<CefV8Value> globalObj = v8Context->GetGlobal();
  2. CefRefPtr<CefV8Value> evalFunc = globalObj->GetValue("eval");
  3.  
  4. CefRefPtr<CefV8Value> arg0 = CefV8Value::CreateString("1+2");
  5.  
  6. CefV8ValueList args;
  7. args.push_back(arg0);
  8.  
  9. CefRefPtr<CefV8Value> retVal;
  10. CefRefPtr<CefV8Exception> exception;
  11. evalFunc->ExecuteFunctionWithContext(v8Context, globalObj, args, retVal, exception, false)
  12.  

Вызов С++ функциий из JavaScript

Функций непосредственного вызова С++ кода в CEF я не нашел. Впрочем мне они и не нужны, так как одной из целей интеграции html-интерфейса является возможность использовать один и тот же код для локального приложения и веб-сервиса одновременно. Наиболее итересным представляется перехват и подмена AJAХ запросов. Для этого потребуется реализовать несколько методов интерфейса CefRequestHandler Метод OnBeforeResourceLoad будет вызван до отправки запроса (в том числе и AJAX) на сервер

  1. virtual bool OnBeforeResourceLoad(CefRefPtr<CefBrowser> browser,
  2. CefRefPtr<CefRequest> request,
  3. CefString& redirectUrl,
  4. CefRefPtr<CefStreamReader>& resourceStream,
  5. CefRefPtr<CefResponse> response,
  6. int loadFlags) { return false; }
  7.  

Для отмены обработки запроса надо вернуть true, a для обычного выполнения запроса достаточно вернуть из функции false. При возврате false можно указать URL в redirectUrl для перенаправления запроса на другой сервер или заполнить структуру resourceStream, чтобы эмулировать ответ удаленного сервера. Например, вот таким образом можно сформировать собственный ответ, предполагая, что браузер ожидает json в качестве ответа.

  1. std::string dump;
  2. /* don't send request to real server */
  3. dump = "{\"replaced\":\"value\"}";
  4. resourceStream = CefStreamReader::CreateForData((void*)dump.c_str(), dump.size());
  5. response->SetMimeType("text/plain");
  6. response->SetStatus(200);
  7. return false;
  8.  

При необходимости выполнить запрос на реальный сервер и вернуть пользователю результат, после дополнительной обработки на c++ уровне, требуется реализовать callback OnResourceResponse и определить значение объекта filter, ссылка на который передается в функцию. В результате будет вызван метод CefContentFilter::ProcessData, который позволяет просмотреть и изменить данные полученные от сервера

  1. void ClientHandler::ProcessData(const void* data, int data_size,
  2. CefRefPtr<CefStreamReader>& substitute_data)
  3. {
  4. /* change response data */
  5. std::vector<char> v;
  6. v.resize(data_size);
  7. memcpy(&*v.begin(), data, data_size);
  8. std::string s(v.begin(), v.end());
  9. s = StringReplace(s, "}", ",\"ts\":\"1234567890\"}");
  10. substitute_data = CefStreamReader::CreateForData((void*)s.c_str(), s.size());
  11. }
  12.  

Что ж, описанного вполне достаточно для организации взаимодействия C++ и JavaScript в CEF, а о том, с каким трудностями мне придется столкнуться при разработке реального приложения я непременно напишу в следующих статьях.

Читайте в блоге

Chromium Embedded Framework. Интеграция с MFC
PHP. Преобразование таблицы в картинку. Создание информеров.
Использование \K в регулярных выражениях. Игнорирование начальной части совпадения.

Комментарии:

None @ 28.03.2012 22:33

Как я понимаю можно вообще не писать скриптов на JS, и через ajax ничего не посылать, а навесить обработчики на пустые JS функции и обработку всю делать на С++ уровне.
Хотя наверное это не очень будет удобно...

Выводы сделал прочитав:
http://code.google.com/p/chromiumembedded/wiki/JavaScriptIntegration

Владимир Рыбаков @ 29.03.2012 01:10

Да, действительно можно. Непеременно попробую и этот способ.

Мне он кажется менее интересным потому, что для проекта, в котором я хочу использовать CEF, я предполагаю создание веб сервиса с тем же самым инетерфейсом, но серверной частью на PHP. Возможно, что и в desktop-приложении я буду использовать CEF только в качестве транспорта между JS и PHP, который будет выполнять скрипты в консольном режиме.

None @ 29.03.2012 08:48

"..., но серверной частью на PHP..."
Я так понимаю создание сервиса с тем же самым интерфейсом, что и для работы с PHP.

"...CEF только в качестве транспорта между JS и PHP..."

Это получается desktop приложение - web сервис, не проще ли сделать просто web сервис и не использовать CEF вообще. Зачем такие сложности?

Владимир Рыбаков @ 29.03.2012 09:44

Идея заключается в том, чтобы создать приложение, которое может работать без подключения к интернету вообще. Однако при наличии интернета обеспечиваются дополнительные плюшки - синхронизация данных на разных компьютерах, доступ к ним через веб-морду, которая выглядит точно так же как интерфейс самого приложения.

Да, конечно, можно развернуть веб-сервер на локальной машине и работать с ним по http-протоколу, но мне этот метод кажется менее надежным и удобным. В этом случае гораздо сложнее контролировать то, что происходит.

Кроме того CEF я собираюсь использовать и в других проектах, где роль с++ кода будет существенно больше.
Вот Александр @ 12.01.2013 10:50

Владимир спасибо за статьи.
Однако возник следующий вопрос:

При вызове модального окна, будь то наследник CDialog или AfxMessageBox в теле функции OnBeforeResourceLoad, созданное окно не являеться модальным. Не знаете как решить эту проблему?
Вот Александр @ 12.01.2013 11:37

Все разобрался. Вызов диалогов надо производить из окна на котором размещается браузер.

Войдите на сайт, чтобы оставить комментарий