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