// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights // reserved. Use of this source code is governed by a BSD-style license that // can be found in the LICENSE file. // This file is shared by cefclient and cef_unittests so don't include using // a qualified path. #include "client_app.h" // NOLINT(build/include) #include #include "include/cef_cookie.h" #include "include/cef_process_message.h" #include "include/cef_task.h" #include "include/cef_v8.h" #include "util.h" // NOLINT(build/include) namespace { // Forward declarations. void SetList(CefRefPtr source, CefRefPtr target); void SetList(CefRefPtr source, CefRefPtr target); // Transfer a V8 value to a List index. void SetListValue(CefRefPtr list, int index, CefRefPtr value) { if (value->IsArray()) { CefRefPtr new_list = CefListValue::Create(); SetList(value, new_list); list->SetList(index, new_list); } else if (value->IsString()) { list->SetString(index, value->GetStringValue()); } else if (value->IsBool()) { list->SetBool(index, value->GetBoolValue()); } else if (value->IsInt()) { list->SetInt(index, value->GetIntValue()); } else if (value->IsDouble()) { list->SetDouble(index, value->GetDoubleValue()); } } // Transfer a V8 array to a List. void SetList(CefRefPtr source, CefRefPtr target) { ASSERT(source->IsArray()); int arg_length = source->GetArrayLength(); if (arg_length == 0) return; // Start with null types in all spaces. target->SetSize(arg_length); for (int i = 0; i < arg_length; ++i) SetListValue(target, i, source->GetValue(i)); } // Transfer a List value to a V8 array index. void SetListValue(CefRefPtr list, int index, CefRefPtr value) { CefRefPtr new_value; CefValueType type = value->GetType(index); switch (type) { case VTYPE_LIST: { CefRefPtr list = value->GetList(index); new_value = CefV8Value::CreateArray(list->GetSize()); SetList(list, new_value); } break; case VTYPE_BOOL: new_value = CefV8Value::CreateBool(value->GetBool(index)); break; case VTYPE_DOUBLE: new_value = CefV8Value::CreateDouble(value->GetDouble(index)); break; case VTYPE_INT: new_value = CefV8Value::CreateInt(value->GetInt(index)); break; case VTYPE_STRING: new_value = CefV8Value::CreateString(value->GetString(index)); break; default: break; } if (new_value.get()) { list->SetValue(index, new_value); } else { list->SetValue(index, CefV8Value::CreateNull()); } } // Transfer a List to a V8 array. void SetList(CefRefPtr source, CefRefPtr target) { ASSERT(target->IsArray()); int arg_length = source->GetSize(); if (arg_length == 0) return; for (int i = 0; i < arg_length; ++i) SetListValue(target, i, source); } // Handles the native implementation for the client_app extension. class ClientAppExtensionHandler : public CefV8Handler { public: explicit ClientAppExtensionHandler(CefRefPtr client_app) : client_app_(client_app) { } virtual bool Execute(const CefString& name, CefRefPtr object, const CefV8ValueList& arguments, CefRefPtr& retval, CefString& exception) { bool handled = false; if (name == "sendMessage") { // Send a message to the browser process. if ((arguments.size() == 1 || arguments.size() == 2) && arguments[0]->IsString()) { CefRefPtr browser = CefV8Context::GetCurrentContext()->GetBrowser(); ASSERT(browser.get()); CefString name = arguments[0]->GetStringValue(); if (!name.empty()) { CefRefPtr message = CefProcessMessage::Create(name); // Translate the arguments, if any. if (arguments.size() == 2 && arguments[1]->IsArray()) SetList(arguments[1], message->GetArgumentList()); browser->SendProcessMessage(PID_BROWSER, message); handled = true; } } } else if (name == "setMessageCallback") { // Set a message callback. if (arguments.size() == 2 && arguments[0]->IsString() && arguments[1]->IsFunction()) { std::string name = arguments[0]->GetStringValue(); CefRefPtr context = CefV8Context::GetCurrentContext(); int browser_id = context->GetBrowser()->GetIdentifier(); client_app_->SetMessageCallback(name, browser_id, context, arguments[1]); handled = true; } } else if (name == "removeMessageCallback") { // Remove a message callback. if (arguments.size() == 1 && arguments[0]->IsString()) { std::string name = arguments[0]->GetStringValue(); CefRefPtr context = CefV8Context::GetCurrentContext(); int browser_id = context->GetBrowser()->GetIdentifier(); bool removed = client_app_->RemoveMessageCallback(name, browser_id); retval = CefV8Value::CreateBool(removed); handled = true; } } if (!handled) exception = "Invalid method arguments"; return true; } private: CefRefPtr client_app_; IMPLEMENT_REFCOUNTING(ClientAppExtensionHandler); }; } // namespace ClientApp::ClientApp() : proxy_type_(CEF_PROXY_TYPE_DIRECT) { CreateBrowserDelegates(browser_delegates_); CreateRenderDelegates(render_delegates_); // Default schemes that support cookies. cookieable_schemes_.push_back("http"); cookieable_schemes_.push_back("https"); } void ClientApp::SetMessageCallback(const std::string& message_name, int browser_id, CefRefPtr context, CefRefPtr function) { ASSERT(CefCurrentlyOn(TID_RENDERER)); callback_map_.insert( std::make_pair(std::make_pair(message_name, browser_id), std::make_pair(context, function))); } bool ClientApp::RemoveMessageCallback(const std::string& message_name, int browser_id) { ASSERT(CefCurrentlyOn(TID_RENDERER)); CallbackMap::iterator it = callback_map_.find(std::make_pair(message_name, browser_id)); if (it != callback_map_.end()) { callback_map_.erase(it); return true; } return false; } void ClientApp::OnContextInitialized() { // Register cookieable schemes with the global cookie manager. CefRefPtr manager = CefCookieManager::GetGlobalManager(); ASSERT(manager.get()); manager->SetSupportedSchemes(cookieable_schemes_); BrowserDelegateSet::iterator it = browser_delegates_.begin(); for (; it != browser_delegates_.end(); ++it) (*it)->OnContextInitialized(this); } void ClientApp::OnBeforeChildProcessLaunch( CefRefPtr command_line) { BrowserDelegateSet::iterator it = browser_delegates_.begin(); for (; it != browser_delegates_.end(); ++it) (*it)->OnBeforeChildProcessLaunch(this, command_line); } void ClientApp::OnRenderProcessThreadCreated( CefRefPtr extra_info) { BrowserDelegateSet::iterator it = browser_delegates_.begin(); for (; it != browser_delegates_.end(); ++it) (*it)->OnRenderProcessThreadCreated(this, extra_info); } void ClientApp::GetProxyForUrl(const CefString& url, CefProxyInfo& proxy_info) { proxy_info.proxyType = proxy_type_; if (!proxy_config_.empty()) CefString(&proxy_info.proxyList) = proxy_config_; } void ClientApp::OnRenderThreadCreated(CefRefPtr extra_info) { RenderDelegateSet::iterator it = render_delegates_.begin(); for (; it != render_delegates_.end(); ++it) (*it)->OnRenderThreadCreated(this, extra_info); } void ClientApp::OnWebKitInitialized() { // Register the client_app extension. std::string app_code = "var app;" "if (!app)" " app = {};" "(function() {" " app.sendMessage = function(name, arguments) {" " native function sendMessage();" " return sendMessage(name, arguments);" " };" " app.setMessageCallback = function(name, callback) {" " native function setMessageCallback();" " return setMessageCallback(name, callback);" " };" " app.removeMessageCallback = function(name) {" " native function removeMessageCallback();" " return removeMessageCallback(name);" " };" "})();"; CefRegisterExtension("v8/app", app_code, new ClientAppExtensionHandler(this)); RenderDelegateSet::iterator it = render_delegates_.begin(); for (; it != render_delegates_.end(); ++it) (*it)->OnWebKitInitialized(this); } void ClientApp::OnBrowserCreated(CefRefPtr browser) { RenderDelegateSet::iterator it = render_delegates_.begin(); for (; it != render_delegates_.end(); ++it) (*it)->OnBrowserCreated(this, browser); } void ClientApp::OnBrowserDestroyed(CefRefPtr browser) { RenderDelegateSet::iterator it = render_delegates_.begin(); for (; it != render_delegates_.end(); ++it) (*it)->OnBrowserDestroyed(this, browser); } bool ClientApp::OnBeforeNavigation(CefRefPtr browser, CefRefPtr frame, CefRefPtr request, NavigationType navigation_type, bool is_redirect) { RenderDelegateSet::iterator it = render_delegates_.begin(); for (; it != render_delegates_.end(); ++it) { if ((*it)->OnBeforeNavigation(this, browser, frame, request, navigation_type, is_redirect)) { return true; } } return false; } void ClientApp::OnContextCreated(CefRefPtr browser, CefRefPtr frame, CefRefPtr context) { RenderDelegateSet::iterator it = render_delegates_.begin(); for (; it != render_delegates_.end(); ++it) (*it)->OnContextCreated(this, browser, frame, context); } void ClientApp::OnContextReleased(CefRefPtr browser, CefRefPtr frame, CefRefPtr context) { RenderDelegateSet::iterator it = render_delegates_.begin(); for (; it != render_delegates_.end(); ++it) (*it)->OnContextReleased(this, browser, frame, context); // Remove any JavaScript callbacks registered for the context that has been // released. if (!callback_map_.empty()) { CallbackMap::iterator it = callback_map_.begin(); for (; it != callback_map_.end();) { if (it->second.first->IsSame(context)) callback_map_.erase(it++); else ++it; } } } void ClientApp::OnUncaughtException(CefRefPtr browser, CefRefPtr frame, CefRefPtr context, CefRefPtr exception, CefRefPtr stackTrace) { RenderDelegateSet::iterator it = render_delegates_.begin(); for (; it != render_delegates_.end(); ++it) { (*it)->OnUncaughtException(this, browser, frame, context, exception, stackTrace); } } void ClientApp::OnWorkerContextCreated(int worker_id, const CefString& url, CefRefPtr context) { RenderDelegateSet::iterator it = render_delegates_.begin(); for (; it != render_delegates_.end(); ++it) (*it)->OnWorkerContextCreated(this, worker_id, url, context); } void ClientApp::OnWorkerContextReleased(int worker_id, const CefString& url, CefRefPtr context) { RenderDelegateSet::iterator it = render_delegates_.begin(); for (; it != render_delegates_.end(); ++it) (*it)->OnWorkerContextReleased(this, worker_id, url, context); } void ClientApp::OnWorkerUncaughtException( int worker_id, const CefString& url, CefRefPtr context, CefRefPtr exception, CefRefPtr stackTrace) { RenderDelegateSet::iterator it = render_delegates_.begin(); for (; it != render_delegates_.end(); ++it) { (*it)->OnWorkerUncaughtException(this, worker_id, url, context, exception, stackTrace); } } void ClientApp::OnFocusedNodeChanged(CefRefPtr browser, CefRefPtr frame, CefRefPtr node) { RenderDelegateSet::iterator it = render_delegates_.begin(); for (; it != render_delegates_.end(); ++it) (*it)->OnFocusedNodeChanged(this, browser, frame, node); } bool ClientApp::OnProcessMessageReceived( CefRefPtr browser, CefProcessId source_process, CefRefPtr message) { ASSERT(source_process == PID_BROWSER); bool handled = false; RenderDelegateSet::iterator it = render_delegates_.begin(); for (; it != render_delegates_.end() && !handled; ++it) { handled = (*it)->OnProcessMessageReceived(this, browser, source_process, message); } if (handled) return true; // Execute the registered JavaScript callback if any. if (!callback_map_.empty()) { CefString message_name = message->GetName(); CallbackMap::const_iterator it = callback_map_.find( std::make_pair(message_name.ToString(), browser->GetIdentifier())); if (it != callback_map_.end()) { // Keep a local reference to the objects. The callback may remove itself // from the callback map. CefRefPtr context = it->second.first; CefRefPtr callback = it->second.second; // Enter the context. context->Enter(); CefV8ValueList arguments; // First argument is the message name. arguments.push_back(CefV8Value::CreateString(message_name)); // Second argument is the list of message arguments. CefRefPtr list = message->GetArgumentList(); CefRefPtr args = CefV8Value::CreateArray(list->GetSize()); SetList(list, args); arguments.push_back(args); // Execute the callback. CefRefPtr retval = callback->ExecuteFunction(NULL, arguments); if (retval.get()) { if (retval->IsBool()) handled = retval->GetBoolValue(); } // Exit the context. context->Exit(); } } return handled; }