Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(60)

Side by Side Diff: chrome/browser/notifications/notification_platform_bridge_linux.cc

Issue 2821533003: Refactor NotificationPlatformBridgeLinux (Closed)
Patch Set: Fix icon file getting prematurely deleted Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2017 The Chromium Authors. All rights reserved. 1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/notifications/notification_platform_bridge_linux.h" 5 #include "chrome/browser/notifications/notification_platform_bridge_linux.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 8
9 #include "base/files/file_util.h" 9 #include "base/files/file_util.h"
10 #include "base/memory/ptr_util.h"
11 #include "base/stl_util.h"
12 #include "base/strings/nullable_string16.h" 10 #include "base/strings/nullable_string16.h"
13 #include "base/strings/string_number_conversions.h" 11 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_util.h" 12 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h" 13 #include "base/strings/utf_string_conversions.h"
14 #include "base/synchronization/waitable_event.h"
16 #include "base/task_scheduler/post_task.h" 15 #include "base/task_scheduler/post_task.h"
17 #include "chrome/browser/browser_process.h" 16 #include "chrome/browser/browser_process.h"
18 #include "chrome/browser/chrome_notification_types.h"
19 #include "chrome/browser/notifications/native_notification_display_service.h" 17 #include "chrome/browser/notifications/native_notification_display_service.h"
20 #include "chrome/browser/notifications/notification.h" 18 #include "chrome/browser/notifications/notification.h"
21 #include "chrome/browser/notifications/notification_display_service_factory.h" 19 #include "chrome/browser/notifications/notification_display_service_factory.h"
22 #include "chrome/browser/profiles/profile_manager.h" 20 #include "chrome/browser/profiles/profile_manager.h"
23 #include "chrome/browser/shell_integration_linux.h" 21 #include "chrome/browser/shell_integration_linux.h"
24 #include "content/public/browser/notification_service.h" 22 #include "content/public/browser/browser_thread.h"
23 #include "dbus/bus.h"
24 #include "dbus/message.h"
25 #include "dbus/object_proxy.h"
25 26
26 namespace { 27 namespace {
27 28
28 const char kFreedesktopNotificationsName[] = "org.freedesktop.Notifications"; 29 const char kFreedesktopNotificationsName[] = "org.freedesktop.Notifications";
29 const char kFreedesktopNotificationsPath[] = "/org/freedesktop/Notifications"; 30 const char kFreedesktopNotificationsPath[] = "/org/freedesktop/Notifications";
30 31
31 void AddActionToNotification(GVariantBuilder* actions_builder, 32 class NotificationThreadSafe : public Notification {
Lei Zhang 2017/04/17 23:32:13 Seems a bit weird, as Notification is probably not
Tom (Use chromium acct) 2017/04/18 01:41:11 Yeah I guess it is. However now that I think abou
32 const char* action_id, 33 public:
33 const char* button_label) { 34 NotificationThreadSafe(const Notification& notification,
34 g_variant_builder_add(actions_builder, "s", action_id); 35 const scoped_refptr<base::RefCountedMemory>& icon_data)
35 g_variant_builder_add(actions_builder, "s", button_label); 36 : Notification(notification), icon_data(icon_data) {
36 } 37 set_icon(gfx::Image());
38 set_image(gfx::Image());
39 }
40 const scoped_refptr<base::RefCountedMemory> icon_data;
41 };
37 42
38 int NotificationPriorityToFdoUrgency(int priority) { 43 int NotificationPriorityToFdoUrgency(int priority) {
39 enum FdoUrgency { 44 enum FdoUrgency {
40 LOW = 0, 45 LOW = 0,
41 NORMAL = 1, 46 NORMAL = 1,
42 CRITICAL = 2, 47 CRITICAL = 2,
43 }; 48 };
44 switch (priority) { 49 switch (priority) {
45 case message_center::MIN_PRIORITY: 50 case message_center::MIN_PRIORITY:
46 case message_center::LOW_PRIORITY: 51 case message_center::LOW_PRIORITY:
47 return LOW; 52 return LOW;
48 case message_center::HIGH_PRIORITY: 53 case message_center::HIGH_PRIORITY:
49 case message_center::MAX_PRIORITY: 54 case message_center::MAX_PRIORITY:
50 return CRITICAL; 55 return CRITICAL;
51 default: 56 default:
52 NOTREACHED(); 57 NOTREACHED();
53 // fallthrough
54 case message_center::DEFAULT_PRIORITY: 58 case message_center::DEFAULT_PRIORITY:
55 return NORMAL; 59 return NORMAL;
56 } 60 }
57 } 61 }
58 62
59 // Callback used by GLib when the "Notify" message completes for the
60 // first time.
61 void NotifyCompleteReceiver(GObject* source_object,
62 GAsyncResult* result,
63 gpointer user_data) {
64 GDBusProxy* proxy = G_DBUS_PROXY(source_object);
65 GVariant* value = g_dbus_proxy_call_finish(proxy, result, nullptr);
66 if (!value) {
67 // The message might have been cancelled, in which case
68 // |user_data| points to a destroyed NotificationData.
69 return;
70 }
71 auto* platform_bridge_linux = static_cast<NotificationPlatformBridgeLinux*>(
72 g_browser_process->notification_platform_bridge());
73 platform_bridge_linux->NotifyCompleteInternal(user_data, value);
74 }
75
76 // Runs once the profile has been loaded in order to perform a given 63 // Runs once the profile has been loaded in order to perform a given
77 // |operation| on a notification. 64 // |operation| on a notification.
78 void ProfileLoadedCallback(NotificationCommon::Operation operation, 65 void ProfileLoadedCallback(NotificationCommon::Operation operation,
79 NotificationCommon::Type notification_type, 66 NotificationCommon::Type notification_type,
80 const std::string& origin, 67 const std::string& origin,
81 const std::string& notification_id, 68 const std::string& notification_id,
82 int action_index, 69 int action_index,
83 const base::NullableString16& reply, 70 const base::NullableString16& reply,
84 Profile* profile) { 71 Profile* profile) {
85 if (!profile) 72 if (!profile)
(...skipping 18 matching lines...) Expand all
104 if (!base::CreateTemporaryFile(&file_path)) 91 if (!base::CreateTemporaryFile(&file_path))
105 return base::FilePath(); 92 return base::FilePath();
106 if (base::WriteFile(file_path, data->front_as<char>(), data_len) != 93 if (base::WriteFile(file_path, data->front_as<char>(), data_len) !=
107 data_len) { 94 data_len) {
108 base::DeleteFile(file_path, false); 95 base::DeleteFile(file_path, false);
109 return base::FilePath(); 96 return base::FilePath();
110 } 97 }
111 return file_path; 98 return file_path;
112 } 99 }
113 100
114 void DeleteNotificationResourceFile(const base::FilePath& file_path) {
115 if (file_path.empty())
116 return;
117 base::PostTaskWithTraits(
118 FROM_HERE,
119 base::TaskTraits()
120 .MayBlock()
121 .WithPriority(base::TaskPriority::BACKGROUND)
122 .WithShutdownBehavior(base::TaskShutdownBehavior::BLOCK_SHUTDOWN),
123 base::Bind(base::IgnoreResult(base::DeleteFile), file_path, false));
124 }
125
126 } // namespace 101 } // namespace
127 102
128 // static 103 // static
129 NotificationPlatformBridge* NotificationPlatformBridge::Create() { 104 NotificationPlatformBridge* NotificationPlatformBridge::Create() {
130 GDBusProxy* notification_proxy = g_dbus_proxy_new_for_bus_sync( 105 NotificationPlatformBridgeLinux* npbl = new NotificationPlatformBridgeLinux();
Lei Zhang 2017/04/17 23:32:13 With unique_ptr: auto npbl = base::MakeUnique<Not
Tom (Use chromium acct) 2017/04/18 01:41:11 Done.
131 G_BUS_TYPE_SESSION, 106 if (npbl->BlockUntilReady())
Lei Zhang 2017/04/17 23:32:13 Does this block the UI thread? Maybe if you used a
Tom (Use chromium acct) 2017/04/18 01:41:11 We would still have to wait for the bus connection
132 static_cast<GDBusProxyFlags>(G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | 107 return npbl;
133 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START), 108 delete npbl;
134 nullptr, kFreedesktopNotificationsName, kFreedesktopNotificationsPath,
135 kFreedesktopNotificationsName, nullptr, nullptr);
136 if (!notification_proxy)
137 return nullptr;
138 return new NotificationPlatformBridgeLinux(notification_proxy);
139 }
140
141 struct NotificationPlatformBridgeLinux::NotificationData {
142 NotificationData(NotificationCommon::Type notification_type,
143 const std::string& notification_id,
144 const std::string& profile_id,
145 bool is_incognito,
146 const GURL& origin_url)
147 : notification_type(notification_type),
148 notification_id(notification_id),
149 profile_id(profile_id),
150 is_incognito(is_incognito),
151 origin_url(origin_url),
152 weak_factory(this) {}
153
154 ~NotificationData() {
155 if (cancellable)
156 g_cancellable_cancel(cancellable);
157 ResetResourceFiles();
158 }
159
160 void ResetResourceFiles() {
161 for (const base::FilePath& file : resource_files)
162 DeleteNotificationResourceFile(file);
163 resource_files.clear();
164 }
165
166 // The ID used by the notification server. Will be 0 until the
167 // first "Notify" message completes.
168 uint32_t dbus_id = 0;
169
170 // Same parameters used by NotificationPlatformBridge::Display().
171 NotificationCommon::Type notification_type;
172 const std::string notification_id;
173 const std::string profile_id;
174 const bool is_incognito;
175
176 // A copy of the origin_url from the underlying
177 // message_center::Notification. Used to pass back to
178 // NativeNotificationDisplayService.
179 const GURL origin_url;
180
181 // Used to keep track of the IDs of the buttons currently displayed
182 // on this notification. The valid range of action IDs is
183 // [action_start, action_end).
184 size_t action_start = 0;
185 size_t action_end = 0;
186
187 // Temporary resource files associated with the notification that
188 // should be cleaned up when the notification is closed or on
189 // shutdown.
190 std::vector<base::FilePath> resource_files;
191
192 // Used to cancel the initial "Notify" message so we don't call
193 // NotificationPlatformBridgeLinux::NotifyCompleteInternal() with a
194 // destroyed Notification.
195 ScopedGObject<GCancellable> cancellable;
196
197 // If not null, the data to update the notification with once
198 // |dbus_id| becomes available.
199 std::unique_ptr<Notification> update_data;
200 NotificationCommon::Type update_notification_type =
201 NotificationCommon::TYPE_MAX;
202
203 // If true, indicates the notification should be closed once
204 // |dbus_id| becomes available.
205 bool should_close = false;
206
207 base::WeakPtrFactory<NotificationData> weak_factory;
208 };
209
210 struct NotificationPlatformBridgeLinux::ResourceFiles {
211 explicit ResourceFiles(const base::FilePath& icon_file)
212 : icon_file(icon_file) {}
213 ~ResourceFiles() { DeleteNotificationResourceFile(icon_file); }
214 base::FilePath icon_file;
215 };
216
217 NotificationPlatformBridgeLinux::NotificationPlatformBridgeLinux(
218 GDBusProxy* notification_proxy)
219 : notification_proxy_(notification_proxy), weak_factory_(this) {
220 proxy_signal_handler_ = g_signal_connect(
221 notification_proxy_, "g-signal", G_CALLBACK(GSignalReceiverThunk), this);
222 registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
223 content::NotificationService::AllSources());
224 }
225
226 NotificationPlatformBridgeLinux::~NotificationPlatformBridgeLinux() {
227 if (proxy_signal_handler_)
228 g_signal_handler_disconnect(notification_proxy_, proxy_signal_handler_);
229 }
230
231 void NotificationPlatformBridgeLinux::Display(
232 NotificationCommon::Type notification_type,
233 const std::string& notification_id,
234 const std::string& profile_id,
235 bool is_incognito,
236 const Notification& notification) {
237 NotificationData* data =
238 FindNotificationData(notification_id, profile_id, is_incognito);
239 if (data) {
240 // Update an existing notification.
241 if (data->dbus_id) {
242 data->notification_type = notification_type;
243 Notify(notification, data, nullptr, nullptr);
244 } else {
245 data->update_notification_type = notification_type;
246 data->update_data = base::MakeUnique<Notification>(notification);
247 }
248 } else {
249 // Send the notification for the first time.
250 data = new NotificationData(notification_type, notification_id, profile_id,
251 is_incognito, notification.origin_url());
252 data->cancellable.reset(g_cancellable_new());
253 notifications_.emplace(data, base::WrapUnique(data));
254 Notify(notification, data, NotifyCompleteReceiver, data);
255 }
256 }
257
258 void NotificationPlatformBridgeLinux::Close(
259 const std::string& profile_id,
260 const std::string& notification_id) {
261 std::vector<NotificationData*> to_erase;
262 for (const auto& pair : notifications_) {
263 NotificationData* data = pair.first;
264 if (data->notification_id == notification_id &&
265 data->profile_id == profile_id) {
266 if (data->dbus_id) {
267 CloseNow(data->dbus_id);
268 to_erase.push_back(data);
269 } else {
270 data->should_close = true;
271 }
272 }
273 }
274 for (NotificationData* data : to_erase)
275 notifications_.erase(data);
276 }
277
278 void NotificationPlatformBridgeLinux::GetDisplayed(
279 const std::string& profile_id,
280 bool incognito,
281 const DisplayedNotificationsCallback& callback) const {
282 callback.Run(base::MakeUnique<std::set<std::string>>(), false);
283 }
284
285 void NotificationPlatformBridgeLinux::NotifyCompleteInternal(gpointer user_data,
286 GVariant* value) {
287 NotificationData* data = reinterpret_cast<NotificationData*>(user_data);
288 if (!base::ContainsKey(notifications_, data))
289 return;
290 data->cancellable.reset();
291 if (value && g_variant_is_of_type(value, G_VARIANT_TYPE("(u)")))
292 g_variant_get(value, "(u)", &data->dbus_id);
293
294 if (!data->dbus_id) {
295 // There was some sort of error with creating the notification.
296 notifications_.erase(data);
297 } else if (data->should_close) {
298 CloseNow(data->dbus_id);
299 notifications_.erase(data);
300 } else if (data->update_data) {
301 data->notification_type = data->update_notification_type;
302 Notify(*data->update_data, data, nullptr, nullptr);
303 data->update_data.reset();
304 }
305 }
306
307 void NotificationPlatformBridgeLinux::Observe(
308 int type,
309 const content::NotificationSource& source,
310 const content::NotificationDetails& details) {
311 DCHECK_EQ(chrome::NOTIFICATION_APP_TERMINATING, type);
312 // The browser process is about to exit. Clean up all notification
313 // resource files.
314 notifications_.clear();
315 }
316
317 void NotificationPlatformBridgeLinux::Notify(const Notification& notification,
318 NotificationData* data,
319 GAsyncReadyCallback callback,
320 gpointer user_data) {
321 const scoped_refptr<base::RefCountedMemory> icon_data =
322 notification.icon().As1xPNGBytes();
323 if (!icon_data->size()) {
324 NotifyNow(notification, data->weak_factory.GetWeakPtr(), callback,
325 user_data, base::MakeUnique<ResourceFiles>(base::FilePath()));
326 } else {
327 base::PostTaskWithTraitsAndReplyWithResult(
328 FROM_HERE,
329 base::TaskTraits()
330 .MayBlock()
331 .WithPriority(base::TaskPriority::USER_BLOCKING)
332 .WithShutdownBehavior(base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN),
333 base::Bind(
334 [](scoped_refptr<base::RefCountedMemory> icon) {
335 return base::MakeUnique<ResourceFiles>(WriteDataToTmpFile(icon));
336 },
337 icon_data),
338 base::Bind(&NotificationPlatformBridgeLinux::NotifyNow,
339 weak_factory_.GetWeakPtr(), notification,
340 data->weak_factory.GetWeakPtr(), callback, user_data));
341 }
342 }
343
344 void NotificationPlatformBridgeLinux::NotifyNow(
345 const Notification& notification,
346 base::WeakPtr<NotificationData> data,
347 GAsyncReadyCallback callback,
348 gpointer user_data,
349 std::unique_ptr<ResourceFiles> resource_files) {
350 if (!data)
351 return;
352
353 if (data->dbus_id)
354 DCHECK(!data->cancellable);
355
356 data->ResetResourceFiles();
357
358 GVariantBuilder actions_builder;
359 // Even-indexed elements in this array are action IDs passed back to
360 // us in GSignalReceiver. Odd-indexed ones contain the button text.
361 g_variant_builder_init(&actions_builder, G_VARIANT_TYPE("as"));
362 data->action_start = data->action_end;
363 for (const auto& button_info : notification.buttons()) {
364 // FDO notification buttons can contain either an icon or a label,
365 // but not both, and the type of all buttons must be the same (all
366 // labels or all icons), so always use labels.
367 std::string id = base::SizeTToString(data->action_end++);
368 const std::string label = base::UTF16ToUTF8(button_info.title);
369 AddActionToNotification(&actions_builder, id.c_str(), label.c_str());
370 }
371 if (notification.clickable()) {
372 // Special case: the pair ("default", "") will not add a button,
373 // but instead makes the entire notification clickable.
374 AddActionToNotification(&actions_builder, "default", "");
375 }
376 // Always add a settings button.
377 AddActionToNotification(&actions_builder, "settings", "Settings");
378
379 GVariantBuilder hints_builder;
380 g_variant_builder_init(&hints_builder, G_VARIANT_TYPE("a{sv}"));
381
382 g_variant_builder_add(&hints_builder, "{sv}", "urgency",
383 g_variant_new_byte(NotificationPriorityToFdoUrgency(
384 notification.priority())));
385
386 std::unique_ptr<base::Environment> env = base::Environment::Create();
387 std::string desktop_file = shell_integration_linux::GetDesktopName(env.get());
388 const char kDesktopFileSuffix[] = ".desktop";
389 DCHECK(base::EndsWith(desktop_file, kDesktopFileSuffix,
390 base::CompareCase::SENSITIVE));
391 desktop_file =
392 desktop_file.substr(0, desktop_file.size() - strlen(kDesktopFileSuffix));
393 g_variant_builder_add(&hints_builder, "{sv}", "desktop-entry",
394 g_variant_new_string(desktop_file.c_str()));
395
396 if (!resource_files->icon_file.empty()) {
397 g_variant_builder_add(
398 &hints_builder, "{sv}", "image-path",
399 g_variant_new_string(resource_files->icon_file.value().c_str()));
400 data->resource_files.push_back(resource_files->icon_file);
401 resource_files->icon_file.clear();
402 }
403
404 const std::string title = base::UTF16ToUTF8(notification.title());
405 const std::string message = base::UTF16ToUTF8(notification.message());
406
407 GVariant* parameters = g_variant_new(
408 "(susssasa{sv}i)", "" /* app_name passed implicitly via desktop-entry */,
409 data->dbus_id, "" /* app_icon passed implicitly via desktop-entry */,
410 title.c_str(), message.c_str(), &actions_builder, &hints_builder, -1);
411 g_dbus_proxy_call(notification_proxy_, "Notify", parameters,
412 G_DBUS_CALL_FLAGS_NONE, -1, data->cancellable, callback,
413 user_data);
414 }
415
416 void NotificationPlatformBridgeLinux::CloseNow(uint32_t dbus_id) {
417 g_dbus_proxy_call(notification_proxy_, "CloseNotification",
418 g_variant_new("(u)", dbus_id), G_DBUS_CALL_FLAGS_NONE, -1,
419 nullptr, nullptr, nullptr);
420 }
421
422 NotificationPlatformBridgeLinux::NotificationData*
423 NotificationPlatformBridgeLinux::FindNotificationData(
424 const std::string& notification_id,
425 const std::string& profile_id,
426 bool is_incognito) {
427 for (const auto& pair : notifications_) {
428 NotificationData* data = pair.first;
429 if (data->notification_id == notification_id &&
430 data->profile_id == profile_id && data->is_incognito == is_incognito) {
431 return data;
432 }
433 }
434
435 return nullptr; 109 return nullptr;
436 } 110 }
437 111
438 NotificationPlatformBridgeLinux::NotificationData* 112 class NotificationPlatformBridgeLinux::NativeNotificationThread
439 NotificationPlatformBridgeLinux::FindNotificationData(uint32_t dbus_id) { 113 : public base::Thread {
440 for (const auto& pair : notifications_) { 114 public:
441 NotificationData* data = pair.first; 115 NativeNotificationThread()
442 if (data->dbus_id == dbus_id) 116 : base::Thread("FDO Notifications D-Bus thread"),
443 return data; 117 event_(base::WaitableEvent::ResetPolicy::MANUAL,
444 } 118 base::WaitableEvent::InitialState::NOT_SIGNALED) {
445 119 base::Thread::Options thread_options(base::MessageLoop::TYPE_IO, 0);
446 return nullptr; 120 if (!StartWithOptions(thread_options))
447 } 121 event_.Signal();
448 122 }
449 void NotificationPlatformBridgeLinux::ForwardNotificationOperation( 123
450 uint32_t dbus_id, 124 ~NativeNotificationThread() override {
451 NotificationCommon::Operation operation, 125 Stop();
452 int action_index) { 126 // All of the resources should have been cleaned up on the
453 NotificationData* data = FindNotificationData(dbus_id); 127 // notification thread.
454 if (!data) { 128 DCHECK(notifications_.empty());
455 // This notification either belongs to a different app or we 129 }
456 // already removed the NotificationData after sending a 130
457 // "CloseNotification" message. 131 void Display(NotificationCommon::Type notification_type,
458 return; 132 const std::string& notification_id,
459 } 133 const std::string& profile_id,
460 134 bool is_incognito,
461 ProfileManager* profile_manager = g_browser_process->profile_manager(); 135 const NotificationThreadSafe& notification) {
462 DCHECK(profile_manager); 136 NotificationData* data =
463 137 FindNotificationData(notification_id, profile_id, is_incognito);
464 profile_manager->LoadProfile( 138 if (data) {
465 data->profile_id, data->is_incognito, 139 // Update an existing notification.
466 base::Bind(&ProfileLoadedCallback, operation, data->notification_type, 140 data->notification_type = notification_type;
467 data->origin_url.spec(), data->notification_id, action_index, 141 data->resource_files.clear();
468 base::NullableString16())); 142 } else {
469 } 143 // Send the notification for the first time.
470 144 data =
471 void NotificationPlatformBridgeLinux::GSignalReceiver(GDBusProxy* proxy, 145 new NotificationData(notification_type, notification_id, profile_id,
472 const char* sender_name, 146 is_incognito, notification.origin_url());
473 const char* sender_signal, 147 notifications_.emplace(data, base::WrapUnique(data));
474 GVariant* parameters) { 148 }
475 uint32_t dbus_id = 0; 149
476 if (strcmp("NotificationClosed", sender_signal) == 0 && 150 dbus::MethodCall method_call(kFreedesktopNotificationsName, "Notify");
477 g_variant_is_of_type(parameters, G_VARIANT_TYPE("(uu)"))) { 151 dbus::MessageWriter writer(&method_call);
478 uint32_t reason; 152
479 g_variant_get(parameters, "(uu)", &dbus_id, &reason); 153 // app_name passed implicitly via desktop-entry.
480 ForwardNotificationOperation(dbus_id, NotificationCommon::CLOSE, -1); 154 writer.AppendString("");
481 // std::unordered_map::erase(nullptr) is safe here. 155
482 notifications_.erase(FindNotificationData(dbus_id)); 156 writer.AppendUint32(data->dbus_id);
483 } else if (strcmp("ActionInvoked", sender_signal) == 0 && 157
484 g_variant_is_of_type(parameters, G_VARIANT_TYPE("(us)"))) { 158 // app_icon passed implicitly via desktop-entry.
485 const gchar* action = nullptr; 159 writer.AppendString("");
486 g_variant_get(parameters, "(u&s)", &dbus_id, &action); 160
487 DCHECK(action); 161 const std::string title = base::UTF16ToUTF8(notification.title());
488 162 writer.AppendString(title);
489 if (strcmp(action, "default") == 0) { 163
164 const std::string message = base::UTF16ToUTF8(notification.message());
165 writer.AppendString(message);
166
167 // Even-indexed elements in this vector are action IDs passed back to
168 // us in OnActionInvoked(). Odd-indexed ones contain the button text.
169 std::vector<std::string> actions;
170 data->action_start = data->action_end;
171 for (const auto& button_info : notification.buttons()) {
172 // FDO notification buttons can contain either an icon or a label,
173 // but not both, and the type of all buttons must be the same (all
174 // labels or all icons), so always use labels.
175 std::string id = base::SizeTToString(data->action_end++);
176 const std::string label = base::UTF16ToUTF8(button_info.title);
177 actions.push_back(id);
178 actions.push_back(label);
179 }
180 if (notification.clickable()) {
181 // Special case: the pair ("default", "") will not add a button,
182 // but instead makes the entire notification clickable.
183 actions.push_back("default");
184 actions.push_back("");
185 }
186 // Always add a settings button.
187 actions.push_back("settings");
188 actions.push_back("Settings");
189 writer.AppendArrayOfStrings(actions);
190
191 dbus::MessageWriter hints_writer(nullptr);
192 writer.OpenArray("{sv}", &hints_writer);
193 dbus::MessageWriter urgency_writer(nullptr);
194 hints_writer.OpenDictEntry(&urgency_writer);
195 urgency_writer.AppendString("urgency");
196 urgency_writer.AppendVariantOfUint32(
197 NotificationPriorityToFdoUrgency(notification.priority()));
198 hints_writer.CloseContainer(&urgency_writer);
199
200 std::unique_ptr<base::Environment> env = base::Environment::Create();
201 std::string desktop_file =
202 shell_integration_linux::GetDesktopName(env.get());
203 const char kDesktopFileSuffix[] = ".desktop";
204 DCHECK(base::EndsWith(desktop_file, kDesktopFileSuffix,
205 base::CompareCase::SENSITIVE));
206 desktop_file = desktop_file.substr(
207 0, desktop_file.size() - strlen(kDesktopFileSuffix));
208 dbus::MessageWriter desktop_entry_writer(nullptr);
209 hints_writer.OpenDictEntry(&desktop_entry_writer);
210 desktop_entry_writer.AppendString("desktop-entry");
211 desktop_entry_writer.AppendVariantOfString(desktop_file);
212 hints_writer.CloseContainer(&desktop_entry_writer);
213
214 base::FilePath icon_file = WriteDataToTmpFile(notification.icon_data);
215 if (!icon_file.empty()) {
216 dbus::MessageWriter image_path_writer(nullptr);
217 hints_writer.OpenDictEntry(&image_path_writer);
218 image_path_writer.AppendString("image-path");
219 image_path_writer.AppendVariantOfString(icon_file.value());
220 hints_writer.CloseContainer(&image_path_writer);
221 data->resource_files.push_back(base::MakeUnique<ResourceFile>(icon_file));
222 }
223
224 writer.CloseContainer(&hints_writer);
225
226 int32_t expire_timeout = -1;
Lei Zhang 2017/04/17 23:32:13 const? kExpireTimeoutNever maybe?
Tom (Use chromium acct) 2017/04/18 01:41:11 Done.
227 writer.AppendInt32(expire_timeout);
228
229 if (data->dbus_id) {
230 notification_proxy_->CallMethod(&method_call,
231 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
232 base::Bind([](dbus::Response*) {}));
233 } else {
234 std::unique_ptr<dbus::Response> response =
235 notification_proxy_->CallMethodAndBlock(
236 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
237 if (response) {
238 dbus::MessageReader reader(response.get());
239 reader.PopUint32(&data->dbus_id);
240 }
241 if (!data->dbus_id) {
242 // There was some sort of error with creating the notification.
243 notifications_.erase(data);
244 }
245 }
246 }
247
248 void Close(const std::string& profile_id,
249 const std::string& notification_id) {
250 std::vector<NotificationData*> to_erase;
251 for (const auto& pair : notifications_) {
252 NotificationData* data = pair.first;
253 if (data->notification_id == notification_id &&
254 data->profile_id == profile_id) {
255 dbus::MethodCall method_call(kFreedesktopNotificationsName,
256 "CloseNotification");
257 dbus::MessageWriter writer(&method_call);
258 writer.AppendUint32(data->dbus_id);
259 notification_proxy_->CallMethod(&method_call,
260 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
261 base::Bind([](dbus::Response*) {}));
262 }
263 }
264 for (NotificationData* data : to_erase)
265 notifications_.erase(data);
266 }
267
268 void GetDisplayed(const std::string& profile_id,
269 bool incognito,
270 const DisplayedNotificationsCallback& callback) const {
271 content::BrowserThread::PostTask(
272 content::BrowserThread::UI, FROM_HERE,
273 base::Bind(callback,
274 base::Passed(base::MakeUnique<std::set<std::string>>()),
275 false));
276 }
277
278 bool BlockUntilReady() {
279 event_.Wait();
280 return notification_proxy_initialized_.IsSet();
281 }
282
283 private:
284 struct ResourceFile {
285 explicit ResourceFile(const base::FilePath& file_path)
286 : file_path(file_path) {}
287 ~ResourceFile() { base::DeleteFile(file_path, false); }
288 base::FilePath file_path;
289 };
290
291 struct NotificationData {
292 NotificationData(NotificationCommon::Type notification_type,
293 const std::string& notification_id,
294 const std::string& profile_id,
295 bool is_incognito,
296 const GURL& origin_url)
297 : notification_type(notification_type),
298 notification_id(notification_id),
299 profile_id(profile_id),
300 is_incognito(is_incognito),
301 origin_url(origin_url) {}
302
303 // The ID used by the notification server. Will be 0 until the
304 // first "Notify" message completes.
305 uint32_t dbus_id = 0;
306
307 // Same parameters used by NotificationPlatformBridge::Display().
308 NotificationCommon::Type notification_type;
309 const std::string notification_id;
310 const std::string profile_id;
311 const bool is_incognito;
312
313 // A copy of the origin_url from the underlying
314 // message_center::Notification. Used to pass back to
315 // NativeNotificationDisplayService.
316 const GURL origin_url;
317
318 // Used to keep track of the IDs of the buttons currently displayed
319 // on this notification. The valid range of action IDs is
320 // [action_start, action_end).
321 size_t action_start = 0;
322 size_t action_end = 0;
323
324 // Temporary resource files associated with the notification that
325 // should be cleaned up when the notification is closed or on
326 // shutdown.
327 std::vector<std::unique_ptr<ResourceFile>> resource_files;
328 };
329
330 void Init() override {
331 dbus::Bus::Options bus_options;
332 bus_options.bus_type = dbus::Bus::SESSION;
333 bus_options.connection_type = dbus::Bus::PRIVATE;
334 bus_ = new dbus::Bus(bus_options);
335
336 notification_proxy_ =
337 bus_->GetObjectProxy(kFreedesktopNotificationsName,
338 dbus::ObjectPath(kFreedesktopNotificationsPath));
339 if (notification_proxy_)
340 notification_proxy_initialized_.Set();
341 event_.Signal();
342 if (!notification_proxy_)
343 return;
344
345 auto on_signal_connected = [](const std::string&, const std::string&,
346 bool success) { DCHECK(success); };
347 notification_proxy_->ConnectToSignal(
348 kFreedesktopNotificationsName, "ActionInvoked",
349 base::Bind(&NativeNotificationThread::OnActionInvoked,
350 base::Unretained(this)),
351 base::Bind(on_signal_connected));
352 notification_proxy_->ConnectToSignal(
353 kFreedesktopNotificationsName, "NotificationClosed",
354 base::Bind(&NativeNotificationThread::OnNotificationClosed,
355 base::Unretained(this)),
356 base::Bind(on_signal_connected));
357 }
358
359 void CleanUp() override {
360 bus_->ShutdownAndBlock();
361 notifications_.clear();
362 }
363
364 NotificationData* FindNotificationData(const std::string& notification_id,
365 const std::string& profile_id,
366 bool is_incognito) {
367 for (const auto& pair : notifications_) {
368 NotificationData* data = pair.first;
369 if (data->notification_id == notification_id &&
370 data->profile_id == profile_id &&
371 data->is_incognito == is_incognito) {
372 return data;
373 }
374 }
375
376 return nullptr;
377 }
378
379 NotificationData* FindNotificationData(uint32_t dbus_id) {
380 for (const auto& pair : notifications_) {
381 NotificationData* data = pair.first;
382 if (data->dbus_id == dbus_id)
383 return data;
384 }
385
386 return nullptr;
387 }
388
389 void ForwardNotificationOperation(uint32_t dbus_id,
390 NotificationCommon::Operation operation,
391 int action_index) {
392 NotificationData* data = FindNotificationData(dbus_id);
393 if (!data) {
394 // This notification either belongs to a different app or we
395 // already removed the NotificationData after sending a
396 // "CloseNotification" message.
397 return;
398 }
399
400 content::BrowserThread::PostTask(
401 content::BrowserThread::UI, FROM_HERE,
402 base::Bind(
403 [](NotificationCommon::Operation operation,
404 NotificationCommon::Type notification_type,
405 const std::string& origin, const std::string& notification_id,
406 int action_index, const std::string& profile_id,
407 bool is_incognito) {
408 ProfileManager* profile_manager =
409 g_browser_process->profile_manager();
410 DCHECK(profile_manager);
411
412 profile_manager->LoadProfile(
413 profile_id, is_incognito,
414 base::Bind(&ProfileLoadedCallback, operation,
415 notification_type, origin, notification_id,
416 action_index, base::NullableString16()));
417 },
418 operation, data->notification_type, data->origin_url.spec(),
419 data->notification_id, action_index, data->profile_id,
420 data->is_incognito));
421 }
422
423 void OnActionInvoked(dbus::Signal* signal) {
424 dbus::MessageReader reader(signal);
425 uint32_t dbus_id;
426 if (!reader.PopUint32(&dbus_id))
427 return;
428 std::string action;
429 if (!reader.PopString(&action))
430 return;
431
432 if (action == "default") {
490 ForwardNotificationOperation(dbus_id, NotificationCommon::CLICK, -1); 433 ForwardNotificationOperation(dbus_id, NotificationCommon::CLICK, -1);
491 } else if (strcmp(action, "settings") == 0) { 434 } else if (action == "settings") {
492 ForwardNotificationOperation(dbus_id, NotificationCommon::SETTINGS, -1); 435 ForwardNotificationOperation(dbus_id, NotificationCommon::SETTINGS, -1);
493 } else { 436 } else {
494 size_t id; 437 size_t id;
495 if (!base::StringToSizeT(action, &id)) 438 if (!base::StringToSizeT(action, &id))
496 return; 439 return;
497 NotificationData* data = FindNotificationData(dbus_id); 440 NotificationData* data = FindNotificationData(dbus_id);
498 if (!data) 441 if (!data)
499 return; 442 return;
500 size_t n_buttons = data->action_end - data->action_start; 443 size_t n_buttons = data->action_end - data->action_start;
501 size_t id_zero_based = id - data->action_start; 444 size_t id_zero_based = id - data->action_start;
502 if (id_zero_based >= n_buttons) 445 if (id_zero_based >= n_buttons)
503 return; 446 return;
504 ForwardNotificationOperation(dbus_id, NotificationCommon::CLICK, 447 ForwardNotificationOperation(dbus_id, NotificationCommon::CLICK,
505 id_zero_based); 448 id_zero_based);
506 } 449 }
507 } 450 }
451
452 void OnNotificationClosed(dbus::Signal* signal) {
453 dbus::MessageReader reader(signal);
454 uint32_t dbus_id;
455 if (!reader.PopUint32(&dbus_id))
456 return;
457 ForwardNotificationOperation(dbus_id, NotificationCommon::CLOSE, -1);
458 // std::unordered_map::erase(nullptr) is safe here.
459 notifications_.erase(FindNotificationData(dbus_id));
460 }
461
462 scoped_refptr<dbus::Bus> bus_;
463
464 dbus::ObjectProxy* notification_proxy_ = nullptr;
465 // Event that will be signaled once |notification_proxy_| is done
466 // connecting, or has failed to connect.
467 base::WaitableEvent event_;
468
469 // Set if the |notification_proxy_| has initialized without error.
470 // Only valid once |event_| has been signaled.
471 base::AtomicFlag notification_proxy_initialized_;
472
473 // A std::set<std::unique_ptr<T>> doesn't work well because
474 // eg. std::set::erase(T) would require a std::unique_ptr<T>
475 // argument, so the data would get double-destructed.
476 template <typename T>
477 using UnorderedUniqueSet = std::unordered_map<T*, std::unique_ptr<T>>;
478
479 UnorderedUniqueSet<NotificationData> notifications_;
480 };
481
482 NotificationPlatformBridgeLinux::NotificationPlatformBridgeLinux()
483 : thread_(new NativeNotificationThread()) {}
484
485 NotificationPlatformBridgeLinux::~NotificationPlatformBridgeLinux() {}
486
487 void NotificationPlatformBridgeLinux::Display(
488 NotificationCommon::Type notification_type,
489 const std::string& notification_id,
490 const std::string& profile_id,
491 bool is_incognito,
492 const Notification& notification) {
493 NotificationThreadSafe notification_thread_safe(
494 notification, notification.icon().As1xPNGBytes());
495 thread_->task_runner()->PostTask(
496 FROM_HERE, base::Bind(&NativeNotificationThread::Display,
497 base::Unretained(thread_.get()), notification_type,
498 notification_id, profile_id, is_incognito,
499 notification_thread_safe));
508 } 500 }
501
502 void NotificationPlatformBridgeLinux::Close(
503 const std::string& profile_id,
504 const std::string& notification_id) {
505 thread_->task_runner()->PostTask(
506 FROM_HERE,
507 base::Bind(&NativeNotificationThread::Close,
508 base::Unretained(thread_.get()), profile_id, notification_id));
509 }
510
511 void NotificationPlatformBridgeLinux::GetDisplayed(
512 const std::string& profile_id,
513 bool incognito,
514 const DisplayedNotificationsCallback& callback) const {
515 thread_->task_runner()->PostTask(
516 FROM_HERE, base::Bind(&NativeNotificationThread::GetDisplayed,
517 base::Unretained(thread_.get()), profile_id,
518 incognito, callback));
519 }
520
521 bool NotificationPlatformBridgeLinux::BlockUntilReady() {
522 return thread_->BlockUntilReady();
523 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698