// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/permissions/model/permissions_tab_helper.h"
#import "base/test/task_environment.h"
#import "base/threading/platform_thread.h"
#import "base/time/time.h"
#import "components/infobars/core/infobar_manager.h"
#import "ios/chrome/browser/infobars/model/infobar_ios.h"
#import "ios/chrome/browser/infobars/model/infobar_manager_impl.h"
#import "ios/chrome/browser/infobars/model/overlays/default_infobar_overlay_request_factory.h"
#import "ios/chrome/browser/infobars/model/overlays/infobar_overlay_request_inserter.h"
#import "ios/chrome/browser/overlays/model/public/overlay_request_queue.h"
#import "ios/chrome/browser/permissions/model/permissions_infobar_delegate.h"
#import "ios/web/public/permissions/permissions.h"
#import "ios/web/public/test/fakes/fake_navigation_manager.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/platform_test.h"
namespace {
constexpr base::TimeDelta kTimeoutDelay = base::Milliseconds(251);
} // namespace
// Test fixture for PermissionsTabHelper.
class PermissionsTabHelperTest : public PlatformTest {
public:
PermissionsTabHelperTest()
: task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
web_state_.SetNavigationManager(
std::make_unique<web::FakeNavigationManager>());
OverlayRequestQueue::CreateForWebState(&web_state_);
InfoBarManagerImpl::CreateForWebState(&web_state_);
InfobarOverlayRequestInserter::CreateForWebState(
&web_state_, &DefaultInfobarOverlayRequestFactory);
PermissionsTabHelper::CreateForWebState(&web_state_);
}
~PermissionsTabHelperTest() override {
InfoBarManagerImpl::FromWebState(&web_state_)->ShutDown();
web_state_.RemoveObserver(PermissionsTabHelper::FromWebState(&web_state_));
}
protected:
// Returns InfoBarManager attached to `web_state()`.
infobars::InfoBarManager* infobar_manager() {
return InfoBarManagerImpl::FromWebState(&web_state_);
}
// Returns the current infobar, if available.
InfoBarIOS* infobar() {
return infobar_manager()->infobars().empty()
? nullptr
: static_cast<InfoBarIOS*>(infobar_manager()->infobars()[0]);
}
// Returns recently_accessible_permissions determined by the tab helper.
NSArray<NSNumber*>* recently_accessible_permissions() {
if (infobar() == nullptr) {
return [NSArray array];
}
PermissionsInfobarDelegate* delegate =
static_cast<PermissionsInfobarDelegate*>(infobar()->delegate());
return delegate->GetMostRecentlyAccessiblePermissions();
}
base::test::TaskEnvironment task_environment_;
web::FakeWebState web_state_;
};
// Tests that an infobar is setup with the correct acceptance state when the
// status of a single permission changes.
TEST_F(PermissionsTabHelperTest, CheckInfobarCountForSinglePermission) {
// Allowed permission.
web_state_.SetStateForPermission(web::PermissionStateAllowed,
web::PermissionCamera);
EXPECT_EQ(0U, infobar_manager()->infobars().size());
task_environment_.FastForwardBy(kTimeoutDelay);
EXPECT_EQ(1U, infobar_manager()->infobars().size());
EXPECT_TRUE(infobar()->accepted());
EXPECT_EQ(1U, [recently_accessible_permissions() count]);
EXPECT_EQ(recently_accessible_permissions()[0].unsignedIntegerValue,
web::PermissionCamera);
// Blocked permission.
web_state_.SetStateForPermission(web::PermissionStateBlocked,
web::PermissionCamera);
EXPECT_EQ(1U, infobar_manager()->infobars().size());
EXPECT_FALSE(infobar()->accepted());
// Permission not accessible.
web_state_.SetStateForPermission(web::PermissionStateNotAccessible,
web::PermissionCamera);
EXPECT_EQ(0U, infobar_manager()->infobars().size());
EXPECT_EQ(0U, [recently_accessible_permissions() count]);
}
// Tests that blocking a permission and allowing it again correctly resets
// infobar and acceptance state.
TEST_F(PermissionsTabHelperTest, BlockingAndAllowingSinglePermission) {
// Allowed permission.
web_state_.SetStateForPermission(web::PermissionStateAllowed,
web::PermissionMicrophone);
EXPECT_EQ(0U, infobar_manager()->infobars().size());
task_environment_.FastForwardBy(kTimeoutDelay);
EXPECT_EQ(1U, infobar_manager()->infobars().size());
EXPECT_TRUE(infobar()->accepted());
EXPECT_EQ(1U, [recently_accessible_permissions() count]);
EXPECT_EQ(recently_accessible_permissions()[0].unsignedIntegerValue,
web::PermissionMicrophone);
// Block this permission.
web_state_.SetStateForPermission(web::PermissionStateBlocked,
web::PermissionMicrophone);
EXPECT_EQ(1U, infobar_manager()->infobars().size());
EXPECT_FALSE(infobar()->accepted());
EXPECT_EQ(1U, [recently_accessible_permissions() count]);
EXPECT_EQ(recently_accessible_permissions()[0].unsignedIntegerValue,
web::PermissionMicrophone);
// Allow it again.
web_state_.SetStateForPermission(web::PermissionStateAllowed,
web::PermissionMicrophone);
// The tab helper should not have to wait for the timeout.
EXPECT_EQ(1U, infobar_manager()->infobars().size());
EXPECT_TRUE(infobar()->accepted());
EXPECT_EQ(1U, [recently_accessible_permissions() count]);
EXPECT_EQ(recently_accessible_permissions()[0].unsignedIntegerValue,
web::PermissionMicrophone);
}
// Tests that making a permission inaccessible and allowing it again correctly
// resets infobar and acceptance state.
TEST_F(PermissionsTabHelperTest,
MakingPermissionNotAccessibleAndAllowingItAgain) {
// Allowed permission.
web_state_.SetStateForPermission(web::PermissionStateAllowed,
web::PermissionCamera);
EXPECT_EQ(0U, infobar_manager()->infobars().size());
task_environment_.FastForwardBy(kTimeoutDelay);
EXPECT_EQ(1U, infobar_manager()->infobars().size());
EXPECT_TRUE(infobar()->accepted());
EXPECT_EQ(1U, [recently_accessible_permissions() count]);
EXPECT_EQ(recently_accessible_permissions()[0].unsignedIntegerValue,
web::PermissionCamera);
// Make this permission inaccessible.
web_state_.SetStateForPermission(web::PermissionStateNotAccessible,
web::PermissionCamera);
EXPECT_EQ(0U, infobar_manager()->infobars().size());
// Access it again.
web_state_.SetStateForPermission(web::PermissionStateAllowed,
web::PermissionCamera);
// The tab helper should wait for the timeout again to create the infobar.
EXPECT_EQ(0U, infobar_manager()->infobars().size());
task_environment_.FastForwardBy(kTimeoutDelay);
EXPECT_EQ(1U, infobar_manager()->infobars().size());
EXPECT_TRUE(infobar()->accepted());
EXPECT_EQ(1U, [recently_accessible_permissions() count]);
EXPECT_EQ(recently_accessible_permissions()[0].unsignedIntegerValue,
web::PermissionCamera);
}
// Tests that an infobar is setup with the correct acceptance state when the
// status of both permission are allowed simultaneously.
TEST_F(PermissionsTabHelperTest,
CheckInfobarCountForSimultaneouslyAllowedPermissions) {
// Allow both permissions.
web_state_.SetStateForPermission(web::PermissionStateAllowed,
web::PermissionCamera);
web_state_.SetStateForPermission(web::PermissionStateAllowed,
web::PermissionMicrophone);
EXPECT_EQ(0U, infobar_manager()->infobars().size());
task_environment_.FastForwardBy(kTimeoutDelay);
EXPECT_EQ(1U, infobar_manager()->infobars().size());
EXPECT_TRUE(infobar()->accepted());
EXPECT_EQ(2U, [recently_accessible_permissions() count]);
EXPECT_EQ(recently_accessible_permissions()[0].unsignedIntegerValue,
web::PermissionCamera);
EXPECT_EQ(recently_accessible_permissions()[1].unsignedIntegerValue,
web::PermissionMicrophone);
// Blocked one of the permissions.
web_state_.SetStateForPermission(web::PermissionStateBlocked,
web::PermissionCamera);
EXPECT_EQ(1U, infobar_manager()->infobars().size());
EXPECT_TRUE(infobar()->accepted());
// Now block the other.
web_state_.SetStateForPermission(web::PermissionStateBlocked,
web::PermissionMicrophone);
EXPECT_EQ(1U, infobar_manager()->infobars().size());
EXPECT_FALSE(infobar()->accepted());
// Make one of the permissions not accessible. Infobar should still exist.
web_state_.SetStateForPermission(web::PermissionStateNotAccessible,
web::PermissionMicrophone);
EXPECT_EQ(1U, infobar_manager()->infobars().size());
EXPECT_FALSE(infobar()->accepted());
// Make the other not accessible too. Infobar should be removed.
web_state_.SetStateForPermission(web::PermissionStateNotAccessible,
web::PermissionCamera);
EXPECT_EQ(0U, infobar_manager()->infobars().size());
}
// Tests that an infobar is setup and replaced with the correct acceptance
// state when the status of both permission are allowed one by one.
TEST_F(PermissionsTabHelperTest,
CheckInfobarCountForSeparatelyAllowedPermissions) {
// Allow one permission.
web_state_.SetStateForPermission(web::PermissionStateAllowed,
web::PermissionCamera);
task_environment_.FastForwardBy(kTimeoutDelay);
EXPECT_EQ(1U, infobar_manager()->infobars().size());
EXPECT_EQ(1U, [recently_accessible_permissions() count]);
EXPECT_EQ(recently_accessible_permissions()[0].unsignedIntegerValue,
web::PermissionCamera);
InfoBarIOS* first_infobar = infobar();
// Now allow the other.
web_state_.SetStateForPermission(web::PermissionStateAllowed,
web::PermissionMicrophone);
task_environment_.FastForwardBy(kTimeoutDelay);
// Check that infobar is properly replaced.
EXPECT_EQ(1U, infobar_manager()->infobars().size());
EXPECT_NE(infobar(), first_infobar);
EXPECT_TRUE(infobar()->accepted());
EXPECT_EQ(1U, [recently_accessible_permissions() count]);
EXPECT_EQ(recently_accessible_permissions()[0].unsignedIntegerValue,
web::PermissionMicrophone);
// Blocked one of the permissions.
web_state_.SetStateForPermission(web::PermissionStateBlocked,
web::PermissionMicrophone);
EXPECT_EQ(1U, infobar_manager()->infobars().size());
EXPECT_TRUE(infobar()->accepted());
// Now block the other.
web_state_.SetStateForPermission(web::PermissionStateBlocked,
web::PermissionCamera);
EXPECT_EQ(1U, infobar_manager()->infobars().size());
EXPECT_FALSE(infobar()->accepted());
// Make one of the permissions not accessible. Infobar should still exist.
web_state_.SetStateForPermission(web::PermissionStateNotAccessible,
web::PermissionMicrophone);
EXPECT_EQ(1U, infobar_manager()->infobars().size());
EXPECT_FALSE(infobar()->accepted());
// Make the other not accessible too. Infobar should be removed.
web_state_.SetStateForPermission(web::PermissionStateNotAccessible,
web::PermissionCamera);
EXPECT_EQ(0U, infobar_manager()->infobars().size());
}
// Tests that infobar and acceptance state would be handled correctly when one
// permission is blocked while the other permission changes states.
TEST_F(PermissionsTabHelperTest,
CheckInfobarAndAcceptanceStateWhenOnePermissionIsBlocked) {
// Allow one permission.
web_state_.SetStateForPermission(web::PermissionStateAllowed,
web::PermissionCamera);
web_state_.SetStateForPermission(web::PermissionStateAllowed,
web::PermissionMicrophone);
task_environment_.FastForwardBy(kTimeoutDelay);
ASSERT_EQ(2U, [recently_accessible_permissions() count]);
EXPECT_EQ(recently_accessible_permissions()[0].unsignedIntegerValue,
web::PermissionCamera);
EXPECT_EQ(recently_accessible_permissions()[1].unsignedIntegerValue,
web::PermissionMicrophone);
// Now block one. The other one is still allowed, so infobar should still be
// accepted.
web_state_.SetStateForPermission(web::PermissionStateBlocked,
web::PermissionMicrophone);
EXPECT_EQ(1U, infobar_manager()->infobars().size());
EXPECT_TRUE(infobar()->accepted());
// Now make the other one inaccessible. Infobar should still exist, but its
// acceptance state should be false.
web_state_.SetStateForPermission(web::PermissionStateNotAccessible,
web::PermissionCamera);
EXPECT_EQ(1U, infobar_manager()->infobars().size());
EXPECT_FALSE(infobar()->accepted());
// Now make the other one "allowed" again. The acceptance state would be
// changed immediately, but the infobar would not be replaced until timeout
// is reached.
InfoBarIOS* first_infobar = infobar();
web_state_.SetStateForPermission(web::PermissionStateAllowed,
web::PermissionCamera);
ASSERT_EQ(1U, infobar_manager()->infobars().size());
EXPECT_EQ(first_infobar, infobar());
EXPECT_TRUE(infobar()->accepted());
// But after the timeout, the infobar should be replaced and have the right
// acceptance state.
task_environment_.FastForwardBy(kTimeoutDelay);
EXPECT_EQ(1U, infobar_manager()->infobars().size());
EXPECT_NE(first_infobar, infobar());
EXPECT_TRUE(infobar()->accepted());
EXPECT_EQ(1U, [recently_accessible_permissions() count]);
EXPECT_EQ(recently_accessible_permissions()[0].unsignedIntegerValue,
web::PermissionCamera);
}