1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=4:tabstop=4:
3  */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 
8 #include <sys/types.h>
9 #include <unistd.h>
10 #include <fcntl.h>
11 #include <errno.h>
12 #include <gdk/gdk.h>
13 #include "nsAppShell.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/Unused.h"
20 #include "mozilla/WidgetUtils.h"
21 #include "GeckoProfiler.h"
22 #include "nsIPowerManagerService.h"
23 #ifdef MOZ_ENABLE_DBUS
24 #  include "WakeLockListener.h"
25 #endif
26 #include "gfxPlatform.h"
27 #include "ScreenHelperGTK.h"
28 #include "HeadlessScreenHelper.h"
29 #include "mozilla/widget/ScreenManager.h"
30 #ifdef MOZ_WAYLAND
31 #  include "nsWaylandDisplay.h"
32 #endif
33 
34 using mozilla::LazyLogModule;
35 using mozilla::Unused;
36 using mozilla::widget::HeadlessScreenHelper;
37 using mozilla::widget::ScreenHelperGTK;
38 using mozilla::widget::ScreenManager;
39 
40 #define NOTIFY_TOKEN 0xFA
41 
42 LazyLogModule gWidgetLog("Widget");
43 LazyLogModule gWidgetDragLog("WidgetDrag");
44 LazyLogModule gWidgetWaylandLog("WidgetWayland");
45 LazyLogModule gWidgetPopupLog("WidgetPopup");
46 LazyLogModule gDmabufLog("Dmabuf");
47 LazyLogModule gClipboardLog("WidgetClipboard");
48 
49 static GPollFunc sPollFunc;
50 
51 // Wrapper function to disable hang monitoring while waiting in poll().
PollWrapper(GPollFD * ufds,guint nfsd,gint timeout_)52 static gint PollWrapper(GPollFD* ufds, guint nfsd, gint timeout_) {
53   mozilla::BackgroundHangMonitor().NotifyWait();
54   gint result;
55   {
56     AUTO_PROFILER_LABEL("PollWrapper", IDLE);
57     AUTO_PROFILER_THREAD_SLEEP;
58     result = (*sPollFunc)(ufds, nfsd, timeout_);
59   }
60   mozilla::BackgroundHangMonitor().NotifyActivity();
61   return result;
62 }
63 
64 // Emit resume-events on GdkFrameClock if flush-events has not been
65 // balanced by resume-events at dispose.
66 // For https://bugzilla.gnome.org/show_bug.cgi?id=742636
67 static decltype(GObjectClass::constructed) sRealGdkFrameClockConstructed;
68 static decltype(GObjectClass::dispose) sRealGdkFrameClockDispose;
69 static GQuark sPendingResumeQuark;
70 
OnFlushEvents(GObject * clock,gpointer)71 static void OnFlushEvents(GObject* clock, gpointer) {
72   g_object_set_qdata(clock, sPendingResumeQuark, GUINT_TO_POINTER(1));
73 }
74 
OnResumeEvents(GObject * clock,gpointer)75 static void OnResumeEvents(GObject* clock, gpointer) {
76   g_object_set_qdata(clock, sPendingResumeQuark, nullptr);
77 }
78 
WrapGdkFrameClockConstructed(GObject * object)79 static void WrapGdkFrameClockConstructed(GObject* object) {
80   sRealGdkFrameClockConstructed(object);
81 
82   g_signal_connect(object, "flush-events", G_CALLBACK(OnFlushEvents), nullptr);
83   g_signal_connect(object, "resume-events", G_CALLBACK(OnResumeEvents),
84                    nullptr);
85 }
86 
WrapGdkFrameClockDispose(GObject * object)87 static void WrapGdkFrameClockDispose(GObject* object) {
88   if (g_object_get_qdata(object, sPendingResumeQuark)) {
89     g_signal_emit_by_name(object, "resume-events");
90   }
91 
92   sRealGdkFrameClockDispose(object);
93 }
94 
95 /*static*/
EventProcessorCallback(GIOChannel * source,GIOCondition condition,gpointer data)96 gboolean nsAppShell::EventProcessorCallback(GIOChannel* source,
97                                             GIOCondition condition,
98                                             gpointer data) {
99   nsAppShell* self = static_cast<nsAppShell*>(data);
100 
101   unsigned char c;
102   Unused << read(self->mPipeFDs[0], &c, 1);
103   NS_ASSERTION(c == (unsigned char)NOTIFY_TOKEN, "wrong token");
104 
105   self->NativeEventCallback();
106   return TRUE;
107 }
108 
~nsAppShell()109 nsAppShell::~nsAppShell() {
110   mozilla::hal::Shutdown();
111 
112   if (mTag) g_source_remove(mTag);
113   if (mPipeFDs[0]) close(mPipeFDs[0]);
114   if (mPipeFDs[1]) close(mPipeFDs[1]);
115 }
116 
Init()117 nsresult nsAppShell::Init() {
118   mozilla::hal::Init();
119 
120 #ifdef MOZ_ENABLE_DBUS
121   if (XRE_IsParentProcess()) {
122     nsCOMPtr<nsIPowerManagerService> powerManagerService =
123         do_GetService(POWERMANAGERSERVICE_CONTRACTID);
124 
125     if (powerManagerService) {
126       powerManagerService->AddWakeLockListener(
127           WakeLockListener::GetSingleton());
128     } else {
129       NS_WARNING(
130           "Failed to retrieve PowerManagerService, wakelocks will be broken!");
131     }
132   }
133 #endif
134 
135   if (!sPollFunc) {
136     sPollFunc = g_main_context_get_poll_func(nullptr);
137     g_main_context_set_poll_func(nullptr, &PollWrapper);
138   }
139 
140   if (XRE_IsParentProcess()) {
141     ScreenManager& screenManager = ScreenManager::GetSingleton();
142     if (gfxPlatform::IsHeadless()) {
143       screenManager.SetHelper(mozilla::MakeUnique<HeadlessScreenHelper>());
144     } else {
145       screenManager.SetHelper(mozilla::MakeUnique<ScreenHelperGTK>());
146     }
147 
148     if (gtk_check_version(3, 16, 3) == nullptr) {
149       // Before 3.16.3, GDK cannot override classname by --class command line
150       // option when program uses gdk_set_program_class().
151       //
152       // See https://bugzilla.gnome.org/show_bug.cgi?id=747634
153       //
154       // Only bother doing this for the parent process, since it's the one
155       // creating top-level windows. (At this point, a child process hasn't
156       // received the list of registered chrome packages, so the
157       // GetBrandShortName call would fail anyway.)
158       nsAutoString brandName;
159       mozilla::widget::WidgetUtils::GetBrandShortName(brandName);
160       if (!brandName.IsEmpty()) {
161         gdk_set_program_class(NS_ConvertUTF16toUTF8(brandName).get());
162       }
163     }
164   }
165 
166   if (!sPendingResumeQuark &&
167       gtk_check_version(3, 14, 7) != nullptr) {  // GTK 3.0 to GTK 3.14.7.
168     // GTK 3.8 - 3.14 registered this type when creating the frame clock
169     // for the root window of the display when the display was opened.
170     GType gdkFrameClockIdleType = g_type_from_name("GdkFrameClockIdle");
171     if (gdkFrameClockIdleType) {  // not in versions prior to 3.8
172       sPendingResumeQuark = g_quark_from_string("moz-resume-is-pending");
173       auto gdk_frame_clock_idle_class =
174           G_OBJECT_CLASS(g_type_class_peek_static(gdkFrameClockIdleType));
175       auto constructed = &gdk_frame_clock_idle_class->constructed;
176       sRealGdkFrameClockConstructed = *constructed;
177       *constructed = WrapGdkFrameClockConstructed;
178       auto dispose = &gdk_frame_clock_idle_class->dispose;
179       sRealGdkFrameClockDispose = *dispose;
180       *dispose = WrapGdkFrameClockDispose;
181     }
182   }
183 
184   // Workaround for bug 1209659 which is fixed by Gtk3.20
185   if (gtk_check_version(3, 20, 0) != nullptr) {
186     unsetenv("GTK_CSD");
187   }
188 
189   if (PR_GetEnv("MOZ_DEBUG_PAINTS")) {
190     gdk_window_set_debug_updates(TRUE);
191   }
192 
193   // Whitelist of only common, stable formats - see bugs 1197059 and 1203078
194   GSList* pixbufFormats = gdk_pixbuf_get_formats();
195   for (GSList* iter = pixbufFormats; iter; iter = iter->next) {
196     GdkPixbufFormat* format = static_cast<GdkPixbufFormat*>(iter->data);
197     gchar* name = gdk_pixbuf_format_get_name(format);
198     if (strcmp(name, "jpeg") && strcmp(name, "png") && strcmp(name, "gif") &&
199         strcmp(name, "bmp") && strcmp(name, "ico") && strcmp(name, "xpm") &&
200         strcmp(name, "svg")) {
201       gdk_pixbuf_format_set_disabled(format, TRUE);
202     }
203     g_free(name);
204   }
205   g_slist_free(pixbufFormats);
206 
207   int err = pipe(mPipeFDs);
208   if (err) return NS_ERROR_OUT_OF_MEMORY;
209 
210   GIOChannel* ioc;
211   GSource* source;
212 
213   // make the pipe nonblocking
214 
215   int flags = fcntl(mPipeFDs[0], F_GETFL, 0);
216   if (flags == -1) goto failed;
217   err = fcntl(mPipeFDs[0], F_SETFL, flags | O_NONBLOCK);
218   if (err == -1) goto failed;
219   flags = fcntl(mPipeFDs[1], F_GETFL, 0);
220   if (flags == -1) goto failed;
221   err = fcntl(mPipeFDs[1], F_SETFL, flags | O_NONBLOCK);
222   if (err == -1) goto failed;
223 
224   ioc = g_io_channel_unix_new(mPipeFDs[0]);
225   source = g_io_create_watch(ioc, G_IO_IN);
226   g_io_channel_unref(ioc);
227   g_source_set_callback(source, (GSourceFunc)EventProcessorCallback, this,
228                         nullptr);
229   g_source_set_can_recurse(source, TRUE);
230   mTag = g_source_attach(source, nullptr);
231   g_source_unref(source);
232 
233   return nsBaseAppShell::Init();
234 failed:
235   close(mPipeFDs[0]);
236   close(mPipeFDs[1]);
237   mPipeFDs[0] = mPipeFDs[1] = 0;
238   return NS_ERROR_FAILURE;
239 }
240 
ScheduleNativeEventCallback()241 void nsAppShell::ScheduleNativeEventCallback() {
242   unsigned char buf[] = {NOTIFY_TOKEN};
243   Unused << write(mPipeFDs[1], buf, 1);
244 }
245 
ProcessNextNativeEvent(bool mayWait)246 bool nsAppShell::ProcessNextNativeEvent(bool mayWait) {
247   bool ret = g_main_context_iteration(nullptr, mayWait);
248 #ifdef MOZ_WAYLAND
249   mozilla::widget::WaylandDispatchDisplays();
250 #endif
251   return ret;
252 }
253