chromium/ios/chrome/app/application_delegate/url_opener.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/app/application_delegate/url_opener.h"

#import <Foundation/Foundation.h>

#import "base/metrics/histogram_macros.h"
#import "components/prefs/pref_service.h"
#import "ios/chrome/app/application_delegate/app_state.h"
#import "ios/chrome/app/application_delegate/startup_information.h"
#import "ios/chrome/app/application_delegate/tab_opening.h"
#import "ios/chrome/app/application_delegate/url_opener_params.h"
#import "ios/chrome/app/startup/chrome_app_startup_parameters.h"
#import "ios/chrome/browser/policy/model/policy_util.h"
#import "ios/chrome/browser/shared/coordinator/scene/connection_information.h"
#import "ios/chrome/browser/shared/model/url/url_util.h"
#import "ios/chrome/browser/url_loading/model/url_loading_params.h"
#import "url/gurl.h"

namespace {
// Key of the UMA Startup.MobileSessionStartFromApps histogram.
const char* const kUMAMobileSessionStartFromAppsHistogram =
    "Startup.MobileSessionStartFromApps";
// Key of the UMA Startup.ShowDefaultPromoFromApps histogram.
const char* const kUMAShowDefaultPromoFromAppsHistogram =
    "Startup.ShowDefaultPromoFromApps";
}  // namespace

@implementation URLOpener

#pragma mark - ApplicationDelegate - URL Opening methods

+ (BOOL)openURL:(URLOpenerParams*)options
        applicationActive:(BOOL)applicationActive
                tabOpener:(id<TabOpening>)tabOpener
    connectionInformation:(id<ConnectionInformation>)connectionInformation
       startupInformation:(id<StartupInformation>)startupInformation
              prefService:(PrefService*)prefService
                initStage:(InitStage)initStage {
  NSURL* URL = options.URL;
  NSString* sourceApplication = options.sourceApplication;

  ChromeAppStartupParameters* params =
      [ChromeAppStartupParameters startupParametersWithURL:URL
                                         sourceApplication:sourceApplication];

  if (IsIncognitoModeDisabled(prefService)) {
    params.applicationMode = ApplicationModeForTabOpening::NORMAL;
  } else if (IsIncognitoModeForced(prefService)) {
    params.applicationMode = ApplicationModeForTabOpening::INCOGNITO;
  }

  MobileSessionCallerApp callerApp = [params callerApp];

  UMA_HISTOGRAM_ENUMERATION(kUMAMobileSessionStartFromAppsHistogram, callerApp,
                            MOBILE_SESSION_CALLER_APP_COUNT);

  if (params.postOpeningAction == SHOW_DEFAULT_BROWSER_SETTINGS) {
    UMA_HISTOGRAM_ENUMERATION(kUMAShowDefaultPromoFromAppsHistogram, callerApp,
                              MOBILE_SESSION_CALLER_APP_COUNT);
  }

  if (initStage == InitStageFirstRun) {
    UMA_HISTOGRAM_ENUMERATION("FirstRun.LaunchSource", [params launchSource],
                              first_run::LAUNCH_SIZE);
  } else if (applicationActive) {
    // The app is already active so the applicationDidBecomeActive: method will
    // never be called. Open the requested URL immediately and return YES if
    // the parsed URL was valid.
    if (params) {
      // As applicationDidBecomeActive: will not be called again,
      // _startupParameters will not include the command from openURL.
      // Pass the startup parameters from here.

      // TODO(crbug.com/40938699): Investigate why
      // connectionInformation.startupParamters can be not nil and what to do in
      // that case.
      [connectionInformation setStartupParameters:params];
      ProceduralBlock tabOpenedCompletion = ^{
        [connectionInformation setStartupParameters:nil];
      };

      // TODO(crbug.com/41443029): Exacly the same copy of this code is present
      // in
      // +[UserAcrtivityHandler
      // handleStartupParametersWithTabOpener:startupInformation:browserState:]

      GURL gurl;
      GURL virtualURL;
      if ([params completeURL].SchemeIsFile()) {
        // External URL will be loaded by WebState, which expects `completeURL`.
        // Omnibox however suppose to display `externalURL`, which is used as
        // virtual URL.
        gurl = [params completeURL];
        virtualURL = [params externalURL];
      } else {
        gurl = [params externalURL];
      }
      UrlLoadParams urlLoadParams = UrlLoadParams::InNewTab(gurl, virtualURL);

      ApplicationModeForTabOpening targetMode = params.applicationMode;
      // If the call is coming from the app, it should be opened in the current
      // mode to avoid changing mode.
      if (callerApp == CALLER_APP_GOOGLE_CHROME)
        targetMode = ApplicationModeForTabOpening::CURRENT;

      if (params.applicationMode != ApplicationModeForTabOpening::INCOGNITO &&
          [tabOpener URLIsOpenedInRegularMode:urlLoadParams.web_params.url]) {
        // Record metric.
      }

      [tabOpener
          dismissModalsAndMaybeOpenSelectedTabInMode:targetMode
                                   withUrlLoadParams:urlLoadParams
                                      dismissOmnibox:[params
                                                         postOpeningAction] !=
                                                     FOCUS_OMNIBOX
                                          completion:tabOpenedCompletion];

      return YES;
    }
    return NO;
  } else {
    // Don't record the first user action if application is not active.
    [startupInformation resetFirstUserActionRecorder];
  }

  connectionInformation.startupParameters = params;
  return connectionInformation.startupParameters != nil;
}

+ (void)handleLaunchOptions:(URLOpenerParams*)options
                  tabOpener:(id<TabOpening>)tabOpener
      connectionInformation:(id<ConnectionInformation>)connectionInformation
         startupInformation:(id<StartupInformation>)startupInformation
                   appState:(AppState*)appState
                prefService:(PrefService*)prefService {
  if (options.URL) {
    // This method is always called when the SceneState transitions to
    // SceneActivationLevelForegroundActive, and before the handling of
    // startupInformation is done.
    // Pass `NO` as active to avoid double processing.
    [URLOpener openURL:options
            applicationActive:NO
                    tabOpener:tabOpener
        connectionInformation:connectionInformation
           startupInformation:startupInformation
                  prefService:prefService
                    initStage:appState.initStage];
  }
}

@end