1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
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/BackgroundHangMonitor.h"
18 #include "mozilla/Hal.h"
19 #include "mozilla/Unused.h"
20 #include "mozilla/WidgetUtils.h"
21 #include "GeckoProfiler.h"
22 #include "nsIPowerManagerService.h"
23 #ifdef MOZ_ENABLE_DBUS
24 #  include <dbus/dbus-glib-lowlevel.h>
25 #  include <gio/gio.h>
26 #  include "WakeLockListener.h"
27 #  include "nsIObserverService.h"
28 #endif
29 #include "gfxPlatform.h"
30 #include "nsAppRunner.h"
31 #include "mozilla/XREAppData.h"
32 #include "ScreenHelperGTK.h"
33 #include "HeadlessScreenHelper.h"
34 #include "mozilla/widget/ScreenManager.h"
35 #ifdef MOZ_WAYLAND
36 #  include "nsWaylandDisplay.h"
37 #endif
38 
39 using mozilla::LazyLogModule;
40 using mozilla::Unused;
41 using mozilla::widget::HeadlessScreenHelper;
42 using mozilla::widget::ScreenHelperGTK;
43 using mozilla::widget::ScreenManager;
44 
45 #define NOTIFY_TOKEN 0xFA
46 
47 LazyLogModule gWidgetLog("Widget");
48 LazyLogModule gWidgetDragLog("WidgetDrag");
49 LazyLogModule gWidgetWaylandLog("WidgetWayland");
50 LazyLogModule gWidgetPopupLog("WidgetPopup");
51 LazyLogModule gWidgetVsync("WidgetVsync");
52 LazyLogModule gDmabufLog("Dmabuf");
53 LazyLogModule gClipboardLog("WidgetClipboard");
54 
55 static GPollFunc sPollFunc;
56 
57 // Wrapper function to disable hang monitoring while waiting in poll().
PollWrapper(GPollFD * aUfds,guint aNfsd,gint aTimeout)58 static gint PollWrapper(GPollFD* aUfds, guint aNfsd, gint aTimeout) {
59   if (aTimeout == 0) {
60     // When the timeout is 0, there is no wait, so no point in notifying
61     // the BackgroundHangMonitor and the profiler.
62     return (*sPollFunc)(aUfds, aNfsd, aTimeout);
63   }
64 
65   mozilla::BackgroundHangMonitor().NotifyWait();
66   gint result;
67   {
68     gint timeout = aTimeout;
69     gint64 begin = 0;
70     if (aTimeout != -1) {
71       begin = g_get_monotonic_time();
72     }
73 
74     AUTO_PROFILER_LABEL("PollWrapper", IDLE);
75     AUTO_PROFILER_THREAD_SLEEP;
76     do {
77       result = (*sPollFunc)(aUfds, aNfsd, timeout);
78 
79       // The result will be -1 with the EINTR error if the poll was interrupted
80       // by a signal, typically the signal sent by the profiler to sample the
81       // process. We are only done waiting if we are not in that case.
82       if (result != -1 || errno != EINTR) {
83         break;
84       }
85 
86       if (aTimeout != -1) {
87         // Adjust the timeout to account for the time already spent waiting.
88         gint elapsedSinceBegin = (g_get_monotonic_time() - begin) / 1000;
89         if (elapsedSinceBegin < aTimeout) {
90           timeout = aTimeout - elapsedSinceBegin;
91         } else {
92           // poll returns 0 to indicate the call timed out before any fd
93           // became ready.
94           result = 0;
95           break;
96         }
97       }
98     } while (true);
99   }
100   mozilla::BackgroundHangMonitor().NotifyActivity();
101   return result;
102 }
103 
104 // Emit resume-events on GdkFrameClock if flush-events has not been
105 // balanced by resume-events at dispose.
106 // For https://bugzilla.gnome.org/show_bug.cgi?id=742636
107 static decltype(GObjectClass::constructed) sRealGdkFrameClockConstructed;
108 static decltype(GObjectClass::dispose) sRealGdkFrameClockDispose;
109 static GQuark sPendingResumeQuark;
110 
OnFlushEvents(GObject * clock,gpointer)111 static void OnFlushEvents(GObject* clock, gpointer) {
112   g_object_set_qdata(clock, sPendingResumeQuark, GUINT_TO_POINTER(1));
113 }
114 
OnResumeEvents(GObject * clock,gpointer)115 static void OnResumeEvents(GObject* clock, gpointer) {
116   g_object_set_qdata(clock, sPendingResumeQuark, nullptr);
117 }
118 
WrapGdkFrameClockConstructed(GObject * object)119 static void WrapGdkFrameClockConstructed(GObject* object) {
120   sRealGdkFrameClockConstructed(object);
121 
122   g_signal_connect(object, "flush-events", G_CALLBACK(OnFlushEvents), nullptr);
123   g_signal_connect(object, "resume-events", G_CALLBACK(OnResumeEvents),
124                    nullptr);
125 }
126 
WrapGdkFrameClockDispose(GObject * object)127 static void WrapGdkFrameClockDispose(GObject* object) {
128   if (g_object_get_qdata(object, sPendingResumeQuark)) {
129     g_signal_emit_by_name(object, "resume-events");
130   }
131 
132   sRealGdkFrameClockDispose(object);
133 }
134 
135 /*static*/
EventProcessorCallback(GIOChannel * source,GIOCondition condition,gpointer data)136 gboolean nsAppShell::EventProcessorCallback(GIOChannel* source,
137                                             GIOCondition condition,
138                                             gpointer data) {
139   nsAppShell* self = static_cast<nsAppShell*>(data);
140 
141   unsigned char c;
142   Unused << read(self->mPipeFDs[0], &c, 1);
143   NS_ASSERTION(c == (unsigned char)NOTIFY_TOKEN, "wrong token");
144 
145   self->NativeEventCallback();
146   return TRUE;
147 }
148 
~nsAppShell()149 nsAppShell::~nsAppShell() {
150 #ifdef MOZ_ENABLE_DBUS
151   StopDBusListening();
152 #endif
153   mozilla::hal::Shutdown();
154 
155   if (mTag) g_source_remove(mTag);
156   if (mPipeFDs[0]) close(mPipeFDs[0]);
157   if (mPipeFDs[1]) close(mPipeFDs[1]);
158 }
159 
160 #ifdef MOZ_ENABLE_DBUS
SessionSleepCallback(DBusGProxy * aProxy,gboolean aSuspend,gpointer data)161 static void SessionSleepCallback(DBusGProxy* aProxy, gboolean aSuspend,
162                                  gpointer data) {
163   nsCOMPtr<nsIObserverService> observerService =
164       mozilla::services::GetObserverService();
165   if (!observerService) {
166     return;
167   }
168 
169   if (aSuspend) {
170     // Post sleep_notification
171     observerService->NotifyObservers(nullptr, NS_WIDGET_SLEEP_OBSERVER_TOPIC,
172                                      nullptr);
173   } else {
174     // Post wake_notification
175     observerService->NotifyObservers(nullptr, NS_WIDGET_WAKE_OBSERVER_TOPIC,
176                                      nullptr);
177   }
178 }
179 
ConnectionSignalFilter(DBusConnection * aConnection,DBusMessage * aMessage,void * aData)180 static DBusHandlerResult ConnectionSignalFilter(DBusConnection* aConnection,
181                                                 DBusMessage* aMessage,
182                                                 void* aData) {
183   if (dbus_message_is_signal(aMessage, DBUS_INTERFACE_LOCAL, "Disconnected")) {
184     auto* appShell = static_cast<nsAppShell*>(aData);
185     appShell->StopDBusListening();
186     // We do not return DBUS_HANDLER_RESULT_HANDLED here because the connection
187     // might be shared and some other filters might want to do something.
188   }
189 
190   return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
191 }
192 
193 // Based on
194 // https://github.com/lcp/NetworkManager/blob/240f47c892b4e935a3e92fc09eb15163d1fa28d8/src/nm-sleep-monitor-systemd.c
195 // Use login1 to signal sleep and wake notifications.
StartDBusListening()196 void nsAppShell::StartDBusListening() {
197   GError* error = nullptr;
198   mDBusConnection = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error);
199   if (!mDBusConnection) {
200     NS_WARNING(nsPrintfCString("gds: Failed to open connection to bus %s\n",
201                                error->message)
202                    .get());
203     g_error_free(error);
204     return;
205   }
206 
207   DBusConnection* dbusConnection =
208       dbus_g_connection_get_connection(mDBusConnection);
209 
210   // Make sure we do not exit the entire program if DBus connection gets
211   // lost.
212   dbus_connection_set_exit_on_disconnect(dbusConnection, false);
213 
214   // Listening to signals the DBus connection is going to get so we will
215   // know when it is lost and we will be able to disconnect cleanly.
216   dbus_connection_add_filter(dbusConnection, ConnectionSignalFilter, this,
217                              nullptr);
218 
219   mLogin1Proxy = dbus_g_proxy_new_for_name(
220       mDBusConnection, "org.freedesktop.login1", "/org/freedesktop/login1",
221       "org.freedesktop.login1.Manager");
222 
223   if (!mLogin1Proxy) {
224     NS_WARNING("gds: error-no dbus proxy\n");
225     return;
226   }
227 
228   dbus_g_proxy_add_signal(mLogin1Proxy, "PrepareForSleep", G_TYPE_BOOLEAN,
229                           G_TYPE_INVALID);
230   dbus_g_proxy_connect_signal(mLogin1Proxy, "PrepareForSleep",
231                               G_CALLBACK(SessionSleepCallback), this, nullptr);
232 }
233 
StopDBusListening()234 void nsAppShell::StopDBusListening() {
235   // If mDBusConnection isn't initialized, that means we are not really
236   // listening.
237   if (!mDBusConnection) {
238     return;
239   }
240   dbus_connection_remove_filter(
241       dbus_g_connection_get_connection(mDBusConnection), ConnectionSignalFilter,
242       this);
243 
244   if (mLogin1Proxy) {
245     dbus_g_proxy_disconnect_signal(mLogin1Proxy, "PrepareForSleep",
246                                    G_CALLBACK(SessionSleepCallback), this);
247     g_object_unref(mLogin1Proxy);
248     mLogin1Proxy = nullptr;
249   }
250   dbus_g_connection_unref(mDBusConnection);
251   mDBusConnection = nullptr;
252 }
253 
254 #endif
255 
Init()256 nsresult nsAppShell::Init() {
257   mozilla::hal::Init();
258 
259 #ifdef MOZ_ENABLE_DBUS
260   if (XRE_IsParentProcess()) {
261     nsCOMPtr<nsIPowerManagerService> powerManagerService =
262         do_GetService(POWERMANAGERSERVICE_CONTRACTID);
263 
264     if (powerManagerService) {
265       powerManagerService->AddWakeLockListener(
266           WakeLockListener::GetSingleton());
267     } else {
268       NS_WARNING(
269           "Failed to retrieve PowerManagerService, wakelocks will be broken!");
270     }
271 
272     StartDBusListening();
273   }
274 #endif
275 
276   if (!sPollFunc) {
277     sPollFunc = g_main_context_get_poll_func(nullptr);
278     g_main_context_set_poll_func(nullptr, &PollWrapper);
279   }
280 
281   if (XRE_IsParentProcess()) {
282     ScreenManager& screenManager = ScreenManager::GetSingleton();
283     if (gfxPlatform::IsHeadless()) {
284       screenManager.SetHelper(mozilla::MakeUnique<HeadlessScreenHelper>());
285     } else {
286       screenManager.SetHelper(mozilla::MakeUnique<ScreenHelperGTK>());
287     }
288 
289     if (gtk_check_version(3, 16, 3) == nullptr) {
290       // Before 3.16.3, GDK cannot override classname by --class command line
291       // option when program uses gdk_set_program_class().
292       //
293       // See https://bugzilla.gnome.org/show_bug.cgi?id=747634
294       //
295       // Only bother doing this for the parent process, since it's the one
296       // creating top-level windows.
297       if (gAppData) {
298         gdk_set_program_class(gAppData->remotingName);
299       }
300     }
301   }
302 
303   if (!sPendingResumeQuark &&
304       gtk_check_version(3, 14, 7) != nullptr) {  // GTK 3.0 to GTK 3.14.7.
305     // GTK 3.8 - 3.14 registered this type when creating the frame clock
306     // for the root window of the display when the display was opened.
307     GType gdkFrameClockIdleType = g_type_from_name("GdkFrameClockIdle");
308     if (gdkFrameClockIdleType) {  // not in versions prior to 3.8
309       sPendingResumeQuark = g_quark_from_string("moz-resume-is-pending");
310       auto gdk_frame_clock_idle_class =
311           G_OBJECT_CLASS(g_type_class_peek_static(gdkFrameClockIdleType));
312       auto constructed = &gdk_frame_clock_idle_class->constructed;
313       sRealGdkFrameClockConstructed = *constructed;
314       *constructed = WrapGdkFrameClockConstructed;
315       auto dispose = &gdk_frame_clock_idle_class->dispose;
316       sRealGdkFrameClockDispose = *dispose;
317       *dispose = WrapGdkFrameClockDispose;
318     }
319   }
320 
321   // Workaround for bug 1209659 which is fixed by Gtk3.20
322   if (gtk_check_version(3, 20, 0) != nullptr) {
323     unsetenv("GTK_CSD");
324   }
325 
326   if (PR_GetEnv("MOZ_DEBUG_PAINTS")) {
327     gdk_window_set_debug_updates(TRUE);
328   }
329 
330   // Whitelist of only common, stable formats - see bugs 1197059 and 1203078
331   GSList* pixbufFormats = gdk_pixbuf_get_formats();
332   for (GSList* iter = pixbufFormats; iter; iter = iter->next) {
333     GdkPixbufFormat* format = static_cast<GdkPixbufFormat*>(iter->data);
334     gchar* name = gdk_pixbuf_format_get_name(format);
335     if (strcmp(name, "jpeg") && strcmp(name, "png") && strcmp(name, "gif") &&
336         strcmp(name, "bmp") && strcmp(name, "ico") && strcmp(name, "xpm") &&
337         strcmp(name, "svg")) {
338       gdk_pixbuf_format_set_disabled(format, TRUE);
339     }
340     g_free(name);
341   }
342   g_slist_free(pixbufFormats);
343 
344   int err = pipe(mPipeFDs);
345   if (err) return NS_ERROR_OUT_OF_MEMORY;
346 
347   GIOChannel* ioc;
348   GSource* source;
349 
350   // make the pipe nonblocking
351 
352   int flags = fcntl(mPipeFDs[0], F_GETFL, 0);
353   if (flags == -1) goto failed;
354   err = fcntl(mPipeFDs[0], F_SETFL, flags | O_NONBLOCK);
355   if (err == -1) goto failed;
356   flags = fcntl(mPipeFDs[1], F_GETFL, 0);
357   if (flags == -1) goto failed;
358   err = fcntl(mPipeFDs[1], F_SETFL, flags | O_NONBLOCK);
359   if (err == -1) goto failed;
360 
361   ioc = g_io_channel_unix_new(mPipeFDs[0]);
362   source = g_io_create_watch(ioc, G_IO_IN);
363   g_io_channel_unref(ioc);
364   g_source_set_callback(source, (GSourceFunc)EventProcessorCallback, this,
365                         nullptr);
366   g_source_set_can_recurse(source, TRUE);
367   mTag = g_source_attach(source, nullptr);
368   g_source_unref(source);
369 
370   return nsBaseAppShell::Init();
371 failed:
372   close(mPipeFDs[0]);
373   close(mPipeFDs[1]);
374   mPipeFDs[0] = mPipeFDs[1] = 0;
375   return NS_ERROR_FAILURE;
376 }
377 
ScheduleNativeEventCallback()378 void nsAppShell::ScheduleNativeEventCallback() {
379   unsigned char buf[] = {NOTIFY_TOKEN};
380   Unused << write(mPipeFDs[1], buf, 1);
381 }
382 
ProcessNextNativeEvent(bool mayWait)383 bool nsAppShell::ProcessNextNativeEvent(bool mayWait) {
384   bool didProcessEvent = g_main_context_iteration(nullptr, mayWait);
385 #ifdef MOZ_WAYLAND
386   mozilla::widget::WaylandDispatchDisplays();
387 #endif
388   return didProcessEvent;
389 }
390