// Copyright (c) 2012-2014 The CEF Python authors. All rights reserved.
// License: New BSD License.
// Website: http://code.google.com/p/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
#include "cefpython_app.h"
#include "util.h"
#include "include/wrapper/cef_closure_task.h"
#include "include/base/cef_bind.h"
#include "DebugLog.h"
#include "LOG_DEBUG.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
bool g_debug = false;
std::string g_logFile = "debug.log";
CefPythonApp::CefPythonApp() {
#ifdef BROWSER_PROCESS
cefpython_GetDebugOptions(&g_debug, &g_logFile);
#endif
}
// -----------------------------------------------------------------------------
// CefApp
// -----------------------------------------------------------------------------
void CefPythonApp::OnBeforeCommandLineProcessing(
const CefString& process_type,
CefRefPtr command_line) {
#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
std::string process_name = process_type.ToString();
if (process_name.empty()) {
process_name = "browser";
}
std::string logMessage = "Command line string for the ";
logMessage.append(process_name);
logMessage.append(" process: ");
std::string clString = command_line->GetCommandLineString().ToString();
logMessage.append(clString.c_str());
// OnBeforeCommandLineProcessing() is called before
// CefRenderHandler::OnRenderThreadCreated() which sets
// the debug options. Thus debugging will always be Off
// at the time this method is called. The fix for that
// is to keep the command line string somewhere and call
// DebugLog later in OnRenderThreadCreated().
if (g_debug) {
DebugLog(logMessage.c_str());
} else {
commandLineString_ = logMessage;
}
}
void CefPythonApp::OnRegisterCustomSchemes(
CefRefPtr 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.
BrowserProcessHandler_OnBeforeChildProcessLaunch(command_line);
#endif // BROWSER_PROCESS
}
void CefPythonApp::OnRenderProcessThreadCreated(
CefRefPtr 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();
extra_info->SetBool(0, g_debug);
extra_info->SetString(1, g_logFile);
// This is included only in the Browser process, when building
// the libcefpythonapp library.
BrowserProcessHandler_OnRenderProcessThreadCreated(extra_info);
#endif // BROWSER_PROCESS
}
CefRefPtr CefPythonApp::GetPrintHandler() {
return print_handler_;
}
void CefPythonApp::OnScheduleMessagePumpWork(int64 delay_ms) {
#ifdef BROWSER_PROCESS
MainMessageLoopExternalPump* message_pump =\
MainMessageLoopExternalPump::Get();
if (message_pump) {
message_pump->OnScheduleMessagePumpWork(delay_ms);
}
#endif // BROWSER_PROCESS
}
// -----------------------------------------------------------------------------
// CefRenderProcessHandler
// -----------------------------------------------------------------------------
void CefPythonApp::OnRenderThreadCreated(CefRefPtr extra_info) {
if (extra_info->GetType(0) == VTYPE_BOOL) {
g_debug = extra_info->GetBool(0);
if (g_debug) {
FILELog::ReportingLevel() = logDEBUG;
} else {
// In reality this disables logging in LOG_DEBUG.h, as
// we're using only LOG_DEBUG macro.
FILELog::ReportingLevel() = logERROR;
}
}
if (extra_info->GetType(1) == VTYPE_STRING) {
g_logFile = extra_info->GetString(1).ToString();
}
if (!commandLineString_.empty()) {
// See comment in OnBeforeCommandLineProcessing().
DebugLog(commandLineString_.c_str());
commandLineString_ = "";
}
}
void CefPythonApp::OnWebKitInitialized() {
}
void CefPythonApp::OnBrowserCreated(CefRefPtr browser) {
}
void CefPythonApp::OnBrowserDestroyed(CefRefPtr browser) {
DebugLog("Renderer: OnBrowserDestroyed()");
RemoveJavascriptBindings(browser);
}
bool CefPythonApp::OnBeforeNavigation(CefRefPtr 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) {
DebugLog("Renderer: OnContextCreated()");
CefRefPtr 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 = "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) {
DebugLog("Renderer: OnContextReleased()");
CefRefPtr 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: OnProcessMessageReceived(): ";
logMessage.append(messageName.c_str());
DebugLog(logMessage.c_str());
CefRefPtr 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 {
DebugLog("Renderer: OnProcessMessageReceived(): invalid arguments,"\
" messageName=DoJavascriptBindings");
return false;
}
} else if (messageName == "ExecuteJavascriptCallback") {
if (args->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 {
DebugLog("Renderer: OnProcessMessageReceived: invalid arguments," \
"expected first argument to be a javascript callback " \
"(int)");
return false;
}
}
return true;
}
void CefPythonApp::SetJavascriptBindings(CefRefPtr 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)) {
DebugLog("Renderer: BindedFunctionExists() FAILED: "\
"objects dictionary not found");
return false;
}
CefRefPtr objects = \
jsBindings->GetDictionary("objects");
if (objects->HasKey(objectName)) {
if (!(objects->GetType(objectName) == VTYPE_DICTIONARY)) {
DebugLog("Renderer: BindedFunctionExists() FAILED: "\
"objects dictionary has invalid type");
return false;
}
CefRefPtr 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)) {
DebugLog("Renderer: BindedFunctionExists() FAILED: "\
"functions dictionary not found");
return false;
}
CefRefPtr 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.
DebugLog("Renderer: DoJavascriptBindingsForBrowser() FAILED: " \
"bindings not set");
return;
}
std::vector 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()) {
DebugLog("Renderer: DoJavascriptBindingsForBrowser() FAILED: " \
"frameIds.size() == 0");
return;
}
for (std::vector::iterator it = frameIds.begin(); \
it != frameIds.end(); ++it) {
if (*it frame = browser->GetFrame(*it);
if (!frame.get()) {
DebugLog("Renderer: DoJavascriptBindingsForBrowser() WARNING: " \
"GetFrame() failed");
continue;
}
CefRefPtr 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.
DebugLog("Renderer: DoJavascriptBindingsForFrame(): bindings not set");
return;
}
DebugLog("Renderer: DoJavascriptBindingsForFrame(): bindings are set");
if (!(jsBindings->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)) {
DebugLog("Renderer: DoJavascriptBindingsForFrame() FAILED: " \
"invalid data [1]");
return;
}
// A context must be explicitly entered before creating a
// V8 Object, Array, Function or Date asynchronously.
// NOTE: you cannot call CefV8Context::GetEnteredContext
// or GetCurrentContext when CefV8Context::InContext
// returns false, as it will result in crashes.
bool didEnterContext = false;
if (!CefV8Context::InContext()) {
if (!context->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.
DebugLog("Renderer: DoJavascriptBindingsForFrame() FAILED:"\
" V8 context provided by CEF is invalid");
return;
}
context->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())) {
DebugLog("Renderer: DoJavascriptBindingsForFrame() FAILED: " \
"invalid data [2]");
if (didEnterContext)
context->Exit();
return;
}
CefRefPtr v8Window = context->GetGlobal();
CefRefPtr v8Function;
CefRefPtr v8FunctionHandler(new V8FunctionHandler(this, 0));
// FUNCTIONS.
std::vector functionsVector;
if (!functions->GetKeys(functionsVector)) {
DebugLog("Renderer: DoJavascriptBindingsForFrame(): " \
"functions->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)) {
DebugLog("DoJavascriptBindingsForFrame() FAILED: " \
"v8Properties->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)) {
DebugLog("Renderer: DoJavascriptBindingsForFrame() FAILED: " \
"objects->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);
v8Window->SetValue(objectName, v8Object, V8_PROPERTY_ATTRIBUTE_NONE);
// METHODS.
if (!(objects->GetType(objectName) == VTYPE_DICTIONARY)) {
DebugLog("Renderer: DoJavascriptBindingsForFrame() FAILED: " \
"objects->GetType() != VTYPE_DICTIONARY");
if (didEnterContext)
context->Exit();
return;
}
CefRefPtr methods = \
objects->GetDictionary(objectName);
std::vector methodsVector;
if (!(methods->IsValid() && methods->GetKeys(methodsVector))) {
DebugLog("Renderer: DoJavascriptBindingsForFrame() FAILED: " \
"methods->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();
}