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");
56 LazyLogModule
gClipboardLog("WidgetClipboard");
58 static GPollFunc sPollFunc
;
60 nsAppShell
* sAppShell
= nullptr;
62 // Wrapper function to disable hang monitoring while waiting in poll().
63 static gint
PollWrapper(GPollFD
* aUfds
, guint aNfsd
, gint aTimeout
) {
65 // When the timeout is 0, there is no wait, so no point in notifying
66 // the BackgroundHangMonitor and the profiler.
67 return (*sPollFunc
)(aUfds
, aNfsd
, aTimeout
);
70 mozilla::BackgroundHangMonitor().NotifyWait();
73 gint timeout
= aTimeout
;
76 begin
= g_get_monotonic_time();
79 AUTO_PROFILER_LABEL("PollWrapper", IDLE
);
80 AUTO_PROFILER_THREAD_SLEEP
;
82 result
= (*sPollFunc
)(aUfds
, aNfsd
, timeout
);
84 // The result will be -1 with the EINTR error if the poll was interrupted
85 // by a signal, typically the signal sent by the profiler to sample the
86 // process. We are only done waiting if we are not in that case.
87 if (result
!= -1 || errno
!= EINTR
) {
92 // Adjust the timeout to account for the time already spent waiting.
93 gint elapsedSinceBegin
= (g_get_monotonic_time() - begin
) / 1000;
94 if (elapsedSinceBegin
< aTimeout
) {
95 timeout
= aTimeout
- elapsedSinceBegin
;
97 // poll returns 0 to indicate the call timed out before any fd
105 mozilla::BackgroundHangMonitor().NotifyActivity();
109 // Emit resume-events on GdkFrameClock if flush-events has not been
110 // balanced by resume-events at dispose.
111 // For https://bugzilla.gnome.org/show_bug.cgi?id=742636
112 static decltype(GObjectClass::constructed
) sRealGdkFrameClockConstructed
;
113 static decltype(GObjectClass::dispose
) sRealGdkFrameClockDispose
;
114 static GQuark sPendingResumeQuark
;
116 static void OnFlushEvents(GObject
* clock
, gpointer
) {
117 g_object_set_qdata(clock
, sPendingResumeQuark
, GUINT_TO_POINTER(1));
120 static void OnResumeEvents(GObject
* clock
, gpointer
) {
121 g_object_set_qdata(clock
, sPendingResumeQuark
, nullptr);
124 static void WrapGdkFrameClockConstructed(GObject
* object
) {
125 sRealGdkFrameClockConstructed(object
);
127 g_signal_connect(object
, "flush-events", G_CALLBACK(OnFlushEvents
), nullptr);
128 g_signal_connect(object
, "resume-events", G_CALLBACK(OnResumeEvents
),
132 static void WrapGdkFrameClockDispose(GObject
* object
) {
133 if (g_object_get_qdata(object
, sPendingResumeQuark
)) {
134 g_signal_emit_by_name(object
, "resume-events");
137 sRealGdkFrameClockDispose(object
);
141 gboolean
nsAppShell::EventProcessorCallback(GIOChannel
* source
,
142 GIOCondition condition
,
144 nsAppShell
* self
= static_cast<nsAppShell
*>(data
);
147 Unused
<< read(self
->mPipeFDs
[0], &c
, 1);
150 self
->NativeEventCallback();
156 NS_ASSERTION(false, "wrong token");
162 nsAppShell::~nsAppShell() {
165 #ifdef MOZ_ENABLE_DBUS
168 mozilla::hal::Shutdown();
170 if (mTag
) g_source_remove(mTag
);
171 if (mPipeFDs
[0]) close(mPipeFDs
[0]);
172 if (mPipeFDs
[1]) close(mPipeFDs
[1]);
175 mozilla::StaticRefPtr
<WakeLockListener
> sWakeLockListener
;
176 static void AddScreenWakeLockListener() {
177 nsCOMPtr
<nsIPowerManagerService
> powerManager
=
178 do_GetService(POWERMANAGERSERVICE_CONTRACTID
);
180 sWakeLockListener
= new WakeLockListener();
181 powerManager
->AddWakeLockListener(sWakeLockListener
);
184 "Failed to retrieve PowerManagerService, wakelocks will be broken!");
188 static void RemoveScreenWakeLockListener() {
189 nsCOMPtr
<nsIPowerManagerService
> powerManager
=
190 do_GetService(POWERMANAGERSERVICE_CONTRACTID
);
192 powerManager
->RemoveWakeLockListener(sWakeLockListener
);
193 sWakeLockListener
= nullptr;
197 #ifdef MOZ_ENABLE_DBUS
198 void nsAppShell::DBusSessionSleepCallback(GDBusProxy
* aProxy
,
201 GVariant
* aParameters
,
202 gpointer aUserData
) {
203 if (g_strcmp0(aSignalName
, "PrepareForSleep")) {
206 nsCOMPtr
<nsIObserverService
> observerService
=
207 mozilla::services::GetObserverService();
208 if (!observerService
) {
211 if (!g_variant_is_of_type(aParameters
, G_VARIANT_TYPE_TUPLE
) ||
212 g_variant_n_children(aParameters
) != 1) {
214 nsPrintfCString("Unexpected location updated signal params type: %s\n",
215 g_variant_get_type_string(aParameters
))
220 RefPtr
<GVariant
> variant
=
221 dont_AddRef(g_variant_get_child_value(aParameters
, 0));
222 if (!g_variant_is_of_type(variant
, G_VARIANT_TYPE_BOOLEAN
)) {
224 nsPrintfCString("Unexpected location updated signal params type: %s\n",
225 g_variant_get_type_string(aParameters
))
230 gboolean suspend
= g_variant_get_boolean(variant
);
232 // Post sleep_notification
233 observerService
->NotifyObservers(nullptr, NS_WIDGET_SLEEP_OBSERVER_TOPIC
,
236 // Post wake_notification
237 observerService
->NotifyObservers(nullptr, NS_WIDGET_WAKE_OBSERVER_TOPIC
,
242 void nsAppShell::DBusTimedatePropertiesChangedCallback(GDBusProxy
* aProxy
,
245 GVariant
* aParameters
,
246 gpointer aUserData
) {
247 if (g_strcmp0(aSignalName
, "PropertiesChanged")) {
250 nsBaseAppShell::OnSystemTimezoneChange();
253 void nsAppShell::DBusConnectClientResponse(GObject
* aObject
,
254 GAsyncResult
* aResult
,
255 gpointer aUserData
) {
256 GUniquePtr
<GError
> error
;
257 RefPtr
<GDBusProxy
> proxyClient
=
258 dont_AddRef(g_dbus_proxy_new_finish(aResult
, getter_Transfers(error
)));
260 if (!IsCancelledGError(error
.get())) {
262 nsPrintfCString("Failed to connect to client: %s\n", error
->message
)
268 RefPtr self
= static_cast<nsAppShell
*>(aUserData
);
269 if (!strcmp(g_dbus_proxy_get_name(proxyClient
), "org.freedesktop.login1")) {
270 self
->mLogin1Proxy
= std::move(proxyClient
);
271 g_signal_connect(self
->mLogin1Proxy
, "g-signal",
272 G_CALLBACK(DBusSessionSleepCallback
), self
);
274 self
->mTimedate1Proxy
= std::move(proxyClient
);
275 g_signal_connect(self
->mTimedate1Proxy
, "g-signal",
276 G_CALLBACK(DBusTimedatePropertiesChangedCallback
), self
);
281 // https://github.com/lcp/NetworkManager/blob/240f47c892b4e935a3e92fc09eb15163d1fa28d8/src/nm-sleep-monitor-systemd.c
282 // Use login1 to signal sleep and wake notifications.
283 void nsAppShell::StartDBusListening() {
284 MOZ_DIAGNOSTIC_ASSERT(!mLogin1Proxy
, "Already configured?");
285 MOZ_DIAGNOSTIC_ASSERT(!mTimedate1Proxy
, "Already configured?");
286 MOZ_DIAGNOSTIC_ASSERT(!mLogin1ProxyCancellable
, "Already configured?");
287 MOZ_DIAGNOSTIC_ASSERT(!mTimedate1ProxyCancellable
, "Already configured?");
289 mLogin1ProxyCancellable
= dont_AddRef(g_cancellable_new());
290 mTimedate1ProxyCancellable
= dont_AddRef(g_cancellable_new());
292 g_dbus_proxy_new_for_bus(
293 G_BUS_TYPE_SYSTEM
, G_DBUS_PROXY_FLAGS_NONE
, nullptr,
294 "org.freedesktop.login1", "/org/freedesktop/login1",
295 "org.freedesktop.login1.Manager", mLogin1ProxyCancellable
,
296 reinterpret_cast<GAsyncReadyCallback
>(DBusConnectClientResponse
), this);
298 g_dbus_proxy_new_for_bus(
299 G_BUS_TYPE_SYSTEM
, G_DBUS_PROXY_FLAGS_NONE
, nullptr,
300 "org.freedesktop.timedate1", "/org/freedesktop/timedate1",
301 "org.freedesktop.DBus.Properties", mTimedate1ProxyCancellable
,
302 reinterpret_cast<GAsyncReadyCallback
>(DBusConnectClientResponse
), this);
305 void nsAppShell::StopDBusListening() {
307 g_signal_handlers_disconnect_matched(mLogin1Proxy
, G_SIGNAL_MATCH_DATA
, 0,
308 0, nullptr, nullptr, this);
310 if (mLogin1ProxyCancellable
) {
311 g_cancellable_cancel(mLogin1ProxyCancellable
);
312 mLogin1ProxyCancellable
= nullptr;
314 mLogin1Proxy
= nullptr;
316 if (mTimedate1Proxy
) {
317 g_signal_handlers_disconnect_matched(mTimedate1Proxy
, G_SIGNAL_MATCH_DATA
,
318 0, 0, nullptr, nullptr, this);
320 if (mTimedate1ProxyCancellable
) {
321 g_cancellable_cancel(mTimedate1ProxyCancellable
);
322 mTimedate1ProxyCancellable
= nullptr;
324 mTimedate1Proxy
= nullptr;
328 void nsAppShell::TermSignalHandler(int signo
) {
329 if (signo
!= SIGTERM
) {
330 NS_WARNING("Wrong signal!");
333 sAppShell
->ScheduleQuitEvent();
336 void nsAppShell::InstallTermSignalHandler() {
337 if (!XRE_IsParentProcess() || PR_GetEnv("MOZ_DISABLE_SIG_HANDLER") ||
342 struct sigaction act
= {}, oldact
;
343 act
.sa_handler
= TermSignalHandler
;
344 sigfillset(&act
.sa_mask
);
346 if (NS_WARN_IF(sigaction(SIGTERM
, nullptr, &oldact
) != 0)) {
349 if (oldact
.sa_handler
!= SIG_DFL
) {
350 NS_WARNING("SIGTERM signal handler is already set?");
353 sigaction(SIGTERM
, &act
, nullptr);
356 nsresult
nsAppShell::Init() {
357 mozilla::hal::Init();
359 #ifdef MOZ_ENABLE_DBUS
360 if (XRE_IsParentProcess()) {
361 StartDBusListening();
366 sPollFunc
= g_main_context_get_poll_func(nullptr);
367 g_main_context_set_poll_func(nullptr, &PollWrapper
);
370 if (XRE_IsParentProcess()) {
371 ScreenManager
& screenManager
= ScreenManager::GetSingleton();
372 if (gfxPlatform::IsHeadless()) {
373 screenManager
.SetHelper(mozilla::MakeUnique
<HeadlessScreenHelper
>());
375 screenManager
.SetHelper(mozilla::MakeUnique
<ScreenHelperGTK
>());
378 if (gtk_check_version(3, 16, 3) == nullptr) {
379 // Before 3.16.3, GDK cannot override classname by --class command line
380 // option when program uses gdk_set_program_class().
382 // See https://bugzilla.gnome.org/show_bug.cgi?id=747634
384 // Only bother doing this for the parent process, since it's the one
385 // creating top-level windows.
387 gdk_set_program_class(gAppData
->remotingName
);
392 if (!sPendingResumeQuark
&&
393 gtk_check_version(3, 14, 7) != nullptr) { // GTK 3.0 to GTK 3.14.7.
394 // GTK 3.8 - 3.14 registered this type when creating the frame clock
395 // for the root window of the display when the display was opened.
396 GType gdkFrameClockIdleType
= g_type_from_name("GdkFrameClockIdle");
397 if (gdkFrameClockIdleType
) { // not in versions prior to 3.8
398 sPendingResumeQuark
= g_quark_from_string("moz-resume-is-pending");
399 auto gdk_frame_clock_idle_class
=
400 G_OBJECT_CLASS(g_type_class_peek_static(gdkFrameClockIdleType
));
401 auto constructed
= &gdk_frame_clock_idle_class
->constructed
;
402 sRealGdkFrameClockConstructed
= *constructed
;
403 *constructed
= WrapGdkFrameClockConstructed
;
404 auto dispose
= &gdk_frame_clock_idle_class
->dispose
;
405 sRealGdkFrameClockDispose
= *dispose
;
406 *dispose
= WrapGdkFrameClockDispose
;
410 // Workaround for bug 1209659 which is fixed by Gtk3.20
411 if (gtk_check_version(3, 20, 0) != nullptr) {
415 // Whitelist of only common, stable formats - see bugs 1197059 and 1203078
416 GSList
* pixbufFormats
= gdk_pixbuf_get_formats();
417 for (GSList
* iter
= pixbufFormats
; iter
; iter
= iter
->next
) {
418 GdkPixbufFormat
* format
= static_cast<GdkPixbufFormat
*>(iter
->data
);
419 gchar
* name
= gdk_pixbuf_format_get_name(format
);
420 if (strcmp(name
, "jpeg") && strcmp(name
, "png") && strcmp(name
, "gif") &&
421 strcmp(name
, "bmp") && strcmp(name
, "ico") && strcmp(name
, "xpm") &&
422 strcmp(name
, "svg") && strcmp(name
, "webp") && strcmp(name
, "avif")) {
423 gdk_pixbuf_format_set_disabled(format
, TRUE
);
427 g_slist_free(pixbufFormats
);
429 int err
= pipe(mPipeFDs
);
430 if (err
) return NS_ERROR_OUT_OF_MEMORY
;
435 // make the pipe nonblocking
437 int flags
= fcntl(mPipeFDs
[0], F_GETFL
, 0);
438 if (flags
== -1) goto failed
;
439 err
= fcntl(mPipeFDs
[0], F_SETFL
, flags
| O_NONBLOCK
);
440 if (err
== -1) goto failed
;
441 flags
= fcntl(mPipeFDs
[1], F_GETFL
, 0);
442 if (flags
== -1) goto failed
;
443 err
= fcntl(mPipeFDs
[1], F_SETFL
, flags
| O_NONBLOCK
);
444 if (err
== -1) goto failed
;
446 ioc
= g_io_channel_unix_new(mPipeFDs
[0]);
447 source
= g_io_create_watch(ioc
, G_IO_IN
);
448 g_io_channel_unref(ioc
);
449 g_source_set_callback(source
, (GSourceFunc
)EventProcessorCallback
, this,
451 g_source_set_can_recurse(source
, TRUE
);
452 mTag
= g_source_attach(source
, nullptr);
453 g_source_unref(source
);
457 return nsBaseAppShell::Init();
461 mPipeFDs
[0] = mPipeFDs
[1] = 0;
462 return NS_ERROR_FAILURE
;
465 NS_IMETHODIMP
nsAppShell::Run() {
466 if (XRE_IsParentProcess()) {
467 AddScreenWakeLockListener();
470 nsresult rv
= nsBaseAppShell::Run();
472 if (XRE_IsParentProcess()) {
473 RemoveScreenWakeLockListener();
478 void nsAppShell::ScheduleNativeEventCallback() {
479 unsigned char buf
[] = {NOTIFY_TOKEN
};
480 Unused
<< write(mPipeFDs
[1], buf
, 1);
483 void nsAppShell::ScheduleQuitEvent() {
484 unsigned char buf
[] = {QUIT_TOKEN
};
485 Unused
<< write(mPipeFDs
[1], buf
, 1);
488 bool nsAppShell::ProcessNextNativeEvent(bool mayWait
) {
489 if (mSuspendNativeCount
) {
492 bool didProcessEvent
= g_main_context_iteration(nullptr, mayWait
);
493 return didProcessEvent
;