1 // Copyright (c) 2018 The Chromium Embedded Framework Authors. All rights
2 // reserved. Use of this source code is governed by a BSD-style license that
3 // can be found in the LICENSE file.
4
5 #include "tests/cefclient/browser/main_message_loop_multithreaded_gtk.h"
6
7 #include <X11/Xlib.h>
8 #include <gtk/gtk.h>
9
10 #include "include/base/cef_callback.h"
11 #include "include/base/cef_logging.h"
12 #include "include/wrapper/cef_closure_task.h"
13
14 namespace client {
15
16 namespace {
17
18 base::Lock g_global_lock;
19 base::PlatformThreadId g_global_lock_thread = kInvalidPlatformThreadId;
20
lock_enter()21 void lock_enter() {
22 // The GDK lock is not reentrant, so check that we're using it correctly.
23 // See comments on ScopedGdkThreadsEnter.
24 base::PlatformThreadId current_thread = base::PlatformThread::CurrentId();
25 CHECK(current_thread != g_global_lock_thread);
26
27 g_global_lock.Acquire();
28 g_global_lock_thread = current_thread;
29 }
30
lock_leave()31 void lock_leave() {
32 g_global_lock_thread = kInvalidPlatformThreadId;
33 g_global_lock.Release();
34 }
35
36 // Same as g_idle_add() but specifying the GMainContext.
idle_add(GMainContext * main_context,GSourceFunc function,gpointer data)37 guint idle_add(GMainContext* main_context,
38 GSourceFunc function,
39 gpointer data) {
40 GSource* source = g_idle_source_new();
41 g_source_set_callback(source, function, data, nullptr);
42 guint id = g_source_attach(source, main_context);
43 g_source_unref(source);
44 return id;
45 }
46
47 // Same as g_timeout_add() but specifying the GMainContext.
timeout_add(GMainContext * main_context,guint interval,GSourceFunc function,gpointer data)48 guint timeout_add(GMainContext* main_context,
49 guint interval,
50 GSourceFunc function,
51 gpointer data) {
52 GSource* source = g_timeout_source_new(interval);
53 g_source_set_callback(source, function, data, nullptr);
54 guint id = g_source_attach(source, main_context);
55 g_source_unref(source);
56 return id;
57 }
58
59 } // namespace
60
MainMessageLoopMultithreadedGtk()61 MainMessageLoopMultithreadedGtk::MainMessageLoopMultithreadedGtk()
62 : thread_id_(base::PlatformThread::CurrentId()) {
63 // Initialize Xlib support for concurrent threads. This function must be the
64 // first Xlib function a multi-threaded program calls, and it must complete
65 // before any other Xlib call is made.
66 CHECK(XInitThreads() != 0);
67
68 // Initialize GDK thread support. See comments on ScopedGdkThreadsEnter.
69 gdk_threads_set_lock_functions(lock_enter, lock_leave);
70 gdk_threads_init();
71 }
72
~MainMessageLoopMultithreadedGtk()73 MainMessageLoopMultithreadedGtk::~MainMessageLoopMultithreadedGtk() {
74 DCHECK(RunsTasksOnCurrentThread());
75 DCHECK(queued_tasks_.empty());
76 }
77
Run()78 int MainMessageLoopMultithreadedGtk::Run() {
79 DCHECK(RunsTasksOnCurrentThread());
80
81 // We use the default Glib context and Chromium creates its own context in
82 // MessagePumpGlib (starting in M86).
83 main_context_ = g_main_context_default();
84
85 main_loop_ = g_main_loop_new(main_context_, TRUE);
86
87 // Check the queue when GTK is idle, or at least every 100ms.
88 // TODO(cef): It might be more efficient to use input functions
89 // (gdk_input_add) and trigger by writing to an fd.
90 idle_add(main_context_, MainMessageLoopMultithreadedGtk::TriggerRunTasks,
91 this);
92 timeout_add(main_context_, 100,
93 MainMessageLoopMultithreadedGtk::TriggerRunTasks, this);
94
95 // Block until g_main_loop_quit().
96 g_main_loop_run(main_loop_);
97
98 // Release GLib resources.
99 g_main_loop_unref(main_loop_);
100 main_loop_ = nullptr;
101
102 main_context_ = nullptr;
103
104 return 0;
105 }
106
Quit()107 void MainMessageLoopMultithreadedGtk::Quit() {
108 PostTask(CefCreateClosureTask(base::BindOnce(
109 &MainMessageLoopMultithreadedGtk::DoQuit, base::Unretained(this))));
110 }
111
PostTask(CefRefPtr<CefTask> task)112 void MainMessageLoopMultithreadedGtk::PostTask(CefRefPtr<CefTask> task) {
113 base::AutoLock lock_scope(lock_);
114
115 // Queue the task.
116 queued_tasks_.push(task);
117 }
118
RunsTasksOnCurrentThread() const119 bool MainMessageLoopMultithreadedGtk::RunsTasksOnCurrentThread() const {
120 return (thread_id_ == base::PlatformThread::CurrentId());
121 }
122
123 // static
TriggerRunTasks(void * self)124 int MainMessageLoopMultithreadedGtk::TriggerRunTasks(void* self) {
125 static_cast<MainMessageLoopMultithreadedGtk*>(self)->RunTasks();
126 return G_SOURCE_CONTINUE;
127 }
128
RunTasks()129 void MainMessageLoopMultithreadedGtk::RunTasks() {
130 DCHECK(RunsTasksOnCurrentThread());
131
132 std::queue<CefRefPtr<CefTask>> tasks;
133
134 {
135 base::AutoLock lock_scope(lock_);
136 tasks.swap(queued_tasks_);
137 }
138
139 // Execute all queued tasks.
140 while (!tasks.empty()) {
141 CefRefPtr<CefTask> task = tasks.front();
142 tasks.pop();
143 task->Execute();
144 }
145 }
146
DoQuit()147 void MainMessageLoopMultithreadedGtk::DoQuit() {
148 DCHECK(RunsTasksOnCurrentThread());
149 g_main_loop_quit(main_loop_);
150 }
151
152 } // namespace client
153