diff --git a/cef_paths2.gypi b/cef_paths2.gypi index 3c71213d0..20cbbbbfb 100644 --- a/cef_paths2.gypi +++ b/cef_paths2.gypi @@ -368,6 +368,8 @@ 'tests/cefclient/browser/dialog_handler_gtk.cc', 'tests/cefclient/browser/dialog_handler_gtk.h', '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.h', 'tests/cefclient/browser/resource_util_linux.cc', @@ -377,6 +379,8 @@ 'tests/cefclient/browser/root_window_views.h', 'tests/cefclient/browser/temp_window_x11.cc', '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.h', 'tests/cefclient/browser/views_style.cc', diff --git a/include/internal/cef_types.h b/include/internal/cef_types.h index 5bac44a84..384a73318 100644 --- a/include/internal/cef_types.h +++ b/include/internal/cef_types.h @@ -181,7 +181,7 @@ typedef struct _cef_settings_t { // 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 // called from your application message loop. This option is only supported on - // Windows. + // Windows and Linux. /// int multi_threaded_message_loop; diff --git a/libcef/browser/context.cc b/libcef/browser/context.cc index 633ca1764..fe767a111 100644 --- a/libcef/browser/context.cc +++ b/libcef/browser/context.cc @@ -29,6 +29,7 @@ #include "content/public/browser/render_process_host.h" #include "content/public/common/content_switches.h" #include "services/service_manager/embedder/main.h" +#include "ui/base/resource/resource_bundle.h" #include "ui/base/ui_base_switches.h" #if defined(OS_WIN) @@ -343,7 +344,7 @@ bool CefContext::Initialize(const CefMainArgs& args, init_thread_id_ = base::PlatformThread::CurrentId(); settings_ = settings; -#if !defined(OS_WIN) +#if !(defined(OS_WIN) || defined(OS_LINUX)) if (settings.multi_threaded_message_loop) { NOTIMPLEMENTED() << "multi_threaded_message_loop is not supported."; return false; @@ -526,6 +527,8 @@ void CefContext::FinishShutdownOnUIThread( static_cast(g_browser_process)->Shutdown(); + ui::ResourceBundle::GetSharedInstance().CleanupOnUIThread(); + if (uithread_shutdown_event) uithread_shutdown_event->Signal(); } diff --git a/libcef/browser/native/window_x11.cc b/libcef/browser/native/window_x11.cc index 5429fd49e..298a13c11 100644 --- a/libcef/browser/native/window_x11.cc +++ b/libcef/browser/native/window_x11.cc @@ -104,6 +104,7 @@ CefWindowX11::CefWindowX11(CefRefPtr browser, InputOutput, CopyFromParent, // visual CWBackPixmap | CWOverrideRedirect, &swa); + CHECK(xwindow_); if (ui::PlatformEventSource::GetInstance()) ui::PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this); diff --git a/patch/patch.cfg b/patch/patch.cfg index c80a4b616..21054340a 100644 --- a/patch/patch.cfg +++ b/patch/patch.cfg @@ -380,6 +380,12 @@ patches = [ # https://bitbucket.org/chromiumembedded/cef/issues/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 tools/clang/scripts/update.py failure with custom VS toolchain on # Windows. This needs to be done in DEPS because it executes during the diff --git a/patch/patches/resource_bundle_2512.patch b/patch/patches/resource_bundle_2512.patch new file mode 100644 index 000000000..12d92fc3e --- /dev/null +++ b/patch/patches/resource_bundle_2512.patch @@ -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, diff --git a/tests/cefclient/browser/browser_window.cc b/tests/cefclient/browser/browser_window.cc index 708b98d5f..47e9f1270 100644 --- a/tests/cefclient/browser/browser_window.cc +++ b/tests/cefclient/browser/browser_window.cc @@ -42,6 +42,8 @@ void BrowserWindow::OnBrowserClosing(CefRefPtr browser) { REQUIRE_MAIN_THREAD(); DCHECK_EQ(browser->GetIdentifier(), browser_->GetIdentifier()); is_closing_ = true; + + delegate_->OnBrowserWindowClosing(); } void BrowserWindow::OnBrowserClosed(CefRefPtr browser) { diff --git a/tests/cefclient/browser/browser_window.h b/tests/cefclient/browser/browser_window.h index 06d57dd20..40c738b41 100644 --- a/tests/cefclient/browser/browser_window.h +++ b/tests/cefclient/browser/browser_window.h @@ -25,6 +25,9 @@ class BrowserWindow : public ClientHandler::Delegate { // Called when the browser has been created. virtual void OnBrowserCreated(CefRefPtr browser) = 0; + // Called when the BrowserWindow is closing. + virtual void OnBrowserWindowClosing() {} + // Called when the BrowserWindow has been destroyed. virtual void OnBrowserWindowDestroyed() = 0; diff --git a/tests/cefclient/browser/browser_window_osr_gtk.cc b/tests/cefclient/browser/browser_window_osr_gtk.cc index 0d4a78464..8600bed02 100644 --- a/tests/cefclient/browser/browser_window_osr_gtk.cc +++ b/tests/cefclient/browser/browser_window_osr_gtk.cc @@ -19,6 +19,7 @@ #include "include/base/cef_logging.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/main_message_loop.h" @@ -922,6 +923,7 @@ class ScopedGLContext { bool swap_buffers_; GdkGLDrawable* gldrawable_; bool is_valid_; + ScopedGdkThreadsEnter scoped_gdk_threads_; }; } // namespace @@ -930,23 +932,26 @@ BrowserWindowOsrGtk::BrowserWindowOsrGtk(BrowserWindow::Delegate* delegate, const std::string& startup_url, const OsrRenderer::Settings& settings) : BrowserWindow(delegate), + xdisplay_(nullptr), renderer_(settings), - glarea_(NULL), - hidden_(false), gl_enabled_(false), painting_popup_(false), - device_scale_factor_(1.0f), + hidden_(false), + glarea_(NULL), drag_trigger_event_(NULL), drag_data_(NULL), drag_operation_(DRAG_OPERATION_NONE), drag_context_(NULL), drag_targets_(gtk_target_list_new(NULL, 0)), drag_leave_(false), - drag_drop_(false) { + drag_drop_(false), + device_scale_factor_(1.0f) { client_handler_ = new ClientHandlerOsr(this, this, startup_url); } BrowserWindowOsrGtk::~BrowserWindowOsrGtk() { + ScopedGdkThreadsEnter scoped_gdk_threads; + if (drag_trigger_event_) { gdk_event_free(drag_trigger_event_); } @@ -956,6 +961,12 @@ BrowserWindowOsrGtk::~BrowserWindowOsrGtk() { gtk_target_list_unref(drag_targets_); } +void BrowserWindowOsrGtk::set_xdisplay(XDisplay* xdisplay) { + REQUIRE_MAIN_THREAD(); + DCHECK(!xdisplay_); + xdisplay_ = xdisplay; +} + void BrowserWindowOsrGtk::CreateBrowser( ClientWindowHandle parent_handle, const CefRect& rect, @@ -966,6 +977,8 @@ void BrowserWindowOsrGtk::CreateBrowser( // Create the native window. Create(parent_handle); + ScopedGdkThreadsEnter scoped_gdk_threads; + // Retrieve the X11 Window ID for the GTK parent window. GtkWidget* window = gtk_widget_get_ancestor(GTK_WIDGET(parent_handle), GTK_TYPE_WINDOW); @@ -1045,20 +1058,25 @@ void BrowserWindowOsrGtk::SetBounds(int x, int y, size_t width, size_t height) { void BrowserWindowOsrGtk::SetFocus(bool focus) { REQUIRE_MAIN_THREAD(); - if (glarea_ && focus) + ScopedGdkThreadsEnter scoped_gdk_threads; + if (glarea_ && focus) { gtk_widget_grab_focus(glarea_); + } } void BrowserWindowOsrGtk::SetDeviceScaleFactor(float device_scale_factor) { REQUIRE_MAIN_THREAD(); - if (device_scale_factor == device_scale_factor_) - return; + { + base::AutoLock lock_scope(lock_); + if (device_scale_factor == device_scale_factor_) + return; - // Apply some sanity checks. - if (device_scale_factor < 1.0f || device_scale_factor > 4.0f) - return; + // Apply some sanity checks. + if (device_scale_factor < 1.0f || device_scale_factor > 4.0f) + return; - device_scale_factor_ = device_scale_factor; + device_scale_factor_ = device_scale_factor; + } if (browser_) { browser_->GetHost()->NotifyScreenInfoChanged(); @@ -1068,6 +1086,7 @@ void BrowserWindowOsrGtk::SetDeviceScaleFactor(float device_scale_factor) { float BrowserWindowOsrGtk::GetDeviceScaleFactor() const { REQUIRE_MAIN_THREAD(); + base::AutoLock lock_scope(lock_); return device_scale_factor_; } @@ -1082,11 +1101,12 @@ void BrowserWindowOsrGtk::OnAfterCreated(CefRefPtr browser) { void BrowserWindowOsrGtk::OnBeforeClose(CefRefPtr browser) { CEF_REQUIRE_UI_THREAD(); - REQUIRE_MAIN_THREAD(); // Detach |this| from the ClientHandlerOsr. static_cast(client_handler_.get())->DetachOsrDelegate(); + ScopedGdkThreadsEnter scoped_gdk_threads; + UnregisterDragDrop(); // Disconnect all signal handlers that reference |this|. @@ -1105,17 +1125,22 @@ bool BrowserWindowOsrGtk::GetRootScreenRect(CefRefPtr browser, bool BrowserWindowOsrGtk::GetViewRect(CefRefPtr browser, CefRect& rect) { CEF_REQUIRE_UI_THREAD(); - REQUIRE_MAIN_THREAD(); if (!glarea_) 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 // for popup menus to be located and sized inside the view. 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 = - DeviceToLogical(glarea_->allocation.height, device_scale_factor_); + DeviceToLogical(glarea_->allocation.height, device_scale_factor); return true; } @@ -1125,12 +1150,17 @@ bool BrowserWindowOsrGtk::GetScreenPoint(CefRefPtr browser, int& screenX, int& screenY) { 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; GetWidgetRectInScreen(glarea_, &screen_rect); - screenX = screen_rect.x + LogicalToDevice(viewX, device_scale_factor_); - screenY = screen_rect.y + LogicalToDevice(viewY, device_scale_factor_); + screenX = screen_rect.x + LogicalToDevice(viewX, device_scale_factor); + screenY = screen_rect.y + LogicalToDevice(viewY, device_scale_factor); return true; } @@ -1141,7 +1171,13 @@ bool BrowserWindowOsrGtk::GetScreenInfo(CefRefPtr browser, CefRect 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 // popups. Keep popups inside the view rectangle. @@ -1153,7 +1189,6 @@ bool BrowserWindowOsrGtk::GetScreenInfo(CefRefPtr browser, void BrowserWindowOsrGtk::OnPopupShow(CefRefPtr browser, bool show) { CEF_REQUIRE_UI_THREAD(); - REQUIRE_MAIN_THREAD(); if (!show) { renderer_.ClearPopupRects(); @@ -1165,9 +1200,14 @@ void BrowserWindowOsrGtk::OnPopupShow(CefRefPtr browser, void BrowserWindowOsrGtk::OnPopupSize(CefRefPtr browser, const CefRect& rect) { 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 browser, @@ -1177,7 +1217,6 @@ void BrowserWindowOsrGtk::OnPaint(CefRefPtr browser, int width, int height) { CEF_REQUIRE_UI_THREAD(); - REQUIRE_MAIN_THREAD(); if (width <= 2 && height <= 2) { // Ignore really small buffer sizes while the widget is starting up. @@ -1211,17 +1250,17 @@ void BrowserWindowOsrGtk::OnCursorChange( CefRenderHandler::CursorType type, const CefCursorInfo& custom_cursor_info) { CEF_REQUIRE_UI_THREAD(); - REQUIRE_MAIN_THREAD(); // Retrieve the X11 display shared with Chromium. - ::Display* xdisplay = cef_get_xdisplay(); - DCHECK(xdisplay); + CHECK(xdisplay_ != 0); + + ScopedGdkThreadsEnter scoped_gdk_threads; // Retrieve the X11 window handle for the GTK widget. ::Window xwindow = GDK_WINDOW_XID(gtk_widget_get_window(glarea_)); // Set the cursor. - XDefineCursor(xdisplay, xwindow, cursor); + XDefineCursor(xdisplay_, xwindow, cursor); } bool BrowserWindowOsrGtk::StartDragging( @@ -1231,7 +1270,6 @@ bool BrowserWindowOsrGtk::StartDragging( int x, int y) { CEF_REQUIRE_UI_THREAD(); - REQUIRE_MAIN_THREAD(); if (!drag_data->HasImage()) { LOG(ERROR) << "Drag image representation not available"; @@ -1241,6 +1279,8 @@ bool BrowserWindowOsrGtk::StartDragging( DragReset(); drag_data_ = drag_data; + ScopedGdkThreadsEnter scoped_gdk_threads; + // Begin drag. if (drag_trigger_event_) { LOG(ERROR) << "Dragging started, but last mouse event is missing"; @@ -1271,7 +1311,6 @@ void BrowserWindowOsrGtk::UpdateDragCursor( CefRefPtr browser, CefRenderHandler::DragOperation operation) { CEF_REQUIRE_UI_THREAD(); - REQUIRE_MAIN_THREAD(); drag_operation_ = operation; } @@ -1290,6 +1329,8 @@ void BrowserWindowOsrGtk::Create(ClientWindowHandle parent_handle) { REQUIRE_MAIN_THREAD(); DCHECK(!glarea_); + ScopedGdkThreadsEnter scoped_gdk_threads; + glarea_ = gtk_drawing_area_new(); DCHECK(glarea_); @@ -1344,8 +1385,7 @@ void BrowserWindowOsrGtk::Create(ClientWindowHandle parent_handle) { gint BrowserWindowOsrGtk::SizeAllocation(GtkWidget* widget, GtkAllocation* allocation, BrowserWindowOsrGtk* self) { - REQUIRE_MAIN_THREAD(); - + CEF_REQUIRE_UI_THREAD(); if (self->browser_.get()) { // Results in a call to GetViewRect(). self->browser_->GetHost()->WasResized(); @@ -1357,7 +1397,7 @@ gint BrowserWindowOsrGtk::SizeAllocation(GtkWidget* widget, gint BrowserWindowOsrGtk::ClickEvent(GtkWidget* widget, GdkEventButton* event, BrowserWindowOsrGtk* self) { - REQUIRE_MAIN_THREAD(); + CEF_REQUIRE_UI_THREAD(); if (!self->browser_.get()) return TRUE; @@ -1379,16 +1419,23 @@ gint BrowserWindowOsrGtk::ClickEvent(GtkWidget* widget, return FALSE; } + float device_scale_factor; + { + base::AutoLock lock_scope(self->lock_); + device_scale_factor = self->device_scale_factor_; + } + CefMouseEvent mouse_event; mouse_event.x = event->x; mouse_event.y = 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); bool mouse_up = (event->type == GDK_BUTTON_RELEASE); - if (!mouse_up) + if (!mouse_up) { gtk_widget_grab_focus(widget); + } int click_count = 1; switch (event->type) { @@ -1420,7 +1467,7 @@ gint BrowserWindowOsrGtk::ClickEvent(GtkWidget* widget, gint BrowserWindowOsrGtk::KeyEvent(GtkWidget* widget, GdkEventKey* event, BrowserWindowOsrGtk* self) { - REQUIRE_MAIN_THREAD(); + CEF_REQUIRE_UI_THREAD(); if (!self->browser_.get()) return TRUE; @@ -1476,7 +1523,7 @@ gint BrowserWindowOsrGtk::KeyEvent(GtkWidget* widget, gint BrowserWindowOsrGtk::MoveEvent(GtkWidget* widget, GdkEventMotion* event, BrowserWindowOsrGtk* self) { - REQUIRE_MAIN_THREAD(); + CEF_REQUIRE_UI_THREAD(); if (!self->browser_.get()) 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; mouse_event.x = x; mouse_event.y = 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); bool mouse_leave = (event->type == GDK_LEAVE_NOTIFY); @@ -1527,18 +1580,24 @@ gint BrowserWindowOsrGtk::MoveEvent(GtkWidget* widget, gint BrowserWindowOsrGtk::ScrollEvent(GtkWidget* widget, GdkEventScroll* event, BrowserWindowOsrGtk* self) { - REQUIRE_MAIN_THREAD(); + CEF_REQUIRE_UI_THREAD(); if (!self->browser_.get()) return TRUE; CefRefPtr host = self->browser_->GetHost(); + float device_scale_factor; + { + base::AutoLock lock_scope(self->lock_); + device_scale_factor = self->device_scale_factor_; + } + CefMouseEvent mouse_event; mouse_event.x = event->x; mouse_event.y = 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); static const int scrollbarPixelsPerGtkTick = 40; @@ -1567,8 +1626,7 @@ gint BrowserWindowOsrGtk::ScrollEvent(GtkWidget* widget, gint BrowserWindowOsrGtk::FocusEvent(GtkWidget* widget, GdkEventFocus* event, BrowserWindowOsrGtk* self) { - REQUIRE_MAIN_THREAD(); - + // May be called on the main thread and the UI thread. if (self->browser_.get()) self->browser_->GetHost()->SendFocusEvent(event->in == TRUE); return TRUE; @@ -1597,7 +1655,7 @@ void BrowserWindowOsrGtk::ApplyPopupOffset(int& x, int& y) const { } void BrowserWindowOsrGtk::EnableGL() { - REQUIRE_MAIN_THREAD(); + CEF_REQUIRE_UI_THREAD(); if (gl_enabled_) return; @@ -1612,7 +1670,7 @@ void BrowserWindowOsrGtk::EnableGL() { } void BrowserWindowOsrGtk::DisableGL() { - REQUIRE_MAIN_THREAD(); + CEF_REQUIRE_UI_THREAD(); if (!gl_enabled_) return; @@ -1629,6 +1687,8 @@ void BrowserWindowOsrGtk::DisableGL() { void BrowserWindowOsrGtk::RegisterDragDrop() { REQUIRE_MAIN_THREAD(); + ScopedGdkThreadsEnter scoped_gdk_threads; + // Succession of CEF d&d calls: // 1. DragTargetDragEnter // 2. DragTargetDragOver @@ -1679,14 +1739,15 @@ void BrowserWindowOsrGtk::RegisterDragDrop() { } void BrowserWindowOsrGtk::UnregisterDragDrop() { - REQUIRE_MAIN_THREAD(); + CEF_REQUIRE_UI_THREAD(); + ScopedGdkThreadsEnter scoped_gdk_threads; gtk_drag_dest_unset(glarea_); // Drag events are unregistered in OnBeforeClose by calling // g_signal_handlers_disconnect_matched. } void BrowserWindowOsrGtk::DragReset() { - REQUIRE_MAIN_THREAD(); + CEF_REQUIRE_UI_THREAD(); if (drag_trigger_event_) { gdk_event_free(drag_trigger_event_); drag_trigger_event_ = NULL; @@ -1705,22 +1766,30 @@ void BrowserWindowOsrGtk::DragReset() { void BrowserWindowOsrGtk::DragBegin(GtkWidget* widget, GdkDragContext* drag_context, BrowserWindowOsrGtk* self) { - REQUIRE_MAIN_THREAD(); + CEF_REQUIRE_UI_THREAD(); // Load drag icon. if (!self->drag_data_->HasImage()) { LOG(ERROR) << "Failed to set drag icon, drag image not available"; return; } + + float device_scale_factor; + { + base::AutoLock lock_scope(self->lock_); + device_scale_factor = self->device_scale_factor_; + } + int pixel_width = 0; int pixel_height = 0; CefRefPtr 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); if (!image_binary) { LOG(ERROR) << "Failed to set drag icon, drag image error"; return; } + size_t image_size = image_binary->GetSize(); guint8* image_buffer = (guint8*)malloc(image_size); // must free image_binary->GetData((void*)image_buffer, image_size, 0); @@ -1768,7 +1837,7 @@ void BrowserWindowOsrGtk::DragDataGet(GtkWidget* widget, guint info, guint time, BrowserWindowOsrGtk* self) { - REQUIRE_MAIN_THREAD(); + CEF_REQUIRE_UI_THREAD(); // 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, GdkDragContext* drag_context, BrowserWindowOsrGtk* self) { - REQUIRE_MAIN_THREAD(); + CEF_REQUIRE_UI_THREAD(); if (self->browser_) { // Sometimes there is DragEnd event generated without prior DragDrop. @@ -1799,7 +1868,13 @@ gboolean BrowserWindowOsrGtk::DragMotion(GtkWidget* widget, gint y, guint time, 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 // SendMouseMoveEvent here. @@ -1808,7 +1883,7 @@ gboolean BrowserWindowOsrGtk::DragMotion(GtkWidget* widget, mouse_event.y = y; mouse_event.modifiers = EVENTFLAG_LEFT_MOUSE_BUTTON; self->ApplyPopupOffset(mouse_event.x, mouse_event.y); - DeviceToLogical(mouse_event, self->device_scale_factor_); + DeviceToLogical(mouse_event, device_scale_factor); if (self->browser_) { bool mouse_leave = self->drag_leave_; self->browser_->GetHost()->SendMouseMoveEvent(mouse_event, mouse_leave); @@ -1853,7 +1928,7 @@ void BrowserWindowOsrGtk::DragLeave(GtkWidget* widget, GdkDragContext* drag_context, guint time, BrowserWindowOsrGtk* self) { - REQUIRE_MAIN_THREAD(); + CEF_REQUIRE_UI_THREAD(); // There is no drag-enter event in GTK. The first drag-motion event // after drag-leave will be a drag-enter event. @@ -1875,7 +1950,7 @@ gboolean BrowserWindowOsrGtk::DragFailed(GtkWidget* widget, GdkDragContext* drag_context, GtkDragResult result, BrowserWindowOsrGtk* self) { - REQUIRE_MAIN_THREAD(); + CEF_REQUIRE_UI_THREAD(); // Send drag end coordinates and system drag ended event. if (self->browser_) { @@ -1895,7 +1970,7 @@ gboolean BrowserWindowOsrGtk::DragDrop(GtkWidget* widget, gint y, guint time, BrowserWindowOsrGtk* self) { - REQUIRE_MAIN_THREAD(); + CEF_REQUIRE_UI_THREAD(); // Finish GTK drag. gtk_drag_finish(drag_context, TRUE, FALSE, time); @@ -1939,7 +2014,7 @@ void BrowserWindowOsrGtk::DragDataReceived(GtkWidget* widget, guint info, guint time, BrowserWindowOsrGtk* self) { - REQUIRE_MAIN_THREAD(); + CEF_REQUIRE_UI_THREAD(); // This callback is never called because DragDrop does not call // gtk_drag_get_data, as only dragging inside web view is supported. } diff --git a/tests/cefclient/browser/browser_window_osr_gtk.h b/tests/cefclient/browser/browser_window_osr_gtk.h index e52feb61d..56d417b2f 100644 --- a/tests/cefclient/browser/browser_window_osr_gtk.h +++ b/tests/cefclient/browser/browser_window_osr_gtk.h @@ -6,6 +6,8 @@ #define CEF_TESTS_CEFCLIENT_BROWSER_BROWSER_WINDOW_OSR_GTK_H_ #pragma once +#include "include/base/cef_lock.h" + #include "tests/cefclient/browser/browser_window.h" #include "tests/cefclient/browser/client_handler_osr.h" #include "tests/cefclient/browser/osr_renderer.h" @@ -24,6 +26,9 @@ class BrowserWindowOsrGtk : public BrowserWindow, const std::string& startup_url, const OsrRenderer::Settings& settings); + // Called from RootWindowGtk::CreateRootWindow before CreateBrowser. + void set_xdisplay(XDisplay* xdisplay); + // BrowserWindow methods. void CreateBrowser(ClientWindowHandle parent_handle, const CefRect& rect, @@ -162,15 +167,18 @@ class BrowserWindowOsrGtk : public BrowserWindow, guint time, BrowserWindowOsrGtk* self); - // The below members will only be accessed on the main thread which should be - // the same as the CEF UI thread. + XDisplay* xdisplay_; + + // Members only accessed on the UI thread. OsrRenderer renderer_; - ClientWindowHandle glarea_; - bool hidden_; bool gl_enabled_; 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 GdkEvent* drag_trigger_event_; // mouse event, a possible trigger for drag @@ -181,6 +189,11 @@ class BrowserWindowOsrGtk : public BrowserWindow, bool drag_leave_; 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); }; diff --git a/tests/cefclient/browser/browser_window_std_gtk.cc b/tests/cefclient/browser/browser_window_std_gtk.cc index 8256c3589..b0e633223 100644 --- a/tests/cefclient/browser/browser_window_std_gtk.cc +++ b/tests/cefclient/browser/browser_window_std_gtk.cc @@ -14,6 +14,7 @@ #include "include/base/cef_logging.h" #include "tests/cefclient/browser/client_handler_std.h" +#include "tests/cefclient/browser/util_gtk.h" #include "tests/shared/browser/main_message_loop.h" namespace client { @@ -21,14 +22,16 @@ namespace client { namespace { ::Window GetXWindowForWidget(GtkWidget* widget) { + ScopedGdkThreadsEnter scoped_gdk_threads; + // The GTK window must be visible before we can retrieve the XID. ::Window xwindow = GDK_WINDOW_XID(gtk_widget_get_window(widget)); DCHECK(xwindow); return xwindow; } -void SetXWindowVisible(::Window xwindow, bool visible) { - ::Display* xdisplay = cef_get_xdisplay(); +void SetXWindowVisible(XDisplay* xdisplay, ::Window xwindow, bool visible) { + CHECK(xdisplay != 0); // Retrieve the atoms required by the below XChangeProperty call. 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 y, size_t width, size_t height) { - ::Display* xdisplay = cef_get_xdisplay(); + CHECK(xdisplay != 0); XWindowChanges changes = {0}; changes.x = x; changes.y = y; @@ -79,10 +83,16 @@ void SetXWindowBounds(::Window xwindow, BrowserWindowStdGtk::BrowserWindowStdGtk(Delegate* delegate, const std::string& startup_url) - : BrowserWindow(delegate) { + : BrowserWindow(delegate), xdisplay_(nullptr) { client_handler_ = new ClientHandlerStd(this, startup_url); } +void BrowserWindowStdGtk::set_xdisplay(XDisplay* xdisplay) { + REQUIRE_MAIN_THREAD(); + DCHECK(!xdisplay_); + xdisplay_ = xdisplay; +} + void BrowserWindowStdGtk::CreateBrowser( ClientWindowHandle parent_handle, const CefRect& rect, @@ -118,14 +128,14 @@ void BrowserWindowStdGtk::ShowPopup(ClientWindowHandle parent_handle, if (browser_) { ::Window parent_xwindow = GetXWindowForWidget(parent_handle); - ::Display* xdisplay = cef_get_xdisplay(); + CHECK(xdisplay_ != 0); ::Window xwindow = browser_->GetHost()->GetWindowHandle(); DCHECK(xwindow); - XReparentWindow(xdisplay, xwindow, parent_xwindow, x, y); + XReparentWindow(xdisplay_, xwindow, parent_xwindow, x, y); - SetXWindowBounds(xwindow, x, y, width, height); - SetXWindowVisible(xwindow, true); + SetXWindowBounds(xdisplay_, xwindow, x, y, width, height); + SetXWindowVisible(xdisplay_, xwindow, true); } } @@ -135,7 +145,7 @@ void BrowserWindowStdGtk::Show() { if (browser_) { ::Window xwindow = browser_->GetHost()->GetWindowHandle(); DCHECK(xwindow); - SetXWindowVisible(xwindow, true); + SetXWindowVisible(xdisplay_, xwindow, true); } } @@ -145,7 +155,7 @@ void BrowserWindowStdGtk::Hide() { if (browser_) { ::Window xwindow = browser_->GetHost()->GetWindowHandle(); 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_) { ::Window xwindow = browser_->GetHost()->GetWindowHandle(); DCHECK(xwindow); - SetXWindowBounds(xwindow, x, y, width, height); + SetXWindowBounds(xdisplay_, xwindow, x, y, width, height); } } diff --git a/tests/cefclient/browser/browser_window_std_gtk.h b/tests/cefclient/browser/browser_window_std_gtk.h index 8b9fa0cf1..47a944ed4 100644 --- a/tests/cefclient/browser/browser_window_std_gtk.h +++ b/tests/cefclient/browser/browser_window_std_gtk.h @@ -19,6 +19,9 @@ class BrowserWindowStdGtk : public BrowserWindow { // |delegate| must outlive this object. BrowserWindowStdGtk(Delegate* delegate, const std::string& startup_url); + // Called from RootWindowGtk::CreateRootWindow before CreateBrowser. + void set_xdisplay(XDisplay* xdisplay); + // BrowserWindow methods. void CreateBrowser(ClientWindowHandle parent_handle, const CefRect& rect, @@ -40,6 +43,8 @@ class BrowserWindowStdGtk : public BrowserWindow { ClientWindowHandle GetWindowHandle() const OVERRIDE; private: + XDisplay* xdisplay_; + DISALLOW_COPY_AND_ASSIGN(BrowserWindowStdGtk); }; diff --git a/tests/cefclient/browser/dialog_handler_gtk.cc b/tests/cefclient/browser/dialog_handler_gtk.cc index 042be2ca9..6ce0af937 100644 --- a/tests/cefclient/browser/dialog_handler_gtk.cc +++ b/tests/cefclient/browser/dialog_handler_gtk.cc @@ -11,6 +11,7 @@ #include "include/cef_parser.h" #include "include/wrapper/cef_helpers.h" #include "tests/cefclient/browser/root_window.h" +#include "tests/cefclient/browser/util_gtk.h" namespace client { @@ -130,6 +131,7 @@ void AddFilters(GtkFileChooser* chooser, } GtkWindow* GetWindow(CefRefPtr browser) { + REQUIRE_MAIN_THREAD(); scoped_refptr root_window = RootWindow::GetForBrowser(browser->GetIdentifier()); if (root_window) { @@ -141,6 +143,10 @@ GtkWindow* GetWindow(CefRefPtr browser) { return NULL; } +void RunCallback(base::Callback callback, GtkWindow* window) { + callback.Run(window); +} + } // namespace ClientDialogHandlerGtk::ClientDialogHandlerGtk() : gtk_dialog_(NULL) {} @@ -153,6 +159,78 @@ bool ClientDialogHandlerGtk::OnFileDialog( const std::vector& accept_filters, int selected_accept_filter, CefRefPtr 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 browser, + const CefString& origin_url, + JSDialogType dialog_type, + const CefString& message_text, + const CefString& default_prompt_text, + CefRefPtr 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 browser, + const CefString& message_text, + bool is_reload, + CefRefPtr 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 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 files; GtkFileChooserAction action; @@ -160,7 +238,7 @@ bool ClientDialogHandlerGtk::OnFileDialog( // Remove any modifier flags. FileDialogMode mode_type = - static_cast(mode & FILE_DIALOG_TYPE_MASK); + static_cast(params.mode & FILE_DIALOG_TYPE_MASK); if (mode_type == FILE_DIALOG_OPEN || mode_type == FILE_DIALOG_OPEN_MULTIPLE) { action = GTK_FILE_CHOOSER_ACTION_OPEN; @@ -173,12 +251,12 @@ bool ClientDialogHandlerGtk::OnFileDialog( accept_button = GTK_STOCK_SAVE; } else { NOTREACHED(); - return false; + params.callback->Cancel(); } std::string title_str; - if (!title.empty()) { - title_str = title; + if (!params.title.empty()) { + title_str = params.title; } else { switch (mode_type) { 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( title_str.c_str(), GTK_WINDOW(window), action, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, accept_button, GTK_RESPONSE_ACCEPT, NULL); @@ -211,14 +285,15 @@ bool ClientDialogHandlerGtk::OnFileDialog( if (mode_type == FILE_DIALOG_SAVE) { 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), - !(mode & FILE_DIALOG_HIDEREADONLY_FLAG)); + gtk_file_chooser_set_show_hidden( + GTK_FILE_CHOOSER(dialog), !(params.mode & FILE_DIALOG_HIDEREADONLY_FLAG)); - if (!default_file_path.empty() && mode_type == FILE_DIALOG_SAVE) { - const std::string& file_path = default_file_path; + if (!params.default_file_path.empty() && mode_type == FILE_DIALOG_SAVE) { + const std::string& file_path = params.default_file_path; bool exists = false; struct stat sb; @@ -237,10 +312,10 @@ bool ClientDialogHandlerGtk::OnFileDialog( } std::vector filters; - AddFilters(GTK_FILE_CHOOSER(dialog), accept_filters, true, &filters); - if (selected_accept_filter < static_cast(filters.size())) { + AddFilters(GTK_FILE_CHOOSER(dialog), params.accept_filters, true, &filters); + if (params.selected_accept_filter < static_cast(filters.size())) { gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), - filters[selected_accept_filter]); + filters[params.selected_accept_filter]); } 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) { GtkFileFilter* selected_filter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog)); @@ -284,27 +359,22 @@ bool ClientDialogHandlerGtk::OnFileDialog( gtk_widget_destroy(dialog); if (success) - callback->Continue(filter_index, files); + params.callback->Continue(filter_index, files); else - callback->Cancel(); - - return true; + params.callback->Cancel(); } -bool ClientDialogHandlerGtk::OnJSDialog(CefRefPtr browser, - const CefString& origin_url, - JSDialogType dialog_type, - const CefString& message_text, - const CefString& default_prompt_text, - CefRefPtr callback, - bool& suppress_message) { +void ClientDialogHandlerGtk::OnJSDialogContinue(OnJSDialogParams params, + GtkWindow* window) { CEF_REQUIRE_UI_THREAD(); + ScopedGdkThreadsEnter scoped_gdk_threads; + GtkButtonsType buttons = GTK_BUTTONS_NONE; GtkMessageType gtk_message_type = GTK_MESSAGE_OTHER; std::string title; - switch (dialog_type) { + switch (params.dialog_type) { case JSDIALOGTYPE_ALERT: buttons = GTK_BUTTONS_NONE; gtk_message_type = GTK_MESSAGE_WARNING; @@ -324,20 +394,16 @@ bool ClientDialogHandlerGtk::OnJSDialog(CefRefPtr browser, break; } - js_dialog_callback_ = callback; + js_dialog_callback_ = params.callback; - if (!origin_url.empty()) { + if (!params.origin_url.empty()) { 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_message_type, buttons, "%s", - message_text.ToString().c_str()); + params.message_text.ToString().c_str()); g_signal_connect(gtk_dialog_, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL); @@ -346,15 +412,15 @@ bool ClientDialogHandlerGtk::OnJSDialog(CefRefPtr browser, GtkWidget* ok_button = gtk_dialog_add_button(GTK_DIALOG(gtk_dialog_), GTK_STOCK_OK, GTK_RESPONSE_OK); - if (dialog_type != JSDIALOGTYPE_PROMPT) + if (params.dialog_type != JSDIALOGTYPE_PROMPT) gtk_widget_grab_focus(ok_button); - if (dialog_type == JSDIALOGTYPE_PROMPT) { + if (params.dialog_type == JSDIALOGTYPE_PROMPT) { GtkWidget* content_area = gtk_dialog_get_content_area(GTK_DIALOG(gtk_dialog_)); GtkWidget* text_box = gtk_entry_new(); 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); g_object_set_data(G_OBJECT(gtk_dialog_), kPromptTextId, text_box); gtk_entry_set_activates_default(GTK_ENTRY(text_box), TRUE); @@ -363,33 +429,21 @@ bool ClientDialogHandlerGtk::OnJSDialog(CefRefPtr browser, gtk_dialog_set_default_response(GTK_DIALOG(gtk_dialog_), GTK_RESPONSE_OK); g_signal_connect(gtk_dialog_, "response", G_CALLBACK(OnDialogResponse), this); gtk_widget_show_all(GTK_WIDGET(gtk_dialog_)); - - return true; } -bool ClientDialogHandlerGtk::OnBeforeUnloadDialog( +void ClientDialogHandlerGtk::GetWindowAndContinue( CefRefPtr browser, - const CefString& message_text, - bool is_reload, - CefRefPtr 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 browser) { - CEF_REQUIRE_UI_THREAD(); - - if (!gtk_dialog_) + base::Callback callback) { + if (!CURRENTLY_ON_MAIN_THREAD()) { + MAIN_POST_CLOSURE(base::Bind(&ClientDialogHandlerGtk::GetWindowAndContinue, + this, browser, callback)); return; - gtk_widget_destroy(gtk_dialog_); - gtk_dialog_ = NULL; - js_dialog_callback_ = NULL; + } + + GtkWindow* window = GetWindow(browser); + if (window) { + CefPostTask(TID_UI, base::Bind(RunCallback, callback, window)); + } } // static diff --git a/tests/cefclient/browser/dialog_handler_gtk.h b/tests/cefclient/browser/dialog_handler_gtk.h index 163d0a35b..c29d9f010 100644 --- a/tests/cefclient/browser/dialog_handler_gtk.h +++ b/tests/cefclient/browser/dialog_handler_gtk.h @@ -8,6 +8,7 @@ #include +#include "include/base/cef_callback_forward.h" #include "include/cef_dialog_handler.h" #include "include/cef_jsdialog_handler.h" @@ -42,6 +43,30 @@ class ClientDialogHandlerGtk : public CefDialogHandler, void OnResetDialogState(CefRefPtr browser) OVERRIDE; private: + struct OnFileDialogParams { + CefRefPtr browser; + FileDialogMode mode; + CefString title; + CefString default_file_path; + std::vector accept_filters; + int selected_accept_filter; + CefRefPtr callback; + }; + void OnFileDialogContinue(OnFileDialogParams params, GtkWindow* window); + + struct OnJSDialogParams { + CefRefPtr browser; + CefString origin_url; + JSDialogType dialog_type; + CefString message_text; + CefString default_prompt_text; + CefRefPtr callback; + }; + void OnJSDialogContinue(OnJSDialogParams params, GtkWindow* window); + + void GetWindowAndContinue(CefRefPtr browser, + base::Callback callback); + static void OnDialogResponse(GtkDialog* dialog, gint response_id, ClientDialogHandlerGtk* handler); diff --git a/tests/cefclient/browser/main_context_impl.cc b/tests/cefclient/browser/main_context_impl.cc index 5ccedae73..8154ba56e 100644 --- a/tests/cefclient/browser/main_context_impl.cc +++ b/tests/cefclient/browser/main_context_impl.cc @@ -135,7 +135,7 @@ bool MainContextImpl::UseWindowlessRendering() { } void MainContextImpl::PopulateSettings(CefSettings* settings) { -#if defined(OS_WIN) +#if defined(OS_WIN) || defined(OS_LINUX) settings->multi_threaded_message_loop = command_line_->HasSwitch(switches::kMultiThreadedMessageLoop); #endif diff --git a/tests/cefclient/browser/main_message_loop_multithreaded_gtk.cc b/tests/cefclient/browser/main_message_loop_multithreaded_gtk.cc new file mode 100644 index 000000000..d4a044354 --- /dev/null +++ b/tests/cefclient/browser/main_message_loop_multithreaded_gtk.cc @@ -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 +#include + +#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 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(self)->RunTasks(); + return G_SOURCE_CONTINUE; +} + +void MainMessageLoopMultithreadedGtk::RunTasks() { + DCHECK(RunsTasksOnCurrentThread()); + + std::queue> tasks; + + { + base::AutoLock lock_scope(lock_); + tasks.swap(queued_tasks_); + } + + // Execute all queued tasks. + while (!tasks.empty()) { + CefRefPtr task = tasks.front(); + tasks.pop(); + task->Execute(); + } +} + +void MainMessageLoopMultithreadedGtk::DoQuit() { + DCHECK(RunsTasksOnCurrentThread()); + g_main_loop_quit(main_loop_); +} + +} // namespace client diff --git a/tests/cefclient/browser/main_message_loop_multithreaded_gtk.h b/tests/cefclient/browser/main_message_loop_multithreaded_gtk.h new file mode 100644 index 000000000..51087fa83 --- /dev/null +++ b/tests/cefclient/browser/main_message_loop_multithreaded_gtk.h @@ -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 + +#include + +#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 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> queued_tasks_; + + DISALLOW_COPY_AND_ASSIGN(MainMessageLoopMultithreadedGtk); +}; + +} // namespace client + +#endif // CEF_TESTS_CEFCLIENT_BROWSER_MAIN_MESSAGE_LOOP_MULTITHREADED_GTK_H_ diff --git a/tests/cefclient/browser/print_handler_gtk.cc b/tests/cefclient/browser/print_handler_gtk.cc index 9a822b7a9..1ae875b95 100644 --- a/tests/cefclient/browser/print_handler_gtk.cc +++ b/tests/cefclient/browser/print_handler_gtk.cc @@ -10,11 +10,13 @@ #include #include +#include "include/base/cef_bind.h" #include "include/base/cef_logging.h" #include "include/base/cef_macros.h" #include "include/wrapper/cef_helpers.h" #include "tests/cefclient/browser/root_window.h" +#include "tests/cefclient/browser/util_gtk.h" namespace client { @@ -270,6 +272,33 @@ void InitPrintSettings(GtkPrintSettings* settings, printable_area_device_units, true); } +// Returns the GtkWindow* for the browser. Will return NULL when using the Views +// framework. +GtkWindow* GetWindow(CefRefPtr browser) { + scoped_refptr root_window = + RootWindow::GetForBrowser(browser->GetIdentifier()); + if (root_window) + return GTK_WINDOW(root_window->GetWindowHandle()); + return NULL; +} + +void RunCallback(base::Callback callback, GtkWindow* window) { + callback.Run(window); +} + +void GetWindowAndContinue(CefRefPtr browser, + base::Callback 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 struct ClientPrintHandlerGtk::PrintHandler { @@ -281,6 +310,8 @@ struct ClientPrintHandlerGtk::PrintHandler { printer_(NULL) {} ~PrintHandler() { + ScopedGdkThreadsEnter scoped_gdk_threads; + if (dialog_) { gtk_widget_destroy(dialog_); dialog_ = NULL; @@ -301,6 +332,8 @@ struct ClientPrintHandlerGtk::PrintHandler { void OnPrintSettings(CefRefPtr settings, bool get_defaults) { + ScopedGdkThreadsEnter scoped_gdk_threads; + if (get_defaults) { DCHECK(!page_setup_); DCHECK(!printer_); @@ -369,11 +402,13 @@ struct ClientPrintHandlerGtk::PrintHandler { InitPrintSettings(gtk_settings_, page_setup_, settings); } - bool OnPrintDialog(bool has_selection, - CefRefPtr callback) { + void OnPrintDialog(bool has_selection, + CefRefPtr callback, + GtkWindow* parent) { dialog_callback_ = callback; - GtkWindow* parent = GetWindow(); + ScopedGdkThreadsEnter scoped_gdk_threads; + // TODO(estade): We need a window title here. dialog_ = gtk_print_unix_dialog_new(NULL, parent); g_signal_connect(dialog_, "delete-event", @@ -401,8 +436,6 @@ struct ClientPrintHandlerGtk::PrintHandler { g_signal_connect(dialog_, "response", G_CALLBACK(OnDialogResponseThunk), this); gtk_widget_show(dialog_); - - return true; } bool OnPrintJob(const CefString& document_name, @@ -413,6 +446,8 @@ struct ClientPrintHandlerGtk::PrintHandler { if (!printer_) return false; + ScopedGdkThreadsEnter scoped_gdk_threads; + job_callback_ = callback; // Save the settings for next time. @@ -428,16 +463,6 @@ struct ClientPrintHandlerGtk::PrintHandler { } private: - // Returns the GtkWindow* for the browser. Will return NULL when using the - // Views framework. - GtkWindow* GetWindow() { - scoped_refptr root_window = - RootWindow::GetForBrowser(browser_->GetIdentifier()); - if (root_window) - return GTK_WINDOW(root_window->GetWindowHandle()); - return NULL; - } - void OnDialogResponse(GtkDialog* dialog, gint response_id) { int num_matched_handlers = g_signal_handlers_disconnect_by_func( dialog_, reinterpret_cast(&OnDialogResponseThunk), this); @@ -581,7 +606,11 @@ bool ClientPrintHandlerGtk::OnPrintDialog( CefRefPtr callback) { 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( @@ -609,6 +638,8 @@ void ClientPrintHandlerGtk::OnPrintReset(CefRefPtr browser) { CefSize ClientPrintHandlerGtk::GetPdfPaperSize(int device_units_per_inch) { CEF_REQUIRE_UI_THREAD(); + ScopedGdkThreadsEnter scoped_gdk_threads; + GtkPageSetup* page_setup = gtk_page_setup_new(); float width = gtk_page_setup_get_paper_width(page_setup, GTK_UNIT_INCH); diff --git a/tests/cefclient/browser/root_window_gtk.cc b/tests/cefclient/browser/root_window_gtk.cc index 480dd096b..1a3a1939d 100644 --- a/tests/cefclient/browser/root_window_gtk.cc +++ b/tests/cefclient/browser/root_window_gtk.cc @@ -18,6 +18,7 @@ #include "tests/cefclient/browser/main_context.h" #include "tests/cefclient/browser/resource.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/shared/browser/main_message_loop.h" #include "tests/shared/common/client_switches.h" @@ -62,9 +63,10 @@ RootWindowGtk::RootWindowGtk() url_entry_(NULL), toolbar_height_(0), menubar_height_(0), - force_close_(false), window_destroyed_(false), - browser_destroyed_(false) {} + browser_destroyed_(false), + force_close_(false), + is_closing_(false) {} RootWindowGtk::~RootWindowGtk() { REQUIRE_MAIN_THREAD(); @@ -140,6 +142,8 @@ void RootWindowGtk::Show(ShowMode mode) { if (!window_) return; + ScopedGdkThreadsEnter scoped_gdk_threads; + // Show the GTK window. gtk_widget_show_all(window_); @@ -158,6 +162,8 @@ void RootWindowGtk::Show(ShowMode mode) { void RootWindowGtk::Hide() { REQUIRE_MAIN_THREAD(); + ScopedGdkThreadsEnter scoped_gdk_threads; + if (window_) gtk_widget_hide(window_); } @@ -168,6 +174,8 @@ void RootWindowGtk::SetBounds(int x, int y, size_t width, size_t height) { if (!window_) return; + ScopedGdkThreadsEnter scoped_gdk_threads; + GtkWindow* window = GTK_WINDOW(window_); GdkWindow* gdk_window = gtk_widget_get_window(window_); @@ -184,7 +192,11 @@ void RootWindowGtk::Close(bool force) { REQUIRE_MAIN_THREAD(); if (window_) { - force_close_ = force; + ScopedGdkThreadsEnter scoped_gdk_threads; + + if (force) { + NotifyForceClose(); + } gtk_widget_destroy(window_); } } @@ -258,7 +270,11 @@ void RootWindowGtk::CreateRootWindow(const CefBrowserSettings& settings, height = start_rect_.height; } + ScopedGdkThreadsEnter scoped_gdk_threads; + window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL); + CHECK(window_); + gtk_window_set_default_size(GTK_WINDOW(window_), width, height); g_signal_connect(G_OBJECT(window_), "focus-in-event", 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. 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(browser_window_.get()) + ->set_xdisplay(xdisplay); + } else { + static_cast(browser_window_.get()) + ->set_xdisplay(xdisplay); + } + if (!is_popup_) { // Create the browser window. browser_window_->CreateBrowser(parent, browser_bounds_, settings, @@ -368,6 +394,16 @@ void RootWindowGtk::OnBrowserCreated(CefRefPtr 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() { REQUIRE_MAIN_THREAD(); @@ -380,14 +416,15 @@ void RootWindowGtk::OnBrowserWindowDestroyed() { Close(true); } - browser_destroyed_ = true; - NotifyDestroyedIfDone(); + NotifyDestroyedIfDone(false, true); } void RootWindowGtk::OnSetAddress(const std::string& url) { REQUIRE_MAIN_THREAD(); if (url_entry_) { + ScopedGdkThreadsEnter scoped_gdk_threads; + std::string urlStr(url); 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(); if (window_) { + ScopedGdkThreadsEnter scoped_gdk_threads; + std::string titleStr(title); gtk_window_set_title(GTK_WINDOW(window_), titleStr.c_str()); } @@ -422,6 +461,8 @@ void RootWindowGtk::OnAutoResize(const CefSize& new_size) { if (!window_) return; + ScopedGdkThreadsEnter scoped_gdk_threads; + GtkWindow* window = GTK_WINDOW(window_); GdkWindow* gdk_window = gtk_widget_get_window(window_); @@ -440,6 +481,8 @@ void RootWindowGtk::OnSetLoadingState(bool isLoading, REQUIRE_MAIN_THREAD(); if (with_controls_) { + ScopedGdkThreadsEnter scoped_gdk_threads; + gtk_widget_set_sensitive(GTK_WIDGET(stop_button_), isLoading); gtk_widget_set_sensitive(GTK_WIDGET(reload_button_), !isLoading); 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. } -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 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 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 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 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. if (window_destroyed_ && browser_destroyed_) delegate_->OnRootWindowDestroyed(this); @@ -463,9 +675,11 @@ void RootWindowGtk::NotifyDestroyedIfDone() { gboolean RootWindowGtk::WindowFocusIn(GtkWidget* widget, GdkEventFocus* event, RootWindowGtk* self) { - if (event->in && self->browser_window_.get()) { - self->browser_window_->SetFocus(true); - self->delegate_->OnRootWindowActivated(self); + CEF_REQUIRE_UI_THREAD(); + + if (event->in) { + self->NotifySetFocus(); + // Return true for a windowed browser so that focus is not passed to GTK. return self->with_osr_ ? FALSE : TRUE; } @@ -477,14 +691,13 @@ gboolean RootWindowGtk::WindowFocusIn(GtkWidget* widget, gboolean RootWindowGtk::WindowState(GtkWidget* widget, GdkEventWindowState* event, RootWindowGtk* self) { + CEF_REQUIRE_UI_THREAD(); + // Called when the root window is iconified or restored. Hide the browser // window when the root window is iconified to reduce resource usage. - if ((event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) && - self->browser_window_.get()) { - if (event->new_window_state & GDK_WINDOW_STATE_ICONIFIED) - self->browser_window_->Hide(); - else - self->browser_window_->Show(); + if (event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) { + self->NotifyVisibilityChange( + !(event->new_window_state & GDK_WINDOW_STATE_ICONIFIED)); } return TRUE; @@ -494,45 +707,35 @@ gboolean RootWindowGtk::WindowState(GtkWidget* widget, gboolean RootWindowGtk::WindowConfigure(GtkWindow* window, GdkEvent* event, RootWindowGtk* self) { - // Called when size, position or stack order changes. - CefRefPtr browser = self->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(); - } - + CEF_REQUIRE_UI_THREAD(); + self->NotifyMoveOrResizeStarted(); return FALSE; // Don't stop this message. } // static void RootWindowGtk::WindowDestroy(GtkWidget* widget, RootWindowGtk* self) { - // Called when the root window is destroyed. - self->window_destroyed_ = true; - self->NotifyDestroyedIfDone(); + // May be called on the main thread or the UI thread. + self->NotifyDestroyedIfDone(true, false); } // static gboolean RootWindowGtk::WindowDelete(GtkWidget* widget, GdkEvent* event, RootWindowGtk* self) { + CEF_REQUIRE_UI_THREAD(); + // Called to query whether the root window should be closed. if (self->force_close_) return FALSE; // Allow the close. - if (self->browser_window_.get() && !self->browser_window_->IsClosing()) { - CefRefPtr browser = self->GetBrowser(); - if (browser) { - // Notify the browser window that we would like to close it. This - // will result in a call to ClientHandler::DoClose() if the - // JavaScript 'onbeforeunload' event handler allows it. - browser->GetHost()->CloseBrowser(false); + if (!self->is_closing_) { + // Notify the browser window that we would like to close it. This + // will result in a call to ClientHandler::DoClose() if the + // JavaScript 'onbeforeunload' event handler allows it. + self->NotifyCloseBrowser(); - // Cancel the close. - return TRUE; - } + // Cancel the close. + return TRUE; } // Allow the close. @@ -543,35 +746,27 @@ gboolean RootWindowGtk::WindowDelete(GtkWidget* widget, void RootWindowGtk::VboxSizeAllocated(GtkWidget* widget, GtkAllocation* allocation, RootWindowGtk* self) { - // Offset browser positioning by any controls that will appear in the client - // area. - const int ux_height = self->toolbar_height_ + self->menubar_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); + // May be called on the main thread and the UI thread. + self->NotifyContentBounds(allocation->x, allocation->y, allocation->width, + allocation->height); } // static void RootWindowGtk::MenubarSizeAllocated(GtkWidget* widget, GtkAllocation* allocation, RootWindowGtk* self) { - self->menubar_height_ = allocation->height; + // May be called on the main thread and the UI thread. + self->NotifyMenuBarHeight(allocation->height); } // static gboolean RootWindowGtk::MenuItemActivated(GtkWidget* widget, RootWindowGtk* self) { + CEF_REQUIRE_UI_THREAD(); + // Retrieve the menu ID set in AddMenuEntry. int id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), kMenuIdKey)); - // Run the test. - if (self->delegate_) - self->delegate_->OnTest(self, id); + self->NotifyMenuItem(id); return FALSE; // Don't stop this message. } @@ -585,47 +780,43 @@ void RootWindowGtk::ToolbarSizeAllocated(GtkWidget* widget, // static void RootWindowGtk::BackButtonClicked(GtkButton* button, RootWindowGtk* self) { - CefRefPtr browser = self->GetBrowser(); - if (browser.get()) - browser->GoBack(); + CEF_REQUIRE_UI_THREAD(); + self->NotifyButtonClicked(IDC_NAV_BACK); } // static void RootWindowGtk::ForwardButtonClicked(GtkButton* button, RootWindowGtk* self) { - CefRefPtr browser = self->GetBrowser(); - if (browser.get()) - browser->GoForward(); + CEF_REQUIRE_UI_THREAD(); + self->NotifyButtonClicked(IDC_NAV_FORWARD); } // static void RootWindowGtk::StopButtonClicked(GtkButton* button, RootWindowGtk* self) { - CefRefPtr browser = self->GetBrowser(); - if (browser.get()) - browser->StopLoad(); + CEF_REQUIRE_UI_THREAD(); + self->NotifyButtonClicked(IDC_NAV_STOP); } // static void RootWindowGtk::ReloadButtonClicked(GtkButton* button, RootWindowGtk* self) { - CefRefPtr browser = self->GetBrowser(); - if (browser.get()) - browser->Reload(); + CEF_REQUIRE_UI_THREAD(); + self->NotifyButtonClicked(IDC_NAV_RELOAD); } // static void RootWindowGtk::URLEntryActivate(GtkEntry* entry, RootWindowGtk* self) { - CefRefPtr browser = self->GetBrowser(); - if (browser.get()) { - const gchar* url = gtk_entry_get_text(entry); - browser->GetMainFrame()->LoadURL(std::string(url).c_str()); - } + CEF_REQUIRE_UI_THREAD(); + const gchar* url = gtk_entry_get_text(entry); + self->NotifyLoadURL(std::string(url)); } // static gboolean RootWindowGtk::URLEntryButtonPress(GtkWidget* widget, GdkEventButton* event, RootWindowGtk* self) { + CEF_REQUIRE_UI_THREAD(); + // 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 // by X11. diff --git a/tests/cefclient/browser/root_window_gtk.h b/tests/cefclient/browser/root_window_gtk.h index f94723ed5..6b2f5a17f 100644 --- a/tests/cefclient/browser/root_window_gtk.h +++ b/tests/cefclient/browser/root_window_gtk.h @@ -53,6 +53,7 @@ class RootWindowGtk : public RootWindow, public BrowserWindow::Delegate { // BrowserWindow::Delegate methods. void OnBrowserCreated(CefRefPtr browser) OVERRIDE; + void OnBrowserWindowClosing() OVERRIDE; void OnBrowserWindowDestroyed() OVERRIDE; void OnSetAddress(const std::string& url) OVERRIDE; void OnSetTitle(const std::string& title) OVERRIDE; @@ -64,7 +65,17 @@ class RootWindowGtk : public RootWindow, public BrowserWindow::Delegate { void OnSetDraggableRegions( const std::vector& 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* CreateMenu(GtkWidget* menu_bar, const char* text); @@ -139,10 +150,14 @@ class RootWindowGtk : public RootWindow, public BrowserWindow::Delegate { CefRect browser_bounds_; - bool force_close_; bool window_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); }; diff --git a/tests/cefclient/browser/root_window_manager.cc b/tests/cefclient/browser/root_window_manager.cc index 4258333d5..8444fe5c9 100644 --- a/tests/cefclient/browser/root_window_manager.cc +++ b/tests/cefclient/browser/root_window_manager.cc @@ -159,6 +159,11 @@ scoped_refptr RootWindowManager::CreateRootWindowAsPopup( CefBrowserSettings& settings) { CEF_REQUIRE_UI_THREAD(); + if (!temp_window_) { + // TempWindow must be created on the UI thread. + temp_window_.reset(new TempWindow()); + } + MainContext::Get()->PopulateBrowserSettings(&settings); scoped_refptr root_window = @@ -390,8 +395,9 @@ void RootWindowManager::OnRootWindowDestroyed(RootWindow* root_window) { } if (terminate_when_all_windows_closed_ && root_windows_.empty()) { - // Quit the main message loop after all windows have closed. - MainMessageLoop::Get()->Quit(); + // All windows have closed. Clean up on the UI thread. + 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 diff --git a/tests/cefclient/browser/root_window_manager.h b/tests/cefclient/browser/root_window_manager.h index 23e60757d..f98f41ece 100644 --- a/tests/cefclient/browser/root_window_manager.h +++ b/tests/cefclient/browser/root_window_manager.h @@ -106,6 +106,8 @@ class RootWindowManager : public RootWindow::Delegate { const base::Closure& close_callback, bool with_osr) OVERRIDE; + void CleanupOnUIThread(); + const bool terminate_when_all_windows_closed_; bool request_context_per_browser_; bool request_context_shared_cache_; @@ -124,7 +126,7 @@ class RootWindowManager : public RootWindow::Delegate { CefRefPtr active_browser_; // Singleton window used as the temporary parent for popup browsers. - TempWindow temp_window_; + scoped_ptr temp_window_; CefRefPtr shared_request_context_; diff --git a/tests/cefclient/browser/temp_window_mac.h b/tests/cefclient/browser/temp_window_mac.h index a4b968a46..0bec1e0cf 100644 --- a/tests/cefclient/browser/temp_window_mac.h +++ b/tests/cefclient/browser/temp_window_mac.h @@ -11,7 +11,7 @@ namespace client { // Represents a singleton hidden window that acts as a temporary parent for -// popup browsers. +// popup browsers. Only accessed on the UI thread. class TempWindowMac { public: // Returns the singleton window handle. @@ -20,6 +20,8 @@ class TempWindowMac { private: // A single instance will be created/owned by RootWindowManager. friend class RootWindowManager; + // Allow deletion via scoped_ptr only. + friend struct base::DefaultDeleter; TempWindowMac(); ~TempWindowMac(); diff --git a/tests/cefclient/browser/temp_window_win.h b/tests/cefclient/browser/temp_window_win.h index 6498a4552..b5f312ebf 100644 --- a/tests/cefclient/browser/temp_window_win.h +++ b/tests/cefclient/browser/temp_window_win.h @@ -11,7 +11,7 @@ namespace client { // Represents a singleton hidden window that acts as a temporary parent for -// popup browsers. +// popup browsers. Only accessed on the UI thread. class TempWindowWin { public: // Returns the singleton window handle. @@ -20,6 +20,8 @@ class TempWindowWin { private: // A single instance will be created/owned by RootWindowManager. friend class RootWindowManager; + // Allow deletion via scoped_ptr only. + friend struct base::DefaultDeleter; TempWindowWin(); ~TempWindowWin(); diff --git a/tests/cefclient/browser/temp_window_x11.cc b/tests/cefclient/browser/temp_window_x11.cc index ca9dad789..051cdfdbb 100644 --- a/tests/cefclient/browser/temp_window_x11.cc +++ b/tests/cefclient/browser/temp_window_x11.cc @@ -16,6 +16,7 @@ namespace { // Create the temp window. ::Window CreateTempWindow() { ::Display* xdisplay = cef_get_xdisplay(); + CHECK(xdisplay != 0); ::Window parent_xwindow = DefaultRootWindow(xdisplay); XSetWindowAttributes swa; @@ -33,6 +34,7 @@ namespace { // Close the temp window. void CloseTempWindow(::Window xwindow) { ::Display* xdisplay = cef_get_xdisplay(); + CHECK(xdisplay != 0); XDestroyWindow(xdisplay, xwindow); } diff --git a/tests/cefclient/browser/temp_window_x11.h b/tests/cefclient/browser/temp_window_x11.h index 4d986981c..08e452e67 100644 --- a/tests/cefclient/browser/temp_window_x11.h +++ b/tests/cefclient/browser/temp_window_x11.h @@ -11,7 +11,7 @@ namespace client { // Represents a singleton hidden window that acts as a temporary parent for -// popup browsers. +// popup browsers. Only accessed on the UI thread. class TempWindowX11 { public: // Returns the singleton window handle. @@ -20,6 +20,8 @@ class TempWindowX11 { private: // A single instance will be created/owned by RootWindowManager. friend class RootWindowManager; + // Allow deletion via scoped_ptr only. + friend struct base::DefaultDeleter; TempWindowX11(); ~TempWindowX11(); diff --git a/tests/cefclient/browser/util_gtk.cc b/tests/cefclient/browser/util_gtk.cc new file mode 100644 index 000000000..be5e30b67 --- /dev/null +++ b/tests/cefclient/browser/util_gtk.cc @@ -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 + +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 diff --git a/tests/cefclient/browser/util_gtk.h b/tests/cefclient/browser/util_gtk.h new file mode 100644 index 000000000..c8836fefb --- /dev/null +++ b/tests/cefclient/browser/util_gtk.h @@ -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_ diff --git a/tests/cefclient/browser/window_test_runner_gtk.cc b/tests/cefclient/browser/window_test_runner_gtk.cc index 6dfe6a514..56f078b72 100644 --- a/tests/cefclient/browser/window_test_runner_gtk.cc +++ b/tests/cefclient/browser/window_test_runner_gtk.cc @@ -8,6 +8,7 @@ #include "include/wrapper/cef_helpers.h" #include "tests/cefclient/browser/root_window.h" +#include "tests/cefclient/browser/util_gtk.h" #include "tests/shared/browser/main_message_loop.h" namespace client { @@ -33,17 +34,13 @@ bool IsMaximized(GtkWindow* window) { return (state & GDK_WINDOW_STATE_MAXIMIZED) ? true : false; } -} // namespace - -WindowTestRunnerGtk::WindowTestRunnerGtk() {} - -void WindowTestRunnerGtk::SetPos(CefRefPtr browser, - int x, - int y, - int width, - int height) { - CEF_REQUIRE_UI_THREAD(); +void SetPosImpl(CefRefPtr browser, + int x, + int y, + int width, + int height) { REQUIRE_MAIN_THREAD(); + ScopedGdkThreadsEnter scoped_gdk_threads; GtkWindow* window = GetWindow(browser); if (!window) @@ -65,15 +62,15 @@ void WindowTestRunnerGtk::SetPos(CefRefPtr browser, // Make sure the window is inside the display. CefRect display_rect(rect.x, rect.y, rect.width, rect.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, window_rect.width, window_rect.height); } -void WindowTestRunnerGtk::Minimize(CefRefPtr browser) { - CEF_REQUIRE_UI_THREAD(); +void MinimizeImpl(CefRefPtr browser) { REQUIRE_MAIN_THREAD(); + ScopedGdkThreadsEnter scoped_gdk_threads; GtkWindow* window = GetWindow(browser); if (!window) @@ -86,9 +83,9 @@ void WindowTestRunnerGtk::Minimize(CefRefPtr browser) { gtk_window_iconify(window); } -void WindowTestRunnerGtk::Maximize(CefRefPtr browser) { - CEF_REQUIRE_UI_THREAD(); +void MaximizeImpl(CefRefPtr browser) { REQUIRE_MAIN_THREAD(); + ScopedGdkThreadsEnter scoped_gdk_threads; GtkWindow* window = GetWindow(browser); if (!window) @@ -96,9 +93,9 @@ void WindowTestRunnerGtk::Maximize(CefRefPtr browser) { gtk_window_maximize(window); } -void WindowTestRunnerGtk::Restore(CefRefPtr browser) { - CEF_REQUIRE_UI_THREAD(); +void RestoreImpl(CefRefPtr browser) { REQUIRE_MAIN_THREAD(); + ScopedGdkThreadsEnter scoped_gdk_threads; GtkWindow* window = GetWindow(browser); if (!window) @@ -109,5 +106,29 @@ void WindowTestRunnerGtk::Restore(CefRefPtr browser) { gtk_window_present(window); } +} // namespace + +WindowTestRunnerGtk::WindowTestRunnerGtk() {} + +void WindowTestRunnerGtk::SetPos(CefRefPtr browser, + int x, + int y, + int width, + int height) { + MAIN_POST_CLOSURE(base::Bind(SetPosImpl, browser, x, y, width, height)); +} + +void WindowTestRunnerGtk::Minimize(CefRefPtr browser) { + MAIN_POST_CLOSURE(base::Bind(MinimizeImpl, browser)); +} + +void WindowTestRunnerGtk::Maximize(CefRefPtr browser) { + MAIN_POST_CLOSURE(base::Bind(MaximizeImpl, browser)); +} + +void WindowTestRunnerGtk::Restore(CefRefPtr browser) { + MAIN_POST_CLOSURE(base::Bind(RestoreImpl, browser)); +} + } // namespace window_test } // namespace client diff --git a/tests/cefclient/cefclient_gtk.cc b/tests/cefclient/cefclient_gtk.cc index 57938ae05..3b8bf3e62 100644 --- a/tests/cefclient/cefclient_gtk.cc +++ b/tests/cefclient/cefclient_gtk.cc @@ -19,6 +19,7 @@ #include "include/cef_command_line.h" #include "include/wrapper/cef_helpers.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/shared/browser/client_app_browser.h" #include "tests/shared/browser/main_message_loop_external_pump.h" @@ -92,7 +93,9 @@ int RunMain(int argc, char* argv[]) { // Create the main message loop object. scoped_ptr 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(); else message_loop.reset(new MainMessageLoopStd); diff --git a/tests/ceftests/test_suite.cc b/tests/ceftests/test_suite.cc index 95b6400ac..99143014e 100644 --- a/tests/ceftests/test_suite.cc +++ b/tests/ceftests/test_suite.cc @@ -119,7 +119,7 @@ int CefTestSuite::Run() { } void CefTestSuite::GetSettings(CefSettings& settings) const { -#if defined(OS_WIN) +#if (defined(OS_WIN) || defined(OS_LINUX)) settings.multi_threaded_message_loop = command_line_->HasSwitch(client::switches::kMultiThreadedMessageLoop); #endif