blob: 4a18e5a5872d81d5bfdc9310a496a76228d6ec4a [file] [log] [blame]
// 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 <string>
#include <windows.data.xml.dom.h>
#include <wrl/client.h>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_restrictions.h"
#include "base/win/scoped_hstring.h"
#include "base/win/windows_version.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/browser/notifications/notification_platform_bridge_win.h"
#include "chrome/browser/notifications/win/mock_itoastnotification.h"
#include "chrome/browser/notifications/win/mock_itoastnotifier.h"
#include "chrome/browser/notifications/win/notification_launch_id.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
namespace mswr = Microsoft::WRL;
namespace winui = ABI::Windows::UI;
namespace winxml = ABI::Windows::Data::Xml;
namespace {
const char kLaunchId[] = "0|0|Default|0|https://example.com/|notification_id";
const char kLaunchIdButtonClick[] =
"1|0|0|Default|0|https://example.com/|notification_id";
const char kLaunchIdSettings[] =
"2|0|Default|0|https://example.com/|notification_id";
// Windows native notification have a dependency on WinRT (Win 8+) and the
// built in Notification Center. Although native notifications in Chrome are
// only available in Win 10+ we keep the minimum test coverage at Win 8 in case
// we decide to backport.
constexpr int kMinimumWindowsVersion = base::win::VERSION_WIN8;
Profile* CreateTestingProfile(const base::FilePath& path) {
base::ScopedAllowBlockingForTesting allow_blocking;
ProfileManager* profile_manager = g_browser_process->profile_manager();
size_t starting_number_of_profiles = profile_manager->GetNumberOfProfiles();
if (!base::PathExists(path) && !base::CreateDirectory(path))
NOTREACHED() << "Could not create directory at " << path.MaybeAsASCII();
Profile* profile =
Profile::CreateProfile(path, nullptr, Profile::CREATE_MODE_SYNCHRONOUS);
profile_manager->RegisterTestingProfile(profile, true, false);
EXPECT_EQ(starting_number_of_profiles + 1,
profile_manager->GetNumberOfProfiles());
return profile;
}
Profile* CreateTestingProfile(const std::string& profile_name) {
base::FilePath path;
base::PathService::Get(chrome::DIR_USER_DATA, &path);
path = path.AppendASCII(profile_name);
return CreateTestingProfile(path);
}
base::string16 GetToastString(const base::string16& notification_id,
const base::string16& profile_id,
bool incognito) {
return base::StringPrintf(
LR"(<toast launch="0|0|%ls|%d|https://foo.com/|%ls"></toast>)",
profile_id.c_str(), incognito, notification_id.c_str());
}
} // namespace
class NotificationPlatformBridgeWinUITest : public InProcessBrowserTest {
public:
NotificationPlatformBridgeWinUITest() = default;
~NotificationPlatformBridgeWinUITest() override = default;
void SetUpOnMainThread() override {
display_service_tester_ =
std::make_unique<NotificationDisplayServiceTester>(
browser()->profile());
}
NotificationPlatformBridgeWin* GetBridge() {
return static_cast<NotificationPlatformBridgeWin*>(
g_browser_process->notification_platform_bridge());
}
void TearDownOnMainThread() override { display_service_tester_.reset(); }
void HandleOperation(const base::RepeatingClosure& quit_task,
NotificationCommon::Operation operation,
NotificationHandler::Type notification_type,
const GURL& origin,
const std::string& notification_id,
const base::Optional<int>& action_index,
const base::Optional<base::string16>& reply,
const base::Optional<bool>& by_user) {
last_operation_ = operation;
last_notification_type_ = notification_type;
last_origin_ = origin;
last_notification_id_ = notification_id;
last_action_index_ = action_index;
last_reply_ = reply;
last_by_user_ = by_user;
quit_task.Run();
}
void DisplayedNotifications(
const base::RepeatingClosure& quit_task,
std::unique_ptr<std::set<std::string>> displayed_notifications,
bool supports_synchronization) {
displayed_notifications_ = *displayed_notifications;
quit_task.Run();
}
void ValidateLaunchId(const std::string& expected_launch_id,
const base::RepeatingClosure& quit_task,
const NotificationLaunchId& launch_id) {
ASSERT_TRUE(launch_id.is_valid());
ASSERT_STREQ(expected_launch_id.c_str(), launch_id.Serialize().c_str());
quit_task.Run();
}
protected:
void ProcessLaunchIdViaCmdLine(const std::string& launch_id,
const std::string& inline_reply) {
base::RunLoop run_loop;
display_service_tester_->SetProcessNotificationOperationDelegate(
base::BindRepeating(
&NotificationPlatformBridgeWinUITest::HandleOperation,
base::Unretained(this), run_loop.QuitClosure()));
// Simulate clicks on the toast.
base::CommandLine command_line = base::CommandLine::FromString(L"");
command_line.AppendSwitchASCII(switches::kNotificationLaunchId, launch_id);
if (!inline_reply.empty()) {
command_line.AppendSwitchASCII(switches::kNotificationInlineReply,
inline_reply);
}
ASSERT_TRUE(NotificationPlatformBridgeWin::HandleActivation(command_line));
run_loop.Run();
}
bool ValidateNotificationValues(NotificationCommon::Operation operation,
NotificationHandler::Type notification_type,
const GURL& origin,
const std::string& notification_id,
const base::Optional<int>& action_index,
const base::Optional<base::string16>& reply,
const base::Optional<bool>& by_user) {
return operation == last_operation_ &&
notification_type == last_notification_type_ &&
origin == last_origin_ && notification_id == last_notification_id_ &&
action_index == last_action_index_ && reply == last_reply_ &&
by_user == last_by_user_;
}
std::unique_ptr<NotificationDisplayServiceTester> display_service_tester_;
NotificationCommon::Operation last_operation_;
NotificationHandler::Type last_notification_type_;
GURL last_origin_;
std::string last_notification_id_;
base::Optional<int> last_action_index_;
base::Optional<base::string16> last_reply_;
base::Optional<bool> last_by_user_;
std::set<std::string> displayed_notifications_;
bool delegate_called_ = false;
};
class MockIToastActivatedEventArgs
: public winui::Notifications::IToastActivatedEventArgs {
public:
explicit MockIToastActivatedEventArgs(const base::string16& args)
: arguments_(args) {}
virtual ~MockIToastActivatedEventArgs() = default;
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid,
void** ppvObject) override {
return E_NOTIMPL;
}
ULONG STDMETHODCALLTYPE AddRef() override { return 1; }
ULONG STDMETHODCALLTYPE Release() override { return 0; }
HRESULT STDMETHODCALLTYPE GetIids(ULONG* iidCount, IID** iids) override {
return E_NOTIMPL;
}
HRESULT STDMETHODCALLTYPE GetRuntimeClassName(HSTRING* className) override {
return E_NOTIMPL;
}
HRESULT STDMETHODCALLTYPE GetTrustLevel(TrustLevel* trustLevel) override {
return E_NOTIMPL;
}
HRESULT STDMETHODCALLTYPE get_Arguments(HSTRING* value) override {
base::win::ScopedHString arguments =
base::win::ScopedHString::Create(arguments_);
*value = arguments.get();
return S_OK;
}
private:
base::string16 arguments_;
DISALLOW_COPY_AND_ASSIGN(MockIToastActivatedEventArgs);
};
IN_PROC_BROWSER_TEST_F(NotificationPlatformBridgeWinUITest, HandleEvent) {
if (base::win::GetVersion() < kMinimumWindowsVersion)
return;
const wchar_t kXmlDoc[] =
LR"(<toast launch="0|0|Default|0|https://example.com/|notification_id">
<visual>
<binding template="ToastGeneric">
<text>My Title</text>
<text placement="attribution">example.com</text>
</binding>
</visual>
<actions>
<action content="Click" arguments="args" activationType="foreground"/>
</actions>
</toast>
)";
MockIToastNotification toast(kXmlDoc, L"tag");
MockIToastActivatedEventArgs args(
L"1|1|0|Default|0|https://example.com/|notification_id");
base::RunLoop run_loop;
display_service_tester_->SetProcessNotificationOperationDelegate(
base::BindRepeating(&NotificationPlatformBridgeWinUITest::HandleOperation,
base::Unretained(this), run_loop.QuitClosure()));
// Simulate clicks on the toast.
NotificationPlatformBridgeWin* bridge = GetBridge();
ASSERT_TRUE(bridge);
bridge->ForwardHandleEventForTesting(NotificationCommon::CLICK, &toast, &args,
base::nullopt);
run_loop.Run();
// Validate the click values.
EXPECT_EQ(NotificationCommon::CLICK, last_operation_);
EXPECT_EQ(NotificationHandler::Type::WEB_PERSISTENT, last_notification_type_);
EXPECT_EQ(GURL("https://example.com/"), last_origin_);
EXPECT_EQ("notification_id", last_notification_id_);
EXPECT_EQ(base::Optional<int>(1), last_action_index_);
EXPECT_EQ(base::nullopt, last_reply_);
EXPECT_EQ(base::nullopt, last_by_user_);
}
IN_PROC_BROWSER_TEST_F(NotificationPlatformBridgeWinUITest, HandleActivation) {
if (base::win::GetVersion() < kMinimumWindowsVersion)
return;
base::RunLoop run_loop;
display_service_tester_->SetProcessNotificationOperationDelegate(
base::BindRepeating(&NotificationPlatformBridgeWinUITest::HandleOperation,
base::Unretained(this), run_loop.QuitClosure()));
// Simulate notification activation.
base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
command_line.AppendSwitchNative(
switches::kNotificationLaunchId,
L"1|1|0|Default|0|https://example.com/|notification_id");
NotificationPlatformBridgeWin::HandleActivation(command_line);
run_loop.Run();
// Validate the values.
EXPECT_EQ(NotificationCommon::CLICK, last_operation_);
EXPECT_EQ(NotificationHandler::Type::WEB_PERSISTENT, last_notification_type_);
EXPECT_EQ(GURL("https://example.com/"), last_origin_);
EXPECT_EQ("notification_id", last_notification_id_);
EXPECT_EQ(base::Optional<int>(1), last_action_index_);
EXPECT_EQ(base::nullopt, last_reply_);
EXPECT_EQ(true, last_by_user_);
}
IN_PROC_BROWSER_TEST_F(NotificationPlatformBridgeWinUITest, HandleSettings) {
if (base::win::GetVersion() < kMinimumWindowsVersion)
return;
const wchar_t kXmlDoc[] =
LR"(<toast launch="0|0|Default|0|https://example.com/|notification_id">
<visual>
<binding template="ToastGeneric">
<text>My Title</text>
<text placement="attribution">example.com</text>
</binding>
</visual>
<actions>
<action content="settings" placement="contextMenu" activationType="foreground" arguments="2|0|Default|0|https://example.com/|notification_id"/>
</actions>
</toast>
)";
MockIToastNotification toast(kXmlDoc, L"tag");
MockIToastActivatedEventArgs args(
L"2|0|Default|0|https://example.com/|notification_id");
base::RunLoop run_loop;
display_service_tester_->SetProcessNotificationOperationDelegate(
base::BindRepeating(&NotificationPlatformBridgeWinUITest::HandleOperation,
base::Unretained(this), run_loop.QuitClosure()));
// Simulate clicks on the toast.
NotificationPlatformBridgeWin* bridge = GetBridge();
ASSERT_TRUE(bridge);
bridge->ForwardHandleEventForTesting(NotificationCommon::SETTINGS, &toast,
&args, base::nullopt);
run_loop.Run();
// Validate the click values.
EXPECT_EQ(NotificationCommon::SETTINGS, last_operation_);
EXPECT_EQ(NotificationHandler::Type::WEB_PERSISTENT, last_notification_type_);
EXPECT_EQ(GURL("https://example.com/"), last_origin_);
EXPECT_EQ("notification_id", last_notification_id_);
EXPECT_EQ(base::nullopt, last_action_index_);
EXPECT_EQ(base::nullopt, last_reply_);
EXPECT_EQ(base::nullopt, last_by_user_);
}
IN_PROC_BROWSER_TEST_F(NotificationPlatformBridgeWinUITest, GetDisplayed) {
if (base::win::GetVersion() < kMinimumWindowsVersion)
return;
NotificationPlatformBridgeWin* bridge = GetBridge();
ASSERT_TRUE(bridge);
std::vector<winui::Notifications::IToastNotification*> notifications;
bridge->SetDisplayedNotificationsForTesting(&notifications);
// Validate that empty list of notifications show 0 results.
{
base::RunLoop run_loop;
bridge->GetDisplayed(
browser()->profile(),
base::BindRepeating(
&NotificationPlatformBridgeWinUITest::DisplayedNotifications,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_EQ(0U, displayed_notifications_.size());
}
// Add four items (two in each profile, one for each being incognito and one
// for each that is not).
bool incognito = true;
Profile* profile1 = CreateTestingProfile("P1");
MockIToastNotification item1(GetToastString(L"P1i", L"P1", incognito),
L"tag");
notifications.push_back(&item1);
MockIToastNotification item2(GetToastString(L"P1reg", L"P1", !incognito),
L"tag");
notifications.push_back(&item2);
Profile* profile2 = CreateTestingProfile("P2");
MockIToastNotification item3(GetToastString(L"P2i", L"P2", incognito),
L"tag");
notifications.push_back(&item3);
MockIToastNotification item4(GetToastString(L"P2reg", L"P2", !incognito),
L"tag");
notifications.push_back(&item4);
// Query for profile P1 in incognito (should return 1 item).
{
base::RunLoop run_loop;
bridge->GetDisplayed(
profile1->GetOffTheRecordProfile(),
base::BindRepeating(
&NotificationPlatformBridgeWinUITest::DisplayedNotifications,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_EQ(1U, displayed_notifications_.size());
EXPECT_EQ(1U, displayed_notifications_.count("P1i"));
}
// Query for profile P1 not in incognito (should return 1 item).
{
base::RunLoop run_loop;
bridge->GetDisplayed(
profile1,
base::BindRepeating(
&NotificationPlatformBridgeWinUITest::DisplayedNotifications,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_EQ(1U, displayed_notifications_.size());
EXPECT_EQ(1U, displayed_notifications_.count("P1reg"));
}
// Query for profile P2 in incognito (should return 1 item).
{
base::RunLoop run_loop;
bridge->GetDisplayed(
profile2->GetOffTheRecordProfile(),
base::BindRepeating(
&NotificationPlatformBridgeWinUITest::DisplayedNotifications,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_EQ(1U, displayed_notifications_.size());
EXPECT_EQ(1U, displayed_notifications_.count("P2i"));
}
// Query for profile P2 not in incognito (should return 1 item).
{
base::RunLoop run_loop;
bridge->GetDisplayed(
profile2,
base::BindRepeating(
&NotificationPlatformBridgeWinUITest::DisplayedNotifications,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_EQ(1U, displayed_notifications_.size());
EXPECT_EQ(1U, displayed_notifications_.count("P2reg"));
}
bridge->SetDisplayedNotificationsForTesting(nullptr);
}
// Test calling Display with a mock implementation of the Action Center
// and validate it gets the values expected.
IN_PROC_BROWSER_TEST_F(NotificationPlatformBridgeWinUITest, DisplayWithMockAC) {
if (base::win::GetVersion() < kMinimumWindowsVersion)
return;
NotificationPlatformBridgeWin* bridge = GetBridge();
ASSERT_TRUE(bridge);
MockIToastNotifier notifier;
bridge->SetNotifierForTesting(&notifier);
std::string launch_id_value = "0|0|P1|0|https://example.com/|notification_id";
NotificationLaunchId launch_id(launch_id_value);
ASSERT_TRUE(launch_id.is_valid());
auto notification = std::make_unique<message_center::Notification>(
message_center::NOTIFICATION_TYPE_SIMPLE, "notification_id", L"Text1",
L"Text2", gfx::Image(), base::string16(), GURL("https://example.com/"),
message_center::NotifierId(), message_center::RichNotificationData(),
nullptr);
std::unique_ptr<NotificationCommon::Metadata> metadata;
Profile* profile = CreateTestingProfile("P1");
{
base::RunLoop run_loop;
notifier.SetNotificationShownCallback(base::BindRepeating(
&NotificationPlatformBridgeWinUITest::ValidateLaunchId,
base::Unretained(this), launch_id_value, run_loop.QuitClosure()));
bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile,
*notification, std::move(metadata));
run_loop.Run();
}
bridge->SetNotifierForTesting(nullptr);
}
IN_PROC_BROWSER_TEST_F(NotificationPlatformBridgeWinUITest, CmdLineClick) {
if (base::win::GetVersion() < kMinimumWindowsVersion)
return;
ASSERT_NO_FATAL_FAILURE(ProcessLaunchIdViaCmdLine(kLaunchId, /*reply=*/""));
// Validate the click values.
EXPECT_EQ(NotificationCommon::CLICK, last_operation_);
EXPECT_EQ(NotificationHandler::Type::WEB_PERSISTENT, last_notification_type_);
EXPECT_EQ(GURL("https://example.com/"), last_origin_);
EXPECT_EQ("notification_id", last_notification_id_);
EXPECT_EQ(-1, last_action_index_);
EXPECT_EQ(base::nullopt, last_reply_);
EXPECT_TRUE(last_by_user_);
}
IN_PROC_BROWSER_TEST_F(NotificationPlatformBridgeWinUITest,
CmdLineInlineReply) {
if (base::win::GetVersion() < kMinimumWindowsVersion)
return;
ASSERT_NO_FATAL_FAILURE(
ProcessLaunchIdViaCmdLine(kLaunchIdButtonClick, "Inline reply"));
// Validate the click values.
EXPECT_EQ(NotificationCommon::CLICK, last_operation_);
EXPECT_EQ(NotificationHandler::Type::WEB_PERSISTENT, last_notification_type_);
EXPECT_EQ(GURL("https://example.com/"), last_origin_);
EXPECT_EQ("notification_id", last_notification_id_);
EXPECT_EQ(0, last_action_index_);
EXPECT_EQ(L"Inline reply", last_reply_);
EXPECT_TRUE(last_by_user_);
}
IN_PROC_BROWSER_TEST_F(NotificationPlatformBridgeWinUITest, CmdLineButton) {
if (base::win::GetVersion() < kMinimumWindowsVersion)
return;
ASSERT_NO_FATAL_FAILURE(
ProcessLaunchIdViaCmdLine(kLaunchIdButtonClick, /*reply=*/""));
// Validate the click values.
EXPECT_EQ(NotificationCommon::CLICK, last_operation_);
EXPECT_EQ(NotificationHandler::Type::WEB_PERSISTENT, last_notification_type_);
EXPECT_EQ(GURL("https://example.com/"), last_origin_);
EXPECT_EQ("notification_id", last_notification_id_);
EXPECT_EQ(0, last_action_index_);
EXPECT_EQ(base::nullopt, last_reply_);
EXPECT_TRUE(last_by_user_);
}
IN_PROC_BROWSER_TEST_F(NotificationPlatformBridgeWinUITest, CmdLineSettings) {
if (base::win::GetVersion() < kMinimumWindowsVersion)
return;
ASSERT_NO_FATAL_FAILURE(
ProcessLaunchIdViaCmdLine(kLaunchIdSettings, /*reply=*/""));
// Validate the click values.
EXPECT_EQ(NotificationCommon::SETTINGS, last_operation_);
EXPECT_EQ(NotificationHandler::Type::WEB_PERSISTENT, last_notification_type_);
EXPECT_EQ(GURL("https://example.com/"), last_origin_);
EXPECT_EQ("notification_id", last_notification_id_);
EXPECT_EQ(-1, last_action_index_);
EXPECT_EQ(base::nullopt, last_reply_);
EXPECT_TRUE(last_by_user_);
}