blob: 7d0114d8ac1df70dc6e819eaacf640373f0cfc7f [file] [log] [blame]
// Copyright 2012 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/app_startup_parameters.h"
#import "base/feature_list.h"
#import "base/metrics/histogram_macros.h"
#import "ios/chrome/app/startup/app_startup_utils.h"
#import "ios/chrome/browser/shared/model/url/chrome_url_constants.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/public/provider/chrome/browser/application_mode_fetcher/application_mode_fetcher_api.h"
#import "net/base/apple/url_conversions.h"
#import "net/base/url_util.h"
#import "url/gurl.h"
namespace {
// This enum is used for Histogram. Items should not be removed or reordered and
// this enum should be kept synced with `IOSAppModeFetchingOutcomeType` in
// histograms.xml. The four states correspond to:
// - APP_MODE_FETCHED_INCOGNITO: The application mode fetching response was
// successful and the fetched app mode is incognito.
// - APP_MODE_FETCHED_NON_INCOGNITO: The application mode fetching response was
// successful and the fetched app mode is non-incognito.
// - APP_MODE_FETCHED_FAILURE: The application mode fetching results in an error
// (different from a time out error)
// - APP_MODE_FETCHED_TIME_OUT: The application mode fetching results in a time
// out error.
enum class AppModeFetchingOutcomeType {
kIncognito,
kNonIncognito,
kFailure,
kTimeOut,
kMaxValue = kTimeOut
};
// Returns whether the application should be requested based on the id of the
// app requesting the opening of an external link.
bool ShouldRequestAppMode(NSString* app_id) {
if (IsYoutubeIncognitoTargetAllEnabled()) {
return true;
}
if (IsYoutubeIncognitoTargetFirstPartyEnabled()) {
return IsCallerAppFirstParty(app_id);
}
return IsCallerAppAllowListed(app_id);
}
// Returns a `ApplicationModeRequestStatus` based on the source `app_ID`, the
// `ApplicationModeRequestStatus` and if the mode is forced or not.
ApplicationModeRequestStatus ApplicationModeAvailability(
NSString* app_id,
ApplicationModeForTabOpening mode,
bool application_mode_forced) {
// The `ApplicationModeRequestStatus` is considered available if, either it
// can't be requested (based on the source app requesting the opening of the
// URL) or it is incognito forced.
if ((application_mode_forced &&
mode == ApplicationModeForTabOpening::INCOGNITO) ||
!ShouldRequestAppMode(app_id)) {
return ApplicationModeRequestStatus::kAvailable;
}
return ApplicationModeRequestStatus::kUnavailable;
}
} // namespace
@implementation AppStartupParameters {
GURL _externalURL;
GURL _completeURL;
std::vector<GURL> _URLs;
ApplicationModeRequestStatus _applicationModeRequestStatus;
NSString* _sourceAppID;
// The mode in which the tab must be opened. Defaults to UNDETERMINED.
ApplicationModeForTabOpening _applicationMode;
// Whether the application mode is forced or not (for example incognito mode
// or regular mode were forced based on the profile prefs).
BOOL _forceApplicationMode;
// An array of blocks to execute once the `applicationMode` is available.
NSMutableArray<AppModeRequestBlock>* _pendingBlocks;
}
@synthesize inputURLs = _inputURLs;
@synthesize postOpeningAction = _postOpeningAction;
@synthesize textQuery = _textQuery;
- (const GURL&)externalURL {
return _externalURL;
}
- (const GURL&)completeURL {
return _completeURL;
}
- (instancetype)initWithExternalURL:(const GURL&)externalURL
completeURL:(const GURL&)completeURL
applicationMode:(ApplicationModeForTabOpening)mode
forceApplicationMode:(BOOL)forceApplicationMode {
self = [super init];
if (self) {
_externalURL = externalURL;
_completeURL = completeURL;
_applicationMode = mode;
_applicationModeRequestStatus = ApplicationModeRequestStatus::kAvailable;
_forceApplicationMode = forceApplicationMode;
}
return self;
}
- (instancetype)initWithExternalURL:(const GURL&)externalURL
completeURL:(const GURL&)completeURL
sourceAppID:(NSString*)sourceAppID
applicationMode:(ApplicationModeForTabOpening)mode
forceApplicationMode:(BOOL)forceApplicationMode {
self = [super init];
if (self) {
_externalURL = externalURL;
_completeURL = completeURL;
_sourceAppID = [sourceAppID copy];
_applicationMode = mode;
_applicationModeRequestStatus =
ApplicationModeAvailability(sourceAppID, mode, forceApplicationMode);
_forceApplicationMode = forceApplicationMode;
}
return self;
}
- (instancetype)initWithURLs:(const std::vector<GURL>&)URLs
applicationMode:(ApplicationModeForTabOpening)mode
forceApplicationMode:(BOOL)forceApplicationMode {
if (URLs.empty()) {
self = [self initWithExternalURL:GURL(kChromeUINewTabURL)
completeURL:GURL(kChromeUINewTabURL)
applicationMode:mode
forceApplicationMode:forceApplicationMode];
} else {
self = [self initWithExternalURL:URLs.front()
completeURL:URLs.front()
applicationMode:mode
forceApplicationMode:forceApplicationMode];
}
if (self) {
_URLs = URLs;
}
return self;
}
- (NSString*)description {
NSMutableString* description =
[NSMutableString stringWithFormat:@"AppStartupParameters: %s",
_externalURL.spec().c_str()];
if (_applicationMode == ApplicationModeForTabOpening::INCOGNITO) {
[description appendString:@", should launch in incognito"];
}
switch (self.postOpeningAction) {
case START_QR_CODE_SCANNER:
[description appendString:@", should launch QR scanner"];
break;
case START_LENS_FROM_APP_ICON_LONG_PRESS:
case START_LENS_FROM_HOME_SCREEN_WIDGET:
case START_LENS_FROM_SPOTLIGHT:
case START_LENS_FROM_INTENTS:
[description appendString:@", should launch Lens"];
break;
case START_VOICE_SEARCH:
[description appendString:@", should launch voice search"];
break;
case FOCUS_OMNIBOX:
[description appendString:@", should focus omnibox"];
break;
case OPEN_READING_LIST:
[description appendString:@", should open reading list"];
break;
case OPEN_BOOKMARKS:
[description appendString:@", should open bookmarks"];
break;
case OPEN_RECENT_TABS:
[description appendString:@", should open recent tabs"];
break;
case OPEN_TAB_GRID:
[description appendString:@", should open tab grid"];
break;
case SET_CHROME_DEFAULT_BROWSER:
[description appendString:@", should open set chrome default browser"];
break;
case VIEW_HISTORY:
[description appendString:@", should open history"];
break;
case OPEN_PAYMENT_METHODS:
[description appendString:@", should open payment methods"];
break;
case RUN_SAFETY_CHECK:
[description appendString:@", should run safety check"];
break;
case MANAGE_PASSWORDS:
[description appendString:@", should open manage passwords setting page"];
break;
case MANAGE_SETTINGS:
[description appendString:@", should open settings page"];
break;
case OPEN_LATEST_TAB:
[description appendString:@", should resume latest tab"];
break;
case OPEN_CLEAR_BROWSING_DATA_DIALOG:
[description appendString:@", should open Clear Browsing Data dialog"];
break;
case ADD_BOOKMARKS:
[description appendString:@", should add bookmarks"];
break;
case ADD_READING_LIST_ITEMS:
[description appendString:@", should add reading list items"];
break;
default:
break;
}
return description;
}
- (void)setPostOpeningAction:(TabOpeningPostOpeningAction)action {
DCHECK([self isValidPostOpeningAction:action]);
_postOpeningAction = action;
}
#pragma mark - Private methods
- (BOOL)isValidPostOpeningAction:(TabOpeningPostOpeningAction)action {
switch (action) {
// Post opening actions that are allowed on any URL.
case NO_ACTION:
case SHOW_DEFAULT_BROWSER_SETTINGS:
case EXTERNAL_ACTION_SHOW_BROWSER_SETTINGS:
case SEARCH_PASSWORDS:
return YES;
// Lens action are valid on empty URLs, in addition to
// the URLs where all actions are valid.
case START_LENS_FROM_APP_ICON_LONG_PRESS:
case START_LENS_FROM_HOME_SCREEN_WIDGET:
case START_LENS_FROM_SPOTLIGHT:
case OPEN_LATEST_TAB:
case START_LENS_FROM_INTENTS:
if (_externalURL.is_empty()) {
return YES;
}
[[fallthrough]];
// Other actions are only valid on NTP;
default:
return _externalURL == GURL(kChromeUINewTabURL);
}
}
- (void)requestApplicationModeWithBlock:(AppModeRequestBlock)block {
switch (_applicationModeRequestStatus) {
case ApplicationModeRequestStatus::kAvailable:
CHECK(!_pendingBlocks);
block(_applicationMode);
break;
case ApplicationModeRequestStatus::kRequested:
CHECK(_pendingBlocks);
[_pendingBlocks addObject:block];
break;
case ApplicationModeRequestStatus::kUnavailable: {
CHECK(!_pendingBlocks);
_pendingBlocks = [[NSMutableArray alloc] init];
[_pendingBlocks addObject:block];
_applicationModeRequestStatus = ApplicationModeRequestStatus::kRequested;
__weak __typeof(self) weakSelf = self;
auto fetching_response = base::BindOnce(
[](AppStartupParameters* startupParams,
base::TimeTicks startFetchTime, bool isAppSwitcherIncognito,
NSError* error) {
[startupParams handleApplicationModeRequest:isAppSwitcherIncognito
error:error
startFetchTime:startFetchTime];
},
weakSelf, base::TimeTicks::Now());
ios::provider::FetchApplicationMode(_externalURL, _sourceAppID,
std::move(fetching_response));
break;
}
}
}
- (void)setApplicationMode:(ApplicationModeForTabOpening)applicationMode
forceApplicationMode:(BOOL)forceApplicationMode {
if (forceApplicationMode) {
if (applicationMode == ApplicationModeForTabOpening::INCOGNITO) {
self.unexpectedMode =
_applicationMode == ApplicationModeForTabOpening::NORMAL;
} else if (applicationMode == ApplicationModeForTabOpening::NORMAL) {
self.unexpectedMode =
_applicationMode == ApplicationModeForTabOpening::INCOGNITO;
}
}
_applicationMode = applicationMode;
_forceApplicationMode = forceApplicationMode;
}
- (ApplicationModeForTabOpening)applicationMode {
CHECK(!base::FeatureList::IsEnabled(kChromeStartupParametersAsync));
return _applicationMode;
}
- (void)handleApplicationModeRequest:(BOOL)isAppSwitcherIncognito
error:(NSError*)error
startFetchTime:(base::TimeTicks)startFetchTime {
_applicationModeRequestStatus = ApplicationModeRequestStatus::kAvailable;
AppModeFetchingOutcomeType outcome =
AppModeFetchingOutcomeType::kNonIncognito;
if (isAppSwitcherIncognito) {
// When the `applicationMode` needs changing the error associated to the
// response must be nil.
CHECK(!error);
_applicationMode = ApplicationModeForTabOpening::APP_SWITCHER_INCOGNITO;
outcome = AppModeFetchingOutcomeType::kIncognito;
} else {
if (error &&
!IsYoutubeIncognitoErrorHandlingWithoutIncognitoInterstitialEnabled()) {
_applicationMode =
ApplicationModeForTabOpening::APP_SWITCHER_UNDETERMINED;
outcome = [error.domain isEqualToString:@"AppSwitcherTimeoutError"]
? AppModeFetchingOutcomeType::kTimeOut
: AppModeFetchingOutcomeType::kFailure;
}
}
for (AppModeRequestBlock block in _pendingBlocks) {
block(_applicationMode);
}
_pendingBlocks = nil;
UMA_HISTOGRAM_ENUMERATION("IOS.AppModeFetching.Outcome", outcome);
UMA_HISTOGRAM_TIMES("IOS.AppModeFetching.Duration",
base::TimeTicks::Now() - startFetchTime);
}
@end