Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /* vim:expandtab:shiftwidth=4:tabstop=4:
3 : */
4 : /* This Source Code Form is subject to the terms of the Mozilla Public
5 : * License, v. 2.0. If a copy of the MPL was not distributed with this
6 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 :
8 : #include <sys/types.h>
9 : #include <unistd.h>
10 : #include <fcntl.h>
11 : #include <errno.h>
12 : #include <gdk/gdk.h>
13 : #include "nsAppShell.h"
14 : #include "nsWindow.h"
15 : #include "mozilla/Logging.h"
16 : #include "prenv.h"
17 : #include "mozilla/HangMonitor.h"
18 : #include "mozilla/Unused.h"
19 : #include "mozilla/WidgetUtils.h"
20 : #include "GeckoProfiler.h"
21 : #include "nsIPowerManagerService.h"
22 : #ifdef MOZ_ENABLE_DBUS
23 : #include "WakeLockListener.h"
24 : #endif
25 : #include "gfxPlatform.h"
26 : #include "ScreenHelperGTK.h"
27 : #include "HeadlessScreenHelper.h"
28 : #include "mozilla/widget/ScreenManager.h"
29 :
30 : using mozilla::Unused;
31 : using mozilla::widget::ScreenHelperGTK;
32 : using mozilla::widget::HeadlessScreenHelper;
33 : using mozilla::widget::ScreenManager;
34 : using mozilla::LazyLogModule;
35 :
36 : #define NOTIFY_TOKEN 0xFA
37 :
38 : LazyLogModule gWidgetLog("Widget");
39 : LazyLogModule gWidgetFocusLog("WidgetFocus");
40 : LazyLogModule gWidgetDragLog("WidgetDrag");
41 : LazyLogModule gWidgetDrawLog("WidgetDraw");
42 :
43 : static GPollFunc sPollFunc;
44 :
45 : // Wrapper function to disable hang monitoring while waiting in poll().
46 : static gint
47 0 : PollWrapper(GPollFD *ufds, guint nfsd, gint timeout_)
48 : {
49 0 : mozilla::HangMonitor::Suspend();
50 : gint result;
51 : {
52 0 : AUTO_PROFILER_LABEL("PollWrapper", IDLE);
53 0 : AUTO_PROFILER_THREAD_SLEEP;
54 1433 : result = (*sPollFunc)(ufds, nfsd, timeout_);
55 : }
56 0 : mozilla::HangMonitor::NotifyActivity();
57 1433 : return result;
58 : }
59 :
60 : #ifdef MOZ_WIDGET_GTK
61 : // For bug 726483.
62 : static decltype(GtkContainerClass::check_resize) sReal_gtk_window_check_resize;
63 :
64 : static void
65 0 : wrap_gtk_window_check_resize(GtkContainer *container)
66 : {
67 0 : GdkWindow* gdk_window = gtk_widget_get_window(&container->widget);
68 0 : if (gdk_window) {
69 0 : g_object_ref(gdk_window);
70 : }
71 :
72 0 : sReal_gtk_window_check_resize(container);
73 :
74 0 : if (gdk_window) {
75 0 : g_object_unref(gdk_window);
76 : }
77 0 : }
78 :
79 : // Emit resume-events on GdkFrameClock if flush-events has not been
80 : // balanced by resume-events at dispose.
81 : // For https://bugzilla.gnome.org/show_bug.cgi?id=742636
82 : static decltype(GObjectClass::constructed) sRealGdkFrameClockConstructed;
83 : static decltype(GObjectClass::dispose) sRealGdkFrameClockDispose;
84 : static GQuark sPendingResumeQuark;
85 :
86 : static void
87 0 : OnFlushEvents(GObject* clock, gpointer)
88 : {
89 0 : g_object_set_qdata(clock, sPendingResumeQuark, GUINT_TO_POINTER(1));
90 0 : }
91 :
92 : static void
93 0 : OnResumeEvents(GObject* clock, gpointer)
94 : {
95 0 : g_object_set_qdata(clock, sPendingResumeQuark, nullptr);
96 0 : }
97 :
98 : static void
99 0 : WrapGdkFrameClockConstructed(GObject* object)
100 : {
101 0 : sRealGdkFrameClockConstructed(object);
102 :
103 : g_signal_connect(object, "flush-events",
104 0 : G_CALLBACK(OnFlushEvents), nullptr);
105 : g_signal_connect(object, "resume-events",
106 0 : G_CALLBACK(OnResumeEvents), nullptr);
107 0 : }
108 :
109 : static void
110 0 : WrapGdkFrameClockDispose(GObject* object)
111 : {
112 0 : if (g_object_get_qdata(object, sPendingResumeQuark)) {
113 0 : g_signal_emit_by_name(object, "resume-events");
114 : }
115 :
116 0 : sRealGdkFrameClockDispose(object);
117 0 : }
118 : #endif
119 :
120 : /*static*/ gboolean
121 499 : nsAppShell::EventProcessorCallback(GIOChannel *source,
122 : GIOCondition condition,
123 : gpointer data)
124 : {
125 499 : nsAppShell *self = static_cast<nsAppShell *>(data);
126 :
127 : unsigned char c;
128 0 : Unused << read(self->mPipeFDs[0], &c, 1);
129 499 : NS_ASSERTION(c == (unsigned char) NOTIFY_TOKEN, "wrong token");
130 :
131 0 : self->NativeEventCallback();
132 499 : return TRUE;
133 : }
134 :
135 0 : nsAppShell::~nsAppShell()
136 : {
137 0 : if (mTag)
138 0 : g_source_remove(mTag);
139 0 : if (mPipeFDs[0])
140 0 : close(mPipeFDs[0]);
141 0 : if (mPipeFDs[1])
142 0 : close(mPipeFDs[1]);
143 0 : }
144 :
145 : nsresult
146 1 : nsAppShell::Init()
147 : {
148 : // For any versions of Glib before 2.36, g_type_init must be explicitly called
149 : // to safely use the library. Failure to do so may cause various failures/crashes
150 : // in any code that uses Glib, Gdk, or Gtk. In later versions of Glib, this call
151 : // is a no-op.
152 1 : g_type_init();
153 :
154 : #ifdef MOZ_ENABLE_DBUS
155 1 : if (XRE_IsParentProcess()) {
156 : nsCOMPtr<nsIPowerManagerService> powerManagerService =
157 2 : do_GetService(POWERMANAGERSERVICE_CONTRACTID);
158 :
159 0 : if (powerManagerService) {
160 1 : powerManagerService->AddWakeLockListener(WakeLockListener::GetSingleton());
161 : } else {
162 0 : NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!");
163 : }
164 : }
165 : #endif
166 :
167 0 : if (!sPollFunc) {
168 0 : sPollFunc = g_main_context_get_poll_func(nullptr);
169 1 : g_main_context_set_poll_func(nullptr, &PollWrapper);
170 : }
171 :
172 0 : if (XRE_IsParentProcess()) {
173 0 : ScreenManager& screenManager = ScreenManager::GetSingleton();
174 0 : if (gfxPlatform::IsHeadless()) {
175 2 : screenManager.SetHelper(mozilla::MakeUnique<HeadlessScreenHelper>());
176 : } else {
177 0 : screenManager.SetHelper(mozilla::MakeUnique<ScreenHelperGTK>());
178 : }
179 : }
180 :
181 1 : if (gtk_check_version(3, 16, 3) == nullptr) {
182 : // Before 3.16.3, GDK cannot override classname by --class command line
183 : // option when program uses gdk_set_program_class().
184 : //
185 : // See https://bugzilla.gnome.org/show_bug.cgi?id=747634
186 0 : nsAutoString brandName;
187 0 : mozilla::widget::WidgetUtils::GetBrandShortName(brandName);
188 0 : if (!brandName.IsEmpty()) {
189 2 : gdk_set_program_class(NS_ConvertUTF16toUTF8(brandName).get());
190 : }
191 : }
192 :
193 : #ifdef MOZ_WIDGET_GTK
194 0 : if (!sReal_gtk_window_check_resize &&
195 1 : gtk_check_version(3,8,0) != nullptr) { // GTK 3.0 to GTK 3.6.
196 : // GtkWindow is a static class and so will leak anyway but this ref
197 : // makes sure it isn't recreated.
198 0 : gpointer gtk_plug_class = g_type_class_ref(GTK_TYPE_WINDOW);
199 0 : auto check_resize = >K_CONTAINER_CLASS(gtk_plug_class)->check_resize;
200 0 : sReal_gtk_window_check_resize = *check_resize;
201 0 : *check_resize = wrap_gtk_window_check_resize;
202 : }
203 :
204 0 : if (!sPendingResumeQuark &&
205 1 : gtk_check_version(3,14,7) != nullptr) { // GTK 3.0 to GTK 3.14.7.
206 : // GTK 3.8 - 3.14 registered this type when creating the frame clock
207 : // for the root window of the display when the display was opened.
208 0 : GType gdkFrameClockIdleType = g_type_from_name("GdkFrameClockIdle");
209 0 : if (gdkFrameClockIdleType) { // not in versions prior to 3.8
210 0 : sPendingResumeQuark = g_quark_from_string("moz-resume-is-pending");
211 : auto gdk_frame_clock_idle_class =
212 0 : G_OBJECT_CLASS(g_type_class_peek_static(gdkFrameClockIdleType));
213 0 : auto constructed = &gdk_frame_clock_idle_class->constructed;
214 0 : sRealGdkFrameClockConstructed = *constructed;
215 0 : *constructed = WrapGdkFrameClockConstructed;
216 0 : auto dispose = &gdk_frame_clock_idle_class->dispose;
217 0 : sRealGdkFrameClockDispose = *dispose;
218 0 : *dispose = WrapGdkFrameClockDispose;
219 : }
220 : }
221 :
222 : // Workaround for bug 1209659 which is fixed by Gtk3.20
223 0 : if (gtk_check_version(3, 20, 0) != nullptr)
224 1 : unsetenv("GTK_CSD");
225 : #endif
226 :
227 1 : if (PR_GetEnv("MOZ_DEBUG_PAINTS"))
228 0 : gdk_window_set_debug_updates(TRUE);
229 :
230 : // Whitelist of only common, stable formats - see bugs 1197059 and 1203078
231 0 : GSList* pixbufFormats = gdk_pixbuf_get_formats();
232 0 : for (GSList* iter = pixbufFormats; iter; iter = iter->next) {
233 0 : GdkPixbufFormat* format = static_cast<GdkPixbufFormat*>(iter->data);
234 0 : gchar* name = gdk_pixbuf_format_get_name(format);
235 0 : if (strcmp(name, "jpeg") &&
236 0 : strcmp(name, "png") &&
237 0 : strcmp(name, "gif") &&
238 0 : strcmp(name, "bmp") &&
239 0 : strcmp(name, "ico") &&
240 0 : strcmp(name, "xpm") &&
241 0 : strcmp(name, "svg")) {
242 9 : gdk_pixbuf_format_set_disabled(format, TRUE);
243 : }
244 16 : g_free(name);
245 : }
246 1 : g_slist_free(pixbufFormats);
247 :
248 0 : int err = pipe(mPipeFDs);
249 1 : if (err)
250 : return NS_ERROR_OUT_OF_MEMORY;
251 :
252 : GIOChannel *ioc;
253 : GSource *source;
254 :
255 : // make the pipe nonblocking
256 :
257 0 : int flags = fcntl(mPipeFDs[0], F_GETFL, 0);
258 1 : if (flags == -1)
259 : goto failed;
260 0 : err = fcntl(mPipeFDs[0], F_SETFL, flags | O_NONBLOCK);
261 1 : if (err == -1)
262 : goto failed;
263 0 : flags = fcntl(mPipeFDs[1], F_GETFL, 0);
264 1 : if (flags == -1)
265 : goto failed;
266 0 : err = fcntl(mPipeFDs[1], F_SETFL, flags | O_NONBLOCK);
267 1 : if (err == -1)
268 : goto failed;
269 :
270 0 : ioc = g_io_channel_unix_new(mPipeFDs[0]);
271 0 : source = g_io_create_watch(ioc, G_IO_IN);
272 0 : g_io_channel_unref(ioc);
273 0 : g_source_set_callback(source, (GSourceFunc)EventProcessorCallback, this, nullptr);
274 0 : g_source_set_can_recurse(source, TRUE);
275 0 : mTag = g_source_attach(source, nullptr);
276 1 : g_source_unref(source);
277 :
278 1 : return nsBaseAppShell::Init();
279 : failed:
280 0 : close(mPipeFDs[0]);
281 0 : close(mPipeFDs[1]);
282 0 : mPipeFDs[0] = mPipeFDs[1] = 0;
283 0 : return NS_ERROR_FAILURE;
284 : }
285 :
286 : void
287 500 : nsAppShell::ScheduleNativeEventCallback()
288 : {
289 0 : unsigned char buf[] = { NOTIFY_TOKEN };
290 0 : Unused << write(mPipeFDs[1], buf, 1);
291 500 : }
292 :
293 : bool
294 1433 : nsAppShell::ProcessNextNativeEvent(bool mayWait)
295 : {
296 : return g_main_context_iteration(nullptr, mayWait);
297 : }
|