1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
12 #include "nsAppShell.h"
13 #include "nsBaseAppShell.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
27 # include "nsIObserverService.h"
28 # include "WidgetUtilsGtk.h"
30 #include "WakeLockListener.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 namespace mozilla::widget
;
43 using mozilla::widget::HeadlessScreenHelper
;
44 using mozilla::widget::ScreenHelperGTK
;
45 using mozilla::widget::ScreenManager
;
47 #define NOTIFY_TOKEN 0xFA
48 #define QUIT_TOKEN 0xFB
50 LazyLogModule
gWidgetLog("Widget");
51 LazyLogModule
gWidgetDragLog("WidgetDrag");
52 LazyLogModule
gWidgetWaylandLog("WidgetWayland");
53 LazyLogModule
gWidgetPopupLog("WidgetPopup");
54 LazyLogModule
gWidgetVsync("WidgetVsync");
55 LazyLogModule
gDmabufLog("Dmabuf");
57 static GPollFunc sPollFunc
;
59 nsAppShell
* sAppShell
= nullptr;
61 // Wrapper function to disable hang monitoring while waiting in poll().
62 static gint
PollWrapper(GPollFD
* aUfds
, guint aNfsd
, gint aTimeout
) {
64 // When the timeout is 0, there is no wait, so no point in notifying
65 // the BackgroundHangMonitor and the profiler.
66 return (*sPollFunc
)(aUfds
, aNfsd
, aTimeout
);
69 mozilla::BackgroundHangMonitor().NotifyWait();
72 gint timeout
= aTimeout
;
75 begin
= g_get_monotonic_time();
78 AUTO_PROFILER_LABEL("PollWrapper", IDLE
);
79 AUTO_PROFILER_THREAD_SLEEP
;
81 result
= (*sPollFunc
)(aUfds
, aNfsd
, timeout
);
83 // The result will be -1 with the EINTR error if the poll was interrupted
84 // by a signal, typically the signal sent by the profiler to sample the
85 // process. We are only done waiting if we are not in that case.
86 if (result
!= -1 || errno
!= EINTR
) {
91 // Adjust the timeout to account for the time already spent waiting.
92 gint elapsedSinceBegin
= (g_get_monotonic_time() - begin
) / 1000;
93 if (elapsedSinceBegin
< aTimeout
) {
94 timeout
= aTimeout
- elapsedSinceBegin
;
96 // poll returns 0 to indicate the call timed out before any fd
104 mozilla::BackgroundHangMonitor().NotifyActivity();
108 // Emit resume-events on GdkFrameClock if flush-events has not been
109 // balanced by resume-events at dispose.
110 // For https://bugzilla.gnome.org/show_bug.cgi?id=742636
111 static decltype(GObjectClass::constructed
) sRealGdkFrameClockConstructed
;
112 static decltype(GObjectClass::dispose
) sRealGdkFrameClockDispose
;
113 static GQuark sPendingResumeQuark
;
115 static void OnFlushEvents(GObject
* clock
, gpointer
) {
116 g_object_set_qdata(clock
, sPendingResumeQuark
, GUINT_TO_POINTER(1));
119 static void OnResumeEvents(GObject
* clock
, gpointer
) {
120 g_object_set_qdata(clock
, sPendingResumeQuark
, nullptr);
123 static void WrapGdkFrameClockConstructed(GObject
* object
) {
124 sRealGdkFrameClockConstructed(object
);
126 g_signal_connect(object
, "flush-events", G_CALLBACK(OnFlushEvents
), nullptr);
127 g_signal_connect(object
, "resume-events", G_CALLBACK(OnResumeEvents
),
131 static void WrapGdkFrameClockDispose(GObject
* object
) {
132 if (g_object_get_qdata(object
, sPendingResumeQuark
)) {
133 g_signal_emit_by_name(object
, "resume-events");
136 sRealGdkFrameClockDispose(object
);
140 gboolean
nsAppShell::EventProcessorCallback(GIOChannel
* source
,
141 GIOCondition condition
,
143 nsAppShell
* self
= static_cast<nsAppShell
*>(data
);
146 Unused
<< read(self
->mPipeFDs
[0], &c
, 1);
149 self
->NativeEventCallback();
155 NS_ASSERTION(false, "wrong token");
161 nsAppShell::~nsAppShell() {
164 #ifdef MOZ_ENABLE_DBUS
167 mozilla::hal::Shutdown();
169 if (mTag
) g_source_remove(mTag
);
170 if (mPipeFDs
[0]) close(mPipeFDs
[0]);
171 if (mPipeFDs
[1]) close(mPipeFDs
[1]);
174 mozilla::StaticRefPtr
<WakeLockListener
> sWakeLockListener
;
175 static void AddScreenWakeLockListener() {
176 nsCOMPtr
<nsIPowerManagerService
> powerManager
=
177 do_GetService(POWERMANAGERSERVICE_CONTRACTID
);
179 sWakeLockListener
= new WakeLockListener();
180 powerManager
->AddWakeLockListener(sWakeLockListener
);
183 "Failed to retrieve PowerManagerService, wakelocks will be broken!");
187 static void RemoveScreenWakeLockListener() {
188 nsCOMPtr
<nsIPowerManagerService
> powerManager
=
189 do_GetService(POWERMANAGERSERVICE_CONTRACTID
);
191 powerManager
->RemoveWakeLockListener(sWakeLockListener
);
192 sWakeLockListener
= nullptr;
196 #ifdef MOZ_ENABLE_DBUS
197 void nsAppShell::DBusSessionSleepCallback(GDBusProxy
* aProxy
,
200 GVariant
* aParameters
,
201 gpointer aUserData
) {
202 if (g_strcmp0(aSignalName
, "PrepareForSleep")) {
205 nsCOMPtr
<nsIObserverService
> observerService
=
206 mozilla::services::GetObserverService();
207 if (!observerService
) {
210 if (!g_variant_is_of_type(aParameters
, G_VARIANT_TYPE_TUPLE
) ||
211 g_variant_n_children(aParameters
) != 1) {
213 nsPrintfCString("Unexpected location updated signal params type: %s\n",
214 g_variant_get_type_string(aParameters
))
219 RefPtr
<GVariant
> variant
=
220 dont_AddRef(g_variant_get_child_value(aParameters
, 0));
221 if (!g_variant_is_of_type(variant
, G_VARIANT_TYPE_BOOLEAN
)) {
223 nsPrintfCString("Unexpected location updated signal params type: %s\n",
224 g_variant_get_type_string(aParameters
))
229 gboolean suspend
= g_variant_get_boolean(variant
);
231 // Post sleep_notification
232 observerService
->NotifyObservers(nullptr, NS_WIDGET_SLEEP_OBSERVER_TOPIC
,
235 // Post wake_notification
236 observerService
->NotifyObservers(nullptr, NS_WIDGET_WAKE_OBSERVER_TOPIC
,
241 void nsAppShell::DBusTimedatePropertiesChangedCallback(GDBusProxy
* aProxy
,
244 GVariant
* aParameters
,
245 gpointer aUserData
) {
246 if (g_strcmp0(aSignalName
, "PropertiesChanged")) {
249 nsBaseAppShell::OnSystemTimezoneChange();
252 void nsAppShell::DBusConnectClientResponse(GObject
* aObject
,
253 GAsyncResult
* aResult
,
254 gpointer aUserData
) {
255 GUniquePtr
<GError
> error
;
256 RefPtr
<GDBusProxy
> proxyClient
=
257 dont_AddRef(g_dbus_proxy_new_finish(aResult
, getter_Transfers(error
)));
259 if (!IsCancelledGError(error
.get())) {
261 nsPrintfCString("Failed to connect to client: %s\n", error
->message
)
267 RefPtr self
= static_cast<nsAppShell
*>(aUserData
);
268 if (!strcmp(g_dbus_proxy_get_name(proxyClient
), "org.freedesktop.login1")) {
269 self
->mLogin1Proxy
= std::move(proxyClient
);
270 g_signal_connect(self
->mLogin1Proxy
, "g-signal",
271 G_CALLBACK(DBusSessionSleepCallback
), self
);
273 self
->mTimedate1Proxy
= std::move(proxyClient
);
274 g_signal_connect(self
->mTimedate1Proxy
, "g-signal",
275 G_CALLBACK(DBusTimedatePropertiesChangedCallback
), self
);
280 // https://github.com/lcp/NetworkManager/blob/240f47c892b4e935a3e92fc09eb15163d1fa28d8/src/nm-sleep-monitor-systemd.c
281 // Use login1 to signal sleep and wake notifications.
282 void nsAppShell::StartDBusListening() {
283 MOZ_DIAGNOSTIC_ASSERT(!mLogin1Proxy
, "Already configured?");
284 MOZ_DIAGNOSTIC_ASSERT(!mTimedate1Proxy
, "Already configured?");
285 MOZ_DIAGNOSTIC_ASSERT(!mLogin1ProxyCancellable
, "Already configured?");
286 MOZ_DIAGNOSTIC_ASSERT(!mTimedate1ProxyCancellable
, "Already configured?");
288 mLogin1ProxyCancellable
= dont_AddRef(g_cancellable_new());
289 mTimedate1ProxyCancellable
= dont_AddRef(g_cancellable_new());
291 g_dbus_proxy_new_for_bus(
292 G_BUS_TYPE_SYSTEM
, G_DBUS_PROXY_FLAGS_NONE
, nullptr,
293 "org.freedesktop.login1", "/org/freedesktop/login1",
294 "org.freedesktop.login1.Manager", mLogin1ProxyCancellable
,
295 reinterpret_cast<GAsyncReadyCallback
>(DBusConnectClientResponse
), this);
297 g_dbus_proxy_new_for_bus(
298 G_BUS_TYPE_SYSTEM
, G_DBUS_PROXY_FLAGS_NONE
, nullptr,
299 "org.freedesktop.timedate1", "/org/freedesktop/timedate1",
300 "org.freedesktop.DBus.Properties", mTimedate1ProxyCancellable
,
301 reinterpret_cast<GAsyncReadyCallback
>(DBusConnectClientResponse
), this);
304 void nsAppShell::StopDBusListening() {
306 g_signal_handlers_disconnect_matched(mLogin1Proxy
, G_SIGNAL_MATCH_DATA
, 0,
307 0, nullptr, nullptr, this);
309 if (mLogin1ProxyCancellable
) {
310 g_cancellable_cancel(mLogin1ProxyCancellable
);
311 mLogin1ProxyCancellable
= nullptr;
313 mLogin1Proxy
= nullptr;
315 if (mTimedate1Proxy
) {
316 g_signal_handlers_disconnect_matched(mTimedate1Proxy
, G_SIGNAL_MATCH_DATA
,
317 0, 0, nullptr, nullptr, this);
319 if (mTimedate1ProxyCancellable
) {
320 g_cancellable_cancel(mTimedate1ProxyCancellable
);
321 mTimedate1ProxyCancellable
= nullptr;
323 mTimedate1Proxy
= nullptr;
327 void nsAppShell::TermSignalHandler(int signo
) {
328 if (signo
!= SIGTERM
) {
329 NS_WARNING("Wrong signal!");
332 sAppShell
->ScheduleQuitEvent();
335 void nsAppShell::InstallTermSignalHandler() {
336 if (!XRE_IsParentProcess() || PR_GetEnv("MOZ_DISABLE_SIG_HANDLER") ||
341 struct sigaction act
= {}, oldact
;
342 act
.sa_handler
= TermSignalHandler
;
343 sigfillset(&act
.sa_mask
);
345 if (NS_WARN_IF(sigaction(SIGTERM
, nullptr, &oldact
) != 0)) {
348 if (oldact
.sa_handler
!= SIG_DFL
) {
349 NS_WARNING("SIGTERM signal handler is already set?");
352 sigaction(SIGTERM
, &act
, nullptr);
355 nsresult
nsAppShell::Init() {
356 mozilla::hal::Init();
358 #ifdef MOZ_ENABLE_DBUS
359 if (XRE_IsParentProcess()) {
360 StartDBusListening();
365 sPollFunc
= g_main_context_get_poll_func(nullptr);
366 g_main_context_set_poll_func(nullptr, &PollWrapper
);
369 if (XRE_IsParentProcess()) {
370 ScreenManager
& screenManager
= ScreenManager::GetSingleton();
371 if (gfxPlatform::IsHeadless()) {
372 screenManager
.SetHelper(mozilla::MakeUnique
<HeadlessScreenHelper
>());
374 screenManager
.SetHelper(mozilla::MakeUnique
<ScreenHelperGTK
>());
377 if (gtk_check_version(3, 16, 3) == nullptr) {
378 // Before 3.16.3, GDK cannot override classname by --class command line
379 // option when program uses gdk_set_program_class().
381 // See https://bugzilla.gnome.org/show_bug.cgi?id=747634
383 // Only bother doing this for the parent process, since it's the one
384 // creating top-level windows.
386 gdk_set_program_class(gAppData
->remotingName
);
391 if (!sPendingResumeQuark
&&
392 gtk_check_version(3, 14, 7) != nullptr) { // GTK 3.0 to GTK 3.14.7.
393 // GTK 3.8 - 3.14 registered this type when creating the frame clock
394 // for the root window of the display when the display was opened.
395 GType gdkFrameClockIdleType
= g_type_from_name("GdkFrameClockIdle");
396 if (gdkFrameClockIdleType
) { // not in versions prior to 3.8
397 sPendingResumeQuark
= g_quark_from_string("moz-resume-is-pending");
398 auto gdk_frame_clock_idle_class
=
399 G_OBJECT_CLASS(g_type_class_peek_static(gdkFrameClockIdleType
));
400 auto constructed
= &gdk_frame_clock_idle_class
->constructed
;
401 sRealGdkFrameClockConstructed
= *constructed
;
402 *constructed
= WrapGdkFrameClockConstructed
;
403 auto dispose
= &gdk_frame_clock_idle_class
->dispose
;
404 sRealGdkFrameClockDispose
= *dispose
;
405 *dispose
= WrapGdkFrameClockDispose
;
409 // Workaround for bug 1209659 which is fixed by Gtk3.20
410 if (gtk_check_version(3, 20, 0) != nullptr) {
414 // Whitelist of only common, stable formats - see bugs 1197059 and 1203078
415 GSList
* pixbufFormats
= gdk_pixbuf_get_formats();
416 for (GSList
* iter
= pixbufFormats
; iter
; iter
= iter
->next
) {
417 GdkPixbufFormat
* format
= static_cast<GdkPixbufFormat
*>(iter
->data
);
418 gchar
* name
= gdk_pixbuf_format_get_name(format
);
419 if (strcmp(name
, "jpeg") && strcmp(name
, "png") && strcmp(name
, "gif") &&
420 strcmp(name
, "bmp") && strcmp(name
, "ico") && strcmp(name
, "xpm") &&
421 strcmp(name
, "svg") && strcmp(name
, "webp") && strcmp(name
, "avif")) {
422 gdk_pixbuf_format_set_disabled(format
, TRUE
);
426 g_slist_free(pixbufFormats
);
428 int err
= pipe(mPipeFDs
);
429 if (err
) return NS_ERROR_OUT_OF_MEMORY
;
434 // make the pipe nonblocking
436 int flags
= fcntl(mPipeFDs
[0], F_GETFL
, 0);
437 if (flags
== -1) goto failed
;
438 err
= fcntl(mPipeFDs
[0], F_SETFL
, flags
| O_NONBLOCK
);
439 if (err
== -1) goto failed
;
440 flags
= fcntl(mPipeFDs
[1], F_GETFL
, 0);
441 if (flags
== -1) goto failed
;
442 err
= fcntl(mPipeFDs
[1], F_SETFL
, flags
| O_NONBLOCK
);
443 if (err
== -1) goto failed
;
445 ioc
= g_io_channel_unix_new(mPipeFDs
[0]);
446 source
= g_io_create_watch(ioc
, G_IO_IN
);
447 g_io_channel_unref(ioc
);
448 g_source_set_callback(source
, (GSourceFunc
)EventProcessorCallback
, this,
450 g_source_set_can_recurse(source
, TRUE
);
451 mTag
= g_source_attach(source
, nullptr);
452 g_source_unref(source
);
456 return nsBaseAppShell::Init();
460 mPipeFDs
[0] = mPipeFDs
[1] = 0;
461 return NS_ERROR_FAILURE
;
464 NS_IMETHODIMP
nsAppShell::Run() {
465 if (XRE_IsParentProcess()) {
466 AddScreenWakeLockListener();
469 nsresult rv
= nsBaseAppShell::Run();
471 if (XRE_IsParentProcess()) {
472 RemoveScreenWakeLockListener();
477 void nsAppShell::ScheduleNativeEventCallback() {
478 unsigned char buf
[] = {NOTIFY_TOKEN
};
479 Unused
<< write(mPipeFDs
[1], buf
, 1);
482 void nsAppShell::ScheduleQuitEvent() {
483 unsigned char buf
[] = {QUIT_TOKEN
};
484 Unused
<< write(mPipeFDs
[1], buf
, 1);
487 bool nsAppShell::ProcessNextNativeEvent(bool mayWait
) {
488 if (mSuspendNativeCount
) {
491 bool didProcessEvent
= g_main_context_iteration(nullptr, mayWait
);
492 return didProcessEvent
;