Linux: Add multi-threaded message loop support (issue #2512)

This commit is contained in:
Marshall Greenblatt
2018-09-20 06:00:14 -07:00
parent c7d187a512
commit 8aa4b7aaae
31 changed files with 1070 additions and 252 deletions

View File

@ -376,6 +376,8 @@
'tests/cefclient/browser/dialog_handler_gtk.cc', 'tests/cefclient/browser/dialog_handler_gtk.cc',
'tests/cefclient/browser/dialog_handler_gtk.h', 'tests/cefclient/browser/dialog_handler_gtk.h',
'tests/cefclient/browser/main_context_impl_posix.cc', 'tests/cefclient/browser/main_context_impl_posix.cc',
'tests/cefclient/browser/main_message_loop_multithreaded_gtk.cc',
'tests/cefclient/browser/main_message_loop_multithreaded_gtk.h',
'tests/cefclient/browser/print_handler_gtk.cc', 'tests/cefclient/browser/print_handler_gtk.cc',
'tests/cefclient/browser/print_handler_gtk.h', 'tests/cefclient/browser/print_handler_gtk.h',
'tests/cefclient/browser/resource_util_linux.cc', 'tests/cefclient/browser/resource_util_linux.cc',
@ -385,6 +387,8 @@
'tests/cefclient/browser/root_window_views.h', 'tests/cefclient/browser/root_window_views.h',
'tests/cefclient/browser/temp_window_x11.cc', 'tests/cefclient/browser/temp_window_x11.cc',
'tests/cefclient/browser/temp_window_x11.h', 'tests/cefclient/browser/temp_window_x11.h',
'tests/cefclient/browser/util_gtk.cc',
'tests/cefclient/browser/util_gtk.h',
'tests/cefclient/browser/views_menu_bar.cc', 'tests/cefclient/browser/views_menu_bar.cc',
'tests/cefclient/browser/views_menu_bar.h', 'tests/cefclient/browser/views_menu_bar.h',
'tests/cefclient/browser/views_style.cc', 'tests/cefclient/browser/views_style.cc',

View File

@ -181,7 +181,7 @@ typedef struct _cef_settings_t {
// Set to true (1) to have the browser process message loop run in a separate // Set to true (1) to have the browser process message loop run in a separate
// thread. If false (0) than the CefDoMessageLoopWork() function must be // thread. If false (0) than the CefDoMessageLoopWork() function must be
// called from your application message loop. This option is only supported on // called from your application message loop. This option is only supported on
// Windows. // Windows and Linux.
/// ///
int multi_threaded_message_loop; int multi_threaded_message_loop;

View File

@ -29,6 +29,7 @@
#include "content/public/browser/render_process_host.h" #include "content/public/browser/render_process_host.h"
#include "content/public/common/content_switches.h" #include "content/public/common/content_switches.h"
#include "services/service_manager/embedder/main.h" #include "services/service_manager/embedder/main.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_switches.h" #include "ui/base/ui_base_switches.h"
#if defined(OS_WIN) #if defined(OS_WIN)
@ -356,7 +357,7 @@ bool CefContext::Initialize(const CefMainArgs& args,
init_thread_id_ = base::PlatformThread::CurrentId(); init_thread_id_ = base::PlatformThread::CurrentId();
settings_ = settings; settings_ = settings;
#if !defined(OS_WIN) #if !(defined(OS_WIN) || defined(OS_LINUX))
if (settings.multi_threaded_message_loop) { if (settings.multi_threaded_message_loop) {
NOTIMPLEMENTED() << "multi_threaded_message_loop is not supported."; NOTIMPLEMENTED() << "multi_threaded_message_loop is not supported.";
return false; return false;
@ -539,6 +540,8 @@ void CefContext::FinishShutdownOnUIThread(
static_cast<ChromeBrowserProcessStub*>(g_browser_process)->Shutdown(); static_cast<ChromeBrowserProcessStub*>(g_browser_process)->Shutdown();
ui::ResourceBundle::GetSharedInstance().CleanupOnUIThread();
if (uithread_shutdown_event) if (uithread_shutdown_event)
uithread_shutdown_event->Signal(); uithread_shutdown_event->Signal();
} }

View File

@ -104,6 +104,7 @@ CefWindowX11::CefWindowX11(CefRefPtr<CefBrowserHostImpl> browser,
InputOutput, InputOutput,
CopyFromParent, // visual CopyFromParent, // visual
CWBackPixmap | CWOverrideRedirect, &swa); CWBackPixmap | CWOverrideRedirect, &swa);
CHECK(xwindow_);
if (ui::PlatformEventSource::GetInstance()) if (ui::PlatformEventSource::GetInstance())
ui::PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this); ui::PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this);

View File

@ -375,6 +375,12 @@ patches = [
# https://bitbucket.org/chromiumembedded/cef/issues/2466 # https://bitbucket.org/chromiumembedded/cef/issues/2466
'name': 'linux_poll_2466', 'name': 'linux_poll_2466',
}, },
{
# Allow ResourceBundle creation/destruction on the main thread and usage on
# the UI thread.
# https://bitbucket.org/chromiumembedded/cef/issues/2398
'name': 'resource_bundle_2512',
},
{ {
# Fix VR target dependencies. # Fix VR target dependencies.
# https://bugs.chromium.org/p/chromium/issues/detail?id=873170 # https://bugs.chromium.org/p/chromium/issues/detail?id=873170

View File

@ -0,0 +1,45 @@
diff --git ui/base/resource/resource_bundle.cc ui/base/resource/resource_bundle.cc
index 2a00d4e..d2328b7 100644
--- ui/base/resource/resource_bundle.cc
+++ ui/base/resource/resource_bundle.cc
@@ -737,6 +737,12 @@ ResourceBundle::ResourceBundle(Delegate* delegate)
: delegate_(delegate),
locale_resources_data_lock_(new base::Lock),
max_scale_factor_(SCALE_FACTOR_100P) {
+ // With CEF's multi-threaded mode the ResourceBundle may be created on the
+ // main thread and then accessed on the UI thread. Allow the SequenceChecker
+ // to re-bind on the UI thread when CalledOnValidSequence() is called for the
+ // first time.
+ DETACH_FROM_SEQUENCE(sequence_checker_);
+
mangle_localized_strings_ = base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kMangleLocalizedStrings);
}
@@ -746,6 +752,11 @@ ResourceBundle::~ResourceBundle() {
UnloadLocaleResources();
}
+void ResourceBundle::CleanupOnUIThread() {
+ FreeImages();
+ font_cache_.clear();
+}
+
// static
void ResourceBundle::InitSharedInstance(Delegate* delegate) {
DCHECK(g_shared_instance_ == NULL) << "ResourceBundle initialized twice";
diff --git ui/base/resource/resource_bundle.h ui/base/resource/resource_bundle.h
index 422d84b..669522fd 100644
--- ui/base/resource/resource_bundle.h
+++ ui/base/resource/resource_bundle.h
@@ -150,6 +150,11 @@ class UI_BASE_EXPORT ResourceBundle {
// Return the global resource loader instance.
static ResourceBundle& GetSharedInstance();
+ // With CEF's multi-threaded mode the ResourceBundle may be created/destroyed
+ // on the main thread but accessed on the UI thread. Call this method on the
+ // UI thread to clean up resources before destruction.
+ void CleanupOnUIThread();
+
// Loads a secondary locale data pack using the given file region.
void LoadSecondaryLocaleDataWithPakFileRegion(
base::File pak_file,

View File

@ -42,6 +42,8 @@ void BrowserWindow::OnBrowserClosing(CefRefPtr<CefBrowser> browser) {
REQUIRE_MAIN_THREAD(); REQUIRE_MAIN_THREAD();
DCHECK_EQ(browser->GetIdentifier(), browser_->GetIdentifier()); DCHECK_EQ(browser->GetIdentifier(), browser_->GetIdentifier());
is_closing_ = true; is_closing_ = true;
delegate_->OnBrowserWindowClosing();
} }
void BrowserWindow::OnBrowserClosed(CefRefPtr<CefBrowser> browser) { void BrowserWindow::OnBrowserClosed(CefRefPtr<CefBrowser> browser) {

View File

@ -25,6 +25,9 @@ class BrowserWindow : public ClientHandler::Delegate {
// Called when the browser has been created. // Called when the browser has been created.
virtual void OnBrowserCreated(CefRefPtr<CefBrowser> browser) = 0; virtual void OnBrowserCreated(CefRefPtr<CefBrowser> browser) = 0;
// Called when the BrowserWindow is closing.
virtual void OnBrowserWindowClosing() {}
// Called when the BrowserWindow has been destroyed. // Called when the BrowserWindow has been destroyed.
virtual void OnBrowserWindowDestroyed() = 0; virtual void OnBrowserWindowDestroyed() = 0;

View File

@ -19,6 +19,7 @@
#include "include/base/cef_logging.h" #include "include/base/cef_logging.h"
#include "include/wrapper/cef_closure_task.h" #include "include/wrapper/cef_closure_task.h"
#include "tests/cefclient/browser/util_gtk.h"
#include "tests/shared/browser/geometry_util.h" #include "tests/shared/browser/geometry_util.h"
#include "tests/shared/browser/main_message_loop.h" #include "tests/shared/browser/main_message_loop.h"
@ -922,6 +923,7 @@ class ScopedGLContext {
bool swap_buffers_; bool swap_buffers_;
GdkGLDrawable* gldrawable_; GdkGLDrawable* gldrawable_;
bool is_valid_; bool is_valid_;
ScopedGdkThreadsEnter scoped_gdk_threads_;
}; };
} // namespace } // namespace
@ -930,23 +932,26 @@ BrowserWindowOsrGtk::BrowserWindowOsrGtk(BrowserWindow::Delegate* delegate,
const std::string& startup_url, const std::string& startup_url,
const OsrRenderer::Settings& settings) const OsrRenderer::Settings& settings)
: BrowserWindow(delegate), : BrowserWindow(delegate),
xdisplay_(nullptr),
renderer_(settings), renderer_(settings),
glarea_(NULL),
hidden_(false),
gl_enabled_(false), gl_enabled_(false),
painting_popup_(false), painting_popup_(false),
device_scale_factor_(1.0f), hidden_(false),
glarea_(NULL),
drag_trigger_event_(NULL), drag_trigger_event_(NULL),
drag_data_(NULL), drag_data_(NULL),
drag_operation_(DRAG_OPERATION_NONE), drag_operation_(DRAG_OPERATION_NONE),
drag_context_(NULL), drag_context_(NULL),
drag_targets_(gtk_target_list_new(NULL, 0)), drag_targets_(gtk_target_list_new(NULL, 0)),
drag_leave_(false), drag_leave_(false),
drag_drop_(false) { drag_drop_(false),
device_scale_factor_(1.0f) {
client_handler_ = new ClientHandlerOsr(this, this, startup_url); client_handler_ = new ClientHandlerOsr(this, this, startup_url);
} }
BrowserWindowOsrGtk::~BrowserWindowOsrGtk() { BrowserWindowOsrGtk::~BrowserWindowOsrGtk() {
ScopedGdkThreadsEnter scoped_gdk_threads;
if (drag_trigger_event_) { if (drag_trigger_event_) {
gdk_event_free(drag_trigger_event_); gdk_event_free(drag_trigger_event_);
} }
@ -956,6 +961,12 @@ BrowserWindowOsrGtk::~BrowserWindowOsrGtk() {
gtk_target_list_unref(drag_targets_); gtk_target_list_unref(drag_targets_);
} }
void BrowserWindowOsrGtk::set_xdisplay(XDisplay* xdisplay) {
REQUIRE_MAIN_THREAD();
DCHECK(!xdisplay_);
xdisplay_ = xdisplay;
}
void BrowserWindowOsrGtk::CreateBrowser( void BrowserWindowOsrGtk::CreateBrowser(
ClientWindowHandle parent_handle, ClientWindowHandle parent_handle,
const CefRect& rect, const CefRect& rect,
@ -966,6 +977,8 @@ void BrowserWindowOsrGtk::CreateBrowser(
// Create the native window. // Create the native window.
Create(parent_handle); Create(parent_handle);
ScopedGdkThreadsEnter scoped_gdk_threads;
// Retrieve the X11 Window ID for the GTK parent window. // Retrieve the X11 Window ID for the GTK parent window.
GtkWidget* window = GtkWidget* window =
gtk_widget_get_ancestor(GTK_WIDGET(parent_handle), GTK_TYPE_WINDOW); gtk_widget_get_ancestor(GTK_WIDGET(parent_handle), GTK_TYPE_WINDOW);
@ -1045,12 +1058,16 @@ void BrowserWindowOsrGtk::SetBounds(int x, int y, size_t width, size_t height) {
void BrowserWindowOsrGtk::SetFocus(bool focus) { void BrowserWindowOsrGtk::SetFocus(bool focus) {
REQUIRE_MAIN_THREAD(); REQUIRE_MAIN_THREAD();
if (glarea_ && focus) ScopedGdkThreadsEnter scoped_gdk_threads;
if (glarea_ && focus) {
gtk_widget_grab_focus(glarea_); gtk_widget_grab_focus(glarea_);
} }
}
void BrowserWindowOsrGtk::SetDeviceScaleFactor(float device_scale_factor) { void BrowserWindowOsrGtk::SetDeviceScaleFactor(float device_scale_factor) {
REQUIRE_MAIN_THREAD(); REQUIRE_MAIN_THREAD();
{
base::AutoLock lock_scope(lock_);
if (device_scale_factor == device_scale_factor_) if (device_scale_factor == device_scale_factor_)
return; return;
@ -1059,6 +1076,7 @@ void BrowserWindowOsrGtk::SetDeviceScaleFactor(float device_scale_factor) {
return; return;
device_scale_factor_ = device_scale_factor; device_scale_factor_ = device_scale_factor;
}
if (browser_) { if (browser_) {
browser_->GetHost()->NotifyScreenInfoChanged(); browser_->GetHost()->NotifyScreenInfoChanged();
@ -1068,6 +1086,7 @@ void BrowserWindowOsrGtk::SetDeviceScaleFactor(float device_scale_factor) {
float BrowserWindowOsrGtk::GetDeviceScaleFactor() const { float BrowserWindowOsrGtk::GetDeviceScaleFactor() const {
REQUIRE_MAIN_THREAD(); REQUIRE_MAIN_THREAD();
base::AutoLock lock_scope(lock_);
return device_scale_factor_; return device_scale_factor_;
} }
@ -1082,11 +1101,12 @@ void BrowserWindowOsrGtk::OnAfterCreated(CefRefPtr<CefBrowser> browser) {
void BrowserWindowOsrGtk::OnBeforeClose(CefRefPtr<CefBrowser> browser) { void BrowserWindowOsrGtk::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
CEF_REQUIRE_UI_THREAD(); CEF_REQUIRE_UI_THREAD();
REQUIRE_MAIN_THREAD();
// Detach |this| from the ClientHandlerOsr. // Detach |this| from the ClientHandlerOsr.
static_cast<ClientHandlerOsr*>(client_handler_.get())->DetachOsrDelegate(); static_cast<ClientHandlerOsr*>(client_handler_.get())->DetachOsrDelegate();
ScopedGdkThreadsEnter scoped_gdk_threads;
UnregisterDragDrop(); UnregisterDragDrop();
// Disconnect all signal handlers that reference |this|. // Disconnect all signal handlers that reference |this|.
@ -1105,17 +1125,22 @@ bool BrowserWindowOsrGtk::GetRootScreenRect(CefRefPtr<CefBrowser> browser,
bool BrowserWindowOsrGtk::GetViewRect(CefRefPtr<CefBrowser> browser, bool BrowserWindowOsrGtk::GetViewRect(CefRefPtr<CefBrowser> browser,
CefRect& rect) { CefRect& rect) {
CEF_REQUIRE_UI_THREAD(); CEF_REQUIRE_UI_THREAD();
REQUIRE_MAIN_THREAD();
if (!glarea_) if (!glarea_)
return false; return false;
float device_scale_factor;
{
base::AutoLock lock_scope(lock_);
device_scale_factor = device_scale_factor_;
}
// The simulated screen and view rectangle are the same. This is necessary // The simulated screen and view rectangle are the same. This is necessary
// for popup menus to be located and sized inside the view. // for popup menus to be located and sized inside the view.
rect.x = rect.y = 0; rect.x = rect.y = 0;
rect.width = DeviceToLogical(glarea_->allocation.width, device_scale_factor_); rect.width = DeviceToLogical(glarea_->allocation.width, device_scale_factor);
rect.height = rect.height =
DeviceToLogical(glarea_->allocation.height, device_scale_factor_); DeviceToLogical(glarea_->allocation.height, device_scale_factor);
return true; return true;
} }
@ -1125,12 +1150,17 @@ bool BrowserWindowOsrGtk::GetScreenPoint(CefRefPtr<CefBrowser> browser,
int& screenX, int& screenX,
int& screenY) { int& screenY) {
CEF_REQUIRE_UI_THREAD(); CEF_REQUIRE_UI_THREAD();
REQUIRE_MAIN_THREAD();
float device_scale_factor;
{
base::AutoLock lock_scope(lock_);
device_scale_factor = device_scale_factor_;
}
GdkRectangle screen_rect; GdkRectangle screen_rect;
GetWidgetRectInScreen(glarea_, &screen_rect); GetWidgetRectInScreen(glarea_, &screen_rect);
screenX = screen_rect.x + LogicalToDevice(viewX, device_scale_factor_); screenX = screen_rect.x + LogicalToDevice(viewX, device_scale_factor);
screenY = screen_rect.y + LogicalToDevice(viewY, device_scale_factor_); screenY = screen_rect.y + LogicalToDevice(viewY, device_scale_factor);
return true; return true;
} }
@ -1141,7 +1171,13 @@ bool BrowserWindowOsrGtk::GetScreenInfo(CefRefPtr<CefBrowser> browser,
CefRect view_rect; CefRect view_rect;
GetViewRect(browser, view_rect); GetViewRect(browser, view_rect);
screen_info.device_scale_factor = device_scale_factor_; float device_scale_factor;
{
base::AutoLock lock_scope(lock_);
device_scale_factor = device_scale_factor_;
}
screen_info.device_scale_factor = device_scale_factor;
// The screen info rectangles are used by the renderer to create and position // The screen info rectangles are used by the renderer to create and position
// popups. Keep popups inside the view rectangle. // popups. Keep popups inside the view rectangle.
@ -1153,7 +1189,6 @@ bool BrowserWindowOsrGtk::GetScreenInfo(CefRefPtr<CefBrowser> browser,
void BrowserWindowOsrGtk::OnPopupShow(CefRefPtr<CefBrowser> browser, void BrowserWindowOsrGtk::OnPopupShow(CefRefPtr<CefBrowser> browser,
bool show) { bool show) {
CEF_REQUIRE_UI_THREAD(); CEF_REQUIRE_UI_THREAD();
REQUIRE_MAIN_THREAD();
if (!show) { if (!show) {
renderer_.ClearPopupRects(); renderer_.ClearPopupRects();
@ -1165,9 +1200,14 @@ void BrowserWindowOsrGtk::OnPopupShow(CefRefPtr<CefBrowser> browser,
void BrowserWindowOsrGtk::OnPopupSize(CefRefPtr<CefBrowser> browser, void BrowserWindowOsrGtk::OnPopupSize(CefRefPtr<CefBrowser> browser,
const CefRect& rect) { const CefRect& rect) {
CEF_REQUIRE_UI_THREAD(); CEF_REQUIRE_UI_THREAD();
REQUIRE_MAIN_THREAD();
renderer_.OnPopupSize(browser, LogicalToDevice(rect, device_scale_factor_)); float device_scale_factor;
{
base::AutoLock lock_scope(lock_);
device_scale_factor = device_scale_factor_;
}
renderer_.OnPopupSize(browser, LogicalToDevice(rect, device_scale_factor));
} }
void BrowserWindowOsrGtk::OnPaint(CefRefPtr<CefBrowser> browser, void BrowserWindowOsrGtk::OnPaint(CefRefPtr<CefBrowser> browser,
@ -1177,7 +1217,6 @@ void BrowserWindowOsrGtk::OnPaint(CefRefPtr<CefBrowser> browser,
int width, int width,
int height) { int height) {
CEF_REQUIRE_UI_THREAD(); CEF_REQUIRE_UI_THREAD();
REQUIRE_MAIN_THREAD();
if (width <= 2 && height <= 2) { if (width <= 2 && height <= 2) {
// Ignore really small buffer sizes while the widget is starting up. // Ignore really small buffer sizes while the widget is starting up.
@ -1211,17 +1250,17 @@ void BrowserWindowOsrGtk::OnCursorChange(
CefRenderHandler::CursorType type, CefRenderHandler::CursorType type,
const CefCursorInfo& custom_cursor_info) { const CefCursorInfo& custom_cursor_info) {
CEF_REQUIRE_UI_THREAD(); CEF_REQUIRE_UI_THREAD();
REQUIRE_MAIN_THREAD();
// Retrieve the X11 display shared with Chromium. // Retrieve the X11 display shared with Chromium.
::Display* xdisplay = cef_get_xdisplay(); CHECK(xdisplay_ != 0);
DCHECK(xdisplay);
ScopedGdkThreadsEnter scoped_gdk_threads;
// Retrieve the X11 window handle for the GTK widget. // Retrieve the X11 window handle for the GTK widget.
::Window xwindow = GDK_WINDOW_XID(gtk_widget_get_window(glarea_)); ::Window xwindow = GDK_WINDOW_XID(gtk_widget_get_window(glarea_));
// Set the cursor. // Set the cursor.
XDefineCursor(xdisplay, xwindow, cursor); XDefineCursor(xdisplay_, xwindow, cursor);
} }
bool BrowserWindowOsrGtk::StartDragging( bool BrowserWindowOsrGtk::StartDragging(
@ -1231,7 +1270,6 @@ bool BrowserWindowOsrGtk::StartDragging(
int x, int x,
int y) { int y) {
CEF_REQUIRE_UI_THREAD(); CEF_REQUIRE_UI_THREAD();
REQUIRE_MAIN_THREAD();
if (!drag_data->HasImage()) { if (!drag_data->HasImage()) {
LOG(ERROR) << "Drag image representation not available"; LOG(ERROR) << "Drag image representation not available";
@ -1241,6 +1279,8 @@ bool BrowserWindowOsrGtk::StartDragging(
DragReset(); DragReset();
drag_data_ = drag_data; drag_data_ = drag_data;
ScopedGdkThreadsEnter scoped_gdk_threads;
// Begin drag. // Begin drag.
if (drag_trigger_event_) { if (drag_trigger_event_) {
LOG(ERROR) << "Dragging started, but last mouse event is missing"; LOG(ERROR) << "Dragging started, but last mouse event is missing";
@ -1271,7 +1311,6 @@ void BrowserWindowOsrGtk::UpdateDragCursor(
CefRefPtr<CefBrowser> browser, CefRefPtr<CefBrowser> browser,
CefRenderHandler::DragOperation operation) { CefRenderHandler::DragOperation operation) {
CEF_REQUIRE_UI_THREAD(); CEF_REQUIRE_UI_THREAD();
REQUIRE_MAIN_THREAD();
drag_operation_ = operation; drag_operation_ = operation;
} }
@ -1290,6 +1329,8 @@ void BrowserWindowOsrGtk::Create(ClientWindowHandle parent_handle) {
REQUIRE_MAIN_THREAD(); REQUIRE_MAIN_THREAD();
DCHECK(!glarea_); DCHECK(!glarea_);
ScopedGdkThreadsEnter scoped_gdk_threads;
glarea_ = gtk_drawing_area_new(); glarea_ = gtk_drawing_area_new();
DCHECK(glarea_); DCHECK(glarea_);
@ -1344,8 +1385,7 @@ void BrowserWindowOsrGtk::Create(ClientWindowHandle parent_handle) {
gint BrowserWindowOsrGtk::SizeAllocation(GtkWidget* widget, gint BrowserWindowOsrGtk::SizeAllocation(GtkWidget* widget,
GtkAllocation* allocation, GtkAllocation* allocation,
BrowserWindowOsrGtk* self) { BrowserWindowOsrGtk* self) {
REQUIRE_MAIN_THREAD(); CEF_REQUIRE_UI_THREAD();
if (self->browser_.get()) { if (self->browser_.get()) {
// Results in a call to GetViewRect(). // Results in a call to GetViewRect().
self->browser_->GetHost()->WasResized(); self->browser_->GetHost()->WasResized();
@ -1357,7 +1397,7 @@ gint BrowserWindowOsrGtk::SizeAllocation(GtkWidget* widget,
gint BrowserWindowOsrGtk::ClickEvent(GtkWidget* widget, gint BrowserWindowOsrGtk::ClickEvent(GtkWidget* widget,
GdkEventButton* event, GdkEventButton* event,
BrowserWindowOsrGtk* self) { BrowserWindowOsrGtk* self) {
REQUIRE_MAIN_THREAD(); CEF_REQUIRE_UI_THREAD();
if (!self->browser_.get()) if (!self->browser_.get())
return TRUE; return TRUE;
@ -1379,16 +1419,23 @@ gint BrowserWindowOsrGtk::ClickEvent(GtkWidget* widget,
return FALSE; return FALSE;
} }
float device_scale_factor;
{
base::AutoLock lock_scope(self->lock_);
device_scale_factor = self->device_scale_factor_;
}
CefMouseEvent mouse_event; CefMouseEvent mouse_event;
mouse_event.x = event->x; mouse_event.x = event->x;
mouse_event.y = event->y; mouse_event.y = event->y;
self->ApplyPopupOffset(mouse_event.x, mouse_event.y); self->ApplyPopupOffset(mouse_event.x, mouse_event.y);
DeviceToLogical(mouse_event, self->device_scale_factor_); DeviceToLogical(mouse_event, device_scale_factor);
mouse_event.modifiers = GetCefStateModifiers(event->state); mouse_event.modifiers = GetCefStateModifiers(event->state);
bool mouse_up = (event->type == GDK_BUTTON_RELEASE); bool mouse_up = (event->type == GDK_BUTTON_RELEASE);
if (!mouse_up) if (!mouse_up) {
gtk_widget_grab_focus(widget); gtk_widget_grab_focus(widget);
}
int click_count = 1; int click_count = 1;
switch (event->type) { switch (event->type) {
@ -1420,7 +1467,7 @@ gint BrowserWindowOsrGtk::ClickEvent(GtkWidget* widget,
gint BrowserWindowOsrGtk::KeyEvent(GtkWidget* widget, gint BrowserWindowOsrGtk::KeyEvent(GtkWidget* widget,
GdkEventKey* event, GdkEventKey* event,
BrowserWindowOsrGtk* self) { BrowserWindowOsrGtk* self) {
REQUIRE_MAIN_THREAD(); CEF_REQUIRE_UI_THREAD();
if (!self->browser_.get()) if (!self->browser_.get())
return TRUE; return TRUE;
@ -1476,7 +1523,7 @@ gint BrowserWindowOsrGtk::KeyEvent(GtkWidget* widget,
gint BrowserWindowOsrGtk::MoveEvent(GtkWidget* widget, gint BrowserWindowOsrGtk::MoveEvent(GtkWidget* widget,
GdkEventMotion* event, GdkEventMotion* event,
BrowserWindowOsrGtk* self) { BrowserWindowOsrGtk* self) {
REQUIRE_MAIN_THREAD(); CEF_REQUIRE_UI_THREAD();
if (!self->browser_.get()) if (!self->browser_.get())
return TRUE; return TRUE;
@ -1500,11 +1547,17 @@ gint BrowserWindowOsrGtk::MoveEvent(GtkWidget* widget,
} }
} }
float device_scale_factor;
{
base::AutoLock lock_scope(self->lock_);
device_scale_factor = self->device_scale_factor_;
}
CefMouseEvent mouse_event; CefMouseEvent mouse_event;
mouse_event.x = x; mouse_event.x = x;
mouse_event.y = y; mouse_event.y = y;
self->ApplyPopupOffset(mouse_event.x, mouse_event.y); self->ApplyPopupOffset(mouse_event.x, mouse_event.y);
DeviceToLogical(mouse_event, self->device_scale_factor_); DeviceToLogical(mouse_event, device_scale_factor);
mouse_event.modifiers = GetCefStateModifiers(state); mouse_event.modifiers = GetCefStateModifiers(state);
bool mouse_leave = (event->type == GDK_LEAVE_NOTIFY); bool mouse_leave = (event->type == GDK_LEAVE_NOTIFY);
@ -1527,18 +1580,24 @@ gint BrowserWindowOsrGtk::MoveEvent(GtkWidget* widget,
gint BrowserWindowOsrGtk::ScrollEvent(GtkWidget* widget, gint BrowserWindowOsrGtk::ScrollEvent(GtkWidget* widget,
GdkEventScroll* event, GdkEventScroll* event,
BrowserWindowOsrGtk* self) { BrowserWindowOsrGtk* self) {
REQUIRE_MAIN_THREAD(); CEF_REQUIRE_UI_THREAD();
if (!self->browser_.get()) if (!self->browser_.get())
return TRUE; return TRUE;
CefRefPtr<CefBrowserHost> host = self->browser_->GetHost(); CefRefPtr<CefBrowserHost> host = self->browser_->GetHost();
float device_scale_factor;
{
base::AutoLock lock_scope(self->lock_);
device_scale_factor = self->device_scale_factor_;
}
CefMouseEvent mouse_event; CefMouseEvent mouse_event;
mouse_event.x = event->x; mouse_event.x = event->x;
mouse_event.y = event->y; mouse_event.y = event->y;
self->ApplyPopupOffset(mouse_event.x, mouse_event.y); self->ApplyPopupOffset(mouse_event.x, mouse_event.y);
DeviceToLogical(mouse_event, self->device_scale_factor_); DeviceToLogical(mouse_event, device_scale_factor);
mouse_event.modifiers = GetCefStateModifiers(event->state); mouse_event.modifiers = GetCefStateModifiers(event->state);
static const int scrollbarPixelsPerGtkTick = 40; static const int scrollbarPixelsPerGtkTick = 40;
@ -1567,8 +1626,7 @@ gint BrowserWindowOsrGtk::ScrollEvent(GtkWidget* widget,
gint BrowserWindowOsrGtk::FocusEvent(GtkWidget* widget, gint BrowserWindowOsrGtk::FocusEvent(GtkWidget* widget,
GdkEventFocus* event, GdkEventFocus* event,
BrowserWindowOsrGtk* self) { BrowserWindowOsrGtk* self) {
REQUIRE_MAIN_THREAD(); // May be called on the main thread and the UI thread.
if (self->browser_.get()) if (self->browser_.get())
self->browser_->GetHost()->SendFocusEvent(event->in == TRUE); self->browser_->GetHost()->SendFocusEvent(event->in == TRUE);
return TRUE; return TRUE;
@ -1597,7 +1655,7 @@ void BrowserWindowOsrGtk::ApplyPopupOffset(int& x, int& y) const {
} }
void BrowserWindowOsrGtk::EnableGL() { void BrowserWindowOsrGtk::EnableGL() {
REQUIRE_MAIN_THREAD(); CEF_REQUIRE_UI_THREAD();
if (gl_enabled_) if (gl_enabled_)
return; return;
@ -1612,7 +1670,7 @@ void BrowserWindowOsrGtk::EnableGL() {
} }
void BrowserWindowOsrGtk::DisableGL() { void BrowserWindowOsrGtk::DisableGL() {
REQUIRE_MAIN_THREAD(); CEF_REQUIRE_UI_THREAD();
if (!gl_enabled_) if (!gl_enabled_)
return; return;
@ -1629,6 +1687,8 @@ void BrowserWindowOsrGtk::DisableGL() {
void BrowserWindowOsrGtk::RegisterDragDrop() { void BrowserWindowOsrGtk::RegisterDragDrop() {
REQUIRE_MAIN_THREAD(); REQUIRE_MAIN_THREAD();
ScopedGdkThreadsEnter scoped_gdk_threads;
// Succession of CEF d&d calls: // Succession of CEF d&d calls:
// 1. DragTargetDragEnter // 1. DragTargetDragEnter
// 2. DragTargetDragOver // 2. DragTargetDragOver
@ -1679,14 +1739,15 @@ void BrowserWindowOsrGtk::RegisterDragDrop() {
} }
void BrowserWindowOsrGtk::UnregisterDragDrop() { void BrowserWindowOsrGtk::UnregisterDragDrop() {
REQUIRE_MAIN_THREAD(); CEF_REQUIRE_UI_THREAD();
ScopedGdkThreadsEnter scoped_gdk_threads;
gtk_drag_dest_unset(glarea_); gtk_drag_dest_unset(glarea_);
// Drag events are unregistered in OnBeforeClose by calling // Drag events are unregistered in OnBeforeClose by calling
// g_signal_handlers_disconnect_matched. // g_signal_handlers_disconnect_matched.
} }
void BrowserWindowOsrGtk::DragReset() { void BrowserWindowOsrGtk::DragReset() {
REQUIRE_MAIN_THREAD(); CEF_REQUIRE_UI_THREAD();
if (drag_trigger_event_) { if (drag_trigger_event_) {
gdk_event_free(drag_trigger_event_); gdk_event_free(drag_trigger_event_);
drag_trigger_event_ = NULL; drag_trigger_event_ = NULL;
@ -1705,22 +1766,30 @@ void BrowserWindowOsrGtk::DragReset() {
void BrowserWindowOsrGtk::DragBegin(GtkWidget* widget, void BrowserWindowOsrGtk::DragBegin(GtkWidget* widget,
GdkDragContext* drag_context, GdkDragContext* drag_context,
BrowserWindowOsrGtk* self) { BrowserWindowOsrGtk* self) {
REQUIRE_MAIN_THREAD(); CEF_REQUIRE_UI_THREAD();
// Load drag icon. // Load drag icon.
if (!self->drag_data_->HasImage()) { if (!self->drag_data_->HasImage()) {
LOG(ERROR) << "Failed to set drag icon, drag image not available"; LOG(ERROR) << "Failed to set drag icon, drag image not available";
return; return;
} }
float device_scale_factor;
{
base::AutoLock lock_scope(self->lock_);
device_scale_factor = self->device_scale_factor_;
}
int pixel_width = 0; int pixel_width = 0;
int pixel_height = 0; int pixel_height = 0;
CefRefPtr<CefBinaryValue> image_binary = CefRefPtr<CefBinaryValue> image_binary =
self->drag_data_->GetImage()->GetAsPNG(self->device_scale_factor_, true, self->drag_data_->GetImage()->GetAsPNG(device_scale_factor, true,
pixel_width, pixel_height); pixel_width, pixel_height);
if (!image_binary) { if (!image_binary) {
LOG(ERROR) << "Failed to set drag icon, drag image error"; LOG(ERROR) << "Failed to set drag icon, drag image error";
return; return;
} }
size_t image_size = image_binary->GetSize(); size_t image_size = image_binary->GetSize();
guint8* image_buffer = (guint8*)malloc(image_size); // must free guint8* image_buffer = (guint8*)malloc(image_size); // must free
image_binary->GetData((void*)image_buffer, image_size, 0); image_binary->GetData((void*)image_buffer, image_size, 0);
@ -1768,7 +1837,7 @@ void BrowserWindowOsrGtk::DragDataGet(GtkWidget* widget,
guint info, guint info,
guint time, guint time,
BrowserWindowOsrGtk* self) { BrowserWindowOsrGtk* self) {
REQUIRE_MAIN_THREAD(); CEF_REQUIRE_UI_THREAD();
// No drag targets are set so this callback is never called. // No drag targets are set so this callback is never called.
} }
@ -1776,7 +1845,7 @@ void BrowserWindowOsrGtk::DragDataGet(GtkWidget* widget,
void BrowserWindowOsrGtk::DragEnd(GtkWidget* widget, void BrowserWindowOsrGtk::DragEnd(GtkWidget* widget,
GdkDragContext* drag_context, GdkDragContext* drag_context,
BrowserWindowOsrGtk* self) { BrowserWindowOsrGtk* self) {
REQUIRE_MAIN_THREAD(); CEF_REQUIRE_UI_THREAD();
if (self->browser_) { if (self->browser_) {
// Sometimes there is DragEnd event generated without prior DragDrop. // Sometimes there is DragEnd event generated without prior DragDrop.
@ -1799,7 +1868,13 @@ gboolean BrowserWindowOsrGtk::DragMotion(GtkWidget* widget,
gint y, gint y,
guint time, guint time,
BrowserWindowOsrGtk* self) { BrowserWindowOsrGtk* self) {
REQUIRE_MAIN_THREAD(); CEF_REQUIRE_UI_THREAD();
float device_scale_factor;
{
base::AutoLock lock_scope(self->lock_);
device_scale_factor = self->device_scale_factor_;
}
// MoveEvent is never called during drag & drop, so must call // MoveEvent is never called during drag & drop, so must call
// SendMouseMoveEvent here. // SendMouseMoveEvent here.
@ -1808,7 +1883,7 @@ gboolean BrowserWindowOsrGtk::DragMotion(GtkWidget* widget,
mouse_event.y = y; mouse_event.y = y;
mouse_event.modifiers = EVENTFLAG_LEFT_MOUSE_BUTTON; mouse_event.modifiers = EVENTFLAG_LEFT_MOUSE_BUTTON;
self->ApplyPopupOffset(mouse_event.x, mouse_event.y); self->ApplyPopupOffset(mouse_event.x, mouse_event.y);
DeviceToLogical(mouse_event, self->device_scale_factor_); DeviceToLogical(mouse_event, device_scale_factor);
if (self->browser_) { if (self->browser_) {
bool mouse_leave = self->drag_leave_; bool mouse_leave = self->drag_leave_;
self->browser_->GetHost()->SendMouseMoveEvent(mouse_event, mouse_leave); self->browser_->GetHost()->SendMouseMoveEvent(mouse_event, mouse_leave);
@ -1853,7 +1928,7 @@ void BrowserWindowOsrGtk::DragLeave(GtkWidget* widget,
GdkDragContext* drag_context, GdkDragContext* drag_context,
guint time, guint time,
BrowserWindowOsrGtk* self) { BrowserWindowOsrGtk* self) {
REQUIRE_MAIN_THREAD(); CEF_REQUIRE_UI_THREAD();
// There is no drag-enter event in GTK. The first drag-motion event // There is no drag-enter event in GTK. The first drag-motion event
// after drag-leave will be a drag-enter event. // after drag-leave will be a drag-enter event.
@ -1875,7 +1950,7 @@ gboolean BrowserWindowOsrGtk::DragFailed(GtkWidget* widget,
GdkDragContext* drag_context, GdkDragContext* drag_context,
GtkDragResult result, GtkDragResult result,
BrowserWindowOsrGtk* self) { BrowserWindowOsrGtk* self) {
REQUIRE_MAIN_THREAD(); CEF_REQUIRE_UI_THREAD();
// Send drag end coordinates and system drag ended event. // Send drag end coordinates and system drag ended event.
if (self->browser_) { if (self->browser_) {
@ -1895,7 +1970,7 @@ gboolean BrowserWindowOsrGtk::DragDrop(GtkWidget* widget,
gint y, gint y,
guint time, guint time,
BrowserWindowOsrGtk* self) { BrowserWindowOsrGtk* self) {
REQUIRE_MAIN_THREAD(); CEF_REQUIRE_UI_THREAD();
// Finish GTK drag. // Finish GTK drag.
gtk_drag_finish(drag_context, TRUE, FALSE, time); gtk_drag_finish(drag_context, TRUE, FALSE, time);
@ -1939,7 +2014,7 @@ void BrowserWindowOsrGtk::DragDataReceived(GtkWidget* widget,
guint info, guint info,
guint time, guint time,
BrowserWindowOsrGtk* self) { BrowserWindowOsrGtk* self) {
REQUIRE_MAIN_THREAD(); CEF_REQUIRE_UI_THREAD();
// This callback is never called because DragDrop does not call // This callback is never called because DragDrop does not call
// gtk_drag_get_data, as only dragging inside web view is supported. // gtk_drag_get_data, as only dragging inside web view is supported.
} }

View File

@ -6,6 +6,8 @@
#define CEF_TESTS_CEFCLIENT_BROWSER_BROWSER_WINDOW_OSR_GTK_H_ #define CEF_TESTS_CEFCLIENT_BROWSER_BROWSER_WINDOW_OSR_GTK_H_
#pragma once #pragma once
#include "include/base/cef_lock.h"
#include "tests/cefclient/browser/browser_window.h" #include "tests/cefclient/browser/browser_window.h"
#include "tests/cefclient/browser/client_handler_osr.h" #include "tests/cefclient/browser/client_handler_osr.h"
#include "tests/cefclient/browser/osr_renderer.h" #include "tests/cefclient/browser/osr_renderer.h"
@ -24,6 +26,9 @@ class BrowserWindowOsrGtk : public BrowserWindow,
const std::string& startup_url, const std::string& startup_url,
const OsrRenderer::Settings& settings); const OsrRenderer::Settings& settings);
// Called from RootWindowGtk::CreateRootWindow before CreateBrowser.
void set_xdisplay(XDisplay* xdisplay);
// BrowserWindow methods. // BrowserWindow methods.
void CreateBrowser(ClientWindowHandle parent_handle, void CreateBrowser(ClientWindowHandle parent_handle,
const CefRect& rect, const CefRect& rect,
@ -162,15 +167,18 @@ class BrowserWindowOsrGtk : public BrowserWindow,
guint time, guint time,
BrowserWindowOsrGtk* self); BrowserWindowOsrGtk* self);
// The below members will only be accessed on the main thread which should be XDisplay* xdisplay_;
// the same as the CEF UI thread.
// Members only accessed on the UI thread.
OsrRenderer renderer_; OsrRenderer renderer_;
ClientWindowHandle glarea_;
bool hidden_;
bool gl_enabled_; bool gl_enabled_;
bool painting_popup_; bool painting_popup_;
float device_scale_factor_; // Members only accessed on the main thread.
bool hidden_;
// Members protected by the GDK global lock.
ClientWindowHandle glarea_;
// Drag & drop // Drag & drop
GdkEvent* drag_trigger_event_; // mouse event, a possible trigger for drag GdkEvent* drag_trigger_event_; // mouse event, a possible trigger for drag
@ -181,6 +189,11 @@ class BrowserWindowOsrGtk : public BrowserWindow,
bool drag_leave_; bool drag_leave_;
bool drag_drop_; bool drag_drop_;
mutable base::Lock lock_;
// Access to these members must be protected by |lock_|.
float device_scale_factor_;
DISALLOW_COPY_AND_ASSIGN(BrowserWindowOsrGtk); DISALLOW_COPY_AND_ASSIGN(BrowserWindowOsrGtk);
}; };

View File

@ -14,6 +14,7 @@
#include "include/base/cef_logging.h" #include "include/base/cef_logging.h"
#include "tests/cefclient/browser/client_handler_std.h" #include "tests/cefclient/browser/client_handler_std.h"
#include "tests/cefclient/browser/util_gtk.h"
#include "tests/shared/browser/main_message_loop.h" #include "tests/shared/browser/main_message_loop.h"
namespace client { namespace client {
@ -21,14 +22,16 @@ namespace client {
namespace { namespace {
::Window GetXWindowForWidget(GtkWidget* widget) { ::Window GetXWindowForWidget(GtkWidget* widget) {
ScopedGdkThreadsEnter scoped_gdk_threads;
// The GTK window must be visible before we can retrieve the XID. // The GTK window must be visible before we can retrieve the XID.
::Window xwindow = GDK_WINDOW_XID(gtk_widget_get_window(widget)); ::Window xwindow = GDK_WINDOW_XID(gtk_widget_get_window(widget));
DCHECK(xwindow); DCHECK(xwindow);
return xwindow; return xwindow;
} }
void SetXWindowVisible(::Window xwindow, bool visible) { void SetXWindowVisible(XDisplay* xdisplay, ::Window xwindow, bool visible) {
::Display* xdisplay = cef_get_xdisplay(); CHECK(xdisplay != 0);
// Retrieve the atoms required by the below XChangeProperty call. // Retrieve the atoms required by the below XChangeProperty call.
const char* kAtoms[] = {"_NET_WM_STATE", "ATOM", "_NET_WM_STATE_HIDDEN"}; const char* kAtoms[] = {"_NET_WM_STATE", "ATOM", "_NET_WM_STATE_HIDDEN"};
@ -61,12 +64,13 @@ void SetXWindowVisible(::Window xwindow, bool visible) {
} }
} }
void SetXWindowBounds(::Window xwindow, void SetXWindowBounds(XDisplay* xdisplay,
::Window xwindow,
int x, int x,
int y, int y,
size_t width, size_t width,
size_t height) { size_t height) {
::Display* xdisplay = cef_get_xdisplay(); CHECK(xdisplay != 0);
XWindowChanges changes = {0}; XWindowChanges changes = {0};
changes.x = x; changes.x = x;
changes.y = y; changes.y = y;
@ -79,10 +83,16 @@ void SetXWindowBounds(::Window xwindow,
BrowserWindowStdGtk::BrowserWindowStdGtk(Delegate* delegate, BrowserWindowStdGtk::BrowserWindowStdGtk(Delegate* delegate,
const std::string& startup_url) const std::string& startup_url)
: BrowserWindow(delegate) { : BrowserWindow(delegate), xdisplay_(nullptr) {
client_handler_ = new ClientHandlerStd(this, startup_url); client_handler_ = new ClientHandlerStd(this, startup_url);
} }
void BrowserWindowStdGtk::set_xdisplay(XDisplay* xdisplay) {
REQUIRE_MAIN_THREAD();
DCHECK(!xdisplay_);
xdisplay_ = xdisplay;
}
void BrowserWindowStdGtk::CreateBrowser( void BrowserWindowStdGtk::CreateBrowser(
ClientWindowHandle parent_handle, ClientWindowHandle parent_handle,
const CefRect& rect, const CefRect& rect,
@ -118,14 +128,14 @@ void BrowserWindowStdGtk::ShowPopup(ClientWindowHandle parent_handle,
if (browser_) { if (browser_) {
::Window parent_xwindow = GetXWindowForWidget(parent_handle); ::Window parent_xwindow = GetXWindowForWidget(parent_handle);
::Display* xdisplay = cef_get_xdisplay(); CHECK(xdisplay_ != 0);
::Window xwindow = browser_->GetHost()->GetWindowHandle(); ::Window xwindow = browser_->GetHost()->GetWindowHandle();
DCHECK(xwindow); DCHECK(xwindow);
XReparentWindow(xdisplay, xwindow, parent_xwindow, x, y); XReparentWindow(xdisplay_, xwindow, parent_xwindow, x, y);
SetXWindowBounds(xwindow, x, y, width, height); SetXWindowBounds(xdisplay_, xwindow, x, y, width, height);
SetXWindowVisible(xwindow, true); SetXWindowVisible(xdisplay_, xwindow, true);
} }
} }
@ -135,7 +145,7 @@ void BrowserWindowStdGtk::Show() {
if (browser_) { if (browser_) {
::Window xwindow = browser_->GetHost()->GetWindowHandle(); ::Window xwindow = browser_->GetHost()->GetWindowHandle();
DCHECK(xwindow); DCHECK(xwindow);
SetXWindowVisible(xwindow, true); SetXWindowVisible(xdisplay_, xwindow, true);
} }
} }
@ -145,7 +155,7 @@ void BrowserWindowStdGtk::Hide() {
if (browser_) { if (browser_) {
::Window xwindow = browser_->GetHost()->GetWindowHandle(); ::Window xwindow = browser_->GetHost()->GetWindowHandle();
DCHECK(xwindow); DCHECK(xwindow);
SetXWindowVisible(xwindow, false); SetXWindowVisible(xdisplay_, xwindow, false);
} }
} }
@ -155,7 +165,7 @@ void BrowserWindowStdGtk::SetBounds(int x, int y, size_t width, size_t height) {
if (browser_) { if (browser_) {
::Window xwindow = browser_->GetHost()->GetWindowHandle(); ::Window xwindow = browser_->GetHost()->GetWindowHandle();
DCHECK(xwindow); DCHECK(xwindow);
SetXWindowBounds(xwindow, x, y, width, height); SetXWindowBounds(xdisplay_, xwindow, x, y, width, height);
} }
} }

View File

@ -19,6 +19,9 @@ class BrowserWindowStdGtk : public BrowserWindow {
// |delegate| must outlive this object. // |delegate| must outlive this object.
BrowserWindowStdGtk(Delegate* delegate, const std::string& startup_url); BrowserWindowStdGtk(Delegate* delegate, const std::string& startup_url);
// Called from RootWindowGtk::CreateRootWindow before CreateBrowser.
void set_xdisplay(XDisplay* xdisplay);
// BrowserWindow methods. // BrowserWindow methods.
void CreateBrowser(ClientWindowHandle parent_handle, void CreateBrowser(ClientWindowHandle parent_handle,
const CefRect& rect, const CefRect& rect,
@ -40,6 +43,8 @@ class BrowserWindowStdGtk : public BrowserWindow {
ClientWindowHandle GetWindowHandle() const OVERRIDE; ClientWindowHandle GetWindowHandle() const OVERRIDE;
private: private:
XDisplay* xdisplay_;
DISALLOW_COPY_AND_ASSIGN(BrowserWindowStdGtk); DISALLOW_COPY_AND_ASSIGN(BrowserWindowStdGtk);
}; };

View File

@ -11,6 +11,7 @@
#include "include/cef_parser.h" #include "include/cef_parser.h"
#include "include/wrapper/cef_helpers.h" #include "include/wrapper/cef_helpers.h"
#include "tests/cefclient/browser/root_window.h" #include "tests/cefclient/browser/root_window.h"
#include "tests/cefclient/browser/util_gtk.h"
namespace client { namespace client {
@ -130,6 +131,7 @@ void AddFilters(GtkFileChooser* chooser,
} }
GtkWindow* GetWindow(CefRefPtr<CefBrowser> browser) { GtkWindow* GetWindow(CefRefPtr<CefBrowser> browser) {
REQUIRE_MAIN_THREAD();
scoped_refptr<RootWindow> root_window = scoped_refptr<RootWindow> root_window =
RootWindow::GetForBrowser(browser->GetIdentifier()); RootWindow::GetForBrowser(browser->GetIdentifier());
if (root_window) { if (root_window) {
@ -141,6 +143,10 @@ GtkWindow* GetWindow(CefRefPtr<CefBrowser> browser) {
return NULL; return NULL;
} }
void RunCallback(base::Callback<void(GtkWindow*)> callback, GtkWindow* window) {
callback.Run(window);
}
} // namespace } // namespace
ClientDialogHandlerGtk::ClientDialogHandlerGtk() : gtk_dialog_(NULL) {} ClientDialogHandlerGtk::ClientDialogHandlerGtk() : gtk_dialog_(NULL) {}
@ -153,6 +159,78 @@ bool ClientDialogHandlerGtk::OnFileDialog(
const std::vector<CefString>& accept_filters, const std::vector<CefString>& accept_filters,
int selected_accept_filter, int selected_accept_filter,
CefRefPtr<CefFileDialogCallback> callback) { CefRefPtr<CefFileDialogCallback> callback) {
CEF_REQUIRE_UI_THREAD();
OnFileDialogParams params;
params.browser = browser;
params.mode = mode;
params.title = title;
params.default_file_path = default_file_path;
params.accept_filters = accept_filters;
params.selected_accept_filter = selected_accept_filter;
params.callback = callback;
GetWindowAndContinue(
browser,
base::Bind(&ClientDialogHandlerGtk::OnFileDialogContinue, this, params));
return true;
}
bool ClientDialogHandlerGtk::OnJSDialog(CefRefPtr<CefBrowser> browser,
const CefString& origin_url,
JSDialogType dialog_type,
const CefString& message_text,
const CefString& default_prompt_text,
CefRefPtr<CefJSDialogCallback> callback,
bool& suppress_message) {
CEF_REQUIRE_UI_THREAD();
OnJSDialogParams params;
params.browser = browser;
params.origin_url = origin_url;
params.dialog_type = dialog_type;
params.message_text = message_text;
params.default_prompt_text = default_prompt_text;
params.callback = callback;
GetWindowAndContinue(
browser,
base::Bind(&ClientDialogHandlerGtk::OnJSDialogContinue, this, params));
return true;
}
bool ClientDialogHandlerGtk::OnBeforeUnloadDialog(
CefRefPtr<CefBrowser> browser,
const CefString& message_text,
bool is_reload,
CefRefPtr<CefJSDialogCallback> callback) {
CEF_REQUIRE_UI_THREAD();
const std::string& new_message_text =
message_text.ToString() + "\n\nIs it OK to leave/reload this page?";
bool suppress_message = false;
return OnJSDialog(browser, CefString(), JSDIALOGTYPE_CONFIRM,
new_message_text, CefString(), callback, suppress_message);
}
void ClientDialogHandlerGtk::OnResetDialogState(CefRefPtr<CefBrowser> browser) {
CEF_REQUIRE_UI_THREAD();
if (!gtk_dialog_)
return;
gtk_widget_destroy(gtk_dialog_);
gtk_dialog_ = NULL;
js_dialog_callback_ = NULL;
}
void ClientDialogHandlerGtk::OnFileDialogContinue(OnFileDialogParams params,
GtkWindow* window) {
CEF_REQUIRE_UI_THREAD();
ScopedGdkThreadsEnter scoped_gdk_threads;
std::vector<CefString> files; std::vector<CefString> files;
GtkFileChooserAction action; GtkFileChooserAction action;
@ -160,7 +238,7 @@ bool ClientDialogHandlerGtk::OnFileDialog(
// Remove any modifier flags. // Remove any modifier flags.
FileDialogMode mode_type = FileDialogMode mode_type =
static_cast<FileDialogMode>(mode & FILE_DIALOG_TYPE_MASK); static_cast<FileDialogMode>(params.mode & FILE_DIALOG_TYPE_MASK);
if (mode_type == FILE_DIALOG_OPEN || mode_type == FILE_DIALOG_OPEN_MULTIPLE) { if (mode_type == FILE_DIALOG_OPEN || mode_type == FILE_DIALOG_OPEN_MULTIPLE) {
action = GTK_FILE_CHOOSER_ACTION_OPEN; action = GTK_FILE_CHOOSER_ACTION_OPEN;
@ -173,12 +251,12 @@ bool ClientDialogHandlerGtk::OnFileDialog(
accept_button = GTK_STOCK_SAVE; accept_button = GTK_STOCK_SAVE;
} else { } else {
NOTREACHED(); NOTREACHED();
return false; params.callback->Cancel();
} }
std::string title_str; std::string title_str;
if (!title.empty()) { if (!params.title.empty()) {
title_str = title; title_str = params.title;
} else { } else {
switch (mode_type) { switch (mode_type) {
case FILE_DIALOG_OPEN: case FILE_DIALOG_OPEN:
@ -198,10 +276,6 @@ bool ClientDialogHandlerGtk::OnFileDialog(
} }
} }
GtkWindow* window = GetWindow(browser);
if (!window)
return false;
GtkWidget* dialog = gtk_file_chooser_dialog_new( GtkWidget* dialog = gtk_file_chooser_dialog_new(
title_str.c_str(), GTK_WINDOW(window), action, GTK_STOCK_CANCEL, title_str.c_str(), GTK_WINDOW(window), action, GTK_STOCK_CANCEL,
GTK_RESPONSE_CANCEL, accept_button, GTK_RESPONSE_ACCEPT, NULL); GTK_RESPONSE_CANCEL, accept_button, GTK_RESPONSE_ACCEPT, NULL);
@ -211,14 +285,15 @@ bool ClientDialogHandlerGtk::OnFileDialog(
if (mode_type == FILE_DIALOG_SAVE) { if (mode_type == FILE_DIALOG_SAVE) {
gtk_file_chooser_set_do_overwrite_confirmation( gtk_file_chooser_set_do_overwrite_confirmation(
GTK_FILE_CHOOSER(dialog), !!(mode & FILE_DIALOG_OVERWRITEPROMPT_FLAG)); GTK_FILE_CHOOSER(dialog),
!!(params.mode & FILE_DIALOG_OVERWRITEPROMPT_FLAG));
} }
gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dialog), gtk_file_chooser_set_show_hidden(
!(mode & FILE_DIALOG_HIDEREADONLY_FLAG)); GTK_FILE_CHOOSER(dialog), !(params.mode & FILE_DIALOG_HIDEREADONLY_FLAG));
if (!default_file_path.empty() && mode_type == FILE_DIALOG_SAVE) { if (!params.default_file_path.empty() && mode_type == FILE_DIALOG_SAVE) {
const std::string& file_path = default_file_path; const std::string& file_path = params.default_file_path;
bool exists = false; bool exists = false;
struct stat sb; struct stat sb;
@ -237,10 +312,10 @@ bool ClientDialogHandlerGtk::OnFileDialog(
} }
std::vector<GtkFileFilter*> filters; std::vector<GtkFileFilter*> filters;
AddFilters(GTK_FILE_CHOOSER(dialog), accept_filters, true, &filters); AddFilters(GTK_FILE_CHOOSER(dialog), params.accept_filters, true, &filters);
if (selected_accept_filter < static_cast<int>(filters.size())) { if (params.selected_accept_filter < static_cast<int>(filters.size())) {
gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog),
filters[selected_accept_filter]); filters[params.selected_accept_filter]);
} }
bool success = false; bool success = false;
@ -267,7 +342,7 @@ bool ClientDialogHandlerGtk::OnFileDialog(
} }
} }
int filter_index = selected_accept_filter; int filter_index = params.selected_accept_filter;
if (success) { if (success) {
GtkFileFilter* selected_filter = GtkFileFilter* selected_filter =
gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog)); gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog));
@ -284,27 +359,22 @@ bool ClientDialogHandlerGtk::OnFileDialog(
gtk_widget_destroy(dialog); gtk_widget_destroy(dialog);
if (success) if (success)
callback->Continue(filter_index, files); params.callback->Continue(filter_index, files);
else else
callback->Cancel(); params.callback->Cancel();
return true;
} }
bool ClientDialogHandlerGtk::OnJSDialog(CefRefPtr<CefBrowser> browser, void ClientDialogHandlerGtk::OnJSDialogContinue(OnJSDialogParams params,
const CefString& origin_url, GtkWindow* window) {
JSDialogType dialog_type,
const CefString& message_text,
const CefString& default_prompt_text,
CefRefPtr<CefJSDialogCallback> callback,
bool& suppress_message) {
CEF_REQUIRE_UI_THREAD(); CEF_REQUIRE_UI_THREAD();
ScopedGdkThreadsEnter scoped_gdk_threads;
GtkButtonsType buttons = GTK_BUTTONS_NONE; GtkButtonsType buttons = GTK_BUTTONS_NONE;
GtkMessageType gtk_message_type = GTK_MESSAGE_OTHER; GtkMessageType gtk_message_type = GTK_MESSAGE_OTHER;
std::string title; std::string title;
switch (dialog_type) { switch (params.dialog_type) {
case JSDIALOGTYPE_ALERT: case JSDIALOGTYPE_ALERT:
buttons = GTK_BUTTONS_NONE; buttons = GTK_BUTTONS_NONE;
gtk_message_type = GTK_MESSAGE_WARNING; gtk_message_type = GTK_MESSAGE_WARNING;
@ -324,20 +394,16 @@ bool ClientDialogHandlerGtk::OnJSDialog(CefRefPtr<CefBrowser> browser,
break; break;
} }
js_dialog_callback_ = callback; js_dialog_callback_ = params.callback;
if (!origin_url.empty()) { if (!params.origin_url.empty()) {
title += " - "; title += " - ";
title += CefFormatUrlForSecurityDisplay(origin_url).ToString(); title += CefFormatUrlForSecurityDisplay(params.origin_url).ToString();
} }
GtkWindow* window = GetWindow(browser);
if (!window)
return false;
gtk_dialog_ = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL, gtk_dialog_ = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL,
gtk_message_type, buttons, "%s", gtk_message_type, buttons, "%s",
message_text.ToString().c_str()); params.message_text.ToString().c_str());
g_signal_connect(gtk_dialog_, "delete-event", g_signal_connect(gtk_dialog_, "delete-event",
G_CALLBACK(gtk_widget_hide_on_delete), NULL); G_CALLBACK(gtk_widget_hide_on_delete), NULL);
@ -346,15 +412,15 @@ bool ClientDialogHandlerGtk::OnJSDialog(CefRefPtr<CefBrowser> browser,
GtkWidget* ok_button = gtk_dialog_add_button(GTK_DIALOG(gtk_dialog_), GtkWidget* ok_button = gtk_dialog_add_button(GTK_DIALOG(gtk_dialog_),
GTK_STOCK_OK, GTK_RESPONSE_OK); GTK_STOCK_OK, GTK_RESPONSE_OK);
if (dialog_type != JSDIALOGTYPE_PROMPT) if (params.dialog_type != JSDIALOGTYPE_PROMPT)
gtk_widget_grab_focus(ok_button); gtk_widget_grab_focus(ok_button);
if (dialog_type == JSDIALOGTYPE_PROMPT) { if (params.dialog_type == JSDIALOGTYPE_PROMPT) {
GtkWidget* content_area = GtkWidget* content_area =
gtk_dialog_get_content_area(GTK_DIALOG(gtk_dialog_)); gtk_dialog_get_content_area(GTK_DIALOG(gtk_dialog_));
GtkWidget* text_box = gtk_entry_new(); GtkWidget* text_box = gtk_entry_new();
gtk_entry_set_text(GTK_ENTRY(text_box), gtk_entry_set_text(GTK_ENTRY(text_box),
default_prompt_text.ToString().c_str()); params.default_prompt_text.ToString().c_str());
gtk_box_pack_start(GTK_BOX(content_area), text_box, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(content_area), text_box, TRUE, TRUE, 0);
g_object_set_data(G_OBJECT(gtk_dialog_), kPromptTextId, text_box); g_object_set_data(G_OBJECT(gtk_dialog_), kPromptTextId, text_box);
gtk_entry_set_activates_default(GTK_ENTRY(text_box), TRUE); gtk_entry_set_activates_default(GTK_ENTRY(text_box), TRUE);
@ -363,33 +429,21 @@ bool ClientDialogHandlerGtk::OnJSDialog(CefRefPtr<CefBrowser> browser,
gtk_dialog_set_default_response(GTK_DIALOG(gtk_dialog_), GTK_RESPONSE_OK); gtk_dialog_set_default_response(GTK_DIALOG(gtk_dialog_), GTK_RESPONSE_OK);
g_signal_connect(gtk_dialog_, "response", G_CALLBACK(OnDialogResponse), this); g_signal_connect(gtk_dialog_, "response", G_CALLBACK(OnDialogResponse), this);
gtk_widget_show_all(GTK_WIDGET(gtk_dialog_)); gtk_widget_show_all(GTK_WIDGET(gtk_dialog_));
return true;
} }
bool ClientDialogHandlerGtk::OnBeforeUnloadDialog( void ClientDialogHandlerGtk::GetWindowAndContinue(
CefRefPtr<CefBrowser> browser, CefRefPtr<CefBrowser> browser,
const CefString& message_text, base::Callback<void(GtkWindow*)> callback) {
bool is_reload, if (!CURRENTLY_ON_MAIN_THREAD()) {
CefRefPtr<CefJSDialogCallback> callback) { MAIN_POST_CLOSURE(base::Bind(&ClientDialogHandlerGtk::GetWindowAndContinue,
CEF_REQUIRE_UI_THREAD(); this, browser, callback));
return;
const std::string& new_message_text =
message_text.ToString() + "\n\nIs it OK to leave/reload this page?";
bool suppress_message = false;
return OnJSDialog(browser, CefString(), JSDIALOGTYPE_CONFIRM,
new_message_text, CefString(), callback, suppress_message);
} }
void ClientDialogHandlerGtk::OnResetDialogState(CefRefPtr<CefBrowser> browser) { GtkWindow* window = GetWindow(browser);
CEF_REQUIRE_UI_THREAD(); if (window) {
CefPostTask(TID_UI, base::Bind(RunCallback, callback, window));
if (!gtk_dialog_) }
return;
gtk_widget_destroy(gtk_dialog_);
gtk_dialog_ = NULL;
js_dialog_callback_ = NULL;
} }
// static // static

View File

@ -8,6 +8,7 @@
#include <gtk/gtk.h> #include <gtk/gtk.h>
#include "include/base/cef_callback_forward.h"
#include "include/cef_dialog_handler.h" #include "include/cef_dialog_handler.h"
#include "include/cef_jsdialog_handler.h" #include "include/cef_jsdialog_handler.h"
@ -42,6 +43,30 @@ class ClientDialogHandlerGtk : public CefDialogHandler,
void OnResetDialogState(CefRefPtr<CefBrowser> browser) OVERRIDE; void OnResetDialogState(CefRefPtr<CefBrowser> browser) OVERRIDE;
private: private:
struct OnFileDialogParams {
CefRefPtr<CefBrowser> browser;
FileDialogMode mode;
CefString title;
CefString default_file_path;
std::vector<CefString> accept_filters;
int selected_accept_filter;
CefRefPtr<CefFileDialogCallback> callback;
};
void OnFileDialogContinue(OnFileDialogParams params, GtkWindow* window);
struct OnJSDialogParams {
CefRefPtr<CefBrowser> browser;
CefString origin_url;
JSDialogType dialog_type;
CefString message_text;
CefString default_prompt_text;
CefRefPtr<CefJSDialogCallback> callback;
};
void OnJSDialogContinue(OnJSDialogParams params, GtkWindow* window);
void GetWindowAndContinue(CefRefPtr<CefBrowser> browser,
base::Callback<void(GtkWindow*)> callback);
static void OnDialogResponse(GtkDialog* dialog, static void OnDialogResponse(GtkDialog* dialog,
gint response_id, gint response_id,
ClientDialogHandlerGtk* handler); ClientDialogHandlerGtk* handler);

View File

@ -135,7 +135,7 @@ bool MainContextImpl::UseWindowlessRendering() {
} }
void MainContextImpl::PopulateSettings(CefSettings* settings) { void MainContextImpl::PopulateSettings(CefSettings* settings) {
#if defined(OS_WIN) #if defined(OS_WIN) || defined(OS_LINUX)
settings->multi_threaded_message_loop = settings->multi_threaded_message_loop =
command_line_->HasSwitch(switches::kMultiThreadedMessageLoop); command_line_->HasSwitch(switches::kMultiThreadedMessageLoop);
#endif #endif

View File

@ -0,0 +1,155 @@
// Copyright (c) 2018 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 "tests/cefclient/browser/main_message_loop_multithreaded_gtk.h"
#include <X11/Xlib.h>
#include <gtk/gtkmain.h>
#include "include/base/cef_bind.h"
#include "include/base/cef_logging.h"
#include "include/wrapper/cef_closure_task.h"
namespace client {
namespace {
base::Lock g_global_lock;
base::PlatformThreadId g_global_lock_thread = kInvalidPlatformThreadId;
void lock_enter() {
// The GDK lock is not reentrant, so check that we're using it correctly.
// See comments on ScopedGdkThreadsEnter.
base::PlatformThreadId current_thread = base::PlatformThread::CurrentId();
CHECK(current_thread != g_global_lock_thread);
g_global_lock.Acquire();
g_global_lock_thread = current_thread;
}
void lock_leave() {
g_global_lock_thread = kInvalidPlatformThreadId;
g_global_lock.Release();
}
// Same as g_idle_add() but specifying the GMainContext.
guint idle_add(GMainContext* main_context,
GSourceFunc function,
gpointer data) {
GSource* source = g_idle_source_new();
g_source_set_callback(source, function, data, nullptr);
guint id = g_source_attach(source, main_context);
g_source_unref(source);
return id;
}
// Same as g_timeout_add() but specifying the GMainContext.
guint timeout_add(GMainContext* main_context,
guint interval,
GSourceFunc function,
gpointer data) {
GSource* source = g_timeout_source_new(interval);
g_source_set_callback(source, function, data, nullptr);
guint id = g_source_attach(source, main_context);
g_source_unref(source);
return id;
}
} // namespace
MainMessageLoopMultithreadedGtk::MainMessageLoopMultithreadedGtk()
: thread_id_(base::PlatformThread::CurrentId()) {
// Initialize Xlib support for concurrent threads. This function must be the
// first Xlib function a multi-threaded program calls, and it must complete
// before any other Xlib call is made.
CHECK(XInitThreads() != 0);
// Initialize GDK thread support. See comments on ScopedGdkThreadsEnter.
gdk_threads_set_lock_functions(lock_enter, lock_leave);
gdk_threads_init();
}
MainMessageLoopMultithreadedGtk::~MainMessageLoopMultithreadedGtk() {
DCHECK(RunsTasksOnCurrentThread());
DCHECK(queued_tasks_.empty());
}
int MainMessageLoopMultithreadedGtk::Run() {
DCHECK(RunsTasksOnCurrentThread());
// Chromium uses the default GLib context so we create our own context and
// make it the default for this thread.
main_context_ = g_main_context_new();
g_main_context_push_thread_default(main_context_);
main_loop_ = g_main_loop_new(main_context_, TRUE);
// Check the queue when GTK is idle, or at least every 100ms.
// TODO(cef): It might be more efficient to use input functions
// (gdk_input_add) and trigger by writing to an fd.
idle_add(main_context_, MainMessageLoopMultithreadedGtk::TriggerRunTasks,
this);
timeout_add(main_context_, 100,
MainMessageLoopMultithreadedGtk::TriggerRunTasks, this);
// Block until g_main_loop_quit().
g_main_loop_run(main_loop_);
// Release GLib resources.
g_main_loop_unref(main_loop_);
main_loop_ = nullptr;
g_main_context_pop_thread_default(main_context_);
g_main_context_unref(main_context_);
main_context_ = nullptr;
return 0;
}
void MainMessageLoopMultithreadedGtk::Quit() {
PostTask(CefCreateClosureTask(base::Bind(
&MainMessageLoopMultithreadedGtk::DoQuit, base::Unretained(this))));
}
void MainMessageLoopMultithreadedGtk::PostTask(CefRefPtr<CefTask> task) {
base::AutoLock lock_scope(lock_);
// Queue the task.
queued_tasks_.push(task);
}
bool MainMessageLoopMultithreadedGtk::RunsTasksOnCurrentThread() const {
return (thread_id_ == base::PlatformThread::CurrentId());
}
// static
int MainMessageLoopMultithreadedGtk::TriggerRunTasks(void* self) {
static_cast<MainMessageLoopMultithreadedGtk*>(self)->RunTasks();
return G_SOURCE_CONTINUE;
}
void MainMessageLoopMultithreadedGtk::RunTasks() {
DCHECK(RunsTasksOnCurrentThread());
std::queue<CefRefPtr<CefTask>> tasks;
{
base::AutoLock lock_scope(lock_);
tasks.swap(queued_tasks_);
}
// Execute all queued tasks.
while (!tasks.empty()) {
CefRefPtr<CefTask> task = tasks.front();
tasks.pop();
task->Execute();
}
}
void MainMessageLoopMultithreadedGtk::DoQuit() {
DCHECK(RunsTasksOnCurrentThread());
g_main_loop_quit(main_loop_);
}
} // namespace client

View File

@ -0,0 +1,54 @@
// Copyright (c) 2018 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_TESTS_CEFCLIENT_BROWSER_MAIN_MESSAGE_LOOP_MULTITHREADED_GTK_H_
#define CEF_TESTS_CEFCLIENT_BROWSER_MAIN_MESSAGE_LOOP_MULTITHREADED_GTK_H_
#pragma once
#include <queue>
#include <gdk/gdk.h>
#include "include/base/cef_lock.h"
#include "include/base/cef_platform_thread.h"
#include "tests/shared/browser/main_message_loop.h"
namespace client {
// Represents the main message loop in the browser process when using multi-
// threaded message loop mode on Linux. In this mode there is no Chromium
// message loop running on the main application thread. Instead, this
// implementation utilizes a Glib context for running tasks.
class MainMessageLoopMultithreadedGtk : public MainMessageLoop {
public:
MainMessageLoopMultithreadedGtk();
~MainMessageLoopMultithreadedGtk();
// MainMessageLoop methods.
int Run() OVERRIDE;
void Quit() OVERRIDE;
void PostTask(CefRefPtr<CefTask> task) OVERRIDE;
bool RunsTasksOnCurrentThread() const OVERRIDE;
private:
static int TriggerRunTasks(void* self);
void RunTasks();
void DoQuit();
base::PlatformThreadId thread_id_;
GMainContext* main_context_;
GMainLoop* main_loop_;
base::Lock lock_;
// Must be protected by |lock_|.
std::queue<CefRefPtr<CefTask>> queued_tasks_;
DISALLOW_COPY_AND_ASSIGN(MainMessageLoopMultithreadedGtk);
};
} // namespace client
#endif // CEF_TESTS_CEFCLIENT_BROWSER_MAIN_MESSAGE_LOOP_MULTITHREADED_GTK_H_

View File

@ -10,11 +10,13 @@
#include <gtk/gtk.h> #include <gtk/gtk.h>
#include <gtk/gtkunixprint.h> #include <gtk/gtkunixprint.h>
#include "include/base/cef_bind.h"
#include "include/base/cef_logging.h" #include "include/base/cef_logging.h"
#include "include/base/cef_macros.h" #include "include/base/cef_macros.h"
#include "include/wrapper/cef_helpers.h" #include "include/wrapper/cef_helpers.h"
#include "tests/cefclient/browser/root_window.h" #include "tests/cefclient/browser/root_window.h"
#include "tests/cefclient/browser/util_gtk.h"
namespace client { namespace client {
@ -270,6 +272,33 @@ void InitPrintSettings(GtkPrintSettings* settings,
printable_area_device_units, true); printable_area_device_units, true);
} }
// Returns the GtkWindow* for the browser. Will return NULL when using the Views
// framework.
GtkWindow* GetWindow(CefRefPtr<CefBrowser> browser) {
scoped_refptr<RootWindow> root_window =
RootWindow::GetForBrowser(browser->GetIdentifier());
if (root_window)
return GTK_WINDOW(root_window->GetWindowHandle());
return NULL;
}
void RunCallback(base::Callback<void(GtkWindow*)> callback, GtkWindow* window) {
callback.Run(window);
}
void GetWindowAndContinue(CefRefPtr<CefBrowser> browser,
base::Callback<void(GtkWindow*)> callback) {
if (!CURRENTLY_ON_MAIN_THREAD()) {
MAIN_POST_CLOSURE(base::Bind(GetWindowAndContinue, browser, callback));
return;
}
GtkWindow* window = GetWindow(browser);
if (window) {
CefPostTask(TID_UI, base::Bind(RunCallback, callback, window));
}
}
} // namespace } // namespace
struct ClientPrintHandlerGtk::PrintHandler { struct ClientPrintHandlerGtk::PrintHandler {
@ -281,6 +310,8 @@ struct ClientPrintHandlerGtk::PrintHandler {
printer_(NULL) {} printer_(NULL) {}
~PrintHandler() { ~PrintHandler() {
ScopedGdkThreadsEnter scoped_gdk_threads;
if (dialog_) { if (dialog_) {
gtk_widget_destroy(dialog_); gtk_widget_destroy(dialog_);
dialog_ = NULL; dialog_ = NULL;
@ -301,6 +332,8 @@ struct ClientPrintHandlerGtk::PrintHandler {
void OnPrintSettings(CefRefPtr<CefPrintSettings> settings, void OnPrintSettings(CefRefPtr<CefPrintSettings> settings,
bool get_defaults) { bool get_defaults) {
ScopedGdkThreadsEnter scoped_gdk_threads;
if (get_defaults) { if (get_defaults) {
DCHECK(!page_setup_); DCHECK(!page_setup_);
DCHECK(!printer_); DCHECK(!printer_);
@ -369,11 +402,13 @@ struct ClientPrintHandlerGtk::PrintHandler {
InitPrintSettings(gtk_settings_, page_setup_, settings); InitPrintSettings(gtk_settings_, page_setup_, settings);
} }
bool OnPrintDialog(bool has_selection, void OnPrintDialog(bool has_selection,
CefRefPtr<CefPrintDialogCallback> callback) { CefRefPtr<CefPrintDialogCallback> callback,
GtkWindow* parent) {
dialog_callback_ = callback; dialog_callback_ = callback;
GtkWindow* parent = GetWindow(); ScopedGdkThreadsEnter scoped_gdk_threads;
// TODO(estade): We need a window title here. // TODO(estade): We need a window title here.
dialog_ = gtk_print_unix_dialog_new(NULL, parent); dialog_ = gtk_print_unix_dialog_new(NULL, parent);
g_signal_connect(dialog_, "delete-event", g_signal_connect(dialog_, "delete-event",
@ -401,8 +436,6 @@ struct ClientPrintHandlerGtk::PrintHandler {
g_signal_connect(dialog_, "response", G_CALLBACK(OnDialogResponseThunk), g_signal_connect(dialog_, "response", G_CALLBACK(OnDialogResponseThunk),
this); this);
gtk_widget_show(dialog_); gtk_widget_show(dialog_);
return true;
} }
bool OnPrintJob(const CefString& document_name, bool OnPrintJob(const CefString& document_name,
@ -413,6 +446,8 @@ struct ClientPrintHandlerGtk::PrintHandler {
if (!printer_) if (!printer_)
return false; return false;
ScopedGdkThreadsEnter scoped_gdk_threads;
job_callback_ = callback; job_callback_ = callback;
// Save the settings for next time. // Save the settings for next time.
@ -428,16 +463,6 @@ struct ClientPrintHandlerGtk::PrintHandler {
} }
private: private:
// Returns the GtkWindow* for the browser. Will return NULL when using the
// Views framework.
GtkWindow* GetWindow() {
scoped_refptr<RootWindow> root_window =
RootWindow::GetForBrowser(browser_->GetIdentifier());
if (root_window)
return GTK_WINDOW(root_window->GetWindowHandle());
return NULL;
}
void OnDialogResponse(GtkDialog* dialog, gint response_id) { void OnDialogResponse(GtkDialog* dialog, gint response_id) {
int num_matched_handlers = g_signal_handlers_disconnect_by_func( int num_matched_handlers = g_signal_handlers_disconnect_by_func(
dialog_, reinterpret_cast<gpointer>(&OnDialogResponseThunk), this); dialog_, reinterpret_cast<gpointer>(&OnDialogResponseThunk), this);
@ -581,7 +606,11 @@ bool ClientPrintHandlerGtk::OnPrintDialog(
CefRefPtr<CefPrintDialogCallback> callback) { CefRefPtr<CefPrintDialogCallback> callback) {
CEF_REQUIRE_UI_THREAD(); CEF_REQUIRE_UI_THREAD();
return GetPrintHandler(browser)->OnPrintDialog(has_selection, callback); PrintHandler* print_handler = GetPrintHandler(browser);
GetWindowAndContinue(browser, base::Bind(&PrintHandler::OnPrintDialog,
base::Unretained(print_handler),
has_selection, callback));
return true;
} }
bool ClientPrintHandlerGtk::OnPrintJob( bool ClientPrintHandlerGtk::OnPrintJob(
@ -609,6 +638,8 @@ void ClientPrintHandlerGtk::OnPrintReset(CefRefPtr<CefBrowser> browser) {
CefSize ClientPrintHandlerGtk::GetPdfPaperSize(int device_units_per_inch) { CefSize ClientPrintHandlerGtk::GetPdfPaperSize(int device_units_per_inch) {
CEF_REQUIRE_UI_THREAD(); CEF_REQUIRE_UI_THREAD();
ScopedGdkThreadsEnter scoped_gdk_threads;
GtkPageSetup* page_setup = gtk_page_setup_new(); GtkPageSetup* page_setup = gtk_page_setup_new();
float width = gtk_page_setup_get_paper_width(page_setup, GTK_UNIT_INCH); float width = gtk_page_setup_get_paper_width(page_setup, GTK_UNIT_INCH);

View File

@ -18,6 +18,7 @@
#include "tests/cefclient/browser/main_context.h" #include "tests/cefclient/browser/main_context.h"
#include "tests/cefclient/browser/resource.h" #include "tests/cefclient/browser/resource.h"
#include "tests/cefclient/browser/temp_window.h" #include "tests/cefclient/browser/temp_window.h"
#include "tests/cefclient/browser/util_gtk.h"
#include "tests/cefclient/browser/window_test_runner_gtk.h" #include "tests/cefclient/browser/window_test_runner_gtk.h"
#include "tests/shared/browser/main_message_loop.h" #include "tests/shared/browser/main_message_loop.h"
#include "tests/shared/common/client_switches.h" #include "tests/shared/common/client_switches.h"
@ -62,9 +63,10 @@ RootWindowGtk::RootWindowGtk()
url_entry_(NULL), url_entry_(NULL),
toolbar_height_(0), toolbar_height_(0),
menubar_height_(0), menubar_height_(0),
force_close_(false),
window_destroyed_(false), window_destroyed_(false),
browser_destroyed_(false) {} browser_destroyed_(false),
force_close_(false),
is_closing_(false) {}
RootWindowGtk::~RootWindowGtk() { RootWindowGtk::~RootWindowGtk() {
REQUIRE_MAIN_THREAD(); REQUIRE_MAIN_THREAD();
@ -140,6 +142,8 @@ void RootWindowGtk::Show(ShowMode mode) {
if (!window_) if (!window_)
return; return;
ScopedGdkThreadsEnter scoped_gdk_threads;
// Show the GTK window. // Show the GTK window.
gtk_widget_show_all(window_); gtk_widget_show_all(window_);
@ -158,6 +162,8 @@ void RootWindowGtk::Show(ShowMode mode) {
void RootWindowGtk::Hide() { void RootWindowGtk::Hide() {
REQUIRE_MAIN_THREAD(); REQUIRE_MAIN_THREAD();
ScopedGdkThreadsEnter scoped_gdk_threads;
if (window_) if (window_)
gtk_widget_hide(window_); gtk_widget_hide(window_);
} }
@ -168,6 +174,8 @@ void RootWindowGtk::SetBounds(int x, int y, size_t width, size_t height) {
if (!window_) if (!window_)
return; return;
ScopedGdkThreadsEnter scoped_gdk_threads;
GtkWindow* window = GTK_WINDOW(window_); GtkWindow* window = GTK_WINDOW(window_);
GdkWindow* gdk_window = gtk_widget_get_window(window_); GdkWindow* gdk_window = gtk_widget_get_window(window_);
@ -184,7 +192,11 @@ void RootWindowGtk::Close(bool force) {
REQUIRE_MAIN_THREAD(); REQUIRE_MAIN_THREAD();
if (window_) { if (window_) {
force_close_ = force; ScopedGdkThreadsEnter scoped_gdk_threads;
if (force) {
NotifyForceClose();
}
gtk_widget_destroy(window_); gtk_widget_destroy(window_);
} }
} }
@ -258,7 +270,11 @@ void RootWindowGtk::CreateRootWindow(const CefBrowserSettings& settings,
height = start_rect_.height; height = start_rect_.height;
} }
ScopedGdkThreadsEnter scoped_gdk_threads;
window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL); window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
CHECK(window_);
gtk_window_set_default_size(GTK_WINDOW(window_), width, height); gtk_window_set_default_size(GTK_WINDOW(window_), width, height);
g_signal_connect(G_OBJECT(window_), "focus-in-event", g_signal_connect(G_OBJECT(window_), "focus-in-event",
G_CALLBACK(&RootWindowGtk::WindowFocusIn), this); G_CALLBACK(&RootWindowGtk::WindowFocusIn), this);
@ -345,6 +361,16 @@ void RootWindowGtk::CreateRootWindow(const CefBrowserSettings& settings,
// added to the Vbox container for automatic layout-based sizing. // added to the Vbox container for automatic layout-based sizing.
GtkWidget* parent = with_osr_ ? vbox : window_; GtkWidget* parent = with_osr_ ? vbox : window_;
// Set the Display associated with the browser.
::Display* xdisplay = GDK_WINDOW_XDISPLAY(gtk_widget_get_window(window_));
if (with_osr_) {
static_cast<BrowserWindowOsrGtk*>(browser_window_.get())
->set_xdisplay(xdisplay);
} else {
static_cast<BrowserWindowStdGtk*>(browser_window_.get())
->set_xdisplay(xdisplay);
}
if (!is_popup_) { if (!is_popup_) {
// Create the browser window. // Create the browser window.
browser_window_->CreateBrowser(parent, browser_bounds_, settings, browser_window_->CreateBrowser(parent, browser_bounds_, settings,
@ -368,6 +394,16 @@ void RootWindowGtk::OnBrowserCreated(CefRefPtr<CefBrowser> browser) {
delegate_->OnBrowserCreated(this, browser); delegate_->OnBrowserCreated(this, browser);
} }
void RootWindowGtk::OnBrowserWindowClosing() {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI,
base::Bind(&RootWindowGtk::OnBrowserWindowClosing, this));
return;
}
is_closing_ = true;
}
void RootWindowGtk::OnBrowserWindowDestroyed() { void RootWindowGtk::OnBrowserWindowDestroyed() {
REQUIRE_MAIN_THREAD(); REQUIRE_MAIN_THREAD();
@ -380,14 +416,15 @@ void RootWindowGtk::OnBrowserWindowDestroyed() {
Close(true); Close(true);
} }
browser_destroyed_ = true; NotifyDestroyedIfDone(false, true);
NotifyDestroyedIfDone();
} }
void RootWindowGtk::OnSetAddress(const std::string& url) { void RootWindowGtk::OnSetAddress(const std::string& url) {
REQUIRE_MAIN_THREAD(); REQUIRE_MAIN_THREAD();
if (url_entry_) { if (url_entry_) {
ScopedGdkThreadsEnter scoped_gdk_threads;
std::string urlStr(url); std::string urlStr(url);
gtk_entry_set_text(GTK_ENTRY(url_entry_), urlStr.c_str()); gtk_entry_set_text(GTK_ENTRY(url_entry_), urlStr.c_str());
} }
@ -397,6 +434,8 @@ void RootWindowGtk::OnSetTitle(const std::string& title) {
REQUIRE_MAIN_THREAD(); REQUIRE_MAIN_THREAD();
if (window_) { if (window_) {
ScopedGdkThreadsEnter scoped_gdk_threads;
std::string titleStr(title); std::string titleStr(title);
gtk_window_set_title(GTK_WINDOW(window_), titleStr.c_str()); gtk_window_set_title(GTK_WINDOW(window_), titleStr.c_str());
} }
@ -422,6 +461,8 @@ void RootWindowGtk::OnAutoResize(const CefSize& new_size) {
if (!window_) if (!window_)
return; return;
ScopedGdkThreadsEnter scoped_gdk_threads;
GtkWindow* window = GTK_WINDOW(window_); GtkWindow* window = GTK_WINDOW(window_);
GdkWindow* gdk_window = gtk_widget_get_window(window_); GdkWindow* gdk_window = gtk_widget_get_window(window_);
@ -440,6 +481,8 @@ void RootWindowGtk::OnSetLoadingState(bool isLoading,
REQUIRE_MAIN_THREAD(); REQUIRE_MAIN_THREAD();
if (with_controls_) { if (with_controls_) {
ScopedGdkThreadsEnter scoped_gdk_threads;
gtk_widget_set_sensitive(GTK_WIDGET(stop_button_), isLoading); gtk_widget_set_sensitive(GTK_WIDGET(stop_button_), isLoading);
gtk_widget_set_sensitive(GTK_WIDGET(reload_button_), !isLoading); gtk_widget_set_sensitive(GTK_WIDGET(reload_button_), !isLoading);
gtk_widget_set_sensitive(GTK_WIDGET(back_button_), canGoBack); gtk_widget_set_sensitive(GTK_WIDGET(back_button_), canGoBack);
@ -453,7 +496,176 @@ void RootWindowGtk::OnSetDraggableRegions(
// TODO(cef): Implement support for draggable regions on this platform. // TODO(cef): Implement support for draggable regions on this platform.
} }
void RootWindowGtk::NotifyDestroyedIfDone() { void RootWindowGtk::NotifyMoveOrResizeStarted() {
if (!CURRENTLY_ON_MAIN_THREAD()) {
MAIN_POST_CLOSURE(
base::Bind(&RootWindowGtk::NotifyMoveOrResizeStarted, this));
return;
}
// Called when size, position or stack order changes.
CefRefPtr<CefBrowser> browser = GetBrowser();
if (browser.get()) {
// Notify the browser of move/resize events so that:
// - Popup windows are displayed in the correct location and dismissed
// when the window moves.
// - Drag&drop areas are updated accordingly.
browser->GetHost()->NotifyMoveOrResizeStarted();
}
}
void RootWindowGtk::NotifySetFocus() {
if (!CURRENTLY_ON_MAIN_THREAD()) {
MAIN_POST_CLOSURE(base::Bind(&RootWindowGtk::NotifySetFocus, this));
return;
}
if (!browser_window_.get())
return;
browser_window_->SetFocus(true);
delegate_->OnRootWindowActivated(this);
}
void RootWindowGtk::NotifyVisibilityChange(bool show) {
if (!CURRENTLY_ON_MAIN_THREAD()) {
MAIN_POST_CLOSURE(
base::Bind(&RootWindowGtk::NotifyVisibilityChange, this, show));
return;
}
if (!browser_window_.get())
return;
if (show)
browser_window_->Show();
else
browser_window_->Hide();
}
void RootWindowGtk::NotifyMenuBarHeight(int height) {
if (!CURRENTLY_ON_MAIN_THREAD()) {
MAIN_POST_CLOSURE(
base::Bind(&RootWindowGtk::NotifyMenuBarHeight, this, height));
return;
}
menubar_height_ = height;
}
void RootWindowGtk::NotifyContentBounds(int x, int y, int width, int height) {
if (!CURRENTLY_ON_MAIN_THREAD()) {
MAIN_POST_CLOSURE(base::Bind(&RootWindowGtk::NotifyContentBounds, this, x,
y, width, height));
return;
}
// Offset browser positioning by any controls that will appear in the client
// area.
const int ux_height = toolbar_height_ + menubar_height_;
const int browser_x = x;
const int browser_y = y + ux_height;
const int browser_width = width;
const int browser_height = height - ux_height;
// Size the browser window to match the GTK widget.
browser_bounds_ =
CefRect(browser_x, browser_y, browser_width, browser_height);
if (browser_window_.get()) {
browser_window_->SetBounds(browser_x, browser_y, browser_width,
browser_height);
}
}
void RootWindowGtk::NotifyLoadURL(const std::string& url) {
if (!CURRENTLY_ON_MAIN_THREAD()) {
MAIN_POST_CLOSURE(base::Bind(&RootWindowGtk::NotifyLoadURL, this, url));
return;
}
CefRefPtr<CefBrowser> browser = GetBrowser();
if (browser.get()) {
browser->GetMainFrame()->LoadURL(url);
}
}
void RootWindowGtk::NotifyButtonClicked(int id) {
if (!CURRENTLY_ON_MAIN_THREAD()) {
MAIN_POST_CLOSURE(
base::Bind(&RootWindowGtk::NotifyButtonClicked, this, id));
return;
}
CefRefPtr<CefBrowser> browser = GetBrowser();
if (!browser.get())
return;
switch (id) {
case IDC_NAV_BACK:
browser->GoBack();
break;
case IDC_NAV_FORWARD:
browser->GoForward();
break;
case IDC_NAV_RELOAD:
browser->Reload();
break;
case IDC_NAV_STOP:
browser->StopLoad();
break;
default:
NOTREACHED() << "id=" << id;
}
}
void RootWindowGtk::NotifyMenuItem(int id) {
if (!CURRENTLY_ON_MAIN_THREAD()) {
MAIN_POST_CLOSURE(base::Bind(&RootWindowGtk::NotifyMenuItem, this, id));
return;
}
// Run the test.
if (delegate_)
delegate_->OnTest(this, id);
}
void RootWindowGtk::NotifyForceClose() {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI, base::Bind(&RootWindowGtk::NotifyForceClose, this));
return;
}
force_close_ = true;
}
void RootWindowGtk::NotifyCloseBrowser() {
if (!CURRENTLY_ON_MAIN_THREAD()) {
MAIN_POST_CLOSURE(base::Bind(&RootWindowGtk::NotifyCloseBrowser, this));
return;
}
CefRefPtr<CefBrowser> browser = GetBrowser();
if (browser) {
browser->GetHost()->CloseBrowser(false);
}
}
void RootWindowGtk::NotifyDestroyedIfDone(bool window_destroyed,
bool browser_destroyed) {
// Each call will to this method will set only one state flag.
DCHECK_EQ(1, window_destroyed + browser_destroyed);
if (!CURRENTLY_ON_MAIN_THREAD()) {
MAIN_POST_CLOSURE(base::Bind(&RootWindowGtk::NotifyDestroyedIfDone, this,
window_destroyed, browser_destroyed));
return;
}
if (window_destroyed)
window_destroyed_ = true;
if (browser_destroyed)
browser_destroyed_ = true;
// Notify once both the window and the browser have been destroyed. // Notify once both the window and the browser have been destroyed.
if (window_destroyed_ && browser_destroyed_) if (window_destroyed_ && browser_destroyed_)
delegate_->OnRootWindowDestroyed(this); delegate_->OnRootWindowDestroyed(this);
@ -463,9 +675,11 @@ void RootWindowGtk::NotifyDestroyedIfDone() {
gboolean RootWindowGtk::WindowFocusIn(GtkWidget* widget, gboolean RootWindowGtk::WindowFocusIn(GtkWidget* widget,
GdkEventFocus* event, GdkEventFocus* event,
RootWindowGtk* self) { RootWindowGtk* self) {
if (event->in && self->browser_window_.get()) { CEF_REQUIRE_UI_THREAD();
self->browser_window_->SetFocus(true);
self->delegate_->OnRootWindowActivated(self); if (event->in) {
self->NotifySetFocus();
// Return true for a windowed browser so that focus is not passed to GTK. // Return true for a windowed browser so that focus is not passed to GTK.
return self->with_osr_ ? FALSE : TRUE; return self->with_osr_ ? FALSE : TRUE;
} }
@ -477,14 +691,13 @@ gboolean RootWindowGtk::WindowFocusIn(GtkWidget* widget,
gboolean RootWindowGtk::WindowState(GtkWidget* widget, gboolean RootWindowGtk::WindowState(GtkWidget* widget,
GdkEventWindowState* event, GdkEventWindowState* event,
RootWindowGtk* self) { RootWindowGtk* self) {
CEF_REQUIRE_UI_THREAD();
// Called when the root window is iconified or restored. Hide the browser // Called when the root window is iconified or restored. Hide the browser
// window when the root window is iconified to reduce resource usage. // window when the root window is iconified to reduce resource usage.
if ((event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) && if (event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) {
self->browser_window_.get()) { self->NotifyVisibilityChange(
if (event->new_window_state & GDK_WINDOW_STATE_ICONIFIED) !(event->new_window_state & GDK_WINDOW_STATE_ICONIFIED));
self->browser_window_->Hide();
else
self->browser_window_->Show();
} }
return TRUE; return TRUE;
@ -494,46 +707,36 @@ gboolean RootWindowGtk::WindowState(GtkWidget* widget,
gboolean RootWindowGtk::WindowConfigure(GtkWindow* window, gboolean RootWindowGtk::WindowConfigure(GtkWindow* window,
GdkEvent* event, GdkEvent* event,
RootWindowGtk* self) { RootWindowGtk* self) {
// Called when size, position or stack order changes. CEF_REQUIRE_UI_THREAD();
CefRefPtr<CefBrowser> browser = self->GetBrowser(); self->NotifyMoveOrResizeStarted();
if (browser.get()) {
// Notify the browser of move/resize events so that:
// - Popup windows are displayed in the correct location and dismissed
// when the window moves.
// - Drag&drop areas are updated accordingly.
browser->GetHost()->NotifyMoveOrResizeStarted();
}
return FALSE; // Don't stop this message. return FALSE; // Don't stop this message.
} }
// static // static
void RootWindowGtk::WindowDestroy(GtkWidget* widget, RootWindowGtk* self) { void RootWindowGtk::WindowDestroy(GtkWidget* widget, RootWindowGtk* self) {
// Called when the root window is destroyed. // May be called on the main thread or the UI thread.
self->window_destroyed_ = true; self->NotifyDestroyedIfDone(true, false);
self->NotifyDestroyedIfDone();
} }
// static // static
gboolean RootWindowGtk::WindowDelete(GtkWidget* widget, gboolean RootWindowGtk::WindowDelete(GtkWidget* widget,
GdkEvent* event, GdkEvent* event,
RootWindowGtk* self) { RootWindowGtk* self) {
CEF_REQUIRE_UI_THREAD();
// Called to query whether the root window should be closed. // Called to query whether the root window should be closed.
if (self->force_close_) if (self->force_close_)
return FALSE; // Allow the close. return FALSE; // Allow the close.
if (self->browser_window_.get() && !self->browser_window_->IsClosing()) { if (!self->is_closing_) {
CefRefPtr<CefBrowser> browser = self->GetBrowser();
if (browser) {
// Notify the browser window that we would like to close it. This // Notify the browser window that we would like to close it. This
// will result in a call to ClientHandler::DoClose() if the // will result in a call to ClientHandler::DoClose() if the
// JavaScript 'onbeforeunload' event handler allows it. // JavaScript 'onbeforeunload' event handler allows it.
browser->GetHost()->CloseBrowser(false); self->NotifyCloseBrowser();
// Cancel the close. // Cancel the close.
return TRUE; return TRUE;
} }
}
// Allow the close. // Allow the close.
return FALSE; return FALSE;
@ -543,35 +746,27 @@ gboolean RootWindowGtk::WindowDelete(GtkWidget* widget,
void RootWindowGtk::VboxSizeAllocated(GtkWidget* widget, void RootWindowGtk::VboxSizeAllocated(GtkWidget* widget,
GtkAllocation* allocation, GtkAllocation* allocation,
RootWindowGtk* self) { RootWindowGtk* self) {
// Offset browser positioning by any controls that will appear in the client // May be called on the main thread and the UI thread.
// area. self->NotifyContentBounds(allocation->x, allocation->y, allocation->width,
const int ux_height = self->toolbar_height_ + self->menubar_height_; allocation->height);
const int x = allocation->x;
const int y = allocation->y + ux_height;
const int width = allocation->width;
const int height = allocation->height - ux_height;
// Size the browser window to match the GTK widget.
self->browser_bounds_ = CefRect(x, y, width, height);
if (self->browser_window_.get())
self->browser_window_->SetBounds(x, y, width, height);
} }
// static // static
void RootWindowGtk::MenubarSizeAllocated(GtkWidget* widget, void RootWindowGtk::MenubarSizeAllocated(GtkWidget* widget,
GtkAllocation* allocation, GtkAllocation* allocation,
RootWindowGtk* self) { RootWindowGtk* self) {
self->menubar_height_ = allocation->height; // May be called on the main thread and the UI thread.
self->NotifyMenuBarHeight(allocation->height);
} }
// static // static
gboolean RootWindowGtk::MenuItemActivated(GtkWidget* widget, gboolean RootWindowGtk::MenuItemActivated(GtkWidget* widget,
RootWindowGtk* self) { RootWindowGtk* self) {
CEF_REQUIRE_UI_THREAD();
// Retrieve the menu ID set in AddMenuEntry. // Retrieve the menu ID set in AddMenuEntry.
int id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), kMenuIdKey)); int id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), kMenuIdKey));
// Run the test. self->NotifyMenuItem(id);
if (self->delegate_)
self->delegate_->OnTest(self, id);
return FALSE; // Don't stop this message. return FALSE; // Don't stop this message.
} }
@ -585,47 +780,43 @@ void RootWindowGtk::ToolbarSizeAllocated(GtkWidget* widget,
// static // static
void RootWindowGtk::BackButtonClicked(GtkButton* button, RootWindowGtk* self) { void RootWindowGtk::BackButtonClicked(GtkButton* button, RootWindowGtk* self) {
CefRefPtr<CefBrowser> browser = self->GetBrowser(); CEF_REQUIRE_UI_THREAD();
if (browser.get()) self->NotifyButtonClicked(IDC_NAV_BACK);
browser->GoBack();
} }
// static // static
void RootWindowGtk::ForwardButtonClicked(GtkButton* button, void RootWindowGtk::ForwardButtonClicked(GtkButton* button,
RootWindowGtk* self) { RootWindowGtk* self) {
CefRefPtr<CefBrowser> browser = self->GetBrowser(); CEF_REQUIRE_UI_THREAD();
if (browser.get()) self->NotifyButtonClicked(IDC_NAV_FORWARD);
browser->GoForward();
} }
// static // static
void RootWindowGtk::StopButtonClicked(GtkButton* button, RootWindowGtk* self) { void RootWindowGtk::StopButtonClicked(GtkButton* button, RootWindowGtk* self) {
CefRefPtr<CefBrowser> browser = self->GetBrowser(); CEF_REQUIRE_UI_THREAD();
if (browser.get()) self->NotifyButtonClicked(IDC_NAV_STOP);
browser->StopLoad();
} }
// static // static
void RootWindowGtk::ReloadButtonClicked(GtkButton* button, void RootWindowGtk::ReloadButtonClicked(GtkButton* button,
RootWindowGtk* self) { RootWindowGtk* self) {
CefRefPtr<CefBrowser> browser = self->GetBrowser(); CEF_REQUIRE_UI_THREAD();
if (browser.get()) self->NotifyButtonClicked(IDC_NAV_RELOAD);
browser->Reload();
} }
// static // static
void RootWindowGtk::URLEntryActivate(GtkEntry* entry, RootWindowGtk* self) { void RootWindowGtk::URLEntryActivate(GtkEntry* entry, RootWindowGtk* self) {
CefRefPtr<CefBrowser> browser = self->GetBrowser(); CEF_REQUIRE_UI_THREAD();
if (browser.get()) {
const gchar* url = gtk_entry_get_text(entry); const gchar* url = gtk_entry_get_text(entry);
browser->GetMainFrame()->LoadURL(std::string(url).c_str()); self->NotifyLoadURL(std::string(url));
}
} }
// static // static
gboolean RootWindowGtk::URLEntryButtonPress(GtkWidget* widget, gboolean RootWindowGtk::URLEntryButtonPress(GtkWidget* widget,
GdkEventButton* event, GdkEventButton* event,
RootWindowGtk* self) { RootWindowGtk* self) {
CEF_REQUIRE_UI_THREAD();
// Give focus to the GTK window. This is a work-around for bad focus-related // Give focus to the GTK window. This is a work-around for bad focus-related
// interaction between the root window managed by GTK and the browser managed // interaction between the root window managed by GTK and the browser managed
// by X11. // by X11.

View File

@ -53,6 +53,7 @@ class RootWindowGtk : public RootWindow, public BrowserWindow::Delegate {
// BrowserWindow::Delegate methods. // BrowserWindow::Delegate methods.
void OnBrowserCreated(CefRefPtr<CefBrowser> browser) OVERRIDE; void OnBrowserCreated(CefRefPtr<CefBrowser> browser) OVERRIDE;
void OnBrowserWindowClosing() OVERRIDE;
void OnBrowserWindowDestroyed() OVERRIDE; void OnBrowserWindowDestroyed() OVERRIDE;
void OnSetAddress(const std::string& url) OVERRIDE; void OnSetAddress(const std::string& url) OVERRIDE;
void OnSetTitle(const std::string& title) OVERRIDE; void OnSetTitle(const std::string& title) OVERRIDE;
@ -64,7 +65,17 @@ class RootWindowGtk : public RootWindow, public BrowserWindow::Delegate {
void OnSetDraggableRegions( void OnSetDraggableRegions(
const std::vector<CefDraggableRegion>& regions) OVERRIDE; const std::vector<CefDraggableRegion>& regions) OVERRIDE;
void NotifyDestroyedIfDone(); void NotifyMoveOrResizeStarted();
void NotifySetFocus();
void NotifyVisibilityChange(bool show);
void NotifyMenuBarHeight(int height);
void NotifyContentBounds(int x, int y, int width, int height);
void NotifyLoadURL(const std::string& url);
void NotifyButtonClicked(int id);
void NotifyMenuItem(int id);
void NotifyForceClose();
void NotifyCloseBrowser();
void NotifyDestroyedIfDone(bool window_destroyed, bool browser_destroyed);
GtkWidget* CreateMenuBar(); GtkWidget* CreateMenuBar();
GtkWidget* CreateMenu(GtkWidget* menu_bar, const char* text); GtkWidget* CreateMenu(GtkWidget* menu_bar, const char* text);
@ -139,10 +150,14 @@ class RootWindowGtk : public RootWindow, public BrowserWindow::Delegate {
CefRect browser_bounds_; CefRect browser_bounds_;
bool force_close_;
bool window_destroyed_; bool window_destroyed_;
bool browser_destroyed_; bool browser_destroyed_;
// Members only accessed on the UI thread because they're needed for
// WindowDelete.
bool force_close_;
bool is_closing_;
DISALLOW_COPY_AND_ASSIGN(RootWindowGtk); DISALLOW_COPY_AND_ASSIGN(RootWindowGtk);
}; };

View File

@ -159,6 +159,11 @@ scoped_refptr<RootWindow> RootWindowManager::CreateRootWindowAsPopup(
CefBrowserSettings& settings) { CefBrowserSettings& settings) {
CEF_REQUIRE_UI_THREAD(); CEF_REQUIRE_UI_THREAD();
if (!temp_window_) {
// TempWindow must be created on the UI thread.
temp_window_.reset(new TempWindow());
}
MainContext::Get()->PopulateBrowserSettings(&settings); MainContext::Get()->PopulateBrowserSettings(&settings);
scoped_refptr<RootWindow> root_window = scoped_refptr<RootWindow> root_window =
@ -390,8 +395,9 @@ void RootWindowManager::OnRootWindowDestroyed(RootWindow* root_window) {
} }
if (terminate_when_all_windows_closed_ && root_windows_.empty()) { if (terminate_when_all_windows_closed_ && root_windows_.empty()) {
// Quit the main message loop after all windows have closed. // All windows have closed. Clean up on the UI thread.
MainMessageLoop::Get()->Quit(); CefPostTask(TID_UI, base::Bind(&RootWindowManager::CleanupOnUIThread,
base::Unretained(this)));
} }
} }
@ -440,4 +446,16 @@ void RootWindowManager::CreateExtensionWindow(
} }
} }
void RootWindowManager::CleanupOnUIThread() {
CEF_REQUIRE_UI_THREAD();
if (temp_window_) {
// TempWindow must be destroyed on the UI thread.
temp_window_.reset(nullptr);
}
// Quit the main message loop.
MainMessageLoop::Get()->Quit();
}
} // namespace client } // namespace client

View File

@ -106,6 +106,8 @@ class RootWindowManager : public RootWindow::Delegate {
const base::Closure& close_callback, const base::Closure& close_callback,
bool with_osr) OVERRIDE; bool with_osr) OVERRIDE;
void CleanupOnUIThread();
const bool terminate_when_all_windows_closed_; const bool terminate_when_all_windows_closed_;
bool request_context_per_browser_; bool request_context_per_browser_;
bool request_context_shared_cache_; bool request_context_shared_cache_;
@ -124,7 +126,7 @@ class RootWindowManager : public RootWindow::Delegate {
CefRefPtr<CefBrowser> active_browser_; CefRefPtr<CefBrowser> active_browser_;
// Singleton window used as the temporary parent for popup browsers. // Singleton window used as the temporary parent for popup browsers.
TempWindow temp_window_; scoped_ptr<TempWindow> temp_window_;
CefRefPtr<CefRequestContext> shared_request_context_; CefRefPtr<CefRequestContext> shared_request_context_;

View File

@ -11,7 +11,7 @@
namespace client { namespace client {
// Represents a singleton hidden window that acts as a temporary parent for // Represents a singleton hidden window that acts as a temporary parent for
// popup browsers. // popup browsers. Only accessed on the UI thread.
class TempWindowMac { class TempWindowMac {
public: public:
// Returns the singleton window handle. // Returns the singleton window handle.
@ -20,6 +20,8 @@ class TempWindowMac {
private: private:
// A single instance will be created/owned by RootWindowManager. // A single instance will be created/owned by RootWindowManager.
friend class RootWindowManager; friend class RootWindowManager;
// Allow deletion via scoped_ptr only.
friend struct base::DefaultDeleter<TempWindowMac>;
TempWindowMac(); TempWindowMac();
~TempWindowMac(); ~TempWindowMac();

View File

@ -11,7 +11,7 @@
namespace client { namespace client {
// Represents a singleton hidden window that acts as a temporary parent for // Represents a singleton hidden window that acts as a temporary parent for
// popup browsers. // popup browsers. Only accessed on the UI thread.
class TempWindowWin { class TempWindowWin {
public: public:
// Returns the singleton window handle. // Returns the singleton window handle.
@ -20,6 +20,8 @@ class TempWindowWin {
private: private:
// A single instance will be created/owned by RootWindowManager. // A single instance will be created/owned by RootWindowManager.
friend class RootWindowManager; friend class RootWindowManager;
// Allow deletion via scoped_ptr only.
friend struct base::DefaultDeleter<TempWindowWin>;
TempWindowWin(); TempWindowWin();
~TempWindowWin(); ~TempWindowWin();

View File

@ -16,6 +16,7 @@ namespace {
// Create the temp window. // Create the temp window.
::Window CreateTempWindow() { ::Window CreateTempWindow() {
::Display* xdisplay = cef_get_xdisplay(); ::Display* xdisplay = cef_get_xdisplay();
CHECK(xdisplay != 0);
::Window parent_xwindow = DefaultRootWindow(xdisplay); ::Window parent_xwindow = DefaultRootWindow(xdisplay);
XSetWindowAttributes swa; XSetWindowAttributes swa;
@ -33,6 +34,7 @@ namespace {
// Close the temp window. // Close the temp window.
void CloseTempWindow(::Window xwindow) { void CloseTempWindow(::Window xwindow) {
::Display* xdisplay = cef_get_xdisplay(); ::Display* xdisplay = cef_get_xdisplay();
CHECK(xdisplay != 0);
XDestroyWindow(xdisplay, xwindow); XDestroyWindow(xdisplay, xwindow);
} }

View File

@ -11,7 +11,7 @@
namespace client { namespace client {
// Represents a singleton hidden window that acts as a temporary parent for // Represents a singleton hidden window that acts as a temporary parent for
// popup browsers. // popup browsers. Only accessed on the UI thread.
class TempWindowX11 { class TempWindowX11 {
public: public:
// Returns the singleton window handle. // Returns the singleton window handle.
@ -20,6 +20,8 @@ class TempWindowX11 {
private: private:
// A single instance will be created/owned by RootWindowManager. // A single instance will be created/owned by RootWindowManager.
friend class RootWindowManager; friend class RootWindowManager;
// Allow deletion via scoped_ptr only.
friend struct base::DefaultDeleter<TempWindowX11>;
TempWindowX11(); TempWindowX11();
~TempWindowX11(); ~TempWindowX11();

View File

@ -0,0 +1,33 @@
// Copyright (c) 2018 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 "tests/cefclient/browser/util_gtk.h"
#include <gdk/gdk.h>
namespace client {
base::PlatformThreadId ScopedGdkThreadsEnter::locked_thread_ =
kInvalidPlatformThreadId;
ScopedGdkThreadsEnter::ScopedGdkThreadsEnter() {
// The GDK lock is not reentrant, so don't try to lock again if the current
// thread already holds it.
base::PlatformThreadId current_thread = base::PlatformThread::CurrentId();
take_lock_ = current_thread != locked_thread_;
if (take_lock_) {
gdk_threads_enter();
locked_thread_ = current_thread;
}
}
ScopedGdkThreadsEnter::~ScopedGdkThreadsEnter() {
if (take_lock_) {
locked_thread_ = kInvalidPlatformThreadId;
gdk_threads_leave();
}
}
} // namespace client

View File

@ -0,0 +1,41 @@
// Copyright (c) 2018 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_TESTS_CEFCLIENT_BROWSER_UTIL_GTK_H_
#define CEF_TESTS_CEFCLIENT_BROWSER_UTIL_GTK_H_
#pragma once
#include "include/base/cef_macros.h"
#include "include/base/cef_platform_thread.h"
namespace client {
// Scoped helper that manages the global GDK lock by calling gdk_threads_enter()
// and gdk_threads_leave(). The lock is not reentrant so this helper implements
// additional checking to avoid deadlocks.
//
// When using GTK in multi-threaded mode you must do the following:
// 1. Call gdk_threads_init() before making any other GTK/GDK/GLib calls.
// 2. Acquire the global lock before making any GTK/GDK calls, and release the
// lock afterwards. This should only be done with callbacks that do not
// originate from GTK signals (because those callbacks already hold the
// lock).
//
// See https://www.geany.org/manual/gtk/gtk-faq/x482.html for more information.
class ScopedGdkThreadsEnter {
public:
ScopedGdkThreadsEnter();
~ScopedGdkThreadsEnter();
private:
bool take_lock_;
static base::PlatformThreadId locked_thread_;
DISALLOW_COPY_AND_ASSIGN(ScopedGdkThreadsEnter);
};
} // namespace client
#endif // CEF_TESTS_CEFCLIENT_BROWSER_UTIL_GTK_H_

View File

@ -8,6 +8,7 @@
#include "include/wrapper/cef_helpers.h" #include "include/wrapper/cef_helpers.h"
#include "tests/cefclient/browser/root_window.h" #include "tests/cefclient/browser/root_window.h"
#include "tests/cefclient/browser/util_gtk.h"
#include "tests/shared/browser/main_message_loop.h" #include "tests/shared/browser/main_message_loop.h"
namespace client { namespace client {
@ -33,17 +34,13 @@ bool IsMaximized(GtkWindow* window) {
return (state & GDK_WINDOW_STATE_MAXIMIZED) ? true : false; return (state & GDK_WINDOW_STATE_MAXIMIZED) ? true : false;
} }
} // namespace void SetPosImpl(CefRefPtr<CefBrowser> browser,
WindowTestRunnerGtk::WindowTestRunnerGtk() {}
void WindowTestRunnerGtk::SetPos(CefRefPtr<CefBrowser> browser,
int x, int x,
int y, int y,
int width, int width,
int height) { int height) {
CEF_REQUIRE_UI_THREAD();
REQUIRE_MAIN_THREAD(); REQUIRE_MAIN_THREAD();
ScopedGdkThreadsEnter scoped_gdk_threads;
GtkWindow* window = GetWindow(browser); GtkWindow* window = GetWindow(browser);
if (!window) if (!window)
@ -65,15 +62,15 @@ void WindowTestRunnerGtk::SetPos(CefRefPtr<CefBrowser> browser,
// Make sure the window is inside the display. // Make sure the window is inside the display.
CefRect display_rect(rect.x, rect.y, rect.width, rect.height); CefRect display_rect(rect.x, rect.y, rect.width, rect.height);
CefRect window_rect(x, y, width, height); CefRect window_rect(x, y, width, height);
ModifyBounds(display_rect, window_rect); WindowTestRunner::ModifyBounds(display_rect, window_rect);
gdk_window_move_resize(gdk_window, window_rect.x, window_rect.y, gdk_window_move_resize(gdk_window, window_rect.x, window_rect.y,
window_rect.width, window_rect.height); window_rect.width, window_rect.height);
} }
void WindowTestRunnerGtk::Minimize(CefRefPtr<CefBrowser> browser) { void MinimizeImpl(CefRefPtr<CefBrowser> browser) {
CEF_REQUIRE_UI_THREAD();
REQUIRE_MAIN_THREAD(); REQUIRE_MAIN_THREAD();
ScopedGdkThreadsEnter scoped_gdk_threads;
GtkWindow* window = GetWindow(browser); GtkWindow* window = GetWindow(browser);
if (!window) if (!window)
@ -86,9 +83,9 @@ void WindowTestRunnerGtk::Minimize(CefRefPtr<CefBrowser> browser) {
gtk_window_iconify(window); gtk_window_iconify(window);
} }
void WindowTestRunnerGtk::Maximize(CefRefPtr<CefBrowser> browser) { void MaximizeImpl(CefRefPtr<CefBrowser> browser) {
CEF_REQUIRE_UI_THREAD();
REQUIRE_MAIN_THREAD(); REQUIRE_MAIN_THREAD();
ScopedGdkThreadsEnter scoped_gdk_threads;
GtkWindow* window = GetWindow(browser); GtkWindow* window = GetWindow(browser);
if (!window) if (!window)
@ -96,9 +93,9 @@ void WindowTestRunnerGtk::Maximize(CefRefPtr<CefBrowser> browser) {
gtk_window_maximize(window); gtk_window_maximize(window);
} }
void WindowTestRunnerGtk::Restore(CefRefPtr<CefBrowser> browser) { void RestoreImpl(CefRefPtr<CefBrowser> browser) {
CEF_REQUIRE_UI_THREAD();
REQUIRE_MAIN_THREAD(); REQUIRE_MAIN_THREAD();
ScopedGdkThreadsEnter scoped_gdk_threads;
GtkWindow* window = GetWindow(browser); GtkWindow* window = GetWindow(browser);
if (!window) if (!window)
@ -109,5 +106,29 @@ void WindowTestRunnerGtk::Restore(CefRefPtr<CefBrowser> browser) {
gtk_window_present(window); gtk_window_present(window);
} }
} // namespace
WindowTestRunnerGtk::WindowTestRunnerGtk() {}
void WindowTestRunnerGtk::SetPos(CefRefPtr<CefBrowser> browser,
int x,
int y,
int width,
int height) {
MAIN_POST_CLOSURE(base::Bind(SetPosImpl, browser, x, y, width, height));
}
void WindowTestRunnerGtk::Minimize(CefRefPtr<CefBrowser> browser) {
MAIN_POST_CLOSURE(base::Bind(MinimizeImpl, browser));
}
void WindowTestRunnerGtk::Maximize(CefRefPtr<CefBrowser> browser) {
MAIN_POST_CLOSURE(base::Bind(MaximizeImpl, browser));
}
void WindowTestRunnerGtk::Restore(CefRefPtr<CefBrowser> browser) {
MAIN_POST_CLOSURE(base::Bind(RestoreImpl, browser));
}
} // namespace window_test } // namespace window_test
} // namespace client } // namespace client

View File

@ -19,6 +19,7 @@
#include "include/cef_command_line.h" #include "include/cef_command_line.h"
#include "include/wrapper/cef_helpers.h" #include "include/wrapper/cef_helpers.h"
#include "tests/cefclient/browser/main_context_impl.h" #include "tests/cefclient/browser/main_context_impl.h"
#include "tests/cefclient/browser/main_message_loop_multithreaded_gtk.h"
#include "tests/cefclient/browser/test_runner.h" #include "tests/cefclient/browser/test_runner.h"
#include "tests/shared/browser/client_app_browser.h" #include "tests/shared/browser/client_app_browser.h"
#include "tests/shared/browser/main_message_loop_external_pump.h" #include "tests/shared/browser/main_message_loop_external_pump.h"
@ -99,7 +100,9 @@ int RunMain(int argc, char* argv[]) {
// Create the main message loop object. // Create the main message loop object.
scoped_ptr<MainMessageLoop> message_loop; scoped_ptr<MainMessageLoop> message_loop;
if (settings.external_message_pump) if (settings.multi_threaded_message_loop)
message_loop.reset(new MainMessageLoopMultithreadedGtk);
else if (settings.external_message_pump)
message_loop = MainMessageLoopExternalPump::Create(); message_loop = MainMessageLoopExternalPump::Create();
else else
message_loop.reset(new MainMessageLoopStd); message_loop.reset(new MainMessageLoopStd);

View File

@ -119,7 +119,7 @@ int CefTestSuite::Run() {
} }
void CefTestSuite::GetSettings(CefSettings& settings) const { void CefTestSuite::GetSettings(CefSettings& settings) const {
#if defined(OS_WIN) #if (defined(OS_WIN) || defined(OS_LINUX))
settings.multi_threaded_message_loop = settings.multi_threaded_message_loop =
command_line_->HasSwitch(client::switches::kMultiThreadedMessageLoop); command_line_->HasSwitch(client::switches::kMultiThreadedMessageLoop);
#endif #endif