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

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

Issue 2821533003: Refactor NotificationPlatformBridgeLinux (Closed)
Patch Set: final comments 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/barrier_closure.h"
9 #include "base/files/file_util.h" 10 #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" 11 #include "base/strings/nullable_string16.h"
13 #include "base/strings/string_number_conversions.h" 12 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_util.h" 13 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h" 14 #include "base/strings/utf_string_conversions.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" 17 #include "chrome/browser/chrome_notification_types.h"
19 #include "chrome/browser/notifications/native_notification_display_service.h" 18 #include "chrome/browser/notifications/native_notification_display_service.h"
20 #include "chrome/browser/notifications/notification.h" 19 #include "chrome/browser/notifications/notification.h"
21 #include "chrome/browser/notifications/notification_display_service_factory.h" 20 #include "chrome/browser/notifications/notification_display_service_factory.h"
22 #include "chrome/browser/profiles/profile_manager.h" 21 #include "chrome/browser/profiles/profile_manager.h"
23 #include "chrome/browser/shell_integration_linux.h" 22 #include "chrome/browser/shell_integration_linux.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "content/public/browser/notification_service.h" 24 #include "content/public/browser/notification_service.h"
25 #include "dbus/bus.h"
26 #include "dbus/message.h"
27 #include "dbus/object_proxy.h"
28 #include "ui/gfx/image/image_skia.h"
25 29
26 namespace { 30 namespace {
27 31
28 const char kFreedesktopNotificationsName[] = "org.freedesktop.Notifications"; 32 const char kFreedesktopNotificationsName[] = "org.freedesktop.Notifications";
29 const char kFreedesktopNotificationsPath[] = "/org/freedesktop/Notifications"; 33 const char kFreedesktopNotificationsPath[] = "/org/freedesktop/Notifications";
30 34
31 void AddActionToNotification(GVariantBuilder* actions_builder, 35 const char kDefaultButtonId[] = "default";
32 const char* action_id, 36 const char kSettingsButtonId[] = "settings";
33 const char* button_label) { 37
34 g_variant_builder_add(actions_builder, "s", action_id); 38 gfx::Image DeepCopyImage(const gfx::Image& image) {
35 g_variant_builder_add(actions_builder, "s", button_label); 39 std::unique_ptr<gfx::ImageSkia> image_skia(image.CopyImageSkia());
40 return gfx::Image(*image_skia);
36 } 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)
86 return; 73 return;
87 74
88 NotificationDisplayService* display_service = 75 NotificationDisplayService* display_service =
89 NotificationDisplayServiceFactory::GetForProfile(profile); 76 NotificationDisplayServiceFactory::GetForProfile(profile);
90 77
91 static_cast<NativeNotificationDisplayService*>(display_service) 78 static_cast<NativeNotificationDisplayService*>(display_service)
92 ->ProcessNotificationOperation(operation, notification_type, origin, 79 ->ProcessNotificationOperation(operation, notification_type, origin,
93 notification_id, action_index, reply); 80 notification_id, action_index, reply);
94 } 81 }
95 82
83 void ForwardNotificationOperationOnUiThread(
84 NotificationCommon::Operation operation,
85 NotificationCommon::Type notification_type,
86 const std::string& origin,
87 const std::string& notification_id,
88 int action_index,
89 const std::string& profile_id,
90 bool is_incognito) {
91 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
92 ProfileManager* profile_manager = g_browser_process->profile_manager();
93
94 profile_manager->LoadProfile(
95 profile_id, is_incognito,
96 base::Bind(&ProfileLoadedCallback, operation, notification_type, origin,
97 notification_id, action_index, base::NullableString16()));
98 }
99
96 // Writes |data| to a new temporary file and returns its path. 100 // Writes |data| to a new temporary file and returns its path.
97 // Returns base::FilePath() on failure. 101 // Returns base::FilePath() on failure.
98 base::FilePath WriteDataToTmpFile( 102 base::FilePath WriteDataToTmpFile(
99 const scoped_refptr<base::RefCountedMemory>& data) { 103 const scoped_refptr<base::RefCountedMemory>& data) {
100 int data_len = data->size(); 104 int data_len = data->size();
101 if (data_len == 0) 105 if (data_len == 0)
102 return base::FilePath(); 106 return base::FilePath();
103 base::FilePath file_path; 107 base::FilePath file_path;
104 if (!base::CreateTemporaryFile(&file_path)) 108 if (!base::CreateTemporaryFile(&file_path))
105 return base::FilePath(); 109 return base::FilePath();
106 if (base::WriteFile(file_path, data->front_as<char>(), data_len) != 110 if (base::WriteFile(file_path, data->front_as<char>(), data_len) !=
107 data_len) { 111 data_len) {
108 base::DeleteFile(file_path, false); 112 base::DeleteFile(file_path, false);
109 return base::FilePath(); 113 return base::FilePath();
110 } 114 }
111 return file_path; 115 return file_path;
112 } 116 }
113 117
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::BindOnce(base::IgnoreResult(base::DeleteFile), file_path, false));
124 }
125
126 } // namespace 118 } // namespace
127 119
128 // static 120 // static
129 NotificationPlatformBridge* NotificationPlatformBridge::Create() { 121 NotificationPlatformBridge* NotificationPlatformBridge::Create() {
130 GDBusProxy* notification_proxy = g_dbus_proxy_new_for_bus_sync( 122 return new NotificationPlatformBridgeLinux();
131 G_BUS_TYPE_SESSION, 123 }
132 static_cast<GDBusProxyFlags>(G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | 124
133 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START), 125 class NotificationPlatformBridgeLinuxImpl
134 nullptr, kFreedesktopNotificationsName, kFreedesktopNotificationsPath, 126 : public NotificationPlatformBridge,
135 kFreedesktopNotificationsName, nullptr, nullptr); 127 public content::NotificationObserver,
136 if (!notification_proxy) 128 public base::RefCountedThreadSafe<NotificationPlatformBridgeLinuxImpl> {
129 public:
130 NotificationPlatformBridgeLinuxImpl()
131 : task_runner_(base::CreateSingleThreadTaskRunnerWithTraits(
gab 2017/05/25 01:09:36 Surveying existing use cases of base::CreateSingle
132 base::TaskTraits().MayBlock().WithPriority(
133 base::TaskPriority::USER_BLOCKING))) {
134 registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
135 content::NotificationService::AllSources());
136 PostTaskToTaskRunnerThread(
137 base::BindOnce(&NotificationPlatformBridgeLinuxImpl::Init, this));
138 }
139
140 void Display(NotificationCommon::Type notification_type,
141 const std::string& notification_id,
142 const std::string& profile_id,
143 bool is_incognito,
144 const Notification& notification) override {
145 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
146 // Notifications contain gfx::Image's which have reference counts
147 // that are not thread safe. Because of this, we duplicate the
148 // notification and its images. Wrap the notification in a
149 // unique_ptr to transfer ownership of the notification (and the
150 // non-thread-safe reference counts) to the task runner thread.
151 auto notification_copy = base::MakeUnique<Notification>(notification);
152 notification_copy->set_icon(DeepCopyImage(notification_copy->icon()));
153 notification_copy->set_image(gfx::Image());
154 notification_copy->set_small_image(gfx::Image());
155 for (size_t i = 0; i < notification_copy->buttons().size(); i++)
156 notification_copy->SetButtonIcon(i, gfx::Image());
157
158 PostTaskToTaskRunnerThread(base::BindOnce(
159 &NotificationPlatformBridgeLinuxImpl::DisplayOnTaskRunner, this,
160 notification_type, notification_id, profile_id, is_incognito,
161 base::Passed(&notification_copy)));
162 }
163
164 void Close(const std::string& profile_id,
165 const std::string& notification_id) override {
166 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
167 PostTaskToTaskRunnerThread(
168 base::BindOnce(&NotificationPlatformBridgeLinuxImpl::CloseOnTaskRunner,
169 this, profile_id, notification_id));
170 }
171
172 void GetDisplayed(
173 const std::string& profile_id,
174 bool incognito,
175 const GetDisplayedNotificationsCallback& callback) const override {
176 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
177 PostTaskToTaskRunnerThread(base::BindOnce(
178 &NotificationPlatformBridgeLinuxImpl::GetDisplayedOnTaskRunner, this,
179 profile_id, incognito, callback));
180 }
181
182 void SetReadyCallback(NotificationBridgeReadyCallback callback) override {
183 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
184 if (connected_.has_value()) {
185 std::move(callback).Run(connected_.value());
186 } else {
187 on_connected_callbacks_.push_back(std::move(callback));
188 }
189 }
190
191 private:
192 friend class base::RefCountedThreadSafe<NotificationPlatformBridgeLinuxImpl>;
193
194 struct ResourceFile {
195 explicit ResourceFile(const base::FilePath& file_path)
196 : file_path(file_path) {}
197 ~ResourceFile() { base::DeleteFile(file_path, false); }
198 const base::FilePath file_path;
199 };
200
201 struct NotificationData {
202 NotificationData(NotificationCommon::Type notification_type,
203 const std::string& notification_id,
204 const std::string& profile_id,
205 bool is_incognito,
206 const GURL& origin_url)
207 : notification_type(notification_type),
208 notification_id(notification_id),
209 profile_id(profile_id),
210 is_incognito(is_incognito),
211 origin_url(origin_url) {}
212
213 // The ID used by the notification server. Will be 0 until the
214 // first "Notify" message completes.
215 uint32_t dbus_id = 0;
216
217 // Same parameters used by NotificationPlatformBridge::Display().
218 NotificationCommon::Type notification_type;
219 const std::string notification_id;
220 const std::string profile_id;
221 const bool is_incognito;
222
223 // A copy of the origin_url from the underlying
224 // message_center::Notification. Used to pass back to
225 // NativeNotificationDisplayService.
226 const GURL origin_url;
227
228 // Used to keep track of the IDs of the buttons currently displayed
229 // on this notification. The valid range of action IDs is
230 // [action_start, action_end).
231 size_t action_start = 0;
232 size_t action_end = 0;
233
234 // Temporary resource files associated with the notification that
235 // should be cleaned up when the notification is closed or on
236 // shutdown.
237 std::vector<std::unique_ptr<ResourceFile>> resource_files;
238 };
239
240 ~NotificationPlatformBridgeLinuxImpl() override {
241 DCHECK(!bus_);
242 DCHECK(notifications_.empty());
243 }
244
245 void Observe(int type,
246 const content::NotificationSource& source,
247 const content::NotificationDetails& details) override {
248 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
249 DCHECK_EQ(chrome::NOTIFICATION_APP_TERMINATING, type);
250 // The browser process is about to exit. Post the CleanUp() task
251 // while we still can.
252 CleanUp();
253 }
254
255 void PostTaskToUiThread(base::OnceClosure closure) const {
256 DCHECK(task_runner_->RunsTasksOnCurrentThread());
257 bool success = content::BrowserThread::PostTask(
258 content::BrowserThread::UI, FROM_HERE, std::move(closure));
259 DCHECK(success);
260 }
261
262 void PostTaskToTaskRunnerThread(base::OnceClosure closure) const {
263 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
264 DCHECK(task_runner_);
265 bool success = task_runner_->PostTask(FROM_HERE, std::move(closure));
266 DCHECK(success);
267 }
268
269 // Sets up the D-Bus connection.
270 void Init() {
271 DCHECK(task_runner_->RunsTasksOnCurrentThread());
272 dbus::Bus::Options bus_options;
273 bus_options.bus_type = dbus::Bus::SESSION;
274 bus_options.connection_type = dbus::Bus::PRIVATE;
275 bus_options.dbus_task_runner = task_runner_;
gab 2017/05/25 01:09:36 From the documentation on bus.h this doesn't seem
276 bus_ = make_scoped_refptr(new dbus::Bus(bus_options));
277
278 notification_proxy_ =
279 bus_->GetObjectProxy(kFreedesktopNotificationsName,
280 dbus::ObjectPath(kFreedesktopNotificationsPath));
281 if (!notification_proxy_) {
282 OnConnectionInitializationFinishedOnTaskRunner(false);
283 return;
284 }
285
286 connected_signals_barrier_ = base::BarrierClosure(
287 2, base::Bind(&NotificationPlatformBridgeLinuxImpl::
288 OnConnectionInitializationFinishedOnTaskRunner,
289 this, true));
290 notification_proxy_->ConnectToSignal(
291 kFreedesktopNotificationsName, "ActionInvoked",
292 base::Bind(&NotificationPlatformBridgeLinuxImpl::OnActionInvoked, this),
293 base::Bind(&NotificationPlatformBridgeLinuxImpl::OnSignalConnected,
294 this));
295 notification_proxy_->ConnectToSignal(
296 kFreedesktopNotificationsName, "NotificationClosed",
297 base::Bind(&NotificationPlatformBridgeLinuxImpl::OnNotificationClosed,
298 this),
299 base::Bind(&NotificationPlatformBridgeLinuxImpl::OnSignalConnected,
300 this));
301 }
302
303 void CleanUp() {
304 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
305 if (cleanup_posted_)
306 return;
307 PostTaskToTaskRunnerThread(base::BindOnce(
308 &NotificationPlatformBridgeLinuxImpl::CleanUpOnTaskRunner, this));
309 cleanup_posted_ = true;
310 }
311
312 void CleanUpOnTaskRunner() {
313 DCHECK(task_runner_->RunsTasksOnCurrentThread());
314 bus_->ShutdownAndBlock();
315 bus_ = nullptr;
316 notification_proxy_ = nullptr;
317 notifications_.clear();
318 }
319
320 // Makes the "Notify" call to D-Bus.
321 void DisplayOnTaskRunner(NotificationCommon::Type notification_type,
322 const std::string& notification_id,
323 const std::string& profile_id,
324 bool is_incognito,
325 std::unique_ptr<Notification> notification) {
326 DCHECK(task_runner_->RunsTasksOnCurrentThread());
327 NotificationData* data =
328 FindNotificationData(notification_id, profile_id, is_incognito);
329 if (data) {
330 // Update an existing notification.
331 data->notification_type = notification_type;
332 data->resource_files.clear();
333 } else {
334 // Send the notification for the first time.
335 data =
336 new NotificationData(notification_type, notification_id, profile_id,
337 is_incognito, notification->origin_url());
338 notifications_.emplace(data, base::WrapUnique(data));
339 }
340
341 dbus::MethodCall method_call(kFreedesktopNotificationsName, "Notify");
342 dbus::MessageWriter writer(&method_call);
343
344 // app_name passed implicitly via desktop-entry.
345 writer.AppendString("");
346
347 writer.AppendUint32(data->dbus_id);
348
349 // app_icon passed implicitly via desktop-entry.
350 writer.AppendString("");
351
352 writer.AppendString(base::UTF16ToUTF8(notification->title()));
353
354 writer.AppendString(base::UTF16ToUTF8(notification->message()));
355
356 // Even-indexed elements in this vector are action IDs passed back to
357 // us in OnActionInvoked(). Odd-indexed ones contain the button text.
358 std::vector<std::string> actions;
359 data->action_start = data->action_end;
360 for (const auto& button_info : notification->buttons()) {
361 // FDO notification buttons can contain either an icon or a label,
362 // but not both, and the type of all buttons must be the same (all
363 // labels or all icons), so always use labels.
364 const std::string id = base::SizeTToString(data->action_end++);
365 const std::string label = base::UTF16ToUTF8(button_info.title);
366 actions.push_back(id);
367 actions.push_back(label);
368 }
369 if (notification->clickable()) {
370 // Special case: the pair ("default", "") will not add a button,
371 // but instead makes the entire notification clickable.
372 actions.push_back(kDefaultButtonId);
373 actions.push_back("");
374 }
375 // Always add a settings button.
376 actions.push_back(kSettingsButtonId);
377 actions.push_back("Settings");
378 writer.AppendArrayOfStrings(actions);
379
380 dbus::MessageWriter hints_writer(nullptr);
381 writer.OpenArray("{sv}", &hints_writer);
382 dbus::MessageWriter urgency_writer(nullptr);
383 hints_writer.OpenDictEntry(&urgency_writer);
384 urgency_writer.AppendString("urgency");
385 urgency_writer.AppendVariantOfUint32(
386 NotificationPriorityToFdoUrgency(notification->priority()));
387 hints_writer.CloseContainer(&urgency_writer);
388
389 std::unique_ptr<base::Environment> env = base::Environment::Create();
390 base::FilePath desktop_file(
391 shell_integration_linux::GetDesktopName(env.get()));
392 const char kDesktopFileSuffix[] = ".desktop";
393 DCHECK(base::EndsWith(desktop_file.value(), kDesktopFileSuffix,
394 base::CompareCase::SENSITIVE));
395 desktop_file = desktop_file.RemoveFinalExtension();
396 dbus::MessageWriter desktop_entry_writer(nullptr);
397 hints_writer.OpenDictEntry(&desktop_entry_writer);
398 desktop_entry_writer.AppendString("desktop-entry");
399 desktop_entry_writer.AppendVariantOfString(desktop_file.value());
400 hints_writer.CloseContainer(&desktop_entry_writer);
401
402 base::FilePath icon_file =
403 WriteDataToTmpFile(notification->icon().As1xPNGBytes());
404 if (!icon_file.empty()) {
405 dbus::MessageWriter image_path_writer(nullptr);
406 hints_writer.OpenDictEntry(&image_path_writer);
407 image_path_writer.AppendString("image-path");
408 image_path_writer.AppendVariantOfString(icon_file.value());
409 hints_writer.CloseContainer(&image_path_writer);
410 data->resource_files.push_back(base::MakeUnique<ResourceFile>(icon_file));
411 }
412
413 writer.CloseContainer(&hints_writer);
414
415 const int32_t kExpireTimeoutDefault = -1;
416 writer.AppendInt32(kExpireTimeoutDefault);
417
418 std::unique_ptr<dbus::Response> response =
419 notification_proxy_->CallMethodAndBlock(
420 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
421 if (response) {
422 dbus::MessageReader reader(response.get());
423 reader.PopUint32(&data->dbus_id);
424 }
425 if (!data->dbus_id) {
426 // There was some sort of error with creating the notification.
427 notifications_.erase(data);
428 }
429 }
430
431 // Makes the "CloseNotification" call to D-Bus.
432 void CloseOnTaskRunner(const std::string& profile_id,
433 const std::string& notification_id) {
434 DCHECK(task_runner_->RunsTasksOnCurrentThread());
435 std::vector<NotificationData*> to_erase;
436 for (const auto& pair : notifications_) {
437 NotificationData* data = pair.first;
438 if (data->notification_id == notification_id &&
439 data->profile_id == profile_id) {
440 dbus::MethodCall method_call(kFreedesktopNotificationsName,
441 "CloseNotification");
442 dbus::MessageWriter writer(&method_call);
443 writer.AppendUint32(data->dbus_id);
444 notification_proxy_->CallMethodAndBlock(
445 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
446 }
447 }
448 for (NotificationData* data : to_erase)
449 notifications_.erase(data);
450 }
451
452 void GetDisplayedOnTaskRunner(
453 const std::string& profile_id,
454 bool incognito,
455 const GetDisplayedNotificationsCallback& callback) const {
456 DCHECK(task_runner_->RunsTasksOnCurrentThread());
457 // TODO(thomasanderson): Implement.
458 PostTaskToUiThread(base::BindOnce(
459 callback, base::Passed(base::MakeUnique<std::set<std::string>>()),
460 false));
461 }
462
463 NotificationData* FindNotificationData(const std::string& notification_id,
464 const std::string& profile_id,
465 bool is_incognito) {
466 DCHECK(task_runner_->RunsTasksOnCurrentThread());
467 for (const auto& pair : notifications_) {
468 NotificationData* data = pair.first;
469 if (data->notification_id == notification_id &&
470 data->profile_id == profile_id &&
471 data->is_incognito == is_incognito) {
472 return data;
473 }
474 }
475
137 return nullptr; 476 return nullptr;
138 return new NotificationPlatformBridgeLinux(notification_proxy); 477 }
139 } 478
140 479 NotificationData* FindNotificationData(uint32_t dbus_id) {
141 struct NotificationPlatformBridgeLinux::NotificationData { 480 DCHECK(task_runner_->RunsTasksOnCurrentThread());
142 NotificationData(NotificationCommon::Type notification_type, 481 for (const auto& pair : notifications_) {
143 const std::string& notification_id, 482 NotificationData* data = pair.first;
144 const std::string& profile_id, 483 if (data->dbus_id == dbus_id)
145 bool is_incognito, 484 return data;
146 const GURL& origin_url) 485 }
147 : notification_type(notification_type), 486
148 notification_id(notification_id), 487 return nullptr;
149 profile_id(profile_id), 488 }
150 is_incognito(is_incognito), 489
151 origin_url(origin_url), 490 void ForwardNotificationOperation(NotificationData* data,
152 weak_factory(this) {} 491 NotificationCommon::Operation operation,
153 492 int action_index) {
154 ~NotificationData() { 493 DCHECK(task_runner_->RunsTasksOnCurrentThread());
155 if (cancellable) 494 PostTaskToUiThread(base::BindOnce(
156 g_cancellable_cancel(cancellable); 495 ForwardNotificationOperationOnUiThread, operation,
157 ResetResourceFiles(); 496 data->notification_type, data->origin_url.spec(), data->notification_id,
158 } 497 action_index, data->profile_id, data->is_incognito));
159 498 }
160 void ResetResourceFiles() { 499
161 for (const base::FilePath& file : resource_files) 500 void OnActionInvoked(dbus::Signal* signal) {
162 DeleteNotificationResourceFile(file); 501 DCHECK(task_runner_->RunsTasksOnCurrentThread());
163 resource_files.clear(); 502 dbus::MessageReader reader(signal);
164 } 503 uint32_t dbus_id;
165 504 if (!reader.PopUint32(&dbus_id))
166 // The ID used by the notification server. Will be 0 until the 505 return;
167 // first "Notify" message completes. 506 std::string action;
168 uint32_t dbus_id = 0; 507 if (!reader.PopString(&action))
169 508 return;
170 // Same parameters used by NotificationPlatformBridge::Display(). 509
171 NotificationCommon::Type notification_type; 510 NotificationData* data = FindNotificationData(dbus_id);
172 const std::string notification_id; 511 if (!data)
173 const std::string profile_id; 512 return;
174 const bool is_incognito; 513
175 514 if (action == kDefaultButtonId) {
176 // A copy of the origin_url from the underlying 515 ForwardNotificationOperation(data, NotificationCommon::CLICK, -1);
177 // message_center::Notification. Used to pass back to 516 } else if (action == kSettingsButtonId) {
178 // NativeNotificationDisplayService. 517 ForwardNotificationOperation(data, NotificationCommon::SETTINGS, -1);
179 const GURL origin_url; 518 } else {
180 519 size_t id;
181 // Used to keep track of the IDs of the buttons currently displayed 520 if (!base::StringToSizeT(action, &id))
182 // on this notification. The valid range of action IDs is 521 return;
183 // [action_start, action_end). 522 size_t n_buttons = data->action_end - data->action_start;
184 size_t action_start = 0; 523 size_t id_zero_based = id - data->action_start;
185 size_t action_end = 0; 524 if (id_zero_based >= n_buttons)
186 525 return;
187 // Temporary resource files associated with the notification that 526 ForwardNotificationOperation(data, NotificationCommon::CLICK,
188 // should be cleaned up when the notification is closed or on 527 id_zero_based);
189 // shutdown. 528 }
190 std::vector<base::FilePath> resource_files; 529 }
191 530
192 // Used to cancel the initial "Notify" message so we don't call 531 void OnNotificationClosed(dbus::Signal* signal) {
193 // NotificationPlatformBridgeLinux::NotifyCompleteInternal() with a 532 DCHECK(task_runner_->RunsTasksOnCurrentThread());
194 // destroyed Notification. 533 dbus::MessageReader reader(signal);
195 ScopedGObject<GCancellable> cancellable; 534 uint32_t dbus_id;
196 535 if (!reader.PopUint32(&dbus_id))
197 // If not null, the data to update the notification with once 536 return;
198 // |dbus_id| becomes available. 537
199 std::unique_ptr<Notification> update_data; 538 NotificationData* data = FindNotificationData(dbus_id);
200 NotificationCommon::Type update_notification_type = 539 if (!data)
201 NotificationCommon::TYPE_MAX; 540 return;
202 541
203 // If true, indicates the notification should be closed once 542 ForwardNotificationOperation(data, NotificationCommon::CLOSE, -1);
204 // |dbus_id| becomes available. 543 notifications_.erase(data);
205 bool should_close = false; 544 }
206 545
207 base::WeakPtrFactory<NotificationData> weak_factory; 546 // Called once the connection has been set up (or not). |success|
547 // indicates the connection is ready to use.
548 void OnConnectionInitializationFinishedOnUiThread(bool success) {
549 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
550 connected_ = success;
551 for (auto& callback : on_connected_callbacks_)
552 std::move(callback).Run(success);
553 on_connected_callbacks_.clear();
554 if (!success)
555 CleanUp();
556 }
557
558 void OnConnectionInitializationFinishedOnTaskRunner(bool success) {
559 DCHECK(task_runner_->RunsTasksOnCurrentThread());
560 PostTaskToUiThread(
561 base::BindOnce(&NotificationPlatformBridgeLinuxImpl::
562 OnConnectionInitializationFinishedOnUiThread,
563 this, success));
564 }
565
566 void OnSignalConnected(const std::string& interface_name,
567 const std::string& signal_name,
568 bool success) {
569 DCHECK(task_runner_->RunsTasksOnCurrentThread());
570 if (!success) {
571 OnConnectionInitializationFinishedOnTaskRunner(false);
572 return;
573 }
574 connected_signals_barrier_.Run();
575 }
576
577 //////////////////////////////////////////////////////////////////////////////
578 // Members used only on the UI thread.
579
580 scoped_refptr<base::SequencedTaskRunner> task_runner_;
581
582 content::NotificationRegistrar registrar_;
583
584 // State necessary for OnConnectionInitializationFinished() and
585 // SetReadyCallback().
586 base::Optional<bool> connected_;
587 std::vector<NotificationBridgeReadyCallback> on_connected_callbacks_;
588
589 bool cleanup_posted_ = false;
590
591 //////////////////////////////////////////////////////////////////////////////
592 // Members used only on the task runner thread.
593
594 scoped_refptr<dbus::Bus> bus_;
595
596 dbus::ObjectProxy* notification_proxy_ = nullptr;
597
598 base::Closure connected_signals_barrier_;
599
600 // A std::set<std::unique_ptr<T>> doesn't work well because
601 // eg. std::set::erase(T) would require a std::unique_ptr<T>
602 // argument, so the data would get double-destructed.
603 template <typename T>
604 using UnorderedUniqueSet = std::unordered_map<T*, std::unique_ptr<T>>;
605
606 UnorderedUniqueSet<NotificationData> notifications_;
607
608 DISALLOW_COPY_AND_ASSIGN(NotificationPlatformBridgeLinuxImpl);
208 }; 609 };
209 610
210 struct NotificationPlatformBridgeLinux::ResourceFiles { 611 NotificationPlatformBridgeLinux::NotificationPlatformBridgeLinux()
211 explicit ResourceFiles(const base::FilePath& icon_file) 612 : impl_(new NotificationPlatformBridgeLinuxImpl()) {}
212 : icon_file(icon_file) {} 613
213 ~ResourceFiles() { DeleteNotificationResourceFile(icon_file); } 614 NotificationPlatformBridgeLinux::~NotificationPlatformBridgeLinux() = default;
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 615
231 void NotificationPlatformBridgeLinux::Display( 616 void NotificationPlatformBridgeLinux::Display(
232 NotificationCommon::Type notification_type, 617 NotificationCommon::Type notification_type,
233 const std::string& notification_id, 618 const std::string& notification_id,
234 const std::string& profile_id, 619 const std::string& profile_id,
235 bool is_incognito, 620 bool is_incognito,
236 const Notification& notification) { 621 const Notification& notification) {
237 NotificationData* data = 622 impl_->Display(notification_type, notification_id, profile_id, is_incognito,
238 FindNotificationData(notification_id, profile_id, is_incognito); 623 notification);
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 } 624 }
257 625
258 void NotificationPlatformBridgeLinux::Close( 626 void NotificationPlatformBridgeLinux::Close(
259 const std::string& profile_id, 627 const std::string& profile_id,
260 const std::string& notification_id) { 628 const std::string& notification_id) {
261 std::vector<NotificationData*> to_erase; 629 impl_->Close(profile_id, notification_id);
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 } 630 }
277 631
278 void NotificationPlatformBridgeLinux::GetDisplayed( 632 void NotificationPlatformBridgeLinux::GetDisplayed(
279 const std::string& profile_id, 633 const std::string& profile_id,
280 bool incognito, 634 bool incognito,
281 const GetDisplayedNotificationsCallback& callback) const { 635 const GetDisplayedNotificationsCallback& callback) const {
282 // TODO(thomasanderson): implement. 636 impl_->GetDisplayed(profile_id, incognito, callback);
283 callback.Run(base::MakeUnique<std::set<std::string>>(), false);
284 } 637 }
285 638
286 void NotificationPlatformBridgeLinux::NotifyCompleteInternal(gpointer user_data, 639 void NotificationPlatformBridgeLinux::SetReadyCallback(
287 GVariant* value) { 640 NotificationBridgeReadyCallback callback) {
288 NotificationData* data = reinterpret_cast<NotificationData*>(user_data); 641 impl_->SetReadyCallback(std::move(callback));
289 if (!base::ContainsKey(notifications_, data))
290 return;
291 data->cancellable.reset();
292 if (value && g_variant_is_of_type(value, G_VARIANT_TYPE("(u)")))
293 g_variant_get(value, "(u)", &data->dbus_id);
294
295 if (!data->dbus_id) {
296 // There was some sort of error with creating the notification.
297 notifications_.erase(data);
298 } else if (data->should_close) {
299 CloseNow(data->dbus_id);
300 notifications_.erase(data);
301 } else if (data->update_data) {
302 data->notification_type = data->update_notification_type;
303 Notify(*data->update_data, data, nullptr, nullptr);
304 data->update_data.reset();
305 }
306 } 642 }
307
308 void NotificationPlatformBridgeLinux::Observe(
309 int type,
310 const content::NotificationSource& source,
311 const content::NotificationDetails& details) {
312 DCHECK_EQ(chrome::NOTIFICATION_APP_TERMINATING, type);
313 // The browser process is about to exit. Clean up all notification
314 // resource files.
315 notifications_.clear();
316 }
317
318 void NotificationPlatformBridgeLinux::Notify(const Notification& notification,
319 NotificationData* data,
320 GAsyncReadyCallback callback,
321 gpointer user_data) {
322 const scoped_refptr<base::RefCountedMemory> icon_data =
323 notification.icon().As1xPNGBytes();
324 if (!icon_data->size()) {
325 NotifyNow(notification, data->weak_factory.GetWeakPtr(), callback,
326 user_data, base::MakeUnique<ResourceFiles>(base::FilePath()));
327 } else {
328 base::PostTaskWithTraitsAndReplyWithResult(
329 FROM_HERE,
330 base::TaskTraits()
331 .MayBlock()
332 .WithPriority(base::TaskPriority::USER_BLOCKING)
333 .WithShutdownBehavior(base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN),
334 base::Bind(
335 [](scoped_refptr<base::RefCountedMemory> icon) {
336 return base::MakeUnique<ResourceFiles>(WriteDataToTmpFile(icon));
337 },
338 icon_data),
339 base::Bind(&NotificationPlatformBridgeLinux::NotifyNow,
340 weak_factory_.GetWeakPtr(), notification,
341 data->weak_factory.GetWeakPtr(), callback, user_data));
342 }
343 }
344
345 void NotificationPlatformBridgeLinux::NotifyNow(
346 const Notification& notification,
347 base::WeakPtr<NotificationData> data,
348 GAsyncReadyCallback callback,
349 gpointer user_data,
350 std::unique_ptr<ResourceFiles> resource_files) {
351 if (!data)
352 return;
353
354 if (data->dbus_id)
355 DCHECK(!data->cancellable);
356
357 data->ResetResourceFiles();
358
359 GVariantBuilder actions_builder;
360 // Even-indexed elements in this array are action IDs passed back to
361 // us in GSignalReceiver. Odd-indexed ones contain the button text.
362 g_variant_builder_init(&actions_builder, G_VARIANT_TYPE("as"));
363 data->action_start = data->action_end;
364 for (const auto& button_info : notification.buttons()) {
365 // FDO notification buttons can contain either an icon or a label,
366 // but not both, and the type of all buttons must be the same (all
367 // labels or all icons), so always use labels.
368 std::string id = base::SizeTToString(data->action_end++);
369 const std::string label = base::UTF16ToUTF8(button_info.title);
370 AddActionToNotification(&actions_builder, id.c_str(), label.c_str());
371 }
372 if (notification.clickable()) {
373 // Special case: the pair ("default", "") will not add a button,
374 // but instead makes the entire notification clickable.
375 AddActionToNotification(&actions_builder, "default", "");
376 }
377 // Always add a settings button.
378 AddActionToNotification(&actions_builder, "settings", "Settings");
379
380 GVariantBuilder hints_builder;
381 g_variant_builder_init(&hints_builder, G_VARIANT_TYPE("a{sv}"));
382
383 g_variant_builder_add(&hints_builder, "{sv}", "urgency",
384 g_variant_new_byte(NotificationPriorityToFdoUrgency(
385 notification.priority())));
386
387 std::unique_ptr<base::Environment> env = base::Environment::Create();
388 base::FilePath desktop_file(
389 shell_integration_linux::GetDesktopName(env.get()));
390 const char kDesktopFileSuffix[] = ".desktop";
391 DCHECK(base::EndsWith(desktop_file.value(), kDesktopFileSuffix,
392 base::CompareCase::SENSITIVE));
393
394 desktop_file = desktop_file.RemoveFinalExtension();
395 g_variant_builder_add(&hints_builder, "{sv}", "desktop-entry",
396 g_variant_new_string(desktop_file.value().c_str()));
397
398 if (!resource_files->icon_file.empty()) {
399 g_variant_builder_add(
400 &hints_builder, "{sv}", "image-path",
401 g_variant_new_string(resource_files->icon_file.value().c_str()));
402 data->resource_files.push_back(resource_files->icon_file);
403 resource_files->icon_file.clear();
404 }
405
406 const std::string title = base::UTF16ToUTF8(notification.title());
407 const std::string message = base::UTF16ToUTF8(notification.message());
408
409 GVariant* parameters = g_variant_new(
410 "(susssasa{sv}i)", "" /* app_name passed implicitly via desktop-entry */,
411 data->dbus_id, "" /* app_icon passed implicitly via desktop-entry */,
412 title.c_str(), message.c_str(), &actions_builder, &hints_builder, -1);
413 g_dbus_proxy_call(notification_proxy_, "Notify", parameters,
414 G_DBUS_CALL_FLAGS_NONE, -1, data->cancellable, callback,
415 user_data);
416 }
417
418 void NotificationPlatformBridgeLinux::CloseNow(uint32_t dbus_id) {
419 g_dbus_proxy_call(notification_proxy_, "CloseNotification",
420 g_variant_new("(u)", dbus_id), G_DBUS_CALL_FLAGS_NONE, -1,
421 nullptr, nullptr, nullptr);
422 }
423
424 NotificationPlatformBridgeLinux::NotificationData*
425 NotificationPlatformBridgeLinux::FindNotificationData(
426 const std::string& notification_id,
427 const std::string& profile_id,
428 bool is_incognito) {
429 for (const auto& pair : notifications_) {
430 NotificationData* data = pair.first;
431 if (data->notification_id == notification_id &&
432 data->profile_id == profile_id && data->is_incognito == is_incognito) {
433 return data;
434 }
435 }
436
437 return nullptr;
438 }
439
440 NotificationPlatformBridgeLinux::NotificationData*
441 NotificationPlatformBridgeLinux::FindNotificationData(uint32_t dbus_id) {
442 for (const auto& pair : notifications_) {
443 NotificationData* data = pair.first;
444 if (data->dbus_id == dbus_id)
445 return data;
446 }
447
448 return nullptr;
449 }
450
451 void NotificationPlatformBridgeLinux::ForwardNotificationOperation(
452 uint32_t dbus_id,
453 NotificationCommon::Operation operation,
454 int action_index) {
455 NotificationData* data = FindNotificationData(dbus_id);
456 if (!data) {
457 // This notification either belongs to a different app or we
458 // already removed the NotificationData after sending a
459 // "CloseNotification" message.
460 return;
461 }
462
463 ProfileManager* profile_manager = g_browser_process->profile_manager();
464 DCHECK(profile_manager);
465
466 profile_manager->LoadProfile(
467 data->profile_id, data->is_incognito,
468 base::Bind(&ProfileLoadedCallback, operation, data->notification_type,
469 data->origin_url.spec(), data->notification_id, action_index,
470 base::NullableString16()));
471 }
472
473 void NotificationPlatformBridgeLinux::GSignalReceiver(GDBusProxy* proxy,
474 const char* sender_name,
475 const char* sender_signal,
476 GVariant* parameters) {
477 uint32_t dbus_id = 0;
478 if (strcmp("NotificationClosed", sender_signal) == 0 &&
479 g_variant_is_of_type(parameters, G_VARIANT_TYPE("(uu)"))) {
480 uint32_t reason;
481 g_variant_get(parameters, "(uu)", &dbus_id, &reason);
482 ForwardNotificationOperation(dbus_id, NotificationCommon::CLOSE, -1);
483 // std::unordered_map::erase(nullptr) is safe here.
484 notifications_.erase(FindNotificationData(dbus_id));
485 } else if (strcmp("ActionInvoked", sender_signal) == 0 &&
486 g_variant_is_of_type(parameters, G_VARIANT_TYPE("(us)"))) {
487 const gchar* action = nullptr;
488 g_variant_get(parameters, "(u&s)", &dbus_id, &action);
489 DCHECK(action);
490
491 if (strcmp(action, "default") == 0) {
492 ForwardNotificationOperation(dbus_id, NotificationCommon::CLICK, -1);
493 } else if (strcmp(action, "settings") == 0) {
494 ForwardNotificationOperation(dbus_id, NotificationCommon::SETTINGS, -1);
495 } else {
496 size_t id;
497 if (!base::StringToSizeT(action, &id))
498 return;
499 NotificationData* data = FindNotificationData(dbus_id);
500 if (!data)
501 return;
502 size_t n_buttons = data->action_end - data->action_start;
503 size_t id_zero_based = id - data->action_start;
504 if (id_zero_based >= n_buttons)
505 return;
506 ForwardNotificationOperation(dbus_id, NotificationCommon::CLICK,
507 id_zero_based);
508 }
509 }
510 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698