From 7ffa03798801c8dca62b30a2c42ccc31e4af8933 Mon Sep 17 00:00:00 2001 From: Marshall Greenblatt Date: Sat, 24 Jul 2010 01:13:55 +0000 Subject: [PATCH] - Move to a unified thread model for CEF based on the CEF2 implementation. - Add the ability to post user-defined tasks for execution on CEF threads (issue #25). - Update the CEF swap image. git-svn-id: https://chromiumembedded.googlecode.com/svn/trunk@90 5089003a-bbd8-11dd-ad1f-f1f9622dbc98 --- cef.gyp | 19 +- include/cef.h | 34 ++ include/cef_capi.h | 31 ++ include/cef_types.h | 8 + libcef/browser_appcache_system.cc | 3 - libcef/browser_impl.cc | 55 ++- libcef/browser_impl.h | 2 +- libcef/browser_impl_win.cc | 22 +- libcef/browser_request_context.cc | 7 + libcef/browser_request_context.h | 5 +- libcef/browser_resource_loader_bridge.cc | 203 +------- libcef/browser_resource_loader_bridge.h | 15 - libcef/browser_webview_delegate.cc | 6 +- libcef/browser_webview_delegate_win.cc | 2 +- libcef/cef_context.cc | 322 ++++++++++++ libcef/cef_context.h | 81 ++++ libcef/cef_process.cc | 134 +++++ libcef/cef_process.h | 117 +++++ libcef/cef_process_io_thread.cc | 60 +++ libcef/cef_process_io_thread.h | 40 ++ libcef/cef_process_sub_thread.cc | 44 ++ libcef/cef_process_sub_thread.h | 35 ++ libcef/cef_process_ui_thread.cc | 153 ++++++ libcef/cef_process_ui_thread.h | 42 ++ libcef/cef_thread.cc | 210 ++++++++ libcef/cef_thread.h | 185 +++++++ libcef/context.cc | 593 ----------------------- libcef/context.h | 110 ----- libcef/scheme_impl.cc | 9 +- libcef/v8_impl.cc | 4 +- libcef_dll/cpptoc/task_cpptoc.cc | 40 ++ libcef_dll/cpptoc/task_cpptoc.h | 34 ++ libcef_dll/ctocpp/task_ctocpp.cc | 30 ++ libcef_dll/ctocpp/task_ctocpp.h | 39 ++ libcef_dll/libcef_dll.cc | 26 + libcef_dll/wrapper/libcef_dll_wrapper.cc | 18 + tests/cefclient/cefclient.cpp | 35 +- tests/cefclient/cefclient.rc | 2 +- tests/cefclient/res/logo.jpg | Bin 9582 -> 0 bytes tests/cefclient/res/logo.png | Bin 0 -> 19879 bytes tests/cefclient/thread_util.h | 58 --- tools/cef_parser.py | 1 + 42 files changed, 1800 insertions(+), 1034 deletions(-) create mode 100644 libcef/cef_context.cc create mode 100644 libcef/cef_context.h create mode 100644 libcef/cef_process.cc create mode 100644 libcef/cef_process.h create mode 100644 libcef/cef_process_io_thread.cc create mode 100644 libcef/cef_process_io_thread.h create mode 100644 libcef/cef_process_sub_thread.cc create mode 100644 libcef/cef_process_sub_thread.h create mode 100644 libcef/cef_process_ui_thread.cc create mode 100644 libcef/cef_process_ui_thread.h create mode 100644 libcef/cef_thread.cc create mode 100644 libcef/cef_thread.h delete mode 100644 libcef/context.cc delete mode 100644 libcef/context.h create mode 100644 libcef_dll/cpptoc/task_cpptoc.cc create mode 100644 libcef_dll/cpptoc/task_cpptoc.h create mode 100644 libcef_dll/ctocpp/task_ctocpp.cc create mode 100644 libcef_dll/ctocpp/task_ctocpp.h delete mode 100644 tests/cefclient/res/logo.jpg create mode 100644 tests/cefclient/res/logo.png delete mode 100644 tests/cefclient/thread_util.h diff --git a/cef.gyp b/cef.gyp index eba994d7..b6c926e7 100644 --- a/cef.gyp +++ b/cef.gyp @@ -46,7 +46,6 @@ 'tests/cefclient/scheme_test.h', 'tests/cefclient/string_util.cpp', 'tests/cefclient/string_util.h', - 'tests/cefclient/thread_util.h', 'tests/cefclient/uiplugin.cpp', 'tests/cefclient/uiplugin.h', 'tests/cefclient/uiplugin_test.cpp', @@ -208,6 +207,8 @@ 'libcef_dll/ctocpp/scheme_handler_ctocpp.h', 'libcef_dll/ctocpp/scheme_handler_factory_ctocpp.cc', 'libcef_dll/ctocpp/scheme_handler_factory_ctocpp.h', + 'libcef_dll/ctocpp/task_ctocpp.cc', + 'libcef_dll/ctocpp/task_ctocpp.h', 'libcef_dll/ctocpp/v8handler_ctocpp.cc', 'libcef_dll/ctocpp/v8handler_ctocpp.h', 'libcef_dll/ctocpp/write_handler_ctocpp.cc', @@ -264,6 +265,8 @@ 'libcef_dll/ctocpp/stream_reader_ctocpp.h', 'libcef_dll/ctocpp/stream_writer_ctocpp.cc', 'libcef_dll/ctocpp/stream_writer_ctocpp.h', + 'libcef_dll/cpptoc/task_cpptoc.cc', + 'libcef_dll/cpptoc/task_cpptoc.h', 'libcef_dll/ctocpp/v8value_ctocpp.cc', 'libcef_dll/ctocpp/v8value_ctocpp.h', 'libcef_dll/transfer_util.cpp', @@ -353,11 +356,21 @@ 'libcef/browser_webview_delegate.cc', 'libcef/browser_webview_delegate.h', 'libcef/browser_webview_delegate_win.cc', + 'libcef/cef_context.cc', + 'libcef/cef_context.h', + 'libcef/cef_process.cc', + 'libcef/cef_process.h', + 'libcef/cef_process_io_thread.cc', + 'libcef/cef_process_io_thread.h', + 'libcef/cef_process_sub_thread.cc', + 'libcef/cef_process_sub_thread.h', + 'libcef/cef_process_ui_thread.cc', + 'libcef/cef_process_ui_thread.h', 'libcef/cef_string.c', 'libcef/cef_string_list.cc', 'libcef/cef_string_map.cc', - 'libcef/context.cc', - 'libcef/context.h', + 'libcef/cef_thread.cc', + 'libcef/cef_thread.h', 'libcef/printing/print_settings.cc', 'libcef/printing/print_settings.h', 'libcef/printing/win_printing_context.cc', diff --git a/include/cef.h b/include/cef.h index b2dbc16c..d1d7bda2 100644 --- a/include/cef.h +++ b/include/cef.h @@ -54,6 +54,7 @@ class CefSchemeHandler; class CefSchemeHandlerFactory; class CefStreamReader; class CefStreamWriter; +class CefTask; class CefV8Handler; class CefV8Value; @@ -153,6 +154,29 @@ bool CefRegisterScheme(const std::wstring& scheme_name, CefRefPtr factory); +typedef cef_thread_id_t CefThreadId; + +// CEF maintains multiple internal threads that are used for handling different +// types of tasks. The UI thread creates the browser window and is used for all +// interaction with the WebKit rendering engine and V8 JavaScript engine (The +// UI thread will be the same as the main application thread if CefInitialize() +// was called with a |multi_threaded_message_loop| value of false.) The IO +// thread is used for handling schema and network requests. The FILE thread is +// used for the application cache and other miscellaneous activities. This +// method will return true if called on the specified thread. +/*--cef()--*/ +bool CefCurrentlyOn(CefThreadId threadId); + +// Post a task for execution on the specified thread. +/*--cef()--*/ +bool CefPostTask(CefThreadId threadId, CefRefPtr task); + +// Post a task for delayed execution on the specified thread. +/*--cef()--*/ +bool CefPostDelayedTask(CefThreadId threadId, CefRefPtr task, + long delay_ms); + + // Interface defining the the reference count implementation methods. All // framework classes must implement the CefBase class. class CefBase @@ -285,6 +309,16 @@ inline bool operator!=(const CefRect& a, const CefRect& b) return !(a == b); } +// Implement this interface for task execution. +/*--cef(source=client)--*/ +class CefTask : public CefBase +{ +public: + // Method that will be executed. |threadId| is the thread executing the call. + /*--cef()--*/ + virtual void Execute(CefThreadId threadId) =0; +}; + // Class used to represent a browser window. All methods exposed by this class // should be thread safe. diff --git a/include/cef_capi.h b/include/cef_capi.h index 5deb9ba6..371d5dd0 100644 --- a/include/cef_capi.h +++ b/include/cef_capi.h @@ -133,6 +133,24 @@ CEF_EXPORT int cef_register_extension(const wchar_t* extension_name, CEF_EXPORT int cef_register_scheme(const wchar_t* scheme_name, const wchar_t* host_name, struct _cef_scheme_handler_factory_t* factory); +// CEF maintains multiple internal threads that are used for handling different +// types of tasks. The UI thread creates the browser window and is used for all +// interaction with the WebKit rendering engine and V8 JavaScript engine (The UI +// thread will be the same as the main application thread if cef_initialize() +// was called with a |multi_threaded_message_loop| value of false (0).) The IO +// thread is used for handling schema and network requests. The FILE thread is +// used for the application cache and other miscellaneous activities. This +// function will return true (1) if called on the specified thread. +CEF_EXPORT int cef_currently_on(cef_thread_id_t threadId); + +// Post a task for execution on the specified thread. +CEF_EXPORT int cef_post_task(cef_thread_id_t threadId, + struct _cef_task_t* task); + +// Post a task for delayed execution on the specified thread. +CEF_EXPORT int cef_post_delayed_task(cef_thread_id_t threadId, + struct _cef_task_t* task, long delay_ms); + typedef struct _cef_base_t { // Size of the data structure. @@ -157,6 +175,19 @@ typedef struct _cef_base_t #define CEF_MEMBER_MISSING(s, f) (!CEF_MEMBER_EXISTS(s, f) || !((s)->f)) +// Implement this structure for task execution. +typedef struct _cef_task_t +{ + // Base structure. + cef_base_t base; + + // Method that will be executed. |threadId| is the thread executing the call. + void (CEF_CALLBACK *execute)(struct _cef_task_t* self, + cef_thread_id_t threadId); + +} cef_task_t; + + // Structure used to represent a browser window. All functions exposed by this // structure should be thread safe. typedef struct _cef_browser_t diff --git a/include/cef_types.h b/include/cef_types.h index f7ebf9cf..f7a39812 100644 --- a/include/cef_types.h +++ b/include/cef_types.h @@ -229,6 +229,14 @@ typedef struct _cef_rect_t int height; } cef_rect_t; +// Existing thread IDs. +enum cef_thread_id_t +{ + TID_UI = 0, + TID_IO = 1, + TID_FILE = 2, +}; + #ifdef __cplusplus } #endif diff --git a/libcef/browser_appcache_system.cc b/libcef/browser_appcache_system.cc index 8b5efbf0..7700ed7c 100644 --- a/libcef/browser_appcache_system.cc +++ b/libcef/browser_appcache_system.cc @@ -408,9 +408,6 @@ WebApplicationCacheHost* BrowserAppCacheSystem::CreateCacheHostForWebKit( DCHECK(is_ui_thread()); - // The IO thread needs to be running for this system to work. - BrowserResourceLoaderBridge::EnsureIOThread(); - if (!is_initialized()) return NULL; return new WebApplicationCacheHostImpl(client, backend_proxy_.get()); diff --git a/libcef/browser_impl.cc b/libcef/browser_impl.cc index 8ecb9aa3..966d040f 100644 --- a/libcef/browser_impl.cc +++ b/libcef/browser_impl.cc @@ -3,7 +3,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "context.h" +#include "cef_context.h" #include "browser_impl.h" #include "browser_webkit_glue.h" #include "request_impl.h" @@ -54,39 +54,38 @@ CefBrowserImpl::CefBrowserImpl(CefWindowInfo& windowInfo, bool popup, void CefBrowserImpl::GoBack() { - PostTask(FROM_HERE, NewRunnableMethod(this, - &CefBrowserImpl::UIT_HandleActionView, MENU_ID_NAV_BACK)); + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, + &CefBrowserImpl::UIT_HandleActionView, MENU_ID_NAV_BACK)); } void CefBrowserImpl::GoForward() { - PostTask(FROM_HERE, NewRunnableMethod(this, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, &CefBrowserImpl::UIT_HandleActionView, MENU_ID_NAV_FORWARD)); } void CefBrowserImpl::Reload() { - PostTask(FROM_HERE, NewRunnableMethod(this, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, &CefBrowserImpl::UIT_HandleActionView, MENU_ID_NAV_RELOAD)); } void CefBrowserImpl::StopLoad() { - PostTask(FROM_HERE, NewRunnableMethod(this, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, &CefBrowserImpl::UIT_HandleActionView, MENU_ID_NAV_STOP)); } void CefBrowserImpl::SetFocus(bool enable) { - if (_Context->RunningOnUIThread()) + if (CefThread::CurrentlyOn(CefThread::UI)) { UIT_SetFocus(GetWebViewHost(), enable); } else { - PostTask(FROM_HERE, NewRunnableMethod(this, - &CefBrowserImpl::UIT_SetFocus, - GetWebViewHost(), enable)); + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, + &CefBrowserImpl::UIT_SetFocus, GetWebViewHost(), enable)); } } @@ -142,14 +141,14 @@ void CefBrowserImpl::Find(int identifier, const std::wstring& searchText, options.findNext = findNext; // Execute the request on the UI thread. - PostTask(FROM_HERE, NewRunnableMethod(this, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, &CefBrowserImpl::UIT_Find, identifier, searchText, options)); } void CefBrowserImpl::StopFinding(bool clearSelection) { // Execute the request on the UI thread. - PostTask(FROM_HERE, NewRunnableMethod(this, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, &CefBrowserImpl::UIT_StopFinding, clearSelection)); } @@ -209,49 +208,49 @@ WebFrame* CefBrowserImpl::GetWebFrame(CefRefPtr frame) void CefBrowserImpl::Undo(CefRefPtr frame) { frame->AddRef(); - PostTask(FROM_HERE, NewRunnableMethod(this, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, &CefBrowserImpl::UIT_HandleAction, MENU_ID_UNDO, frame.get())); } void CefBrowserImpl::Redo(CefRefPtr frame) { frame->AddRef(); - PostTask(FROM_HERE, NewRunnableMethod(this, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, &CefBrowserImpl::UIT_HandleAction, MENU_ID_REDO, frame.get())); } void CefBrowserImpl::Cut(CefRefPtr frame) { frame->AddRef(); - PostTask(FROM_HERE, NewRunnableMethod(this, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, &CefBrowserImpl::UIT_HandleAction, MENU_ID_CUT, frame.get())); } void CefBrowserImpl::Copy(CefRefPtr frame) { frame->AddRef(); - PostTask(FROM_HERE, NewRunnableMethod(this, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, &CefBrowserImpl::UIT_HandleAction, MENU_ID_COPY, frame.get())); } void CefBrowserImpl::Paste(CefRefPtr frame) { frame->AddRef(); - PostTask(FROM_HERE, NewRunnableMethod(this, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, &CefBrowserImpl::UIT_HandleAction, MENU_ID_PASTE, frame.get())); } void CefBrowserImpl::Delete(CefRefPtr frame) { frame->AddRef(); - PostTask(FROM_HERE, NewRunnableMethod(this, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, &CefBrowserImpl::UIT_HandleAction, MENU_ID_DELETE, frame.get())); } void CefBrowserImpl::SelectAll(CefRefPtr frame) { frame->AddRef(); - PostTask(FROM_HERE, NewRunnableMethod(this, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, &CefBrowserImpl::UIT_HandleAction, MENU_ID_SELECTALL, frame.get())); } @@ -259,14 +258,14 @@ void CefBrowserImpl::SelectAll(CefRefPtr frame) void CefBrowserImpl::Print(CefRefPtr frame) { frame->AddRef(); - PostTask(FROM_HERE, NewRunnableMethod(this, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, &CefBrowserImpl::UIT_HandleAction, MENU_ID_PRINT, frame.get())); } void CefBrowserImpl::ViewSource(CefRefPtr frame) { frame->AddRef(); - PostTask(FROM_HERE, NewRunnableMethod(this, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, &CefBrowserImpl::UIT_HandleAction, MENU_ID_VIEWSOURCE, frame.get())); } @@ -276,7 +275,7 @@ void CefBrowserImpl::LoadRequest(CefRefPtr frame, DCHECK(request.get() != NULL); frame->AddRef(); request->AddRef(); - PostTask(FROM_HERE, NewRunnableMethod(this, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, &CefBrowserImpl::UIT_LoadURLForRequestRef, frame.get(), request.get())); } @@ -284,7 +283,7 @@ void CefBrowserImpl::LoadURL(CefRefPtr frame, const std::wstring& url) { frame->AddRef(); - PostTask(FROM_HERE, NewRunnableMethod(this, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, &CefBrowserImpl::UIT_LoadURL, frame.get(), url)); } @@ -293,7 +292,7 @@ void CefBrowserImpl::LoadString(CefRefPtr frame, const std::wstring& url) { frame->AddRef(); - PostTask(FROM_HERE, NewRunnableMethod(this, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, &CefBrowserImpl::UIT_LoadHTML, frame.get(), string, url)); } @@ -304,7 +303,7 @@ void CefBrowserImpl::LoadStream(CefRefPtr frame, DCHECK(stream.get() != NULL); frame->AddRef(); stream->AddRef(); - PostTask(FROM_HERE, NewRunnableMethod(this, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, &CefBrowserImpl::UIT_LoadHTMLForStreamRef, frame.get(), stream.get(), url)); } @@ -315,7 +314,7 @@ void CefBrowserImpl::ExecuteJavaScript(CefRefPtr frame, int startLine) { frame->AddRef(); - PostTask(FROM_HERE, NewRunnableMethod(this, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, &CefBrowserImpl::UIT_ExecuteJavaScript, frame.get(), jsCode, scriptUrl, startLine)); } @@ -351,7 +350,7 @@ bool CefBrowser::CreateBrowser(CefWindowInfo& windowInfo, bool popup, CefRefPtr browser( new CefBrowserImpl(windowInfo, popup, handler)); - PostTask(FROM_HERE, NewRunnableMethod(browser.get(), + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(browser.get(), &CefBrowserImpl::UIT_CreateBrowser, newUrl)); return true; } @@ -361,7 +360,7 @@ CefRefPtr CefBrowser::CreateBrowserSync(CefWindowInfo& windowInfo, CefRefPtr handler, const std::wstring& url) { - if(!_Context.get() || !_Context->RunningOnUIThread()) + if(!_Context.get() || !CefThread::CurrentlyOn(CefThread::UI)) return NULL; std::wstring newUrl = url; diff --git a/libcef/browser_impl.h b/libcef/browser_impl.h index 19455b86..f68dbccb 100644 --- a/libcef/browser_impl.h +++ b/libcef/browser_impl.h @@ -7,11 +7,11 @@ #define _BROWSER_IMPL_H #include "include/cef.h" -#include "context.h" #include "webview_host.h" #include "browser_webview_delegate.h" #include "browser_navigation_controller.h" +#include "cef_thread.h" #if defined(OS_WIN) #include "printing/win_printing_context.h" #endif diff --git a/libcef/browser_impl_win.cc b/libcef/browser_impl_win.cc index 569c97a6..89b4fd77 100644 --- a/libcef/browser_impl_win.cc +++ b/libcef/browser_impl_win.cc @@ -3,7 +3,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "context.h" +#include "cef_context.h" #include "browser_impl.h" #include "browser_webkit_glue.h" #include "stream_impl.h" @@ -111,7 +111,7 @@ CefWindowHandle CefBrowserImpl::GetWindowHandle() std::wstring CefBrowserImpl::GetSource(CefRefPtr frame) { - if(!_Context->RunningOnUIThread()) + if(!CefThread::CurrentlyOn(CefThread::UI)) { // We need to send the request to the UI thread and wait for the result @@ -125,7 +125,7 @@ std::wstring CefBrowserImpl::GetSource(CefRefPtr frame) // Request the data from the UI thread frame->AddRef(); stream->AddRef(); - PostTask(FROM_HERE, NewRunnableMethod(this, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, &CefBrowserImpl::UIT_GetDocumentStringNotify, frame.get(), stream.get(), hEvent)); @@ -150,7 +150,7 @@ std::wstring CefBrowserImpl::GetSource(CefRefPtr frame) std::wstring CefBrowserImpl::GetText(CefRefPtr frame) { - if(!_Context->RunningOnUIThread()) + if(!CefThread::CurrentlyOn(CefThread::UI)) { // We need to send the request to the UI thread and wait for the result @@ -164,7 +164,7 @@ std::wstring CefBrowserImpl::GetText(CefRefPtr frame) // Request the data from the UI thread frame->AddRef(); stream->AddRef(); - PostTask(FROM_HERE, NewRunnableMethod(this, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, &CefBrowserImpl::UIT_GetDocumentTextNotify, frame.get(), stream.get(), hEvent)); @@ -187,7 +187,7 @@ std::wstring CefBrowserImpl::GetText(CefRefPtr frame) bool CefBrowserImpl::CanGoBack() { - if(!_Context->RunningOnUIThread()) + if(!CefThread::CurrentlyOn(CefThread::UI)) { // We need to send the request to the UI thread and wait for the result @@ -199,7 +199,7 @@ bool CefBrowserImpl::CanGoBack() bool retVal = true; // Request the data from the UI thread - PostTask(FROM_HERE, NewRunnableMethod(this, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, &CefBrowserImpl::UIT_CanGoBackNotify, &retVal, hEvent)); // Wait for the UI thread callback to tell us that the data is available @@ -217,7 +217,7 @@ bool CefBrowserImpl::CanGoBack() bool CefBrowserImpl::CanGoForward() { - if(!_Context->RunningOnUIThread()) + if(!CefThread::CurrentlyOn(CefThread::UI)) { // We need to send the request to the UI thread and wait for the result @@ -229,7 +229,7 @@ bool CefBrowserImpl::CanGoForward() bool retVal = true; // Request the data from the UI thread - PostTask(FROM_HERE, NewRunnableMethod(this, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(this, &CefBrowserImpl::UIT_CanGoForwardNotify, &retVal, hEvent)); // Wait for the UI thread callback to tell us that the data is available @@ -254,7 +254,7 @@ void CefBrowserImpl::UIT_CreateBrowser(const std::wstring& url) window_info_.m_windowName, window_info_.m_dwStyle, window_info_.m_x, window_info_.m_y, window_info_.m_nWidth, window_info_.m_nHeight, window_info_.m_hWndParent, window_info_.m_hMenu, - _Context->GetInstanceHandle(), NULL); + ::GetModuleHandle(NULL), NULL); DCHECK(window_info_.m_hWnd != NULL); // Set window user data to this object for future reference from the window @@ -269,7 +269,7 @@ void CefBrowserImpl::UIT_CreateBrowser(const std::wstring& url) // Create the webview host object webviewhost_.reset( WebViewHost::Create(window_info_.m_hWnd, delegate_.get(), - *_Context->GetWebPreferences())); + *_Context->web_preferences())); delegate_->RegisterDragDrop(); // Size the web view window to the browser window diff --git a/libcef/browser_request_context.cc b/libcef/browser_request_context.cc index 25677a52..35ab38ee 100644 --- a/libcef/browser_request_context.cc +++ b/libcef/browser_request_context.cc @@ -73,6 +73,13 @@ BrowserRequestContext::~BrowserRequestContext() { delete static_cast(cookie_policy_); } +void BrowserRequestContext::SetAcceptAllCookies(bool accept_all_cookies) { + net::StaticCookiePolicy::Type policy_type = accept_all_cookies ? + net::StaticCookiePolicy::ALLOW_ALL_COOKIES : + net::StaticCookiePolicy::BLOCK_THIRD_PARTY_COOKIES; + static_cast(cookie_policy())->set_type(policy_type); +} + const std::string& BrowserRequestContext::GetUserAgent( const GURL& url) const { return webkit_glue::GetUserAgent(url); diff --git a/libcef/browser_request_context.h b/libcef/browser_request_context.h index e117fb65..3072f39d 100644 --- a/libcef/browser_request_context.h +++ b/libcef/browser_request_context.h @@ -16,6 +16,7 @@ class BrowserRequestContext : public URLRequestContext { public: // Use an in-memory cache BrowserRequestContext(); + ~BrowserRequestContext(); // Use an on-disk cache at the specified location. Optionally, use the cache // in playback or record mode. @@ -25,9 +26,9 @@ class BrowserRequestContext : public URLRequestContext { virtual const std::string& GetUserAgent(const GURL& url) const; - private: - ~BrowserRequestContext(); + void SetAcceptAllCookies(bool accept_all_cookies); + private: void Init(const FilePath& cache_path, net::HttpCache::Mode cache_mode, bool no_proxy); }; diff --git a/libcef/browser_resource_loader_bridge.cc b/libcef/browser_resource_loader_bridge.cc index 77dca540..a8e375b9 100644 --- a/libcef/browser_resource_loader_bridge.cc +++ b/libcef/browser_resource_loader_bridge.cc @@ -36,6 +36,9 @@ #include "browser_request_context.h" #include "browser_socket_stream_bridge.h" #include "browser_impl.h" +#include "cef_context.h" +#include "cef_process.h" +#include "cef_process_io_thread.h" #include "request_impl.h" #include "base/file_path.h" @@ -73,80 +76,6 @@ using net::StaticCookiePolicy; namespace { -struct BrowserRequestContextParams { - BrowserRequestContextParams( - const FilePath& in_cache_path, - net::HttpCache::Mode in_cache_mode, - bool in_no_proxy) - : cache_path(in_cache_path), - cache_mode(in_cache_mode), - no_proxy(in_no_proxy), - accept_all_cookies(false) {} - - FilePath cache_path; - net::HttpCache::Mode cache_mode; - bool no_proxy; - bool accept_all_cookies; -}; - -BrowserRequestContextParams* g_request_context_params = NULL; -URLRequestContext* g_request_context = NULL; -base::Thread* g_cache_thread = NULL; - -//----------------------------------------------------------------------------- - -class IOThread : public base::Thread { - public: - IOThread() : base::Thread("IOThread") { - } - - ~IOThread() { - // We cannot rely on our base class to stop the thread since we want our - // CleanUp function to run. - Stop(); - } - - virtual void Init() { - if (g_request_context_params) { - g_request_context = new BrowserRequestContext( - g_request_context_params->cache_path, - g_request_context_params->cache_mode, - g_request_context_params->no_proxy); - SetAcceptAllCookies(g_request_context_params->accept_all_cookies); - delete g_request_context_params; - g_request_context_params = NULL; - } else { - g_request_context = new BrowserRequestContext(); - SetAcceptAllCookies(false); - } - - g_request_context->AddRef(); - - BrowserAppCacheSystem::InitializeOnIOThread(g_request_context); - BrowserSocketStreamBridge::InitializeOnIOThread(g_request_context); - } - - virtual void CleanUp() { - BrowserSocketStreamBridge::Cleanup(); - if (g_request_context) { - g_request_context->Release(); - g_request_context = NULL; - } - } - - void SetAcceptAllCookies(bool accept_all_cookies) { - StaticCookiePolicy::Type policy_type = accept_all_cookies ? - StaticCookiePolicy::ALLOW_ALL_COOKIES : - StaticCookiePolicy::BLOCK_THIRD_PARTY_COOKIES; - static_cast(g_request_context->cookie_policy())-> - set_type(policy_type); - } -}; - -IOThread* g_io_thread = NULL; - -//----------------------------------------------------------------------------- - struct RequestParams { std::string method; GURL url; @@ -185,13 +114,13 @@ class RequestProxy : public URLRequest::Delegate, owner_loop_ = MessageLoop::current(); // proxy over to the io thread - g_io_thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + CefThread::PostTask(CefThread::IO, FROM_HERE, NewRunnableMethod( this, &RequestProxy::AsyncStart, params)); } void Cancel() { // proxy over to the io thread - g_io_thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + CefThread::PostTask(CefThread::IO, FROM_HERE, NewRunnableMethod( this, &RequestProxy::AsyncCancel)); } @@ -200,8 +129,7 @@ class RequestProxy : public URLRequest::Delegate, virtual ~RequestProxy() { // If we have a request, then we'd better be on the io thread! - DCHECK(!request_.get() || - MessageLoop::current() == g_io_thread->message_loop()); + DCHECK(!request_.get() || CefThread::CurrentlyOn(CefThread::IO)); } // -------------------------------------------------------------------------- @@ -216,7 +144,7 @@ class RequestProxy : public URLRequest::Delegate, if (peer_ && peer_->OnReceivedRedirect(new_url, info, &has_new_first_party_for_cookies, &new_first_party_for_cookies)) { - g_io_thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + CefThread::PostTask(CefThread::IO, FROM_HERE, NewRunnableMethod( this, &RequestProxy::AsyncFollowDeferredRedirect, has_new_first_party_for_cookies, new_first_party_for_cookies)); } else { @@ -245,7 +173,7 @@ class RequestProxy : public URLRequest::Delegate, // peer could generate new requests in reponse to the received data, which // when run on the io thread, could race against this function in doing // another InvokeLater. See bug 769249. - g_io_thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + CefThread::PostTask(CefThread::IO, FROM_HERE, NewRunnableMethod( this, &RequestProxy::AsyncReadData)); peer_->OnReceivedData(buf_copy.get(), bytes_read); @@ -349,7 +277,7 @@ class RequestProxy : public URLRequest::Delegate, request_->SetExtraRequestHeaders(headers); request_->set_load_flags(params->load_flags); request_->set_upload(params->upload.get()); - request_->set_context(g_request_context); + request_->set_context(_Context->request_context()); BrowserAppCacheSystem::SetExtraRequestInfo( request_.get(), params->appcache_host_id, params->request_type); @@ -657,7 +585,7 @@ class ResourceLoaderBridgeImpl : public ResourceLoaderBridge { if (proxy_) { proxy_->DropPeer(); // Let the proxy die on the IO thread - g_io_thread->message_loop()->ReleaseSoon(FROM_HERE, proxy_); + CefThread::ReleaseSoon(CefThread::IO, FROM_HERE, proxy_); } } @@ -693,9 +621,6 @@ class ResourceLoaderBridgeImpl : public ResourceLoaderBridge { virtual bool Start(Peer* peer) { DCHECK(!proxy_); - if (!BrowserResourceLoaderBridge::EnsureIOThread()) - return false; - proxy_ = new RequestProxy(browser_); proxy_->AddRef(); @@ -716,9 +641,6 @@ class ResourceLoaderBridgeImpl : public ResourceLoaderBridge { virtual void SyncLoad(SyncLoadResponse* response) { DCHECK(!proxy_); - if (!BrowserResourceLoaderBridge::EnsureIOThread()) - return; - // this may change as the result of a redirect response->url = params_->url; @@ -746,8 +668,8 @@ class ResourceLoaderBridgeImpl : public ResourceLoaderBridge { class CookieSetter : public base::RefCountedThreadSafe { public: void Set(const GURL& url, const std::string& cookie) { - DCHECK(MessageLoop::current() == g_io_thread->message_loop()); - g_request_context->cookie_store()->SetCookie(url, cookie); + REQUIRE_IOT(); + _Context->request_context()->cookie_store()->SetCookie(url, cookie); } private: @@ -762,7 +684,7 @@ class CookieGetter : public base::RefCountedThreadSafe { } void Get(const GURL& url) { - result_ = g_request_context->cookie_store()->GetCookies(url); + result_ = _Context->request_context()->cookie_store()->GetCookies(url); event_.Signal(); } @@ -798,11 +720,12 @@ ResourceLoaderBridge* ResourceLoaderBridge::Create( // Issue the proxy resolve request on the io thread, and wait // for the result. bool FindProxyForUrl(const GURL& url, std::string* proxy_list) { - DCHECK(g_request_context); + DCHECK(_Context->request_context()); scoped_refptr sync_proxy_service( - new net::SyncProxyServiceHelper(g_io_thread->message_loop(), - g_request_context->proxy_service())); + new net::SyncProxyServiceHelper( + _Context->process()->io_thread()->message_loop(), + _Context->request_context()->proxy_service())); net::ProxyInfo proxy_info; int rv = sync_proxy_service->ResolveProxy(url, &proxy_info, @@ -818,53 +741,14 @@ bool FindProxyForUrl(const GURL& url, std::string* proxy_list) { //----------------------------------------------------------------------------- -// static -void BrowserResourceLoaderBridge::Init( - const FilePath& cache_path, - net::HttpCache::Mode cache_mode, - bool no_proxy) { - // Make sure to stop any existing IO thread since it may be using the - // current request context. - Shutdown(); - - DCHECK(!g_request_context_params); - DCHECK(!g_request_context); - DCHECK(!g_io_thread); - - g_request_context_params = new BrowserRequestContextParams( - cache_path, cache_mode, no_proxy); -} - -// static -void BrowserResourceLoaderBridge::Shutdown() { - if (g_io_thread) { - delete g_io_thread; - g_io_thread = NULL; - - DCHECK(g_cache_thread); - delete g_cache_thread; - g_cache_thread = NULL; - - DCHECK(!g_request_context) << "should have been nulled by thread dtor"; - } else { - delete g_request_context_params; - g_request_context_params = NULL; - } -} - // static void BrowserResourceLoaderBridge::SetCookie(const GURL& url, const GURL& first_party_for_cookies, const std::string& cookie) { // Proxy to IO thread to synchronize w/ network loading. - if (!EnsureIOThread()) { - NOTREACHED(); - return; - } - scoped_refptr cookie_setter = new CookieSetter(); - g_io_thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + CefThread::PostTask(CefThread::IO, FROM_HERE, NewRunnableMethod( cookie_setter.get(), &CookieSetter::Set, url, cookie)); } @@ -873,61 +757,24 @@ std::string BrowserResourceLoaderBridge::GetCookies( const GURL& url, const GURL& first_party_for_cookies) { // Proxy to IO thread to synchronize w/ network loading - if (!EnsureIOThread()) { - NOTREACHED(); - return std::string(); - } - scoped_refptr getter = new CookieGetter(); - g_io_thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + CefThread::PostTask(CefThread::IO, FROM_HERE, NewRunnableMethod( getter.get(), &CookieGetter::Get, url)); return getter->GetResult(); } -// static -bool BrowserResourceLoaderBridge::EnsureIOThread() { - if (g_io_thread) - return true; - -#if defined(OS_WIN) - // Use NSS for SSL on Windows. TODO(wtc): this should eventually be hidden - // inside DefaultClientSocketFactory::CreateSSLClientSocket. - net::ClientSocketFactory::SetSSLClientSocketFactory( - net::SSLClientSocketNSSFactory); -#endif -#if defined(OS_MACOSX) || defined(OS_WIN) - // We want to be sure to init NSPR on the main thread. - base::EnsureNSPRInit(); -#endif - - // Create the cache thread. We want the cache thread to outlive the IO thread, - // so its lifetime is bonded to the IO thread lifetime. - DCHECK(!g_cache_thread); - g_cache_thread = new base::Thread("cache"); - CHECK(g_cache_thread->StartWithOptions( - base::Thread::Options(MessageLoop::TYPE_IO, 0))); - - g_io_thread = new IOThread(); - base::Thread::Options options; - options.message_loop_type = MessageLoop::TYPE_IO; - return g_io_thread->StartWithOptions(options); -} - // static void BrowserResourceLoaderBridge::SetAcceptAllCookies(bool accept_all_cookies) { - if (g_request_context_params) { - g_request_context_params->accept_all_cookies = accept_all_cookies; - DCHECK(!g_request_context); - DCHECK(!g_io_thread); - } else { - g_io_thread->SetAcceptAllCookies(accept_all_cookies); - } + // Proxy to IO thread to synchronize w/ network loading + CefThread::PostTask(CefThread::IO, FROM_HERE, NewRunnableMethod( + _Context->request_context().get(), + &BrowserRequestContext::SetAcceptAllCookies, accept_all_cookies)); } -// static +//static scoped_refptr BrowserResourceLoaderBridge::GetCacheThread() { - return g_cache_thread->message_loop_proxy(); + return CefThread::GetMessageLoopProxyForThread(CefThread::FILE); } diff --git a/libcef/browser_resource_loader_bridge.h b/libcef/browser_resource_loader_bridge.h index cdb0e75c..92a25686 100644 --- a/libcef/browser_resource_loader_bridge.h +++ b/libcef/browser_resource_loader_bridge.h @@ -15,29 +15,14 @@ class GURL; class BrowserResourceLoaderBridge { public: - // Call this function to initialize the simple resource loader bridge. - // It is safe to call this function multiple times. - // - // NOTE: If this function is not called, then a default request context will - // be initialized lazily. - // - static void Init(const FilePath& cache_path, - net::HttpCache::Mode cache_mode, - bool no_proxy); - - // Call this function to shutdown the simple resource loader bridge. - static void Shutdown(); - // May only be called after Init. static void SetCookie(const GURL& url, const GURL& first_party_for_cookies, const std::string& cookie); static std::string GetCookies(const GURL& url, const GURL& first_party_for_cookies); - static bool EnsureIOThread(); static void SetAcceptAllCookies(bool accept_all_cookies); - // This method should only be called after Init(), and before Shutdown(). static scoped_refptr GetCacheThread(); }; diff --git a/libcef/browser_webview_delegate.cc b/libcef/browser_webview_delegate.cc index 37ace4c2..16a7783c 100644 --- a/libcef/browser_webview_delegate.cc +++ b/libcef/browser_webview_delegate.cc @@ -11,7 +11,7 @@ #include "browser_appcache_system.h" #include "browser_impl.h" #include "browser_navigation_controller.h" -#include "context.h" +#include "cef_context.h" #include "request_impl.h" #include "v8_impl.h" @@ -126,13 +126,13 @@ int next_page_id_ = 1; // WebViewDelegate ----------------------------------------------------------- void BrowserWebViewDelegate::SetUserStyleSheetEnabled(bool is_enabled) { - WebPreferences* prefs = _Context->GetWebPreferences(); + WebPreferences* prefs = _Context->web_preferences(); prefs->user_style_sheet_enabled = is_enabled; prefs->Apply(browser_->GetWebView()); } void BrowserWebViewDelegate::SetUserStyleSheetLocation(const GURL& location) { - WebPreferences* prefs = _Context->GetWebPreferences(); + WebPreferences* prefs = _Context->web_preferences(); prefs->user_style_sheet_enabled = true; prefs->user_style_sheet_location = location; prefs->Apply(browser_->GetWebView()); diff --git a/libcef/browser_webview_delegate_win.cc b/libcef/browser_webview_delegate_win.cc index 9befc396..72157825 100644 --- a/libcef/browser_webview_delegate_win.cc +++ b/libcef/browser_webview_delegate_win.cc @@ -12,7 +12,7 @@ #include "browser_drop_delegate.h" #include "browser_navigation_controller.h" #include "browser_impl.h" -#include "context.h" +#include "cef_context.h" #include #include diff --git a/libcef/cef_context.cc b/libcef/cef_context.cc new file mode 100644 index 00000000..954b3ea5 --- /dev/null +++ b/libcef/cef_context.cc @@ -0,0 +1,322 @@ +// Copyright (c) 2010 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 "cef_context.h" +#include "browser_impl.h" +#include "browser_webkit_glue.h" +#include "cef_thread.h" +#include "cef_process.h" +#include "../include/cef_nplugin.h" + +#include "base/file_util.h" +#if defined(OS_MACOSX) || defined(OS_WIN) +#include "base/nss_util.h" +#endif +#include "webkit/glue/plugins/plugin_list.h" +#include "webkit/glue/webpreferences.h" + +// Global CefContext pointer +CefRefPtr _Context; + +bool CefInitialize(bool multi_threaded_message_loop, + const std::wstring& cache_path) +{ + // Return true if the context is already initialized + if(_Context.get()) + return true; + + // Create the new global context object + _Context = new CefContext(); + // Initialize the global context + return _Context->Initialize(multi_threaded_message_loop, cache_path); +} + +void CefShutdown() +{ + // Verify that the context is already initialized + if(!_Context.get()) + return; + + // Shut down the global context + _Context->Shutdown(); + // Delete the global context object + _Context = NULL; +} + +void CefDoMessageLoopWork() +{ + // Verify that the context is already initialized. + if(!_Context.get() || !_Context->process()) + return; + + if(!_Context->process()->CalledOnValidThread()) + return; + _Context->process()->DoMessageLoopIteration(); +} + +static void UIT_RegisterPlugin(struct CefPluginInfo* plugin_info) +{ + REQUIRE_UIT(); + + NPAPI::PluginVersionInfo info; + + info.path = FilePath(plugin_info->unique_name); + info.product_name = plugin_info->display_name; + info.file_description = plugin_info->description; + info.file_version =plugin_info->version; + + for(size_t i = 0; i < plugin_info->mime_types.size(); ++i) { + if(i > 0) { + info.mime_types += L"|"; + info.file_extensions += L"|"; + info.type_descriptions += L"|"; + } + + info.mime_types += plugin_info->mime_types[i].mime_type; + info.type_descriptions += plugin_info->mime_types[i].description; + + for(size_t j = 0; + j < plugin_info->mime_types[i].file_extensions.size(); ++j) { + if(j > 0) { + info.file_extensions += L","; + } + info.file_extensions += plugin_info->mime_types[i].file_extensions[j]; + } + } + + info.entry_points.np_getentrypoints = plugin_info->np_getentrypoints; + info.entry_points.np_initialize = plugin_info->np_initialize; + info.entry_points.np_shutdown = plugin_info->np_shutdown; + + NPAPI::PluginList::Singleton()->RegisterInternalPlugin(info); + + delete plugin_info; +} + +bool CefRegisterPlugin(const struct CefPluginInfo& plugin_info) +{ + if(!_Context.get()) + return false; + + CefPluginInfo* pPluginInfo = new CefPluginInfo; + *pPluginInfo = plugin_info; + + CefThread::PostTask(CefThread::UI, FROM_HERE, + NewRunnableFunction(UIT_RegisterPlugin, pPluginInfo)); + + return true; +} + +static int GetThreadId(CefThreadId threadId) +{ + switch(threadId) { + case TID_UI: return CefThread::UI; + case TID_IO: return CefThread::IO; + case TID_FILE: return CefThread::FILE; + }; + NOTREACHED(); + return -1; +} + +bool CefCurrentlyOn(CefThreadId threadId) +{ + int id = GetThreadId(threadId); + if(id < 0) + return false; + + return CefThread::CurrentlyOn(static_cast(id)); +} + +class CefTaskHelper : public base::RefCountedThreadSafe +{ +public: + CefTaskHelper(CefRefPtr task) : task_(task) {} + void Execute(CefThreadId threadId) { task_->Execute(threadId); } +private: + CefRefPtr task_; + DISALLOW_COPY_AND_ASSIGN(CefTaskHelper); +}; + +bool CefPostTask(CefThreadId threadId, CefRefPtr task) +{ + int id = GetThreadId(threadId); + if(id < 0) + return false; + + scoped_refptr helper(new CefTaskHelper(task)); + return CefThread::PostTask(static_cast(id), FROM_HERE, + NewRunnableMethod(helper.get(), &CefTaskHelper::Execute, threadId)); +} + +bool CefPostDelayedTask(CefThreadId threadId, CefRefPtr task, + long delay_ms) +{ + int id = GetThreadId(threadId); + if(id < 0) + return false; + + scoped_refptr helper(new CefTaskHelper(task)); + return CefThread::PostDelayedTask(static_cast(id), FROM_HERE, + NewRunnableMethod(helper.get(), &CefTaskHelper::Execute, threadId), + delay_ms); +} + + +// CefContext + +CefContext::CefContext() : process_(NULL), webprefs_(NULL) +{ + +} + +CefContext::~CefContext() +{ + // Just in case CefShutdown() isn't called + Shutdown(); +} + +bool CefContext::Initialize(bool multi_threaded_message_loop, + const std::wstring& cache_path) +{ + cache_path_ = cache_path; + + // Initialize web preferences + webprefs_ = new WebPreferences; + *webprefs_ = WebPreferences(); + webprefs_->standard_font_family = L"Times"; + webprefs_->fixed_font_family = L"Courier"; + webprefs_->serif_font_family = L"Times"; + webprefs_->sans_serif_font_family = L"Helvetica"; + // These two fonts are picked from the intersection of + // Win XP font list and Vista font list : + // http://www.microsoft.com/typography/fonts/winxp.htm + // http://blogs.msdn.com/michkap/archive/2006/04/04/567881.aspx + // Some of them are installed only with CJK and complex script + // support enabled on Windows XP and are out of consideration here. + // (although we enabled both on our buildbots.) + // They (especially Impact for fantasy) are not typical cursive + // and fantasy fonts, but it should not matter for layout tests + // as long as they're available. +#if defined(OS_MACOSX) + webprefs_->cursive_font_family = L"Apple Chancery"; + webprefs_->fantasy_font_family = L"Papyrus"; +#else + webprefs_->cursive_font_family = L"Comic Sans MS"; + webprefs_->fantasy_font_family = L"Impact"; +#endif + webprefs_->default_encoding = "ISO-8859-1"; + webprefs_->default_font_size = 16; + webprefs_->default_fixed_font_size = 13; + webprefs_->minimum_font_size = 1; + webprefs_->minimum_logical_font_size = 9; + webprefs_->javascript_can_open_windows_automatically = true; + webprefs_->dom_paste_enabled = true; + webprefs_->developer_extras_enabled = true; + webprefs_->site_specific_quirks_enabled = true; + webprefs_->shrinks_standalone_images_to_fit = false; + webprefs_->uses_universal_detector = false; + webprefs_->text_areas_are_resizable = true; + webprefs_->java_enabled = true; + webprefs_->allow_scripts_to_close_windows = false; + webprefs_->xss_auditor_enabled = false; + webprefs_->remote_fonts_enabled = true; + webprefs_->local_storage_enabled = true; + webprefs_->application_cache_enabled = true; + webprefs_->databases_enabled = true; + webprefs_->allow_file_access_from_file_urls = true; + +#if defined(OS_MACOSX) || defined(OS_WIN) + // We want to be sure to init NSPR on the main thread. + base::EnsureNSPRInit(); +#endif + + process_ = new CefProcess(multi_threaded_message_loop); + process_->CreateChildThreads(); + + return true; +} + +void CefContext::Shutdown() +{ + // Remove all existing browsers. + //RemoveAllBrowsers(); + + // Deleting the process will destroy the child threads. + process_ = NULL; +} + + +bool CefContext::AddBrowser(CefRefPtr browser) +{ + bool found = false; + + Lock(); + + // check that the browser isn't already in the list before adding + BrowserList::const_iterator it = browserlist_.begin(); + for(; it != browserlist_.end(); ++it) { + if(it->get() == browser.get()) { + found = true; + break; + } + } + + if(!found) + { + browser->UIT_SetUniqueID(next_browser_id_++); + browserlist_.push_back(browser); + } + + Unlock(); + return !found; +} + +bool CefContext::RemoveBrowser(CefRefPtr browser) +{ + bool deleted = false; + bool empty = false; + + Lock(); + + BrowserList::iterator it = browserlist_.begin(); + for(; it != browserlist_.end(); ++it) { + if(it->get() == browser.get()) { + browserlist_.erase(it); + deleted = true; + break; + } + } + + if (browserlist_.empty()) { + next_browser_id_ = 1; + empty = true; + } + + Unlock(); + + if (empty) { + CefThread::PostTask(CefThread::UI, FROM_HERE, + NewRunnableFunction(webkit_glue::ClearCache)); + } + + return deleted; +} + +CefRefPtr CefContext::GetBrowserByID(int id) +{ + CefRefPtr browser; + Lock(); + + BrowserList::const_iterator it = browserlist_.begin(); + for(; it != browserlist_.end(); ++it) { + if(it->get()->UIT_GetUniqueID() == id) { + browser = it->get(); + break; + } + } + + Unlock(); + + return browser; +} diff --git a/libcef/cef_context.h b/libcef/cef_context.h new file mode 100644 index 00000000..eaf99aa9 --- /dev/null +++ b/libcef/cef_context.h @@ -0,0 +1,81 @@ +// Copyright (c) 2010 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. + +#ifndef _CEF_CONTEXT_H +#define _CEF_CONTEXT_H + +#include "../include/cef.h" +#include "browser_request_context.h" +#include "cef_process.h" +#include "cef_thread.h" + +#include "base/at_exit.h" +#include "base/file_path.h" +#include "base/message_loop.h" +#include "base/ref_counted.h" +#include + +class BrowserRequestContext; +class CefBrowserImpl; +struct WebPreferences; + +class CefContext : public CefThreadSafeBase +{ +public: + typedef std::list > BrowserList; + + CefContext(); + ~CefContext(); + + // These methods will be called on the main application thread. + bool Initialize(bool multi_threaded_message_loop, + const std::wstring& cache_path); + void Shutdown(); + + scoped_refptr process() { return process_; } + + bool AddBrowser(CefRefPtr browser); + bool RemoveBrowser(CefRefPtr browser); + CefRefPtr GetBrowserByID(int id); + BrowserList* GetBrowserList() { return &browserlist_; } + + // Retrieve the path at which cache data will be stored on disk. If empty, + // cache data will be stored in-memory. + const std::wstring& cache_path() { return cache_path_; } + + WebPreferences* web_preferences() + { + REQUIRE_UIT(); + return webprefs_; + } + + // The BrowserRequestContext object is managed by CefProcessIOThread. + void set_request_context(BrowserRequestContext* request_context) + { request_context_ = request_context; } + scoped_refptr request_context() + { return request_context_; } + +private: + // Manages the various process threads. + scoped_refptr process_; + + // Initialize the AtExitManager on the main application thread to avoid + // asserts and possible memory leaks. + base::AtExitManager at_exit_manager_; + + std::wstring cache_path_; + WebPreferences* webprefs_; + scoped_refptr request_context_; + + // Map of browsers that currently exist. + BrowserList browserlist_; + + // Used for assigning unique IDs to browser instances. + int next_browser_id_; +}; + +// Global context object pointer +extern CefRefPtr _Context; + +#endif // _CONTEXT_H diff --git a/libcef/cef_process.cc b/libcef/cef_process.cc new file mode 100644 index 00000000..83f7e477 --- /dev/null +++ b/libcef/cef_process.cc @@ -0,0 +1,134 @@ +// Copyright (c) 2010 The Chromium Embedded Framework Authors. +// Portions copyright (c) 2010 The Chromium 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 "cef_process.h" +#include "cef_process_io_thread.h" +#include "cef_process_sub_thread.h" +#include "cef_process_ui_thread.h" + +#include "base/thread.h" +#include "base/waitable_event.h" + +CefProcess* g_cef_process = NULL; + +// Class used to process events on the current message loop. +class CefMessageLoopForUI : public MessageLoopForUI +{ + typedef MessageLoopForUI inherited; + +public: + CefMessageLoopForUI() + { + } + + // Returns the MessageLoopForUI of the current thread. + static CefMessageLoopForUI* current() { + MessageLoop* loop = MessageLoop::current(); + DCHECK_EQ(MessageLoop::TYPE_UI, loop->type()); + return static_cast(loop); + } + + virtual bool DoIdleWork() { + bool valueToRet = inherited::DoIdleWork(); + pump_->Quit(); + return valueToRet; + } + + void DoMessageLoopIteration() { + Run(NULL); + } + + private: + DISALLOW_COPY_AND_ASSIGN(CefMessageLoopForUI); +}; + +CefProcess::CefProcess(bool multi_threaded_message_loop) + : multi_threaded_message_loop_(multi_threaded_message_loop), + created_ui_thread_(false), + created_io_thread_(false), + created_file_thread_(false) { + g_cef_process = this; +} + +CefProcess::~CefProcess() { + // Terminate the IO thread. + io_thread_.reset(); + + // Terminate the FILE thread. + file_thread_.reset(); + + if(!multi_threaded_message_loop_) { + // Must explicitly clean up the UI thread. + ui_thread_->CleanUp(); + + // Terminate the UI thread. + ui_thread_.reset(); + + // Terminate the message loop. + ui_message_loop_.reset(); + } + + g_cef_process = NULL; +} + +void CefProcess::DoMessageLoopIteration() { + DCHECK(CalledOnValidThread() && ui_message_loop_.get() != NULL); + ui_message_loop_->DoMessageLoopIteration(); +} + +void CefProcess::CreateUIThread() { + DCHECK(!created_ui_thread_ && ui_thread_.get() == NULL); + created_ui_thread_ = true; + + scoped_ptr thread; + if(multi_threaded_message_loop_) { + // Create the message loop on a new thread. + thread.reset(new CefProcessUIThread()); + base::Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_UI; + if (!thread->StartWithOptions(options)) + return; + } else { + // Create the message loop on the current (main application) thread. + ui_message_loop_.reset(new CefMessageLoopForUI()); + thread.reset( + new CefProcessUIThread(ui_message_loop_.get())); + + // Must explicitly initialize the UI thread. + thread->Init(); + } + + ui_thread_.swap(thread); +} + +void CefProcess::CreateIOThread() { + DCHECK(!created_io_thread_ && io_thread_.get() == NULL); + created_io_thread_ = true; + + scoped_ptr thread(new CefProcessIOThread()); + base::Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_IO; + if (!thread->StartWithOptions(options)) + return; + io_thread_.swap(thread); +} + +void CefProcess::CreateFileThread() { + DCHECK(!created_file_thread_ && file_thread_.get() == NULL); + created_file_thread_ = true; + + scoped_ptr thread(new CefProcessSubThread(CefThread::FILE)); + base::Thread::Options options; +#if defined(OS_WIN) + // On Windows, the FILE thread needs to be have a UI message loop which pumps + // messages in such a way that Google Update can communicate back to us. + options.message_loop_type = MessageLoop::TYPE_UI; +#else + options.message_loop_type = MessageLoop::TYPE_IO; +#endif + if (!thread->StartWithOptions(options)) + return; + file_thread_.swap(thread); +} diff --git a/libcef/cef_process.h b/libcef/cef_process.h new file mode 100644 index 00000000..3c42c6cc --- /dev/null +++ b/libcef/cef_process.h @@ -0,0 +1,117 @@ +// Copyright (c) 2010 The Chromium Embedded Framework Authors. +// Portions copyright (c) 2010 The Chromium 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 interface is for managing the global services of the application. Each +// service is lazily created when requested the first time. The service getters +// will return NULL if the service is not available, so callers must check for +// this condition. + +#ifndef _CEF_PROCESS_H +#define _CEF_PROCESS_H + +#include +#include + +#include "base/basictypes.h" +#include "base/message_loop.h" +#include "base/non_thread_safe.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "ipc/ipc_message.h" + +namespace base { +class Thread; +class WaitableEvent; +} + +class CefProcessIOThread; +class CefProcessUIThread; +class CefMessageLoopForUI; + +// NOT THREAD SAFE, call only from the main thread. +// These functions shouldn't return NULL unless otherwise noted. +class CefProcess : public base::RefCounted, public NonThreadSafe { + public: + CefProcess(bool multi_threaded_message_loop); + virtual ~CefProcess(); + + // Creates key child threads. We need to do this explicitly since + // CefThread::PostTask silently deletes a posted task if the target message + // loop isn't created. + void CreateChildThreads() { + ui_thread(); + // Create the FILE thread before the IO thread because IO thread + // initialization depends on existance of the FILE thread (for cache + // support, etc). + file_thread(); + io_thread(); + } + + CefProcessUIThread* ui_thread() { + DCHECK(CalledOnValidThread()); + if (!created_ui_thread_) + CreateUIThread(); + return ui_thread_.get(); + } + + // Necessary to perform work on the UI thread if started without a multi + // threaded message loop. + void DoMessageLoopIteration(); + + // Returns the thread that we perform I/O coordination on (network requests, + // communication with renderers, etc. + // NOTE: You should ONLY use this to pass to IPC or other objects which must + // need a MessageLoop*. If you just want to post a task, use + // CefThread::PostTask (or other variants) as they take care of checking + // that a thread is still alive, race conditions, lifetime differences etc. + // If you still must use this, need to check the return value for NULL. + CefProcessIOThread* io_thread() { + DCHECK(CalledOnValidThread()); + if (!created_io_thread_) + CreateIOThread(); + return io_thread_.get(); + } + + // Returns the thread that we perform random file operations on. For code + // that wants to do I/O operations (not network requests or even file: URL + // requests), this is the thread to use to avoid blocking the UI thread. + // It might be nicer to have a thread pool for this kind of thing. + base::Thread* file_thread() { + DCHECK(CalledOnValidThread()); + if (!created_file_thread_) + CreateFileThread(); + return file_thread_.get(); + } + +#if defined(IPC_MESSAGE_LOG_ENABLED) + // Enable or disable IPC logging for the browser, all processes + // derived from ChildProcess (plugin etc), and all + // renderers. + void SetIPCLoggingEnabled(bool enable); +#endif + + private: + void CreateUIThread(); + void CreateIOThread(); + void CreateFileThread(); + + bool multi_threaded_message_loop_; + + bool created_ui_thread_; + scoped_ptr ui_thread_; + scoped_ptr ui_message_loop_; + + bool created_io_thread_; + scoped_ptr io_thread_; + + bool created_file_thread_; + scoped_ptr file_thread_; + + DISALLOW_COPY_AND_ASSIGN(CefProcess); +}; + +extern CefProcess* g_cef_process; + +#endif // _CEF_PROCESS_H diff --git a/libcef/cef_process_io_thread.cc b/libcef/cef_process_io_thread.cc new file mode 100644 index 00000000..38f60319 --- /dev/null +++ b/libcef/cef_process_io_thread.cc @@ -0,0 +1,60 @@ +// Copyright (c) 2010 The Chromium Embedded Framework Authors. +// Portions copyright (c) 2010 The Chromium 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 "cef_process_io_thread.h" +#include "cef_context.h" +#include "browser_appcache_system.h" +#include "browser_resource_loader_bridge.h" +#include "browser_socket_stream_bridge.h" + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include +#endif + +CefProcessIOThread::CefProcessIOThread() + : CefThread(CefThread::IO), request_context_(NULL) {} + +CefProcessIOThread::CefProcessIOThread(MessageLoop* message_loop) + : CefThread(CefThread::IO, message_loop), request_context_(NULL) {} + +CefProcessIOThread::~CefProcessIOThread() { + // We cannot rely on our base class to stop the thread since we want our + // CleanUp function to run. + Stop(); +} + +void CefProcessIOThread::Init() { +#if defined(OS_WIN) + // Initializes the COM library on the current thread. + CoInitialize(NULL); +#endif + + request_context_ = new BrowserRequestContext(FilePath(_Context->cache_path()), + net::HttpCache::NORMAL, false); + _Context->set_request_context(request_context_); + + BrowserAppCacheSystem::InitializeOnIOThread(request_context_); + BrowserSocketStreamBridge::InitializeOnIOThread(request_context_); +} + +void CefProcessIOThread::CleanUp() { + // Flush any remaining messages. This ensures that any accumulated + // Task objects get destroyed before we exit, which avoids noise in + // purify leak-test results. + MessageLoop::current()->RunAllPending(); + + BrowserSocketStreamBridge::Cleanup(); + + _Context->set_request_context(NULL); + request_context_ = NULL; + +#if defined(OS_WIN) + // Closes the COM library on the current thread. CoInitialize must + // be balanced by a corresponding call to CoUninitialize. + CoUninitialize(); +#endif +} diff --git a/libcef/cef_process_io_thread.h b/libcef/cef_process_io_thread.h new file mode 100644 index 00000000..fa3057a1 --- /dev/null +++ b/libcef/cef_process_io_thread.h @@ -0,0 +1,40 @@ +// Copyright (c) 2010 The Chromium Embedded Framework Authors. +// Portions copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef _CEF_PROCESS_IO_THREAD +#define _CEF_PROCESS_IO_THREAD + +#include "base/basictypes.h" +#include "cef_thread.h" +#include "browser_request_context.h" + +// ---------------------------------------------------------------------------- +// CefProcessIOThread +// +// This simple thread object is used for the specialized threads that the +// CefProcess spins up. +// +// Applications must initialize the COM library before they can call +// COM library functions other than CoGetMalloc and memory allocation +// functions, so this class initializes COM for those users. +class CefProcessIOThread : public CefThread { + public: + explicit CefProcessIOThread(); + CefProcessIOThread(MessageLoop* message_loop); + virtual ~CefProcessIOThread(); + + virtual void Init(); + virtual void CleanUp(); + + scoped_refptr request_context() + { return request_context_; } + + private: + scoped_refptr request_context_; + + DISALLOW_COPY_AND_ASSIGN(CefProcessIOThread); +}; + +#endif // _CEF_PROCESS_UI_THREAD diff --git a/libcef/cef_process_sub_thread.cc b/libcef/cef_process_sub_thread.cc new file mode 100644 index 00000000..42e9f4f1 --- /dev/null +++ b/libcef/cef_process_sub_thread.cc @@ -0,0 +1,44 @@ +// Copyright (c) 2010 The Chromium Embedded Framework Authors. +// Portions copyright (c) 2010 The Chromium 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 "cef_process_sub_thread.h" +#include "build/build_config.h" + +#if defined(OS_WIN) +#include +#endif + +CefProcessSubThread::CefProcessSubThread(CefThread::ID identifier) + : CefThread(identifier) {} + +CefProcessSubThread::CefProcessSubThread(CefThread::ID identifier, + MessageLoop* message_loop) + : CefThread(identifier, message_loop) {} + +CefProcessSubThread::~CefProcessSubThread() { + // We cannot rely on our base class to stop the thread since we want our + // CleanUp function to run. + Stop(); +} + +void CefProcessSubThread::Init() { +#if defined(OS_WIN) + // Initializes the COM library on the current thread. + CoInitialize(NULL); +#endif +} + +void CefProcessSubThread::CleanUp() { + // Flush any remaining messages. This ensures that any accumulated + // Task objects get destroyed before we exit, which avoids noise in + // purify leak-test results. + MessageLoop::current()->RunAllPending(); + +#if defined(OS_WIN) + // Closes the COM library on the current thread. CoInitialize must + // be balanced by a corresponding call to CoUninitialize. + CoUninitialize(); +#endif +} diff --git a/libcef/cef_process_sub_thread.h b/libcef/cef_process_sub_thread.h new file mode 100644 index 00000000..31a33bd6 --- /dev/null +++ b/libcef/cef_process_sub_thread.h @@ -0,0 +1,35 @@ +// Copyright (c) 2010 The Chromium Embedded Framework Authors. +// Portions copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef _CEF_PROCESS_SUB_THREAD +#define _CEF_PROCESS_SUB_THREAD + +#include "base/basictypes.h" +#include "cef_thread.h" + +// ---------------------------------------------------------------------------- +// CefProcessSubThread +// +// This simple thread object is used for the specialized threads that the +// CefProcess spins up. +// +// Applications must initialize the COM library before they can call +// COM library functions other than CoGetMalloc and memory allocation +// functions, so this class initializes COM for those users. +class CefProcessSubThread : public CefThread { + public: + explicit CefProcessSubThread(CefThread::ID identifier); + CefProcessSubThread(CefThread::ID identifier, MessageLoop* message_loop); + virtual ~CefProcessSubThread(); + + protected: + virtual void Init(); + virtual void CleanUp(); + + private: + DISALLOW_COPY_AND_ASSIGN(CefProcessSubThread); +}; + +#endif // _CEF_PROCESS_SUB_THREAD diff --git a/libcef/cef_process_ui_thread.cc b/libcef/cef_process_ui_thread.cc new file mode 100644 index 00000000..76f14d94 --- /dev/null +++ b/libcef/cef_process_ui_thread.cc @@ -0,0 +1,153 @@ +// Copyright (c) 2010 The Chromium Embedded Framework Authors. +// Portions copyright (c) 2010 The Chromium 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 "cef_process_ui_thread.h" +#include "browser_impl.h" +#include "browser_resource_loader_bridge.h" +#include "browser_request_context.h" +#include "browser_webkit_glue.h" +#include "browser_webkit_init.h" +#include "cef_context.h" + +#include "base/command_line.h" +#include "base/i18n/icu_util.h" +#include "base/rand_util.h" +#include "base/stats_table.h" +#include "build/build_config.h" +#include "net/base/net_module.h" +#if defined(OS_WIN) +#include "net/socket/ssl_client_socket_nss_factory.h" +#endif +#include "webkit/extensions/v8/gc_extension.h" + +#if defined(OS_WIN) +#include +#include +#endif + +static const char* kStatsFilePrefix = "libcef_"; +static int kStatsFileThreads = 20; +static int kStatsFileCounters = 200; + +CefProcessUIThread::CefProcessUIThread() + : CefThread(CefThread::UI), statstable_(NULL), webkit_init_(NULL) {} + +CefProcessUIThread::CefProcessUIThread(MessageLoop* message_loop) + : CefThread(CefThread::UI, message_loop), statstable_(NULL), + webkit_init_(NULL) {} + +CefProcessUIThread::~CefProcessUIThread() { + // We cannot rely on our base class to stop the thread since we want our + // CleanUp function to run. + Stop(); +} + +void CefProcessUIThread::Init() { +#if defined(OS_WIN) + HRESULT res; + + // Initialize common controls + res = CoInitialize(NULL); + DCHECK(SUCCEEDED(res)); + INITCOMMONCONTROLSEX InitCtrlEx; + InitCtrlEx.dwSize = sizeof(INITCOMMONCONTROLSEX); + InitCtrlEx.dwICC = ICC_STANDARD_CLASSES; + InitCommonControlsEx(&InitCtrlEx); + + // Start COM stuff + res = OleInitialize(NULL); + DCHECK(SUCCEEDED(res)); + + // Register the window class + WNDCLASSEX wcex = { + /* cbSize = */ sizeof(WNDCLASSEX), + /* style = */ CS_HREDRAW | CS_VREDRAW, + /* lpfnWndProc = */ CefBrowserImpl::WndProc, + /* cbClsExtra = */ 0, + /* cbWndExtra = */ 0, + /* hInstance = */ ::GetModuleHandle(NULL), + /* hIcon = */ NULL, + /* hCursor = */ LoadCursor(NULL, IDC_ARROW), + /* hbrBackground = */ 0, + /* lpszMenuName = */ NULL, + /* lpszClassName = */ CefBrowserImpl::GetWndClass(), + /* hIconSm = */ NULL, + }; + RegisterClassEx(&wcex); +#endif + +#ifndef _DEBUG + // Only log error messages and above in release build. + logging::SetMinLogLevel(logging::LOG_ERROR); +#endif + + // Initialize the global CommandLine object. + CommandLine::Init(0, NULL); + + // Initialize WebKit. + webkit_init_ = new BrowserWebKitInit(); + + // Initialize WebKit encodings + webkit_glue::InitializeTextEncoding(); + + // Load ICU data tables. + bool ret = icu_util::Initialize(); + if(!ret) { +#if defined(OS_WIN) + MessageBox(NULL, L"Failed to load the required icudt38 library", + L"CEF Initialization Error", MB_ICONERROR | MB_OK); +#endif + return; + } + + // Config the network module so it has access to a limited set of resources. + net::NetModule::SetResourceProvider(webkit_glue::NetResourceProvider); + + // Load and initialize the stats table. Attempt to construct a somewhat + // unique name to isolate separate instances from each other. + statstable_ = new StatsTable( + kStatsFilePrefix + Uint64ToString(base::RandUint64()), + kStatsFileThreads, + kStatsFileCounters); + StatsTable::set_current(statstable_); + + // CEF always exposes the GC. + webkit_glue::SetJavaScriptFlags(L"--expose-gc"); + // Expose GCController to JavaScript. + WebKit::WebScriptController::registerExtension( + extensions_v8::GCExtension::Get()); + +#if defined(OS_WIN) + // Use NSS for SSL on Windows. TODO(wtc): this should eventually be hidden + // inside DefaultClientSocketFactory::CreateSSLClientSocket. + net::ClientSocketFactory::SetSSLClientSocketFactory( + net::SSLClientSocketNSSFactory); +#endif +} + +void CefProcessUIThread::CleanUp() { + // Flush any remaining messages. This ensures that any accumulated + // Task objects get destroyed before we exit, which avoids noise in + // purify leak-test results. + MessageLoop::current()->RunAllPending(); + + // Tear down the shared StatsTable. + StatsTable::set_current(NULL); + delete statstable_; + statstable_ = NULL; + + // Shut down WebKit. + delete webkit_init_; + webkit_init_ = NULL; + +#if defined(OS_WIN) + // Uninitialize COM stuff + OleUninitialize(); + + // Closes the COM library on the current thread. CoInitialize must + // be balanced by a corresponding call to CoUninitialize. + CoUninitialize(); +#endif +} diff --git a/libcef/cef_process_ui_thread.h b/libcef/cef_process_ui_thread.h new file mode 100644 index 00000000..e7fd07ac --- /dev/null +++ b/libcef/cef_process_ui_thread.h @@ -0,0 +1,42 @@ +// Copyright (c) 2010 The Chromium Embedded Framework Authors. +// Portions copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef _CEF_PROCESS_UI_THREAD +#define _CEF_PROCESS_UI_THREAD + +#include "base/basictypes.h" +#include "cef_thread.h" + +class BrowserWebKitInit; +class StatsTable; + +// ---------------------------------------------------------------------------- +// CefProcessUIThread +// +// This simple thread object is used for the specialized threads that the +// CefProcess spins up. +// +// Applications must initialize the COM library before they can call +// COM library functions other than CoGetMalloc and memory allocation +// functions, so this class initializes COM for those users. +class CefProcessUIThread : public CefThread { + public: + explicit CefProcessUIThread(); + CefProcessUIThread(MessageLoop* message_loop); + virtual ~CefProcessUIThread(); + + virtual void Init(); + virtual void CleanUp(); + + private: + StatsTable* statstable_; + + // WebKit implementation class. + BrowserWebKitInit* webkit_init_; + + DISALLOW_COPY_AND_ASSIGN(CefProcessUIThread); +}; + +#endif // _CEF_PROCESS_UI_THREAD diff --git a/libcef/cef_thread.cc b/libcef/cef_thread.cc new file mode 100644 index 00000000..975d8ecc --- /dev/null +++ b/libcef/cef_thread.cc @@ -0,0 +1,210 @@ +// Copyright (c) 2010 The Chromium Embedded Framework Authors. +// Portions copyright (c) 2010 The Chromium 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 "cef_thread.h" + +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" + +using base::MessageLoopProxy; + +// Friendly names for the well-known threads. +static const char* cef_thread_names[CefThread::ID_COUNT] = { + "Cef_UIThread", // UI + "Cef_FileThread", // FILE + "Cef_IOThread", // IO +}; + +// An implementation of MessageLoopProxy to be used in conjunction +// with CefThread. +class CefThreadMessageLoopProxy : public MessageLoopProxy { + public: + explicit CefThreadMessageLoopProxy(CefThread::ID identifier) + : id_(identifier) { + } + + // MessageLoopProxy implementation. + virtual bool PostTask(const tracked_objects::Location& from_here, + Task* task) { + return CefThread::PostTask(id_, from_here, task); + } + + virtual bool PostDelayedTask(const tracked_objects::Location& from_here, + Task* task, int64 delay_ms) { + return CefThread::PostDelayedTask(id_, from_here, task, delay_ms); + } + + virtual bool PostNonNestableTask(const tracked_objects::Location& from_here, + Task* task) { + return CefThread::PostNonNestableTask(id_, from_here, task); + } + + virtual bool PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + Task* task, + int64 delay_ms) { + return CefThread::PostNonNestableDelayedTask(id_, from_here, task, + delay_ms); + } + virtual bool BelongsToCurrentThread() { + return CefThread::CurrentlyOn(id_); + } + + private: + CefThread::ID id_; + DISALLOW_COPY_AND_ASSIGN(CefThreadMessageLoopProxy); +}; + + +Lock CefThread::lock_; + +CefThread* CefThread::cef_threads_[ID_COUNT]; + +CefThread::CefThread(CefThread::ID identifier) + : Thread(cef_thread_names[identifier]), + identifier_(identifier) { + Initialize(); +} + +CefThread::CefThread(ID identifier, MessageLoop* message_loop) + : Thread(cef_thread_names[identifier]), + identifier_(identifier) { + message_loop->set_thread_name(cef_thread_names[identifier]); + set_message_loop(message_loop); + Initialize(); +} + +void CefThread::Initialize() { + AutoLock lock(lock_); + DCHECK(identifier_ >= 0 && identifier_ < ID_COUNT); + DCHECK(cef_threads_[identifier_] == NULL); + cef_threads_[identifier_] = this; +} + +CefThread::~CefThread() { + // Stop the thread here, instead of the parent's class destructor. This is so + // that if there are pending tasks that run, code that checks that it's on the + // correct CefThread succeeds. + Stop(); + + AutoLock lock(lock_); + cef_threads_[identifier_] = NULL; +#ifndef NDEBUG + // Double check that the threads are ordererd correctly in the enumeration. + for (int i = identifier_ + 1; i < ID_COUNT; ++i) { + DCHECK(!cef_threads_[i]) << + "Threads must be listed in the reverse order that they die"; + } +#endif +} + +// static +bool CefThread::IsWellKnownThread(ID identifier) { + AutoLock lock(lock_); + return (identifier >= 0 && identifier < ID_COUNT && + cef_threads_[identifier]); +} + +// static +bool CefThread::CurrentlyOn(ID identifier) { + AutoLock lock(lock_); + DCHECK(identifier >= 0 && identifier < ID_COUNT); + return cef_threads_[identifier] && + cef_threads_[identifier]->message_loop() == MessageLoop::current(); +} + +// static +bool CefThread::PostTask(ID identifier, + const tracked_objects::Location& from_here, + Task* task) { + return PostTaskHelper(identifier, from_here, task, 0, true); +} + +// static +bool CefThread::PostDelayedTask(ID identifier, + const tracked_objects::Location& from_here, + Task* task, + int64 delay_ms) { + return PostTaskHelper(identifier, from_here, task, delay_ms, true); +} + +// static +bool CefThread::PostNonNestableTask( + ID identifier, + const tracked_objects::Location& from_here, + Task* task) { + return PostTaskHelper(identifier, from_here, task, 0, false); +} + +// static +bool CefThread::PostNonNestableDelayedTask( + ID identifier, + const tracked_objects::Location& from_here, + Task* task, + int64 delay_ms) { + return PostTaskHelper(identifier, from_here, task, delay_ms, false); +} + +// static +bool CefThread::GetCurrentThreadIdentifier(ID* identifier) { + MessageLoop* cur_message_loop = MessageLoop::current(); + for (int i = 0; i < ID_COUNT; ++i) { + if (cef_threads_[i] && + cef_threads_[i]->message_loop() == cur_message_loop) { + *identifier = cef_threads_[i]->identifier_; + return true; + } + } + + return false; +} + +// static +scoped_refptr CefThread::GetMessageLoopProxyForThread( + ID identifier) { + scoped_refptr proxy = + new CefThreadMessageLoopProxy(identifier); + return proxy; +} + +// static +bool CefThread::PostTaskHelper( + ID identifier, + const tracked_objects::Location& from_here, + Task* task, + int64 delay_ms, + bool nestable) { + DCHECK(identifier >= 0 && identifier < ID_COUNT); + // Optimization: to avoid unnecessary locks, we listed the ID enumeration in + // order of lifetime. So no need to lock if we know that the other thread + // outlives this one. + // Note: since the array is so small, ok to loop instead of creating a map, + // which would require a lock because std::map isn't thread safe, defeating + // the whole purpose of this optimization. + ID current_thread; + bool guaranteed_to_outlive_target_thread = + GetCurrentThreadIdentifier(¤t_thread) && + current_thread >= identifier; + + if (!guaranteed_to_outlive_target_thread) + lock_.Acquire(); + + MessageLoop* message_loop = cef_threads_[identifier] ? + cef_threads_[identifier]->message_loop() : NULL; + if (message_loop) { + if (nestable) { + message_loop->PostDelayedTask(from_here, task, delay_ms); + } else { + message_loop->PostNonNestableDelayedTask(from_here, task, delay_ms); + } + } else { + delete task; + } + + if (!guaranteed_to_outlive_target_thread) + lock_.Release(); + + return !!message_loop; +} diff --git a/libcef/cef_thread.h b/libcef/cef_thread.h new file mode 100644 index 00000000..ff655a4b --- /dev/null +++ b/libcef/cef_thread.h @@ -0,0 +1,185 @@ +// Copyright (c) 2010 The Chromium Embedded Framework Authors. +// Portions copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef _CEF_THREAD_H +#define _CEF_THREAD_H + +#include "base/lock.h" +#include "base/task.h" +#include "base/thread.h" + +namespace base { +class MessageLoopProxy; +} + +/////////////////////////////////////////////////////////////////////////////// +// CefThread +// +// This class represents a thread that is known by a browser-wide name. For +// example, there is one IO thread for the entire browser process, and various +// pieces of code find it useful to retrieve a pointer to the IO thread's +// Invoke a task by thread ID: +// +// CefThread::PostTask(CefThread::IO, FROM_HERE, task); +// +// The return value is false if the task couldn't be posted because the target +// thread doesn't exist. If this could lead to data loss, you need to check the +// result and restructure the code to ensure it doesn't occur. +// +// This class automatically handles the lifetime of different threads. +// It's always safe to call PostTask on any thread. If it's not yet created, +// the task is deleted. There are no race conditions. If the thread that the +// task is posted to is guaranteed to outlive the current thread, then no locks +// are used. You should never need to cache pointers to MessageLoops, since +// they're not thread safe. +class CefThread : public base::Thread { + public: + // An enumeration of the well-known threads. + // NOTE: threads must be listed in the order of their life-time, with each + // thread outliving every other thread below it. + enum ID { + // The main thread in the browser. + UI, + + // This is the thread that interacts with the file system. + FILE, + + // This is the thread that processes IPC and network messages. + IO, + + // This identifier does not represent a thread. Instead it counts the + // number of well-known threads. Insert new well-known threads before this + // identifier. + ID_COUNT + }; + + // Construct a CefThread with the supplied identifier. It is an error + // to construct a CefThread that already exists. + explicit CefThread(ID identifier); + + // Special constructor for the main (UI) thread and unittests. We use a dummy + // thread here since the main thread already exists. + CefThread(ID identifier, MessageLoop* message_loop); + + virtual ~CefThread(); + + // These are the same methods in message_loop.h, but are guaranteed to either + // get posted to the MessageLoop if it's still alive, or be deleted otherwise. + // They return true if the thread existed and the task was posted. Note that + // even if the task is posted, there's no guarantee that it will run, since + // the target thread may already have a Quit message in its queue. + static bool PostTask(ID identifier, + const tracked_objects::Location& from_here, + Task* task); + static bool PostDelayedTask(ID identifier, + const tracked_objects::Location& from_here, + Task* task, + int64 delay_ms); + static bool PostNonNestableTask(ID identifier, + const tracked_objects::Location& from_here, + Task* task); + static bool PostNonNestableDelayedTask( + ID identifier, + const tracked_objects::Location& from_here, + Task* task, + int64 delay_ms); + + template + static bool DeleteSoon(ID identifier, + const tracked_objects::Location& from_here, + T* object) { + return PostNonNestableTask( + identifier, from_here, new DeleteTask(object)); + } + + template + static bool ReleaseSoon(ID identifier, + const tracked_objects::Location& from_here, + T* object) { + return PostNonNestableTask( + identifier, from_here, new ReleaseTask(object)); + } + + // Callable on any thread. Returns whether the given ID corresponds to a well + // known thread. + static bool IsWellKnownThread(ID identifier); + + // Callable on any thread. Returns whether you're currently on a particular + // thread. + static bool CurrentlyOn(ID identifier); + + // If the current message loop is one of the known threads, returns true and + // sets identifier to its ID. Otherwise returns false. + static bool GetCurrentThreadIdentifier(ID* identifier); + + // Callers can hold on to a refcounted MessageLoopProxy beyond the lifetime + // of the thread. + static scoped_refptr GetMessageLoopProxyForThread( + ID identifier); + + // Use these templates in conjuction with RefCountedThreadSafe when you want + // to ensure that an object is deleted on a specific thread. This is needed + // when an object can hop between threads (i.e. IO -> FILE -> IO), and thread + // switching delays can mean that the final IO tasks executes before the FILE + // task's stack unwinds. This would lead to the object destructing on the + // FILE thread, which often is not what you want (i.e. to unregister from + // NotificationService, to notify other objects on the creating thread etc). + template + struct DeleteOnThread { + template + static void Destruct(T* x) { + if (CurrentlyOn(thread)) { + delete x; + } else { + DeleteSoon(thread, FROM_HERE, x); + } + } + }; + + // Sample usage: + // class Foo + // : public base::RefCountedThreadSafe< + // Foo, CefThread::DeleteOnIOThread> { + // + // ... + // private: + // friend class CefThread; + // friend class DeleteTask; + // + // ~Foo(); + struct DeleteOnUIThread : public DeleteOnThread { }; + struct DeleteOnIOThread : public DeleteOnThread { }; + struct DeleteOnFileThread : public DeleteOnThread { }; + + private: + // Common initialization code for the constructors. + void Initialize(); + + static bool PostTaskHelper( + ID identifier, + const tracked_objects::Location& from_here, + Task* task, + int64 delay_ms, + bool nestable); + + // The identifier of this thread. Only one thread can exist with a given + // identifier at a given time. + ID identifier_; + + // This lock protects |cef_threads_|. Do not read or modify that array + // without holding this lock. Do not block while holding this lock. + static Lock lock_; + + // An array of the CefThread objects. This array is protected by |lock_|. + // The threads are not owned by this array. Typically, the threads are owned + // on the UI thread by the g_browser_process object. CefThreads remove + // themselves from this array upon destruction. + static CefThread* cef_threads_[ID_COUNT]; +}; + +#define REQUIRE_UIT() DCHECK(CefThread::CurrentlyOn(CefThread::UI)) +#define REQUIRE_IOT() DCHECK(CefThread::CurrentlyOn(CefThread::IO)) + +#endif // _CEF_THREAD_H diff --git a/libcef/context.cc b/libcef/context.cc deleted file mode 100644 index d9dd3ef6..00000000 --- a/libcef/context.cc +++ /dev/null @@ -1,593 +0,0 @@ -// Copyright (c) 2008-2009 The Chromium Embedded Framework Authors. -// Portions copyright (c) 2006-2008 The Chromium 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 "context.h" -#include "browser_impl.h" -#include "browser_resource_loader_bridge.h" -#include "browser_request_context.h" -#include "browser_webkit_glue.h" -#include "browser_webkit_init.h" -#include "../include/cef_nplugin.h" - -#include "base/command_line.h" -#include "base/i18n/icu_util.h" -#include "base/path_service.h" -#include "base/rand_util.h" -#include "base/resource_util.h" -#include "base/stats_table.h" -#include "base/utf_string_conversions.h" -#include "net/base/net_module.h" -#include "third_party/WebKit/WebKit/chromium/public/WebScriptController.h" -#include "webkit/extensions/v8/gc_extension.h" -#include "webkit/glue/plugins/webplugin.h" -#include "webkit/glue/plugins/plugin_lib.h" -#include "webkit/glue/plugins/plugin_list.h" - -#include - - -// Global CefContext pointer -CefRefPtr _Context; - -static const char* kStatsFilePrefix = "libcef_"; -static int kStatsFileThreads = 20; -static int kStatsFileCounters = 200; - - -// Class used to process events on the current message loop. -class CefMessageLoopForUI : public MessageLoopForUI -{ - typedef MessageLoopForUI inherited; - -public: - CefMessageLoopForUI() - { - } - - virtual bool DoIdleWork() { - bool valueToRet = inherited::DoIdleWork(); - pump_->Quit(); - return valueToRet; - } - - void DoMessageLoopIteration() { - Run(NULL); - } -}; - - -bool CefInitialize(bool multi_threaded_message_loop, - const std::wstring& cache_path) -{ - // Return true if the context is already initialized - if(_Context.get()) - return true; - - // Create the new global context object - _Context = new CefContext(); - // Initialize the glboal context - return _Context->Initialize(multi_threaded_message_loop, cache_path); -} - -void CefShutdown() -{ - // Verify that the context is already initialized - if(!_Context.get()) - return; - - // Shut down the global context - _Context->Shutdown(); - // Delete the global context object - _Context = NULL; -} - -void CefDoMessageLoopWork() -{ - if(!_Context->RunningOnUIThread()) - return; - ((CefMessageLoopForUI*)CefMessageLoopForUI::current())->DoMessageLoopIteration(); -} - -bool CefRegisterPlugin(const struct CefPluginInfo& plugin_info) -{ - if(!_Context.get()) - return false; - - CefPluginInfo* pPluginInfo = new CefPluginInfo; - *pPluginInfo = plugin_info; - - PostTask(FROM_HERE, NewRunnableMethod(_Context.get(), - &CefContext::UIT_RegisterPlugin, pPluginInfo)); - - return true; -} - -void CefContext::UIT_RegisterPlugin(struct CefPluginInfo* plugin_info) -{ - REQUIRE_UIT(); - - NPAPI::PluginVersionInfo info; - - info.path = FilePath(plugin_info->unique_name); - info.product_name = plugin_info->display_name; - info.file_description = plugin_info->description; - info.file_version =plugin_info->version; - - for(size_t i = 0; i < plugin_info->mime_types.size(); ++i) { - if(i > 0) { - info.mime_types += L"|"; - info.file_extensions += L"|"; - info.type_descriptions += L"|"; - } - - info.mime_types += plugin_info->mime_types[i].mime_type; - info.type_descriptions += plugin_info->mime_types[i].description; - - for(size_t j = 0; - j < plugin_info->mime_types[i].file_extensions.size(); ++j) { - if(j > 0) { - info.file_extensions += L","; - } - info.file_extensions += plugin_info->mime_types[i].file_extensions[j]; - } - } - - info.entry_points.np_getentrypoints = plugin_info->np_getentrypoints; - info.entry_points.np_initialize = plugin_info->np_initialize; - info.entry_points.np_shutdown = plugin_info->np_shutdown; - - NPAPI::PluginList::Singleton()->RegisterInternalPlugin(info); - - delete plugin_info; -} - -bool CefContext::DoInitialize() -{ - HRESULT res; - - // Initialize common controls - res = CoInitialize(NULL); - DCHECK(SUCCEEDED(res)); - INITCOMMONCONTROLSEX InitCtrlEx; - InitCtrlEx.dwSize = sizeof(INITCOMMONCONTROLSEX); - InitCtrlEx.dwICC = ICC_STANDARD_CLASSES; - InitCommonControlsEx(&InitCtrlEx); - - // Start COM stuff - res = OleInitialize(NULL); - DCHECK(SUCCEEDED(res)); - - // Initialize the global CommandLine object. - CommandLine::Init(0, NULL); - - // Initialize WebKit. - webkit_init_ = new BrowserWebKitInit(); - - // Initialize WebKit encodings - webkit_glue::InitializeTextEncoding(); - - // Initializing with a default context, which means no on-disk cookie DB, - // and no support for directory listings. - //PathService::Get(base::DIR_EXE, &cache_path); - BrowserResourceLoaderBridge::Init(FilePath(cache_path_), net::HttpCache::NORMAL, - false); - - // Load ICU data tables. - bool ret = icu_util::Initialize(); - if(!ret) { - MessageBox(NULL, L"Failed to load the required icudt38 library", - L"CEF Initialization Error", MB_ICONERROR | MB_OK); - return false; - } - - // Config the network module so it has access to a limited set of resources. - net::NetModule::SetResourceProvider(webkit_glue::NetResourceProvider); - - // Load and initialize the stats table. Attempt to construct a somewhat - // unique name to isolate separate instances from each other. - statstable_ = new StatsTable( - kStatsFilePrefix + Uint64ToString(base::RandUint64()), - kStatsFileThreads, - kStatsFileCounters); - StatsTable::set_current(statstable_); - - // CEF always exposes the GC. - webkit_glue::SetJavaScriptFlags(L"--expose-gc"); - // Expose GCController to JavaScript. - WebKit::WebScriptController::registerExtension( - extensions_v8::GCExtension::Get()); - - return true; -} - -void CefContext::DoUninitialize() -{ - // Flush any remaining messages. This ensures that any accumulated - // Task objects get destroyed before we exit, which avoids noise in - // purify leak-test results. - MessageLoop::current()->RunAllPending(); - - BrowserResourceLoaderBridge::Shutdown(); - - // Tear down the shared StatsTable. - StatsTable::set_current(NULL); - delete statstable_; - statstable_ = NULL; - - // Shut down WebKit. - delete webkit_init_; - webkit_init_ = NULL; - - // Uninitialize COM stuff - OleUninitialize(); - - // Uninitialize common controls - CoUninitialize(); -} - -DWORD WINAPI ThreadHandlerUI(LPVOID lpParam) -{ - CefContext *pContext = static_cast(lpParam); - - // AtExitManager is scoped to the UI thread. - base::AtExitManager at_exit_manager; - - if (!pContext->DoInitialize()) - return 1; - - // Instantiate the message loop for this thread. - MessageLoopForUI main_message_loop; - pContext->SetMessageLoopForUI(&main_message_loop); - - // Notify the context that initialization is complete so that the - // Initialize() function can return. - pContext->NotifyEvent(); - - // Execute the message loop that will run until a quit task is received. - MessageLoop::current()->Run(); - - pContext->DoUninitialize(); - - return 0; -} - -CefContext::CefContext() -{ - multi_threaded_message_loop_ = false; - hthreadui_ = NULL; - idthreadui_ = 0; - heventui_ = NULL; - messageloopui_ = NULL; - in_transition_ = false; - webprefs_ = NULL; - statstable_ = NULL; - at_exit_manager_ = NULL; - hinstance_ = ::GetModuleHandle(NULL); - next_browser_id_ = 1; - webkit_init_ = NULL; -} - -CefContext::~CefContext() -{ - // Just in case CefShutdown() isn't called - Shutdown(); - if(!multi_threaded_message_loop_) { - if(messageloopui_) - delete messageloopui_; - if(at_exit_manager_) - delete at_exit_manager_; - } -} - -bool CefContext::Initialize(bool multi_threaded_message_loop, - const std::wstring& cache_path) -{ - bool initialized = false, intransition = false; - - Lock(); - - // We only need to initialize if the UI thread is not currently running - if(!idthreadui_) { - // Only start the initialization if we're not currently in a transitional - // state - intransition = in_transition_; - if(!intransition) { - // We are now in a transitional state - in_transition_ = true; - - cache_path_ = cache_path; - - // Register the window class - WNDCLASSEX wcex = { - /* cbSize = */ sizeof(WNDCLASSEX), - /* style = */ CS_HREDRAW | CS_VREDRAW, - /* lpfnWndProc = */ CefBrowserImpl::WndProc, - /* cbClsExtra = */ 0, - /* cbWndExtra = */ 0, - /* hInstance = */ hinstance_, - /* hIcon = */ NULL, - /* hCursor = */ LoadCursor(NULL, IDC_ARROW), - /* hbrBackground = */ 0, - /* lpszMenuName = */ NULL, - /* lpszClassName = */ CefBrowserImpl::GetWndClass(), - /* hIconSm = */ NULL, - }; - RegisterClassEx(&wcex); - -#ifndef _DEBUG - // Only log error messages and above in release build. - logging::SetMinLogLevel(logging::LOG_ERROR); -#endif - - // Initialize web preferences - webprefs_ = new WebPreferences; - *webprefs_ = WebPreferences(); - webprefs_->standard_font_family = L"Times"; - webprefs_->fixed_font_family = L"Courier"; - webprefs_->serif_font_family = L"Times"; - webprefs_->sans_serif_font_family = L"Helvetica"; - // These two fonts are picked from the intersection of - // Win XP font list and Vista font list : - // http://www.microsoft.com/typography/fonts/winxp.htm - // http://blogs.msdn.com/michkap/archive/2006/04/04/567881.aspx - // Some of them are installed only with CJK and complex script - // support enabled on Windows XP and are out of consideration here. - // (although we enabled both on our buildbots.) - // They (especially Impact for fantasy) are not typical cursive - // and fantasy fonts, but it should not matter for layout tests - // as long as they're available. -#if defined(OS_MACOSX) - webprefs_->cursive_font_family = L"Apple Chancery"; - webprefs_->fantasy_font_family = L"Papyrus"; -#else - webprefs_->cursive_font_family = L"Comic Sans MS"; - webprefs_->fantasy_font_family = L"Impact"; -#endif - webprefs_->default_encoding = "ISO-8859-1"; - webprefs_->default_font_size = 16; - webprefs_->default_fixed_font_size = 13; - webprefs_->minimum_font_size = 1; - webprefs_->minimum_logical_font_size = 9; - webprefs_->javascript_can_open_windows_automatically = true; - webprefs_->dom_paste_enabled = true; - webprefs_->developer_extras_enabled = true; - webprefs_->site_specific_quirks_enabled = true; - webprefs_->shrinks_standalone_images_to_fit = false; - webprefs_->uses_universal_detector = false; - webprefs_->text_areas_are_resizable = true; - webprefs_->java_enabled = true; - webprefs_->allow_scripts_to_close_windows = false; - webprefs_->xss_auditor_enabled = false; - webprefs_->remote_fonts_enabled = true; - webprefs_->local_storage_enabled = true; - webprefs_->application_cache_enabled = true; - webprefs_->databases_enabled = true; - webprefs_->allow_file_access_from_file_urls = true; - - if (multi_threaded_message_loop) { - // Event that will be used to signal thread setup completion. Start - // in non-signaled mode so that the event will block. - heventui_ = CreateEvent(NULL, TRUE, FALSE, NULL); - DCHECK(heventui_ != NULL); - - // Thread that hosts the UI loop - hthreadui_ = CreateThread( - NULL, 0, ThreadHandlerUI, this, 0, &idthreadui_); - DCHECK(hthreadui_ != NULL); - DCHECK(idthreadui_ != 0); - } else { - // AtExitManager is scoped to the context. - at_exit_manager_ = new base::AtExitManager; - if (!DoInitialize()) { - // TODO: Process initialization errors - } - // Message loop is scoped to the context. - SetMessageLoopForUI(new CefMessageLoopForUI()); - idthreadui_ = GetCurrentThreadId(); - DCHECK(idthreadui_ != 0); - } - - initialized = true; - } - } - - Unlock(); - - if(initialized) { - multi_threaded_message_loop_ = multi_threaded_message_loop; - - if (multi_threaded_message_loop) { - // Wait for initial UI thread setup to complete - WaitForSingleObject(heventui_, INFINITE); - } - - Lock(); - - // We have exited the transitional state - in_transition_ = false; - - Unlock(); - } - - return intransition ? false : true; -} - -void CefContext::Shutdown() -{ - bool shutdown = false, intransition = false; - BrowserList browserlist; - - Lock(); - - // We only need to shut down if the UI thread is currently running - if(idthreadui_) { - // Only start the shutdown if we're not currently in a transitional state - intransition = in_transition_; - if(!intransition) { - DCHECK(messageloopui_ != NULL); - - // We are now in a transitional state - in_transition_ = true; - - browserlist = browserlist_; - browserlist_.empty(); - - if(webprefs_) { - delete webprefs_; - webprefs_ = NULL; - } - - // Post the quit message to the UI message loop - messageloopui_->PostTask(FROM_HERE, new MessageLoop::QuitTask()); - - shutdown = true; - } - } - - Unlock(); - - if(shutdown) { - if (browserlist.size() > 0) { - // Close any remaining browser windows - BrowserList::const_iterator it = browserlist.begin(); - for (; it != browserlist.end(); ++it) - PostMessage((*it)->GetWindowHandle(), WM_CLOSE, 0, 0); - browserlist.empty(); - } - - if (hthreadui_) { - // Wait for the UI thread to exit - WaitForSingleObject(hthreadui_, INFINITE); - - // Clean up thread and event handles - CloseHandle(hthreadui_); - CloseHandle(heventui_); - - hthreadui_ = NULL; - heventui_ = NULL; - } else { - DoUninitialize(); - } - - Lock(); - - // Unregister the window class - UnregisterClass(CefBrowserImpl::GetWndClass(), hinstance_); - - idthreadui_ = 0; - - // We have exited the transitional state - in_transition_ = false; - - Unlock(); - } -} - -bool CefContext::AddBrowser(CefRefPtr browser) -{ - bool found = false; - - Lock(); - - // check that the browser isn't already in the list before adding - BrowserList::const_iterator it = browserlist_.begin(); - for(; it != browserlist_.end(); ++it) { - if(it->get() == browser.get()) { - found = true; - break; - } - } - - if(!found) - { - browser->UIT_SetUniqueID(next_browser_id_++); - browserlist_.push_back(browser); - } - - Unlock(); - return !found; -} - -void CefContext::UIT_ClearCache() -{ - webkit_glue::ClearCache(); -} - -bool CefContext::RemoveBrowser(CefRefPtr browser) -{ - bool deleted = false; - bool empty = false; - - Lock(); - - BrowserList::iterator it = browserlist_.begin(); - for(; it != browserlist_.end(); ++it) { - if(it->get() == browser.get()) { - browserlist_.erase(it); - deleted = true; - break; - } - } - - if (browserlist_.empty()) { - next_browser_id_ = 1; - empty = true; - } - - Unlock(); - - if (empty) { - PostTask(FROM_HERE, NewRunnableMethod(_Context.get(), - &CefContext::UIT_ClearCache)); - } - - return deleted; -} - -CefRefPtr CefContext::GetBrowserByID(int id) -{ - CefRefPtr browser; - Lock(); - - BrowserList::const_iterator it = browserlist_.begin(); - for(; it != browserlist_.end(); ++it) { - if(it->get()->UIT_GetUniqueID() == id) { - browser = it->get(); - break; - } - } - - Unlock(); - - return browser; -} - -void CefContext::SetMessageLoopForUI(MessageLoopForUI* loop) -{ - Lock(); - messageloopui_ = loop; - Unlock(); -} - -void CefContext::NotifyEvent() -{ - Lock(); - // Set the event state to signaled so that the waiting thread will be - // released. - if(heventui_) - SetEvent(heventui_); - Unlock(); -} - -void PostTask(const tracked_objects::Location& from_here, Task* task) -{ - if(_Context.get()) { - _Context->Lock(); - MessageLoopForUI *pMsgLoop = _Context->GetMessageLoopForUI(); - if(pMsgLoop) - pMsgLoop->PostTask(from_here, task); - _Context->Unlock(); - } -} diff --git a/libcef/context.h b/libcef/context.h deleted file mode 100644 index 9a1e1e23..00000000 --- a/libcef/context.h +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) 2008 The Chromium Embedded Framework Authors. -// Portions copyright (c) 2006-2008 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef _CONTEXT_H -#define _CONTEXT_H - -#include "../include/cef.h" -#include "base/at_exit.h" -#include "base/message_loop.h" -#include "base/stats_table.h" -#include "gfx/native_widget_types.h" -#include "webkit/glue/webpreferences.h" - -class BrowserWebKitInit; -class CefBrowserImpl; - -class CefContext : public CefThreadSafeBase -{ -public: - typedef std::list > BrowserList; - - CefContext(); - ~CefContext(); - - bool Initialize(bool multi_threaded_message_loop, - const std::wstring& cache_path); - void Shutdown(); - - MessageLoopForUI* GetMessageLoopForUI() { return messageloopui_; } - - HMODULE GetInstanceHandle() { return hinstance_; } - HANDLE GetUIThreadHandle() { return hthreadui_; } - DWORD GetUIThreadId() { return idthreadui_; } - WebPreferences* GetWebPreferences() { return webprefs_; } - - // Retrieve the path at which cache data will be stored on disk. If empty, - // cache data will be stored in-memory. - std::wstring GetCachePath() { return cache_path_; } - - bool AddBrowser(CefRefPtr browser); - bool RemoveBrowser(CefRefPtr browser); - CefRefPtr GetBrowserByID(int id); - BrowserList* GetBrowserList() { return &browserlist_; } - - // Returns true if the calling thread is the same as the UI thread - bool RunningOnUIThread() { return (GetCurrentThreadId() == idthreadui_); } - - //////////////////////////////////////////////////////////// - // ALL UIT_* METHODS MUST ONLY BE CALLED ON THE UI THREAD // - //////////////////////////////////////////////////////////// - - void UIT_RegisterPlugin(struct CefPluginInfo* plugin_info); - void UIT_UnregisterPlugin(struct CefPluginInfo* plugin_info); - void UIT_ClearCache(); - - bool DoWork(); - bool DoDelayedWork(); - bool DoIdleWork(); - - static bool ImplementsThreadSafeReferenceCounting() { return true; } - -private: - void SetMessageLoopForUI(MessageLoopForUI* loop); - void NotifyEvent(); - - bool DoInitialize(); - void DoUninitialize(); - -protected: - bool multi_threaded_message_loop_; - - HMODULE hinstance_; - DWORD idthreadui_; - HANDLE hthreadui_; - HANDLE heventui_; - bool in_transition_; - BrowserList browserlist_; - WebPreferences* webprefs_; - StatsTable* statstable_; - std::wstring cache_path_; - - // MessageLoopForUI will be scoped to the context when running in single - // threaded message loop mode. - MessageLoopForUI* messageloopui_; - - // AtExitManager will be scoped to the context when running in single threaded - // message loop mode. - base::AtExitManager* at_exit_manager_; - - // WebKit implementation class. - BrowserWebKitInit* webkit_init_; - - // Used for assigning unique IDs to browser instances. - int next_browser_id_; - - friend DWORD WINAPI ThreadHandlerUI(LPVOID lpParam); -}; - -// Post a task to the UI message loop -void PostTask(const tracked_objects::Location& from_here, Task* task); - -// Global context object pointer -extern CefRefPtr _Context; - -// Macro for requiring that a function be called on the UI thread -#define REQUIRE_UIT() DCHECK(_Context->RunningOnUIThread()) - -#endif // _CONTEXT_H diff --git a/libcef/scheme_impl.cc b/libcef/scheme_impl.cc index 3bf6d205..6af4f7a2 100644 --- a/libcef/scheme_impl.cc +++ b/libcef/scheme_impl.cc @@ -7,7 +7,6 @@ #include "base/logging.h" #include "base/message_loop.h" #include "base/utf_string_conversions.h" -#include "base/worker_pool.h" #include "googleurl/src/url_util.h" #include "net/base/completion_callback.h" #include "net/base/io_buffer.h" @@ -19,7 +18,7 @@ #include "include/cef.h" #include "tracker.h" -#include "context.h" +#include "cef_context.h" #include "request_impl.h" #include @@ -68,8 +67,8 @@ public: // Continue asynchronously. DCHECK(!async_resolver_); async_resolver_ = new AsyncResolver(this); - WorkerPool::PostTask(FROM_HERE, NewRunnableMethod( - async_resolver_.get(), &AsyncResolver::Resolve, url_), true); + CefThread::PostTask(CefThread::IO, FROM_HERE, NewRunnableMethod( + async_resolver_.get(), &AsyncResolver::Resolve, url_)); return; } @@ -411,7 +410,7 @@ bool CefRegisterScheme(const std::wstring& scheme_name, new SchemeRequestJobWrapper(WideToUTF8(scheme_name), WideToUTF8(host_name), factory)); - PostTask(FROM_HERE, NewRunnableMethod(wrapper.get(), + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(wrapper.get(), &SchemeRequestJobWrapper::RegisterScheme)); return true; diff --git a/libcef/v8_impl.cc b/libcef/v8_impl.cc index b4afa682..912c6be5 100644 --- a/libcef/v8_impl.cc +++ b/libcef/v8_impl.cc @@ -3,7 +3,7 @@ // can be found in the LICENSE file. #include "v8_impl.h" -#include "context.h" +#include "cef_context.h" #include "tracker.h" #include "base/lazy_instance.h" #include "base/utf_string_conversions.h" @@ -156,7 +156,7 @@ bool CefRegisterExtension(const std::wstring& extension_name, ExtensionWrapper* wrapper = new ExtensionWrapper(name->GetString(), code->GetString(), handler.get()); - PostTask(FROM_HERE, NewRunnableMethod(wrapper, + CefThread::PostTask(CefThread::UI, FROM_HERE, NewRunnableMethod(wrapper, &ExtensionWrapper::UIT_RegisterExtension)); return true; } diff --git a/libcef_dll/cpptoc/task_cpptoc.cc b/libcef_dll/cpptoc/task_cpptoc.cc new file mode 100644 index 00000000..c8dd5ba3 --- /dev/null +++ b/libcef_dll/cpptoc/task_cpptoc.cc @@ -0,0 +1,40 @@ +// Copyright (c) 2009 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. +// +// --------------------------------------------------------------------------- +// +// A portion of this file was generated by the CEF translator tool. When +// making changes by hand only do so within the body of existing function +// implementations. See the translator.README.txt file in the tools directory +// for more information. +// + +#include "libcef_dll/cpptoc/task_cpptoc.h" + + +// MEMBER FUNCTIONS - Body may be edited by hand. + +void CEF_CALLBACK task_execute(struct _cef_task_t* self, + cef_thread_id_t threadId) +{ + DCHECK(self); + if(!self) + return; + + CefTaskCppToC::Get(self)->Execute(threadId); +} + + +// CONSTRUCTOR - Do not edit by hand. + +CefTaskCppToC::CefTaskCppToC(CefTask* cls) + : CefCppToC(cls) +{ + struct_.struct_.execute = task_execute; +} + +#ifdef _DEBUG +long CefCppToC::DebugObjCt = 0; +#endif + diff --git a/libcef_dll/cpptoc/task_cpptoc.h b/libcef_dll/cpptoc/task_cpptoc.h new file mode 100644 index 00000000..40a42dec --- /dev/null +++ b/libcef_dll/cpptoc/task_cpptoc.h @@ -0,0 +1,34 @@ +// Copyright (c) 2009 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 was generated by the CEF translator tool and should not edited +// by hand. See the translator.README.txt file in the tools directory for +// more information. +// +#ifndef _TASK_CPPTOC_H +#define _TASK_CPPTOC_H + +#ifndef USING_CEF_SHARED +#pragma message("Warning: "__FILE__" may be accessed wrapper-side only") +#else // USING_CEF_SHARED + +#include "include/cef.h" +#include "include/cef_capi.h" +#include "libcef_dll/cpptoc/cpptoc.h" + +// Wrap a C++ class with a C structure. +// This class may be instantiated and accessed wrapper-side only. +class CefTaskCppToC + : public CefCppToC +{ +public: + CefTaskCppToC(CefTask* cls); + virtual ~CefTaskCppToC() {} +}; + +#endif // USING_CEF_SHARED +#endif // _TASK_CPPTOC_H + diff --git a/libcef_dll/ctocpp/task_ctocpp.cc b/libcef_dll/ctocpp/task_ctocpp.cc new file mode 100644 index 00000000..1597a252 --- /dev/null +++ b/libcef_dll/ctocpp/task_ctocpp.cc @@ -0,0 +1,30 @@ +// Copyright (c) 2009 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. +// +// --------------------------------------------------------------------------- +// +// A portion of this file was generated by the CEF translator tool. When +// making changes by hand only do so within the body of existing static and +// virtual method implementations. See the translator.README.txt file in the +// tools directory for more information. +// + +#include "libcef_dll/ctocpp/task_ctocpp.h" + + +// VIRTUAL METHODS - Body may be edited by hand. + +void CefTaskCToCpp::Execute(CefThreadId threadId) +{ + if(CEF_MEMBER_MISSING(struct_, execute)) + return; + + struct_->execute(struct_, threadId); +} + + +#ifdef _DEBUG +long CefCToCpp::DebugObjCt = 0; +#endif + diff --git a/libcef_dll/ctocpp/task_ctocpp.h b/libcef_dll/ctocpp/task_ctocpp.h new file mode 100644 index 00000000..24f0c2ee --- /dev/null +++ b/libcef_dll/ctocpp/task_ctocpp.h @@ -0,0 +1,39 @@ +// Copyright (c) 2009 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 was generated by the CEF translator tool and should not edited +// by hand. See the translator.README.txt file in the tools directory for +// more information. +// + +#ifndef _TASK_CTOCPP_H +#define _TASK_CTOCPP_H + +#ifndef BUILDING_CEF_SHARED +#pragma message("Warning: "__FILE__" may be accessed DLL-side only") +#else // BUILDING_CEF_SHARED + +#include "include/cef.h" +#include "include/cef_capi.h" +#include "libcef_dll/ctocpp/ctocpp.h" + +// Wrap a C structure with a C++ class. +// This class may be instantiated and accessed DLL-side only. +class CefTaskCToCpp + : public CefCToCpp +{ +public: + CefTaskCToCpp(cef_task_t* str) + : CefCToCpp(str) {} + virtual ~CefTaskCToCpp() {} + + // CefTask methods + virtual void Execute(CefThreadId threadId); +}; + +#endif // BUILDING_CEF_SHARED +#endif // _TASK_CTOCPP_H + diff --git a/libcef_dll/libcef_dll.cc b/libcef_dll/libcef_dll.cc index d0bacf7e..4b034163 100644 --- a/libcef_dll/libcef_dll.cc +++ b/libcef_dll/libcef_dll.cc @@ -18,6 +18,7 @@ #include "ctocpp/read_handler_ctocpp.h" #include "ctocpp/scheme_handler_ctocpp.h" #include "ctocpp/scheme_handler_factory_ctocpp.h" +#include "ctocpp/task_ctocpp.h" #include "ctocpp/v8handler_ctocpp.h" #include "ctocpp/write_handler_ctocpp.h" #include "base/string_util.h" @@ -133,3 +134,28 @@ CEF_EXPORT int cef_register_scheme(const wchar_t* scheme_name, return CefRegisterScheme(nameStr, codeStr, CefSchemeHandlerFactoryCToCpp::Wrap(factory)); } + +CEF_EXPORT int cef_currently_on(cef_thread_id_t threadId) +{ + return CefCurrentlyOn(threadId); +} + +CEF_EXPORT int cef_post_task(cef_thread_id_t threadId, + struct _cef_task_t* task) +{ + DCHECK(task); + if(!task) + return 0; + + return CefPostTask(threadId, CefTaskCToCpp::Wrap(task)); +} + +CEF_EXPORT int cef_post_delayed_task(cef_thread_id_t threadId, + struct _cef_task_t* task, long delay_ms) +{ + DCHECK(task); + if(!task) + return 0; + + return CefPostDelayedTask(threadId, CefTaskCToCpp::Wrap(task), delay_ms); +} \ No newline at end of file diff --git a/libcef_dll/wrapper/libcef_dll_wrapper.cc b/libcef_dll/wrapper/libcef_dll_wrapper.cc index a473fe20..3f42948e 100644 --- a/libcef_dll/wrapper/libcef_dll_wrapper.cc +++ b/libcef_dll/wrapper/libcef_dll_wrapper.cc @@ -10,6 +10,7 @@ #include "libcef_dll/cpptoc/read_handler_cpptoc.h" #include "libcef_dll/cpptoc/scheme_handler_cpptoc.h" #include "libcef_dll/cpptoc/scheme_handler_factory_cpptoc.h" +#include "libcef_dll/cpptoc/task_cpptoc.h" #include "libcef_dll/cpptoc/v8handler_cpptoc.h" #include "libcef_dll/cpptoc/write_handler_cpptoc.h" #include "libcef_dll/ctocpp/browser_ctocpp.h" @@ -111,3 +112,20 @@ bool CefRegisterScheme(const std::wstring& scheme_name, return cef_register_scheme(scheme_name.c_str(), host_name.c_str(), CefSchemeHandlerFactoryCppToC::Wrap(factory))?true:false; } + +bool CefCurrentlyOn(CefThreadId threadId) +{ + return cef_currently_on(threadId)?true:false; +} + +bool CefPostTask(CefThreadId threadId, CefRefPtr task) +{ + return cef_post_task(threadId, CefTaskCppToC::Wrap(task))?true:false; +} + +bool CefPostDelayedTask(CefThreadId threadId, CefRefPtr task, + long delay_ms) +{ + return cef_post_delayed_task(threadId, CefTaskCppToC::Wrap(task), delay_ms)? + true:false; +} diff --git a/tests/cefclient/cefclient.cpp b/tests/cefclient/cefclient.cpp index 4c8164d1..9a401ce2 100644 --- a/tests/cefclient/cefclient.cpp +++ b/tests/cefclient/cefclient.cpp @@ -9,7 +9,6 @@ #include "resource_util.h" #include "scheme_test.h" #include "string_util.h" -#include "thread_util.h" #include "uiplugin_test.h" #include #include @@ -376,12 +375,12 @@ public: new ClientReadHandler(pBytes, dwSize)); mimeType = L"text/html"; } - } else if(wcsstr(url.c_str(), L"/logo.gif") != NULL) { + } else if(wcsstr(url.c_str(), L"/logo1w.png") != NULL) { // Any time we find "logo.gif" in the URL substitute in our own image if(LoadBinaryResource(IDS_LOGO, dwSize, pBytes)) { resourceStream = CefStreamReader::CreateForHandler( new ClientReadHandler(pBytes, dwSize)); - mimeType = L"image/jpg"; + mimeType = L"image/png"; } } else if(wcsstr(url.c_str(), L"/logoball.png") != NULL) { // Load the "logoball.png" image resource. @@ -563,7 +562,7 @@ public: fclose(file); if(first_message) { - // Show the message box on the UI thread. + // Show the message box on the main application thread. PostMessage(m_MainHwnd, WM_COMMAND, ID_WARN_CONSOLEMESSAGE, 0); } } @@ -945,23 +944,21 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) #ifdef TEST_SINGLE_THREADED_MESSAGE_LOOP RunGetSourceTest(browser->GetMainFrame()); #else // !TEST_SINGLE_THREADED_MESSAGE_LOOP - // Execute the GetSource() call on a new worker thread to avoid - // blocking the UI thread when using a multi-threaded message loop + // Execute the GetSource() call on the FILE thread to avoid blocking + // the UI thread when using a multi-threaded message loop // (issue #79). - class ExecHandler : public Thread::Handler + class ExecTask : public CefThreadSafeBase { public: - ExecHandler(CefRefPtr frame) : m_Frame(frame) {} - virtual DWORD Run() + ExecTask(CefRefPtr frame) : m_Frame(frame) {} + virtual void Execute(CefThreadId threadId) { RunGetSourceTest(m_Frame); - return 0; } - virtual void Destroy() { delete this; } private: CefRefPtr m_Frame; }; - Thread::Execute(new ExecHandler(browser->GetMainFrame())); + CefPostTask(TID_FILE, new ExecTask(browser->GetMainFrame())); #endif // !TEST_SINGLE_THREADED_MESSAGE_LOOP } return 0; @@ -970,23 +967,21 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) #ifdef TEST_SINGLE_THREADED_MESSAGE_LOOP RunGetTextTest(browser->GetMainFrame()); #else // !TEST_SINGLE_THREADED_MESSAGE_LOOP - // Execute the GetText() call on a new worker thread to avoid - // blocking the UI thread when using a multi-threaded message loop + // Execute the GetText() call on the FILE thread to avoid blocking + // the UI thread when using a multi-threaded message loop // (issue #79). - class ExecHandler : public Thread::Handler + class ExecTask : public CefThreadSafeBase { public: - ExecHandler(CefRefPtr frame) : m_Frame(frame) {} - virtual DWORD Run() + ExecTask(CefRefPtr frame) : m_Frame(frame) {} + virtual void Execute(CefThreadId threadId) { RunGetTextTest(m_Frame); - return 0; } - virtual void Destroy() { delete this; } private: CefRefPtr m_Frame; }; - Thread::Execute(new ExecHandler(browser->GetMainFrame())); + CefPostTask(TID_FILE, new ExecTask(browser->GetMainFrame())); #endif // !TEST_SINGLE_THREADED_MESSAGE_LOOP } return 0; diff --git a/tests/cefclient/cefclient.rc b/tests/cefclient/cefclient.rc index cb6a5196..57a7aa9b 100644 --- a/tests/cefclient/cefclient.rc +++ b/tests/cefclient/cefclient.rc @@ -28,7 +28,7 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // Binary // -IDS_LOGO BINARY "res\logo.jpg" +IDS_LOGO BINARY "res\logo.png" IDS_UIPLUGIN BINARY "res\uiplugin.html" IDS_LOGOBALL BINARY "res\logoball.png" diff --git a/tests/cefclient/res/logo.jpg b/tests/cefclient/res/logo.jpg deleted file mode 100644 index 1050d1b8440beae62c34ab664d09c8b5e545a275..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9582 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 Tq{K)p{Bra^^ZMVRPmBKtpu;8M diff --git a/tests/cefclient/res/logo.png b/tests/cefclient/res/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..41dd728df08295acf843dce12d9d19a342922df7 GIT binary patch literal 19879 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 eIt+)cS^poYk|IgQ(8)vq0000 - -class Thread -{ -public: - // Interface for thread execution. - class Handler - { - public: - virtual DWORD Run() =0; - virtual void Destroy() =0; - }; - - // Create and execute a new thread for the specified handler. - static HANDLE Execute(Handler* pHandler) - { - if (!pHandler) - return 0; - Thread* pThread = new Thread(pHandler); - return pThread->Execute(); - } - -private: - Thread(Handler* pHandler) : m_hThread(NULL), m_pHandler(pHandler) {} - ~Thread() - { - if (m_hThread) - CloseHandle(m_hThread); - m_pHandler->Destroy(); - } - - HANDLE Execute() - { - m_hThread = CreateThread(NULL, 4096, ThreadHandler, - reinterpret_cast(this), 0, NULL); - if (m_hThread == NULL) { - delete this; - return NULL; - } - return m_hThread; - } - - static DWORD WINAPI ThreadHandler(LPVOID lpThreadParameter) - { - Thread* pThread = reinterpret_cast(lpThreadParameter); - DWORD ret = pThread->m_pHandler->Run(); - delete pThread; - return ret; - } - - Handler* m_pHandler; - HANDLE m_hThread; -}; diff --git a/tools/cef_parser.py b/tools/cef_parser.py index ab971876..3f605631 100644 --- a/tools/cef_parser.py +++ b/tools/cef_parser.py @@ -1072,6 +1072,7 @@ class obj_analysis: 'bool' : 'int', 'CefWindowHandle' : 'cef_window_handle_t', 'CefRect' : 'cef_rect_t', + 'CefThreadId' : 'cef_thread_id_t', } if value in simpletypes.keys(): return {