| // 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 |