blob: 560d58568c16879e20b9ad1d2bd0cbf05ca9d1f0 [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.
#import "ios/chrome/browser/web/sad_tab_tab_helper.h"
#include <memory>
#include "base/test/scoped_task_environment.h"
#include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
#import "ios/chrome/browser/web/page_placeholder_tab_helper.h"
#import "ios/chrome/browser/web/sad_tab_tab_helper_delegate.h"
#import "ios/web/public/test/fakes/fake_navigation_context.h"
#import "ios/web/public/test/fakes/test_navigation_manager.h"
#import "ios/web/public/test/fakes/test_web_state.h"
#import "ios/web/public/web_state/ui/crw_generic_content_view.h"
#include "testing/gtest/include/gtest/gtest.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
// Delegate for testing.
@interface SadTabTabHelperTestDelegate : NSObject<SadTabTabHelperDelegate>
// |repeatedFailure| could be used by the delegate to display different types of
// SadTabs.
@property(nonatomic, assign) BOOL repeatedFailure;
// YES if SadTab is currently being shown.
@property(nonatomic, assign) BOOL showingSadTab;
@end
@implementation SadTabTabHelperTestDelegate
@synthesize repeatedFailure = _repeatedFailure;
- (void)sadTabTabHelper:(SadTabTabHelper*)tabHelper
presentSadTabForWebState:(web::WebState*)webState
repeatedFailure:(BOOL)repeatedFailure {
self.repeatedFailure = repeatedFailure;
self.showingSadTab = YES;
}
- (void)sadTabTabHelperDismissSadTab:(SadTabTabHelper*)tabHelper {
self.showingSadTab = NO;
}
- (void)sadTabTabHelper:(SadTabTabHelper*)tabHelper
didShowForRepeatedFailure:(BOOL)repeatedFailure {
self.repeatedFailure = repeatedFailure;
self.showingSadTab = YES;
}
- (void)sadTabTabHelperDidHide:(SadTabTabHelper*)tabHelper {
self.showingSadTab = NO;
}
@end
class SadTabTabHelperTest : public PlatformTest {
protected:
SadTabTabHelperTest()
: application_(OCMClassMock([UIApplication class])),
sad_tab_delegate_([[SadTabTabHelperTestDelegate alloc] init]) {
browser_state_ = TestChromeBrowserState::Builder().Build();
SadTabTabHelper::CreateForWebState(&web_state_, sad_tab_delegate_);
PagePlaceholderTabHelper::CreateForWebState(&web_state_);
OCMStub([application_ sharedApplication]).andReturn(application_);
// Setup navigation manager.
std::unique_ptr<web::TestNavigationManager> navigation_manager =
std::make_unique<web::TestNavigationManager>();
navigation_manager->SetBrowserState(browser_state_.get());
navigation_manager_ = navigation_manager.get();
web_state_.SetNavigationManager(std::move(navigation_manager));
web_state_.SetBrowserState(browser_state_.get());
}
SadTabTabHelper* tab_helper() {
return SadTabTabHelper::FromWebState(&web_state_);
}
~SadTabTabHelperTest() override { [application_ stopMocking]; }
base::test::ScopedTaskEnvironment environment_;
std::unique_ptr<ios::ChromeBrowserState> browser_state_;
web::TestWebState web_state_;
web::TestNavigationManager* navigation_manager_;
id application_;
SadTabTabHelperTestDelegate* sad_tab_delegate_;
};
// Tests that SadTab is not presented for not shown web states and navigation
// item is reloaded once web state was shown.
TEST_F(SadTabTabHelperTest, ReloadedWhenWebStateWasShown) {
OCMStub([application_ applicationState]).andReturn(UIApplicationStateActive);
web_state_.WasHidden();
// Delegate and TabHelper should not present a SadTab.
EXPECT_FALSE(tab_helper()->is_showing_sad_tab());
EXPECT_FALSE(sad_tab_delegate_.showingSadTab);
// Helper should get notified of render process failure,
// but Sad Tab should not be presented, because web state was not shown.
web_state_.OnRenderProcessGone();
EXPECT_FALSE(tab_helper()->is_showing_sad_tab());
EXPECT_FALSE(sad_tab_delegate_.showingSadTab);
// Navigation item must be reloaded once web state is shown.
EXPECT_FALSE(navigation_manager_->LoadIfNecessaryWasCalled());
web_state_.WasShown();
EXPECT_TRUE(PagePlaceholderTabHelper::FromWebState(&web_state_)
->will_add_placeholder_for_next_navigation());
EXPECT_TRUE(navigation_manager_->LoadIfNecessaryWasCalled());
}
// Tests that SadTab is not presented if app is in background and navigation
// item is reloaded once the app became active.
TEST_F(SadTabTabHelperTest, AppInBackground) {
OCMStub([application_ applicationState])
.andReturn(UIApplicationStateBackground);
web_state_.WasShown();
// Delegate and TabHelper should not present a SadTab.
EXPECT_FALSE(tab_helper()->is_showing_sad_tab());
EXPECT_FALSE(sad_tab_delegate_.showingSadTab);
// Helper should get notified of render process failure,
// but Sad Tab should not be presented, because application is backgrounded.
web_state_.OnRenderProcessGone();
EXPECT_FALSE(tab_helper()->is_showing_sad_tab());
EXPECT_FALSE(sad_tab_delegate_.showingSadTab);
// Navigation item must be reloaded once the app became active.
EXPECT_FALSE(navigation_manager_->LoadIfNecessaryWasCalled());
[[NSNotificationCenter defaultCenter]
postNotificationName:UIApplicationDidBecomeActiveNotification
object:nil];
EXPECT_TRUE(PagePlaceholderTabHelper::FromWebState(&web_state_)
->will_add_placeholder_for_next_navigation());
EXPECT_TRUE(navigation_manager_->LoadIfNecessaryWasCalled());
}
// Tests that SadTab is not presented if app is in inactive and navigation
// item is reloaded once the app became active.
TEST_F(SadTabTabHelperTest, AppIsInactive) {
OCMStub([application_ applicationState])
.andReturn(UIApplicationStateInactive);
web_state_.WasShown();
// Delegate and TabHelper should not present a SadTab.
EXPECT_FALSE(tab_helper()->is_showing_sad_tab());
EXPECT_FALSE(sad_tab_delegate_.showingSadTab);
// Helper should get notified of render process failure,
// but Sad Tab should not be presented, because application is inactive.
web_state_.OnRenderProcessGone();
EXPECT_FALSE(tab_helper()->is_showing_sad_tab());
EXPECT_FALSE(sad_tab_delegate_.showingSadTab);
// Navigation item must be reloaded once the app became active.
EXPECT_FALSE(navigation_manager_->LoadIfNecessaryWasCalled());
[[NSNotificationCenter defaultCenter]
postNotificationName:UIApplicationDidBecomeActiveNotification
object:nil];
EXPECT_TRUE(PagePlaceholderTabHelper::FromWebState(&web_state_)
->will_add_placeholder_for_next_navigation());
EXPECT_TRUE(navigation_manager_->LoadIfNecessaryWasCalled());
}
// Tests that SadTab is presented for shown web states.
TEST_F(SadTabTabHelperTest, Presented) {
OCMStub([application_ applicationState]).andReturn(UIApplicationStateActive);
web_state_.WasShown();
// Delegate and TabHelper should not present a SadTab.
EXPECT_FALSE(tab_helper()->is_showing_sad_tab());
EXPECT_FALSE(sad_tab_delegate_.showingSadTab);
// Helper should get notified of render process failure. And the delegate
// and TabHelper should present a SadTab.
web_state_.OnRenderProcessGone();
EXPECT_TRUE(tab_helper()->is_showing_sad_tab());
EXPECT_TRUE(sad_tab_delegate_.showingSadTab);
}
// Tests that SadTab is removed by the navigation.
TEST_F(SadTabTabHelperTest, SadTabClearedByNavigation) {
OCMStub([application_ applicationState]).andReturn(UIApplicationStateActive);
web_state_.WasShown();
// Delegate should not present a SadTab.
EXPECT_FALSE(sad_tab_delegate_.showingSadTab);
// Helper should get notified of render process failure. And the delegate
// and TabHelper should present a SadTab.
web_state_.OnRenderProcessGone();
EXPECT_TRUE(tab_helper()->is_showing_sad_tab());
ASSERT_TRUE(sad_tab_delegate_.showingSadTab);
// Novigation should clear the Sad Tab.
web::FakeNavigationContext context;
web_state_.OnNavigationStarted(&context);
EXPECT_FALSE(tab_helper()->is_showing_sad_tab());
EXPECT_FALSE(sad_tab_delegate_.showingSadTab);
}
// Tests that SadTab is presented after web state is shown and removed when web
// state is hidden.
TEST_F(SadTabTabHelperTest, HideAndShowPresented) {
OCMStub([application_ applicationState]).andReturn(UIApplicationStateActive);
web_state_.WasShown();
// Delegate and TabHelper should not present a SadTab.
EXPECT_FALSE(tab_helper()->is_showing_sad_tab());
EXPECT_FALSE(sad_tab_delegate_.showingSadTab);
// Helper should get notified of render process failure. And the delegate
// should present a SadTab.
web_state_.OnRenderProcessGone();
EXPECT_TRUE(sad_tab_delegate_.showingSadTab);
// Delegate does not show Sad Tab anymore, because WebState was hidden. But
// TabHelper still shows the Sad Tab.
web_state_.WasHidden();
EXPECT_TRUE(tab_helper()->is_showing_sad_tab());
EXPECT_FALSE(sad_tab_delegate_.showingSadTab);
web_state_.WasShown();
EXPECT_TRUE(sad_tab_delegate_.showingSadTab);
EXPECT_FALSE(sad_tab_delegate_.repeatedFailure);
}
// Tests that SadTab is presented after web state is shown and removed when web
// state is hidden.
TEST_F(SadTabTabHelperTest, HideAndShowPresentedForRepeatedFailure) {
OCMStub([application_ applicationState]).andReturn(UIApplicationStateActive);
web_state_.WasShown();
// Delegate and TabHelper should not present a SadTab.
EXPECT_FALSE(tab_helper()->is_showing_sad_tab());
EXPECT_FALSE(sad_tab_delegate_.showingSadTab);
// Helper should get notified of render process failure. And the delegate
// should present a SadTab.
web_state_.OnRenderProcessGone();
EXPECT_TRUE(tab_helper()->is_showing_sad_tab());
EXPECT_TRUE(sad_tab_delegate_.showingSadTab);
// Simulate repeated failure.
web_state_.OnRenderProcessGone();
ASSERT_TRUE(sad_tab_delegate_.repeatedFailure);
// Delegate does not show Sad Tab anymore, because WebState was hidden. But
// TabHelper still shows the Sad Tab.
web_state_.WasHidden();
EXPECT_TRUE(tab_helper()->is_showing_sad_tab());
EXPECT_FALSE(sad_tab_delegate_.showingSadTab);
web_state_.WasShown();
EXPECT_TRUE(tab_helper()->is_showing_sad_tab());
EXPECT_TRUE(sad_tab_delegate_.showingSadTab);
EXPECT_TRUE(sad_tab_delegate_.repeatedFailure);
}
// Tests that repeated failures are communicated to the delegate correctly.
TEST_F(SadTabTabHelperTest, RepeatedFailuresShowCorrectUI) {
OCMStub([application_ applicationState]).andReturn(UIApplicationStateActive);
web_state_.WasShown();
// Helper should get notified of render process failure.
web_state_.OnRenderProcessGone();
// SadTab should be displayed and repeatedFailure should be NO.
EXPECT_TRUE(tab_helper()->is_showing_sad_tab());
EXPECT_TRUE(sad_tab_delegate_.showingSadTab);
EXPECT_FALSE(sad_tab_delegate_.repeatedFailure);
// On a second render process crash, SadTab should be displayed and
// repeatedFailure should be YES.
web_state_.OnRenderProcessGone();
EXPECT_TRUE(tab_helper()->is_showing_sad_tab());
EXPECT_TRUE(sad_tab_delegate_.showingSadTab);
EXPECT_TRUE(sad_tab_delegate_.repeatedFailure);
// All subsequent crashes should have repeatedFailure as YES.
web_state_.OnRenderProcessGone();
EXPECT_TRUE(tab_helper()->is_showing_sad_tab());
EXPECT_TRUE(sad_tab_delegate_.showingSadTab);
EXPECT_TRUE(sad_tab_delegate_.repeatedFailure);
}
// Tests that repeated failures can time out, and reset repeatedFailure to NO.
TEST_F(SadTabTabHelperTest, FailureInterval) {
OCMStub([application_ applicationState]).andReturn(UIApplicationStateActive);
// N.B. The test fixture web_state_ is not used for this test as a custom
// |repeat_failure_interval| is required.
std::unique_ptr<ios::ChromeBrowserState> browser_state =
TestChromeBrowserState::Builder().Build();
web::TestWebState web_state;
web_state.SetBrowserState(browser_state.get());
SadTabTabHelper::CreateForWebState(&web_state, 0.0f, sad_tab_delegate_);
PagePlaceholderTabHelper::CreateForWebState(&web_state);
web_state.WasShown();
// Helper should get notified of render process failure.
// SadTab should be shown.
web_state.OnRenderProcessGone();
// SadTab should be displayed and repeatedFailure should be NO.
EXPECT_TRUE(sad_tab_delegate_.showingSadTab);
EXPECT_FALSE(sad_tab_delegate_.repeatedFailure);
// On a second render process crash, SadTab should be displayed and
// repeatedFailure should still be NO due to the 0.0f interval timeout.
web_state.OnRenderProcessGone();
EXPECT_TRUE(sad_tab_delegate_.showingSadTab);
EXPECT_FALSE(sad_tab_delegate_.repeatedFailure);
}