Bug 1795082 - Part 2/2: Drop post-processing from getURL() r=zombie
[gecko.git] / widget / gtk / nsAppShell.cpp
blob652801fe696f1291251cd51d1cfaad1d3266f823
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");
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) {
63 if (aTimeout == 0) {
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();
70 gint result;
72 gint timeout = aTimeout;
73 gint64 begin = 0;
74 if (aTimeout != -1) {
75 begin = g_get_monotonic_time();
78 AUTO_PROFILER_LABEL("PollWrapper", IDLE);
79 AUTO_PROFILER_THREAD_SLEEP;
80 do {
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) {
87 break;
90 if (aTimeout != -1) {
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;
95 } else {
96 // poll returns 0 to indicate the call timed out before any fd
97 // became ready.
98 result = 0;
99 break;
102 } while (true);
104 mozilla::BackgroundHangMonitor().NotifyActivity();
105 return result;
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),
128 nullptr);
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);
139 /*static*/
140 gboolean nsAppShell::EventProcessorCallback(GIOChannel* source,
141 GIOCondition condition,
142 gpointer data) {
143 nsAppShell* self = static_cast<nsAppShell*>(data);
145 unsigned char c;
146 Unused << read(self->mPipeFDs[0], &c, 1);
147 switch (c) {
148 case NOTIFY_TOKEN:
149 self->NativeEventCallback();
150 break;
151 case QUIT_TOKEN:
152 self->Exit();
153 break;
154 default:
155 NS_ASSERTION(false, "wrong token");
156 break;
158 return TRUE;
161 nsAppShell::~nsAppShell() {
162 sAppShell = nullptr;
164 #ifdef MOZ_ENABLE_DBUS
165 StopDBusListening();
166 #endif
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);
178 if (powerManager) {
179 sWakeLockListener = new WakeLockListener();
180 powerManager->AddWakeLockListener(sWakeLockListener);
181 } else {
182 NS_WARNING(
183 "Failed to retrieve PowerManagerService, wakelocks will be broken!");
187 static void RemoveScreenWakeLockListener() {
188 nsCOMPtr<nsIPowerManagerService> powerManager =
189 do_GetService(POWERMANAGERSERVICE_CONTRACTID);
190 if (powerManager) {
191 powerManager->RemoveWakeLockListener(sWakeLockListener);
192 sWakeLockListener = nullptr;
196 #ifdef MOZ_ENABLE_DBUS
197 void nsAppShell::DBusSessionSleepCallback(GDBusProxy* aProxy,
198 gchar* aSenderName,
199 gchar* aSignalName,
200 GVariant* aParameters,
201 gpointer aUserData) {
202 if (g_strcmp0(aSignalName, "PrepareForSleep")) {
203 return;
205 nsCOMPtr<nsIObserverService> observerService =
206 mozilla::services::GetObserverService();
207 if (!observerService) {
208 return;
210 if (!g_variant_is_of_type(aParameters, G_VARIANT_TYPE_TUPLE) ||
211 g_variant_n_children(aParameters) != 1) {
212 NS_WARNING(
213 nsPrintfCString("Unexpected location updated signal params type: %s\n",
214 g_variant_get_type_string(aParameters))
215 .get());
216 return;
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)) {
222 NS_WARNING(
223 nsPrintfCString("Unexpected location updated signal params type: %s\n",
224 g_variant_get_type_string(aParameters))
225 .get());
226 return;
229 gboolean suspend = g_variant_get_boolean(variant);
230 if (suspend) {
231 // Post sleep_notification
232 observerService->NotifyObservers(nullptr, NS_WIDGET_SLEEP_OBSERVER_TOPIC,
233 nullptr);
234 } else {
235 // Post wake_notification
236 observerService->NotifyObservers(nullptr, NS_WIDGET_WAKE_OBSERVER_TOPIC,
237 nullptr);
241 void nsAppShell::DBusTimedatePropertiesChangedCallback(GDBusProxy* aProxy,
242 gchar* aSenderName,
243 gchar* aSignalName,
244 GVariant* aParameters,
245 gpointer aUserData) {
246 if (g_strcmp0(aSignalName, "PropertiesChanged")) {
247 return;
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)));
258 if (!proxyClient) {
259 if (!IsCancelledGError(error.get())) {
260 NS_WARNING(
261 nsPrintfCString("Failed to connect to client: %s\n", error->message)
262 .get());
264 return;
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);
272 } else {
273 self->mTimedate1Proxy = std::move(proxyClient);
274 g_signal_connect(self->mTimedate1Proxy, "g-signal",
275 G_CALLBACK(DBusTimedatePropertiesChangedCallback), self);
279 // Based on
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() {
305 if (mLogin1Proxy) {
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;
325 #endif
327 void nsAppShell::TermSignalHandler(int signo) {
328 if (signo != SIGTERM) {
329 NS_WARNING("Wrong signal!");
330 return;
332 sAppShell->ScheduleQuitEvent();
335 void nsAppShell::InstallTermSignalHandler() {
336 if (!XRE_IsParentProcess() || PR_GetEnv("MOZ_DISABLE_SIG_HANDLER") ||
337 !sAppShell) {
338 return;
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)) {
346 return;
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();
362 #endif
364 if (!sPollFunc) {
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>());
373 } else {
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.
385 if (gAppData) {
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) {
411 unsetenv("GTK_CSD");
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);
424 g_free(name);
426 g_slist_free(pixbufFormats);
428 int err = pipe(mPipeFDs);
429 if (err) return NS_ERROR_OUT_OF_MEMORY;
431 GIOChannel* ioc;
432 GSource* source;
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,
449 nullptr);
450 g_source_set_can_recurse(source, TRUE);
451 mTag = g_source_attach(source, nullptr);
452 g_source_unref(source);
454 sAppShell = this;
456 return nsBaseAppShell::Init();
457 failed:
458 close(mPipeFDs[0]);
459 close(mPipeFDs[1]);
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();
474 return rv;
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) {
489 return false;
491 bool didProcessEvent = g_main_context_iteration(nullptr, mayWait);
492 return didProcessEvent;