Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / widget / gtk / nsAppShell.cpp
blobd26b43737d033ee7c3c7fe3c8dcd953db212aa1a
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/. */
7 #include <sys/types.h>
8 #include <unistd.h>
9 #include <fcntl.h>
10 #include <errno.h>
11 #include <gdk/gdk.h>
12 #include "nsAppShell.h"
13 #include "nsBaseAppShell.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/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 <gio/gio.h>
27 # include "nsIObserverService.h"
28 # include "WidgetUtilsGtk.h"
29 #endif
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"
37 #ifdef MOZ_WAYLAND
38 # include "nsWaylandDisplay.h"
39 #endif
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) {
64 if (aTimeout == 0) {
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();
71 gint result;
73 gint timeout = aTimeout;
74 gint64 begin = 0;
75 if (aTimeout != -1) {
76 begin = g_get_monotonic_time();
79 AUTO_PROFILER_LABEL("PollWrapper", IDLE);
80 AUTO_PROFILER_THREAD_SLEEP;
81 do {
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) {
88 break;
91 if (aTimeout != -1) {
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;
96 } else {
97 // poll returns 0 to indicate the call timed out before any fd
98 // became ready.
99 result = 0;
100 break;
103 } while (true);
105 mozilla::BackgroundHangMonitor().NotifyActivity();
106 return result;
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),
129 nullptr);
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);
140 /*static*/
141 gboolean nsAppShell::EventProcessorCallback(GIOChannel* source,
142 GIOCondition condition,
143 gpointer data) {
144 nsAppShell* self = static_cast<nsAppShell*>(data);
146 unsigned char c;
147 Unused << read(self->mPipeFDs[0], &c, 1);
148 switch (c) {
149 case NOTIFY_TOKEN:
150 self->NativeEventCallback();
151 break;
152 case QUIT_TOKEN:
153 self->Exit();
154 break;
155 default:
156 NS_ASSERTION(false, "wrong token");
157 break;
159 return TRUE;
162 nsAppShell::~nsAppShell() {
163 sAppShell = nullptr;
165 #ifdef MOZ_ENABLE_DBUS
166 StopDBusListening();
167 #endif
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);
179 if (powerManager) {
180 sWakeLockListener = new WakeLockListener();
181 powerManager->AddWakeLockListener(sWakeLockListener);
182 } else {
183 NS_WARNING(
184 "Failed to retrieve PowerManagerService, wakelocks will be broken!");
188 static void RemoveScreenWakeLockListener() {
189 nsCOMPtr<nsIPowerManagerService> powerManager =
190 do_GetService(POWERMANAGERSERVICE_CONTRACTID);
191 if (powerManager) {
192 powerManager->RemoveWakeLockListener(sWakeLockListener);
193 sWakeLockListener = nullptr;
197 #ifdef MOZ_ENABLE_DBUS
198 void nsAppShell::DBusSessionSleepCallback(GDBusProxy* aProxy,
199 gchar* aSenderName,
200 gchar* aSignalName,
201 GVariant* aParameters,
202 gpointer aUserData) {
203 if (g_strcmp0(aSignalName, "PrepareForSleep")) {
204 return;
206 nsCOMPtr<nsIObserverService> observerService =
207 mozilla::services::GetObserverService();
208 if (!observerService) {
209 return;
211 if (!g_variant_is_of_type(aParameters, G_VARIANT_TYPE_TUPLE) ||
212 g_variant_n_children(aParameters) != 1) {
213 NS_WARNING(
214 nsPrintfCString("Unexpected location updated signal params type: %s\n",
215 g_variant_get_type_string(aParameters))
216 .get());
217 return;
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)) {
223 NS_WARNING(
224 nsPrintfCString("Unexpected location updated signal params type: %s\n",
225 g_variant_get_type_string(aParameters))
226 .get());
227 return;
230 gboolean suspend = g_variant_get_boolean(variant);
231 if (suspend) {
232 // Post sleep_notification
233 observerService->NotifyObservers(nullptr, NS_WIDGET_SLEEP_OBSERVER_TOPIC,
234 nullptr);
235 } else {
236 // Post wake_notification
237 observerService->NotifyObservers(nullptr, NS_WIDGET_WAKE_OBSERVER_TOPIC,
238 nullptr);
242 void nsAppShell::DBusTimedatePropertiesChangedCallback(GDBusProxy* aProxy,
243 gchar* aSenderName,
244 gchar* aSignalName,
245 GVariant* aParameters,
246 gpointer aUserData) {
247 if (g_strcmp0(aSignalName, "PropertiesChanged")) {
248 return;
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)));
259 if (!proxyClient) {
260 if (!IsCancelledGError(error.get())) {
261 NS_WARNING(
262 nsPrintfCString("Failed to connect to client: %s\n", error->message)
263 .get());
265 return;
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);
273 } else {
274 self->mTimedate1Proxy = std::move(proxyClient);
275 g_signal_connect(self->mTimedate1Proxy, "g-signal",
276 G_CALLBACK(DBusTimedatePropertiesChangedCallback), self);
280 // Based on
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() {
306 if (mLogin1Proxy) {
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;
326 #endif
328 void nsAppShell::TermSignalHandler(int signo) {
329 if (signo != SIGTERM) {
330 NS_WARNING("Wrong signal!");
331 return;
333 sAppShell->ScheduleQuitEvent();
336 void nsAppShell::InstallTermSignalHandler() {
337 if (!XRE_IsParentProcess() || PR_GetEnv("MOZ_DISABLE_SIG_HANDLER") ||
338 !sAppShell) {
339 return;
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)) {
347 return;
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();
363 #endif
365 if (!sPollFunc) {
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>());
374 } else {
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.
386 if (gAppData) {
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) {
412 unsetenv("GTK_CSD");
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);
425 g_free(name);
427 g_slist_free(pixbufFormats);
429 int err = pipe(mPipeFDs);
430 if (err) return NS_ERROR_OUT_OF_MEMORY;
432 GIOChannel* ioc;
433 GSource* source;
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,
450 nullptr);
451 g_source_set_can_recurse(source, TRUE);
452 mTag = g_source_attach(source, nullptr);
453 g_source_unref(source);
455 sAppShell = this;
457 return nsBaseAppShell::Init();
458 failed:
459 close(mPipeFDs[0]);
460 close(mPipeFDs[1]);
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();
475 return rv;
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) {
490 return false;
492 bool didProcessEvent = g_main_context_iteration(nullptr, mayWait);
493 return didProcessEvent;