chromium/ios/chrome/test/app/chrome_test_util.mm

// Copyright 2016 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/test/app/chrome_test_util.h"

#import "base/apple/foundation_util.h"
#import "base/check.h"
#import "base/ios/ios_util.h"
#import "base/test/ios/wait_util.h"
#import "components/crash/core/common/reporter_running_ios.h"
#import "components/metrics/metrics_pref_names.h"
#import "components/metrics/metrics_service.h"
#import "components/prefs/pref_member.h"
#import "components/previous_session_info/previous_session_info.h"
#import "components/previous_session_info/previous_session_info_private.h"
#import "ios/chrome/app/application_delegate/app_state.h"
#import "ios/chrome/app/application_delegate/metrics_mediator.h"
#import "ios/chrome/app/application_delegate/metrics_mediator_testing.h"
#import "ios/chrome/app/chrome_overlay_window.h"
#import "ios/chrome/app/main_application_delegate_testing.h"
#import "ios/chrome/app/main_controller.h"
#import "ios/chrome/browser/browser_view/ui_bundled/browser_view_controller.h"
#import "ios/chrome/browser/infobars/model/infobar_manager_impl.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_controller.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_controller_testing.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/browser/browser_list.h"
#import "ios/chrome/browser/shared/model/browser/browser_list_factory.h"
#import "ios/chrome/browser/shared/model/browser/browser_provider.h"
#import "ios/chrome/browser/shared/model/browser/browser_provider_interface.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/profile/profile_manager_ios.h"
#import "ios/chrome/browser/shared/public/commands/country_code_picker_commands.h"
#import "ios/chrome/browser/shared/public/commands/unit_conversion_commands.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/ui/main/bvc_container_view_controller.h"
#import "ios/chrome/common/crash_report/crash_helper.h"
#import "ios/chrome/test/app/tab_test_util.h"
#import "ios/web/public/navigation/navigation_context.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/test/fakes/fake_web_state_observer.h"
#import "net/base/apple/url_conversions.h"

// A subclass to pass instances of UIOpenURLContext to scene delegate during
// testing. UIOpenURLContext has no init available, so this can only be
// allocated. It uses obscuring properties for URL and options.
// TODO(crbug.com/40711105) Explore improving this which can become brittle.
@interface FakeUIOpenURLContext : UIOpenURLContext
@property(nonatomic, copy) NSURL* URL;
@property(nonatomic, strong) UISceneOpenURLOptions* options;
@end

@implementation FakeUIOpenURLContext
@synthesize URL = _URL;
@synthesize options = _options;
@end

namespace {

// Returns the original ChromeBrowserState if `incognito` is false. If
// `incognito` is true, returns an off-the-record ChromeBrowserState.
ChromeBrowserState* GetBrowserState(bool incognito) {
  const std::vector<ChromeBrowserState*> loaded_profiles =
      GetApplicationContext()->GetProfileManager()->GetLoadedProfiles();
  DCHECK(!loaded_profiles.empty());

  ChromeBrowserState* browser_state = loaded_profiles.front();
  DCHECK(!browser_state->IsOffTheRecord());

  return incognito ? browser_state->GetOffTheRecordChromeBrowserState()
                   : browser_state;
}

}  // namespace

namespace chrome_test_util {

MainController* GetMainController() {
  return MainApplicationDelegate.sharedMainController;
}

SceneState* GetForegroundActiveScene() {
  return MainApplicationDelegate.sharedAppState.foregroundActiveScene;
}

SceneController* GetForegroundActiveSceneController() {
  return MainApplicationDelegate.sharedAppState.foregroundActiveScene
      .controller;
}

NSUInteger RegularBrowserCount() {
  return static_cast<NSUInteger>(
      BrowserListFactory::GetForBrowserState(GetOriginalBrowserState())
          ->BrowsersOfType(BrowserList::BrowserType::kRegularAndInactive)
          .size());
}

ChromeBrowserState* GetOriginalBrowserState() {
  return GetBrowserState(false);
}

ChromeBrowserState* GetCurrentIncognitoBrowserState() {
  return GetBrowserState(true);
}

Browser* GetMainBrowser() {
  return GetForegroundActiveScene()
      .browserProviderInterface.mainBrowserProvider.browser;
}

Browser* GetCurrentBrowser() {
  return GetForegroundActiveScene()
      .browserProviderInterface.currentBrowserProvider.browser;
}

UIViewController* GetActiveViewController() {
  UIWindow* main_window = GetAnyKeyWindow();
  DCHECK([main_window isKindOfClass:[ChromeOverlayWindow class]]);
  UIViewController* main_view_controller = main_window.rootViewController;

  // The active view controller is either the TabGridViewController or its
  // presented BVC. The BVC is itself contained inside of a
  // BVCContainerViewController.
  UIViewController* active_view_controller =
      main_view_controller.presentedViewController
          ? main_view_controller.presentedViewController
          : main_view_controller;
  if ([active_view_controller
          isKindOfClass:[BVCContainerViewController class]]) {
    active_view_controller =
        base::apple::ObjCCastStrict<BVCContainerViewController>(
            active_view_controller)
            .currentBVC;
  }
  return active_view_controller;
}

id<ApplicationCommands,
   BrowserCommands,
   BrowserCoordinatorCommands,
   CountryCodePickerCommands,
   UnitConversionCommands>
HandlerForActiveBrowser() {
  return static_cast<
      id<ApplicationCommands, BrowserCommands, BrowserCoordinatorCommands,
         UnitConversionCommands, CountryCodePickerCommands>>(
      GetMainBrowser()->GetCommandDispatcher());
}

void RemoveAllInfoBars() {
  web::WebState* webState = GetCurrentWebState();
  if (webState) {
    infobars::InfoBarManager* info_bar_manager =
        InfoBarManagerImpl::FromWebState(webState);
    if (info_bar_manager) {
      info_bar_manager->RemoveAllInfoBars(false /* animate */);
    }
  }
}

void ClearPresentedState(ProceduralBlock completion) {
  [GetForegroundActiveSceneController()
      dismissModalDialogsWithCompletion:completion
                         dismissOmnibox:YES];
}

void PresentSignInAccountsViewControllerIfNecessary() {
  [GetForegroundActiveSceneController()
      presentSignInAccountsViewControllerIfNecessary];
}

void SetBooleanLocalStatePref(const char* pref_name, bool value) {
  DCHECK(GetApplicationContext());
  DCHECK(GetApplicationContext()->GetLocalState());
  BooleanPrefMember pref;
  pref.Init(pref_name, GetApplicationContext()->GetLocalState());
  pref.SetValue(value);
}

void SetBooleanUserPref(ChromeBrowserState* browser_state,
                        const char* pref_name,
                        bool value) {
  DCHECK(browser_state);
  DCHECK(browser_state->GetPrefs());
  BooleanPrefMember pref;
  pref.Init(pref_name, browser_state->GetPrefs());
  pref.SetValue(value);
}

void SetIntegerUserPref(ChromeBrowserState* browser_state,
                        const char* pref_name,
                        int value) {
  DCHECK(browser_state);
  DCHECK(browser_state->GetPrefs());
  IntegerPrefMember pref;
  pref.Init(pref_name, browser_state->GetPrefs());
  pref.SetValue(value);
}

bool IsMetricsRecordingEnabled() {
  DCHECK(GetApplicationContext());
  DCHECK(GetApplicationContext()->GetMetricsService());
  return GetApplicationContext()->GetMetricsService()->recording_active();
}

bool IsMetricsReportingEnabled() {
  DCHECK(GetApplicationContext());
  DCHECK(GetApplicationContext()->GetMetricsService());
  return GetApplicationContext()->GetMetricsService()->reporting_active();
}

bool IsCrashpadEnabled() {
  return crash_reporter::IsCrashpadRunning();
}

bool IsCrashpadReportingEnabled() {
  return crash_helper::common::UserEnabledUploading();
}

void OpenChromeFromExternalApp(const GURL& url) {
  UIScene* scene =
      [[UIApplication sharedApplication].connectedScenes anyObject];
  [scene.delegate sceneWillResignActive:scene];

  // FakeUIOpenURLContext cannot be instanciated, but it is just needed
  // for carrying the properties over to the scene delegate.
  FakeUIOpenURLContext* context = [FakeUIOpenURLContext alloc];
  context.URL = net::NSURLWithGURL(url);

  NSSet<UIOpenURLContext*>* URLContexts =
      [[NSSet alloc] initWithArray:@[ context ]];

  [scene.delegate scene:scene openURLContexts:URLContexts];
  [scene.delegate sceneDidBecomeActive:scene];
}

bool PurgeCachedWebViewPages() {
  web::WebState* web_state = chrome_test_util::GetCurrentWebState();
  const GURL last_committed_url = web_state->GetLastCommittedURL();

  web_state->SetWebUsageEnabled(false);
  web_state->SetWebUsageEnabled(true);

  auto observer = std::make_unique<web::FakeWebStateObserver>(web_state);
  web::FakeWebStateObserver* observer_ptr = observer.get();

  web_state->GetNavigationManager()->LoadIfNecessary();

  // The navigation triggered by LoadIfNecessary() may only start loading in the
  // next run loop, if it is for a web URL. The most reliable way to detect that
  // this navigation has finished is via the WebStateObserver.
  return base::test::ios::WaitUntilConditionOrTimeout(
      base::test::ios::kWaitForPageLoadTimeout, ^{
        return observer_ptr->did_finish_navigation_info() &&
               observer_ptr->did_finish_navigation_info()->context &&
               observer_ptr->did_finish_navigation_info()
                       ->context->GetWebState()
                       ->GetVisibleURL() == last_committed_url;
      });
}

}  // namespace chrome_test_util