Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsAlertsIconListener.h"
7 #include "imgIContainer.h"
8 #include "imgILoader.h"
9 #include "imgIRequest.h"
10 #include "nsNetUtil.h"
11 #include "nsIImageToPixbuf.h"
12 #include "nsIStringBundle.h"
13 #include "nsIObserverService.h"
14 #include "nsCRT.h"
16 #include <dlfcn.h>
17 #include <gdk/gdk.h>
19 static bool gHasActions = false;
20 static bool gHasCaps = false;
22 void* nsAlertsIconListener::libNotifyHandle = nullptr;
23 bool nsAlertsIconListener::libNotifyNotAvail = false;
24 nsAlertsIconListener::notify_is_initted_t nsAlertsIconListener::notify_is_initted = nullptr;
25 nsAlertsIconListener::notify_init_t nsAlertsIconListener::notify_init = nullptr;
26 nsAlertsIconListener::notify_get_server_caps_t nsAlertsIconListener::notify_get_server_caps = nullptr;
27 nsAlertsIconListener::notify_notification_new_t nsAlertsIconListener::notify_notification_new = nullptr;
28 nsAlertsIconListener::notify_notification_show_t nsAlertsIconListener::notify_notification_show = nullptr;
29 nsAlertsIconListener::notify_notification_set_icon_from_pixbuf_t nsAlertsIconListener::notify_notification_set_icon_from_pixbuf = nullptr;
30 nsAlertsIconListener::notify_notification_add_action_t nsAlertsIconListener::notify_notification_add_action = nullptr;
32 static void notify_action_cb(NotifyNotification *notification,
33 gchar *action, gpointer user_data)
34 {
35 nsAlertsIconListener* alert = static_cast<nsAlertsIconListener*> (user_data);
36 alert->SendCallback();
37 }
39 static void notify_closed_marshal(GClosure* closure,
40 GValue* return_value,
41 guint n_param_values,
42 const GValue* param_values,
43 gpointer invocation_hint,
44 gpointer marshal_data)
45 {
46 NS_ABORT_IF_FALSE(n_param_values >= 1, "No object in params");
48 nsAlertsIconListener* alert =
49 static_cast<nsAlertsIconListener*>(closure->data);
50 alert->SendClosed();
51 NS_RELEASE(alert);
52 }
54 NS_IMPL_ISUPPORTS(nsAlertsIconListener, imgINotificationObserver,
55 nsIObserver, nsISupportsWeakReference)
57 nsAlertsIconListener::nsAlertsIconListener()
58 : mLoadedFrame(false),
59 mNotification(nullptr)
60 {
61 if (!libNotifyHandle && !libNotifyNotAvail) {
62 libNotifyHandle = dlopen("libnotify.so.4", RTLD_LAZY);
63 if (!libNotifyHandle) {
64 libNotifyHandle = dlopen("libnotify.so.1", RTLD_LAZY);
65 if (!libNotifyHandle) {
66 libNotifyNotAvail = true;
67 return;
68 }
69 }
71 notify_is_initted = (notify_is_initted_t)dlsym(libNotifyHandle, "notify_is_initted");
72 notify_init = (notify_init_t)dlsym(libNotifyHandle, "notify_init");
73 notify_get_server_caps = (notify_get_server_caps_t)dlsym(libNotifyHandle, "notify_get_server_caps");
74 notify_notification_new = (notify_notification_new_t)dlsym(libNotifyHandle, "notify_notification_new");
75 notify_notification_show = (notify_notification_show_t)dlsym(libNotifyHandle, "notify_notification_show");
76 notify_notification_set_icon_from_pixbuf = (notify_notification_set_icon_from_pixbuf_t)dlsym(libNotifyHandle, "notify_notification_set_icon_from_pixbuf");
77 notify_notification_add_action = (notify_notification_add_action_t)dlsym(libNotifyHandle, "notify_notification_add_action");
78 if (!notify_is_initted || !notify_init || !notify_get_server_caps || !notify_notification_new || !notify_notification_show || !notify_notification_set_icon_from_pixbuf || !notify_notification_add_action) {
79 dlclose(libNotifyHandle);
80 libNotifyHandle = nullptr;
81 }
82 }
83 }
85 nsAlertsIconListener::~nsAlertsIconListener()
86 {
87 if (mIconRequest)
88 mIconRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
89 // Don't dlclose libnotify as it uses atexit().
90 }
92 NS_IMETHODIMP
93 nsAlertsIconListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
94 {
95 if (aType == imgINotificationObserver::LOAD_COMPLETE) {
96 return OnStopRequest(aRequest);
97 }
99 if (aType == imgINotificationObserver::FRAME_COMPLETE) {
100 return OnStopFrame(aRequest);
101 }
103 return NS_OK;
104 }
106 nsresult
107 nsAlertsIconListener::OnStopRequest(imgIRequest* aRequest)
108 {
109 uint32_t imgStatus = imgIRequest::STATUS_ERROR;
110 nsresult rv = aRequest->GetImageStatus(&imgStatus);
111 NS_ENSURE_SUCCESS(rv, rv);
112 if (imgStatus == imgIRequest::STATUS_ERROR && !mLoadedFrame) {
113 // We have an error getting the image. Display the notification with no icon.
114 ShowAlert(nullptr);
115 }
117 if (mIconRequest) {
118 mIconRequest->Cancel(NS_BINDING_ABORTED);
119 mIconRequest = nullptr;
120 }
121 return NS_OK;
122 }
124 nsresult
125 nsAlertsIconListener::OnStopFrame(imgIRequest* aRequest)
126 {
127 if (aRequest != mIconRequest)
128 return NS_ERROR_FAILURE;
130 if (mLoadedFrame)
131 return NS_OK; // only use one frame
133 nsCOMPtr<imgIContainer> image;
134 nsresult rv = aRequest->GetImage(getter_AddRefs(image));
135 if (NS_FAILED(rv))
136 return rv;
138 nsCOMPtr<nsIImageToPixbuf> imgToPixbuf =
139 do_GetService("@mozilla.org/widget/image-to-gdk-pixbuf;1");
141 GdkPixbuf* imagePixbuf = imgToPixbuf->ConvertImageToPixbuf(image);
142 if (!imagePixbuf)
143 return NS_ERROR_FAILURE;
145 ShowAlert(imagePixbuf);
147 g_object_unref(imagePixbuf);
149 mLoadedFrame = true;
150 return NS_OK;
151 }
153 nsresult
154 nsAlertsIconListener::ShowAlert(GdkPixbuf* aPixbuf)
155 {
156 mNotification = notify_notification_new(mAlertTitle.get(), mAlertText.get(),
157 nullptr, nullptr);
159 if (!mNotification)
160 return NS_ERROR_OUT_OF_MEMORY;
162 if (aPixbuf)
163 notify_notification_set_icon_from_pixbuf(mNotification, aPixbuf);
165 NS_ADDREF(this);
166 if (mAlertHasAction) {
167 // What we put as the label doesn't matter here, if the action
168 // string is "default" then that makes the entire bubble clickable
169 // rather than creating a button.
170 notify_notification_add_action(mNotification, "default", "Activate",
171 notify_action_cb, this, nullptr);
172 }
174 // Fedora 10 calls NotifyNotification "closed" signal handlers with a
175 // different signature, so a marshaller is used instead of a C callback to
176 // get the user_data (this) in a parseable format. |closure| is created
177 // with a floating reference, which gets sunk by g_signal_connect_closure().
178 GClosure* closure = g_closure_new_simple(sizeof(GClosure), this);
179 g_closure_set_marshal(closure, notify_closed_marshal);
180 mClosureHandler = g_signal_connect_closure(mNotification, "closed", closure, FALSE);
181 gboolean result = notify_notification_show(mNotification, nullptr);
183 return result ? NS_OK : NS_ERROR_FAILURE;
184 }
186 nsresult
187 nsAlertsIconListener::StartRequest(const nsAString & aImageUrl)
188 {
189 if (mIconRequest) {
190 // Another icon request is already in flight. Kill it.
191 mIconRequest->Cancel(NS_BINDING_ABORTED);
192 mIconRequest = nullptr;
193 }
195 nsCOMPtr<nsIURI> imageUri;
196 NS_NewURI(getter_AddRefs(imageUri), aImageUrl);
197 if (!imageUri)
198 return ShowAlert(nullptr);
200 nsCOMPtr<imgILoader> il(do_GetService("@mozilla.org/image/loader;1"));
201 if (!il)
202 return ShowAlert(nullptr);
204 // XXX: Hrmm.... Bypass cache, or isolate to imageUrl?
205 return il->LoadImageXPCOM(imageUri, imageURI, nullptr, nullptr, nullptr,
206 this, nullptr, nsIRequest::LOAD_NORMAL, nullptr,
207 nullptr, getter_AddRefs(mIconRequest));
208 }
210 void
211 nsAlertsIconListener::SendCallback()
212 {
213 if (mAlertListener)
214 mAlertListener->Observe(nullptr, "alertclickcallback", mAlertCookie.get());
215 }
217 void
218 nsAlertsIconListener::SendClosed()
219 {
220 if (mNotification) {
221 g_object_unref(mNotification);
222 mNotification = nullptr;
223 }
224 if (mAlertListener)
225 mAlertListener->Observe(nullptr, "alertfinished", mAlertCookie.get());
226 }
228 NS_IMETHODIMP
229 nsAlertsIconListener::Observe(nsISupports *aSubject, const char *aTopic,
230 const char16_t *aData) {
231 // We need to close any open notifications upon application exit, otherwise
232 // we will leak since libnotify holds a ref for us.
233 if (!nsCRT::strcmp(aTopic, "quit-application") && mNotification) {
234 g_signal_handler_disconnect(mNotification, mClosureHandler);
235 g_object_unref(mNotification);
236 mNotification = nullptr;
237 Release(); // equivalent to NS_RELEASE(this)
238 }
239 return NS_OK;
240 }
242 nsresult
243 nsAlertsIconListener::InitAlertAsync(const nsAString & aImageUrl,
244 const nsAString & aAlertTitle,
245 const nsAString & aAlertText,
246 bool aAlertTextClickable,
247 const nsAString & aAlertCookie,
248 nsIObserver * aAlertListener)
249 {
250 if (!libNotifyHandle)
251 return NS_ERROR_FAILURE;
253 if (!notify_is_initted()) {
254 // Give the name of this application to libnotify
255 nsCOMPtr<nsIStringBundleService> bundleService =
256 do_GetService(NS_STRINGBUNDLE_CONTRACTID);
258 nsAutoCString appShortName;
259 if (bundleService) {
260 nsCOMPtr<nsIStringBundle> bundle;
261 bundleService->CreateBundle("chrome://branding/locale/brand.properties",
262 getter_AddRefs(bundle));
263 nsAutoString appName;
265 if (bundle) {
266 bundle->GetStringFromName(MOZ_UTF16("brandShortName"),
267 getter_Copies(appName));
268 appShortName = NS_ConvertUTF16toUTF8(appName);
269 } else {
270 NS_WARNING("brand.properties not present, using default application name");
271 appShortName.AssignLiteral("Mozilla");
272 }
273 } else {
274 appShortName.AssignLiteral("Mozilla");
275 }
277 if (!notify_init(appShortName.get()))
278 return NS_ERROR_FAILURE;
280 GList *server_caps = notify_get_server_caps();
281 if (server_caps) {
282 gHasCaps = true;
283 for (GList* cap = server_caps; cap != nullptr; cap = cap->next) {
284 if (!strcmp((char*) cap->data, "actions")) {
285 gHasActions = true;
286 break;
287 }
288 }
289 g_list_foreach(server_caps, (GFunc)g_free, nullptr);
290 g_list_free(server_caps);
291 }
292 }
294 if (!gHasCaps) {
295 // if notify_get_server_caps() failed above we need to assume
296 // there is no notification-server to display anything
297 return NS_ERROR_FAILURE;
298 }
300 if (!gHasActions && aAlertTextClickable)
301 return NS_ERROR_FAILURE; // No good, fallback to XUL
303 nsCOMPtr<nsIObserverService> obsServ =
304 do_GetService("@mozilla.org/observer-service;1");
305 if (obsServ)
306 obsServ->AddObserver(this, "quit-application", true);
308 // Workaround for a libnotify bug - blank titles aren't dealt with
309 // properly so we use a space
310 if (aAlertTitle.IsEmpty()) {
311 mAlertTitle = NS_LITERAL_CSTRING(" ");
312 } else {
313 mAlertTitle = NS_ConvertUTF16toUTF8(aAlertTitle);
314 }
316 mAlertText = NS_ConvertUTF16toUTF8(aAlertText);
317 mAlertHasAction = aAlertTextClickable;
319 mAlertListener = aAlertListener;
320 mAlertCookie = aAlertCookie;
322 return StartRequest(aImageUrl);
323 }