notification_platform_bridge_linux.cc 37.9 KB
Newer Older
1 2 3 4 5 6
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/notifications/notification_platform_bridge_linux.h"

7
#include <algorithm>
8 9
#include <memory>
#include <set>
10
#include <sstream>
11
#include <unordered_map>
12
#include <unordered_set>
13 14
#include <utility>
#include <vector>
15

16
#include "base/barrier_closure.h"
17
#include "base/files/file_path_watcher.h"
18
#include "base/files/file_util.h"
19
#include "base/i18n/number_formatting.h"
20
#include "base/metrics/histogram_macros.h"
21
#include "base/strings/nullable_string16.h"
22
#include "base/strings/string_number_conversions.h"
23
#include "base/strings/string_split.h"
24
#include "base/strings/string_util.h"
25
#include "base/strings/utf_string_conversions.h"
26
#include "base/task_scheduler/post_task.h"
27
#include "chrome/browser/browser_process.h"
28
#include "chrome/browser/chrome_notification_types.h"
29
#include "chrome/browser/dbus/dbus_thread_linux.h"
30
#include "chrome/browser/notifications/notification_display_service.h"
31 32
#include "chrome/browser/notifications/notification_display_service_factory.h"
#include "chrome/browser/profiles/profile_manager.h"
33
#include "chrome/browser/shell_integration_linux.h"
34 35
#include "chrome/grit/chrome_unscaled_resources.h"
#include "chrome/grit/chromium_strings.h"
36
#include "chrome/grit/generated_resources.h"
37
#include "components/url_formatter/elide_url.h"
38
#include "content/public/browser/browser_thread.h"
39
#include "content/public/browser/notification_service.h"
40 41 42
#include "dbus/bus.h"
#include "dbus/message.h"
#include "dbus/object_proxy.h"
43
#include "net/base/escape.h"
44
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
45
#include "skia/ext/image_operations.h"
46
#include "ui/base/l10n/l10n_util.h"
47
#include "ui/base/resource/resource_bundle.h"
48
#include "ui/gfx/image/image_skia.h"
49
#include "ui/message_center/notification.h"
50 51 52

namespace {

53
// DBus name / path.
54 55 56
const char kFreedesktopNotificationsName[] = "org.freedesktop.Notifications";
const char kFreedesktopNotificationsPath[] = "/org/freedesktop/Notifications";

57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
// DBus methods.
const char kMethodCloseNotification[] = "CloseNotification";
const char kMethodGetCapabilities[] = "GetCapabilities";
const char kMethodNotify[] = "Notify";

// DBus signals.
const char kSignalActionInvoked[] = "ActionInvoked";
const char kSignalNotificationClosed[] = "NotificationClosed";

// Capabilities.
const char kCapabilityActionIcons[] = "action-icons";
const char kCapabilityActions[] = "actions";
const char kCapabilityBody[] = "body";
const char kCapabilityBodyHyperlinks[] = "body-hyperlinks";
const char kCapabilityBodyImages[] = "body-images";
const char kCapabilityBodyMarkup[] = "body-markup";
const char kCapabilityIconMulti[] = "icon-multi";
const char kCapabilityIconStatic[] = "icon-static";
const char kCapabilityPersistence[] = "persistence";
const char kCapabilitySound[] = "sound";

// Button IDs.
79 80 81
const char kDefaultButtonId[] = "default";
const char kSettingsButtonId[] = "settings";

82 83 84 85
// Max image size; specified in the FDO notification specification.
const int kMaxImageWidth = 200;
const int kMaxImageHeight = 100;

86 87 88
// Notification on-screen time, in milliseconds.
const int32_t kExpireTimeout = 25000;

89 90 91
// The maximum amount of characters for displaying the full origin path.
const size_t kMaxAllowedOriginLength = 28;

92 93 94 95 96 97 98 99 100
// The values in this enumeration correspond to those of the
// Linux.NotificationPlatformBridge.InitializationStatus histogram, so
// the ordering should not be changed.  New error codes should be
// added at the end, before NUM_ITEMS.
enum class ConnectionInitializationStatusCode {
  SUCCESS = 0,
  NATIVE_NOTIFICATIONS_NOT_SUPPORTED = 1,
  MISSING_REQUIRED_CAPABILITIES = 2,
  COULD_NOT_CONNECT_TO_SIGNALS = 3,
101
  INCOMPATIBLE_SPEC_VERSION = 4,  // DEPRECATED
102 103 104
  NUM_ITEMS
};

105 106 107 108
int ClampInt(int value, int low, int hi) {
  return std::max(std::min(value, hi), low);
}

109 110
base::string16 CreateNotificationTitle(
    const message_center::Notification& notification) {
111 112 113 114 115 116 117 118 119
  base::string16 title;
  if (notification.type() == message_center::NOTIFICATION_TYPE_PROGRESS) {
    title += base::FormatPercent(notification.progress());
    title += base::UTF8ToUTF16(" - ");
  }
  title += notification.title();
  return title;
}

120 121 122 123 124 125 126 127 128
void EscapeUnsafeCharacters(std::string* message) {
  // Canonical's notification development guidelines recommends only
  // escaping the '&', '<', and '>' characters:
  // https://wiki.ubuntu.com/NotificationDevelopmentGuidelines
  base::ReplaceChars(*message, "&", "&amp;", message);
  base::ReplaceChars(*message, "<", "&lt;", message);
  base::ReplaceChars(*message, ">", "&gt;", message);
}

129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
int NotificationPriorityToFdoUrgency(int priority) {
  enum FdoUrgency {
    LOW = 0,
    NORMAL = 1,
    CRITICAL = 2,
  };
  switch (priority) {
    case message_center::MIN_PRIORITY:
    case message_center::LOW_PRIORITY:
      return LOW;
    case message_center::HIGH_PRIORITY:
    case message_center::MAX_PRIORITY:
      return CRITICAL;
    default:
      NOTREACHED();
    case message_center::DEFAULT_PRIORITY:
      return NORMAL;
  }
}

149 150 151 152
// Constrain |image|'s size to |kMaxImageWidth|x|kMaxImageHeight|. If
// the image does not need to be resized, or the image is empty,
// returns |image| directly.
gfx::Image ResizeImageToFdoMaxSize(const gfx::Image& image) {
153
  DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
  if (image.IsEmpty())
    return image;
  int width = image.Width();
  int height = image.Height();
  if (width <= kMaxImageWidth && height <= kMaxImageHeight) {
    return image;
  }
  const SkBitmap* image_bitmap = image.ToSkBitmap();
  double scale = std::min(static_cast<double>(kMaxImageWidth) / width,
                          static_cast<double>(kMaxImageHeight) / height);
  width = ClampInt(scale * width, 1, kMaxImageWidth);
  height = ClampInt(scale * height, 1, kMaxImageHeight);
  return gfx::Image(
      gfx::ImageSkia::CreateFrom1xBitmap(skia::ImageOperations::Resize(
          *image_bitmap, skia::ImageOperations::RESIZE_LANCZOS3, width,
          height)));
}

172 173 174 175
// Runs once the profile has been loaded in order to perform a given
// |operation| on a notification.
void ProfileLoadedCallback(NotificationCommon::Operation operation,
                           NotificationCommon::Type notification_type,
176
                           const GURL& origin,
177
                           const std::string& notification_id,
178 179 180
                           const base::Optional<int>& action_index,
                           const base::Optional<base::string16>& reply,
                           const base::Optional<bool>& by_user,
181 182 183 184
                           Profile* profile) {
  if (!profile)
    return;

185 186
  auto* display_service =
      NotificationDisplayServiceFactory::GetForProfile(profile);
187 188
  display_service->ProcessNotificationOperation(operation, notification_type,
                                                origin, notification_id,
189
                                                action_index, reply, by_user);
190 191
}

192 193 194
void ForwardNotificationOperationOnUiThread(
    NotificationCommon::Operation operation,
    NotificationCommon::Type notification_type,
195
    const GURL& origin,
196
    const std::string& notification_id,
197 198
    const base::Optional<int>& action_index,
    const base::Optional<bool>& by_user,
199 200 201
    const std::string& profile_id,
    bool is_incognito) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
202
  g_browser_process->profile_manager()->LoadProfile(
203 204
      profile_id, is_incognito,
      base::Bind(&ProfileLoadedCallback, operation, notification_type, origin,
205 206
                 notification_id, action_index, base::nullopt /* reply */,
                 by_user));
207 208
}

209 210 211 212 213
class ResourceFile {
 public:
  explicit ResourceFile(const base::FilePath& file_path)
      : file_path_(file_path) {
    DCHECK(!file_path.empty());
214
    DCHECK(file_path.IsAbsolute());
215 216 217 218 219 220 221 222 223 224 225 226 227 228
  }
  ~ResourceFile() { base::DeleteFile(file_path_, false); }

  const base::FilePath& file_path() const { return file_path_; }

 private:
  const base::FilePath file_path_;

  DISALLOW_COPY_AND_ASSIGN(ResourceFile);
};

// Writes |data| to a new temporary file and returns the ResourceFile
// that holds it.
std::unique_ptr<ResourceFile> WriteDataToTmpFile(
229 230 231
    const scoped_refptr<base::RefCountedMemory>& data) {
  int data_len = data->size();
  if (data_len == 0)
232
    return nullptr;
233 234
  base::FilePath file_path;
  if (!base::CreateTemporaryFile(&file_path))
235 236
    return nullptr;

237
  auto resource_file = std::make_unique<ResourceFile>(file_path);
238 239
  if (base::WriteFile(file_path, data->front_as<char>(), data_len) !=
      data_len) {
240
    resource_file.reset();
241
  }
242
  return resource_file;
243 244
}

245 246 247 248
}  // namespace

// static
NotificationPlatformBridge* NotificationPlatformBridge::Create() {
249
  return new NotificationPlatformBridgeLinux();
250 251
}

252 253 254 255 256
class NotificationPlatformBridgeLinuxImpl
    : public NotificationPlatformBridge,
      public content::NotificationObserver,
      public base::RefCountedThreadSafe<NotificationPlatformBridgeLinuxImpl> {
 public:
257 258 259
  explicit NotificationPlatformBridgeLinuxImpl(scoped_refptr<dbus::Bus> bus)
      : bus_(bus) {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
260
    task_runner_ = chrome::GetDBusTaskRunner();
261 262
    registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
                   content::NotificationService::AllSources());
263 264 265 266 267 268 269 270 271
  }

  // InitOnTaskRunner() cannot be posted from within the constructor
  // because of a race condition.  The reference count for |this|
  // starts out as 0.  Posting the Init task would increment the count
  // to 1.  If the task finishes before the constructor returns, the
  // count will go to 0 and the object would be prematurely
  // destructed.
  void Init() {
272
    product_logo_png_bytes_ =
273
        gfx::Image(*ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
274 275
                       IDR_PRODUCT_LOGO_256))
            .As1xPNGBytes();
276 277
    PostTaskToTaskRunnerThread(base::BindOnce(
        &NotificationPlatformBridgeLinuxImpl::InitOnTaskRunner, this));
278 279
  }

280 281 282 283
  void Display(
      NotificationCommon::Type notification_type,
      const std::string& profile_id,
      bool is_incognito,
284
      const message_center::Notification& notification,
285
      std::unique_ptr<NotificationCommon::Metadata> metadata) override {
286
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
287 288 289 290 291
    // Make a deep copy of the notification as its resources cannot safely
    // be passed between threads.
    auto notification_copy = message_center::Notification::DeepCopy(
        notification, body_images_supported_.value(),
        /*include_small_image=*/false, /*include_icon_images=*/false);
292 293 294

    PostTaskToTaskRunnerThread(base::BindOnce(
        &NotificationPlatformBridgeLinuxImpl::DisplayOnTaskRunner, this,
295
        notification_type, profile_id, is_incognito,
296
        base::Passed(&notification_copy)));
297 298
  }

299 300 301 302 303 304 305
  void Close(const std::string& profile_id,
             const std::string& notification_id) override {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    PostTaskToTaskRunnerThread(
        base::BindOnce(&NotificationPlatformBridgeLinuxImpl::CloseOnTaskRunner,
                       this, profile_id, notification_id));
  }
306

307 308 309 310 311 312 313 314 315
  void GetDisplayed(
      const std::string& profile_id,
      bool incognito,
      const GetDisplayedNotificationsCallback& callback) const override {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    PostTaskToTaskRunnerThread(base::BindOnce(
        &NotificationPlatformBridgeLinuxImpl::GetDisplayedOnTaskRunner, this,
        profile_id, incognito, callback));
  }
316

317 318 319 320
  void SetReadyCallback(NotificationBridgeReadyCallback callback) override {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    if (connected_.has_value()) {
      std::move(callback).Run(connected_.value());
321
    } else {
322
      on_connected_callbacks_.push_back(std::move(callback));
323 324
    }
  }
325

326 327 328 329 330 331
  void CleanUp() {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    PostTaskToTaskRunnerThread(base::BindOnce(
        &NotificationPlatformBridgeLinuxImpl::CleanUpOnTaskRunner, this));
  }

332 333
 private:
  friend class base::RefCountedThreadSafe<NotificationPlatformBridgeLinuxImpl>;
334

335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
  struct NotificationData {
    NotificationData(NotificationCommon::Type notification_type,
                     const std::string& notification_id,
                     const std::string& profile_id,
                     bool is_incognito,
                     const GURL& origin_url)
        : notification_type(notification_type),
          notification_id(notification_id),
          profile_id(profile_id),
          is_incognito(is_incognito),
          origin_url(origin_url) {}

    // The ID used by the notification server.  Will be 0 until the
    // first "Notify" message completes.
    uint32_t dbus_id = 0;

    NotificationCommon::Type notification_type;
    const std::string notification_id;
    const std::string profile_id;
    const bool is_incognito;

    // A copy of the origin_url from the underlying
    // message_center::Notification.  Used to pass back to
358
    // NotificationDisplayService.
359 360 361 362 363 364 365 366 367 368 369 370 371
    const GURL origin_url;

    // Used to keep track of the IDs of the buttons currently displayed
    // on this notification.  The valid range of action IDs is
    // [action_start, action_end).
    size_t action_start = 0;
    size_t action_end = 0;

    // Temporary resource files associated with the notification that
    // should be cleaned up when the notification is closed or on
    // shutdown.
    std::vector<std::unique_ptr<ResourceFile>> resource_files;
  };
372

373
  ~NotificationPlatformBridgeLinuxImpl() override {
374
    DCHECK(clean_up_on_task_runner_called_);
375 376
  }

377 378 379 380 381 382 383 384 385
  void Observe(int type,
               const content::NotificationSource& source,
               const content::NotificationDetails& details) override {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    DCHECK_EQ(chrome::NOTIFICATION_APP_TERMINATING, type);
    // The browser process is about to exit.  Post the CleanUp() task
    // while we still can.
    CleanUp();
  }
386

387 388 389 390 391
  void SetBodyImagesSupported(bool body_images_supported) {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    body_images_supported_ = body_images_supported;
  }

392
  void PostTaskToUiThread(base::OnceClosure closure) const {
393
    DCHECK(task_runner_->RunsTasksInCurrentSequence());
394 395 396
    bool success = content::BrowserThread::PostTask(
        content::BrowserThread::UI, FROM_HERE, std::move(closure));
    DCHECK(success);
397 398
  }

399 400 401 402 403 404
  void PostTaskToTaskRunnerThread(base::OnceClosure closure) const {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    DCHECK(task_runner_);
    bool success = task_runner_->PostTask(FROM_HERE, std::move(closure));
    DCHECK(success);
  }
405

406
  // Sets up the D-Bus connection.
407
  void InitOnTaskRunner() {
408
    DCHECK(task_runner_->RunsTasksInCurrentSequence());
409 410 411 412 413 414
    // |bus_| may be non-null in unit testing where a fake bus is used.
    if (!bus_) {
      dbus::Bus::Options bus_options;
      bus_options.bus_type = dbus::Bus::SESSION;
      bus_options.connection_type = dbus::Bus::PRIVATE;
      bus_options.dbus_task_runner = task_runner_;
415
      bus_ = base::MakeRefCounted<dbus::Bus>(bus_options);
416
    }
417 418 419 420 421

    notification_proxy_ =
        bus_->GetObjectProxy(kFreedesktopNotificationsName,
                             dbus::ObjectPath(kFreedesktopNotificationsPath));
    if (!notification_proxy_) {
422 423 424
      OnConnectionInitializationFinishedOnTaskRunner(
          ConnectionInitializationStatusCode::
              NATIVE_NOTIFICATIONS_NOT_SUPPORTED);
425 426 427
      return;
    }

428
    dbus::MethodCall get_capabilities_call(kFreedesktopNotificationsName,
429
                                           kMethodGetCapabilities);
430 431 432 433 434 435 436 437 438 439 440
    std::unique_ptr<dbus::Response> capabilities_response =
        notification_proxy_->CallMethodAndBlock(
            &get_capabilities_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
    if (capabilities_response) {
      dbus::MessageReader reader(capabilities_response.get());
      std::vector<std::string> capabilities;
      reader.PopArrayOfStrings(&capabilities);
      for (const std::string& capability : capabilities)
        capabilities_.insert(capability);
    }
    RecordMetricsForCapabilities();
441 442 443 444 445 446
    if (!base::ContainsKey(capabilities_, kCapabilityBody) ||
        !base::ContainsKey(capabilities_, kCapabilityActions)) {
      OnConnectionInitializationFinishedOnTaskRunner(
          ConnectionInitializationStatusCode::MISSING_REQUIRED_CAPABILITIES);
      return;
    }
447 448 449
    PostTaskToUiThread(base::BindOnce(
        &NotificationPlatformBridgeLinuxImpl::SetBodyImagesSupported, this,
        base::ContainsKey(capabilities_, kCapabilityBodyImages)));
450

451 452 453
    connected_signals_barrier_ = base::BarrierClosure(
        2, base::Bind(&NotificationPlatformBridgeLinuxImpl::
                          OnConnectionInitializationFinishedOnTaskRunner,
454
                      this, ConnectionInitializationStatusCode::SUCCESS));
455
    notification_proxy_->ConnectToSignal(
456
        kFreedesktopNotificationsName, kSignalActionInvoked,
457 458 459 460
        base::Bind(&NotificationPlatformBridgeLinuxImpl::OnActionInvoked, this),
        base::Bind(&NotificationPlatformBridgeLinuxImpl::OnSignalConnected,
                   this));
    notification_proxy_->ConnectToSignal(
461
        kFreedesktopNotificationsName, kSignalNotificationClosed,
462 463 464 465
        base::Bind(&NotificationPlatformBridgeLinuxImpl::OnNotificationClosed,
                   this),
        base::Bind(&NotificationPlatformBridgeLinuxImpl::OnSignalConnected,
                   this));
466
  }
467 468

  void CleanUpOnTaskRunner() {
469
    DCHECK(task_runner_->RunsTasksInCurrentSequence());
470 471
    if (bus_)
      bus_->ShutdownAndBlock();
472 473
    bus_ = nullptr;
    notification_proxy_ = nullptr;
474 475 476
    product_logo_png_bytes_ = nullptr;
    product_logo_file_.reset();
    product_logo_file_watcher_.reset();
477
    notifications_.clear();
478
    clean_up_on_task_runner_called_ = true;
479 480
  }

481
  // Makes the "Notify" call to D-Bus.
482 483 484 485 486
  void DisplayOnTaskRunner(
      NotificationCommon::Type notification_type,
      const std::string& profile_id,
      bool is_incognito,
      std::unique_ptr<message_center::Notification> notification) {
487
    DCHECK(task_runner_->RunsTasksInCurrentSequence());
488
    NotificationData* data =
489
        FindNotificationData(notification->id(), profile_id, is_incognito);
490 491 492 493 494 495
    if (data) {
      // Update an existing notification.
      data->notification_type = notification_type;
      data->resource_files.clear();
    } else {
      // Send the notification for the first time.
496 497 498
      data = new NotificationData(notification_type, notification->id(),
                                  profile_id, is_incognito,
                                  notification->origin_url());
499 500 501
      notifications_.emplace(data, base::WrapUnique(data));
    }

502
    dbus::MethodCall method_call(kFreedesktopNotificationsName, kMethodNotify);
503
    dbus::MessageWriter writer(&method_call);
504

505 506
    // app_name
    writer.AppendString(l10n_util::GetStringUTF8(IDS_PRODUCT_NAME));
507

508
    writer.AppendUint32(data->dbus_id);
509

510 511 512 513 514 515 516
    // app_icon
    if (!product_logo_file_) {
      RewriteProductLogoFile();
    }
    writer.AppendString(
        product_logo_file_ ? "file://" + product_logo_file_->file_path().value()
                           : "");
517

518 519
    writer.AppendString(
        base::UTF16ToUTF8(CreateNotificationTitle(*notification)));
520

521
    std::ostringstream body;
522
    if (base::ContainsKey(capabilities_, kCapabilityBody)) {
523 524
      const bool body_markup =
          base::ContainsKey(capabilities_, kCapabilityBodyMarkup);
525 526

      if (notification->UseOriginAsContextMessage()) {
527
        std::string url_display_text =
528 529
            base::UTF16ToUTF8(url_formatter::FormatUrlForSecurityDisplay(
                notification->origin_url(),
530
                url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS));
531 532 533 534 535 536 537 538 539 540
        if (url_display_text.size() > kMaxAllowedOriginLength) {
          std::string domain_and_registry =
              net::registry_controlled_domains::GetDomainAndRegistry(
                  notification->origin_url(),
                  net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
          // localhost, raw IPs etc. are not handled by GetDomainAndRegistry.
          if (!domain_and_registry.empty()) {
            url_display_text = domain_and_registry;
          }
        }
541
        EscapeUnsafeCharacters(&url_display_text);
542 543
        if (body_markup &&
            base::ContainsKey(capabilities_, kCapabilityBodyHyperlinks)) {
544
          body << "<a href=\""
545
               << net::EscapeForHTML(notification->origin_url().spec()) << "\">"
546
               << url_display_text << "</a>\n\n";
547
        } else {
548
          body << url_display_text << "\n\n";
549 550 551 552 553
        }
      } else if (!notification->context_message().empty()) {
        std::string context =
            base::UTF16ToUTF8(notification->context_message());
        if (body_markup)
554
          EscapeUnsafeCharacters(&context);
555
        body << context << "\n\n";
556
      }
557 558 559

      std::string message = base::UTF16ToUTF8(notification->message());
      if (body_markup)
560
        EscapeUnsafeCharacters(&message);
561 562
      if (!message.empty())
        body << message << "\n";
563

564 565 566 567 568 569 570
      if (notification->type() == message_center::NOTIFICATION_TYPE_MULTIPLE) {
        for (const auto& item : notification->items()) {
          const std::string title = base::UTF16ToUTF8(item.title);
          const std::string message = base::UTF16ToUTF8(item.message);
          // TODO(peter): Figure out the right way to internationalize
          // this for RTL languages.
          if (body_markup)
571
            body << "<b>" << title << "</b> " << message << "\n";
572
          else
573
            body << title << " - " << message << "\n";
574 575 576 577 578 579 580 581 582
        }
      } else if (notification->type() ==
                     message_center::NOTIFICATION_TYPE_IMAGE &&
                 base::ContainsKey(capabilities_, kCapabilityBodyImages)) {
        std::unique_ptr<ResourceFile> image_file = WriteDataToTmpFile(
            ResizeImageToFdoMaxSize(notification->image()).As1xPNGBytes());
        if (image_file) {
          body << "<img src=\""
               << net::EscapePath(image_file->file_path().value())
583
               << "\" alt=\"\"/>\n";
584
          data->resource_files.push_back(std::move(image_file));
585 586
        }
      }
587
    }
588 589 590
    std::string body_str = body.str();
    base::TrimString(body_str, "\n", &body_str);
    writer.AppendString(body_str);
591 592 593 594

    // Even-indexed elements in this vector are action IDs passed back to
    // us in OnActionInvoked().  Odd-indexed ones contain the button text.
    std::vector<std::string> actions;
595
    if (base::ContainsKey(capabilities_, kCapabilityActions)) {
596 597 598 599 600 601 602 603 604 605
      data->action_start = data->action_end;
      for (const auto& button_info : notification->buttons()) {
        // FDO notification buttons can contain either an icon or a label,
        // but not both, and the type of all buttons must be the same (all
        // labels or all icons), so always use labels.
        const std::string id = base::SizeTToString(data->action_end++);
        const std::string label = base::UTF16ToUTF8(button_info.title);
        actions.push_back(id);
        actions.push_back(label);
      }
606 607 608 609
      // Special case: the id "default" will not add a button, but
      // instead makes the entire notification clickable.
      actions.push_back(kDefaultButtonId);
      actions.push_back("Activate");
610 611 612 613 614 615
      // Always add a settings button for web notifications.
      if (notification_type != NotificationCommon::EXTENSION) {
        actions.push_back(kSettingsButtonId);
        actions.push_back(
            l10n_util::GetStringUTF8(IDS_NOTIFICATION_BUTTON_SETTINGS));
      }
616 617 618 619 620 621 622 623 624 625 626 627
    }
    writer.AppendArrayOfStrings(actions);

    dbus::MessageWriter hints_writer(nullptr);
    writer.OpenArray("{sv}", &hints_writer);
    dbus::MessageWriter urgency_writer(nullptr);
    hints_writer.OpenDictEntry(&urgency_writer);
    urgency_writer.AppendString("urgency");
    urgency_writer.AppendVariantOfUint32(
        NotificationPriorityToFdoUrgency(notification->priority()));
    hints_writer.CloseContainer(&urgency_writer);

628 629 630 631 632 633 634 635
    if (notification->silent()) {
      dbus::MessageWriter suppress_sound_writer(nullptr);
      hints_writer.OpenDictEntry(&suppress_sound_writer);
      suppress_sound_writer.AppendString("suppress-sound");
      suppress_sound_writer.AppendVariantOfBool(true);
      hints_writer.CloseContainer(&suppress_sound_writer);
    }

636 637 638 639 640 641 642 643 644 645 646 647 648
    std::unique_ptr<base::Environment> env = base::Environment::Create();
    base::FilePath desktop_file(
        shell_integration_linux::GetDesktopName(env.get()));
    const char kDesktopFileSuffix[] = ".desktop";
    DCHECK(base::EndsWith(desktop_file.value(), kDesktopFileSuffix,
                          base::CompareCase::SENSITIVE));
    desktop_file = desktop_file.RemoveFinalExtension();
    dbus::MessageWriter desktop_entry_writer(nullptr);
    hints_writer.OpenDictEntry(&desktop_entry_writer);
    desktop_entry_writer.AppendString("desktop-entry");
    desktop_entry_writer.AppendVariantOfString(desktop_file.value());
    hints_writer.CloseContainer(&desktop_entry_writer);

649
    std::unique_ptr<ResourceFile> icon_file =
650
        WriteDataToTmpFile(notification->icon().As1xPNGBytes());
651
    if (icon_file) {
652 653 654 655 656 657 658
      for (const std::string& hint_name : {"image_path", "image-path"}) {
        dbus::MessageWriter image_path_writer(nullptr);
        hints_writer.OpenDictEntry(&image_path_writer);
        image_path_writer.AppendString(hint_name);
        image_path_writer.AppendVariantOfString(icon_file->file_path().value());
        hints_writer.CloseContainer(&image_path_writer);
      }
659
      data->resource_files.push_back(std::move(icon_file));
660 661 662 663 664
    }

    writer.CloseContainer(&hints_writer);

    const int32_t kExpireTimeoutDefault = -1;
665
    const int32_t kExpireTimeoutNever = 0;
666 667 668 669 670 671
    writer.AppendInt32(
        notification->never_timeout()
            ? kExpireTimeoutNever
            : base::ContainsKey(capabilities_, kCapabilityPersistence)
                  ? kExpireTimeoutDefault
                  : kExpireTimeout);
672 673 674 675 676 677 678 679 680 681 682

    std::unique_ptr<dbus::Response> response =
        notification_proxy_->CallMethodAndBlock(
            &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
    if (response) {
      dbus::MessageReader reader(response.get());
      reader.PopUint32(&data->dbus_id);
    }
    if (!data->dbus_id) {
      // There was some sort of error with creating the notification.
      notifications_.erase(data);
683 684 685
    }
  }

686 687 688
  // Makes the "CloseNotification" call to D-Bus.
  void CloseOnTaskRunner(const std::string& profile_id,
                         const std::string& notification_id) {
689
    DCHECK(task_runner_->RunsTasksInCurrentSequence());
690 691 692 693 694 695
    std::vector<NotificationData*> to_erase;
    for (const auto& pair : notifications_) {
      NotificationData* data = pair.first;
      if (data->notification_id == notification_id &&
          data->profile_id == profile_id) {
        dbus::MethodCall method_call(kFreedesktopNotificationsName,
696
                                     kMethodCloseNotification);
697 698 699 700
        dbus::MessageWriter writer(&method_call);
        writer.AppendUint32(data->dbus_id);
        notification_proxy_->CallMethodAndBlock(
            &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
701
        to_erase.push_back(data);
702 703 704 705 706
      }
    }
    for (NotificationData* data : to_erase)
      notifications_.erase(data);
  }
707

708 709 710 711
  void GetDisplayedOnTaskRunner(
      const std::string& profile_id,
      bool incognito,
      const GetDisplayedNotificationsCallback& callback) const {
712
    DCHECK(task_runner_->RunsTasksInCurrentSequence());
713
    auto displayed = std::make_unique<std::set<std::string>>();
714 715 716 717
    for (const auto& pair : notifications_) {
      NotificationData* data = pair.first;
      if (data->profile_id == profile_id && data->is_incognito == incognito)
        displayed->insert(data->notification_id);
718 719
    }
    PostTaskToUiThread(base::BindOnce(callback, std::move(displayed), true));
720 721
  }

722 723 724
  NotificationData* FindNotificationData(const std::string& notification_id,
                                         const std::string& profile_id,
                                         bool is_incognito) {
725
    DCHECK(task_runner_->RunsTasksInCurrentSequence());
726 727 728 729 730 731 732 733
    for (const auto& pair : notifications_) {
      NotificationData* data = pair.first;
      if (data->notification_id == notification_id &&
          data->profile_id == profile_id &&
          data->is_incognito == is_incognito) {
        return data;
      }
    }
734

735
    return nullptr;
736 737
  }

738
  NotificationData* FindNotificationDataWithDBusId(uint32_t dbus_id) {
739
    DCHECK(task_runner_->RunsTasksInCurrentSequence());
740
    DCHECK(dbus_id);
741 742 743 744 745
    for (const auto& pair : notifications_) {
      NotificationData* data = pair.first;
      if (data->dbus_id == dbus_id)
        return data;
    }
746

747 748
    return nullptr;
  }
749

750 751
  void ForwardNotificationOperation(NotificationData* data,
                                    NotificationCommon::Operation operation,
752 753
                                    const base::Optional<int>& action_index,
                                    const base::Optional<bool>& by_user) {
754
    DCHECK(task_runner_->RunsTasksInCurrentSequence());
755 756
    PostTaskToUiThread(base::BindOnce(
        ForwardNotificationOperationOnUiThread, operation,
757
        data->notification_type, data->origin_url, data->notification_id,
758
        action_index, by_user, data->profile_id, data->is_incognito));
759 760 761
  }

  void OnActionInvoked(dbus::Signal* signal) {
762
    DCHECK(task_runner_->RunsTasksInCurrentSequence());
763 764
    dbus::MessageReader reader(signal);
    uint32_t dbus_id;
765
    if (!reader.PopUint32(&dbus_id) || !dbus_id)
766 767 768 769 770
      return;
    std::string action;
    if (!reader.PopString(&action))
      return;

771
    NotificationData* data = FindNotificationDataWithDBusId(dbus_id);
772 773 774 775
    if (!data)
      return;

    if (action == kDefaultButtonId) {
776 777 778
      ForwardNotificationOperation(data, NotificationCommon::CLICK,
                                   base::nullopt /* action_index */,
                                   base::nullopt /* by_user */);
779
    } else if (action == kSettingsButtonId) {
780 781 782
      ForwardNotificationOperation(data, NotificationCommon::SETTINGS,
                                   base::nullopt /* action_index */,
                                   base::nullopt /* by_user */);
783
    } else {
784 785 786 787 788 789 790
      size_t id;
      if (!base::StringToSizeT(action, &id))
        return;
      size_t n_buttons = data->action_end - data->action_start;
      size_t id_zero_based = id - data->action_start;
      if (id_zero_based >= n_buttons)
        return;
791
      ForwardNotificationOperation(data, NotificationCommon::CLICK,
792
                                   id_zero_based, base::nullopt /* by_user */);
793 794
    }
  }
795 796

  void OnNotificationClosed(dbus::Signal* signal) {
797
    DCHECK(task_runner_->RunsTasksInCurrentSequence());
798 799
    dbus::MessageReader reader(signal);
    uint32_t dbus_id;
800
    if (!reader.PopUint32(&dbus_id) || !dbus_id)
801 802
      return;

803
    NotificationData* data = FindNotificationDataWithDBusId(dbus_id);
804 805 806
    if (!data)
      return;

807 808 809 810
    // TODO(peter): Can we support |by_user| appropriately here?
    ForwardNotificationOperation(data, NotificationCommon::CLOSE,
                                 base::nullopt /* action_index */,
                                 true /* by_user */);
811 812 813 814 815 816 817 818 819 820 821 822 823 824 825
    notifications_.erase(data);
  }

  // Called once the connection has been set up (or not).  |success|
  // indicates the connection is ready to use.
  void OnConnectionInitializationFinishedOnUiThread(bool success) {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    connected_ = success;
    for (auto& callback : on_connected_callbacks_)
      std::move(callback).Run(success);
    on_connected_callbacks_.clear();
    if (!success)
      CleanUp();
  }

826 827
  void OnConnectionInitializationFinishedOnTaskRunner(
      ConnectionInitializationStatusCode status) {
828
    DCHECK(task_runner_->RunsTasksInCurrentSequence());
829 830 831 832 833 834 835 836
    UMA_HISTOGRAM_ENUMERATION(
        "Notifications.Linux.BridgeInitializationStatus",
        static_cast<int>(status),
        static_cast<int>(ConnectionInitializationStatusCode::NUM_ITEMS));
    PostTaskToUiThread(base::BindOnce(
        &NotificationPlatformBridgeLinuxImpl::
            OnConnectionInitializationFinishedOnUiThread,
        this, status == ConnectionInitializationStatusCode::SUCCESS));
837 838 839 840 841
  }

  void OnSignalConnected(const std::string& interface_name,
                         const std::string& signal_name,
                         bool success) {
842
    DCHECK(task_runner_->RunsTasksInCurrentSequence());
843
    if (!success) {
844 845
      OnConnectionInitializationFinishedOnTaskRunner(
          ConnectionInitializationStatusCode::COULD_NOT_CONNECT_TO_SIGNALS);
846 847 848 849 850
      return;
    }
    connected_signals_barrier_.Run();
  }

851 852 853 854 855 856 857 858 859
  void OnProductLogoFileChanged(const base::FilePath& path, bool error) {
    // |error| should always be false on Linux.
    DCHECK(!error);
    // This callback runs whenever the file is deleted or modified.
    // In either case, we want to rewrite the file.
    product_logo_file_.reset();
    product_logo_file_watcher_.reset();
  }

860 861 862
  void RecordMetricsForCapabilities() {
    // Histogram macros must be called with the same name for each
    // callsite, so we can't roll the below into a nice loop.
863 864 865
    UMA_HISTOGRAM_BOOLEAN(
        "Notifications.Freedesktop.Capabilities.ActionIcons",
        base::ContainsKey(capabilities_, kCapabilityActionIcons));
866
    UMA_HISTOGRAM_BOOLEAN("Notifications.Freedesktop.Capabilities.Actions",
867
                          base::ContainsKey(capabilities_, kCapabilityActions));
868
    UMA_HISTOGRAM_BOOLEAN("Notifications.Freedesktop.Capabilities.Body",
869
                          base::ContainsKey(capabilities_, kCapabilityBody));
870 871
    UMA_HISTOGRAM_BOOLEAN(
        "Notifications.Freedesktop.Capabilities.BodyHyperlinks",
872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887
        base::ContainsKey(capabilities_, kCapabilityBodyHyperlinks));
    UMA_HISTOGRAM_BOOLEAN(
        "Notifications.Freedesktop.Capabilities.BodyImages",
        base::ContainsKey(capabilities_, kCapabilityBodyImages));
    UMA_HISTOGRAM_BOOLEAN(
        "Notifications.Freedesktop.Capabilities.BodyMarkup",
        base::ContainsKey(capabilities_, kCapabilityBodyMarkup));
    UMA_HISTOGRAM_BOOLEAN(
        "Notifications.Freedesktop.Capabilities.IconMulti",
        base::ContainsKey(capabilities_, kCapabilityIconMulti));
    UMA_HISTOGRAM_BOOLEAN(
        "Notifications.Freedesktop.Capabilities.IconStatic",
        base::ContainsKey(capabilities_, kCapabilityIconStatic));
    UMA_HISTOGRAM_BOOLEAN(
        "Notifications.Freedesktop.Capabilities.Persistence",
        base::ContainsKey(capabilities_, kCapabilityPersistence));
888
    UMA_HISTOGRAM_BOOLEAN("Notifications.Freedesktop.Capabilities.Sound",
889
                          base::ContainsKey(capabilities_, kCapabilitySound));
890 891
  }

892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910
  void RewriteProductLogoFile() {
    product_logo_file_watcher_.reset();
    product_logo_file_ = WriteDataToTmpFile(product_logo_png_bytes_);
    if (!product_logo_file_)
      return;
    // Temporary files may periodically get cleaned up on Linux.
    // Watch for file deletion and rewrite the file in case we have a
    // long-running Chrome process.
    product_logo_file_watcher_ = std::make_unique<base::FilePathWatcher>();
    if (!product_logo_file_watcher_->Watch(
            product_logo_file_->file_path(), false,
            base::Bind(
                &NotificationPlatformBridgeLinuxImpl::OnProductLogoFileChanged,
                this))) {
      product_logo_file_.reset();
      product_logo_file_watcher_.reset();
    }
  }

911 912 913 914 915 916 917 918 919 920 921 922
  //////////////////////////////////////////////////////////////////////////////
  // Members used only on the UI thread.

  scoped_refptr<base::SequencedTaskRunner> task_runner_;

  content::NotificationRegistrar registrar_;

  // State necessary for OnConnectionInitializationFinished() and
  // SetReadyCallback().
  base::Optional<bool> connected_;
  std::vector<NotificationBridgeReadyCallback> on_connected_callbacks_;

923 924 925 926
  // Notification servers very rarely have the 'body-images'
  // capability, so try to avoid an image copy if possible.
  base::Optional<bool> body_images_supported_;

927 928 929 930 931 932 933
  //////////////////////////////////////////////////////////////////////////////
  // Members used only on the task runner thread.

  scoped_refptr<dbus::Bus> bus_;

  dbus::ObjectProxy* notification_proxy_ = nullptr;

934 935
  std::unordered_set<std::string> capabilities_;

936 937
  base::Closure connected_signals_barrier_;

938 939 940 941
  scoped_refptr<base::RefCountedMemory> product_logo_png_bytes_;
  std::unique_ptr<ResourceFile> product_logo_file_;
  std::unique_ptr<base::FilePathWatcher> product_logo_file_watcher_;

942 943 944 945 946 947 948 949
  // A std::set<std::unique_ptr<T>> doesn't work well because
  // eg. std::set::erase(T) would require a std::unique_ptr<T>
  // argument, so the data would get double-destructed.
  template <typename T>
  using UnorderedUniqueSet = std::unordered_map<T*, std::unique_ptr<T>>;

  UnorderedUniqueSet<NotificationData> notifications_;

950 951
  bool clean_up_on_task_runner_called_ = false;

952 953 954 955
  DISALLOW_COPY_AND_ASSIGN(NotificationPlatformBridgeLinuxImpl);
};

NotificationPlatformBridgeLinux::NotificationPlatformBridgeLinux()
956 957 958 959 960 961 962
    : NotificationPlatformBridgeLinux(nullptr) {}

NotificationPlatformBridgeLinux::NotificationPlatformBridgeLinux(
    scoped_refptr<dbus::Bus> bus)
    : impl_(new NotificationPlatformBridgeLinuxImpl(bus)) {
  impl_->Init();
}
963 964 965 966 967 968 969

NotificationPlatformBridgeLinux::~NotificationPlatformBridgeLinux() = default;

void NotificationPlatformBridgeLinux::Display(
    NotificationCommon::Type notification_type,
    const std::string& profile_id,
    bool is_incognito,
970
    const message_center::Notification& notification,
971
    std::unique_ptr<NotificationCommon::Metadata> metadata) {
972 973
  impl_->Display(notification_type, profile_id, is_incognito, notification,
                 std::move(metadata));
974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991
}

void NotificationPlatformBridgeLinux::Close(
    const std::string& profile_id,
    const std::string& notification_id) {
  impl_->Close(profile_id, notification_id);
}

void NotificationPlatformBridgeLinux::GetDisplayed(
    const std::string& profile_id,
    bool incognito,
    const GetDisplayedNotificationsCallback& callback) const {
  impl_->GetDisplayed(profile_id, incognito, callback);
}

void NotificationPlatformBridgeLinux::SetReadyCallback(
    NotificationBridgeReadyCallback callback) {
  impl_->SetReadyCallback(std::move(callback));
992
}
993 994 995 996

void NotificationPlatformBridgeLinux::CleanUp() {
  impl_->CleanUp();
}