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

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

Issue 2872053002: Linux native notifications: Support image notifications (Closed)
Patch Set: Add unit test Created 3 years, 7 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 | « no previous file | chrome/browser/notifications/notification_platform_bridge_linux_unittest.cc » ('j') | 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 #include <memory> 8 #include <memory>
9 #include <set> 9 #include <set>
10 #include <unordered_map> 10 #include <unordered_map>
(...skipping 18 matching lines...) Expand all
29 #include "chrome/browser/notifications/notification.h" 29 #include "chrome/browser/notifications/notification.h"
30 #include "chrome/browser/notifications/notification_display_service_factory.h" 30 #include "chrome/browser/notifications/notification_display_service_factory.h"
31 #include "chrome/browser/profiles/profile_manager.h" 31 #include "chrome/browser/profiles/profile_manager.h"
32 #include "chrome/browser/shell_integration_linux.h" 32 #include "chrome/browser/shell_integration_linux.h"
33 #include "chrome/grit/generated_resources.h" 33 #include "chrome/grit/generated_resources.h"
34 #include "content/public/browser/browser_thread.h" 34 #include "content/public/browser/browser_thread.h"
35 #include "content/public/browser/notification_service.h" 35 #include "content/public/browser/notification_service.h"
36 #include "dbus/bus.h" 36 #include "dbus/bus.h"
37 #include "dbus/message.h" 37 #include "dbus/message.h"
38 #include "dbus/object_proxy.h" 38 #include "dbus/object_proxy.h"
39 #include "skia/ext/image_operations.h"
39 #include "ui/base/l10n/l10n_util.h" 40 #include "ui/base/l10n/l10n_util.h"
40 #include "ui/gfx/image/image_skia.h" 41 #include "ui/gfx/image/image_skia.h"
41 42
42 namespace { 43 namespace {
43 44
44 // DBus name / path. 45 // DBus name / path.
45 const char kFreedesktopNotificationsName[] = "org.freedesktop.Notifications"; 46 const char kFreedesktopNotificationsName[] = "org.freedesktop.Notifications";
46 const char kFreedesktopNotificationsPath[] = "/org/freedesktop/Notifications"; 47 const char kFreedesktopNotificationsPath[] = "/org/freedesktop/Notifications";
47 48
48 // DBus methods. 49 // DBus methods.
(...skipping 15 matching lines...) Expand all
64 const char kCapabilityBodyMarkup[] = "body-markup"; 65 const char kCapabilityBodyMarkup[] = "body-markup";
65 const char kCapabilityIconMulti[] = "icon-multi"; 66 const char kCapabilityIconMulti[] = "icon-multi";
66 const char kCapabilityIconStatic[] = "icon-static"; 67 const char kCapabilityIconStatic[] = "icon-static";
67 const char kCapabilityPersistence[] = "persistence"; 68 const char kCapabilityPersistence[] = "persistence";
68 const char kCapabilitySound[] = "sound"; 69 const char kCapabilitySound[] = "sound";
69 70
70 // Button IDs. 71 // Button IDs.
71 const char kDefaultButtonId[] = "default"; 72 const char kDefaultButtonId[] = "default";
72 const char kSettingsButtonId[] = "settings"; 73 const char kSettingsButtonId[] = "settings";
73 74
75 // Max image size; specified in the FDO notification specification.
76 const int kMaxImageWidth = 200;
77 const int kMaxImageHeight = 100;
78
74 // The values in this enumeration correspond to those of the 79 // The values in this enumeration correspond to those of the
75 // Linux.NotificationPlatformBridge.InitializationStatus histogram, so 80 // Linux.NotificationPlatformBridge.InitializationStatus histogram, so
76 // the ordering should not be changed. New error codes should be 81 // the ordering should not be changed. New error codes should be
77 // added at the end, before NUM_ITEMS. 82 // added at the end, before NUM_ITEMS.
78 enum class ConnectionInitializationStatusCode { 83 enum class ConnectionInitializationStatusCode {
79 SUCCESS = 0, 84 SUCCESS = 0,
80 NATIVE_NOTIFICATIONS_NOT_SUPPORTED = 1, 85 NATIVE_NOTIFICATIONS_NOT_SUPPORTED = 1,
81 MISSING_REQUIRED_CAPABILITIES = 2, 86 MISSING_REQUIRED_CAPABILITIES = 2,
82 COULD_NOT_CONNECT_TO_SIGNALS = 3, 87 COULD_NOT_CONNECT_TO_SIGNALS = 3,
83 INCOMPATIBLE_SPEC_VERSION = 4, 88 INCOMPATIBLE_SPEC_VERSION = 4,
84 NUM_ITEMS 89 NUM_ITEMS
85 }; 90 };
86 91
92 class StringStreamSizable {
93 public:
94 StringStreamSizable& operator<<(const std::string& str) {
95 stream_ << str;
96 size_ += str.size();
97 return *this;
98 }
99
100 std::string str() const { return stream_.str(); }
101
102 size_t size() const { return size_; }
103
104 private:
105 size_t size_ = 0;
106 std::stringstream stream_;
Lei Zhang 2017/05/15 21:48:41 ostringstream to be more specific?
Lei Zhang 2017/05/15 21:48:41 #include <sstream>
Tom (Use chromium acct) 2017/05/16 00:22:00 Done.
Tom (Use chromium acct) 2017/05/16 00:22:00 Done.
107 };
108
109 int ClampInt(int value, int low, int hi) {
110 return std::max(std::min(value, hi), low);
111 }
112
87 base::string16 CreateNotificationTitle(const Notification& notification) { 113 base::string16 CreateNotificationTitle(const Notification& notification) {
88 base::string16 title; 114 base::string16 title;
89 if (notification.type() == message_center::NOTIFICATION_TYPE_PROGRESS) { 115 if (notification.type() == message_center::NOTIFICATION_TYPE_PROGRESS) {
90 title += base::FormatPercent(notification.progress()); 116 title += base::FormatPercent(notification.progress());
91 title += base::UTF8ToUTF16(" - "); 117 title += base::UTF8ToUTF16(" - ");
92 } 118 }
93 title += notification.title(); 119 title += notification.title();
94 return title; 120 return title;
95 } 121 }
96 122
(...skipping 17 matching lines...) Expand all
114 case message_center::HIGH_PRIORITY: 140 case message_center::HIGH_PRIORITY:
115 case message_center::MAX_PRIORITY: 141 case message_center::MAX_PRIORITY:
116 return CRITICAL; 142 return CRITICAL;
117 default: 143 default:
118 NOTREACHED(); 144 NOTREACHED();
119 case message_center::DEFAULT_PRIORITY: 145 case message_center::DEFAULT_PRIORITY:
120 return NORMAL; 146 return NORMAL;
121 } 147 }
122 } 148 }
123 149
150 // Constrain |image|'s size to |kMaxImageWidth|x|kMaxImageHeight|. If
151 // the image does not need to be resized, or the image is empty,
152 // returns |image| directly.
153 gfx::Image ResizeImageToFdoMaxSize(const gfx::Image& image) {
154 if (image.IsEmpty())
155 return image;
156 int width = image.Width();
157 int height = image.Height();
158 if (width <= kMaxImageWidth && height <= kMaxImageHeight) {
159 return image;
160 }
161 const SkBitmap* image_bitmap = image.ToSkBitmap();
162 double scale = std::min(static_cast<double>(kMaxImageWidth) / width,
163 static_cast<double>(kMaxImageHeight) / height);
164 width = ClampInt(scale * width, 1, kMaxImageWidth);
165 height = ClampInt(scale * height, 1, kMaxImageHeight);
166 return gfx::Image(
167 gfx::ImageSkia::CreateFrom1xBitmap(skia::ImageOperations::Resize(
168 *image_bitmap, skia::ImageOperations::RESIZE_LANCZOS3, width,
169 height)));
170 }
171
124 // Runs once the profile has been loaded in order to perform a given 172 // Runs once the profile has been loaded in order to perform a given
125 // |operation| on a notification. 173 // |operation| on a notification.
126 void ProfileLoadedCallback(NotificationCommon::Operation operation, 174 void ProfileLoadedCallback(NotificationCommon::Operation operation,
127 NotificationCommon::Type notification_type, 175 NotificationCommon::Type notification_type,
128 const std::string& origin, 176 const std::string& origin,
129 const std::string& notification_id, 177 const std::string& notification_id,
130 int action_index, 178 int action_index,
131 const base::NullableString16& reply, 179 const base::NullableString16& reply,
132 Profile* profile) { 180 Profile* profile) {
133 if (!profile) 181 if (!profile)
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after
228 bool is_incognito, 276 bool is_incognito,
229 const Notification& notification) override { 277 const Notification& notification) override {
230 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 278 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
231 // Notifications contain gfx::Image's which have reference counts 279 // Notifications contain gfx::Image's which have reference counts
232 // that are not thread safe. Because of this, we duplicate the 280 // that are not thread safe. Because of this, we duplicate the
233 // notification and its images. Wrap the notification in a 281 // notification and its images. Wrap the notification in a
234 // unique_ptr to transfer ownership of the notification (and the 282 // unique_ptr to transfer ownership of the notification (and the
235 // non-thread-safe reference counts) to the task runner thread. 283 // non-thread-safe reference counts) to the task runner thread.
236 auto notification_copy = base::MakeUnique<Notification>(notification); 284 auto notification_copy = base::MakeUnique<Notification>(notification);
237 notification_copy->set_icon(DeepCopyImage(notification_copy->icon())); 285 notification_copy->set_icon(DeepCopyImage(notification_copy->icon()));
238 notification_copy->set_image(gfx::Image()); 286 notification_copy->set_image(body_images_supported_
287 ? DeepCopyImage(notification_copy->image())
288 : gfx::Image());
239 notification_copy->set_small_image(gfx::Image()); 289 notification_copy->set_small_image(gfx::Image());
240 for (size_t i = 0; i < notification_copy->buttons().size(); i++) 290 for (size_t i = 0; i < notification_copy->buttons().size(); i++)
241 notification_copy->SetButtonIcon(i, gfx::Image()); 291 notification_copy->SetButtonIcon(i, gfx::Image());
242 292
243 PostTaskToTaskRunnerThread(base::BindOnce( 293 PostTaskToTaskRunnerThread(base::BindOnce(
244 &NotificationPlatformBridgeLinuxImpl::DisplayOnTaskRunner, this, 294 &NotificationPlatformBridgeLinuxImpl::DisplayOnTaskRunner, this,
245 notification_type, notification_id, profile_id, is_incognito, 295 notification_type, notification_id, profile_id, is_incognito,
246 base::Passed(&notification_copy))); 296 base::Passed(&notification_copy)));
247 } 297 }
248 298
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after
330 void Observe(int type, 380 void Observe(int type,
331 const content::NotificationSource& source, 381 const content::NotificationSource& source,
332 const content::NotificationDetails& details) override { 382 const content::NotificationDetails& details) override {
333 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 383 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
334 DCHECK_EQ(chrome::NOTIFICATION_APP_TERMINATING, type); 384 DCHECK_EQ(chrome::NOTIFICATION_APP_TERMINATING, type);
335 // The browser process is about to exit. Post the CleanUp() task 385 // The browser process is about to exit. Post the CleanUp() task
336 // while we still can. 386 // while we still can.
337 CleanUp(); 387 CleanUp();
338 } 388 }
339 389
390 void SetBodyImagesSupported(bool body_images_supported) {
391 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
392 body_images_supported_ = body_images_supported;
393 }
394
340 void PostTaskToUiThread(base::OnceClosure closure) const { 395 void PostTaskToUiThread(base::OnceClosure closure) const {
341 DCHECK(task_runner_->RunsTasksOnCurrentThread()); 396 DCHECK(task_runner_->RunsTasksOnCurrentThread());
342 bool success = content::BrowserThread::PostTask( 397 bool success = content::BrowserThread::PostTask(
343 content::BrowserThread::UI, FROM_HERE, std::move(closure)); 398 content::BrowserThread::UI, FROM_HERE, std::move(closure));
344 DCHECK(success); 399 DCHECK(success);
345 } 400 }
346 401
347 void PostTaskToTaskRunnerThread(base::OnceClosure closure) const { 402 void PostTaskToTaskRunnerThread(base::OnceClosure closure) const {
348 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 403 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
349 DCHECK(task_runner_); 404 DCHECK(task_runner_);
(...skipping 29 matching lines...) Expand all
379 notification_proxy_->CallMethodAndBlock( 434 notification_proxy_->CallMethodAndBlock(
380 &get_capabilities_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT); 435 &get_capabilities_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
381 if (capabilities_response) { 436 if (capabilities_response) {
382 dbus::MessageReader reader(capabilities_response.get()); 437 dbus::MessageReader reader(capabilities_response.get());
383 std::vector<std::string> capabilities; 438 std::vector<std::string> capabilities;
384 reader.PopArrayOfStrings(&capabilities); 439 reader.PopArrayOfStrings(&capabilities);
385 for (const std::string& capability : capabilities) 440 for (const std::string& capability : capabilities)
386 capabilities_.insert(capability); 441 capabilities_.insert(capability);
387 } 442 }
388 RecordMetricsForCapabilities(); 443 RecordMetricsForCapabilities();
444 PostTaskToUiThread(base::BindOnce(
445 &NotificationPlatformBridgeLinuxImpl::SetBodyImagesSupported, this,
446 base::ContainsKey(capabilities_, kCapabilityBodyImages)));
389 447
390 dbus::MethodCall get_server_information_call(kFreedesktopNotificationsName, 448 dbus::MethodCall get_server_information_call(kFreedesktopNotificationsName,
391 kMethodGetServerInformation); 449 kMethodGetServerInformation);
392 std::unique_ptr<dbus::Response> server_information_response = 450 std::unique_ptr<dbus::Response> server_information_response =
393 notification_proxy_->CallMethodAndBlock( 451 notification_proxy_->CallMethodAndBlock(
394 &get_server_information_call, 452 &get_server_information_call,
395 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT); 453 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
396 if (server_information_response) { 454 if (server_information_response) {
397 dbus::MessageReader reader(server_information_response.get()); 455 dbus::MessageReader reader(server_information_response.get());
398 std::string spec_version; 456 std::string spec_version;
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
465 writer.AppendString(""); 523 writer.AppendString("");
466 524
467 writer.AppendUint32(data->dbus_id); 525 writer.AppendUint32(data->dbus_id);
468 526
469 // app_icon passed implicitly via desktop-entry. 527 // app_icon passed implicitly via desktop-entry.
470 writer.AppendString(""); 528 writer.AppendString("");
471 529
472 writer.AppendString( 530 writer.AppendString(
473 base::UTF16ToUTF8(CreateNotificationTitle(*notification))); 531 base::UTF16ToUTF8(CreateNotificationTitle(*notification)));
474 532
475 std::string body; 533 StringStreamSizable body;
476 if (base::ContainsKey(capabilities_, kCapabilityBody)) { 534 if (base::ContainsKey(capabilities_, kCapabilityBody)) {
477 body = base::UTF16ToUTF8(notification->message());
478 const bool body_markup = 535 const bool body_markup =
479 base::ContainsKey(capabilities_, kCapabilityBodyMarkup); 536 base::ContainsKey(capabilities_, kCapabilityBodyMarkup);
537 std::string message = base::UTF16ToUTF8(notification->message());
480 if (body_markup) { 538 if (body_markup) {
481 base::ReplaceSubstringsAfterOffset(&body, 0, "&", "&amp;"); 539 base::ReplaceSubstringsAfterOffset(&message, 0, "&", "&amp;");
482 base::ReplaceSubstringsAfterOffset(&body, 0, "<", "&lt;"); 540 base::ReplaceSubstringsAfterOffset(&message, 0, "<", "&lt;");
483 base::ReplaceSubstringsAfterOffset(&body, 0, ">", "&gt;"); 541 base::ReplaceSubstringsAfterOffset(&message, 0, ">", "&gt;");
484 } 542 }
543 body << message;
544
485 if (notification->type() == message_center::NOTIFICATION_TYPE_MULTIPLE) { 545 if (notification->type() == message_center::NOTIFICATION_TYPE_MULTIPLE) {
486 for (const auto& item : notification->items()) { 546 for (const auto& item : notification->items()) {
487 if (!body.empty()) 547 if (body.size())
488 body += "\n"; 548 body << "\n";
489 const std::string title = base::UTF16ToUTF8(item.title); 549 const std::string title = base::UTF16ToUTF8(item.title);
490 const std::string message = base::UTF16ToUTF8(item.message); 550 const std::string message = base::UTF16ToUTF8(item.message);
491 // TODO(peter): Figure out the right way to internationalize 551 // TODO(peter): Figure out the right way to internationalize
492 // this for RTL languages. 552 // this for RTL languages.
493 if (body_markup) 553 if (body_markup)
494 body += "<b>" + title + "</b> " + message; 554 body << "<b>" << title << "</b> " << message;
Peter Beverloo 2017/05/15 22:07:33 One downside of using StringStreamSizable as oppos
Tom (Use chromium acct) 2017/05/16 00:22:00 I just realized that tellp() exists :P
495 else 555 else
496 body += title + " - " + message; 556 body << title << " - " << message;
557 }
558 } else if (notification->type() ==
559 message_center::NOTIFICATION_TYPE_IMAGE &&
560 base::ContainsKey(capabilities_, kCapabilityBodyImages)) {
561 std::unique_ptr<ResourceFile> image_file = WriteDataToTmpFile(
562 ResizeImageToFdoMaxSize(notification->image()).As1xPNGBytes());
563 if (image_file) {
564 if (body.size())
565 body << "\n";
566 body << "<img src=\"" << image_file->file_path().value()
Lei Zhang 2017/05/15 22:13:11 BTW, we should escape the inside of the src= attri
Tom (Use chromium acct) 2017/05/16 00:22:00 Done.
567 << "\" alt=\"\"/>";
568 data->resource_files.push_back(std::move(image_file));
497 } 569 }
498 } 570 }
499 } 571 }
500 writer.AppendString(body); 572 writer.AppendString(body.str());
501 573
502 // Even-indexed elements in this vector are action IDs passed back to 574 // Even-indexed elements in this vector are action IDs passed back to
503 // us in OnActionInvoked(). Odd-indexed ones contain the button text. 575 // us in OnActionInvoked(). Odd-indexed ones contain the button text.
504 std::vector<std::string> actions; 576 std::vector<std::string> actions;
505 if (base::ContainsKey(capabilities_, kCapabilityActions)) { 577 if (base::ContainsKey(capabilities_, kCapabilityActions)) {
506 data->action_start = data->action_end; 578 data->action_start = data->action_end;
507 for (const auto& button_info : notification->buttons()) { 579 for (const auto& button_info : notification->buttons()) {
508 // FDO notification buttons can contain either an icon or a label, 580 // FDO notification buttons can contain either an icon or a label,
509 // but not both, and the type of all buttons must be the same (all 581 // but not both, and the type of all buttons must be the same (all
510 // labels or all icons), so always use labels. 582 // labels or all icons), so always use labels.
(...skipping 265 matching lines...) Expand 10 before | Expand all | Expand 10 after
776 848
777 scoped_refptr<base::SequencedTaskRunner> task_runner_; 849 scoped_refptr<base::SequencedTaskRunner> task_runner_;
778 850
779 content::NotificationRegistrar registrar_; 851 content::NotificationRegistrar registrar_;
780 852
781 // State necessary for OnConnectionInitializationFinished() and 853 // State necessary for OnConnectionInitializationFinished() and
782 // SetReadyCallback(). 854 // SetReadyCallback().
783 base::Optional<bool> connected_; 855 base::Optional<bool> connected_;
784 std::vector<NotificationBridgeReadyCallback> on_connected_callbacks_; 856 std::vector<NotificationBridgeReadyCallback> on_connected_callbacks_;
785 857
858 // Notification servers very rarely have the 'body-images'
859 // capability, so try to avoid an image copy if possible. It is
860 // intialized to true so that the UI thread will conservatively copy
861 // the image before the GetCapabilities message completes.
862 bool body_images_supported_ = true;
863
786 ////////////////////////////////////////////////////////////////////////////// 864 //////////////////////////////////////////////////////////////////////////////
787 // Members used only on the task runner thread. 865 // Members used only on the task runner thread.
788 866
789 scoped_refptr<dbus::Bus> bus_; 867 scoped_refptr<dbus::Bus> bus_;
790 868
791 dbus::ObjectProxy* notification_proxy_ = nullptr; 869 dbus::ObjectProxy* notification_proxy_ = nullptr;
792 870
793 std::unordered_set<std::string> capabilities_; 871 std::unordered_set<std::string> capabilities_;
794 872
795 base::Version spec_version_; 873 base::Version spec_version_;
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
842 } 920 }
843 921
844 void NotificationPlatformBridgeLinux::SetReadyCallback( 922 void NotificationPlatformBridgeLinux::SetReadyCallback(
845 NotificationBridgeReadyCallback callback) { 923 NotificationBridgeReadyCallback callback) {
846 impl_->SetReadyCallback(std::move(callback)); 924 impl_->SetReadyCallback(std::move(callback));
847 } 925 }
848 926
849 void NotificationPlatformBridgeLinux::CleanUp() { 927 void NotificationPlatformBridgeLinux::CleanUp() {
850 impl_->CleanUp(); 928 impl_->CleanUp();
851 } 929 }
OLDNEW
« no previous file with comments | « no previous file | chrome/browser/notifications/notification_platform_bridge_linux_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698