Commit fbe7f1f7 authored by Stepan Khapugin's avatar Stepan Khapugin Committed by Commit Bot

[multiball] Incognito blocker.

Adds a per-scene incognito blocker. Removes the old blocker.
The new blocker is implemented as a scene agent that monitors the scene
for state changes and checks a new SceneState flag
|incognitoContentVisible| that was previously owned by SceneController.
Removes old tests and adds new ones.
Uses window ordering by windowLevel instead of keyWindow, which is
actually possible even before scene startup.

especially with full-screen video in an incognito tab.

Fixed: 1125969
Test: on iOS 12, 13, 14 devices test that the incognito blocker works,
Change-Id: I3192908c61fecc7d548383dab4d068a05f4c3ef3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2404855
Auto-Submit: Stepan Khapugin <stkhapugin@chromium.org>
Commit-Queue: Mark Cogan <marq@chromium.org>
Reviewed-by: default avatarMark Cogan <marq@chromium.org>
Cr-Commit-Position: refs/heads/master@{#806642}
parent 37aa4ade
...@@ -115,8 +115,7 @@ initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher ...@@ -115,8 +115,7 @@ initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher
// Called when going into the background. iOS already broadcasts, so // Called when going into the background. iOS already broadcasts, so
// stakeholders can register for it directly. // stakeholders can register for it directly.
- (void)applicationDidEnterBackground:(UIApplication*)application - (void)applicationDidEnterBackground:(UIApplication*)application
memoryHelper:(MemoryWarningHelper*)memoryHelper memoryHelper:(MemoryWarningHelper*)memoryHelper;
incognitoContentVisible:(BOOL)incognitoContentVisible;
// Called when returning to the foreground. Resets and uploads the metrics. // Called when returning to the foreground. Resets and uploads the metrics.
// Starts the browser to foreground if needed. // Starts the browser to foreground if needed.
......
...@@ -10,7 +10,6 @@ ...@@ -10,7 +10,6 @@
#include "base/callback.h" #include "base/callback.h"
#include "base/critical_closure.h" #include "base/critical_closure.h"
#import "base/ios/crb_protocol_observers.h" #import "base/ios/crb_protocol_observers.h"
#include "base/mac/bundle_locations.h"
#include "base/mac/foundation_util.h" #include "base/mac/foundation_util.h"
#include "base/metrics/histogram_macros.h" #include "base/metrics/histogram_macros.h"
#include "base/task/post_task.h" #include "base/task/post_task.h"
...@@ -104,8 +103,6 @@ NSString* const kStartupAttemptReset = @"StartupAttempReset"; ...@@ -104,8 +103,6 @@ NSString* const kStartupAttemptReset = @"StartupAttempReset";
base::TimeTicks _sessionStartTime; base::TimeTicks _sessionStartTime;
// YES if the app is currently in the process of terminating. // YES if the app is currently in the process of terminating.
BOOL _appIsTerminating; BOOL _appIsTerminating;
// Interstitial view used to block any incognito tabs after backgrounding.
UIView* _incognitoBlocker;
// Whether the application is currently in the background. // Whether the application is currently in the background.
// This is a workaround for rdar://22392526 where // This is a workaround for rdar://22392526 where
// -applicationDidEnterBackground: can be called twice. // -applicationDidEnterBackground: can be called twice.
...@@ -212,8 +209,7 @@ initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher ...@@ -212,8 +209,7 @@ initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher
#pragma mark - Public methods. #pragma mark - Public methods.
- (void)applicationDidEnterBackground:(UIApplication*)application - (void)applicationDidEnterBackground:(UIApplication*)application
memoryHelper:(MemoryWarningHelper*)memoryHelper memoryHelper:(MemoryWarningHelper*)memoryHelper {
incognitoContentVisible:(BOOL)incognitoContentVisible {
if ([self isInSafeMode]) { if ([self isInSafeMode]) {
// Force a crash when backgrounding and in safe mode, so users don't get // Force a crash when backgrounding and in safe mode, so users don't get
// stuck in safe mode. // stuck in safe mode.
...@@ -255,34 +251,6 @@ initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher ...@@ -255,34 +251,6 @@ initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher
[_startupInformation expireFirstUserActionRecorder]; [_startupInformation expireFirstUserActionRecorder];
// If the current BVC is incognito, or if we are in the tab switcher and there
// are incognito tabs visible, place a full screen view containing the
// switcher background to hide any incognito content.
if (incognitoContentVisible) {
// Cover the largest area potentially shown in the app switcher, in case
// the screenshot is reused in a different orientation or size class.
CGRect screenBounds = [[UIScreen mainScreen] bounds];
CGFloat maxDimension =
std::max(CGRectGetWidth(screenBounds), CGRectGetHeight(screenBounds));
_incognitoBlocker = [[UIView alloc]
initWithFrame:CGRectMake(0, 0, maxDimension, maxDimension)];
NSBundle* mainBundle = base::mac::FrameworkBundle();
NSArray* topObjects =
[mainBundle loadNibNamed:@"LaunchScreen" owner:self options:nil];
UIViewController* launchScreenController =
base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
[_incognitoBlocker addSubview:[launchScreenController view]];
[launchScreenController view].autoresizingMask =
UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
_incognitoBlocker.autoresizingMask =
UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
// Adding |_incognitoBlocker| to |_window| won't cover overlay windows such
// as fullscreen video. Instead use the sharedApplication |keyWindow|.
UIWindow* window = [[UIApplication sharedApplication] keyWindow];
[window addSubview:_incognitoBlocker];
}
// Do not save cookies if it is already in progress. // Do not save cookies if it is already in progress.
id<BrowserInterface> currentInterface = id<BrowserInterface> currentInterface =
_browserLauncher.interfaceProvider.currentInterface; _browserLauncher.interfaceProvider.currentInterface;
...@@ -344,9 +312,6 @@ initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher ...@@ -344,9 +312,6 @@ initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher
->OnApplicationWillEnterForeground(); ->OnApplicationWillEnterForeground();
} }
[_incognitoBlocker removeFromSuperview];
_incognitoBlocker = nil;
crash_keys::SetCurrentlyInBackground(false); crash_keys::SetCurrentlyInBackground(false);
// Update the state of metrics and crash reporting, as the method of // Update the state of metrics and crash reporting, as the method of
...@@ -393,9 +358,6 @@ initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher ...@@ -393,9 +358,6 @@ initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher
connectionInformation: connectionInformation:
(id<ConnectionInformation>)connectionInformation { (id<ConnectionInformation>)connectionInformation {
DCHECK(!IsSceneStartupSupported()); DCHECK(!IsSceneStartupSupported());
[_incognitoBlocker removeFromSuperview];
_incognitoBlocker = nil;
DCHECK([_browserLauncher browserInitializationStage] == DCHECK([_browserLauncher browserInitializationStage] ==
INITIALIZATION_STAGE_FOREGROUND); INITIALIZATION_STAGE_FOREGROUND);
......
...@@ -196,27 +196,6 @@ class AppStateTest : public BlockCleanupTest { ...@@ -196,27 +196,6 @@ class AppStateTest : public BlockCleanupTest {
browser_state_ = test_cbs_builder.Build(); browser_state_ = test_cbs_builder.Build();
} }
void initializeIncognitoBlocker(UIWindow* window) {
id application = [OCMockObject niceMockForClass:[UIApplication class]];
id memoryHelper = [OCMockObject mockForClass:[MemoryWarningHelper class]];
StubBrowserInterfaceProvider* interfaceProvider =
[[StubBrowserInterfaceProvider alloc] init];
std::unique_ptr<Browser> browser = std::make_unique<TestBrowser>();
[[startup_information_mock_ stub] expireFirstUserActionRecorder];
[[[memoryHelper stub] andReturnValue:@0] foregroundMemoryWarningCount];
interfaceProvider.mainInterface.browser = browser.get();
swizzleMetricsMediatorDisableReporting();
[app_state_ applicationDidEnterBackground:application
memoryHelper:memoryHelper
incognitoContentVisible:YES];
metrics_mediator_called_ = NO;
}
void swizzleConnectedScenes(NSArray<SceneState*>* connectedScenes) { void swizzleConnectedScenes(NSArray<SceneState*>* connectedScenes) {
connected_scenes_swizzle_block_ = ^NSArray<SceneState*>*(id self) { connected_scenes_swizzle_block_ = ^NSArray<SceneState*>*(id self) {
return connectedScenes; return connectedScenes;
...@@ -269,8 +248,7 @@ class AppStateTest : public BlockCleanupTest { ...@@ -269,8 +248,7 @@ class AppStateTest : public BlockCleanupTest {
handle_startup_swizzle_block_)); handle_startup_swizzle_block_));
} }
AppState* getAppStateWithOpenNTPAndIncognitoBlock(BOOL shouldOpenNTP, AppState* getAppStateWithOpenNTP(BOOL shouldOpenNTP, UIWindow* window) {
UIWindow* window) {
AppState* appState = getAppStateWithRealWindow(window); AppState* appState = getAppStateWithRealWindow(window);
id application = [OCMockObject mockForClass:[UIApplication class]]; id application = [OCMockObject mockForClass:[UIApplication class]];
...@@ -297,8 +275,6 @@ class AppStateTest : public BlockCleanupTest { ...@@ -297,8 +275,6 @@ class AppStateTest : public BlockCleanupTest {
metricsMediator:metricsMediator metricsMediator:metricsMediator
memoryHelper:memoryHelper]; memoryHelper:memoryHelper];
initializeIncognitoBlocker(window);
return appState; return appState;
} }
...@@ -588,7 +564,7 @@ TEST_F(AppStateWithThreadTest, willTerminate) { ...@@ -588,7 +564,7 @@ TEST_F(AppStateWithThreadTest, willTerminate) {
EXPECT_TRUE(provider->cancel_called()); EXPECT_TRUE(provider->cancel_called());
} }
// Test that -resumeSessionWithTabOpener removes incognito blocker, // Test that -resumeSessionWithTabOpener
// restart metrics and launchs from StartupParameters if they exist. // restart metrics and launchs from StartupParameters if they exist.
TEST_F(AppStateTest, resumeSessionWithStartupParameters) { TEST_F(AppStateTest, resumeSessionWithStartupParameters) {
if (IsSceneStartupSupported()) { if (IsSceneStartupSupported()) {
...@@ -627,10 +603,7 @@ TEST_F(AppStateTest, resumeSessionWithStartupParameters) { ...@@ -627,10 +603,7 @@ TEST_F(AppStateTest, resumeSessionWithStartupParameters) {
swizzleHandleStartupParameters(tabOpener, getBrowserState()); swizzleHandleStartupParameters(tabOpener, getBrowserState());
ScopedKeyWindow scopedKeyWindow; ScopedKeyWindow scopedKeyWindow;
AppState* appState = AppState* appState = getAppStateWithOpenNTP(NO, scopedKeyWindow.Get());
getAppStateWithOpenNTPAndIncognitoBlock(NO, scopedKeyWindow.Get());
ASSERT_EQ(NSUInteger(1), [scopedKeyWindow.Get() subviews].count);
// Action. // Action.
[appState resumeSessionWithTabOpener:tabOpener [appState resumeSessionWithTabOpener:tabOpener
...@@ -638,12 +611,11 @@ TEST_F(AppStateTest, resumeSessionWithStartupParameters) { ...@@ -638,12 +611,11 @@ TEST_F(AppStateTest, resumeSessionWithStartupParameters) {
connectionInformation:getConnectionInformationMock()]; connectionInformation:getConnectionInformationMock()];
// Test. // Test.
EXPECT_EQ(NSUInteger(0), [scopedKeyWindow.Get() subviews].count);
EXPECT_EQ(1, getProfileSessionDurationsService()->session_started_count()); EXPECT_EQ(1, getProfileSessionDurationsService()->session_started_count());
EXPECT_EQ(0, getProfileSessionDurationsService()->session_ended_count()); EXPECT_EQ(0, getProfileSessionDurationsService()->session_ended_count());
} }
// Test that -resumeSessionWithTabOpener removes incognito blocker, // Test that -resumeSessionWithTabOpener
// restart metrics and creates a new tab from tab switcher if shouldOpenNTP is // restart metrics and creates a new tab from tab switcher if shouldOpenNTP is
// YES. // YES.
TEST_F(AppStateTest, resumeSessionShouldOpenNTPTabSwitcher) { TEST_F(AppStateTest, resumeSessionShouldOpenNTPTabSwitcher) {
...@@ -681,10 +653,7 @@ TEST_F(AppStateTest, resumeSessionShouldOpenNTPTabSwitcher) { ...@@ -681,10 +653,7 @@ TEST_F(AppStateTest, resumeSessionShouldOpenNTPTabSwitcher) {
[[[tabSwitcher stub] andReturnValue:@YES] openNewTabFromTabSwitcher]; [[[tabSwitcher stub] andReturnValue:@YES] openNewTabFromTabSwitcher];
ScopedKeyWindow scopedKeyWindow; ScopedKeyWindow scopedKeyWindow;
AppState* appState = AppState* appState = getAppStateWithOpenNTP(YES, scopedKeyWindow.Get());
getAppStateWithOpenNTPAndIncognitoBlock(YES, scopedKeyWindow.Get());
ASSERT_EQ(NSUInteger(1), [scopedKeyWindow.Get() subviews].count);
// Action. // Action.
[appState resumeSessionWithTabOpener:tabOpener [appState resumeSessionWithTabOpener:tabOpener
...@@ -695,7 +664,7 @@ TEST_F(AppStateTest, resumeSessionShouldOpenNTPTabSwitcher) { ...@@ -695,7 +664,7 @@ TEST_F(AppStateTest, resumeSessionShouldOpenNTPTabSwitcher) {
EXPECT_EQ(NSUInteger(0), [scopedKeyWindow.Get() subviews].count); EXPECT_EQ(NSUInteger(0), [scopedKeyWindow.Get() subviews].count);
} }
// Test that -resumeSessionWithTabOpener removes incognito blocker, // Test that -resumeSessionWithTabOpener,
// restart metrics and creates a new tab if shouldOpenNTP is YES. // restart metrics and creates a new tab if shouldOpenNTP is YES.
TEST_F(AppStateTest, resumeSessionShouldOpenNTPNoTabSwitcher) { TEST_F(AppStateTest, resumeSessionShouldOpenNTPNoTabSwitcher) {
if (IsSceneStartupSupported()) { if (IsSceneStartupSupported()) {
...@@ -746,11 +715,7 @@ TEST_F(AppStateTest, resumeSessionShouldOpenNTPNoTabSwitcher) { ...@@ -746,11 +715,7 @@ TEST_F(AppStateTest, resumeSessionShouldOpenNTPNoTabSwitcher) {
[[[tabSwitcher stub] andReturnValue:@NO] openNewTabFromTabSwitcher]; [[[tabSwitcher stub] andReturnValue:@NO] openNewTabFromTabSwitcher];
ScopedKeyWindow scopedKeyWindow; ScopedKeyWindow scopedKeyWindow;
AppState* appState = AppState* appState = getAppStateWithOpenNTP(YES, scopedKeyWindow.Get());
getAppStateWithOpenNTPAndIncognitoBlock(YES, scopedKeyWindow.Get());
// incognitoBlocker.
ASSERT_EQ(NSUInteger(1), [scopedKeyWindow.Get() subviews].count);
// Action. // Action.
[appState resumeSessionWithTabOpener:tabOpener [appState resumeSessionWithTabOpener:tabOpener
...@@ -790,8 +755,7 @@ TEST_F(AppStateTest, applicationWillEnterForeground) { ...@@ -790,8 +755,7 @@ TEST_F(AppStateTest, applicationWillEnterForeground) {
[[getStartupInformationMock() expect] expireFirstUserActionRecorder]; [[getStartupInformationMock() expect] expireFirstUserActionRecorder];
swizzleMetricsMediatorDisableReporting(); swizzleMetricsMediatorDisableReporting();
[getAppStateWithMock() applicationDidEnterBackground:application [getAppStateWithMock() applicationDidEnterBackground:application
memoryHelper:memoryHelper memoryHelper:memoryHelper];
incognitoContentVisible:YES];
void (^swizzleBlock)() = ^{ void (^swizzleBlock)() = ^{
}; };
...@@ -887,7 +851,7 @@ TEST_F(AppStateTest, ...@@ -887,7 +851,7 @@ TEST_F(AppStateTest,
EXPECT_TRUE([getAppStateWithMock() isInSafeMode]); EXPECT_TRUE([getAppStateWithMock() isInSafeMode]);
} }
// Tests that -applicationDidEnterBackground creates an incognito blocker. // Tests that -applicationDidEnterBackground calls the metrics mediator.
TEST_F(AppStateTest, applicationDidEnterBackgroundIncognito) { TEST_F(AppStateTest, applicationDidEnterBackgroundIncognito) {
// Setup. // Setup.
ScopedKeyWindow scopedKeyWindow; ScopedKeyWindow scopedKeyWindow;
...@@ -910,17 +874,13 @@ TEST_F(AppStateTest, applicationDidEnterBackgroundIncognito) { ...@@ -910,17 +874,13 @@ TEST_F(AppStateTest, applicationDidEnterBackgroundIncognito) {
swizzleMetricsMediatorDisableReporting(); swizzleMetricsMediatorDisableReporting();
ASSERT_EQ(NSUInteger(0), [scopedKeyWindow.Get() subviews].count);
// Action. // Action.
[appState applicationDidEnterBackground:application [appState applicationDidEnterBackground:application
memoryHelper:memoryHelper memoryHelper:memoryHelper];
incognitoContentVisible:YES];
// Tests. // Tests.
EXPECT_OCMOCK_VERIFY(startupInformation); EXPECT_OCMOCK_VERIFY(startupInformation);
EXPECT_TRUE(metricsMediatorHasBeenCalled()); EXPECT_TRUE(metricsMediatorHasBeenCalled());
EXPECT_EQ(NSUInteger(1), [scopedKeyWindow.Get() subviews].count);
} }
// Tests that -applicationDidEnterBackground do nothing if the application has // Tests that -applicationDidEnterBackground do nothing if the application has
...@@ -941,46 +901,8 @@ TEST_F(AppStateTest, applicationDidEnterBackgroundStageBackground) { ...@@ -941,46 +901,8 @@ TEST_F(AppStateTest, applicationDidEnterBackgroundStageBackground) {
// Action. // Action.
[getAppStateWithRealWindow(scopedKeyWindow.Get()) [getAppStateWithRealWindow(scopedKeyWindow.Get())
applicationDidEnterBackground:application applicationDidEnterBackground:application
memoryHelper:memoryHelper memoryHelper:memoryHelper];
incognitoContentVisible:YES];
// Tests.
EXPECT_EQ(NSUInteger(0), [scopedKeyWindow.Get() subviews].count);
}
// Tests that -applicationDidEnterBackground does not create an incognito
// blocker if there is no incognito tab.
TEST_F(AppStateTest, applicationDidEnterBackgroundNoIncognitoBlocker) {
// Setup.
ScopedKeyWindow scopedKeyWindow;
id application = [OCMockObject niceMockForClass:[UIApplication class]];
id memoryHelper = [OCMockObject mockForClass:[MemoryWarningHelper class]];
StubBrowserInterfaceProvider* interfaceProvider = getInterfaceProvider();
std::unique_ptr<Browser> browser = std::make_unique<TestBrowser>();
id startupInformation = getStartupInformationMock();
id browserLauncher = getBrowserLauncherMock();
BrowserInitializationStageType stage = INITIALIZATION_STAGE_FOREGROUND;
AppState* appState = getAppStateWithRealWindow(scopedKeyWindow.Get());
[[startupInformation expect] expireFirstUserActionRecorder];
[[[memoryHelper stub] andReturnValue:@0] foregroundMemoryWarningCount];
interfaceProvider.incognitoInterface.browser = browser.get();
[[[browserLauncher stub] andReturnValue:@(stage)] browserInitializationStage];
[[[browserLauncher stub] andReturn:interfaceProvider] interfaceProvider];
swizzleMetricsMediatorDisableReporting();
ASSERT_EQ(NSUInteger(0), [scopedKeyWindow.Get() subviews].count);
// Action.
[appState applicationDidEnterBackground:application
memoryHelper:memoryHelper
incognitoContentVisible:NO];
// Tests. // Tests.
EXPECT_OCMOCK_VERIFY(startupInformation);
EXPECT_TRUE(metricsMediatorHasBeenCalled());
EXPECT_EQ(NSUInteger(0), [scopedKeyWindow.Get() subviews].count); EXPECT_EQ(NSUInteger(0), [scopedKeyWindow.Get() subviews].count);
} }
...@@ -193,9 +193,7 @@ ...@@ -193,9 +193,7 @@
} }
[_appState applicationDidEnterBackground:application [_appState applicationDidEnterBackground:application
memoryHelper:_memoryHelper memoryHelper:_memoryHelper];
incognitoContentVisible:self.sceneController
.incognitoContentVisible];
} }
// Called when returning to the foreground. // Called when returning to the foreground.
...@@ -283,9 +281,7 @@ ...@@ -283,9 +281,7 @@
DCHECK(IsSceneStartupSupported()); DCHECK(IsSceneStartupSupported());
if (@available(iOS 13, *)) { if (@available(iOS 13, *)) {
[_appState applicationDidEnterBackground:UIApplication.sharedApplication [_appState applicationDidEnterBackground:UIApplication.sharedApplication
memoryHelper:_memoryHelper memoryHelper:_memoryHelper];
incognitoContentVisible:self.sceneController
.incognitoContentVisible];
} }
} }
......
...@@ -36,6 +36,20 @@ source_set("scene_state_header") { ...@@ -36,6 +36,20 @@ source_set("scene_state_header") {
] ]
} }
source_set("incognito_blocker_scene_agent") {
configs += [ "//build/config/compiler:enable_arc" ]
sources = [
"incognito_blocker_scene_agent.h",
"incognito_blocker_scene_agent.mm",
]
deps = [
":scene_state_header",
"//base",
"//ios/chrome/browser/ui/util:multiwindow_util",
]
frameworks = [ "UIKit.framework" ]
}
source_set("scene") { source_set("scene") {
configs += [ "//build/config/compiler:enable_arc" ] configs += [ "//build/config/compiler:enable_arc" ]
sources = [ sources = [
...@@ -52,6 +66,7 @@ source_set("scene") { ...@@ -52,6 +66,7 @@ source_set("scene") {
] ]
deps = [ deps = [
":incognito_blocker_scene_agent",
":main", ":main",
":scene_guts", ":scene_guts",
":scene_testing", ":scene_testing",
...@@ -193,10 +208,12 @@ source_set("unit_tests") { ...@@ -193,10 +208,12 @@ source_set("unit_tests") {
testonly = true testonly = true
sources = [ sources = [
"browser_view_wrangler_unittest.mm", "browser_view_wrangler_unittest.mm",
"incognito_blocker_scene_agent_unittest.mm",
"scene_controller_unittest.mm", "scene_controller_unittest.mm",
"scene_state_browser_agent_unittest.mm", "scene_state_browser_agent_unittest.mm",
] ]
deps = [ deps = [
":incognito_blocker_scene_agent",
":main", ":main",
":scene", ":scene",
"//base", "//base",
......
// Copyright 2020 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.
#ifndef IOS_CHROME_BROWSER_UI_MAIN_INCOGNITO_BLOCKER_SCENE_AGENT_H_
#define IOS_CHROME_BROWSER_UI_MAIN_INCOGNITO_BLOCKER_SCENE_AGENT_H_
#import "ios/chrome/browser/ui/main/scene_state.h"
// A scene agent that shows a UI overlay to prevent incognito content from being
// shown in the task switcher.
@interface IncognitoBlockerSceneAgent : NSObject <SceneAgent>
@end
#endif // IOS_CHROME_BROWSER_UI_MAIN_INCOGNITO_BLOCKER_SCENE_AGENT_H_
// Copyright 2020 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.
#import "ios/chrome/browser/ui/main/incognito_blocker_scene_agent.h"
#include "base/mac/bundle_locations.h"
#include "base/mac/foundation_util.h"
#import "ios/chrome/browser/ui/util/multi_window_support.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface IncognitoBlockerSceneAgent () <SceneStateObserver>
// Scene to which this agent is attached.
// Implements the setter from SceneAgent protocol.
@property(nonatomic, weak) SceneState* sceneState;
// Interstitial view used to block any incognito tabs after backgrounding.
@property(nonatomic, strong) UIView* overlayView;
@end
@implementation IncognitoBlockerSceneAgent
#pragma mark - properties
- (UIView*)overlayView {
if (!_overlayView) {
// Cover the largest area potentially shown in the app switcher, in case
// the screenshot is reused in a different orientation or size class.
CGRect screenBounds = [[UIScreen mainScreen] bounds];
CGFloat maxDimension =
std::max(CGRectGetWidth(screenBounds), CGRectGetHeight(screenBounds));
_overlayView = [[UIView alloc]
initWithFrame:CGRectMake(0, 0, maxDimension, maxDimension)];
UIViewController* launchScreenController =
[self loadLaunchScreenControllerFromBundle];
[_overlayView addSubview:launchScreenController.view];
_overlayView.autoresizingMask =
UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
}
return _overlayView;
}
#pragma mark - SceneAgent
- (void)setSceneState:(SceneState*)sceneState {
DCHECK(!_sceneState);
_sceneState = sceneState;
[sceneState addObserver:self];
}
#pragma mark - SceneStateObserver
- (void)sceneState:(SceneState*)sceneState
transitionedToActivationLevel:(SceneActivationLevel)level {
if (level == SceneActivationLevelBackground &&
sceneState.incognitoContentVisible) {
// If the current BVC is incognito, or if we are in the tab switcher and
// there are incognito tabs visible, place a full screen view containing
// the switcher background to hide any incognito content.
[self showOverlay];
}
if (level >= SceneActivationLevelForegroundInactive) {
[self hideOverlay];
}
}
#pragma mark - private
- (void)showOverlay {
NSArray<UIWindow*>* windows = nil;
if (@available(iOS 13, *)) {
windows = self.sceneState.scene.windows;
} else {
windows = UIApplication.sharedApplication.windows;
}
// Adding |self.overlayView| to sceneState.window won't cover overlay windows
// such as fullscreen video. Instead use the topmost window.
NSArray<UIWindow*>* sortedWindows =
[windows sortedArrayUsingComparator:^NSComparisonResult(UIWindow* w1,
UIWindow* w2) {
if (w1.windowLevel == w2.windowLevel) {
return NSOrderedSame;
}
return w1.windowLevel < w2.windowLevel ? NSOrderedAscending
: NSOrderedDescending;
}];
UIWindow* topWindow = sortedWindows.lastObject;
[topWindow addSubview:self.overlayView];
}
- (void)hideOverlay {
if (!self.overlayView) {
return;
}
[self.overlayView removeFromSuperview];
// Get rid of the view to save memory.
self.overlayView = nil;
}
- (UIViewController*)loadLaunchScreenControllerFromBundle {
NSBundle* mainBundle = base::mac::FrameworkBundle();
NSArray* topObjects = [mainBundle loadNibNamed:@"LaunchScreen"
owner:self
options:nil];
UIViewController* launchScreenController =
base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
launchScreenController.view.autoresizingMask =
UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
return launchScreenController;
}
@end
// Copyright 2020 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.
#import "ios/chrome/browser/ui/main/incognito_blocker_scene_agent.h"
#include "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
class IncognitoBlockerSceneAgentTest : public PlatformTest {
public:
IncognitoBlockerSceneAgentTest()
: scene_state_([[SceneState alloc] initWithAppState:nil]),
scene_state_mock_(OCMPartialMock(scene_state_)),
agent_([[IncognitoBlockerSceneAgent alloc] init]) {
if (@available(iOS 13, *)) {
scene_mock_ = OCMClassMock([UIWindowScene class]);
scene_state_.scene = scene_mock_;
OCMStub([scene_state_mock_ scene]).andReturn(scene_mock_);
}
agent_.sceneState = scene_state_;
}
protected:
// The scene state that the agent works with.
SceneState* scene_state_;
// Mock for scene_state_'s underlying UIWindowScene.
id scene_mock_;
// Partial mock for stubbing scene_state_'s methods
id scene_state_mock_;
// The tested agent
IncognitoBlockerSceneAgent* agent_;
};
TEST_F(IncognitoBlockerSceneAgentTest, ShowIncognitoBlocker) {
// Pretend there's only one window on this scene.
UIWindow* window = [[UIWindow alloc] init];
id applicationWindowMock = nil;
if (@available(iOS 13, *)) {
OCMStub([scene_mock_ windows]).andReturn(@[ window ]);
} else {
applicationWindowMock = OCMPartialMock(UIApplication.sharedApplication);
OCMStub([applicationWindowMock windows]).andReturn(@[ window ]);
}
// Prepare to go to background with some incognito content.
scene_state_.activationLevel = SceneActivationLevelForegroundActive;
scene_state_.incognitoContentVisible = YES;
EXPECT_EQ(window.subviews.count, 0u);
// Upon background, the blocker should be added.
scene_state_.activationLevel = SceneActivationLevelBackground;
EXPECT_EQ(window.subviews.count, 1u);
// Upon foreground, the blocker should be removed.
scene_state_.activationLevel = SceneActivationLevelForegroundActive;
EXPECT_EQ(window.subviews.count, 0u);
// No blocker should be added when no incognito content is shown.
scene_state_.incognitoContentVisible = NO;
scene_state_.activationLevel = SceneActivationLevelBackground;
EXPECT_EQ(window.subviews.count, 0u);
[applicationWindowMock stopMocking];
}
// Test that when there are multiple windows, for example when there's a
// fullscreen video playing in incognito in a scene, the overlay is added to it.
TEST_F(IncognitoBlockerSceneAgentTest, ShowBlockerOnTopWindow) {
// Pretend there's two windows on this scene.
UIWindow* bottomWindow = [[UIWindow alloc] init];
bottomWindow.windowLevel = UIWindowLevelNormal;
UIWindow* topWindow = [[UIWindow alloc] init];
topWindow.windowLevel = UIWindowLevelStatusBar + 1;
NSArray* windows = @[ topWindow, bottomWindow ];
id applicationWindowMock = nil;
if (@available(iOS 13, *)) {
OCMStub([scene_mock_ windows]).andReturn(windows);
} else {
applicationWindowMock = OCMPartialMock(UIApplication.sharedApplication);
OCMStub([applicationWindowMock windows]).andReturn(windows);
}
// Prepare to go to background with some incognito content.
scene_state_.activationLevel = SceneActivationLevelForegroundActive;
scene_state_.incognitoContentVisible = YES;
EXPECT_EQ(topWindow.subviews.count, 0u);
EXPECT_EQ(bottomWindow.subviews.count, 0u);
// Upon background, the blocker should be added only to the topmost window.
scene_state_.activationLevel = SceneActivationLevelBackground;
EXPECT_EQ(topWindow.subviews.count, 1u);
EXPECT_EQ(bottomWindow.subviews.count, 0u);
// Upon foreground, the blocker should be removed.
scene_state_.activationLevel = SceneActivationLevelForegroundActive;
EXPECT_EQ(topWindow.subviews.count, 0u);
EXPECT_EQ(bottomWindow.subviews.count, 0u);
[applicationWindowMock stopMocking];
}
} // anonymous namespace
...@@ -33,10 +33,6 @@ ...@@ -33,10 +33,6 @@
// The state of the scene controlled by this object. // The state of the scene controlled by this object.
@property(nonatomic, weak, readonly) SceneState* sceneState; @property(nonatomic, weak, readonly) SceneState* sceneState;
// Returns whether the scene is showing or partially showing the
// incognito panel.
@property(nonatomic, assign, readonly) BOOL incognitoContentVisible;
// A temporary pointer to MainController. // A temporary pointer to MainController.
@property(nonatomic, weak) id<MainControllerGuts> mainController; @property(nonatomic, weak) id<MainControllerGuts> mainController;
......
...@@ -61,6 +61,7 @@ ...@@ -61,6 +61,7 @@
#import "ios/chrome/browser/ui/main/browser_interface_provider.h" #import "ios/chrome/browser/ui/main/browser_interface_provider.h"
#import "ios/chrome/browser/ui/main/browser_view_wrangler.h" #import "ios/chrome/browser/ui/main/browser_view_wrangler.h"
#import "ios/chrome/browser/ui/main/default_browser_scene_agent.h" #import "ios/chrome/browser/ui/main/default_browser_scene_agent.h"
#import "ios/chrome/browser/ui/main/incognito_blocker_scene_agent.h"
#import "ios/chrome/browser/ui/main/ui_blocker_scene_agent.h" #import "ios/chrome/browser/ui/main/ui_blocker_scene_agent.h"
#import "ios/chrome/browser/ui/scoped_ui_blocker/scoped_ui_blocker.h" #import "ios/chrome/browser/ui/scoped_ui_blocker/scoped_ui_blocker.h"
#import "ios/chrome/browser/ui/settings/settings_navigation_controller.h" #import "ios/chrome/browser/ui/settings/settings_navigation_controller.h"
...@@ -235,6 +236,7 @@ const char kMultiWindowOpenInNewWindowHistogram[] = ...@@ -235,6 +236,7 @@ const char kMultiWindowOpenInNewWindowHistogram[] =
// Add agents. // Add agents.
[_sceneState addAgent:[[UIBlockerSceneAgent alloc] init]]; [_sceneState addAgent:[[UIBlockerSceneAgent alloc] init]];
[_sceneState addAgent:[[IncognitoBlockerSceneAgent alloc] init]];
} }
return self; return self;
} }
...@@ -1008,7 +1010,7 @@ const char kMultiWindowOpenInNewWindowHistogram[] = ...@@ -1008,7 +1010,7 @@ const char kMultiWindowOpenInNewWindowHistogram[] =
} }
- (void)setIncognitoContentVisible:(BOOL)incognitoContentVisible { - (void)setIncognitoContentVisible:(BOOL)incognitoContentVisible {
_incognitoContentVisible = incognitoContentVisible; self.sceneState.incognitoContentVisible = incognitoContentVisible;
} }
- (void)startVoiceSearch { - (void)startVoiceSearch {
......
...@@ -32,4 +32,14 @@ class SceneControllerTest : public PlatformTest { ...@@ -32,4 +32,14 @@ class SceneControllerTest : public PlatformTest {
// open in new window coming from ios dock. 'Dock' is considered the default // open in new window coming from ios dock. 'Dock' is considered the default
// when the new window opening request is external to chrome and unknown. // when the new window opening request is external to chrome and unknown.
// Tests that scene controller updates scene state's incognitoContentVisible
// when the relevant application command is called.
TEST_F(SceneControllerTest, UpdatesIncognitoContentVisibility) {
EXPECT_FALSE(scene_state_.incognitoContentVisible);
[scene_controller_ setIncognitoContentVisible:YES];
EXPECT_TRUE(scene_state_.incognitoContentVisible);
[scene_controller_ setIncognitoContentVisible:NO];
EXPECT_FALSE(scene_state_.incognitoContentVisible);
}
} // namespace } // namespace
...@@ -82,6 +82,10 @@ typedef NS_ENUM(NSUInteger, SceneActivationLevel) { ...@@ -82,6 +82,10 @@ typedef NS_ENUM(NSUInteger, SceneActivationLevel) {
// WindowActivityRestoredOrigin. // WindowActivityRestoredOrigin.
@property(nonatomic, assign) WindowActivityOrigin currentOrigin; @property(nonatomic, assign) WindowActivityOrigin currentOrigin;
// YES if some incognito content is visible, for example an incognito tab or the
// incognito tab switcher.
@property(nonatomic) BOOL incognitoContentVisible;
// Window for the associated scene, if any. // Window for the associated scene, if any.
@property(nonatomic, strong) UIWindow* window; @property(nonatomic, strong) UIWindow* window;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment