Index: chrome/browser/notifications/notification_platform_bridge_linux.cc |
diff --git a/chrome/browser/notifications/notification_platform_bridge_linux.cc b/chrome/browser/notifications/notification_platform_bridge_linux.cc |
index 68cfe87579c73cfbdc9e90b2019d4573550fd005..cf71db8869d0a8a1c7c5f89085a7fd8338243d6c 100644 |
--- a/chrome/browser/notifications/notification_platform_bridge_linux.cc |
+++ b/chrome/browser/notifications/notification_platform_bridge_linux.cc |
@@ -6,9 +6,8 @@ |
#include <algorithm> |
+#include "base/barrier_closure.h" |
#include "base/files/file_util.h" |
-#include "base/memory/ptr_util.h" |
-#include "base/stl_util.h" |
#include "base/strings/nullable_string16.h" |
#include "base/strings/string_number_conversions.h" |
#include "base/strings/string_util.h" |
@@ -21,18 +20,24 @@ |
#include "chrome/browser/notifications/notification_display_service_factory.h" |
#include "chrome/browser/profiles/profile_manager.h" |
#include "chrome/browser/shell_integration_linux.h" |
+#include "content/public/browser/browser_thread.h" |
#include "content/public/browser/notification_service.h" |
+#include "dbus/bus.h" |
+#include "dbus/message.h" |
+#include "dbus/object_proxy.h" |
+#include "ui/gfx/image/image_skia.h" |
namespace { |
const char kFreedesktopNotificationsName[] = "org.freedesktop.Notifications"; |
const char kFreedesktopNotificationsPath[] = "/org/freedesktop/Notifications"; |
-void AddActionToNotification(GVariantBuilder* actions_builder, |
- const char* action_id, |
- const char* button_label) { |
- g_variant_builder_add(actions_builder, "s", action_id); |
- g_variant_builder_add(actions_builder, "s", button_label); |
+const char kDefaultButtonId[] = "default"; |
+const char kSettingsButtonId[] = "settings"; |
+ |
+gfx::Image DeepCopyImage(const gfx::Image& image) { |
+ std::unique_ptr<gfx::ImageSkia> image_skia(image.CopyImageSkia()); |
+ return gfx::Image(*image_skia); |
} |
int NotificationPriorityToFdoUrgency(int priority) { |
@@ -50,29 +55,11 @@ int NotificationPriorityToFdoUrgency(int priority) { |
return CRITICAL; |
default: |
NOTREACHED(); |
- // fallthrough |
case message_center::DEFAULT_PRIORITY: |
return NORMAL; |
} |
} |
-// Callback used by GLib when the "Notify" message completes for the |
-// first time. |
-void NotifyCompleteReceiver(GObject* source_object, |
- GAsyncResult* result, |
- gpointer user_data) { |
- GDBusProxy* proxy = G_DBUS_PROXY(source_object); |
- GVariant* value = g_dbus_proxy_call_finish(proxy, result, nullptr); |
- if (!value) { |
- // The message might have been cancelled, in which case |
- // |user_data| points to a destroyed NotificationData. |
- return; |
- } |
- auto* platform_bridge_linux = static_cast<NotificationPlatformBridgeLinux*>( |
- g_browser_process->notification_platform_bridge()); |
- platform_bridge_linux->NotifyCompleteInternal(user_data, value); |
-} |
- |
// Runs once the profile has been loaded in order to perform a given |
// |operation| on a notification. |
void ProfileLoadedCallback(NotificationCommon::Operation operation, |
@@ -93,6 +80,23 @@ void ProfileLoadedCallback(NotificationCommon::Operation operation, |
notification_id, action_index, reply); |
} |
+void ForwardNotificationOperationOnUiThread( |
+ NotificationCommon::Operation operation, |
+ NotificationCommon::Type notification_type, |
+ const std::string& origin, |
+ const std::string& notification_id, |
+ int action_index, |
+ const std::string& profile_id, |
+ bool is_incognito) { |
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
+ ProfileManager* profile_manager = g_browser_process->profile_manager(); |
+ |
+ profile_manager->LoadProfile( |
+ profile_id, is_incognito, |
+ base::Bind(&ProfileLoadedCallback, operation, notification_type, origin, |
+ notification_id, action_index, base::NullableString16())); |
+} |
+ |
// Writes |data| to a new temporary file and returns its path. |
// Returns base::FilePath() on failure. |
base::FilePath WriteDataToTmpFile( |
@@ -111,122 +115,503 @@ base::FilePath WriteDataToTmpFile( |
return file_path; |
} |
-void DeleteNotificationResourceFile(const base::FilePath& file_path) { |
- if (file_path.empty()) |
- return; |
- base::PostTaskWithTraits( |
- FROM_HERE, |
- base::TaskTraits() |
- .MayBlock() |
- .WithPriority(base::TaskPriority::BACKGROUND) |
- .WithShutdownBehavior(base::TaskShutdownBehavior::BLOCK_SHUTDOWN), |
- base::BindOnce(base::IgnoreResult(base::DeleteFile), file_path, false)); |
-} |
- |
} // namespace |
// static |
NotificationPlatformBridge* NotificationPlatformBridge::Create() { |
- GDBusProxy* notification_proxy = g_dbus_proxy_new_for_bus_sync( |
- G_BUS_TYPE_SESSION, |
- static_cast<GDBusProxyFlags>(G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | |
- G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START), |
- nullptr, kFreedesktopNotificationsName, kFreedesktopNotificationsPath, |
- kFreedesktopNotificationsName, nullptr, nullptr); |
- if (!notification_proxy) |
- return nullptr; |
- return new NotificationPlatformBridgeLinux(notification_proxy); |
+ return new NotificationPlatformBridgeLinux(); |
} |
-struct NotificationPlatformBridgeLinux::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), |
- weak_factory(this) {} |
- |
- ~NotificationData() { |
- if (cancellable) |
- g_cancellable_cancel(cancellable); |
- ResetResourceFiles(); |
- } |
- |
- void ResetResourceFiles() { |
- for (const base::FilePath& file : resource_files) |
- DeleteNotificationResourceFile(file); |
- resource_files.clear(); |
- } |
- |
- // The ID used by the notification server. Will be 0 until the |
- // first "Notify" message completes. |
- uint32_t dbus_id = 0; |
- |
- // Same parameters used by NotificationPlatformBridge::Display(). |
- 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 |
- // NativeNotificationDisplayService. |
- 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<base::FilePath> resource_files; |
- |
- // Used to cancel the initial "Notify" message so we don't call |
- // NotificationPlatformBridgeLinux::NotifyCompleteInternal() with a |
- // destroyed Notification. |
- ScopedGObject<GCancellable> cancellable; |
- |
- // If not null, the data to update the notification with once |
- // |dbus_id| becomes available. |
- std::unique_ptr<Notification> update_data; |
- NotificationCommon::Type update_notification_type = |
- NotificationCommon::TYPE_MAX; |
- |
- // If true, indicates the notification should be closed once |
- // |dbus_id| becomes available. |
- bool should_close = false; |
- |
- base::WeakPtrFactory<NotificationData> weak_factory; |
-}; |
+class NotificationPlatformBridgeLinuxImpl |
+ : public NotificationPlatformBridge, |
+ public content::NotificationObserver, |
+ public base::RefCountedThreadSafe<NotificationPlatformBridgeLinuxImpl> { |
+ public: |
+ NotificationPlatformBridgeLinuxImpl() |
+ : task_runner_(base::CreateSingleThreadTaskRunnerWithTraits( |
gab
2017/05/25 01:09:36
Surveying existing use cases of base::CreateSingle
|
+ base::TaskTraits().MayBlock().WithPriority( |
+ base::TaskPriority::USER_BLOCKING))) { |
+ registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING, |
+ content::NotificationService::AllSources()); |
+ PostTaskToTaskRunnerThread( |
+ base::BindOnce(&NotificationPlatformBridgeLinuxImpl::Init, this)); |
+ } |
+ |
+ void Display(NotificationCommon::Type notification_type, |
+ const std::string& notification_id, |
+ const std::string& profile_id, |
+ bool is_incognito, |
+ const Notification& notification) override { |
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
+ // Notifications contain gfx::Image's which have reference counts |
+ // that are not thread safe. Because of this, we duplicate the |
+ // notification and its images. Wrap the notification in a |
+ // unique_ptr to transfer ownership of the notification (and the |
+ // non-thread-safe reference counts) to the task runner thread. |
+ auto notification_copy = base::MakeUnique<Notification>(notification); |
+ notification_copy->set_icon(DeepCopyImage(notification_copy->icon())); |
+ notification_copy->set_image(gfx::Image()); |
+ notification_copy->set_small_image(gfx::Image()); |
+ for (size_t i = 0; i < notification_copy->buttons().size(); i++) |
+ notification_copy->SetButtonIcon(i, gfx::Image()); |
+ |
+ PostTaskToTaskRunnerThread(base::BindOnce( |
+ &NotificationPlatformBridgeLinuxImpl::DisplayOnTaskRunner, this, |
+ notification_type, notification_id, profile_id, is_incognito, |
+ base::Passed(¬ification_copy))); |
+ } |
+ |
+ 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)); |
+ } |
+ |
+ 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)); |
+ } |
+ |
+ void SetReadyCallback(NotificationBridgeReadyCallback callback) override { |
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
+ if (connected_.has_value()) { |
+ std::move(callback).Run(connected_.value()); |
+ } else { |
+ on_connected_callbacks_.push_back(std::move(callback)); |
+ } |
+ } |
+ |
+ private: |
+ friend class base::RefCountedThreadSafe<NotificationPlatformBridgeLinuxImpl>; |
+ |
+ struct ResourceFile { |
+ explicit ResourceFile(const base::FilePath& file_path) |
+ : file_path(file_path) {} |
+ ~ResourceFile() { base::DeleteFile(file_path, false); } |
+ const base::FilePath file_path; |
+ }; |
+ |
+ 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; |
+ |
+ // Same parameters used by NotificationPlatformBridge::Display(). |
+ 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 |
+ // NativeNotificationDisplayService. |
+ 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; |
+ }; |
+ |
+ ~NotificationPlatformBridgeLinuxImpl() override { |
+ DCHECK(!bus_); |
+ DCHECK(notifications_.empty()); |
+ } |
+ |
+ 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(); |
+ } |
+ |
+ void PostTaskToUiThread(base::OnceClosure closure) const { |
+ DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
+ bool success = content::BrowserThread::PostTask( |
+ content::BrowserThread::UI, FROM_HERE, std::move(closure)); |
+ DCHECK(success); |
+ } |
+ |
+ 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); |
+ } |
+ |
+ // Sets up the D-Bus connection. |
+ void Init() { |
+ DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
+ 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_; |
gab
2017/05/25 01:09:36
From the documentation on bus.h this doesn't seem
|
+ bus_ = make_scoped_refptr(new dbus::Bus(bus_options)); |
+ |
+ notification_proxy_ = |
+ bus_->GetObjectProxy(kFreedesktopNotificationsName, |
+ dbus::ObjectPath(kFreedesktopNotificationsPath)); |
+ if (!notification_proxy_) { |
+ OnConnectionInitializationFinishedOnTaskRunner(false); |
+ return; |
+ } |
+ |
+ connected_signals_barrier_ = base::BarrierClosure( |
+ 2, base::Bind(&NotificationPlatformBridgeLinuxImpl:: |
+ OnConnectionInitializationFinishedOnTaskRunner, |
+ this, true)); |
+ notification_proxy_->ConnectToSignal( |
+ kFreedesktopNotificationsName, "ActionInvoked", |
+ base::Bind(&NotificationPlatformBridgeLinuxImpl::OnActionInvoked, this), |
+ base::Bind(&NotificationPlatformBridgeLinuxImpl::OnSignalConnected, |
+ this)); |
+ notification_proxy_->ConnectToSignal( |
+ kFreedesktopNotificationsName, "NotificationClosed", |
+ base::Bind(&NotificationPlatformBridgeLinuxImpl::OnNotificationClosed, |
+ this), |
+ base::Bind(&NotificationPlatformBridgeLinuxImpl::OnSignalConnected, |
+ this)); |
+ } |
+ |
+ void CleanUp() { |
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
+ if (cleanup_posted_) |
+ return; |
+ PostTaskToTaskRunnerThread(base::BindOnce( |
+ &NotificationPlatformBridgeLinuxImpl::CleanUpOnTaskRunner, this)); |
+ cleanup_posted_ = true; |
+ } |
+ |
+ void CleanUpOnTaskRunner() { |
+ DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
+ bus_->ShutdownAndBlock(); |
+ bus_ = nullptr; |
+ notification_proxy_ = nullptr; |
+ notifications_.clear(); |
+ } |
+ |
+ // Makes the "Notify" call to D-Bus. |
+ void DisplayOnTaskRunner(NotificationCommon::Type notification_type, |
+ const std::string& notification_id, |
+ const std::string& profile_id, |
+ bool is_incognito, |
+ std::unique_ptr<Notification> notification) { |
+ DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
+ NotificationData* data = |
+ FindNotificationData(notification_id, profile_id, is_incognito); |
+ if (data) { |
+ // Update an existing notification. |
+ data->notification_type = notification_type; |
+ data->resource_files.clear(); |
+ } else { |
+ // Send the notification for the first time. |
+ data = |
+ new NotificationData(notification_type, notification_id, profile_id, |
+ is_incognito, notification->origin_url()); |
+ notifications_.emplace(data, base::WrapUnique(data)); |
+ } |
+ |
+ dbus::MethodCall method_call(kFreedesktopNotificationsName, "Notify"); |
+ dbus::MessageWriter writer(&method_call); |
+ |
+ // app_name passed implicitly via desktop-entry. |
+ writer.AppendString(""); |
+ |
+ writer.AppendUint32(data->dbus_id); |
+ |
+ // app_icon passed implicitly via desktop-entry. |
+ writer.AppendString(""); |
+ |
+ writer.AppendString(base::UTF16ToUTF8(notification->title())); |
+ |
+ writer.AppendString(base::UTF16ToUTF8(notification->message())); |
+ |
+ // 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; |
+ 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); |
+ } |
+ if (notification->clickable()) { |
+ // Special case: the pair ("default", "") will not add a button, |
+ // but instead makes the entire notification clickable. |
+ actions.push_back(kDefaultButtonId); |
+ actions.push_back(""); |
+ } |
+ // Always add a settings button. |
+ actions.push_back(kSettingsButtonId); |
+ actions.push_back("Settings"); |
+ 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); |
+ |
+ 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); |
-struct NotificationPlatformBridgeLinux::ResourceFiles { |
- explicit ResourceFiles(const base::FilePath& icon_file) |
- : icon_file(icon_file) {} |
- ~ResourceFiles() { DeleteNotificationResourceFile(icon_file); } |
- base::FilePath icon_file; |
+ base::FilePath icon_file = |
+ WriteDataToTmpFile(notification->icon().As1xPNGBytes()); |
+ if (!icon_file.empty()) { |
+ dbus::MessageWriter image_path_writer(nullptr); |
+ hints_writer.OpenDictEntry(&image_path_writer); |
+ image_path_writer.AppendString("image-path"); |
+ image_path_writer.AppendVariantOfString(icon_file.value()); |
+ hints_writer.CloseContainer(&image_path_writer); |
+ data->resource_files.push_back(base::MakeUnique<ResourceFile>(icon_file)); |
+ } |
+ |
+ writer.CloseContainer(&hints_writer); |
+ |
+ const int32_t kExpireTimeoutDefault = -1; |
+ writer.AppendInt32(kExpireTimeoutDefault); |
+ |
+ 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); |
+ } |
+ } |
+ |
+ // Makes the "CloseNotification" call to D-Bus. |
+ void CloseOnTaskRunner(const std::string& profile_id, |
+ const std::string& notification_id) { |
+ DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
+ 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, |
+ "CloseNotification"); |
+ dbus::MessageWriter writer(&method_call); |
+ writer.AppendUint32(data->dbus_id); |
+ notification_proxy_->CallMethodAndBlock( |
+ &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT); |
+ } |
+ } |
+ for (NotificationData* data : to_erase) |
+ notifications_.erase(data); |
+ } |
+ |
+ void GetDisplayedOnTaskRunner( |
+ const std::string& profile_id, |
+ bool incognito, |
+ const GetDisplayedNotificationsCallback& callback) const { |
+ DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
+ // TODO(thomasanderson): Implement. |
+ PostTaskToUiThread(base::BindOnce( |
+ callback, base::Passed(base::MakeUnique<std::set<std::string>>()), |
+ false)); |
+ } |
+ |
+ NotificationData* FindNotificationData(const std::string& notification_id, |
+ const std::string& profile_id, |
+ bool is_incognito) { |
+ DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
+ 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; |
+ } |
+ } |
+ |
+ return nullptr; |
+ } |
+ |
+ NotificationData* FindNotificationData(uint32_t dbus_id) { |
+ DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
+ for (const auto& pair : notifications_) { |
+ NotificationData* data = pair.first; |
+ if (data->dbus_id == dbus_id) |
+ return data; |
+ } |
+ |
+ return nullptr; |
+ } |
+ |
+ void ForwardNotificationOperation(NotificationData* data, |
+ NotificationCommon::Operation operation, |
+ int action_index) { |
+ DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
+ PostTaskToUiThread(base::BindOnce( |
+ ForwardNotificationOperationOnUiThread, operation, |
+ data->notification_type, data->origin_url.spec(), data->notification_id, |
+ action_index, data->profile_id, data->is_incognito)); |
+ } |
+ |
+ void OnActionInvoked(dbus::Signal* signal) { |
+ DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
+ dbus::MessageReader reader(signal); |
+ uint32_t dbus_id; |
+ if (!reader.PopUint32(&dbus_id)) |
+ return; |
+ std::string action; |
+ if (!reader.PopString(&action)) |
+ return; |
+ |
+ NotificationData* data = FindNotificationData(dbus_id); |
+ if (!data) |
+ return; |
+ |
+ if (action == kDefaultButtonId) { |
+ ForwardNotificationOperation(data, NotificationCommon::CLICK, -1); |
+ } else if (action == kSettingsButtonId) { |
+ ForwardNotificationOperation(data, NotificationCommon::SETTINGS, -1); |
+ } else { |
+ 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; |
+ ForwardNotificationOperation(data, NotificationCommon::CLICK, |
+ id_zero_based); |
+ } |
+ } |
+ |
+ void OnNotificationClosed(dbus::Signal* signal) { |
+ DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
+ dbus::MessageReader reader(signal); |
+ uint32_t dbus_id; |
+ if (!reader.PopUint32(&dbus_id)) |
+ return; |
+ |
+ NotificationData* data = FindNotificationData(dbus_id); |
+ if (!data) |
+ return; |
+ |
+ ForwardNotificationOperation(data, NotificationCommon::CLOSE, -1); |
+ 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(); |
+ } |
+ |
+ void OnConnectionInitializationFinishedOnTaskRunner(bool success) { |
+ DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
+ PostTaskToUiThread( |
+ base::BindOnce(&NotificationPlatformBridgeLinuxImpl:: |
+ OnConnectionInitializationFinishedOnUiThread, |
+ this, success)); |
+ } |
+ |
+ void OnSignalConnected(const std::string& interface_name, |
+ const std::string& signal_name, |
+ bool success) { |
+ DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
+ if (!success) { |
+ OnConnectionInitializationFinishedOnTaskRunner(false); |
+ return; |
+ } |
+ connected_signals_barrier_.Run(); |
+ } |
+ |
+ ////////////////////////////////////////////////////////////////////////////// |
+ // 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_; |
+ |
+ bool cleanup_posted_ = false; |
+ |
+ ////////////////////////////////////////////////////////////////////////////// |
+ // Members used only on the task runner thread. |
+ |
+ scoped_refptr<dbus::Bus> bus_; |
+ |
+ dbus::ObjectProxy* notification_proxy_ = nullptr; |
+ |
+ base::Closure connected_signals_barrier_; |
+ |
+ // 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_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(NotificationPlatformBridgeLinuxImpl); |
}; |
-NotificationPlatformBridgeLinux::NotificationPlatformBridgeLinux( |
- GDBusProxy* notification_proxy) |
- : notification_proxy_(notification_proxy), weak_factory_(this) { |
- proxy_signal_handler_ = g_signal_connect( |
- notification_proxy_, "g-signal", G_CALLBACK(GSignalReceiverThunk), this); |
- registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING, |
- content::NotificationService::AllSources()); |
-} |
+NotificationPlatformBridgeLinux::NotificationPlatformBridgeLinux() |
+ : impl_(new NotificationPlatformBridgeLinuxImpl()) {} |
-NotificationPlatformBridgeLinux::~NotificationPlatformBridgeLinux() { |
- if (proxy_signal_handler_) |
- g_signal_handler_disconnect(notification_proxy_, proxy_signal_handler_); |
-} |
+NotificationPlatformBridgeLinux::~NotificationPlatformBridgeLinux() = default; |
void NotificationPlatformBridgeLinux::Display( |
NotificationCommon::Type notification_type, |
@@ -234,277 +619,24 @@ void NotificationPlatformBridgeLinux::Display( |
const std::string& profile_id, |
bool is_incognito, |
const Notification& notification) { |
- NotificationData* data = |
- FindNotificationData(notification_id, profile_id, is_incognito); |
- if (data) { |
- // Update an existing notification. |
- if (data->dbus_id) { |
- data->notification_type = notification_type; |
- Notify(notification, data, nullptr, nullptr); |
- } else { |
- data->update_notification_type = notification_type; |
- data->update_data = base::MakeUnique<Notification>(notification); |
- } |
- } else { |
- // Send the notification for the first time. |
- data = new NotificationData(notification_type, notification_id, profile_id, |
- is_incognito, notification.origin_url()); |
- data->cancellable.reset(g_cancellable_new()); |
- notifications_.emplace(data, base::WrapUnique(data)); |
- Notify(notification, data, NotifyCompleteReceiver, data); |
- } |
+ impl_->Display(notification_type, notification_id, profile_id, is_incognito, |
+ notification); |
} |
void NotificationPlatformBridgeLinux::Close( |
const std::string& profile_id, |
const std::string& notification_id) { |
- 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) { |
- if (data->dbus_id) { |
- CloseNow(data->dbus_id); |
- to_erase.push_back(data); |
- } else { |
- data->should_close = true; |
- } |
- } |
- } |
- for (NotificationData* data : to_erase) |
- notifications_.erase(data); |
+ impl_->Close(profile_id, notification_id); |
} |
void NotificationPlatformBridgeLinux::GetDisplayed( |
const std::string& profile_id, |
bool incognito, |
const GetDisplayedNotificationsCallback& callback) const { |
- // TODO(thomasanderson): implement. |
- callback.Run(base::MakeUnique<std::set<std::string>>(), false); |
+ impl_->GetDisplayed(profile_id, incognito, callback); |
} |
-void NotificationPlatformBridgeLinux::NotifyCompleteInternal(gpointer user_data, |
- GVariant* value) { |
- NotificationData* data = reinterpret_cast<NotificationData*>(user_data); |
- if (!base::ContainsKey(notifications_, data)) |
- return; |
- data->cancellable.reset(); |
- if (value && g_variant_is_of_type(value, G_VARIANT_TYPE("(u)"))) |
- g_variant_get(value, "(u)", &data->dbus_id); |
- |
- if (!data->dbus_id) { |
- // There was some sort of error with creating the notification. |
- notifications_.erase(data); |
- } else if (data->should_close) { |
- CloseNow(data->dbus_id); |
- notifications_.erase(data); |
- } else if (data->update_data) { |
- data->notification_type = data->update_notification_type; |
- Notify(*data->update_data, data, nullptr, nullptr); |
- data->update_data.reset(); |
- } |
-} |
- |
-void NotificationPlatformBridgeLinux::Observe( |
- int type, |
- const content::NotificationSource& source, |
- const content::NotificationDetails& details) { |
- DCHECK_EQ(chrome::NOTIFICATION_APP_TERMINATING, type); |
- // The browser process is about to exit. Clean up all notification |
- // resource files. |
- notifications_.clear(); |
-} |
- |
-void NotificationPlatformBridgeLinux::Notify(const Notification& notification, |
- NotificationData* data, |
- GAsyncReadyCallback callback, |
- gpointer user_data) { |
- const scoped_refptr<base::RefCountedMemory> icon_data = |
- notification.icon().As1xPNGBytes(); |
- if (!icon_data->size()) { |
- NotifyNow(notification, data->weak_factory.GetWeakPtr(), callback, |
- user_data, base::MakeUnique<ResourceFiles>(base::FilePath())); |
- } else { |
- base::PostTaskWithTraitsAndReplyWithResult( |
- FROM_HERE, |
- base::TaskTraits() |
- .MayBlock() |
- .WithPriority(base::TaskPriority::USER_BLOCKING) |
- .WithShutdownBehavior(base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN), |
- base::Bind( |
- [](scoped_refptr<base::RefCountedMemory> icon) { |
- return base::MakeUnique<ResourceFiles>(WriteDataToTmpFile(icon)); |
- }, |
- icon_data), |
- base::Bind(&NotificationPlatformBridgeLinux::NotifyNow, |
- weak_factory_.GetWeakPtr(), notification, |
- data->weak_factory.GetWeakPtr(), callback, user_data)); |
- } |
-} |
- |
-void NotificationPlatformBridgeLinux::NotifyNow( |
- const Notification& notification, |
- base::WeakPtr<NotificationData> data, |
- GAsyncReadyCallback callback, |
- gpointer user_data, |
- std::unique_ptr<ResourceFiles> resource_files) { |
- if (!data) |
- return; |
- |
- if (data->dbus_id) |
- DCHECK(!data->cancellable); |
- |
- data->ResetResourceFiles(); |
- |
- GVariantBuilder actions_builder; |
- // Even-indexed elements in this array are action IDs passed back to |
- // us in GSignalReceiver. Odd-indexed ones contain the button text. |
- g_variant_builder_init(&actions_builder, G_VARIANT_TYPE("as")); |
- 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. |
- std::string id = base::SizeTToString(data->action_end++); |
- const std::string label = base::UTF16ToUTF8(button_info.title); |
- AddActionToNotification(&actions_builder, id.c_str(), label.c_str()); |
- } |
- if (notification.clickable()) { |
- // Special case: the pair ("default", "") will not add a button, |
- // but instead makes the entire notification clickable. |
- AddActionToNotification(&actions_builder, "default", ""); |
- } |
- // Always add a settings button. |
- AddActionToNotification(&actions_builder, "settings", "Settings"); |
- |
- GVariantBuilder hints_builder; |
- g_variant_builder_init(&hints_builder, G_VARIANT_TYPE("a{sv}")); |
- |
- g_variant_builder_add(&hints_builder, "{sv}", "urgency", |
- g_variant_new_byte(NotificationPriorityToFdoUrgency( |
- notification.priority()))); |
- |
- 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(); |
- g_variant_builder_add(&hints_builder, "{sv}", "desktop-entry", |
- g_variant_new_string(desktop_file.value().c_str())); |
- |
- if (!resource_files->icon_file.empty()) { |
- g_variant_builder_add( |
- &hints_builder, "{sv}", "image-path", |
- g_variant_new_string(resource_files->icon_file.value().c_str())); |
- data->resource_files.push_back(resource_files->icon_file); |
- resource_files->icon_file.clear(); |
- } |
- |
- const std::string title = base::UTF16ToUTF8(notification.title()); |
- const std::string message = base::UTF16ToUTF8(notification.message()); |
- |
- GVariant* parameters = g_variant_new( |
- "(susssasa{sv}i)", "" /* app_name passed implicitly via desktop-entry */, |
- data->dbus_id, "" /* app_icon passed implicitly via desktop-entry */, |
- title.c_str(), message.c_str(), &actions_builder, &hints_builder, -1); |
- g_dbus_proxy_call(notification_proxy_, "Notify", parameters, |
- G_DBUS_CALL_FLAGS_NONE, -1, data->cancellable, callback, |
- user_data); |
-} |
- |
-void NotificationPlatformBridgeLinux::CloseNow(uint32_t dbus_id) { |
- g_dbus_proxy_call(notification_proxy_, "CloseNotification", |
- g_variant_new("(u)", dbus_id), G_DBUS_CALL_FLAGS_NONE, -1, |
- nullptr, nullptr, nullptr); |
-} |
- |
-NotificationPlatformBridgeLinux::NotificationData* |
-NotificationPlatformBridgeLinux::FindNotificationData( |
- const std::string& notification_id, |
- const std::string& profile_id, |
- bool is_incognito) { |
- 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; |
- } |
- } |
- |
- return nullptr; |
-} |
- |
-NotificationPlatformBridgeLinux::NotificationData* |
-NotificationPlatformBridgeLinux::FindNotificationData(uint32_t dbus_id) { |
- for (const auto& pair : notifications_) { |
- NotificationData* data = pair.first; |
- if (data->dbus_id == dbus_id) |
- return data; |
- } |
- |
- return nullptr; |
-} |
- |
-void NotificationPlatformBridgeLinux::ForwardNotificationOperation( |
- uint32_t dbus_id, |
- NotificationCommon::Operation operation, |
- int action_index) { |
- NotificationData* data = FindNotificationData(dbus_id); |
- if (!data) { |
- // This notification either belongs to a different app or we |
- // already removed the NotificationData after sending a |
- // "CloseNotification" message. |
- return; |
- } |
- |
- ProfileManager* profile_manager = g_browser_process->profile_manager(); |
- DCHECK(profile_manager); |
- |
- profile_manager->LoadProfile( |
- data->profile_id, data->is_incognito, |
- base::Bind(&ProfileLoadedCallback, operation, data->notification_type, |
- data->origin_url.spec(), data->notification_id, action_index, |
- base::NullableString16())); |
-} |
- |
-void NotificationPlatformBridgeLinux::GSignalReceiver(GDBusProxy* proxy, |
- const char* sender_name, |
- const char* sender_signal, |
- GVariant* parameters) { |
- uint32_t dbus_id = 0; |
- if (strcmp("NotificationClosed", sender_signal) == 0 && |
- g_variant_is_of_type(parameters, G_VARIANT_TYPE("(uu)"))) { |
- uint32_t reason; |
- g_variant_get(parameters, "(uu)", &dbus_id, &reason); |
- ForwardNotificationOperation(dbus_id, NotificationCommon::CLOSE, -1); |
- // std::unordered_map::erase(nullptr) is safe here. |
- notifications_.erase(FindNotificationData(dbus_id)); |
- } else if (strcmp("ActionInvoked", sender_signal) == 0 && |
- g_variant_is_of_type(parameters, G_VARIANT_TYPE("(us)"))) { |
- const gchar* action = nullptr; |
- g_variant_get(parameters, "(u&s)", &dbus_id, &action); |
- DCHECK(action); |
- |
- if (strcmp(action, "default") == 0) { |
- ForwardNotificationOperation(dbus_id, NotificationCommon::CLICK, -1); |
- } else if (strcmp(action, "settings") == 0) { |
- ForwardNotificationOperation(dbus_id, NotificationCommon::SETTINGS, -1); |
- } else { |
- size_t id; |
- if (!base::StringToSizeT(action, &id)) |
- return; |
- NotificationData* data = FindNotificationData(dbus_id); |
- if (!data) |
- 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; |
- ForwardNotificationOperation(dbus_id, NotificationCommon::CLICK, |
- id_zero_based); |
- } |
- } |
+void NotificationPlatformBridgeLinux::SetReadyCallback( |
+ NotificationBridgeReadyCallback callback) { |
+ impl_->SetReadyCallback(std::move(callback)); |
} |