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

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

Issue 2821533003: Refactor NotificationPlatformBridgeLinux (Closed)
Patch Set: Rebase 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
« no previous file with comments | « chrome/browser/notifications/notification_platform_bridge_linux.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 const char* action_id,
33 const char* button_label) {
34 g_variant_builder_add(actions_builder, "s", action_id);
35 g_variant_builder_add(actions_builder, "s", button_label);
36 }
37
38 int NotificationPriorityToFdoUrgency(int priority) { 32 int NotificationPriorityToFdoUrgency(int priority) {
39 enum FdoUrgency { 33 enum FdoUrgency {
40 LOW = 0, 34 LOW = 0,
41 NORMAL = 1, 35 NORMAL = 1,
42 CRITICAL = 2, 36 CRITICAL = 2,
43 }; 37 };
44 switch (priority) { 38 switch (priority) {
45 case message_center::MIN_PRIORITY: 39 case message_center::MIN_PRIORITY:
46 case message_center::LOW_PRIORITY: 40 case message_center::LOW_PRIORITY:
47 return LOW; 41 return LOW;
48 case message_center::HIGH_PRIORITY: 42 case message_center::HIGH_PRIORITY:
49 case message_center::MAX_PRIORITY: 43 case message_center::MAX_PRIORITY:
50 return CRITICAL; 44 return CRITICAL;
51 default: 45 default:
52 NOTREACHED(); 46 NOTREACHED();
53 // fallthrough
54 case message_center::DEFAULT_PRIORITY: 47 case message_center::DEFAULT_PRIORITY:
55 return NORMAL; 48 return NORMAL;
56 } 49 }
57 } 50 }
58 51
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 52 // Runs once the profile has been loaded in order to perform a given
77 // |operation| on a notification. 53 // |operation| on a notification.
78 void ProfileLoadedCallback(NotificationCommon::Operation operation, 54 void ProfileLoadedCallback(NotificationCommon::Operation operation,
79 NotificationCommon::Type notification_type, 55 NotificationCommon::Type notification_type,
80 const std::string& origin, 56 const std::string& origin,
81 const std::string& notification_id, 57 const std::string& notification_id,
82 int action_index, 58 int action_index,
83 const base::NullableString16& reply, 59 const base::NullableString16& reply,
84 Profile* profile) { 60 Profile* profile) {
85 if (!profile) 61 if (!profile)
(...skipping 18 matching lines...) Expand all
104 if (!base::CreateTemporaryFile(&file_path)) 80 if (!base::CreateTemporaryFile(&file_path))
105 return base::FilePath(); 81 return base::FilePath();
106 if (base::WriteFile(file_path, data->front_as<char>(), data_len) != 82 if (base::WriteFile(file_path, data->front_as<char>(), data_len) !=
107 data_len) { 83 data_len) {
108 base::DeleteFile(file_path, false); 84 base::DeleteFile(file_path, false);
109 return base::FilePath(); 85 return base::FilePath();
110 } 86 }
111 return file_path; 87 return file_path;
112 } 88 }
113 89
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 90 } // namespace
127 91
128 // static 92 // static
129 NotificationPlatformBridge* NotificationPlatformBridge::Create() { 93 NotificationPlatformBridge* NotificationPlatformBridge::Create() {
130 GDBusProxy* notification_proxy = g_dbus_proxy_new_for_bus_sync( 94 auto npbl = base::MakeUnique<NotificationPlatformBridgeLinux>();
131 G_BUS_TYPE_SESSION, 95 if (!npbl->BlockUntilReady())
Peter Beverloo 2017/04/18 18:09:28 We pretty much have to assume that the Notificatio
Lei Zhang 2017/04/18 22:31:05 Is it really created at start up, or at first use?
Peter Beverloo 2017/04/18 22:42:06 On start-up for Android, but we're moving in that
Tom (Use chromium acct) 2017/04/18 23:07:43 It doesn't create a thread but it does to synchron
Tom (Use chromium acct) 2017/04/18 23:07:43 I'll try creating a followup patch that does this
132 static_cast<GDBusProxyFlags>(G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
133 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START),
134 nullptr, kFreedesktopNotificationsName, kFreedesktopNotificationsPath,
135 kFreedesktopNotificationsName, nullptr, nullptr);
136 if (!notification_proxy)
137 return nullptr; 96 return nullptr;
138 return new NotificationPlatformBridgeLinux(notification_proxy); 97 return npbl.release();
139 } 98 }
140 99
141 struct NotificationPlatformBridgeLinux::NotificationData { 100 class NotificationPlatformBridgeLinux::NativeNotificationThread
142 NotificationData(NotificationCommon::Type notification_type, 101 : public base::Thread {
143 const std::string& notification_id, 102 public:
144 const std::string& profile_id, 103 NativeNotificationThread()
145 bool is_incognito, 104 : base::Thread("FDO Notifications D-Bus thread"),
146 const GURL& origin_url) 105 event_(base::WaitableEvent::ResetPolicy::MANUAL,
147 : notification_type(notification_type), 106 base::WaitableEvent::InitialState::NOT_SIGNALED) {
148 notification_id(notification_id), 107 base::Thread::Options thread_options(base::MessageLoop::TYPE_IO, 0);
149 profile_id(profile_id), 108 if (!StartWithOptions(thread_options))
150 is_incognito(is_incognito), 109 event_.Signal();
151 origin_url(origin_url), 110 }
152 weak_factory(this) {} 111
153 112 ~NativeNotificationThread() override {
154 ~NotificationData() { 113 Stop();
155 if (cancellable) 114 // All of the resources should have been cleaned up on the
156 g_cancellable_cancel(cancellable); 115 // notification thread.
157 ResetResourceFiles(); 116 DCHECK(notifications_.empty());
158 } 117 }
159 118
160 void ResetResourceFiles() { 119 void Display(NotificationCommon::Type notification_type,
161 for (const base::FilePath& file : resource_files) 120 const std::string& notification_id,
162 DeleteNotificationResourceFile(file); 121 const std::string& profile_id,
163 resource_files.clear(); 122 bool is_incognito,
164 } 123 const Notification& notification) {
165 124 NotificationData* data =
166 // The ID used by the notification server. Will be 0 until the 125 FindNotificationData(notification_id, profile_id, is_incognito);
167 // first "Notify" message completes. 126 if (data) {
168 uint32_t dbus_id = 0; 127 // Update an existing notification.
169 128 data->notification_type = notification_type;
170 // Same parameters used by NotificationPlatformBridge::Display(). 129 data->resource_files.clear();
171 NotificationCommon::Type notification_type; 130 } else {
172 const std::string notification_id; 131 // Send the notification for the first time.
173 const std::string profile_id; 132 data =
174 const bool is_incognito; 133 new NotificationData(notification_type, notification_id, profile_id,
175 134 is_incognito, notification.origin_url());
176 // A copy of the origin_url from the underlying 135 notifications_.emplace(data, base::WrapUnique(data));
177 // message_center::Notification. Used to pass back to 136 }
178 // NativeNotificationDisplayService. 137
179 const GURL origin_url; 138 dbus::MethodCall method_call(kFreedesktopNotificationsName, "Notify");
180 139 dbus::MessageWriter writer(&method_call);
181 // Used to keep track of the IDs of the buttons currently displayed 140
182 // on this notification. The valid range of action IDs is 141 // app_name passed implicitly via desktop-entry.
183 // [action_start, action_end). 142 writer.AppendString("");
Peter Beverloo 2017/04/18 18:09:28 One thing to consider for testing is that it may m
Tom (Use chromium acct) 2017/04/18 23:07:43 Acknowledged.
184 size_t action_start = 0; 143
185 size_t action_end = 0; 144 writer.AppendUint32(data->dbus_id);
186 145
187 // Temporary resource files associated with the notification that 146 // app_icon passed implicitly via desktop-entry.
188 // should be cleaned up when the notification is closed or on 147 writer.AppendString("");
189 // shutdown. 148
190 std::vector<base::FilePath> resource_files; 149 const std::string title = base::UTF16ToUTF8(notification.title());
191 150 writer.AppendString(title);
192 // Used to cancel the initial "Notify" message so we don't call 151
193 // NotificationPlatformBridgeLinux::NotifyCompleteInternal() with a 152 const std::string message = base::UTF16ToUTF8(notification.message());
194 // destroyed Notification. 153 writer.AppendString(message);
195 ScopedGObject<GCancellable> cancellable; 154
196 155 // Even-indexed elements in this vector are action IDs passed back to
197 // If not null, the data to update the notification with once 156 // us in OnActionInvoked(). Odd-indexed ones contain the button text.
198 // |dbus_id| becomes available. 157 std::vector<std::string> actions;
199 std::unique_ptr<Notification> update_data; 158 data->action_start = data->action_end;
200 NotificationCommon::Type update_notification_type = 159 for (const auto& button_info : notification.buttons()) {
201 NotificationCommon::TYPE_MAX; 160 // FDO notification buttons can contain either an icon or a label,
202 161 // but not both, and the type of all buttons must be the same (all
203 // If true, indicates the notification should be closed once 162 // labels or all icons), so always use labels.
204 // |dbus_id| becomes available. 163 std::string id = base::SizeTToString(data->action_end++);
Peter Beverloo 2017/04/18 18:09:28 micro nit: line is untouched, but consider marking
Tom (Use chromium acct) 2017/04/18 23:07:43 Done.
205 bool should_close = false; 164 const std::string label = base::UTF16ToUTF8(button_info.title);
206 165 actions.push_back(id);
207 base::WeakPtrFactory<NotificationData> weak_factory; 166 actions.push_back(label);
208 }; 167 }
209 168 if (notification.clickable()) {
210 struct NotificationPlatformBridgeLinux::ResourceFiles { 169 // Special case: the pair ("default", "") will not add a button,
211 explicit ResourceFiles(const base::FilePath& icon_file) 170 // but instead makes the entire notification clickable.
212 : icon_file(icon_file) {} 171 actions.push_back("default");
213 ~ResourceFiles() { DeleteNotificationResourceFile(icon_file); } 172 actions.push_back("");
214 base::FilePath icon_file; 173 }
215 }; 174 // Always add a settings button.
216 175 actions.push_back("settings");
217 NotificationPlatformBridgeLinux::NotificationPlatformBridgeLinux( 176 actions.push_back("Settings");
218 GDBusProxy* notification_proxy) 177 writer.AppendArrayOfStrings(actions);
219 : notification_proxy_(notification_proxy), weak_factory_(this) { 178
220 proxy_signal_handler_ = g_signal_connect( 179 dbus::MessageWriter hints_writer(nullptr);
221 notification_proxy_, "g-signal", G_CALLBACK(GSignalReceiverThunk), this); 180 writer.OpenArray("{sv}", &hints_writer);
222 registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING, 181 dbus::MessageWriter urgency_writer(nullptr);
223 content::NotificationService::AllSources()); 182 hints_writer.OpenDictEntry(&urgency_writer);
224 } 183 urgency_writer.AppendString("urgency");
225 184 urgency_writer.AppendVariantOfUint32(
226 NotificationPlatformBridgeLinux::~NotificationPlatformBridgeLinux() { 185 NotificationPriorityToFdoUrgency(notification.priority()));
227 if (proxy_signal_handler_) 186 hints_writer.CloseContainer(&urgency_writer);
228 g_signal_handler_disconnect(notification_proxy_, proxy_signal_handler_); 187
229 } 188 std::unique_ptr<base::Environment> env = base::Environment::Create();
230 189 base::FilePath desktop_file(
231 void NotificationPlatformBridgeLinux::Display( 190 shell_integration_linux::GetDesktopName(env.get()));
232 NotificationCommon::Type notification_type, 191 const char kDesktopFileSuffix[] = ".desktop";
233 const std::string& notification_id, 192 DCHECK(base::EndsWith(desktop_file.value(), kDesktopFileSuffix,
234 const std::string& profile_id, 193 base::CompareCase::SENSITIVE));
235 bool is_incognito, 194 desktop_file = desktop_file.RemoveFinalExtension();
236 const Notification& notification) { 195 dbus::MessageWriter desktop_entry_writer(nullptr);
237 NotificationData* data = 196 hints_writer.OpenDictEntry(&desktop_entry_writer);
238 FindNotificationData(notification_id, profile_id, is_incognito); 197 desktop_entry_writer.AppendString("desktop-entry");
239 if (data) { 198 desktop_entry_writer.AppendVariantOfString(desktop_file.value());
240 // Update an existing notification. 199 hints_writer.CloseContainer(&desktop_entry_writer);
200
201 base::FilePath icon_file =
202 WriteDataToTmpFile(notification.icon().As1xPNGBytes());
Peter Beverloo 2017/04/18 18:09:28 Do we have to extract the underlying RefCountedMem
Tom (Use chromium acct) 2017/04/18 23:07:43 I thought we didn't have to (see my previous comme
203 if (!icon_file.empty()) {
204 dbus::MessageWriter image_path_writer(nullptr);
205 hints_writer.OpenDictEntry(&image_path_writer);
206 image_path_writer.AppendString("image-path");
207 image_path_writer.AppendVariantOfString(icon_file.value());
208 hints_writer.CloseContainer(&image_path_writer);
209 data->resource_files.push_back(base::MakeUnique<ResourceFile>(icon_file));
210 }
211
212 writer.CloseContainer(&hints_writer);
213
214 const int32_t kExpireTimeoutDefault = -1;
215 writer.AppendInt32(kExpireTimeoutDefault);
216
241 if (data->dbus_id) { 217 if (data->dbus_id) {
242 data->notification_type = notification_type; 218 notification_proxy_->CallMethod(&method_call,
243 Notify(notification, data, nullptr, nullptr); 219 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
220 base::Bind([](dbus::Response*) {}));
Peter Beverloo 2017/04/18 18:09:28 Is there any benefit in using async calls if we're
Tom (Use chromium acct) 2017/04/18 23:07:43 Didn't think about that, but it's better safe than
244 } else { 221 } else {
245 data->update_notification_type = notification_type; 222 std::unique_ptr<dbus::Response> response =
246 data->update_data = base::MakeUnique<Notification>(notification); 223 notification_proxy_->CallMethodAndBlock(
247 } 224 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
248 } else { 225 if (response) {
249 // Send the notification for the first time. 226 dbus::MessageReader reader(response.get());
250 data = new NotificationData(notification_type, notification_id, profile_id, 227 reader.PopUint32(&data->dbus_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 } 228 }
272 } 229 if (!data->dbus_id) {
273 } 230 // There was some sort of error with creating the notification.
274 for (NotificationData* data : to_erase) 231 notifications_.erase(data);
275 notifications_.erase(data); 232 }
276 } 233 }
277 234 }
278 void NotificationPlatformBridgeLinux::GetDisplayed( 235
279 const std::string& profile_id, 236 void Close(const std::string& profile_id,
280 bool incognito, 237 const std::string& notification_id) {
281 const DisplayedNotificationsCallback& callback) const { 238 std::vector<NotificationData*> to_erase;
282 callback.Run(base::MakeUnique<std::set<std::string>>(), false); 239 for (const auto& pair : notifications_) {
283 } 240 NotificationData* data = pair.first;
284 241 if (data->notification_id == notification_id &&
285 void NotificationPlatformBridgeLinux::NotifyCompleteInternal(gpointer user_data, 242 data->profile_id == profile_id) {
286 GVariant* value) { 243 dbus::MethodCall method_call(kFreedesktopNotificationsName,
287 NotificationData* data = reinterpret_cast<NotificationData*>(user_data); 244 "CloseNotification");
288 if (!base::ContainsKey(notifications_, data)) 245 dbus::MessageWriter writer(&method_call);
289 return; 246 writer.AppendUint32(data->dbus_id);
290 data->cancellable.reset(); 247 notification_proxy_->CallMethod(&method_call,
291 if (value && g_variant_is_of_type(value, G_VARIANT_TYPE("(u)"))) 248 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
292 g_variant_get(value, "(u)", &data->dbus_id); 249 base::Bind([](dbus::Response*) {}));
293 250 }
294 if (!data->dbus_id) { 251 }
295 // There was some sort of error with creating the notification. 252 for (NotificationData* data : to_erase)
296 notifications_.erase(data); 253 notifications_.erase(data);
297 } else if (data->should_close) { 254 }
298 CloseNow(data->dbus_id); 255
299 notifications_.erase(data); 256 void GetDisplayed(const std::string& profile_id,
300 } else if (data->update_data) { 257 bool incognito,
301 data->notification_type = data->update_notification_type; 258 const DisplayedNotificationsCallback& callback) const {
302 Notify(*data->update_data, data, nullptr, nullptr); 259 content::BrowserThread::PostTask(
303 data->update_data.reset(); 260 content::BrowserThread::UI, FROM_HERE,
304 } 261 base::Bind(callback,
305 } 262 base::Passed(base::MakeUnique<std::set<std::string>>()),
306 263 false));
307 void NotificationPlatformBridgeLinux::Observe( 264 }
308 int type, 265
309 const content::NotificationSource& source, 266 bool BlockUntilReady() {
Peter Beverloo 2017/04/18 18:09:28 nit: document that it returns whether the proxy is
Tom (Use chromium acct) 2017/04/18 23:07:43 Done.
310 const content::NotificationDetails& details) { 267 event_.Wait();
311 DCHECK_EQ(chrome::NOTIFICATION_APP_TERMINATING, type); 268 return notification_proxy_initialized_.IsSet();
312 // The browser process is about to exit. Clean up all notification 269 }
313 // resource files. 270
314 notifications_.clear(); 271 private:
315 } 272 struct ResourceFile {
316 273 explicit ResourceFile(const base::FilePath& file_path)
317 void NotificationPlatformBridgeLinux::Notify(const Notification& notification, 274 : file_path(file_path) {}
318 NotificationData* data, 275 ~ResourceFile() { base::DeleteFile(file_path, false); }
319 GAsyncReadyCallback callback, 276 const base::FilePath file_path;
320 gpointer user_data) { 277 };
321 const scoped_refptr<base::RefCountedMemory> icon_data = 278
322 notification.icon().As1xPNGBytes(); 279 struct NotificationData {
323 if (!icon_data->size()) { 280 NotificationData(NotificationCommon::Type notification_type,
324 NotifyNow(notification, data->weak_factory.GetWeakPtr(), callback, 281 const std::string& notification_id,
325 user_data, base::MakeUnique<ResourceFiles>(base::FilePath())); 282 const std::string& profile_id,
326 } else { 283 bool is_incognito,
327 base::PostTaskWithTraitsAndReplyWithResult( 284 const GURL& origin_url)
328 FROM_HERE, 285 : notification_type(notification_type),
329 base::TaskTraits() 286 notification_id(notification_id),
330 .MayBlock() 287 profile_id(profile_id),
331 .WithPriority(base::TaskPriority::USER_BLOCKING) 288 is_incognito(is_incognito),
332 .WithShutdownBehavior(base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN), 289 origin_url(origin_url) {}
290
291 // The ID used by the notification server. Will be 0 until the
292 // first "Notify" message completes.
293 uint32_t dbus_id = 0;
294
295 // Same parameters used by NotificationPlatformBridge::Display().
296 NotificationCommon::Type notification_type;
297 const std::string notification_id;
298 const std::string profile_id;
299 const bool is_incognito;
300
301 // A copy of the origin_url from the underlying
302 // message_center::Notification. Used to pass back to
303 // NativeNotificationDisplayService.
304 const GURL origin_url;
305
306 // Used to keep track of the IDs of the buttons currently displayed
307 // on this notification. The valid range of action IDs is
308 // [action_start, action_end).
309 size_t action_start = 0;
310 size_t action_end = 0;
311
312 // Temporary resource files associated with the notification that
313 // should be cleaned up when the notification is closed or on
314 // shutdown.
315 std::vector<std::unique_ptr<ResourceFile>> resource_files;
316 };
317
318 void Init() override {
319 dbus::Bus::Options bus_options;
320 bus_options.bus_type = dbus::Bus::SESSION;
321 bus_options.connection_type = dbus::Bus::PRIVATE;
322 bus_ = new dbus::Bus(bus_options);
Peter Beverloo 2017/04/18 18:09:28 nit: make_scoped_refptr(new dbus::Bus(bus_options)
Tom (Use chromium acct) 2017/04/18 23:07:43 Done.
323
324 notification_proxy_ =
325 bus_->GetObjectProxy(kFreedesktopNotificationsName,
326 dbus::ObjectPath(kFreedesktopNotificationsPath));
327 if (notification_proxy_)
328 notification_proxy_initialized_.Set();
329 event_.Signal();
330 if (!notification_proxy_)
331 return;
332
333 auto on_signal_connected = [](const std::string&, const std::string&,
Peter Beverloo 2017/04/18 18:09:28 question: suppose that we hit this DCHECK, what wo
Tom (Use chromium acct) 2017/04/18 23:07:43 I think the only way this could happen is if the n
334 bool success) { DCHECK(success); };
335 notification_proxy_->ConnectToSignal(
336 kFreedesktopNotificationsName, "ActionInvoked",
337 base::Bind(&NativeNotificationThread::OnActionInvoked,
338 base::Unretained(this)),
339 base::Bind(on_signal_connected));
340 notification_proxy_->ConnectToSignal(
341 kFreedesktopNotificationsName, "NotificationClosed",
342 base::Bind(&NativeNotificationThread::OnNotificationClosed,
343 base::Unretained(this)),
344 base::Bind(on_signal_connected));
345 }
346
347 void CleanUp() override {
348 bus_->ShutdownAndBlock();
349 notifications_.clear();
Peter Beverloo 2017/04/18 18:09:27 nit: consider setting |notification_proxy_| to a n
Tom (Use chromium acct) 2017/04/18 23:07:43 Done.
350 }
351
352 NotificationData* FindNotificationData(const std::string& notification_id,
353 const std::string& profile_id,
354 bool is_incognito) {
355 for (const auto& pair : notifications_) {
356 NotificationData* data = pair.first;
357 if (data->notification_id == notification_id &&
358 data->profile_id == profile_id &&
359 data->is_incognito == is_incognito) {
360 return data;
361 }
362 }
363
364 return nullptr;
365 }
366
367 NotificationData* FindNotificationData(uint32_t dbus_id) {
368 for (const auto& pair : notifications_) {
369 NotificationData* data = pair.first;
370 if (data->dbus_id == dbus_id)
371 return data;
372 }
373
374 return nullptr;
375 }
376
377 void ForwardNotificationOperation(uint32_t dbus_id,
378 NotificationCommon::Operation operation,
379 int action_index) {
380 NotificationData* data = FindNotificationData(dbus_id);
381 if (!data) {
382 // This notification either belongs to a different app or we
383 // already removed the NotificationData after sending a
384 // "CloseNotification" message.
385 return;
386 }
387
388 content::BrowserThread::PostTask(
389 content::BrowserThread::UI, FROM_HERE,
333 base::Bind( 390 base::Bind(
334 [](scoped_refptr<base::RefCountedMemory> icon) { 391 [](NotificationCommon::Operation operation,
335 return base::MakeUnique<ResourceFiles>(WriteDataToTmpFile(icon)); 392 NotificationCommon::Type notification_type,
393 const std::string& origin, const std::string& notification_id,
394 int action_index, const std::string& profile_id,
395 bool is_incognito) {
396 ProfileManager* profile_manager =
397 g_browser_process->profile_manager();
398 DCHECK(profile_manager);
399
400 profile_manager->LoadProfile(
401 profile_id, is_incognito,
402 base::Bind(&ProfileLoadedCallback, operation,
403 notification_type, origin, notification_id,
404 action_index, base::NullableString16()));
336 }, 405 },
337 icon_data), 406 operation, data->notification_type, data->origin_url.spec(),
338 base::Bind(&NotificationPlatformBridgeLinux::NotifyNow, 407 data->notification_id, action_index, data->profile_id,
339 weak_factory_.GetWeakPtr(), notification, 408 data->is_incognito));
340 data->weak_factory.GetWeakPtr(), callback, user_data)); 409 }
341 } 410
342 } 411 void OnActionInvoked(dbus::Signal* signal) {
343 412 dbus::MessageReader reader(signal);
344 void NotificationPlatformBridgeLinux::NotifyNow( 413 uint32_t dbus_id;
345 const Notification& notification, 414 if (!reader.PopUint32(&dbus_id))
346 base::WeakPtr<NotificationData> data, 415 return;
347 GAsyncReadyCallback callback, 416 std::string action;
348 gpointer user_data, 417 if (!reader.PopString(&action))
349 std::unique_ptr<ResourceFiles> resource_files) { 418 return;
350 if (!data) 419
351 return; 420 if (action == "default") {
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 base::FilePath desktop_file(
388 shell_integration_linux::GetDesktopName(env.get()));
389 const char kDesktopFileSuffix[] = ".desktop";
390 DCHECK(base::EndsWith(desktop_file.value(), kDesktopFileSuffix,
391 base::CompareCase::SENSITIVE));
392
393 desktop_file = desktop_file.RemoveFinalExtension();
394 g_variant_builder_add(&hints_builder, "{sv}", "desktop-entry",
395 g_variant_new_string(desktop_file.value().c_str()));
396
397 if (!resource_files->icon_file.empty()) {
398 g_variant_builder_add(
399 &hints_builder, "{sv}", "image-path",
400 g_variant_new_string(resource_files->icon_file.value().c_str()));
401 data->resource_files.push_back(resource_files->icon_file);
402 resource_files->icon_file.clear();
403 }
404
405 const std::string title = base::UTF16ToUTF8(notification.title());
406 const std::string message = base::UTF16ToUTF8(notification.message());
407
408 GVariant* parameters = g_variant_new(
409 "(susssasa{sv}i)", "" /* app_name passed implicitly via desktop-entry */,
410 data->dbus_id, "" /* app_icon passed implicitly via desktop-entry */,
411 title.c_str(), message.c_str(), &actions_builder, &hints_builder, -1);
412 g_dbus_proxy_call(notification_proxy_, "Notify", parameters,
413 G_DBUS_CALL_FLAGS_NONE, -1, data->cancellable, callback,
414 user_data);
415 }
416
417 void NotificationPlatformBridgeLinux::CloseNow(uint32_t dbus_id) {
418 g_dbus_proxy_call(notification_proxy_, "CloseNotification",
419 g_variant_new("(u)", dbus_id), G_DBUS_CALL_FLAGS_NONE, -1,
420 nullptr, nullptr, nullptr);
421 }
422
423 NotificationPlatformBridgeLinux::NotificationData*
424 NotificationPlatformBridgeLinux::FindNotificationData(
425 const std::string& notification_id,
426 const std::string& profile_id,
427 bool is_incognito) {
428 for (const auto& pair : notifications_) {
429 NotificationData* data = pair.first;
430 if (data->notification_id == notification_id &&
431 data->profile_id == profile_id && data->is_incognito == is_incognito) {
432 return data;
433 }
434 }
435
436 return nullptr;
437 }
438
439 NotificationPlatformBridgeLinux::NotificationData*
440 NotificationPlatformBridgeLinux::FindNotificationData(uint32_t dbus_id) {
441 for (const auto& pair : notifications_) {
442 NotificationData* data = pair.first;
443 if (data->dbus_id == dbus_id)
444 return data;
445 }
446
447 return nullptr;
448 }
449
450 void NotificationPlatformBridgeLinux::ForwardNotificationOperation(
451 uint32_t dbus_id,
452 NotificationCommon::Operation operation,
453 int action_index) {
454 NotificationData* data = FindNotificationData(dbus_id);
455 if (!data) {
456 // This notification either belongs to a different app or we
457 // already removed the NotificationData after sending a
458 // "CloseNotification" message.
459 return;
460 }
461
462 ProfileManager* profile_manager = g_browser_process->profile_manager();
463 DCHECK(profile_manager);
464
465 profile_manager->LoadProfile(
466 data->profile_id, data->is_incognito,
467 base::Bind(&ProfileLoadedCallback, operation, data->notification_type,
468 data->origin_url.spec(), data->notification_id, action_index,
469 base::NullableString16()));
470 }
471
472 void NotificationPlatformBridgeLinux::GSignalReceiver(GDBusProxy* proxy,
473 const char* sender_name,
474 const char* sender_signal,
475 GVariant* parameters) {
476 uint32_t dbus_id = 0;
477 if (strcmp("NotificationClosed", sender_signal) == 0 &&
478 g_variant_is_of_type(parameters, G_VARIANT_TYPE("(uu)"))) {
479 uint32_t reason;
480 g_variant_get(parameters, "(uu)", &dbus_id, &reason);
481 ForwardNotificationOperation(dbus_id, NotificationCommon::CLOSE, -1);
482 // std::unordered_map::erase(nullptr) is safe here.
483 notifications_.erase(FindNotificationData(dbus_id));
484 } else if (strcmp("ActionInvoked", sender_signal) == 0 &&
485 g_variant_is_of_type(parameters, G_VARIANT_TYPE("(us)"))) {
486 const gchar* action = nullptr;
487 g_variant_get(parameters, "(u&s)", &dbus_id, &action);
488 DCHECK(action);
489
490 if (strcmp(action, "default") == 0) {
491 ForwardNotificationOperation(dbus_id, NotificationCommon::CLICK, -1); 421 ForwardNotificationOperation(dbus_id, NotificationCommon::CLICK, -1);
492 } else if (strcmp(action, "settings") == 0) { 422 } else if (action == "settings") {
493 ForwardNotificationOperation(dbus_id, NotificationCommon::SETTINGS, -1); 423 ForwardNotificationOperation(dbus_id, NotificationCommon::SETTINGS, -1);
494 } else { 424 } else {
495 size_t id; 425 size_t id;
496 if (!base::StringToSizeT(action, &id)) 426 if (!base::StringToSizeT(action, &id))
497 return; 427 return;
498 NotificationData* data = FindNotificationData(dbus_id); 428 NotificationData* data = FindNotificationData(dbus_id);
499 if (!data) 429 if (!data)
500 return; 430 return;
501 size_t n_buttons = data->action_end - data->action_start; 431 size_t n_buttons = data->action_end - data->action_start;
502 size_t id_zero_based = id - data->action_start; 432 size_t id_zero_based = id - data->action_start;
503 if (id_zero_based >= n_buttons) 433 if (id_zero_based >= n_buttons)
504 return; 434 return;
505 ForwardNotificationOperation(dbus_id, NotificationCommon::CLICK, 435 ForwardNotificationOperation(dbus_id, NotificationCommon::CLICK,
506 id_zero_based); 436 id_zero_based);
507 } 437 }
508 } 438 }
439
440 void OnNotificationClosed(dbus::Signal* signal) {
441 dbus::MessageReader reader(signal);
442 uint32_t dbus_id;
443 if (!reader.PopUint32(&dbus_id))
444 return;
445 ForwardNotificationOperation(dbus_id, NotificationCommon::CLOSE, -1);
446 // std::unordered_map::erase(nullptr) is safe here.
447 notifications_.erase(FindNotificationData(dbus_id));
448 }
449
450 scoped_refptr<dbus::Bus> bus_;
451
452 dbus::ObjectProxy* notification_proxy_ = nullptr;
453 // Event that will be signaled once |notification_proxy_| is done
454 // connecting, or has failed to connect.
455 base::WaitableEvent event_;
456
457 // Set if the |notification_proxy_| has initialized without error.
458 // Only valid once |event_| has been signaled.
459 base::AtomicFlag notification_proxy_initialized_;
460
461 // A std::set<std::unique_ptr<T>> doesn't work well because
462 // eg. std::set::erase(T) would require a std::unique_ptr<T>
463 // argument, so the data would get double-destructed.
464 template <typename T>
465 using UnorderedUniqueSet = std::unordered_map<T*, std::unique_ptr<T>>;
466
467 UnorderedUniqueSet<NotificationData> notifications_;
468 };
469
470 NotificationPlatformBridgeLinux::NotificationPlatformBridgeLinux()
471 : thread_(new NativeNotificationThread()) {}
Peter Beverloo 2017/04/18 18:09:28 style nit: base::MakeUnique<NativeNotificationThre
Tom (Use chromium acct) 2017/04/18 23:07:43 Done.
472
473 NotificationPlatformBridgeLinux::~NotificationPlatformBridgeLinux() {}
Peter Beverloo 2017/04/18 18:09:27 style nit: {} -> "= default;"
Tom (Use chromium acct) 2017/04/18 23:07:43 Done.
474
475 void NotificationPlatformBridgeLinux::Display(
476 NotificationCommon::Type notification_type,
477 const std::string& notification_id,
478 const std::string& profile_id,
479 bool is_incognito,
480 const Notification& notification) {
481 thread_->task_runner()->PostTask(
482 FROM_HERE,
483 base::Bind(&NativeNotificationThread::Display,
484 base::Unretained(thread_.get()), notification_type,
485 notification_id, profile_id, is_incognito, notification));
509 } 486 }
487
488 void NotificationPlatformBridgeLinux::Close(
489 const std::string& profile_id,
490 const std::string& notification_id) {
491 thread_->task_runner()->PostTask(
492 FROM_HERE,
493 base::Bind(&NativeNotificationThread::Close,
494 base::Unretained(thread_.get()), profile_id, notification_id));
495 }
496
497 void NotificationPlatformBridgeLinux::GetDisplayed(
498 const std::string& profile_id,
499 bool incognito,
500 const DisplayedNotificationsCallback& callback) const {
501 thread_->task_runner()->PostTask(
502 FROM_HERE, base::Bind(&NativeNotificationThread::GetDisplayed,
503 base::Unretained(thread_.get()), profile_id,
504 incognito, callback));
Peter Beverloo 2017/04/18 18:09:28 Will we be able to support this eventually, or not
Tom (Use chromium acct) 2017/04/18 23:07:43 I've been putting off implementing this for a whil
Peter Beverloo 2017/04/18 23:16:13 Assuming the parameters of Display(), it should re
505 }
506
507 bool NotificationPlatformBridgeLinux::BlockUntilReady() {
508 return thread_->BlockUntilReady();
509 }
OLDNEW
« no previous file with comments | « chrome/browser/notifications/notification_platform_bridge_linux.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698