1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=4:tabstop=4:
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/. */
13 #include "nsAppShell.h"
15 #include "mozilla/Logging.h"
17 #include "mozilla/BackgroundHangMonitor.h"
18 #include "mozilla/Hal.h"
19 #include "mozilla/ProfilerLabels.h"
20 #include "mozilla/ProfilerThreadSleep.h"
21 #include "mozilla/Unused.h"
22 #include "mozilla/GUniquePtr.h"
23 #include "mozilla/WidgetUtils.h"
24 #include "nsIPowerManagerService.h"
25 #ifdef MOZ_ENABLE_DBUS
26 # include <dbus/dbus-glib-lowlevel.h>
28 # include "WakeLockListener.h"
29 # include "nsIObserverService.h"
31 #include "gfxPlatform.h"
32 #include "nsAppRunner.h"
33 #include "mozilla/XREAppData.h"
34 #include "ScreenHelperGTK.h"
35 #include "HeadlessScreenHelper.h"
36 #include "mozilla/widget/ScreenManager.h"
38 # include "nsWaylandDisplay.h"
41 using namespace mozilla
;
42 using mozilla::widget::HeadlessScreenHelper
;
43 using mozilla::widget::ScreenHelperGTK
;
44 using mozilla::widget::ScreenManager
;
46 #define NOTIFY_TOKEN 0xFA
48 LazyLogModule
gWidgetLog("Widget");
49 LazyLogModule
gWidgetDragLog("WidgetDrag");
50 LazyLogModule
gWidgetWaylandLog("WidgetWayland");
51 LazyLogModule
gWidgetPopupLog("WidgetPopup");
52 LazyLogModule
gWidgetVsync("WidgetVsync");
53 LazyLogModule
gDmabufLog("Dmabuf");
54 LazyLogModule
gClipboardLog("WidgetClipboard");
56 static GPollFunc sPollFunc
;
58 // Wrapper function to disable hang monitoring while waiting in poll().
59 static gint
PollWrapper(GPollFD
* aUfds
, guint aNfsd
, gint aTimeout
) {
61 // When the timeout is 0, there is no wait, so no point in notifying
62 // the BackgroundHangMonitor and the profiler.
63 return (*sPollFunc
)(aUfds
, aNfsd
, aTimeout
);
66 mozilla::BackgroundHangMonitor().NotifyWait();
69 gint timeout
= aTimeout
;
72 begin
= g_get_monotonic_time();
75 AUTO_PROFILER_LABEL("PollWrapper", IDLE
);
76 AUTO_PROFILER_THREAD_SLEEP
;
78 result
= (*sPollFunc
)(aUfds
, aNfsd
, timeout
);
80 // The result will be -1 with the EINTR error if the poll was interrupted
81 // by a signal, typically the signal sent by the profiler to sample the
82 // process. We are only done waiting if we are not in that case.
83 if (result
!= -1 || errno
!= EINTR
) {
88 // Adjust the timeout to account for the time already spent waiting.
89 gint elapsedSinceBegin
= (g_get_monotonic_time() - begin
) / 1000;
90 if (elapsedSinceBegin
< aTimeout
) {
91 timeout
= aTimeout
- elapsedSinceBegin
;
93 // poll returns 0 to indicate the call timed out before any fd
101 mozilla::BackgroundHangMonitor().NotifyActivity();
105 // Emit resume-events on GdkFrameClock if flush-events has not been
106 // balanced by resume-events at dispose.
107 // For https://bugzilla.gnome.org/show_bug.cgi?id=742636
108 static decltype(GObjectClass::constructed
) sRealGdkFrameClockConstructed
;
109 static decltype(GObjectClass::dispose
) sRealGdkFrameClockDispose
;
110 static GQuark sPendingResumeQuark
;
112 static void OnFlushEvents(GObject
* clock
, gpointer
) {
113 g_object_set_qdata(clock
, sPendingResumeQuark
, GUINT_TO_POINTER(1));
116 static void OnResumeEvents(GObject
* clock
, gpointer
) {
117 g_object_set_qdata(clock
, sPendingResumeQuark
, nullptr);
120 static void WrapGdkFrameClockConstructed(GObject
* object
) {
121 sRealGdkFrameClockConstructed(object
);
123 g_signal_connect(object
, "flush-events", G_CALLBACK(OnFlushEvents
), nullptr);
124 g_signal_connect(object
, "resume-events", G_CALLBACK(OnResumeEvents
),
128 static void WrapGdkFrameClockDispose(GObject
* object
) {
129 if (g_object_get_qdata(object
, sPendingResumeQuark
)) {
130 g_signal_emit_by_name(object
, "resume-events");
133 sRealGdkFrameClockDispose(object
);
137 gboolean
nsAppShell::EventProcessorCallback(GIOChannel
* source
,
138 GIOCondition condition
,
140 nsAppShell
* self
= static_cast<nsAppShell
*>(data
);
143 Unused
<< read(self
->mPipeFDs
[0], &c
, 1);
144 NS_ASSERTION(c
== (unsigned char)NOTIFY_TOKEN
, "wrong token");
146 self
->NativeEventCallback();
150 nsAppShell::~nsAppShell() {
151 #ifdef MOZ_ENABLE_DBUS
154 mozilla::hal::Shutdown();
156 if (mTag
) g_source_remove(mTag
);
157 if (mPipeFDs
[0]) close(mPipeFDs
[0]);
158 if (mPipeFDs
[1]) close(mPipeFDs
[1]);
161 #ifdef MOZ_ENABLE_DBUS
162 static void SessionSleepCallback(DBusGProxy
* aProxy
, gboolean aSuspend
,
164 nsCOMPtr
<nsIObserverService
> observerService
=
165 mozilla::services::GetObserverService();
166 if (!observerService
) {
171 // Post sleep_notification
172 observerService
->NotifyObservers(nullptr, NS_WIDGET_SLEEP_OBSERVER_TOPIC
,
175 // Post wake_notification
176 observerService
->NotifyObservers(nullptr, NS_WIDGET_WAKE_OBSERVER_TOPIC
,
181 static DBusHandlerResult
ConnectionSignalFilter(DBusConnection
* aConnection
,
182 DBusMessage
* aMessage
,
184 if (dbus_message_is_signal(aMessage
, DBUS_INTERFACE_LOCAL
, "Disconnected")) {
185 auto* appShell
= static_cast<nsAppShell
*>(aData
);
186 appShell
->StopDBusListening();
187 // We do not return DBUS_HANDLER_RESULT_HANDLED here because the connection
188 // might be shared and some other filters might want to do something.
191 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
195 // https://github.com/lcp/NetworkManager/blob/240f47c892b4e935a3e92fc09eb15163d1fa28d8/src/nm-sleep-monitor-systemd.c
196 // Use login1 to signal sleep and wake notifications.
197 void nsAppShell::StartDBusListening() {
198 GUniquePtr
<GError
> error
;
199 mDBusConnection
= dbus_g_bus_get(DBUS_BUS_SYSTEM
, getter_Transfers(error
));
200 if (!mDBusConnection
) {
201 NS_WARNING(nsPrintfCString("gds: Failed to open connection to bus %s\n",
207 DBusConnection
* dbusConnection
=
208 dbus_g_connection_get_connection(mDBusConnection
);
210 // Make sure we do not exit the entire program if DBus connection gets
212 dbus_connection_set_exit_on_disconnect(dbusConnection
, false);
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,
219 mLogin1Proxy
= dbus_g_proxy_new_for_name(
220 mDBusConnection
, "org.freedesktop.login1", "/org/freedesktop/login1",
221 "org.freedesktop.login1.Manager");
224 NS_WARNING("gds: error-no dbus proxy\n");
228 dbus_g_proxy_add_signal(mLogin1Proxy
, "PrepareForSleep", G_TYPE_BOOLEAN
,
230 dbus_g_proxy_connect_signal(mLogin1Proxy
, "PrepareForSleep",
231 G_CALLBACK(SessionSleepCallback
), this, nullptr);
234 void nsAppShell::StopDBusListening() {
235 // If mDBusConnection isn't initialized, that means we are not really
237 if (!mDBusConnection
) {
240 dbus_connection_remove_filter(
241 dbus_g_connection_get_connection(mDBusConnection
), ConnectionSignalFilter
,
245 dbus_g_proxy_disconnect_signal(mLogin1Proxy
, "PrepareForSleep",
246 G_CALLBACK(SessionSleepCallback
), this);
247 g_object_unref(mLogin1Proxy
);
248 mLogin1Proxy
= nullptr;
250 dbus_g_connection_unref(mDBusConnection
);
251 mDBusConnection
= nullptr;
256 nsresult
nsAppShell::Init() {
257 mozilla::hal::Init();
259 #ifdef MOZ_ENABLE_DBUS
260 if (XRE_IsParentProcess()) {
261 nsCOMPtr
<nsIPowerManagerService
> powerManagerService
=
262 do_GetService(POWERMANAGERSERVICE_CONTRACTID
);
264 if (powerManagerService
) {
265 powerManagerService
->AddWakeLockListener(
266 WakeLockListener::GetSingleton());
269 "Failed to retrieve PowerManagerService, wakelocks will be broken!");
272 StartDBusListening();
277 sPollFunc
= g_main_context_get_poll_func(nullptr);
278 g_main_context_set_poll_func(nullptr, &PollWrapper
);
281 if (XRE_IsParentProcess()) {
282 ScreenManager
& screenManager
= ScreenManager::GetSingleton();
283 if (gfxPlatform::IsHeadless()) {
284 screenManager
.SetHelper(mozilla::MakeUnique
<HeadlessScreenHelper
>());
286 screenManager
.SetHelper(mozilla::MakeUnique
<ScreenHelperGTK
>());
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().
293 // See https://bugzilla.gnome.org/show_bug.cgi?id=747634
295 // Only bother doing this for the parent process, since it's the one
296 // creating top-level windows.
298 gdk_set_program_class(gAppData
->remotingName
);
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
;
321 // Workaround for bug 1209659 which is fixed by Gtk3.20
322 if (gtk_check_version(3, 20, 0) != nullptr) {
326 if (PR_GetEnv("MOZ_DEBUG_PAINTS")) {
327 gdk_window_set_debug_updates(TRUE
);
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
);
342 g_slist_free(pixbufFormats
);
344 int err
= pipe(mPipeFDs
);
345 if (err
) return NS_ERROR_OUT_OF_MEMORY
;
350 // make the pipe nonblocking
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
;
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,
366 g_source_set_can_recurse(source
, TRUE
);
367 mTag
= g_source_attach(source
, nullptr);
368 g_source_unref(source
);
370 return nsBaseAppShell::Init();
374 mPipeFDs
[0] = mPipeFDs
[1] = 0;
375 return NS_ERROR_FAILURE
;
378 void nsAppShell::ScheduleNativeEventCallback() {
379 unsigned char buf
[] = {NOTIFY_TOKEN
};
380 Unused
<< write(mPipeFDs
[1], buf
, 1);
383 bool nsAppShell::ProcessNextNativeEvent(bool mayWait
) {
384 bool didProcessEvent
= g_main_context_iteration(nullptr, mayWait
);
386 mozilla::widget::WaylandDispatchDisplays();
388 return didProcessEvent
;