| // Copyright 2017 The Chromium Authors | 
 | // 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_win.h" | 
 |  | 
 | #include <windows.ui.notifications.h> | 
 | #include <wrl/client.h> | 
 | #include <wrl/implements.h> | 
 |  | 
 | #include <memory> | 
 | #include <string> | 
 | #include <utility> | 
 | #include <vector> | 
 |  | 
 | #include "base/hash/hash.h" | 
 | #include "base/logging.h" | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/strings/utf_string_conversions.h" | 
 | #include "base/win/scoped_com_initializer.h" | 
 | #include "base/win/scoped_hstring.h" | 
 | #include "chrome/browser/notifications/notification_common.h" | 
 | #include "chrome/browser/notifications/win/fake_itoastnotification.h" | 
 | #include "chrome/browser/notifications/win/fake_notification_image_retainer.h" | 
 | #include "chrome/browser/notifications/win/notification_launch_id.h" | 
 | #include "chrome/browser/notifications/win/notification_template_builder.h" | 
 | #include "content/public/test/browser_task_environment.h" | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 | #include "ui/message_center/public/cpp/notification.h" | 
 | #include "ui/message_center/public/cpp/notifier_id.h" | 
 |  | 
 | namespace mswr = Microsoft::WRL; | 
 | namespace winui = ABI::Windows::UI; | 
 |  | 
 | using message_center::Notification; | 
 |  | 
 | namespace { | 
 |  | 
 | constexpr char kLaunchId[] = | 
 |     "0|0|Default|aumi|0|https://example.com/|notification_id"; | 
 | constexpr char kOrigin[] = "https://www.google.com/"; | 
 | constexpr char kNotificationId[] = "id"; | 
 | constexpr char kProfileId[] = "Default"; | 
 | constexpr wchar_t kAppUserModelId[] = L"aumi"; | 
 | constexpr wchar_t kAppUserModelId2[] = L"aumi2"; | 
 | constexpr char kAppUserModelIdUTF8[] = "aumi"; | 
 |  | 
 | }  // namespace | 
 |  | 
 | class NotificationPlatformBridgeWinTest : public testing::Test { | 
 |  public: | 
 |   NotificationPlatformBridgeWinTest() | 
 |       : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} | 
 |   NotificationPlatformBridgeWinTest(const NotificationPlatformBridgeWinTest&) = | 
 |       delete; | 
 |   NotificationPlatformBridgeWinTest& operator=( | 
 |       const NotificationPlatformBridgeWinTest&) = delete; | 
 |  | 
 |   ~NotificationPlatformBridgeWinTest() override = default; | 
 |  | 
 |  protected: | 
 |   mswr::ComPtr<winui::Notifications::IToastNotification2> GetToast( | 
 |       NotificationPlatformBridgeWin* bridge, | 
 |       const NotificationLaunchId& launch_id, | 
 |       bool renotify, | 
 |       const std::string& profile_id, | 
 |       const std::wstring& app_user_model_id, | 
 |       bool incognito) { | 
 |     DCHECK(bridge); | 
 |  | 
 |     GURL origin(kOrigin); | 
 |     auto notification = std::make_unique<message_center::Notification>( | 
 |         message_center::NOTIFICATION_TYPE_SIMPLE, kNotificationId, u"title", | 
 |         u"message", ui::ImageModel(), u"display_source", origin, | 
 |         message_center::NotifierId(origin), | 
 |         message_center::RichNotificationData(), nullptr /* delegate */); | 
 |     notification->set_renotify(renotify); | 
 |     FakeNotificationImageRetainer image_retainer; | 
 |     std::wstring xml_template = | 
 |         BuildNotificationTemplate(&image_retainer, launch_id, *notification); | 
 |  | 
 |     mswr::ComPtr<winui::Notifications::IToastNotification> toast = | 
 |         bridge->GetToastNotificationForTesting(*notification, xml_template, | 
 |                                                profile_id, app_user_model_id, | 
 |                                                incognito); | 
 |     if (!toast) { | 
 |       LOG(ERROR) << "GetToastNotificationForTesting failed"; | 
 |       return nullptr; | 
 |     } | 
 |  | 
 |     mswr::ComPtr<winui::Notifications::IToastNotification2> toast2; | 
 |     HRESULT hr = toast.As<winui::Notifications::IToastNotification2>(&toast2); | 
 |     if (FAILED(hr)) { | 
 |       LOG(ERROR) << "Converting to IToastNotification2 failed"; | 
 |       return nullptr; | 
 |     } | 
 |  | 
 |     return toast2; | 
 |   } | 
 |  | 
 |   content::BrowserTaskEnvironment task_environment_; | 
 | }; | 
 |  | 
 | TEST_F(NotificationPlatformBridgeWinTest, GroupAndTag) { | 
 |   base::win::ScopedCOMInitializer com_initializer; | 
 |  | 
 |   NotificationPlatformBridgeWin bridge; | 
 |  | 
 |   NotificationLaunchId launch_id(kLaunchId); | 
 |   ASSERT_TRUE(launch_id.is_valid()); | 
 |  | 
 |   mswr::ComPtr<winui::Notifications::IToastNotification2> toast2 = | 
 |       GetToast(&bridge, launch_id, /*renotify=*/false, kProfileId, | 
 |                kAppUserModelId, /*incognito=*/false); | 
 |   ASSERT_TRUE(toast2); | 
 |  | 
 |   HSTRING hstring_group; | 
 |   ASSERT_HRESULT_SUCCEEDED(toast2->get_Group(&hstring_group)); | 
 |   base::win::ScopedHString group(hstring_group); | 
 |   // NOTE: If you find yourself needing to change this value, make sure that | 
 |   // NotificationPlatformBridgeWinImpl::Close supports specifying the right | 
 |   // group value for RemoveGroupedTagWithId. | 
 |   ASSERT_EQ(L"Notifications", group.Get()); | 
 |  | 
 |   HSTRING hstring_tag; | 
 |   ASSERT_HRESULT_SUCCEEDED(toast2->get_Tag(&hstring_tag)); | 
 |   base::win::ScopedHString tag(hstring_tag); | 
 |   std::string tag_data = std::string(kNotificationId) + "|" + kProfileId + "|" + | 
 |                          kAppUserModelIdUTF8 + "|0"; | 
 |   ASSERT_EQ(base::NumberToWString(base::Hash(tag_data)), tag.Get()); | 
 |  | 
 |   // Let tasks on |notification_task_runner_| of |bridge| run before its dtor. | 
 |   task_environment_.RunUntilIdle(); | 
 | } | 
 |  | 
 | TEST_F(NotificationPlatformBridgeWinTest, GroupAndTagUniqueness) { | 
 |   base::win::ScopedCOMInitializer com_initializer; | 
 |  | 
 |   NotificationPlatformBridgeWin bridge; | 
 |  | 
 |   NotificationLaunchId launch_id(kLaunchId); | 
 |   ASSERT_TRUE(launch_id.is_valid()); | 
 |  | 
 |   mswr::ComPtr<winui::Notifications::IToastNotification2> toastA; | 
 |   mswr::ComPtr<winui::Notifications::IToastNotification2> toastB; | 
 |   HSTRING hstring_tagA; | 
 |   HSTRING hstring_tagB; | 
 |  | 
 |   // Different profiles, same incognito status -> Unique tags. | 
 |   { | 
 |     toastA = GetToast(&bridge, launch_id, /*renotify=*/false, "Profile1", | 
 |                       kAppUserModelId, /*incognito=*/true); | 
 |     toastB = GetToast(&bridge, launch_id, /*renotify=*/false, "Profile2", | 
 |                       kAppUserModelId, /*incognito=*/true); | 
 |  | 
 |     ASSERT_TRUE(toastA); | 
 |     ASSERT_TRUE(toastB); | 
 |  | 
 |     ASSERT_HRESULT_SUCCEEDED(toastA->get_Tag(&hstring_tagA)); | 
 |     base::win::ScopedHString tagA(hstring_tagA); | 
 |  | 
 |     ASSERT_HRESULT_SUCCEEDED(toastB->get_Tag(&hstring_tagB)); | 
 |     base::win::ScopedHString tagB(hstring_tagB); | 
 |  | 
 |     ASSERT_NE(tagA.Get(), tagB.Get()); | 
 |   } | 
 |  | 
 |   // Same profile, different incognito status -> Unique tags. | 
 |   { | 
 |     toastA = GetToast(&bridge, launch_id, /*renotify=*/false, "Profile1", | 
 |                       kAppUserModelId, /*incognito=*/true); | 
 |     toastB = GetToast(&bridge, launch_id, /*renotify=*/false, "Profile1", | 
 |                       kAppUserModelId, /*incognito=*/false); | 
 |  | 
 |     ASSERT_TRUE(toastA); | 
 |     ASSERT_TRUE(toastB); | 
 |  | 
 |     ASSERT_HRESULT_SUCCEEDED(toastA->get_Tag(&hstring_tagA)); | 
 |     base::win::ScopedHString tagA(hstring_tagA); | 
 |  | 
 |     ASSERT_HRESULT_SUCCEEDED(toastB->get_Tag(&hstring_tagB)); | 
 |     base::win::ScopedHString tagB(hstring_tagB); | 
 |  | 
 |     ASSERT_NE(tagA.Get(), tagB.Get()); | 
 |   } | 
 |  | 
 |   // Same profile, same incognito status -> Identical tags. | 
 |   { | 
 |     toastA = GetToast(&bridge, launch_id, /*renotify=*/false, "Profile1", | 
 |                       kAppUserModelId, /*incognito=*/true); | 
 |     toastB = GetToast(&bridge, launch_id, /*renotify=*/false, "Profile1", | 
 |                       kAppUserModelId, /*incognito=*/true); | 
 |  | 
 |     ASSERT_TRUE(toastA); | 
 |     ASSERT_TRUE(toastB); | 
 |  | 
 |     ASSERT_HRESULT_SUCCEEDED(toastA->get_Tag(&hstring_tagA)); | 
 |     base::win::ScopedHString tagA(hstring_tagA); | 
 |  | 
 |     ASSERT_HRESULT_SUCCEEDED(toastB->get_Tag(&hstring_tagB)); | 
 |     base::win::ScopedHString tagB(hstring_tagB); | 
 |  | 
 |     ASSERT_EQ(tagA.Get(), tagB.Get()); | 
 |   } | 
 |  | 
 |   // Same profile, same incognito status, different app user model id | 
 |   // -> Unique tags. | 
 |   { | 
 |     toastA = GetToast(&bridge, launch_id, /*renotify=*/false, "Profile1", | 
 |                       kAppUserModelId, /*incognito=*/true); | 
 |     toastB = GetToast(&bridge, launch_id, /*renotify=*/false, "Profile1", | 
 |                       kAppUserModelId2, /*incognito=*/false); | 
 |  | 
 |     ASSERT_TRUE(toastA); | 
 |     ASSERT_TRUE(toastB); | 
 |  | 
 |     ASSERT_HRESULT_SUCCEEDED(toastA->get_Tag(&hstring_tagA)); | 
 |     base::win::ScopedHString tagA(hstring_tagA); | 
 |  | 
 |     ASSERT_HRESULT_SUCCEEDED(toastB->get_Tag(&hstring_tagB)); | 
 |     base::win::ScopedHString tagB(hstring_tagB); | 
 |  | 
 |     ASSERT_NE(tagA.Get(), tagB.Get()); | 
 |   } | 
 |  | 
 |   // Let tasks on |notification_task_runner_| of |bridge| run before its dtor. | 
 |   task_environment_.RunUntilIdle(); | 
 | } | 
 |  | 
 | TEST_F(NotificationPlatformBridgeWinTest, Suppress) { | 
 |   base::win::ScopedCOMInitializer com_initializer; | 
 |  | 
 |   NotificationPlatformBridgeWin bridge; | 
 |  | 
 |   std::vector<mswr::ComPtr<winui::Notifications::IToastNotification>> | 
 |       notifications; | 
 |   bridge.SetDisplayedNotificationsForTesting(¬ifications); | 
 |  | 
 |   mswr::ComPtr<winui::Notifications::IToastNotification2> toast2; | 
 |   boolean suppress; | 
 |  | 
 |   NotificationLaunchId launch_id(kLaunchId); | 
 |   ASSERT_TRUE(launch_id.is_valid()); | 
 |  | 
 |   // Make sure this works a toast is not suppressed when no notifications are | 
 |   // registered. | 
 |   toast2 = GetToast(&bridge, launch_id, /*renotify=*/false, kProfileId, | 
 |                     kAppUserModelId, /*incognito=*/false); | 
 |   ASSERT_TRUE(toast2); | 
 |   ASSERT_HRESULT_SUCCEEDED(toast2->get_SuppressPopup(&suppress)); | 
 |   ASSERT_FALSE(suppress); | 
 |   toast2.Reset(); | 
 |  | 
 |   // Register a single notification with a specific tag. | 
 |   std::string tag_data = std::string(kNotificationId) + "|" + kProfileId + "|" + | 
 |                          kAppUserModelIdUTF8 + "|0"; | 
 |   std::wstring tag = base::NumberToWString(base::Hash(tag_data)); | 
 |   // Microsoft::WRL::Make() requires FakeIToastNotification to derive from | 
 |   // RuntimeClass. | 
 |   notifications.push_back(Microsoft::WRL::Make<FakeIToastNotification>( | 
 |       L"<toast launch=\"0|0|Default|aumi|0|https://foo.com/|id\"></toast>", | 
 |       tag)); | 
 |  | 
 |   // Request this notification with renotify true (should not be suppressed). | 
 |   toast2 = GetToast(&bridge, launch_id, /*renotify=*/true, kProfileId, | 
 |                     kAppUserModelId, /*incognito=*/false); | 
 |   ASSERT_TRUE(toast2); | 
 |   ASSERT_HRESULT_SUCCEEDED(toast2->get_SuppressPopup(&suppress)); | 
 |   ASSERT_FALSE(suppress); | 
 |   toast2.Reset(); | 
 |  | 
 |   // Request this notification with renotify false (should be suppressed). | 
 |   toast2 = GetToast(&bridge, launch_id, /*renotify=*/false, kProfileId, | 
 |                     kAppUserModelId, /*incognito=*/false); | 
 |   ASSERT_TRUE(toast2); | 
 |   ASSERT_HRESULT_SUCCEEDED(toast2->get_SuppressPopup(&suppress)); | 
 |   ASSERT_TRUE(suppress); | 
 |   toast2.Reset(); | 
 |  | 
 |   // Let tasks on |notification_task_runner_| of |bridge| run before its dtor. | 
 |   task_environment_.RunUntilIdle(); | 
 |  | 
 |   // Do this after we've finished running tasks to avoid touching | 
 |   // synchronize_displayed_notifications_timer_. See crbug.com/1220122. | 
 |   bridge.SetDisplayedNotificationsForTesting(nullptr); | 
 | } |