// Copyright (c) 2012 CEF Python, see the Authors file.
// All rights reserved. Licensed under BSD 3-clause license.
// Project website: https://github.com/cztomczak/cefpython
// BROWSER_PROCESS macro is defined when compiling the libcefpythonapp library.
// RENDERER_PROCESS macro is define when compiling the subprocess executable.
#ifdef BROWSER_PROCESS
#include "common/cefpython_public_api.h"
#endif
#if defined(OS_WIN)
#include
#pragma comment(lib, "Shell32.lib")
#endif // OS_WIN
#ifdef BROWSER_PROCESS
#ifdef OS_WIN
#include "client_handler/dpi_aware.h"
#elif OS_LINUX // OS_WIN
#include
#include
#include
#include "print_handler_gtk.h"
#endif // OS_LINUX
#endif // BROWSER_PROCESS
#include "cefpython_app.h"
#include "util.h"
#include "include/wrapper/cef_closure_task.h"
#include "include/base/cef_bind.h"
#include "include/base/cef_logging.h"
#include
#include
#include "v8utils.h"
#include "javascript_callback.h"
#include "v8function_handler.h"
#ifdef BROWSER_PROCESS
#include "main_message_loop/main_message_loop_external_pump.h"
#endif
// GLOBALS
bool g_debug = false;
CefPythonApp::CefPythonApp() {
#ifdef BROWSER_PROCESS
cefpython_GetDebugOptions(&g_debug);
#endif
}
// -----------------------------------------------------------------------------
// CefApp
// -----------------------------------------------------------------------------
void CefPythonApp::OnBeforeCommandLineProcessing(
const CefString& process_type,
CefRefPtr command_line) {
// IMPORTANT NOTES
// ---------------
// NOTE 1: Currently CEF logging is limited and you cannot log
// info messages during execution of this function. The
// default log severity in Chromium is LOG_ERROR, thus
// you can will only see log messages when using LOG(ERROR)
// here. You won't see LOG(WARNING) nor LOG(INFO) messages
// here.
// NOTE 2: The "g_debug" variable is never set at this moment
// due to IPC messaging delay. There is some code here
// that depends on this variable, but it is currently
// never executed.
#ifdef BROWSER_PROCESS
// This is included only in the Browser process, when building
// the libcefpythonapp library.
if (process_type.empty()) {
// Empty proc type, so this must be the main browser process.
App_OnBeforeCommandLineProcessing_BrowserProcess(command_line);
}
#endif // BROWSER_PROCESS
// IMPORTANT: This code is currently dead due to g_debug and
// LOG(INFO) issues described at the top of this
// function. Command line string for subprocesses are
// are currently logged in OnBeforeChildProcess().
// Command line string for the main browser process
// is currently never logged.
std::string process_name = process_type.ToString();
if (process_name.empty()) {
process_name = "browser";
}
#ifdef BROWSER_PROCESS
std::string logMessage = "[Browser process] ";
#else // BROWSER_PROCESS
std::string logMessage = "[Non-browser process] ";
#endif // BROWSER_PROCESS
logMessage.append("Command line string for the ");
logMessage.append(process_name);
logMessage.append(" process: ");
std::string clString = command_line->GetCommandLineString().ToString();
logMessage.append(clString.c_str());
if (g_debug) {
// This code is currently never executed, see the "IMPORTANT"
// comment above and comments at the top of this function.
LOG(INFO) GetSwitchValue("app-user-model-id");
if (app_id.length()) {
typedef HRESULT (WINAPI *SetAppUserModelID_Type)(PCWSTR);
static SetAppUserModelID_Type SetAppUserModelID;
SetAppUserModelID = (SetAppUserModelID_Type)GetProcAddress(
shell32, "SetCurrentProcessExplicitAppUserModelID");
HRESULT hr = (*SetAppUserModelID)(app_id.ToWString().c_str());
if (hr == S_OK) {
if (g_debug) {
// This code is currently never executed, see comments
// at the top of this function.
LOG(INFO) registrar) {
}
CefRefPtr CefPythonApp::GetResourceBundleHandler() {
return NULL;
}
CefRefPtr CefPythonApp::GetBrowserProcessHandler() {
return this;
}
CefRefPtr CefPythonApp::GetRenderProcessHandler() {
return this;
}
// ----------------------------------------------------------------------------
// CefBrowserProcessHandler
// ----------------------------------------------------------------------------
void CefPythonApp::OnContextInitialized() {
#ifdef BROWSER_PROCESS
REQUIRE_UI_THREAD();
#if defined(OS_LINUX)
print_handler_ = new ClientPrintHandlerGtk();
#endif // OS_LINUX
#endif // BROWSER_PROCESS
}
void CefPythonApp::OnBeforeChildProcessLaunch(
CefRefPtr command_line) {
#ifdef BROWSER_PROCESS
// This is included only in the Browser process, when building
// the libcefpythonapp library.
if (IsProcessDpiAware()) {
// It is required to set DPI awareness in subprocesses
// as well, see Issue #358.
command_line->AppendSwitch("enable-high-dpi-support");
}
BrowserProcessHandler_OnBeforeChildProcessLaunch(command_line);
#endif // BROWSER_PROCESS
#ifdef BROWSER_PROCESS
std::string logMessage = "[Browser process] ";
#else
std::string logMessage = "[Non-browser process] ";
#endif // BROWSER_PROCESS
logMessage.append("OnBeforeChildProcessLaunch() command line: ");
std::string clString = command_line->GetCommandLineString().ToString();
logMessage.append(clString.c_str());
LOG(INFO) extra_info) {
#ifdef BROWSER_PROCESS
// If you have an existing CefListValue that you would like
// to provide, do this:
// | extra_info = mylist.get()
// The equivalent in Cython is:
// | extra_info.Assign(mylist.get())
REQUIRE_IO_THREAD();
// Eg.:
// | extra_info->SetBool(0, false);
// | extra_info->SetString(1, "test");
// This is included only in the Browser process, when building
// the libcefpythonapp library.
BrowserProcessHandler_OnRenderProcessThreadCreated(extra_info);
#endif // BROWSER_PROCESS
}
CefRefPtr CefPythonApp::GetPrintHandler() {
#ifdef BROWSER_PROCESS
#if defined(OS_LINUX)
// For print handler to work GTK must be initialized. This is
// required for some of the examples.
GdkDisplay* gdk_display = gdk_display_get_default();
if (!gdk_display) {
LOG(INFO) OnScheduleMessagePumpWork(delay_ms);
}
#endif // BROWSER_PROCESS
}
// -----------------------------------------------------------------------------
// CefRenderProcessHandler
// -----------------------------------------------------------------------------
void CefPythonApp::OnRenderThreadCreated(CefRefPtr extra_info) {
}
void CefPythonApp::OnWebKitInitialized() {
}
void CefPythonApp::OnBrowserCreated(CefRefPtr browser) {
}
void CefPythonApp::OnBrowserDestroyed(CefRefPtr browser) {
LOG(INFO) browser,
CefRefPtr frame,
CefRefPtr request,
cef_navigation_type_t navigation_type,
bool is_redirect) {
return false;
}
void CefPythonApp::OnContextCreated(CefRefPtr browser,
CefRefPtr frame,
CefRefPtr context) {
LOG(INFO) message = CefProcessMessage::Create(
"OnContextCreated");
CefRefPtr arguments = message->GetArgumentList();
/*
Sending int64 type using process messaging would require
converting it to a string or a binary, or you could send
two ints, see this topic:
http://www.magpcss.org/ceforum/viewtopic.php?f=6&t=10869
*/
/*
// Example of converting int64 to string. Still need an
// example of converting it back from string.
std::string logMessage = "[Renderer process] OnContextCreated(): frameId=";
stringstream stream;
int64 value = frame->GetIdentifier();
stream SetInt(0, (int)(frame->GetIdentifier()));
browser->SendProcessMessage(PID_BROWSER, message);
CefRefPtr jsBindings = GetJavascriptBindings(browser);
if (jsBindings.get()) {
// Javascript bindings are most probably not yet set for
// the main frame, they will be set a moment later due to
// process messaging delay. The code seems to be executed
// only for iframes.
if (frame->IsMain()) {
DoJavascriptBindingsForFrame(browser, frame, context);
} else {
if (jsBindings->HasKey("bindToFrames")
&& jsBindings->GetType("bindToFrames") == VTYPE_BOOL
&& jsBindings->GetBool("bindToFrames")) {
DoJavascriptBindingsForFrame(browser, frame, context);
}
}
}
}
void CefPythonApp::OnContextReleased(CefRefPtr browser,
CefRefPtr frame,
CefRefPtr context) {
LOG(INFO) message;
CefRefPtr arguments;
// ------------------------------------------------------------------------
// 1. Send "OnContextReleased" message.
// ------------------------------------------------------------------------
message = CefProcessMessage::Create("OnContextReleased");
arguments = message->GetArgumentList();
arguments->SetInt(0, browser->GetIdentifier());
// TODO: losing int64 precision, the solution is to convert
// it to string and then in the Browser process back
// from string to int64. But it is rather unlikely
// that number of frames will exceed int range, so
// casting it to int for now.
arguments->SetInt(1, (int)(frame->GetIdentifier()));
// Should we send the message using current "browser"
// when this is not the main frame? It could fail, so
// it is more reliable to always use the main browser.
browser->SendProcessMessage(PID_BROWSER, message);
// ------------------------------------------------------------------------
// 2. Remove python callbacks for a frame.
// ------------------------------------------------------------------------
// If this is the main frame then the message won't arrive
// to the browser process, as browser is being destroyed,
// but it doesn't matter because in LifespanHandler_BeforeClose()
// we're calling RemovePythonCallbacksForBrowser().
message = CefProcessMessage::Create("RemovePythonCallbacksForFrame");
arguments = message->GetArgumentList();
// TODO: int64 precision lost
arguments->SetInt(0, (int)(frame->GetIdentifier()));
browser->SendProcessMessage(PID_BROWSER, message);
// ------------------------------------------------------------------------
// 3. Clear javascript callbacks.
// ------------------------------------------------------------------------
RemoveJavascriptCallbacksForFrame(frame);
}
void CefPythonApp::OnUncaughtException(CefRefPtr browser,
CefRefPtr frame,
CefRefPtr context,
CefRefPtr exception,
CefRefPtr stackTrace) {
}
void CefPythonApp::OnFocusedNodeChanged(CefRefPtr browser,
CefRefPtr frame,
CefRefPtr node) {
}
bool CefPythonApp::OnProcessMessageReceived(CefRefPtr browser,
CefProcessId source_process,
CefRefPtr message) {
std::string messageName = message->GetName().ToString();
std::string logMessage = "[Renderer process] OnProcessMessageReceived(): ";
logMessage.append(messageName.c_str());
LOG(INFO) args = message->GetArgumentList();
if (messageName == "DoJavascriptBindings") {
if (args->GetSize() == 1
&& args->GetType(0) == VTYPE_DICTIONARY
&& args->GetDictionary(0)->IsValid()) {
// Is it necessary to make a copy? It won't harm.
SetJavascriptBindings(browser,
args->GetDictionary(0)->Copy(false));
DoJavascriptBindingsForBrowser(browser);
} else {
LOG(ERROR) GetType(0) == VTYPE_INT) {
int jsCallbackId = args->GetInt(0);
CefRefPtr jsArgs;
if (args->IsReadOnly()) {
jsArgs = args->Copy();
} else {
jsArgs = args;
}
// Remove jsCallbackId.
jsArgs->Remove(0);
ExecuteJavascriptCallback(jsCallbackId, jsArgs);
} else {
LOG(ERROR) browser,
CefRefPtr data) {
javascriptBindings_[browser->GetIdentifier()] = data;
}
CefRefPtr CefPythonApp::GetJavascriptBindings(
CefRefPtr browser) {
int browserId = browser->GetIdentifier();
if (javascriptBindings_.find(browserId) != javascriptBindings_.end()) {
return javascriptBindings_[browserId];
}
return NULL;
}
void CefPythonApp::RemoveJavascriptBindings(CefRefPtr browser) {
int browserId = browser->GetIdentifier();
if (javascriptBindings_.find(browserId) != javascriptBindings_.end()) {
javascriptBindings_.erase(browserId);
}
}
bool CefPythonApp::BindedFunctionExists(CefRefPtr browser,
const CefString& functionName) {
CefRefPtr jsBindings = GetJavascriptBindings(browser);
if (!jsBindings.get()) {
return false;
}
std::string strFunctionName = functionName.ToString();
size_t dotPosition = strFunctionName.find(".");
if (std::string::npos != dotPosition) {
// This is a method call, functionName == "object.method".
CefString objectName(strFunctionName.substr(0, dotPosition));
CefString methodName(strFunctionName.substr(dotPosition + 1,
std::string::npos));
if (!(jsBindings->HasKey("objects")
&& jsBindings->GetType("objects") == VTYPE_DICTIONARY)) {
LOG(ERROR) objects = \
jsBindings->GetDictionary("objects");
if (objects->HasKey(objectName)) {
if (!(objects->GetType(objectName) == VTYPE_DICTIONARY)) {
LOG(ERROR) methods = \
objects->GetDictionary(objectName);
return methods->HasKey(methodName);
} else {
return false;
}
} else {
// This is a function call.
if (!(jsBindings->HasKey("functions")
&& jsBindings->GetType("functions") == VTYPE_DICTIONARY)) {
LOG(ERROR) functions = \
jsBindings->GetDictionary("functions");
return functions->HasKey(functionName);
}
}
void CefPythonApp::DoJavascriptBindingsForBrowser(
CefRefPtr browser) {
// get frame
// get context
// if bindToFrames is true loop through all frames,
// otherwise just the main frame.
// post task on a valid v8 thread
CefRefPtr jsBindings = GetJavascriptBindings(browser);
if (!jsBindings.get()) {
// Bindings must be set before this function is called.
LOG(ERROR) frameIds;
std::vector frameNames;
if (jsBindings->HasKey("bindToFrames")
&& jsBindings->GetType("bindToFrames") == VTYPE_BOOL
&& jsBindings->GetBool("bindToFrames")) {
// GetFrameIdentifiers() is buggy, returns always a vector
// filled with zeroes (as of revision 1448). Use GetFrameNames()
// instead.
browser->GetFrameNames(frameNames);
for (std::vector::iterator it = frameNames.begin(); \
it != frameNames.end(); ++it) {
CefRefPtr frame = browser->GetFrame(*it);
if (frame.get()) {
frameIds.push_back(frame->GetIdentifier());
// | printf("GetFrameNames(): frameId = %lu\n",
// | frame->GetIdentifier());
}
}
}
// BUG in CEF:
// GetFrameNames() does not return the main frame (as of revision 1448).
// Make it work for the future when this bug gets fixed.
std::vector::iterator find_it = std::find(
frameIds.begin(), frameIds.end(),
browser->GetMainFrame()->GetIdentifier());
if (find_it == frameIds.end()) {
// Main frame not found in frameIds vector, add it now.
// | printf("Adding main frame to frameIds: %lu\n",
// | browser->GetMainFrame()->GetIdentifier());
frameIds.push_back(browser->GetMainFrame()->GetIdentifier());
}
if (!frameIds.size()) {
LOG(ERROR) ::iterator it = frameIds.begin(); \
it != frameIds.end(); ++it) {
if (*it frame = browser->GetFrame(*it);
if (!frame.get()) {
LOG(ERROR) context = frame->GetV8Context();
CefRefPtr taskRunner = context->GetTaskRunner();
taskRunner->PostTask(CefCreateClosureTask(base::Bind(
&CefPythonApp::DoJavascriptBindingsForFrame, this,
browser, frame, context
)));
}
}
void CefPythonApp::DoJavascriptBindingsForFrame(CefRefPtr browser,
CefRefPtr frame,
CefRefPtr context) {
CefRefPtr jsBindings = GetJavascriptBindings(browser);
if (!jsBindings.get()) {
// Bindings may not yet be set, it's okay.
LOG(INFO) HasKey("functions")
&& jsBindings->GetType("functions") == VTYPE_DICTIONARY
&& jsBindings->HasKey("properties")
&& jsBindings->GetType("properties") == VTYPE_DICTIONARY
&& jsBindings->HasKey("objects")
&& jsBindings->GetType("objects") == VTYPE_DICTIONARY
&& jsBindings->HasKey("bindToFrames")
&& jsBindings->GetType("bindToFrames") == VTYPE_BOOL)) {
LOG(ERROR) IsValid()) {
// BUG in CEF (Issue 130), the "context" provided by CEF may
// not be valid. May be a timing issue. Or may be caused by
// a redirect to a different origin and that creates a new
// renderer process.
// This message is logged in the tutorial.py example which
// uses data uri created from html string.
LOG(INFO) Enter();
didEnterContext = true;
}
CefRefPtr functions = \
jsBindings->GetDictionary("functions");
CefRefPtr properties = \
jsBindings->GetDictionary("properties");
CefRefPtr objects = \
jsBindings->GetDictionary("objects");
// Here in this function we bind only for the current frame.
// | bool bindToFrames = jsBindings->GetBool("bindToFrames");
if (!(functions->IsValid() && properties->IsValid()
&& objects->IsValid())) {
LOG(ERROR) Exit();
return;
}
CefRefPtr v8Window = context->GetGlobal();
CefRefPtr v8Function;
CefRefPtr v8FunctionHandler(new V8FunctionHandler(this, 0));
// FUNCTIONS.
std::vector functionsVector;
if (!functions->GetKeys(functionsVector)) {
LOG(ERROR) GetKeys() failed";
if (didEnterContext)
context->Exit();
return;
}
for (std::vector::iterator it = functionsVector.begin(); \
it != functionsVector.end(); ++it) {
CefString functionName = *it;
v8Function = CefV8Value::CreateFunction(functionName,
v8FunctionHandler);
v8Window->SetValue(functionName, v8Function,
V8_PROPERTY_ATTRIBUTE_NONE);
}
// PROPERTIES.
CefRefPtr v8Properties = CefDictionaryValueToV8Value(
properties);
std::vector v8Keys;
if (!v8Properties->GetKeys(v8Keys)) {
LOG(ERROR) GetKeys() failed";
if (didEnterContext)
context->Exit();
return;
}
for (std::vector::iterator it = v8Keys.begin(); \
it != v8Keys.end(); ++it) {
CefString v8Key = *it;
CefRefPtr v8Value = v8Properties->GetValue(v8Key);
v8Window->SetValue(v8Key, v8Value, V8_PROPERTY_ATTRIBUTE_NONE);
}
// OBJECTS AND ITS METHODS.
std::vector objectsVector;
if (!objects->GetKeys(objectsVector)) {
LOG(ERROR) GetKeys() failed";
if (didEnterContext)
context->Exit();
return;
}
for (std::vector::iterator it = objectsVector.begin(); \
it != objectsVector.end(); ++it) {
CefString objectName = *it;
CefRefPtr v8Object = CefV8Value::CreateObject(NULL, NULL);
v8Window->SetValue(objectName, v8Object, V8_PROPERTY_ATTRIBUTE_NONE);
// METHODS.
if (!(objects->GetType(objectName) == VTYPE_DICTIONARY)) {
LOG(ERROR) GetType() != VTYPE_DICTIONARY";
if (didEnterContext)
context->Exit();
return;
}
CefRefPtr methods = \
objects->GetDictionary(objectName);
std::vector methodsVector;
if (!(methods->IsValid() && methods->GetKeys(methodsVector))) {
LOG(ERROR) GetKeys() failed";
if (didEnterContext)
context->Exit();
return;
}
for (std::vector::iterator it = methodsVector.begin(); \
it != methodsVector.end(); ++it) {
CefString methodName = *it;
// fullMethodName = "object.method"
std::string fullMethodName = objectName.ToString().append(".") \
.append(methodName.ToString());
v8Function = CefV8Value::CreateFunction(fullMethodName,
v8FunctionHandler);
v8Object->SetValue(methodName, v8Function,
V8_PROPERTY_ATTRIBUTE_NONE);
}
}
// END.
if (didEnterContext)
context->Exit();
}