blob: 68f42d9b8ca92f101e472223b2a53f874a046a73 [file] [log] [blame]
// Copyright (c) 2012 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 "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/apps/app_browsertest_util.h"
#include "chrome/browser/extensions/api/notifications/extension_notification_display_helper.h"
#include "chrome/browser/extensions/api/notifications/extension_notification_display_helper_factory.h"
#include "chrome/browser/extensions/api/notifications/notifications_api.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_function_test_utils.h"
#include "chrome/browser/notifications/notification.h"
#include "chrome/browser/notifications/notification_common.h"
#include "chrome/browser/notifications/notification_display_service_factory.h"
#include "chrome/browser/notifications/notifier_state_tracker.h"
#include "chrome/browser/notifications/notifier_state_tracker_factory.h"
#include "chrome/browser/notifications/stub_notification_display_service.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/extensions/app_launch_params.h"
#include "chrome/browser/ui/extensions/application_launch.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "content/public/browser/notification_service.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/api/test/test_api.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/browser/app_window/native_app_window.h"
#include "extensions/browser/notification_types.h"
#include "extensions/common/features/feature.h"
#include "extensions/common/test_util.h"
#include "extensions/test/extension_test_message_listener.h"
#include "extensions/test/result_catcher.h"
#include "ui/message_center/notifier_settings.h"
#if defined(OS_MACOSX)
#include "base/mac/mac_util.h"
#include "ui/base/test/scoped_fake_nswindow_fullscreen.h"
#endif
using extensions::AppWindow;
using extensions::AppWindowRegistry;
using extensions::Extension;
using extensions::ExtensionNotificationDisplayHelper;
using extensions::ExtensionNotificationDisplayHelperFactory;
using extensions::ResultCatcher;
namespace utils = extension_function_test_utils;
namespace {
// A class that waits for a |chrome.test.sendMessage| call, ignores the message,
// and writes down the user gesture status of the message.
class UserGestureCatcher : public content::NotificationObserver {
public:
UserGestureCatcher() : waiting_(false) {
registrar_.Add(this,
extensions::NOTIFICATION_EXTENSION_TEST_MESSAGE,
content::NotificationService::AllSources());
}
~UserGestureCatcher() override {}
bool GetNextResult() {
if (results_.empty()) {
waiting_ = true;
content::RunMessageLoop();
waiting_ = false;
}
if (!results_.empty()) {
bool ret = results_.front();
results_.pop_front();
return ret;
}
NOTREACHED();
return false;
}
private:
void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) override {
results_.push_back(
static_cast<content::Source<extensions::TestSendMessageFunction> >(
source)
.ptr()
->user_gesture());
if (waiting_)
base::MessageLoopForUI::current()->QuitWhenIdle();
}
content::NotificationRegistrar registrar_;
// A sequential list of user gesture notifications from the test extension(s).
std::deque<bool> results_;
// True if we're in a nested run loop waiting for results from
// the extension.
bool waiting_;
};
enum class WindowState {
FULLSCREEN,
NORMAL
};
class NotificationsApiTest : public ExtensionApiTest {
public:
const Extension* LoadExtensionAndWait(
const std::string& test_name) {
base::FilePath extdir = test_data_dir_.AppendASCII(test_name);
content::WindowedNotificationObserver page_created(
extensions::NOTIFICATION_EXTENSION_BACKGROUND_PAGE_READY,
content::NotificationService::AllSources());
const extensions::Extension* extension = LoadExtension(extdir);
if (extension) {
page_created.Wait();
}
return extension;
}
const Extension* LoadAppWithWindowState(
const std::string& test_name, WindowState window_state) {
const char* window_state_string = NULL;
switch (window_state) {
case WindowState::FULLSCREEN:
window_state_string = "fullscreen";
break;
case WindowState::NORMAL:
window_state_string = "normal";
break;
}
const std::string& create_window_options = base::StringPrintf(
"{\"state\":\"%s\"}", window_state_string);
base::FilePath extdir = test_data_dir_.AppendASCII(test_name);
const extensions::Extension* extension = LoadExtension(extdir);
EXPECT_TRUE(extension);
ExtensionTestMessageListener launched_listener("launched", true);
LaunchPlatformApp(extension);
EXPECT_TRUE(launched_listener.WaitUntilSatisfied());
launched_listener.Reply(create_window_options);
return extension;
}
AppWindow* GetFirstAppWindow(const std::string& app_id) {
AppWindowRegistry::AppWindowList app_windows = AppWindowRegistry::Get(
browser()->profile())->GetAppWindowsForApp(app_id);
AppWindowRegistry::const_iterator iter = app_windows.begin();
if (iter != app_windows.end())
return *iter;
return NULL;
}
ExtensionNotificationDisplayHelper* GetDisplayHelper() {
return ExtensionNotificationDisplayHelperFactory::GetForProfile(profile());
}
StubNotificationDisplayService* GetDisplayService() {
return reinterpret_cast<StubNotificationDisplayService*>(
NotificationDisplayServiceFactory::GetForProfile(profile()));
}
NotifierStateTracker* GetNotifierStateTracker() {
return NotifierStateTrackerFactory::GetForProfile(profile());
}
protected:
void SetUpOnMainThread() override {
ExtensionApiTest::SetUpOnMainThread();
DCHECK(profile());
NotificationDisplayServiceFactory::GetInstance()->SetTestingFactory(
profile(), &StubNotificationDisplayService::FactoryForTests);
}
// Returns the notification that's being displayed for |extension|, or nullptr
// when the notification count is not equal to one. It's not safe to rely on
// the Notification pointer after closing the notification, but a copy can be
// made to continue to be able to access the underlying information.
Notification* GetNotificationForExtension(
const extensions::Extension* extension) {
DCHECK(extension);
std::set<std::string> notifications =
GetDisplayHelper()->GetNotificationIdsForExtension(extension->url());
if (notifications.size() != 1)
return nullptr;
return GetDisplayHelper()->GetByNotificationId(*notifications.begin());
}
std::string GetNotificationIdFromDelegateId(const std::string& delegate_id) {
return GetDisplayHelper()->GetByNotificationId(delegate_id)->id();
}
void LaunchPlatformApp(const Extension* extension) {
OpenApplication(AppLaunchParams(
browser()->profile(), extension, extensions::LAUNCH_CONTAINER_NONE,
WindowOpenDisposition::NEW_WINDOW, extensions::SOURCE_TEST));
}
void EnableFullscreenNotifications() {
feature_list_.InitWithFeatures({
features::kPreferHtmlOverPlugins,
extensions::kAllowFullscreenAppNotificationsFeature}, {});
}
void DisableFullscreenNotifications() {
feature_list_.InitWithFeatures(
{features::kPreferHtmlOverPlugins},
{extensions::kAllowFullscreenAppNotificationsFeature});
}
private:
base::test::ScopedFeatureList feature_list_;
};
} // namespace
// http://crbug.com/691913
#if (defined(OS_LINUX) || defined(OS_WIN)) && !defined(NDEBUG)
#define MAYBE_TestBasicUsage DISABLED_TestBasicUsage
#else
#define MAYBE_TestBasicUsage TestBasicUsage
#endif
IN_PROC_BROWSER_TEST_F(NotificationsApiTest, MAYBE_TestBasicUsage) {
ASSERT_TRUE(RunExtensionTest("notifications/api/basic_usage")) << message_;
}
IN_PROC_BROWSER_TEST_F(NotificationsApiTest, TestEvents) {
ASSERT_TRUE(RunExtensionTest("notifications/api/events")) << message_;
}
IN_PROC_BROWSER_TEST_F(NotificationsApiTest, TestCSP) {
ASSERT_TRUE(RunExtensionTest("notifications/api/csp")) << message_;
}
IN_PROC_BROWSER_TEST_F(NotificationsApiTest, TestByUser) {
const extensions::Extension* extension =
LoadExtensionAndWait("notifications/api/by_user");
ASSERT_TRUE(extension) << message_;
{
ResultCatcher catcher;
const std::string notification_id =
GetNotificationIdFromDelegateId(extension->id() + "-FOO");
GetDisplayService()->RemoveNotification(
NotificationCommon::EXTENSION, notification_id, false /* by_user */);
EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
}
{
ResultCatcher catcher;
const std::string notification_id =
GetNotificationIdFromDelegateId(extension->id() + "-BAR");
GetDisplayService()->RemoveNotification(
NotificationCommon::EXTENSION, notification_id, true /* by_user */);
EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
}
{
ResultCatcher catcher;
GetDisplayService()->RemoveAllNotifications(NotificationCommon::EXTENSION,
false /* by_user */);
EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
}
{
ResultCatcher catcher;
GetDisplayService()->RemoveAllNotifications(NotificationCommon::EXTENSION,
true /* by_user */);
EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
}
}
IN_PROC_BROWSER_TEST_F(NotificationsApiTest, TestPartialUpdate) {
ASSERT_TRUE(RunExtensionTest("notifications/api/partial_update")) << message_;
const extensions::Extension* extension = GetSingleLoadedExtension();
ASSERT_TRUE(extension) << message_;
const char kNewTitle[] = "Changed!";
const char kNewMessage[] = "Too late! The show ended yesterday";
int kNewPriority = 2;
const char kButtonTitle[] = "NewButton";
Notification* notification = GetNotificationForExtension(extension);
ASSERT_TRUE(notification);
LOG(INFO) << "Notification ID: " << notification->id();
EXPECT_EQ(base::ASCIIToUTF16(kNewTitle), notification->title());
EXPECT_EQ(base::ASCIIToUTF16(kNewMessage), notification->message());
EXPECT_EQ(kNewPriority, notification->priority());
EXPECT_EQ(1u, notification->buttons().size());
EXPECT_EQ(base::ASCIIToUTF16(kButtonTitle), notification->buttons()[0].title);
}
IN_PROC_BROWSER_TEST_F(NotificationsApiTest, TestGetPermissionLevel) {
scoped_refptr<Extension> empty_extension(
extensions::test_util::CreateEmptyExtension());
// Get permission level for the extension whose notifications are enabled.
{
scoped_refptr<extensions::NotificationsGetPermissionLevelFunction>
notification_function(
new extensions::NotificationsGetPermissionLevelFunction());
notification_function->set_extension(empty_extension.get());
notification_function->set_has_callback(true);
std::unique_ptr<base::Value> result(utils::RunFunctionAndReturnSingleResult(
notification_function.get(), "[]", browser(), utils::NONE));
EXPECT_EQ(base::Value::Type::STRING, result->GetType());
std::string permission_level;
EXPECT_TRUE(result->GetAsString(&permission_level));
EXPECT_EQ("granted", permission_level);
}
// Get permission level for the extension whose notifications are disabled.
{
scoped_refptr<extensions::NotificationsGetPermissionLevelFunction>
notification_function(
new extensions::NotificationsGetPermissionLevelFunction());
notification_function->set_extension(empty_extension.get());
notification_function->set_has_callback(true);
message_center::NotifierId notifier_id(
message_center::NotifierId::APPLICATION,
empty_extension->id());
GetNotifierStateTracker()->SetNotifierEnabled(notifier_id, false);
std::unique_ptr<base::Value> result(utils::RunFunctionAndReturnSingleResult(
notification_function.get(), "[]", browser(), utils::NONE));
EXPECT_EQ(base::Value::Type::STRING, result->GetType());
std::string permission_level;
EXPECT_TRUE(result->GetAsString(&permission_level));
EXPECT_EQ("denied", permission_level);
}
}
IN_PROC_BROWSER_TEST_F(NotificationsApiTest, TestOnPermissionLevelChanged) {
const extensions::Extension* extension =
LoadExtensionAndWait("notifications/api/permission");
ASSERT_TRUE(extension) << message_;
// Test permission level changing from granted to denied.
{
ResultCatcher catcher;
message_center::NotifierId notifier_id(
message_center::NotifierId::APPLICATION,
extension->id());
GetNotifierStateTracker()->SetNotifierEnabled(notifier_id, false);
EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
}
// Test permission level changing from denied to granted.
{
ResultCatcher catcher;
message_center::NotifierId notifier_id(
message_center::NotifierId::APPLICATION,
extension->id());
GetNotifierStateTracker()->SetNotifierEnabled(notifier_id, true);
EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
}
}
IN_PROC_BROWSER_TEST_F(NotificationsApiTest, TestUserGesture) {
const extensions::Extension* extension =
LoadExtensionAndWait("notifications/api/user_gesture");
ASSERT_TRUE(extension) << message_;
Notification* notification = GetNotificationForExtension(extension);
ASSERT_TRUE(notification);
{
UserGestureCatcher catcher;
notification->ButtonClick(0);
EXPECT_TRUE(catcher.GetNextResult());
notification->Click();
EXPECT_TRUE(catcher.GetNextResult());
notification->Close(true /* by_user */);
EXPECT_TRUE(catcher.GetNextResult());
// Note that |notification| no longer points to valid memory.
}
ASSERT_FALSE(GetNotificationForExtension(extension));
}
IN_PROC_BROWSER_TEST_F(NotificationsApiTest, TestRequireInteraction) {
const extensions::Extension* extension =
LoadExtensionAndWait("notifications/api/require_interaction");
ASSERT_TRUE(extension) << message_;
Notification* notification = GetNotificationForExtension(extension);
ASSERT_TRUE(notification);
EXPECT_TRUE(notification->never_timeout());
}
IN_PROC_BROWSER_TEST_F(NotificationsApiTest, TestShouldDisplayNormal) {
EnableFullscreenNotifications();
ExtensionTestMessageListener notification_created_listener("created", false);
const Extension* extension = LoadAppWithWindowState(
"notifications/api/basic_app", WindowState::NORMAL);
ASSERT_TRUE(extension) << message_;
ASSERT_TRUE(notification_created_listener.WaitUntilSatisfied());
// We start by making sure the window is actually focused.
ASSERT_TRUE(ui_test_utils::ShowAndFocusNativeWindow(
GetFirstAppWindow(extension->id())->GetNativeWindow()));
Notification* notification = GetNotificationForExtension(extension);
ASSERT_TRUE(notification);
// If the app hasn't created a fullscreen window, then its notifications
// shouldn't be displayed when a window is fullscreen.
ASSERT_FALSE(notification->delegate()->ShouldDisplayOverFullscreen());
}
IN_PROC_BROWSER_TEST_F(NotificationsApiTest, TestShouldDisplayFullscreen) {
#if defined(OS_MACOSX)
ui::test::ScopedFakeNSWindowFullscreen fake_fullscreen;
#endif
EnableFullscreenNotifications();
ExtensionTestMessageListener notification_created_listener("created", false);
const Extension* extension = LoadAppWithWindowState(
"notifications/api/basic_app", WindowState::FULLSCREEN);
ASSERT_TRUE(extension) << message_;
ASSERT_TRUE(notification_created_listener.WaitUntilSatisfied());
// We start by making sure the window is actually focused.
ASSERT_TRUE(ui_test_utils::ShowAndFocusNativeWindow(
GetFirstAppWindow(extension->id())->GetNativeWindow()));
ASSERT_TRUE(GetFirstAppWindow(extension->id())->IsFullscreen())
<< "Not Fullscreen";
ASSERT_TRUE(GetFirstAppWindow(extension->id())->GetBaseWindow()->IsActive())
<< "Not Active";
Notification* notification = GetNotificationForExtension(extension);
ASSERT_TRUE(notification);
// If the app has created a fullscreen window, then its notifications should
// be displayed when a window is fullscreen.
ASSERT_TRUE(notification->delegate()->ShouldDisplayOverFullscreen());
}
IN_PROC_BROWSER_TEST_F(NotificationsApiTest, TestShouldDisplayFullscreenOff) {
#if defined(OS_MACOSX)
ui::test::ScopedFakeNSWindowFullscreen fake_fullscreen;
#endif
DisableFullscreenNotifications();
ExtensionTestMessageListener notification_created_listener("created", false);
const Extension* extension = LoadAppWithWindowState(
"notifications/api/basic_app", WindowState::FULLSCREEN);
ASSERT_TRUE(extension) << message_;
ASSERT_TRUE(notification_created_listener.WaitUntilSatisfied());
// We start by making sure the window is actually focused.
ASSERT_TRUE(ui_test_utils::ShowAndFocusNativeWindow(
GetFirstAppWindow(extension->id())->GetNativeWindow()));
ASSERT_TRUE(GetFirstAppWindow(extension->id())->IsFullscreen())
<< "Not Fullscreen";
ASSERT_TRUE(GetFirstAppWindow(extension->id())->GetBaseWindow()->IsActive())
<< "Not Active";
Notification* notification = GetNotificationForExtension(extension);
ASSERT_TRUE(notification);
// When the experiment flag is off, then ShouldDisplayOverFullscreen should
// return false.
ASSERT_FALSE(notification->delegate()->ShouldDisplayOverFullscreen());
}
// The Fake OSX fullscreen window doesn't like drawing a second fullscreen
// window when another is visible.
#if !defined(OS_MACOSX)
IN_PROC_BROWSER_TEST_F(NotificationsApiTest, TestShouldDisplayMultiFullscreen) {
// Start a fullscreen app, and then start another fullscreen app on top of the
// first. Notifications from the first should not be displayed because it is
// not the app actually displaying on the screen.
EnableFullscreenNotifications();
ExtensionTestMessageListener notification_created_listener("created", false);
const Extension* extension1 = LoadAppWithWindowState(
"notifications/api/basic_app", WindowState::FULLSCREEN);
ASSERT_TRUE(extension1) << message_;
ExtensionTestMessageListener window_visible_listener("visible", false);
const Extension* extension2 = LoadAppWithWindowState(
"notifications/api/other_app", WindowState::FULLSCREEN);
ASSERT_TRUE(extension2) << message_;
ASSERT_TRUE(notification_created_listener.WaitUntilSatisfied());
ASSERT_TRUE(window_visible_listener.WaitUntilSatisfied());
// We start by making sure the window is actually focused.
ASSERT_TRUE(ui_test_utils::ShowAndFocusNativeWindow(
GetFirstAppWindow(extension2->id())->GetNativeWindow()));
Notification* notification = GetNotificationForExtension(extension1);
ASSERT_TRUE(notification);
// The first app window is superseded by the second window, so its
// notification shouldn't be displayed.
ASSERT_FALSE(notification->delegate()->ShouldDisplayOverFullscreen());
}
#endif
// Verify that a notification is actually displayed when the app window that
// creates it is fullscreen with the fullscreen notification flag turned on.
IN_PROC_BROWSER_TEST_F(NotificationsApiTest,
TestShouldDisplayPopupNotification) {
#if defined(OS_MACOSX)
ui::test::ScopedFakeNSWindowFullscreen fake_fullscreen;
#endif
EnableFullscreenNotifications();
ExtensionTestMessageListener notification_created_listener("created", false);
const Extension* extension = LoadAppWithWindowState(
"notifications/api/basic_app", WindowState::FULLSCREEN);
ASSERT_TRUE(extension) << message_;
ASSERT_TRUE(notification_created_listener.WaitUntilSatisfied());
// We start by making sure the window is actually focused.
ASSERT_TRUE(ui_test_utils::ShowAndFocusNativeWindow(
GetFirstAppWindow(extension->id())->GetNativeWindow()));
ASSERT_TRUE(GetFirstAppWindow(extension->id())->IsFullscreen())
<< "Not Fullscreen";
ASSERT_TRUE(GetFirstAppWindow(extension->id())->GetBaseWindow()->IsActive())
<< "Not Active";
Notification* notification = GetNotificationForExtension(extension);
ASSERT_TRUE(notification);
// The extension's window is being shown and focused, so its expected that
// the notification displays on top of it.
ASSERT_TRUE(notification->delegate()->ShouldDisplayOverFullscreen());
}