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

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

Issue 2806203003: Linux native notifications: Support icons (Closed)
Patch Set: Address thestig@'s 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/files/file_util.h"
9 #include "base/memory/ptr_util.h" 10 #include "base/memory/ptr_util.h"
10 #include "base/stl_util.h" 11 #include "base/stl_util.h"
11 #include "base/strings/nullable_string16.h" 12 #include "base/strings/nullable_string16.h"
12 #include "base/strings/utf_string_conversions.h" 13 #include "base/strings/utf_string_conversions.h"
14 #include "base/task_scheduler/post_task.h"
13 #include "chrome/browser/browser_process.h" 15 #include "chrome/browser/browser_process.h"
14 #include "chrome/browser/notifications/native_notification_display_service.h" 16 #include "chrome/browser/notifications/native_notification_display_service.h"
15 #include "chrome/browser/notifications/notification.h" 17 #include "chrome/browser/notifications/notification.h"
16 #include "chrome/browser/notifications/notification_display_service_factory.h" 18 #include "chrome/browser/notifications/notification_display_service_factory.h"
17 #include "chrome/browser/profiles/profile_manager.h" 19 #include "chrome/browser/profiles/profile_manager.h"
18 20
19 namespace { 21 namespace {
20 22
21 const char kFreedesktopNotificationsName[] = "org.freedesktop.Notifications"; 23 const char kFreedesktopNotificationsName[] = "org.freedesktop.Notifications";
22 const char kFreedesktopNotificationsPath[] = "/org/freedesktop/Notifications"; 24 const char kFreedesktopNotificationsPath[] = "/org/freedesktop/Notifications";
23 25
24 void AddActionToNotification(GVariantBuilder* actions_builder, 26 void AddActionToNotification(GVariantBuilder* actions_builder,
25 const char* action_id, 27 const char* action_id,
26 const char* button_label) { 28 const char* button_label) {
27 g_variant_builder_add(actions_builder, "s", action_id); 29 g_variant_builder_add(actions_builder, "s", action_id);
28 g_variant_builder_add(actions_builder, "s", button_label); 30 g_variant_builder_add(actions_builder, "s", button_label);
29 } 31 }
30 32
33 int NotificationPriorityToFdoUrgency(int priority) {
34 enum FdoUrgency {
35 LOW = 0,
36 NORMAL = 1,
37 CRITICAL = 2,
38 };
39 switch (priority) {
40 case message_center::MIN_PRIORITY:
41 case message_center::LOW_PRIORITY:
42 return LOW;
43 case message_center::HIGH_PRIORITY:
44 case message_center::MAX_PRIORITY:
45 return CRITICAL;
46 default:
47 NOTREACHED();
48 // fallthrough
49 case message_center::DEFAULT_PRIORITY:
50 return NORMAL;
51 }
52 }
53
31 // Callback used by GLib when the "Notify" message completes for the 54 // Callback used by GLib when the "Notify" message completes for the
32 // first time. 55 // first time.
33 void NotifyCompleteReceiver(GObject* source_object, 56 void NotifyCompleteReceiver(GObject* source_object,
34 GAsyncResult* result, 57 GAsyncResult* result,
35 gpointer user_data) { 58 gpointer user_data) {
36 GDBusProxy* proxy = G_DBUS_PROXY(source_object); 59 GDBusProxy* proxy = G_DBUS_PROXY(source_object);
37 GVariant* value = g_dbus_proxy_call_finish(proxy, result, nullptr); 60 GVariant* value = g_dbus_proxy_call_finish(proxy, result, nullptr);
38 if (!value) { 61 if (!value) {
39 // The message might have been cancelled, in which case 62 // The message might have been cancelled, in which case
40 // |user_data| points to a destroyed NotificationData. 63 // |user_data| points to a destroyed NotificationData.
(...skipping 17 matching lines...) Expand all
58 return; 81 return;
59 82
60 NotificationDisplayService* display_service = 83 NotificationDisplayService* display_service =
61 NotificationDisplayServiceFactory::GetForProfile(profile); 84 NotificationDisplayServiceFactory::GetForProfile(profile);
62 85
63 static_cast<NativeNotificationDisplayService*>(display_service) 86 static_cast<NativeNotificationDisplayService*>(display_service)
64 ->ProcessNotificationOperation(operation, notification_type, origin, 87 ->ProcessNotificationOperation(operation, notification_type, origin,
65 notification_id, action_index, reply); 88 notification_id, action_index, reply);
66 } 89 }
67 90
91 // Writes |image| to a new temporary file and return its path.
92 // Returns true on success.
93 base::FilePath WriteImageToTmpFile(const gfx::Image& image) {
94 base::FilePath file_path;
95 if (image.IsEmpty() || !base::CreateTemporaryFile(&file_path))
Peter Beverloo 2017/04/12 00:35:16 One thing that we discussed for the Windows Action
Tom (Use chromium acct) 2017/04/13 02:16:28 The spec doesn't make any guarantees about this, b
96 return base::FilePath();
97 auto image_data = image.As1xPNGBytes();
Peter Beverloo 2017/04/12 00:35:16 micro nit: I'd spell out the type (scoped_refptr<b
Tom (Use chromium acct) 2017/04/13 02:16:28 Done.
98 int data_len = image_data->size();
99 if (data_len == 0 || base::WriteFile(file_path, image_data->front_as<char>(),
Lei Zhang 2017/04/12 01:01:41 Can we fetch the image data on the UI thread, and
Tom (Use chromium acct) 2017/04/13 02:16:29 Done.
100 data_len) != data_len) {
101 base::DeleteFile(file_path, false);
102 return base::FilePath();
103 }
104 return file_path;
105 }
106
107 void DeleteFile(const base::FilePath& file_path) {
Lei Zhang 2017/04/13 01:12:17 BTW, name this DeleteNotificationImage() so it's e
Tom (Use chromium acct) 2017/04/13 02:16:28 Done.
108 if (file_path.empty())
109 return;
110 base::PostTaskWithTraits(
111 FROM_HERE,
112 base::TaskTraits()
113 .MayBlock()
114 .WithPriority(base::TaskPriority::BACKGROUND)
115 .WithShutdownBehavior(
116 base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN),
117 base::Bind(
Lei Zhang 2017/04/12 01:03:54 Isn't this just: base::Bind(&base::DeleteFile, fi
Tom (Use chromium acct) 2017/04/13 02:16:28 Done. Turns out base::IgnoreResult() was the miss
118 [](const base::FilePath& file_path) {
119 base::DeleteFile(file_path, false);
120 },
121 file_path));
122 }
123
68 } // namespace 124 } // namespace
69 125
70 // static 126 // static
71 NotificationPlatformBridge* NotificationPlatformBridge::Create() { 127 NotificationPlatformBridge* NotificationPlatformBridge::Create() {
72 GDBusProxy* notification_proxy = g_dbus_proxy_new_for_bus_sync( 128 GDBusProxy* notification_proxy = g_dbus_proxy_new_for_bus_sync(
73 G_BUS_TYPE_SESSION, 129 G_BUS_TYPE_SESSION,
74 static_cast<GDBusProxyFlags>(G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | 130 static_cast<GDBusProxyFlags>(G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
75 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START), 131 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START),
76 nullptr, kFreedesktopNotificationsName, kFreedesktopNotificationsPath, 132 nullptr, kFreedesktopNotificationsName, kFreedesktopNotificationsPath,
77 kFreedesktopNotificationsName, nullptr, nullptr); 133 kFreedesktopNotificationsName, nullptr, nullptr);
78 if (!notification_proxy) 134 if (!notification_proxy)
79 return nullptr; 135 return nullptr;
80 return new NotificationPlatformBridgeLinux(notification_proxy); 136 return new NotificationPlatformBridgeLinux(notification_proxy);
81 } 137 }
82 138
83 struct NotificationPlatformBridgeLinux::NotificationData { 139 struct NotificationPlatformBridgeLinux::NotificationData {
84 NotificationData(NotificationCommon::Type notification_type, 140 NotificationData(NotificationCommon::Type notification_type,
85 const std::string& notification_id, 141 const std::string& notification_id,
86 const std::string& profile_id, 142 const std::string& profile_id,
87 bool is_incognito, 143 bool is_incognito,
88 const GURL& origin_url) 144 const GURL& origin_url)
89 : notification_type(notification_type), 145 : notification_type(notification_type),
90 notification_id(notification_id), 146 notification_id(notification_id),
91 profile_id(profile_id), 147 profile_id(profile_id),
92 is_incognito(is_incognito), 148 is_incognito(is_incognito),
93 origin_url(origin_url) {} 149 origin_url(origin_url),
150 weak_factory(this) {}
94 151
95 ~NotificationData() { 152 ~NotificationData() {
96 if (cancellable) 153 if (cancellable)
97 g_cancellable_cancel(cancellable); 154 g_cancellable_cancel(cancellable);
155 ResetResourceFiles();
Peter Beverloo 2017/04/12 00:35:16 Since the NotificationPlatformBridgeLinux is owned
Tom (Use chromium acct) 2017/04/13 02:16:28 Turns out it will not. Added NOTIFICATION_APP_TER
156 }
157
158 void ResetResourceFiles() {
159 for (const base::FilePath& file : resource_files)
160 DeleteFile(file);
Tom (Use chromium acct) 2017/04/11 23:21:08 So all of the resource files get cleaned up when c
161 resource_files.clear();
98 } 162 }
99 163
100 // The ID used by the notification server. Will be 0 until the 164 // The ID used by the notification server. Will be 0 until the
101 // first "Notify" message completes. 165 // first "Notify" message completes.
102 uint32_t dbus_id = 0; 166 uint32_t dbus_id = 0;
103 167
104 // Same parameters used by NotificationPlatformBridge::Display(). 168 // Same parameters used by NotificationPlatformBridge::Display().
105 const NotificationCommon::Type notification_type; 169 NotificationCommon::Type notification_type;
106 const std::string notification_id; 170 const std::string notification_id;
107 const std::string profile_id; 171 const std::string profile_id;
108 const bool is_incognito; 172 const bool is_incognito;
109 173
110 // A copy of the origin_url from the underlying 174 // A copy of the origin_url from the underlying
111 // message_center::Notification. Used to pass back to 175 // message_center::Notification. Used to pass back to
112 // NativeNotificationDisplayService. 176 // NativeNotificationDisplayService.
113 const GURL origin_url; 177 const GURL origin_url;
114 178
179 // Temporary resource files associated with the notification that
180 // should be cleaned up when the notification is closed.
181 std::vector<base::FilePath> resource_files;
Peter Beverloo 2017/04/12 00:35:16 Out of interest, are you aware of base::FileProxy?
Tom (Use chromium acct) 2017/04/13 02:16:29 Didn't know about that. The only thing is, it wou
182
115 // Used to cancel the initial "Notify" message so we don't call 183 // Used to cancel the initial "Notify" message so we don't call
116 // NotificationPlatformBridgeLinux::NotifyCompleteInternal() with a 184 // NotificationPlatformBridgeLinux::NotifyCompleteInternal() with a
117 // destroyed Notification. 185 // destroyed Notification.
118 ScopedGObject<GCancellable> cancellable; 186 ScopedGObject<GCancellable> cancellable;
119 187
120 // If not null, the data to update the notification with once 188 // If not null, the data to update the notification with once
121 // |dbus_id| becomes available. 189 // |dbus_id| becomes available.
122 std::unique_ptr<Notification> update_data; 190 std::unique_ptr<Notification> update_data;
191 NotificationCommon::Type update_notification_type =
192 NotificationCommon::TYPE_MAX;
123 193
124 // If true, indicates the notification should be closed once 194 // If true, indicates the notification should be closed once
125 // |dbus_id| becomes available. 195 // |dbus_id| becomes available.
126 bool should_close = false; 196 bool should_close = false;
197
198 base::WeakPtrFactory<NotificationData> weak_factory;
199 };
200
201 struct NotificationPlatformBridgeLinux::ResourceFiles {
202 explicit ResourceFiles(const base::FilePath& icon_file)
203 : icon_file(icon_file) {}
204 ~ResourceFiles() { DeleteFile(icon_file); }
205 base::FilePath icon_file;
127 }; 206 };
128 207
129 NotificationPlatformBridgeLinux::NotificationPlatformBridgeLinux( 208 NotificationPlatformBridgeLinux::NotificationPlatformBridgeLinux(
130 GDBusProxy* notification_proxy) 209 GDBusProxy* notification_proxy)
131 : notification_proxy_(notification_proxy) { 210 : notification_proxy_(notification_proxy), weak_factory_(this) {
132 proxy_signal_handler_ = g_signal_connect( 211 proxy_signal_handler_ = g_signal_connect(
133 notification_proxy_, "g-signal", G_CALLBACK(GSignalReceiverThunk), this); 212 notification_proxy_, "g-signal", G_CALLBACK(GSignalReceiverThunk), this);
134 } 213 }
135 214
136 NotificationPlatformBridgeLinux::~NotificationPlatformBridgeLinux() { 215 NotificationPlatformBridgeLinux::~NotificationPlatformBridgeLinux() {
137 if (proxy_signal_handler_) 216 if (proxy_signal_handler_)
138 g_signal_handler_disconnect(notification_proxy_, proxy_signal_handler_); 217 g_signal_handler_disconnect(notification_proxy_, proxy_signal_handler_);
139 } 218 }
140 219
141 void NotificationPlatformBridgeLinux::Display( 220 void NotificationPlatformBridgeLinux::Display(
142 NotificationCommon::Type notification_type, 221 NotificationCommon::Type notification_type,
143 const std::string& notification_id, 222 const std::string& notification_id,
144 const std::string& profile_id, 223 const std::string& profile_id,
145 bool is_incognito, 224 bool is_incognito,
146 const Notification& notification) { 225 const Notification& notification) {
147 NotificationData* data = 226 NotificationData* data =
148 FindNotificationData(notification_id, profile_id, is_incognito); 227 FindNotificationData(notification_id, profile_id, is_incognito);
149 if (data) { 228 if (data) {
150 // Update an existing notification. 229 // Update an existing notification.
151 DCHECK_EQ(data->notification_type, notification_type);
152 if (data->dbus_id) { 230 if (data->dbus_id) {
153 NotifyNow(data->dbus_id, notification_type, notification, nullptr, 231 data->notification_type = notification_type;
154 nullptr, nullptr); 232 Notify(notification, data, nullptr, nullptr);
155 } else { 233 } else {
234 data->update_notification_type = notification_type;
156 data->update_data = base::MakeUnique<Notification>(notification); 235 data->update_data = base::MakeUnique<Notification>(notification);
157 } 236 }
158 } else { 237 } else {
159 // Send the notification for the first time. 238 // Send the notification for the first time.
160 data = new NotificationData(notification_type, notification_id, profile_id, 239 data = new NotificationData(notification_type, notification_id, profile_id,
161 is_incognito, notification.origin_url()); 240 is_incognito, notification.origin_url());
162 data->cancellable.reset(g_cancellable_new()); 241 data->cancellable.reset(g_cancellable_new());
163 notifications_.emplace(data, base::WrapUnique(data)); 242 notifications_.emplace(data, base::WrapUnique(data));
164 NotifyNow(0, notification_type, notification, data->cancellable, 243 Notify(notification, data, NotifyCompleteReceiver, data);
165 NotifyCompleteReceiver, data);
166 } 244 }
167 } 245 }
168 246
169 void NotificationPlatformBridgeLinux::Close( 247 void NotificationPlatformBridgeLinux::Close(
170 const std::string& profile_id, 248 const std::string& profile_id,
171 const std::string& notification_id) { 249 const std::string& notification_id) {
172 std::vector<NotificationData*> to_erase; 250 std::vector<NotificationData*> to_erase;
173 for (const auto& pair : notifications_) { 251 for (const auto& pair : notifications_) {
174 NotificationData* data = pair.first; 252 NotificationData* data = pair.first;
175 if (data->notification_id == notification_id && 253 if (data->notification_id == notification_id &&
(...skipping 26 matching lines...) Expand all
202 if (value && g_variant_is_of_type(value, G_VARIANT_TYPE("(u)"))) 280 if (value && g_variant_is_of_type(value, G_VARIANT_TYPE("(u)")))
203 g_variant_get(value, "(u)", &data->dbus_id); 281 g_variant_get(value, "(u)", &data->dbus_id);
204 282
205 if (!data->dbus_id) { 283 if (!data->dbus_id) {
206 // There was some sort of error with creating the notification. 284 // There was some sort of error with creating the notification.
207 notifications_.erase(data); 285 notifications_.erase(data);
208 } else if (data->should_close) { 286 } else if (data->should_close) {
209 CloseNow(data->dbus_id); 287 CloseNow(data->dbus_id);
210 notifications_.erase(data); 288 notifications_.erase(data);
211 } else if (data->update_data) { 289 } else if (data->update_data) {
212 NotifyNow(data->dbus_id, data->notification_type, *data->update_data, 290 data->notification_type = data->update_notification_type;
213 nullptr, nullptr, nullptr); 291 Notify(*data->update_data, data, nullptr, nullptr);
214 data->update_data.reset(); 292 data->update_data.reset();
215 } 293 }
216 } 294 }
217 295
296 void NotificationPlatformBridgeLinux::Notify(const Notification& notification,
297 NotificationData* data,
298 GAsyncReadyCallback callback,
299 gpointer user_data) {
300 base::PostTaskWithTraitsAndReplyWithResult(
301 FROM_HERE,
302 base::TaskTraits()
303 .MayBlock()
304 .WithPriority(base::TaskPriority::USER_BLOCKING)
305 .WithShutdownBehavior(base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN),
306 base::Bind(
307 [](const gfx::Image& icon) {
308 return base::MakeUnique<ResourceFiles>(WriteImageToTmpFile(icon));
Lei Zhang 2017/04/12 01:03:54 If NotifyNow() can take the FilePath as a result a
Tom (Use chromium acct) 2017/04/13 02:16:28 I want to keep it as a unique_ptr so that the reso
309 },
310 notification.icon()),
311 base::Bind(&NotificationPlatformBridgeLinux::NotifyNow,
312 weak_factory_.GetWeakPtr(), notification,
313 data->weak_factory.GetWeakPtr(), callback, user_data));
314 }
315
218 void NotificationPlatformBridgeLinux::NotifyNow( 316 void NotificationPlatformBridgeLinux::NotifyNow(
219 uint32_t dbus_id,
220 NotificationCommon::Type notification_type,
221 const Notification& notification, 317 const Notification& notification,
222 GCancellable* cancellable, 318 base::WeakPtr<NotificationData> data,
223 GAsyncReadyCallback callback, 319 GAsyncReadyCallback callback,
224 gpointer user_data) { 320 gpointer user_data,
225 // TODO(thomasanderson): Add a complete implementation. 321 std::unique_ptr<ResourceFiles> resource_files) {
322 if (!data)
323 return;
324
325 if (data->dbus_id)
326 DCHECK(!data->cancellable);
327
328 data->ResetResourceFiles();
226 329
227 GVariantBuilder actions_builder; 330 GVariantBuilder actions_builder;
228 // Even-indexed elements in this array are action IDs passed back to 331 // Even-indexed elements in this array are action IDs passed back to
229 // us in GSignalReceiver. Odd-indexed ones contain the button text. 332 // us in GSignalReceiver. Odd-indexed ones contain the button text.
230 g_variant_builder_init(&actions_builder, G_VARIANT_TYPE("as")); 333 g_variant_builder_init(&actions_builder, G_VARIANT_TYPE("as"));
231 if (notification.clickable()) { 334 if (notification.clickable()) {
232 // Special case: the pair ("default", "") will not add a button, 335 // Special case: the pair ("default", "") will not add a button,
233 // but instead makes the entire notification clickable. 336 // but instead makes the entire notification clickable.
234 AddActionToNotification(&actions_builder, "default", ""); 337 AddActionToNotification(&actions_builder, "default", "");
235 } 338 }
236 // Always add a settings button. 339 // Always add a settings button.
237 AddActionToNotification(&actions_builder, "settings", "Settings"); 340 AddActionToNotification(&actions_builder, "settings", "Settings");
238 341
342 GVariantBuilder hints_builder;
343 g_variant_builder_init(&hints_builder, G_VARIANT_TYPE("a{sv}"));
344 g_variant_builder_add(&hints_builder, "{sv}", "urgency",
345 g_variant_new_byte(NotificationPriorityToFdoUrgency(
346 notification.priority())));
347
348 if (!resource_files->icon_file.empty()) {
349 g_variant_builder_add(
350 &hints_builder, "{sv}", "image-path",
351 g_variant_new_string(resource_files->icon_file.value().c_str()));
352 data->resource_files.push_back(resource_files->icon_file);
353 resource_files->icon_file.clear();
354 }
355
239 const std::string title = base::UTF16ToUTF8(notification.title()); 356 const std::string title = base::UTF16ToUTF8(notification.title());
240 const std::string message = base::UTF16ToUTF8(notification.message()); 357 const std::string message = base::UTF16ToUTF8(notification.message());
241 358
242 GVariant* parameters = 359 GVariant* parameters =
243 g_variant_new("(susssasa{sv}i)", "", dbus_id, "", title.c_str(), 360 g_variant_new("(susssasa{sv}i)", "", data->dbus_id, "", title.c_str(),
244 message.c_str(), &actions_builder, nullptr, -1); 361 message.c_str(), &actions_builder, &hints_builder, -1);
245 g_dbus_proxy_call(notification_proxy_, "Notify", parameters, 362 g_dbus_proxy_call(notification_proxy_, "Notify", parameters,
246 G_DBUS_CALL_FLAGS_NONE, -1, cancellable, callback, 363 G_DBUS_CALL_FLAGS_NONE, -1, data->cancellable, callback,
247 user_data); 364 user_data);
248 } 365 }
249 366
250 void NotificationPlatformBridgeLinux::CloseNow(uint32_t dbus_id) { 367 void NotificationPlatformBridgeLinux::CloseNow(uint32_t dbus_id) {
251 g_dbus_proxy_call(notification_proxy_, "CloseNotification", 368 g_dbus_proxy_call(notification_proxy_, "CloseNotification",
252 g_variant_new("(u)", dbus_id), G_DBUS_CALL_FLAGS_NONE, -1, 369 g_variant_new("(u)", dbus_id), G_DBUS_CALL_FLAGS_NONE, -1,
253 nullptr, nullptr, nullptr); 370 nullptr, nullptr, nullptr);
254 } 371 }
255 372
256 NotificationPlatformBridgeLinux::NotificationData* 373 NotificationPlatformBridgeLinux::NotificationData*
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
322 439
323 if (strcmp(action, "default") == 0) { 440 if (strcmp(action, "default") == 0) {
324 ForwardNotificationOperation(dbus_id, NotificationCommon::CLICK, 0); 441 ForwardNotificationOperation(dbus_id, NotificationCommon::CLICK, 0);
325 } else if (strcmp(action, "settings") == 0) { 442 } else if (strcmp(action, "settings") == 0) {
326 ForwardNotificationOperation(dbus_id, NotificationCommon::SETTINGS, -1); 443 ForwardNotificationOperation(dbus_id, NotificationCommon::SETTINGS, -1);
327 } else { 444 } else {
328 NOTIMPLEMENTED() << "No custom buttons just yet!"; 445 NOTIMPLEMENTED() << "No custom buttons just yet!";
329 } 446 }
330 } 447 }
331 } 448 }
OLDNEW
« no previous file with comments | « chrome/browser/notifications/notification_platform_bridge_linux.h ('k') | ui/gfx/image/image.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698