Backed out changeset 795745f450df (bug 1934497) Backed out changeset 6aff7ffeaff4 (bug 1934497) Backed out changeset e6917a8311f2 (bug 1934497) Backed out changeset 2bf4f54a3077 (bug 1934497) Backed out changeset 87613cc32aa4 (bug 1934497) Backed out changeset eea0eef40abf (bug 1934497) Backed out changeset a9a38191e649 (bug 1934497) Backed out changeset 5343854a37e5 (bug 1934497) Backed out changeset 6806bbf97c86 (bug 1934497) Backed out changeset ff8a92afd360 (bug 1934497) Backed out changeset dcef70581e34 (bug 1934497) Backed out changeset c0512daa9188 (bug 1934497) Backed out changeset 693a7c400778 (bug 1934497) Backed out changeset 75d095afa9cf (bug 1934497) Backed out changeset 191397418056 (bug 1934497) Backed out changeset 5ba05279a465 (bug 1934497)
494 lines
16 KiB
C++
494 lines
16 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <gdk/gdk.h>
|
|
#include "nsAppShell.h"
|
|
#include "nsBaseAppShell.h"
|
|
#include "nsWindow.h"
|
|
#include "mozilla/Logging.h"
|
|
#include "prenv.h"
|
|
#include "mozilla/BackgroundHangMonitor.h"
|
|
#include "mozilla/Hal.h"
|
|
#include "mozilla/ProfilerLabels.h"
|
|
#include "mozilla/ProfilerThreadSleep.h"
|
|
#include "mozilla/Unused.h"
|
|
#include "mozilla/GUniquePtr.h"
|
|
#include "mozilla/WidgetUtils.h"
|
|
#include "nsIPowerManagerService.h"
|
|
#ifdef MOZ_ENABLE_DBUS
|
|
# include <gio/gio.h>
|
|
# include "nsIObserverService.h"
|
|
# include "WidgetUtilsGtk.h"
|
|
#endif
|
|
#include "WakeLockListener.h"
|
|
#include "gfxPlatform.h"
|
|
#include "nsAppRunner.h"
|
|
#include "mozilla/XREAppData.h"
|
|
#include "ScreenHelperGTK.h"
|
|
#include "HeadlessScreenHelper.h"
|
|
#include "mozilla/widget/ScreenManager.h"
|
|
#ifdef MOZ_WAYLAND
|
|
# include "nsWaylandDisplay.h"
|
|
#endif
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::widget;
|
|
using mozilla::widget::HeadlessScreenHelper;
|
|
using mozilla::widget::ScreenHelperGTK;
|
|
using mozilla::widget::ScreenManager;
|
|
|
|
#define NOTIFY_TOKEN 0xFA
|
|
#define QUIT_TOKEN 0xFB
|
|
|
|
LazyLogModule gWidgetLog("Widget");
|
|
LazyLogModule gWidgetDragLog("WidgetDrag");
|
|
LazyLogModule gWidgetWaylandLog("WidgetWayland");
|
|
LazyLogModule gWidgetPopupLog("WidgetPopup");
|
|
LazyLogModule gWidgetVsync("WidgetVsync");
|
|
LazyLogModule gDmabufLog("Dmabuf");
|
|
|
|
static GPollFunc sPollFunc;
|
|
|
|
nsAppShell* sAppShell = nullptr;
|
|
|
|
// Wrapper function to disable hang monitoring while waiting in poll().
|
|
static gint PollWrapper(GPollFD* aUfds, guint aNfsd, gint aTimeout) {
|
|
if (aTimeout == 0) {
|
|
// When the timeout is 0, there is no wait, so no point in notifying
|
|
// the BackgroundHangMonitor and the profiler.
|
|
return (*sPollFunc)(aUfds, aNfsd, aTimeout);
|
|
}
|
|
|
|
mozilla::BackgroundHangMonitor().NotifyWait();
|
|
gint result;
|
|
{
|
|
gint timeout = aTimeout;
|
|
gint64 begin = 0;
|
|
if (aTimeout != -1) {
|
|
begin = g_get_monotonic_time();
|
|
}
|
|
|
|
AUTO_PROFILER_LABEL("PollWrapper", IDLE);
|
|
AUTO_PROFILER_THREAD_SLEEP;
|
|
do {
|
|
result = (*sPollFunc)(aUfds, aNfsd, timeout);
|
|
|
|
// The result will be -1 with the EINTR error if the poll was interrupted
|
|
// by a signal, typically the signal sent by the profiler to sample the
|
|
// process. We are only done waiting if we are not in that case.
|
|
if (result != -1 || errno != EINTR) {
|
|
break;
|
|
}
|
|
|
|
if (aTimeout != -1) {
|
|
// Adjust the timeout to account for the time already spent waiting.
|
|
gint elapsedSinceBegin = (g_get_monotonic_time() - begin) / 1000;
|
|
if (elapsedSinceBegin < aTimeout) {
|
|
timeout = aTimeout - elapsedSinceBegin;
|
|
} else {
|
|
// poll returns 0 to indicate the call timed out before any fd
|
|
// became ready.
|
|
result = 0;
|
|
break;
|
|
}
|
|
}
|
|
} while (true);
|
|
}
|
|
mozilla::BackgroundHangMonitor().NotifyActivity();
|
|
return result;
|
|
}
|
|
|
|
// Emit resume-events on GdkFrameClock if flush-events has not been
|
|
// balanced by resume-events at dispose.
|
|
// For https://bugzilla.gnome.org/show_bug.cgi?id=742636
|
|
static decltype(GObjectClass::constructed) sRealGdkFrameClockConstructed;
|
|
static decltype(GObjectClass::dispose) sRealGdkFrameClockDispose;
|
|
static GQuark sPendingResumeQuark;
|
|
|
|
static void OnFlushEvents(GObject* clock, gpointer) {
|
|
g_object_set_qdata(clock, sPendingResumeQuark, GUINT_TO_POINTER(1));
|
|
}
|
|
|
|
static void OnResumeEvents(GObject* clock, gpointer) {
|
|
g_object_set_qdata(clock, sPendingResumeQuark, nullptr);
|
|
}
|
|
|
|
static void WrapGdkFrameClockConstructed(GObject* object) {
|
|
sRealGdkFrameClockConstructed(object);
|
|
|
|
g_signal_connect(object, "flush-events", G_CALLBACK(OnFlushEvents), nullptr);
|
|
g_signal_connect(object, "resume-events", G_CALLBACK(OnResumeEvents),
|
|
nullptr);
|
|
}
|
|
|
|
static void WrapGdkFrameClockDispose(GObject* object) {
|
|
if (g_object_get_qdata(object, sPendingResumeQuark)) {
|
|
g_signal_emit_by_name(object, "resume-events");
|
|
}
|
|
|
|
sRealGdkFrameClockDispose(object);
|
|
}
|
|
|
|
/*static*/
|
|
gboolean nsAppShell::EventProcessorCallback(GIOChannel* source,
|
|
GIOCondition condition,
|
|
gpointer data) {
|
|
nsAppShell* self = static_cast<nsAppShell*>(data);
|
|
|
|
unsigned char c;
|
|
Unused << read(self->mPipeFDs[0], &c, 1);
|
|
switch (c) {
|
|
case NOTIFY_TOKEN:
|
|
self->NativeEventCallback();
|
|
break;
|
|
case QUIT_TOKEN:
|
|
self->Exit();
|
|
break;
|
|
default:
|
|
NS_ASSERTION(false, "wrong token");
|
|
break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
nsAppShell::~nsAppShell() {
|
|
sAppShell = nullptr;
|
|
|
|
#ifdef MOZ_ENABLE_DBUS
|
|
StopDBusListening();
|
|
#endif
|
|
mozilla::hal::Shutdown();
|
|
|
|
if (mTag) g_source_remove(mTag);
|
|
if (mPipeFDs[0]) close(mPipeFDs[0]);
|
|
if (mPipeFDs[1]) close(mPipeFDs[1]);
|
|
}
|
|
|
|
mozilla::StaticRefPtr<WakeLockListener> sWakeLockListener;
|
|
static void AddScreenWakeLockListener() {
|
|
nsCOMPtr<nsIPowerManagerService> powerManager =
|
|
do_GetService(POWERMANAGERSERVICE_CONTRACTID);
|
|
if (powerManager) {
|
|
sWakeLockListener = new WakeLockListener();
|
|
powerManager->AddWakeLockListener(sWakeLockListener);
|
|
} else {
|
|
NS_WARNING(
|
|
"Failed to retrieve PowerManagerService, wakelocks will be broken!");
|
|
}
|
|
}
|
|
|
|
static void RemoveScreenWakeLockListener() {
|
|
nsCOMPtr<nsIPowerManagerService> powerManager =
|
|
do_GetService(POWERMANAGERSERVICE_CONTRACTID);
|
|
if (powerManager) {
|
|
powerManager->RemoveWakeLockListener(sWakeLockListener);
|
|
sWakeLockListener = nullptr;
|
|
}
|
|
}
|
|
|
|
#ifdef MOZ_ENABLE_DBUS
|
|
void nsAppShell::DBusSessionSleepCallback(GDBusProxy* aProxy,
|
|
gchar* aSenderName,
|
|
gchar* aSignalName,
|
|
GVariant* aParameters,
|
|
gpointer aUserData) {
|
|
if (g_strcmp0(aSignalName, "PrepareForSleep")) {
|
|
return;
|
|
}
|
|
nsCOMPtr<nsIObserverService> observerService =
|
|
mozilla::services::GetObserverService();
|
|
if (!observerService) {
|
|
return;
|
|
}
|
|
if (!g_variant_is_of_type(aParameters, G_VARIANT_TYPE_TUPLE) ||
|
|
g_variant_n_children(aParameters) != 1) {
|
|
NS_WARNING(
|
|
nsPrintfCString("Unexpected location updated signal params type: %s\n",
|
|
g_variant_get_type_string(aParameters))
|
|
.get());
|
|
return;
|
|
}
|
|
|
|
RefPtr<GVariant> variant =
|
|
dont_AddRef(g_variant_get_child_value(aParameters, 0));
|
|
if (!g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN)) {
|
|
NS_WARNING(
|
|
nsPrintfCString("Unexpected location updated signal params type: %s\n",
|
|
g_variant_get_type_string(aParameters))
|
|
.get());
|
|
return;
|
|
}
|
|
|
|
gboolean suspend = g_variant_get_boolean(variant);
|
|
if (suspend) {
|
|
// Post sleep_notification
|
|
observerService->NotifyObservers(nullptr, NS_WIDGET_SLEEP_OBSERVER_TOPIC,
|
|
nullptr);
|
|
} else {
|
|
// Post wake_notification
|
|
observerService->NotifyObservers(nullptr, NS_WIDGET_WAKE_OBSERVER_TOPIC,
|
|
nullptr);
|
|
}
|
|
}
|
|
|
|
void nsAppShell::DBusTimedatePropertiesChangedCallback(GDBusProxy* aProxy,
|
|
gchar* aSenderName,
|
|
gchar* aSignalName,
|
|
GVariant* aParameters,
|
|
gpointer aUserData) {
|
|
if (g_strcmp0(aSignalName, "PropertiesChanged")) {
|
|
return;
|
|
}
|
|
nsBaseAppShell::OnSystemTimezoneChange();
|
|
}
|
|
|
|
void nsAppShell::DBusConnectClientResponse(GObject* aObject,
|
|
GAsyncResult* aResult,
|
|
gpointer aUserData) {
|
|
GUniquePtr<GError> error;
|
|
RefPtr<GDBusProxy> proxyClient =
|
|
dont_AddRef(g_dbus_proxy_new_finish(aResult, getter_Transfers(error)));
|
|
if (!proxyClient) {
|
|
if (!IsCancelledGError(error.get())) {
|
|
NS_WARNING(
|
|
nsPrintfCString("Failed to connect to client: %s\n", error->message)
|
|
.get());
|
|
}
|
|
return;
|
|
}
|
|
|
|
RefPtr self = static_cast<nsAppShell*>(aUserData);
|
|
if (!strcmp(g_dbus_proxy_get_name(proxyClient), "org.freedesktop.login1")) {
|
|
self->mLogin1Proxy = std::move(proxyClient);
|
|
g_signal_connect(self->mLogin1Proxy, "g-signal",
|
|
G_CALLBACK(DBusSessionSleepCallback), self);
|
|
} else {
|
|
self->mTimedate1Proxy = std::move(proxyClient);
|
|
g_signal_connect(self->mTimedate1Proxy, "g-signal",
|
|
G_CALLBACK(DBusTimedatePropertiesChangedCallback), self);
|
|
}
|
|
}
|
|
|
|
// Based on
|
|
// https://github.com/lcp/NetworkManager/blob/240f47c892b4e935a3e92fc09eb15163d1fa28d8/src/nm-sleep-monitor-systemd.c
|
|
// Use login1 to signal sleep and wake notifications.
|
|
void nsAppShell::StartDBusListening() {
|
|
MOZ_DIAGNOSTIC_ASSERT(!mLogin1Proxy, "Already configured?");
|
|
MOZ_DIAGNOSTIC_ASSERT(!mTimedate1Proxy, "Already configured?");
|
|
MOZ_DIAGNOSTIC_ASSERT(!mLogin1ProxyCancellable, "Already configured?");
|
|
MOZ_DIAGNOSTIC_ASSERT(!mTimedate1ProxyCancellable, "Already configured?");
|
|
|
|
mLogin1ProxyCancellable = dont_AddRef(g_cancellable_new());
|
|
mTimedate1ProxyCancellable = dont_AddRef(g_cancellable_new());
|
|
|
|
g_dbus_proxy_new_for_bus(
|
|
G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr,
|
|
"org.freedesktop.login1", "/org/freedesktop/login1",
|
|
"org.freedesktop.login1.Manager", mLogin1ProxyCancellable,
|
|
reinterpret_cast<GAsyncReadyCallback>(DBusConnectClientResponse), this);
|
|
|
|
g_dbus_proxy_new_for_bus(
|
|
G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr,
|
|
"org.freedesktop.timedate1", "/org/freedesktop/timedate1",
|
|
"org.freedesktop.DBus.Properties", mTimedate1ProxyCancellable,
|
|
reinterpret_cast<GAsyncReadyCallback>(DBusConnectClientResponse), this);
|
|
}
|
|
|
|
void nsAppShell::StopDBusListening() {
|
|
if (mLogin1Proxy) {
|
|
g_signal_handlers_disconnect_matched(mLogin1Proxy, G_SIGNAL_MATCH_DATA, 0,
|
|
0, nullptr, nullptr, this);
|
|
}
|
|
if (mLogin1ProxyCancellable) {
|
|
g_cancellable_cancel(mLogin1ProxyCancellable);
|
|
mLogin1ProxyCancellable = nullptr;
|
|
}
|
|
mLogin1Proxy = nullptr;
|
|
|
|
if (mTimedate1Proxy) {
|
|
g_signal_handlers_disconnect_matched(mTimedate1Proxy, G_SIGNAL_MATCH_DATA,
|
|
0, 0, nullptr, nullptr, this);
|
|
}
|
|
if (mTimedate1ProxyCancellable) {
|
|
g_cancellable_cancel(mTimedate1ProxyCancellable);
|
|
mTimedate1ProxyCancellable = nullptr;
|
|
}
|
|
mTimedate1Proxy = nullptr;
|
|
}
|
|
#endif
|
|
|
|
void nsAppShell::TermSignalHandler(int signo) {
|
|
if (signo != SIGTERM) {
|
|
NS_WARNING("Wrong signal!");
|
|
return;
|
|
}
|
|
sAppShell->ScheduleQuitEvent();
|
|
}
|
|
|
|
void nsAppShell::InstallTermSignalHandler() {
|
|
if (!XRE_IsParentProcess() || PR_GetEnv("MOZ_DISABLE_SIG_HANDLER") ||
|
|
!sAppShell) {
|
|
return;
|
|
}
|
|
|
|
struct sigaction act = {}, oldact;
|
|
act.sa_handler = TermSignalHandler;
|
|
sigfillset(&act.sa_mask);
|
|
|
|
if (NS_WARN_IF(sigaction(SIGTERM, nullptr, &oldact) != 0)) {
|
|
return;
|
|
}
|
|
if (oldact.sa_handler != SIG_DFL) {
|
|
NS_WARNING("SIGTERM signal handler is already set?");
|
|
}
|
|
|
|
sigaction(SIGTERM, &act, nullptr);
|
|
}
|
|
|
|
nsresult nsAppShell::Init() {
|
|
mozilla::hal::Init();
|
|
|
|
#ifdef MOZ_ENABLE_DBUS
|
|
if (XRE_IsParentProcess()) {
|
|
StartDBusListening();
|
|
}
|
|
#endif
|
|
|
|
if (!sPollFunc) {
|
|
sPollFunc = g_main_context_get_poll_func(nullptr);
|
|
g_main_context_set_poll_func(nullptr, &PollWrapper);
|
|
}
|
|
|
|
if (XRE_IsParentProcess()) {
|
|
ScreenManager& screenManager = ScreenManager::GetSingleton();
|
|
if (gfxPlatform::IsHeadless()) {
|
|
screenManager.SetHelper(mozilla::MakeUnique<HeadlessScreenHelper>());
|
|
} else {
|
|
screenManager.SetHelper(mozilla::MakeUnique<ScreenHelperGTK>());
|
|
}
|
|
|
|
if (gtk_check_version(3, 16, 3) == nullptr) {
|
|
// Before 3.16.3, GDK cannot override classname by --class command line
|
|
// option when program uses gdk_set_program_class().
|
|
//
|
|
// See https://bugzilla.gnome.org/show_bug.cgi?id=747634
|
|
//
|
|
// Only bother doing this for the parent process, since it's the one
|
|
// creating top-level windows.
|
|
if (gAppData) {
|
|
gdk_set_program_class(gAppData->remotingName);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!sPendingResumeQuark &&
|
|
gtk_check_version(3, 14, 7) != nullptr) { // GTK 3.0 to GTK 3.14.7.
|
|
// GTK 3.8 - 3.14 registered this type when creating the frame clock
|
|
// for the root window of the display when the display was opened.
|
|
GType gdkFrameClockIdleType = g_type_from_name("GdkFrameClockIdle");
|
|
if (gdkFrameClockIdleType) { // not in versions prior to 3.8
|
|
sPendingResumeQuark = g_quark_from_string("moz-resume-is-pending");
|
|
auto gdk_frame_clock_idle_class =
|
|
G_OBJECT_CLASS(g_type_class_peek_static(gdkFrameClockIdleType));
|
|
auto constructed = &gdk_frame_clock_idle_class->constructed;
|
|
sRealGdkFrameClockConstructed = *constructed;
|
|
*constructed = WrapGdkFrameClockConstructed;
|
|
auto dispose = &gdk_frame_clock_idle_class->dispose;
|
|
sRealGdkFrameClockDispose = *dispose;
|
|
*dispose = WrapGdkFrameClockDispose;
|
|
}
|
|
}
|
|
|
|
// Workaround for bug 1209659 which is fixed by Gtk3.20
|
|
if (gtk_check_version(3, 20, 0) != nullptr) {
|
|
unsetenv("GTK_CSD");
|
|
}
|
|
|
|
// Whitelist of only common, stable formats - see bugs 1197059 and 1203078
|
|
GSList* pixbufFormats = gdk_pixbuf_get_formats();
|
|
for (GSList* iter = pixbufFormats; iter; iter = iter->next) {
|
|
GdkPixbufFormat* format = static_cast<GdkPixbufFormat*>(iter->data);
|
|
gchar* name = gdk_pixbuf_format_get_name(format);
|
|
if (strcmp(name, "jpeg") && strcmp(name, "png") && strcmp(name, "gif") &&
|
|
strcmp(name, "bmp") && strcmp(name, "ico") && strcmp(name, "xpm") &&
|
|
strcmp(name, "svg") && strcmp(name, "webp") && strcmp(name, "avif")) {
|
|
gdk_pixbuf_format_set_disabled(format, TRUE);
|
|
}
|
|
g_free(name);
|
|
}
|
|
g_slist_free(pixbufFormats);
|
|
|
|
int err = pipe(mPipeFDs);
|
|
if (err) return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
GIOChannel* ioc;
|
|
GSource* source;
|
|
|
|
// make the pipe nonblocking
|
|
|
|
int flags = fcntl(mPipeFDs[0], F_GETFL, 0);
|
|
if (flags == -1) goto failed;
|
|
err = fcntl(mPipeFDs[0], F_SETFL, flags | O_NONBLOCK);
|
|
if (err == -1) goto failed;
|
|
flags = fcntl(mPipeFDs[1], F_GETFL, 0);
|
|
if (flags == -1) goto failed;
|
|
err = fcntl(mPipeFDs[1], F_SETFL, flags | O_NONBLOCK);
|
|
if (err == -1) goto failed;
|
|
|
|
ioc = g_io_channel_unix_new(mPipeFDs[0]);
|
|
source = g_io_create_watch(ioc, G_IO_IN);
|
|
g_io_channel_unref(ioc);
|
|
g_source_set_callback(source, (GSourceFunc)EventProcessorCallback, this,
|
|
nullptr);
|
|
g_source_set_can_recurse(source, TRUE);
|
|
mTag = g_source_attach(source, nullptr);
|
|
g_source_unref(source);
|
|
|
|
sAppShell = this;
|
|
|
|
return nsBaseAppShell::Init();
|
|
failed:
|
|
close(mPipeFDs[0]);
|
|
close(mPipeFDs[1]);
|
|
mPipeFDs[0] = mPipeFDs[1] = 0;
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
NS_IMETHODIMP nsAppShell::Run() {
|
|
if (XRE_IsParentProcess()) {
|
|
AddScreenWakeLockListener();
|
|
}
|
|
|
|
nsresult rv = nsBaseAppShell::Run();
|
|
|
|
if (XRE_IsParentProcess()) {
|
|
RemoveScreenWakeLockListener();
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
void nsAppShell::ScheduleNativeEventCallback() {
|
|
unsigned char buf[] = {NOTIFY_TOKEN};
|
|
Unused << write(mPipeFDs[1], buf, 1);
|
|
}
|
|
|
|
void nsAppShell::ScheduleQuitEvent() {
|
|
unsigned char buf[] = {QUIT_TOKEN};
|
|
Unused << write(mPipeFDs[1], buf, 1);
|
|
}
|
|
|
|
bool nsAppShell::ProcessNextNativeEvent(bool mayWait) {
|
|
if (mSuspendNativeCount) {
|
|
return false;
|
|
}
|
|
bool didProcessEvent = g_main_context_iteration(nullptr, mayWait);
|
|
return didProcessEvent;
|
|
}
|