616 lines
17 KiB
C++
616 lines
17 KiB
C++
// Copyright (c) 2011 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.
|
|
|
|
#include "libcef/cef_context.h"
|
|
#include "libcef/browser_devtools_scheme_handler.h"
|
|
#include "libcef/browser_impl.h"
|
|
#include "libcef/browser_webkit_glue.h"
|
|
#include "libcef/v8_impl.h"
|
|
|
|
#include "base/bind.h"
|
|
#include "base/file_util.h"
|
|
#include "base/path_service.h"
|
|
#include "base/synchronization/waitable_event.h"
|
|
#include "ui/base/resource/resource_bundle.h"
|
|
#include "ui/base/ui_base_paths.h"
|
|
|
|
#if defined(OS_MACOSX) || defined(OS_WIN)
|
|
#include "crypto/nss_util.h"
|
|
#endif
|
|
|
|
#if defined(OS_MACOSX)
|
|
#include "base/mac/foundation_util.h"
|
|
#include "base/mac/mac_util.h"
|
|
#include "grit/webkit_resources.h"
|
|
#endif
|
|
|
|
#if defined(OS_WIN)
|
|
#include "base/win/resource_util.h"
|
|
#endif
|
|
|
|
// Global CefContext pointer
|
|
CefRefPtr<CefContext> _Context;
|
|
|
|
namespace {
|
|
|
|
// Used in multi-threaded message loop mode to observe shutdown of the UI
|
|
// thread.
|
|
class DestructionObserver : public MessageLoop::DestructionObserver {
|
|
public:
|
|
explicit DestructionObserver(base::WaitableEvent *event) : event_(event) {}
|
|
virtual void WillDestroyCurrentMessageLoop() {
|
|
MessageLoop::current()->RemoveDestructionObserver(this);
|
|
event_->Signal();
|
|
delete this;
|
|
}
|
|
private:
|
|
base::WaitableEvent *event_;
|
|
};
|
|
|
|
#if defined(OS_WIN)
|
|
|
|
// Helper method for retrieving a resource from a module.
|
|
base::StringPiece GetRawDataResource(HMODULE module, int resource_id) {
|
|
void* data_ptr;
|
|
size_t data_size;
|
|
return base::win::GetDataResourceFromModule(module, resource_id, &data_ptr,
|
|
&data_size)
|
|
? base::StringPiece(static_cast<char*>(data_ptr), data_size)
|
|
: base::StringPiece();
|
|
}
|
|
|
|
#endif // defined(OS_WIN)
|
|
|
|
} // namespace
|
|
|
|
|
|
class CefResourceBundleDelegate : public ui::ResourceBundle::Delegate {
|
|
public:
|
|
CefResourceBundleDelegate(CefContext* context)
|
|
: context_(context),
|
|
allow_pack_file_load_(false) {
|
|
}
|
|
|
|
void set_allow_pack_file_load(bool val) { allow_pack_file_load_ = val; }
|
|
|
|
private:
|
|
virtual base::FilePath GetPathForResourcePack(
|
|
const base::FilePath& pack_path,
|
|
ui::ScaleFactor scale_factor) OVERRIDE {
|
|
// Only allow the cef pack file to load.
|
|
if (!context_->settings().pack_loading_disabled && allow_pack_file_load_)
|
|
return pack_path;
|
|
return base::FilePath();
|
|
}
|
|
|
|
virtual base::FilePath GetPathForLocalePack(
|
|
const base::FilePath& pack_path,
|
|
const std::string& locale) OVERRIDE {
|
|
if (!context_->settings().pack_loading_disabled)
|
|
return pack_path;
|
|
return base::FilePath();
|
|
}
|
|
|
|
virtual gfx::Image GetImageNamed(int resource_id) OVERRIDE {
|
|
return gfx::Image();
|
|
}
|
|
|
|
virtual gfx::Image GetNativeImageNamed(
|
|
int resource_id,
|
|
ui::ResourceBundle::ImageRTL rtl) OVERRIDE {
|
|
return gfx::Image();
|
|
}
|
|
|
|
virtual base::RefCountedStaticMemory* LoadDataResourceBytes(
|
|
int resource_id,
|
|
ui::ScaleFactor scale_factor) OVERRIDE {
|
|
return NULL;
|
|
}
|
|
|
|
virtual bool GetRawDataResource(int resource_id,
|
|
ui::ScaleFactor scale_factor,
|
|
base::StringPiece* value) OVERRIDE {
|
|
return false;
|
|
}
|
|
|
|
virtual bool GetLocalizedString(int message_id, string16* value) OVERRIDE {
|
|
return false;
|
|
}
|
|
|
|
virtual scoped_ptr<gfx::Font> GetFont(
|
|
ui::ResourceBundle::FontStyle style) OVERRIDE {
|
|
return scoped_ptr<gfx::Font>();
|
|
}
|
|
|
|
// CefContext pointer is guaranteed to outlive this object.
|
|
CefContext* context_;
|
|
bool allow_pack_file_load_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(CefResourceBundleDelegate);
|
|
};
|
|
|
|
|
|
bool CefInitialize(const CefSettings& settings, CefRefPtr<CefApp> application) {
|
|
// Return true if the global context already exists.
|
|
if (_Context.get())
|
|
return true;
|
|
|
|
if (settings.size != sizeof(cef_settings_t)) {
|
|
NOTREACHED() << "invalid CefSettings structure size";
|
|
return false;
|
|
}
|
|
|
|
// Create the new global context object.
|
|
_Context = new CefContext();
|
|
|
|
// Initialize the global context.
|
|
return _Context->Initialize(settings, application);
|
|
}
|
|
|
|
void CefShutdown() {
|
|
// Verify that the context is in a valid state.
|
|
if (!CONTEXT_STATE_VALID()) {
|
|
NOTREACHED() << "context not valid";
|
|
return;
|
|
}
|
|
|
|
// Must always be called on the same thread as Initialize.
|
|
if (!_Context->process()->CalledOnValidThread()) {
|
|
NOTREACHED() << "called on invalid thread";
|
|
return;
|
|
}
|
|
|
|
// Shut down the global context. This will block until shutdown is complete.
|
|
_Context->Shutdown();
|
|
|
|
// Delete the global context object.
|
|
_Context = NULL;
|
|
}
|
|
|
|
void CefDoMessageLoopWork() {
|
|
// Verify that the context is in a valid state.
|
|
if (!CONTEXT_STATE_VALID()) {
|
|
NOTREACHED() << "context not valid";
|
|
return;
|
|
}
|
|
|
|
// Must always be called on the same thread as Initialize.
|
|
if (!_Context->process()->CalledOnValidThread()) {
|
|
NOTREACHED() << "called on invalid thread";
|
|
return;
|
|
}
|
|
|
|
_Context->process()->DoMessageLoopIteration();
|
|
}
|
|
|
|
void CefRunMessageLoop() {
|
|
// Verify that the context is in a valid state.
|
|
if (!CONTEXT_STATE_VALID()) {
|
|
NOTREACHED() << "context not valid";
|
|
return;
|
|
}
|
|
|
|
// Must always be called on the same thread as Initialize.
|
|
if (!_Context->process()->CalledOnValidThread()) {
|
|
NOTREACHED() << "called on invalid thread";
|
|
return;
|
|
}
|
|
|
|
_Context->process()->RunMessageLoop();
|
|
}
|
|
|
|
void CefQuitMessageLoop() {
|
|
// Verify that the context is in a valid state.
|
|
if (!CONTEXT_STATE_VALID()) {
|
|
NOTREACHED() << "context not valid";
|
|
return;
|
|
}
|
|
|
|
// Must always be called on the same thread as Initialize.
|
|
if (!_Context->process()->CalledOnValidThread()) {
|
|
NOTREACHED() << "called on invalid thread";
|
|
return;
|
|
}
|
|
|
|
_Context->process()->QuitMessageLoop();
|
|
}
|
|
|
|
void CefSetOSModalLoop(bool osModalLoop) {
|
|
#if defined(OS_WIN)
|
|
// Verify that the context is in a valid state.
|
|
if (!CONTEXT_STATE_VALID()) {
|
|
NOTREACHED() << "context not valid";
|
|
return;
|
|
}
|
|
|
|
if (CefThread::CurrentlyOn(CefThread::UI)) {
|
|
MessageLoop::current()->set_os_modal_loop(osModalLoop);
|
|
} else {
|
|
CefThread::PostTask(CefThread::UI, FROM_HERE,
|
|
base::Bind(CefSetOSModalLoop, osModalLoop));
|
|
}
|
|
#endif // defined(OS_WIN)
|
|
}
|
|
|
|
|
|
// CefContext
|
|
|
|
CefContext::CefContext()
|
|
: initialized_(false),
|
|
shutting_down_(false),
|
|
request_context_(NULL),
|
|
current_webviewhost_(NULL),
|
|
dev_tools_client_count_(0) {
|
|
}
|
|
|
|
CefContext::~CefContext() {
|
|
if (!shutting_down_)
|
|
Shutdown();
|
|
}
|
|
|
|
bool CefContext::Initialize(const CefSettings& settings,
|
|
CefRefPtr<CefApp> application) {
|
|
settings_ = settings;
|
|
application_ = application;
|
|
|
|
cache_path_ = base::FilePath(CefString(&settings.cache_path));
|
|
if (!cache_path_.empty() &&
|
|
!file_util::PathExists(cache_path_) &&
|
|
!file_util::CreateDirectory(cache_path_)) {
|
|
NOTREACHED() << "The cache_path directory could not be created";
|
|
cache_path_.clear();
|
|
}
|
|
if (cache_path_.empty()) {
|
|
// Create and use a temporary directory.
|
|
if (cache_temp_dir_.CreateUniqueTempDir()) {
|
|
cache_path_ = cache_temp_dir_.path();
|
|
} else {
|
|
NOTREACHED() << "Failed to create temporary cache_path directory";
|
|
}
|
|
}
|
|
|
|
#if defined(OS_MACOSX) || defined(OS_WIN)
|
|
// We want to be sure to init NSPR on the main thread.
|
|
crypto::EnsureNSPRInit();
|
|
#endif
|
|
|
|
process_.reset(new CefProcess(settings_.multi_threaded_message_loop));
|
|
process_->CreateChildThreads();
|
|
|
|
initialized_ = true;
|
|
|
|
// Perform DevTools scheme registration when CEF initialization is complete.
|
|
CefThread::PostTask(CefThread::UI, FROM_HERE,
|
|
base::Bind(&RegisterDevToolsSchemeHandler));
|
|
|
|
return true;
|
|
}
|
|
|
|
void CefContext::Shutdown() {
|
|
// Must always be called on the same thread as Initialize.
|
|
DCHECK(process_->CalledOnValidThread());
|
|
|
|
shutting_down_ = true;
|
|
|
|
if (settings_.multi_threaded_message_loop) {
|
|
// Events that will be used to signal when shutdown is complete. Start in
|
|
// non-signaled mode so that the event will block.
|
|
base::WaitableEvent browser_shutdown_event(false, false);
|
|
base::WaitableEvent uithread_shutdown_event(false, false);
|
|
|
|
// Finish shutdown on the UI thread.
|
|
CefThread::PostTask(CefThread::UI, FROM_HERE,
|
|
base::Bind(&CefContext::UIT_FinishShutdown, this,
|
|
&browser_shutdown_event, &uithread_shutdown_event));
|
|
|
|
// Block until browser shutdown is complete.
|
|
browser_shutdown_event.Wait();
|
|
|
|
// Delete the process to destroy the child threads.
|
|
process_.reset(NULL);
|
|
|
|
// Block until UI thread shutdown is complete.
|
|
uithread_shutdown_event.Wait();
|
|
} else {
|
|
// Finish shutdown on the current thread, which should be the UI thread.
|
|
UIT_FinishShutdown(NULL, NULL);
|
|
|
|
// Delete the process to destroy the child threads.
|
|
process_.reset(NULL);
|
|
}
|
|
}
|
|
|
|
int CefContext::GetNextBrowserID() {
|
|
return next_browser_id_.GetNext() + 1;
|
|
}
|
|
|
|
void CefContext::AddBrowser(CefRefPtr<CefBrowserImpl> browser) {
|
|
AutoLock lock_scope(this);
|
|
|
|
browserlist_.push_back(browser);
|
|
}
|
|
|
|
void CefContext::RemoveBrowser(CefRefPtr<CefBrowserImpl> browser) {
|
|
bool empty = false;
|
|
|
|
{
|
|
AutoLock lock_scope(this);
|
|
|
|
BrowserList::iterator it = browserlist_.begin();
|
|
for (; it != browserlist_.end(); ++it) {
|
|
if (it->get() == browser.get()) {
|
|
browserlist_.erase(it);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (browserlist_.empty())
|
|
empty = true;
|
|
}
|
|
|
|
if (empty) {
|
|
if (CefThread::CurrentlyOn(CefThread::UI)) {
|
|
webkit_glue::ClearCache();
|
|
} else {
|
|
CefThread::PostTask(CefThread::UI, FROM_HERE,
|
|
base::Bind(webkit_glue::ClearCache));
|
|
}
|
|
}
|
|
}
|
|
|
|
CefRefPtr<CefBrowserImpl> CefContext::GetBrowserByID(int id) {
|
|
AutoLock lock_scope(this);
|
|
|
|
BrowserList::const_iterator it = browserlist_.begin();
|
|
for (; it != browserlist_.end(); ++it) {
|
|
if (it->get()->browser_id() == id)
|
|
return it->get();
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void CefContext::InitializeResourceBundle() {
|
|
#if !defined(OS_WIN)
|
|
base::FilePath chrome_pak_file;
|
|
#endif
|
|
base::FilePath devtools_pak_file, locales_dir;
|
|
|
|
if (!settings_.pack_loading_disabled) {
|
|
base::FilePath resources_dir_path;
|
|
if (settings_.resources_dir_path.length > 0)
|
|
resources_dir_path = base::FilePath(CefString(&settings_.resources_dir_path));
|
|
if (resources_dir_path.empty())
|
|
resources_dir_path = GetResourcesFilePath();
|
|
|
|
if (!resources_dir_path.empty()) {
|
|
#if !defined(OS_WIN)
|
|
chrome_pak_file = resources_dir_path.Append(
|
|
FILE_PATH_LITERAL("chrome.pak"));
|
|
#endif
|
|
devtools_pak_file = resources_dir_path.Append(
|
|
FILE_PATH_LITERAL("devtools_resources.pak"));
|
|
}
|
|
|
|
if (settings_.locales_dir_path.length > 0)
|
|
locales_dir = base::FilePath(CefString(&settings_.locales_dir_path));
|
|
|
|
if (!locales_dir.empty())
|
|
PathService::Override(ui::DIR_LOCALES, locales_dir);
|
|
}
|
|
|
|
std::string locale_str = locale();
|
|
if (locale_str.empty())
|
|
locale_str = "en-US";
|
|
|
|
resource_bundle_delegate_.reset(new CefResourceBundleDelegate(this));
|
|
const std::string loaded_locale =
|
|
ui::ResourceBundle::InitSharedInstanceWithLocale(
|
|
locale_str, resource_bundle_delegate_.get());
|
|
if (!settings_.pack_loading_disabled) {
|
|
CHECK(!loaded_locale.empty()) << "Locale could not be found for "
|
|
<< locale_str;
|
|
|
|
resource_bundle_delegate_->set_allow_pack_file_load(true);
|
|
|
|
// The chrome.pak file is required on non-Windows platforms.
|
|
#if !defined(OS_WIN)
|
|
if (file_util::PathExists(chrome_pak_file)) {
|
|
ResourceBundle::GetSharedInstance().AddDataPackFromPath(
|
|
chrome_pak_file, ui::SCALE_FACTOR_NONE);
|
|
} else {
|
|
NOTREACHED() << "Could not load chrome.pak";
|
|
}
|
|
#endif
|
|
|
|
// The devtools_resources.pak file is optional.
|
|
if (file_util::PathExists(devtools_pak_file)) {
|
|
ResourceBundle::GetSharedInstance().AddDataPackFromPath(
|
|
devtools_pak_file, ui::SCALE_FACTOR_NONE);
|
|
}
|
|
|
|
resource_bundle_delegate_->set_allow_pack_file_load(false);
|
|
}
|
|
}
|
|
|
|
void CefContext::CleanupResourceBundle() {
|
|
ResourceBundle::CleanupSharedInstance();
|
|
resource_bundle_delegate_.reset(NULL);
|
|
}
|
|
|
|
string16 CefContext::GetLocalizedString(int message_id) const {
|
|
string16 value;
|
|
|
|
if (application_.get()) {
|
|
CefRefPtr<CefResourceBundleHandler> handler =
|
|
application_->GetResourceBundleHandler();
|
|
if (handler.get()) {
|
|
CefString cef_str;
|
|
if (handler->GetLocalizedString(message_id, cef_str))
|
|
value = cef_str;
|
|
}
|
|
}
|
|
|
|
if (value.empty() && !settings_.pack_loading_disabled)
|
|
value = ResourceBundle::GetSharedInstance().GetLocalizedString(message_id);
|
|
|
|
if (value.empty())
|
|
LOG(ERROR) << "No localized string available for id " << message_id;
|
|
|
|
return value;
|
|
}
|
|
|
|
base::StringPiece CefContext::GetDataResource(int resource_id) const {
|
|
base::StringPiece value;
|
|
|
|
if (application_.get()) {
|
|
CefRefPtr<CefResourceBundleHandler> handler =
|
|
application_->GetResourceBundleHandler();
|
|
if (handler.get()) {
|
|
void* data = NULL;
|
|
size_t data_size = 0;
|
|
if (handler->GetDataResource(resource_id, data, data_size))
|
|
value = base::StringPiece(static_cast<char*>(data), data_size);
|
|
}
|
|
}
|
|
|
|
#if defined(OS_WIN)
|
|
if (value.empty()) {
|
|
base::FilePath file_path;
|
|
HMODULE hModule = NULL;
|
|
|
|
// Try to load the resource from the DLL.
|
|
if (PathService::Get(base::FILE_MODULE, &file_path))
|
|
hModule = ::GetModuleHandle(file_path.value().c_str());
|
|
if (!hModule)
|
|
hModule = ::GetModuleHandle(NULL);
|
|
value = ::GetRawDataResource(hModule, resource_id);
|
|
}
|
|
#elif defined(OS_MACOSX)
|
|
if (value.empty()) {
|
|
switch (resource_id) {
|
|
case IDR_BROKENIMAGE: {
|
|
// Use webkit's broken image icon (16x16)
|
|
static std::string broken_image_data;
|
|
if (broken_image_data.empty()) {
|
|
base::FilePath path = GetResourcesFilePath();
|
|
// In order to match WebKit's colors for the missing image, we have to
|
|
// use a PNG. The GIF doesn't have the color range needed to correctly
|
|
// match the TIFF they use in Safari.
|
|
path = path.AppendASCII("missingImage.png");
|
|
bool success = file_util::ReadFileToString(path, &broken_image_data);
|
|
if (!success) {
|
|
LOG(FATAL) << "Failed reading: " << path.value();
|
|
}
|
|
}
|
|
value = broken_image_data;
|
|
break;
|
|
}
|
|
case IDR_TEXTAREA_RESIZER: {
|
|
// Use webkit's text area resizer image.
|
|
static std::string resize_corner_data;
|
|
if (resize_corner_data.empty()) {
|
|
base::FilePath path = GetResourcesFilePath();
|
|
path = path.AppendASCII("textAreaResizeCorner.png");
|
|
bool success = file_util::ReadFileToString(path, &resize_corner_data);
|
|
if (!success) {
|
|
LOG(FATAL) << "Failed reading: " << path.value();
|
|
}
|
|
}
|
|
value = resize_corner_data;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
#endif // defined(OS_MACOSX)
|
|
|
|
if (value.empty() && !settings_.pack_loading_disabled)
|
|
value = ResourceBundle::GetSharedInstance().GetRawDataResource(resource_id);
|
|
|
|
if (value.empty())
|
|
LOG(ERROR) << "No data resource available for id " << resource_id;
|
|
|
|
return value;
|
|
}
|
|
|
|
base::FilePath CefContext::GetResourcesFilePath() const {
|
|
#if defined(OS_MACOSX)
|
|
// Start out with the path to the running executable.
|
|
base::FilePath execPath;
|
|
PathService::Get(base::FILE_EXE, &execPath);
|
|
|
|
// Get the main bundle path.
|
|
base::FilePath bundlePath = base::mac::GetAppBundlePath(execPath);
|
|
|
|
// Go into the Contents/Resources directory.
|
|
return bundlePath.Append(FILE_PATH_LITERAL("Contents"))
|
|
.Append(FILE_PATH_LITERAL("Resources"));
|
|
#else
|
|
base::FilePath pak_dir;
|
|
PathService::Get(base::DIR_MODULE, &pak_dir);
|
|
return pak_dir;
|
|
#endif
|
|
}
|
|
|
|
std::string CefContext::locale() const {
|
|
std::string localeStr = CefString(&settings_.locale);
|
|
if (!localeStr.empty())
|
|
return localeStr;
|
|
|
|
return "en-US";
|
|
}
|
|
|
|
void CefContext::UIT_FinishShutdown(
|
|
base::WaitableEvent* browser_shutdown_event,
|
|
base::WaitableEvent* uithread_shutdown_event) {
|
|
DCHECK(CefThread::CurrentlyOn(CefThread::UI));
|
|
|
|
BrowserList list;
|
|
|
|
{
|
|
AutoLock lock_scope(this);
|
|
if (!browserlist_.empty()) {
|
|
list = browserlist_;
|
|
browserlist_.clear();
|
|
}
|
|
}
|
|
|
|
// Destroy any remaining browser windows.
|
|
if (!list.empty()) {
|
|
BrowserList::iterator it = list.begin();
|
|
for (; it != list.end(); ++it)
|
|
(*it)->UIT_DestroyBrowser();
|
|
}
|
|
|
|
if (uithread_shutdown_event) {
|
|
// The destruction observer will signal the UI thread shutdown event when
|
|
// the UI thread has been destroyed.
|
|
MessageLoop::current()->AddDestructionObserver(
|
|
new DestructionObserver(uithread_shutdown_event));
|
|
|
|
// Signal the browser shutdown event now.
|
|
browser_shutdown_event->Signal();
|
|
}
|
|
}
|
|
|
|
void CefContext::UIT_DevToolsClientCreated() {
|
|
DCHECK(CefThread::CurrentlyOn(CefThread::UI));
|
|
++dev_tools_client_count_;
|
|
}
|
|
|
|
void CefContext::UIT_DevToolsClientDestroyed() {
|
|
DCHECK(CefThread::CurrentlyOn(CefThread::UI));
|
|
--dev_tools_client_count_;
|
|
if (dev_tools_client_count_ == 0) {
|
|
if (settings_.uncaught_exception_stack_size > 0) {
|
|
v8::V8::SetCaptureStackTraceForUncaughtExceptions(true,
|
|
settings_.uncaught_exception_stack_size,
|
|
v8::StackTrace::kDetailed);
|
|
}
|
|
}
|
|
}
|