// Copyright 2014 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/web_view/shell/shell_view_controller.h"
#import <MobileCoreServices/MobileCoreServices.h>
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#import "ios/web_view/shell/shell_auth_service.h"
#import "ios/web_view/shell/shell_autofill_delegate.h"
#import "ios/web_view/shell/shell_translation_delegate.h"
#import "ios/web_view/shell/shell_trusted_vault_provider.h"
// Externed accessibility identifier.
NSString* const kWebViewShellBackButtonAccessibilityLabel = @"Back";
NSString* const kWebViewShellForwardButtonAccessibilityLabel = @"Forward";
NSString* const kWebViewShellAddressFieldAccessibilityLabel = @"Address field";
NSString* const kWebViewShellJavaScriptDialogTextFieldAccessibilityIdentifier =
@"WebViewShellJavaScriptDialogTextFieldAccessibilityIdentifier";
@interface ShellViewController () <CWVAutofillDataManagerObserver,
CWVDownloadTaskDelegate,
CWVLeakCheckServiceObserver,
CWVNavigationDelegate,
CWVUIDelegate,
CWVSyncControllerDelegate,
UIScrollViewDelegate,
UITextFieldDelegate>
// Header containing navigation buttons and |field|.
@property(nonatomic, strong) UIView* headerBackgroundView;
// Header containing navigation buttons and |field|.
@property(nonatomic, strong) UIView* headerContentView;
// Button to navigate backwards.
@property(nonatomic, strong) UIButton* backButton;
// Button to navigate forwards.
@property(nonatomic, strong) UIButton* forwardButton;
// Button that either refresh the page or stops the page load.
@property(nonatomic, strong) UIButton* reloadOrStopButton;
// Button that shows the menu
@property(nonatomic, strong) UIButton* menuButton;
// Text field used for navigating to URLs.
@property(nonatomic, strong) UITextField* field;
// Container for |webView|.
@property(nonatomic, strong) UIView* contentView;
// Handles the autofill of the content displayed in |webView|.
@property(nonatomic, strong) ShellAutofillDelegate* autofillDelegate;
// Handles the translation of the content displayed in |webView|.
@property(nonatomic, strong) ShellTranslationDelegate* translationDelegate;
// The on-going download task if any.
@property(nonatomic, strong, nullable) CWVDownloadTask* downloadTask;
// The path to a local file which the download task is writing to.
@property(nonatomic, strong, nullable) NSString* downloadFilePath;
// A controller to show a "Share" menu for the downloaded file.
@property(nonatomic, strong, nullable)
UIDocumentInteractionController* documentInteractionController;
// Service that provides authentication to ChromeWebView.
@property(nonatomic, strong) ShellAuthService* authService;
// Provides trusted vault functions to ChromeWebView.
@property(nonatomic, strong) ShellTrustedVaultProvider* trustedVaultProvider;
// The newly opened popup windows e.g., by JavaScript function "window.open()",
// HTML "<a target='_blank'>".
@property(nonatomic, strong) NSMutableArray<CWVWebView*>* popupWebViews;
// A list of active leak checks. These map to a list of passwords since
// you can have multiple passwords that map to the same canonical leak check.
@property(nonatomic, strong)
NSMutableDictionary<CWVLeakCheckCredential*, NSMutableArray<CWVPassword*>*>*
pendingLeakChecks;
- (void)back;
- (void)forward;
- (void)reloadOrStop;
// Disconnects and release the |webView|.
- (void)removeWebView;
// Resets translate settings back to default.
- (void)resetTranslateSettings;
@end
@implementation ShellViewController
@synthesize autofillDelegate = _autofillDelegate;
@synthesize backButton = _backButton;
@synthesize contentView = _contentView;
@synthesize field = _field;
@synthesize forwardButton = _forwardButton;
@synthesize reloadOrStopButton = _reloadOrStopButton;
@synthesize menuButton = _menuButton;
@synthesize headerBackgroundView = _headerBackgroundView;
@synthesize headerContentView = _headerContentView;
@synthesize webView = _webView;
@synthesize translationDelegate = _translationDelegate;
@synthesize downloadTask = _downloadTask;
@synthesize downloadFilePath = _downloadFilePath;
@synthesize documentInteractionController = _documentInteractionController;
@synthesize authService = _authService;
@synthesize trustedVaultProvider = _trustedVaultProvider;
@synthesize popupWebViews = _popupWebViews;
@synthesize pendingLeakChecks = _pendingLeakChecks;
- (void)viewDidLoad {
[super viewDidLoad];
self.popupWebViews = [[NSMutableArray alloc] init];
// View creation.
self.headerBackgroundView = [[UIView alloc] init];
self.headerContentView = [[UIView alloc] init];
self.contentView = [[UIView alloc] init];
self.backButton = [[UIButton alloc] init];
self.forwardButton = [[UIButton alloc] init];
self.reloadOrStopButton = [[UIButton alloc] init];
self.menuButton = [[UIButton alloc] init];
self.field = [[UITextField alloc] init];
// View hierarchy.
[self.view addSubview:_headerBackgroundView];
[self.view addSubview:_contentView];
[_headerBackgroundView addSubview:_headerContentView];
[_headerContentView addSubview:_backButton];
[_headerContentView addSubview:_forwardButton];
[_headerContentView addSubview:_reloadOrStopButton];
[_headerContentView addSubview:_menuButton];
[_headerContentView addSubview:_field];
// Additional view setup.
_headerBackgroundView.backgroundColor = [UIColor colorWithRed:66.0 / 255.0
green:133.0 / 255.0
blue:244.0 / 255.0
alpha:1.0];
[_backButton setImage:[UIImage imageNamed:@"ic_back"]
forState:UIControlStateNormal];
_backButton.tintColor = [UIColor whiteColor];
[_backButton addTarget:self
action:@selector(back)
forControlEvents:UIControlEventTouchUpInside];
[_backButton addTarget:self
action:@selector(logBackStack)
forControlEvents:UIControlEventTouchDragOutside];
[_backButton setAccessibilityLabel:kWebViewShellBackButtonAccessibilityLabel];
[_forwardButton setImage:[UIImage imageNamed:@"ic_forward"]
forState:UIControlStateNormal];
_forwardButton.tintColor = [UIColor whiteColor];
[_forwardButton addTarget:self
action:@selector(forward)
forControlEvents:UIControlEventTouchUpInside];
[_forwardButton addTarget:self
action:@selector(logForwardStack)
forControlEvents:UIControlEventTouchDragOutside];
[_forwardButton
setAccessibilityLabel:kWebViewShellForwardButtonAccessibilityLabel];
_reloadOrStopButton.tintColor = [UIColor whiteColor];
[_reloadOrStopButton addTarget:self
action:@selector(reloadOrStop)
forControlEvents:UIControlEventTouchUpInside];
_menuButton.tintColor = [UIColor whiteColor];
[_menuButton setImage:[UIImage imageNamed:@"ic_menu"]
forState:UIControlStateNormal];
[_menuButton addTarget:self
action:@selector(showMainMenu)
forControlEvents:UIControlEventTouchUpInside];
_field.placeholder = @"Search or type URL";
_field.backgroundColor = [UIColor whiteColor];
_field.tintColor = _headerBackgroundView.backgroundColor;
[_field setContentHuggingPriority:UILayoutPriorityDefaultLow - 1
forAxis:UILayoutConstraintAxisHorizontal];
_field.delegate = self;
_field.layer.cornerRadius = 2.0;
_field.keyboardType = UIKeyboardTypeWebSearch;
_field.autocapitalizationType = UITextAutocapitalizationTypeNone;
_field.clearButtonMode = UITextFieldViewModeWhileEditing;
_field.autocorrectionType = UITextAutocorrectionTypeNo;
UIView* spacerView = [[UIView alloc] init];
spacerView.frame = CGRectMake(0, 0, 8, 8);
_field.leftViewMode = UITextFieldViewModeAlways;
_field.leftView = spacerView;
// Constraints.
_headerBackgroundView.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[_headerBackgroundView.topAnchor
constraintEqualToAnchor:self.view.topAnchor],
[_headerBackgroundView.leadingAnchor
constraintEqualToAnchor:self.view.leadingAnchor],
[_headerBackgroundView.trailingAnchor
constraintEqualToAnchor:self.view.trailingAnchor],
[_headerBackgroundView.bottomAnchor
constraintEqualToAnchor:_headerContentView.bottomAnchor],
]];
_headerContentView.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[_headerContentView.topAnchor
constraintEqualToAnchor:_headerBackgroundView.safeAreaLayoutGuide
.topAnchor],
[_headerContentView.leadingAnchor
constraintEqualToAnchor:_headerBackgroundView.safeAreaLayoutGuide
.leadingAnchor],
[_headerContentView.trailingAnchor
constraintEqualToAnchor:_headerBackgroundView.safeAreaLayoutGuide
.trailingAnchor],
[_headerContentView.heightAnchor constraintEqualToConstant:56.0],
]];
_contentView.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[_contentView.topAnchor
constraintEqualToAnchor:_headerBackgroundView.bottomAnchor],
[_contentView.leadingAnchor
constraintEqualToAnchor:self.view.leadingAnchor],
[_contentView.trailingAnchor
constraintEqualToAnchor:self.view.trailingAnchor],
[_contentView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
]];
_backButton.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[_backButton.leadingAnchor
constraintEqualToAnchor:_headerContentView.safeAreaLayoutGuide
.leadingAnchor
constant:16.0],
[_backButton.centerYAnchor
constraintEqualToAnchor:_headerContentView.centerYAnchor],
]];
_forwardButton.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[_forwardButton.leadingAnchor
constraintEqualToAnchor:_backButton.trailingAnchor
constant:16.0],
[_forwardButton.centerYAnchor
constraintEqualToAnchor:_headerContentView.centerYAnchor],
]];
_reloadOrStopButton.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[_reloadOrStopButton.leadingAnchor
constraintEqualToAnchor:_forwardButton.trailingAnchor
constant:16.0],
[_reloadOrStopButton.centerYAnchor
constraintEqualToAnchor:_headerContentView.centerYAnchor],
]];
_menuButton.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[_menuButton.leadingAnchor
constraintEqualToAnchor:_reloadOrStopButton.trailingAnchor
constant:16.0],
[_menuButton.centerYAnchor
constraintEqualToAnchor:_headerContentView.centerYAnchor],
]];
_field.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[_field.leadingAnchor constraintEqualToAnchor:_menuButton.trailingAnchor
constant:16.0],
[_field.centerYAnchor
constraintEqualToAnchor:_headerContentView.centerYAnchor],
[_field.trailingAnchor
constraintEqualToAnchor:_headerContentView.safeAreaLayoutGuide
.trailingAnchor
constant:-16.0],
[_field.heightAnchor constraintEqualToConstant:32.0],
]];
[CWVWebView setUserAgentProduct:@"Dummy/1.0"];
CWVWebView.chromeContextMenuEnabled = YES;
CWVWebView.webInspectorEnabled = YES;
_authService = [[ShellAuthService alloc] init];
CWVSyncController.dataSource = _authService;
_trustedVaultProvider =
[[ShellTrustedVaultProvider alloc] initWithAuthService:_authService];
CWVSyncController.trustedVaultProvider = _trustedVaultProvider;
_pendingLeakChecks = [NSMutableDictionary dictionary];
CWVWebViewConfiguration* configuration =
[CWVWebViewConfiguration defaultConfiguration];
[configuration.autofillDataManager addObserver:self];
configuration.syncController.delegate = self;
[configuration.leakCheckService addObserver:self];
[configuration.userContentController
addMessageHandler:^(NSDictionary* payload) {
NSLog(@"message handler payload received =\n%@", payload);
}
forCommand:@"messageHandlerCommand"];
self.webView = [self createWebViewWithConfiguration:configuration];
}
- (void)applicationFinishedRestoringState {
[super applicationFinishedRestoringState];
// The scroll view is reset on state restoration. So the delegate must be
// reassigned.
self.webView.scrollView.delegate = self;
}
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}
- (void)observeValueForKeyPath:(NSString*)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id>*)change
context:(void*)context {
if ([keyPath isEqualToString:@"canGoBack"]) {
_backButton.enabled = [_webView canGoBack];
} else if ([keyPath isEqualToString:@"canGoForward"]) {
_forwardButton.enabled = [_webView canGoForward];
} else if ([keyPath isEqualToString:@"loading"]) {
NSString* imageName = _webView.loading ? @"ic_stop" : @"ic_reload";
[_reloadOrStopButton setImage:[UIImage imageNamed:imageName]
forState:UIControlStateNormal];
}
}
- (void)back {
if ([_webView canGoBack]) {
[_webView goBack];
}
}
- (void)logBackStack {
if (!_webView.canGoBack) {
return;
}
CWVBackForwardList* list = _webView.backForwardList;
CWVBackForwardListItemArray* backList = list.backList;
for (size_t i = 0; i < backList.count; i++) {
CWVBackForwardListItem* item = backList[i];
NSLog(@"BackStack Item #%ld: <URL='%@', title='%@'>", i, item.URL,
item.title);
}
}
- (void)forward {
if ([_webView canGoForward]) {
[_webView goForward];
}
}
- (void)logForwardStack {
if (!_webView.canGoForward) {
return;
}
CWVBackForwardList* list = _webView.backForwardList;
CWVBackForwardListItemArray* forwardList = list.forwardList;
for (size_t i = 0; i < forwardList.count; i++) {
CWVBackForwardListItem* item = forwardList[i];
NSLog(@"ForwardStack Item #%ld: <URL='%@', title='%@'>", i, item.URL,
item.title);
}
}
- (void)reloadOrStop {
if (_webView.loading) {
[_webView stopLoading];
} else {
[_webView reload];
}
}
- (void)showAddressData {
CWVAutofillDataManager* dataManager =
_webView.configuration.autofillDataManager;
[dataManager fetchProfilesWithCompletionHandler:^(
NSArray<CWVAutofillProfile*>* _Nonnull profiles) {
NSMutableArray<NSString*>* descriptions = [profiles
valueForKey:NSStringFromSelector(@selector(debugDescription))];
NSString* message = [descriptions componentsJoinedByString:@"\n\n"];
UIAlertController* alertController = [self actionSheetWithTitle:@"Addresses"
message:message];
for (CWVAutofillProfile* profile in profiles) {
NSString* title = [NSString
stringWithFormat:@"Delete %@", @([profiles indexOfObject:profile])];
UIAlertAction* action =
[UIAlertAction actionWithTitle:title
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* theAction) {
[dataManager deleteProfile:profile];
}];
[alertController addAction:action];
}
[alertController
addAction:[UIAlertAction actionWithTitle:@"Done"
style:UIAlertActionStyleCancel
handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
}];
}
- (void)showCreditCardData {
CWVAutofillDataManager* dataManager =
_webView.configuration.autofillDataManager;
[dataManager fetchCreditCardsWithCompletionHandler:^(
NSArray<CWVCreditCard*>* _Nonnull creditCards) {
NSMutableArray<NSString*>* descriptions = [creditCards
valueForKey:NSStringFromSelector(@selector(debugDescription))];
NSString* message = [descriptions componentsJoinedByString:@"\n\n"];
UIAlertController* alertController =
[self actionSheetWithTitle:@"Credit cards" message:message];
__weak ShellViewController* weakSelf = self;
[alertController
addAction:[UIAlertAction
actionWithTitle:@"Manage Google pay cards"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
__weak ShellViewController* strongSelf =
weakSelf;
NSString* URL;
if ([CWVFlags sharedInstance]
.usesSyncAndWalletSandbox) {
URL = @"https://pay.sandbox.google.com/"
@"payments/home#paymentMethods";
} else {
URL = @"https://pay.google.com/payments/"
@"home#paymentMethods";
}
NSURLRequest* request = [NSURLRequest
requestWithURL:[NSURL URLWithString:URL]];
[strongSelf.webView loadRequest:request];
}]];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Done"
style:UIAlertActionStyleCancel
handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
}];
}
- (void)showPasswordData {
__weak ShellViewController* weakSelf = self;
CWVAutofillDataManager* dataManager =
_webView.configuration.autofillDataManager;
[dataManager fetchPasswordsWithCompletionHandler:^(
NSArray<CWVPassword*>* _Nonnull passwords) {
NSMutableArray<NSString*>* descriptions = [NSMutableArray array];
for (CWVPassword* password in passwords) {
NSString* description = [NSString
stringWithFormat:@"%@:\n%@", @([passwords indexOfObject:password]),
password.debugDescription];
[descriptions addObject:description];
}
NSString* message = [descriptions componentsJoinedByString:@"\n\n"];
UIAlertController* alertController = [self actionSheetWithTitle:@"Passwords"
message:message];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Add new"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[weakSelf showAddNewPasswordDialog];
}]];
for (CWVPassword* password in passwords) {
NSString* title = [NSString
stringWithFormat:@"Select %@", @([passwords indexOfObject:password])];
UIAlertAction* action =
[UIAlertAction actionWithTitle:title
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* theAction) {
[weakSelf showMenuForPassword:password];
}];
[alertController addAction:action];
}
[alertController
addAction:[UIAlertAction actionWithTitle:@"Done"
style:UIAlertActionStyleCancel
handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
}];
}
- (void)showAddNewPasswordDialog {
CWVAutofillDataManager* dataManager =
_webView.configuration.autofillDataManager;
UIAlertController* alertController =
[UIAlertController alertControllerWithTitle:@"Add password"
message:nil
preferredStyle:UIAlertControllerStyleAlert];
[alertController
addTextFieldWithConfigurationHandler:^(UITextField* textField) {
textField.placeholder = @"Username";
}];
[alertController
addTextFieldWithConfigurationHandler:^(UITextField* textField) {
textField.placeholder = @"Password";
}];
[alertController
addTextFieldWithConfigurationHandler:^(UITextField* textField) {
textField.placeholder = @"Site";
}];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:nil]];
__weak UIAlertController* weakAlertController = alertController;
[alertController
addAction:[UIAlertAction
actionWithTitle:@"Done"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
NSString* username =
weakAlertController.textFields[0].text;
NSString* password =
weakAlertController.textFields[1].text;
NSString* site =
weakAlertController.textFields[2].text;
[dataManager addNewPasswordForUsername:username
password:password
site:site];
}]];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)showMenuForPassword:(CWVPassword*)password {
UIAlertController* alertController =
[self actionSheetWithTitle:password.title
message:password.debugDescription];
CWVAutofillDataManager* dataManager =
_webView.configuration.autofillDataManager;
__weak ShellViewController* weakSelf = self;
UIAlertAction* update =
[UIAlertAction actionWithTitle:@"Update"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* theAction) {
[weakSelf showUpdateDialogForPassword:password];
}];
[alertController addAction:update];
UIAlertAction* delete =
[UIAlertAction actionWithTitle:@"Delete"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* theAction) {
[dataManager deletePassword:password];
}];
[alertController addAction:delete];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Done"
style:UIAlertActionStyleCancel
handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)showUpdateDialogForPassword:(CWVPassword*)password {
CWVAutofillDataManager* dataManager =
_webView.configuration.autofillDataManager;
UIAlertController* alertController =
[UIAlertController alertControllerWithTitle:password.title
message:password.debugDescription
preferredStyle:UIAlertControllerStyleAlert];
[alertController
addTextFieldWithConfigurationHandler:^(UITextField* textField) {
textField.placeholder = @"New username";
}];
[alertController
addTextFieldWithConfigurationHandler:^(UITextField* textField) {
textField.placeholder = @"New password";
}];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:nil]];
__weak UIAlertController* weakAlertController = alertController;
[alertController
addAction:[UIAlertAction actionWithTitle:@"Done"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
NSString* newUsername =
weakAlertController.textFields
.firstObject.text;
NSString* newPassword =
weakAlertController.textFields
.lastObject.text;
[dataManager
updatePassword:password
newUsername:newUsername
newPassword:newPassword];
}]];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)showSyncMenu {
CWVSyncController* syncController = _webView.configuration.syncController;
NSString* message = [NSString
stringWithFormat:@"Passphrase required: %@\nTrusted vault keys required: "
@"%@\nTrusted vault recoverability degraded: %@",
@(syncController.passphraseNeeded),
@(syncController.trustedVaultKeysRequired),
@(syncController.trustedVaultRecoverabilityDegraded)];
UIAlertController* alertController = [self actionSheetWithTitle:@"Sync menu"
message:message];
CWVIdentity* currentIdentity = syncController.currentIdentity;
__weak ShellViewController* weakSelf = self;
if (currentIdentity) {
NSString* title = [NSString
stringWithFormat:@"Stop syncing for %@", currentIdentity.email];
[alertController
addAction:[UIAlertAction
actionWithTitle:title
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[syncController stopSyncAndClearIdentity];
}]];
if (syncController.passphraseNeeded) {
[alertController
addAction:[UIAlertAction
actionWithTitle:@"Unlock using passphrase"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[weakSelf showPassphraseUnlockAlert];
}]];
} else if (syncController.trustedVaultKeysRequired) {
[alertController
addAction:[UIAlertAction
actionWithTitle:@"Fetch trusted vault keys"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[weakSelf.trustedVaultProvider
showFetchKeysFlowForIdentity:
currentIdentity
fromViewController:weakSelf];
}]];
} else if (syncController.trustedVaultRecoverabilityDegraded) {
[alertController
addAction:
[UIAlertAction
actionWithTitle:@"Fix degraded recoverability"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[weakSelf.trustedVaultProvider
showFixDegradedRecoverabilityFlowForIdentity:
currentIdentity
fromViewController:
weakSelf];
}]];
}
} else {
for (CWVIdentity* identity in [_authService identities]) {
NSString* title =
[NSString stringWithFormat:@"Start sync with %@", identity.email];
[alertController
addAction:[UIAlertAction
actionWithTitle:title
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[syncController
startSyncWithIdentity:identity];
}]];
}
NSString* sandboxTitle = [CWVFlags sharedInstance].usesSyncAndWalletSandbox
? @"Use production sync/wallet"
: @"Use sandbox sync/wallet";
[alertController
addAction:[UIAlertAction actionWithTitle:sandboxTitle
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[CWVFlags sharedInstance]
.usesSyncAndWalletSandbox ^= YES;
}]];
}
[alertController
addAction:[UIAlertAction actionWithTitle:@"Show autofill data"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[weakSelf showAddressData];
}]];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Show credit card data"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[weakSelf showCreditCardData];
}]];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Show password data"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[weakSelf showPasswordData];
}]];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Check leaked passwords"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[weakSelf checkLeakedPasswords];
}]];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Check weak passwords"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[weakSelf checkWeakPasswords];
}]];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Check reused passwords"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[weakSelf checkReusedPasswords];
}]];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)showPassphraseUnlockAlert {
UIAlertController* alertController =
[UIAlertController alertControllerWithTitle:@"Unlock sync"
message:@"Enter passphrase"
preferredStyle:UIAlertControllerStyleAlert];
__weak UIAlertController* weakAlertController = alertController;
CWVSyncController* syncController = _webView.configuration.syncController;
UIAlertAction* submit = [UIAlertAction
actionWithTitle:@"Unlock"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
UITextField* textField =
weakAlertController.textFields.firstObject;
NSString* passphrase = textField.text;
BOOL result = [syncController unlockWithPassphrase:passphrase];
NSLog(@"Sync passphrase unlock result: %d", result);
}];
[alertController addAction:submit];
UIAlertAction* cancel =
[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:nil];
[alertController addAction:cancel];
[alertController
addTextFieldWithConfigurationHandler:^(UITextField* textField) {
textField.placeholder = @"passphrase";
textField.keyboardType = UIKeyboardTypeDefault;
}];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)showMainMenu {
UIAlertController* alertController = [self actionSheetWithTitle:@"Main menu"
message:nil];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:nil]];
__weak ShellViewController* weakSelf = self;
// Toggles the incognito mode.
NSString* incognitoActionTitle = _webView.configuration.persistent
? @"Enter incognito"
: @"Exit incognito";
[alertController
addAction:[UIAlertAction actionWithTitle:incognitoActionTitle
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[weakSelf toggleIncognito];
}]];
// Removes the web view from the view hierarchy, releases it, and recreates
// the web view with the same configuration. This is for testing deallocation
// and sharing configuration.
[alertController
addAction:[UIAlertAction
actionWithTitle:@"Recreate web view"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
CWVWebViewConfiguration* configuration =
weakSelf.webView.configuration;
[weakSelf removeWebView];
weakSelf.webView = [weakSelf
createWebViewWithConfiguration:configuration];
}]];
// Developers can choose to use system or Chrome context menu in the shell
// app. This will also recreate the web view.
BOOL chromeContextMenuEnabled = CWVWebView.chromeContextMenuEnabled;
NSString* contextMenuSwitchActionTitle = [NSString
stringWithFormat:@"%@ Chrome context menu",
chromeContextMenuEnabled ? @"Disable" : @"Enable"];
[alertController
addAction:[UIAlertAction
actionWithTitle:contextMenuSwitchActionTitle
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
CWVWebView.chromeContextMenuEnabled =
!chromeContextMenuEnabled;
NSLog(@"Chrome context menu is %@ now.",
!chromeContextMenuEnabled ? @"OFF" : @"ON");
CWVWebViewConfiguration* configuration =
weakSelf.webView.configuration;
[weakSelf removeWebView];
weakSelf.field.text = @"";
weakSelf.webView = [weakSelf
createWebViewWithConfiguration:configuration];
}]];
// Resets all translation settings to default values.
[alertController
addAction:[UIAlertAction actionWithTitle:@"Reset translate settings"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[weakSelf resetTranslateSettings];
}]];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Request translation offer"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[weakSelf requestTranslationOffer];
}]];
// Shows sync menu.
[alertController
addAction:[UIAlertAction actionWithTitle:@"Sync menu"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[weakSelf showSyncMenu];
}]];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Show Certificate Details"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[weakSelf showCertificateDetails];
}]];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Evaluate JavaScript"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[weakSelf showEvaluateJavaScriptUI];
}]];
[alertController
addAction:[UIAlertAction actionWithTitle:@"User Scripts"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[weakSelf showUserScriptsUI];
}]];
if (self.downloadTask) {
[alertController
addAction:[UIAlertAction actionWithTitle:@"Cancel download"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[weakSelf.downloadTask cancel];
}]];
}
// Using native FiP through is only available for 16.0, otherwise fallback
// to iGA JS FiP.
if (@available(iOS 16.0, *)) {
[alertController
addAction:[UIAlertAction actionWithTitle:@"Start Find In Page"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[weakSelf startFindInPageSession];
}]];
}
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)showCertificateDetails {
CWVX509Certificate* certificate = [[_webView visibleSSLStatus] certificate];
NSString* message;
if (certificate) {
message = [NSString stringWithFormat:@"Issuer: %@\nExpires: %@",
certificate.issuerDisplayName,
certificate.validExpiry];
} else {
message = @"No Certificate";
}
UIAlertController* alertController =
[self actionSheetWithTitle:@"Certificate Details" message:message];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Done"
style:UIAlertActionStyleCancel
handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)checkLeakedPasswords {
CWVAutofillDataManager* dataManager =
_webView.configuration.autofillDataManager;
// Request a check for any password in autofill data manager that is not
// currently being requested.
[dataManager fetchPasswordsWithCompletionHandler:^(
NSArray<CWVPassword*>* _Nonnull passwords) {
NSMutableArray<CWVLeakCheckCredential*>* credentialsToCheck =
[NSMutableArray array];
for (CWVPassword* password in passwords) {
CWVLeakCheckCredential* credential = [CWVLeakCheckCredential
canonicalLeakCheckCredentialWithPassword:password];
NSMutableArray<CWVPassword*>* passwordsForCredential =
self.pendingLeakChecks[credential];
if (!passwordsForCredential) {
passwordsForCredential = [NSMutableArray array];
self.pendingLeakChecks[credential] = passwordsForCredential;
[credentialsToCheck addObject:credential];
}
[passwordsForCredential addObject:password];
}
NSLog(@"Checking leaks for %@ credentials.", @(credentialsToCheck.count));
[self.webView.configuration.leakCheckService
checkCredentials:credentialsToCheck];
}];
}
- (void)checkWeakPasswords {
CWVAutofillDataManager* dataManager =
_webView.configuration.autofillDataManager;
[dataManager fetchPasswordsWithCompletionHandler:^(
NSArray<CWVPassword*>* _Nonnull passwords) {
NSLog(@"Checking weak status of %@ passwords.", @(passwords.count));
for (CWVPassword* password in passwords) {
dispatch_async(dispatch_get_main_queue(), ^{
BOOL isWeak = [CWVWeakCheckUtils isPasswordWeak:password.password];
NSLog(@"Weak password status for %@ at %@ is %s", password.username,
password.site, isWeak ? "true" : "false");
});
}
}];
}
- (void)checkReusedPasswords {
CWVAutofillDataManager* dataManager =
_webView.configuration.autofillDataManager;
[dataManager fetchPasswordsWithCompletionHandler:^(
NSArray<CWVPassword*>* _Nonnull passwords) {
NSLog(@"Checking reuse status of %@ passwords.", @(passwords.count));
[self.webView.configuration.reuseCheckService
checkReusedPasswords:passwords
completionHandler:^(NSSet<NSString*>* reusedPasswords) {
int useCount = 0;
for (CWVPassword* password in passwords) {
if ([reusedPasswords containsObject:password.password]) {
useCount++;
}
}
NSLog(@"%@ reused password(s).", @(reusedPasswords.count));
NSLog(@"Used %d times.", useCount);
UIAlertController* alertController = [UIAlertController
alertControllerWithTitle:@"Reuse Check Complete"
message:[NSString
stringWithFormat:
@"%@ reused password(s). "
@"Used %d times.",
@(reusedPasswords.count),
useCount]
preferredStyle:UIAlertControllerStyleAlert];
[alertController
addAction:[UIAlertAction
actionWithTitle:@"OK"
style:UIAlertActionStyleDefault
handler:nil]];
[self presentViewController:alertController
animated:YES
completion:nil];
}];
}];
}
- (void)showEvaluateJavaScriptUI {
UIAlertController* alertController =
[self alertControllerWithTitle:@"Evaluate JavaScript"
message:nil
preferredStyle:UIAlertControllerStyleAlert];
[alertController
addTextFieldWithConfigurationHandler:^(UITextField* textField) {
textField.placeholder = @"alert('Hello')";
}];
__weak UIAlertController* weakAlertController = alertController;
__weak ShellViewController* weakSelf = self;
[alertController
addAction:[UIAlertAction
actionWithTitle:@"Evaluate"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
NSString* javascript =
weakAlertController.textFields[0].text;
[weakSelf evaluateJavaScript:javascript];
}]];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)evaluateJavaScript:(NSString*)javascript {
[self.webView
evaluateJavaScript:javascript
completion:^(id result, NSError* error) {
if (error) {
NSLog(
@"JavaScript evaluation FAILED with error: %@ result: %@",
error, result);
} else {
NSLog(@"JavaScript evaluation finished with result: %@",
result);
}
}];
}
- (void)showUserScriptsUI {
UIAlertController* alertController =
[self alertControllerWithTitle:@"Add or Remove User Scripts"
message:@"This will also recreate the web view."
preferredStyle:UIAlertControllerStyleAlert];
[alertController
addTextFieldWithConfigurationHandler:^(UITextField* textField) {
textField.placeholder = @"All frames script";
}];
[alertController
addTextFieldWithConfigurationHandler:^(UITextField* textField) {
textField.placeholder = @"Main frame script";
}];
__weak UIAlertController* weakAlertController = alertController;
__weak ShellViewController* weakSelf = self;
[alertController
addAction:[UIAlertAction
actionWithTitle:@"Add"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
NSString* allFramesSource =
weakAlertController.textFields[0].text;
NSString* mainFrameSource =
weakAlertController.textFields[1].text;
[weakSelf
addUserScriptForAllFrames:allFramesSource
forMainFrameOnly:mainFrameSource];
}]];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Remove All"
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction* action) {
[weakSelf removeAllUserScripts];
}]];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)addUserScriptForAllFrames:(nullable NSString*)allFramesSource
forMainFrameOnly:(nullable NSString*)mainFrameSource {
CWVWebViewConfiguration* configuration = self.webView.configuration;
[self removeWebView];
if (allFramesSource.length) {
CWVUserScript* allFramesScript =
[[CWVUserScript alloc] initWithSource:allFramesSource
forMainFrameOnly:NO];
[configuration.userContentController addUserScript:allFramesScript];
}
if (mainFrameSource.length) {
CWVUserScript* mainFrameScript =
[[CWVUserScript alloc] initWithSource:mainFrameSource
forMainFrameOnly:YES];
[configuration.userContentController addUserScript:mainFrameScript];
}
self.webView = [self createWebViewWithConfiguration:configuration];
}
- (void)removeAllUserScripts {
CWVWebViewConfiguration* configuration = self.webView.configuration;
[self removeWebView];
[configuration.userContentController removeAllUserScripts];
self.webView = [self createWebViewWithConfiguration:configuration];
}
- (void)resetTranslateSettings {
CWVWebViewConfiguration* configuration =
[CWVWebViewConfiguration defaultConfiguration];
[configuration.preferences resetTranslationSettings];
}
- (void)requestTranslationOffer {
BOOL offered = [_webView.translationController requestTranslationOffer];
NSLog(@"Manual translation was offered: %d", offered);
}
- (void)toggleIncognito {
BOOL wasPersistent = _webView.configuration.persistent;
[self removeWebView];
CWVWebViewConfiguration* newConfiguration =
wasPersistent ? [CWVWebViewConfiguration nonPersistentConfiguration]
: [CWVWebViewConfiguration defaultConfiguration];
self.webView = [self createWebViewWithConfiguration:newConfiguration];
}
- (void)startFindInPageSession {
// Using native FiP through is only available for 16.0, otherwise fallback
// to iGA JS FiP.
if (@available(iOS 16.0, *)) {
if ([_webView.findInPageController canFindInPage]) {
[_webView.findInPageController startFindInPage];
}
}
}
- (CWVWebView*)createWebViewWithConfiguration:
(CWVWebViewConfiguration*)configuration {
// Set a non empty CGRect to avoid DCHECKs that occur when a load happens
// after state restoration, and before the view hierarchy is laid out for the
// first time.
// https://source.chromium.org/chromium/chromium/src/+/main:ios/web/web_state/ui/crw_web_request_controller.mm;l=518;drc=df887034106ef438611326745a7cd276eedd4953
CGRect frame = CGRectMake(0, 0, 1, 1);
CWVWebView* webView = [[CWVWebView alloc] initWithFrame:frame
configuration:configuration];
[_contentView addSubview:webView];
// Gives a restoration identifier so that state restoration works.
webView.restorationIdentifier = @"webView";
// Configure delegates.
webView.navigationDelegate = self;
webView.UIDelegate = self;
_translationDelegate = [[ShellTranslationDelegate alloc] init];
webView.translationController.delegate = _translationDelegate;
_autofillDelegate = [[ShellAutofillDelegate alloc] init];
webView.autofillController.delegate = _autofillDelegate;
webView.scrollView.delegate = self;
// Constraints.
webView.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[webView.topAnchor
constraintEqualToAnchor:_contentView.safeAreaLayoutGuide.topAnchor],
[webView.leadingAnchor
constraintEqualToAnchor:_contentView.safeAreaLayoutGuide.leadingAnchor],
[webView.trailingAnchor
constraintEqualToAnchor:_contentView.safeAreaLayoutGuide
.trailingAnchor],
[webView.bottomAnchor
constraintEqualToAnchor:_contentView.safeAreaLayoutGuide.bottomAnchor],
]];
[webView addObserver:self
forKeyPath:@"canGoBack"
options:NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionInitial
context:nil];
[webView addObserver:self
forKeyPath:@"canGoForward"
options:NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionInitial
context:nil];
[webView addObserver:self
forKeyPath:@"loading"
options:NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionInitial
context:nil];
[webView
addMessageHandler:^(NSDictionary* payload) {
NSLog(@"webview message handler payload received =\n%@", payload);
}
forCommand:@"webViewMessageHandlerCommand"];
return webView;
}
- (void)removeWebView {
[_webView removeFromSuperview];
[_webView removeObserver:self forKeyPath:@"canGoBack"];
[_webView removeObserver:self forKeyPath:@"canGoForward"];
[_webView removeObserver:self forKeyPath:@"loading"];
[_webView removeMessageHandlerForCommand:@"webViewMessageHandlerCommand"];
_webView = nil;
}
- (void)dealloc {
[_webView removeObserver:self forKeyPath:@"canGoBack"];
[_webView removeObserver:self forKeyPath:@"canGoForward"];
[_webView removeObserver:self forKeyPath:@"loading"];
[_webView removeMessageHandlerForCommand:@"webViewMessageHandlerCommand"];
}
- (BOOL)textFieldShouldReturn:(UITextField*)field {
NSURL* URL = [NSURL URLWithString:field.text];
if (URL.scheme.length == 0) {
NSString* enteredText = field.text;
enteredText =
[enteredText stringByAddingPercentEncodingWithAllowedCharacters:
[NSCharacterSet URLQueryAllowedCharacterSet]];
enteredText = [NSString
stringWithFormat:@"https://www.google.com/search?q=%@", enteredText];
URL = [NSURL URLWithString:enteredText];
}
NSURLRequest* request = [NSURLRequest requestWithURL:URL];
[_webView loadRequest:request];
[field resignFirstResponder];
[self updateToolbar];
return YES;
}
- (void)updateToolbar {
// Do not update the URL if the text field is currently being edited.
if ([_field isFirstResponder]) {
return;
}
[_field setText:[[_webView visibleURL] absoluteString]];
}
- (UIAlertController*)actionSheetWithTitle:(nullable NSString*)title
message:(nullable NSString*)message {
return [self alertControllerWithTitle:title
message:message
preferredStyle:UIAlertControllerStyleActionSheet];
}
- (UIAlertController*)alertControllerWithTitle:(nullable NSString*)title
message:(nullable NSString*)message
preferredStyle:
(UIAlertControllerStyle)preferredStyle {
UIAlertController* alertController =
[UIAlertController alertControllerWithTitle:title
message:message
preferredStyle:preferredStyle];
alertController.popoverPresentationController.sourceView = _menuButton;
alertController.popoverPresentationController.sourceRect =
CGRectMake(CGRectGetWidth(_menuButton.bounds) / 2,
CGRectGetHeight(_menuButton.bounds), 1, 1);
return alertController;
}
- (void)closePopupWebView {
if (self.popupWebViews.count) {
[self.popupWebViews.lastObject removeFromSuperview];
[self.popupWebViews removeLastObject];
}
}
#pragma mark CWVUIDelegate methods
- (CWVWebView*)webView:(CWVWebView*)webView
createWebViewWithConfiguration:(CWVWebViewConfiguration*)configuration
forNavigationAction:(CWVNavigationAction*)action {
NSLog(@"Create new CWVWebView for %@. User initiated? %@", action.request.URL,
action.userInitiated ? @"Yes" : @"No");
CWVWebView* newWebView = [self createWebViewWithConfiguration:configuration];
[self.popupWebViews addObject:newWebView];
UIButton* closeWindowButton = [[UIButton alloc] init];
[closeWindowButton setImage:[UIImage imageNamed:@"ic_stop"]
forState:UIControlStateNormal];
closeWindowButton.tintColor = [UIColor blackColor];
closeWindowButton.backgroundColor = [UIColor whiteColor];
[closeWindowButton addTarget:self
action:@selector(closePopupWebView)
forControlEvents:UIControlEventTouchUpInside];
[newWebView addSubview:closeWindowButton];
closeWindowButton.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[closeWindowButton.topAnchor constraintEqualToAnchor:newWebView.topAnchor
constant:16.0],
[closeWindowButton.centerXAnchor
constraintEqualToAnchor:newWebView.centerXAnchor],
]];
return newWebView;
}
- (void)webViewDidClose:(CWVWebView*)webView {
NSLog(@"webViewDidClose");
}
- (void)webView:(CWVWebView*)webView
requestMediaCapturePermissionForType:(CWVMediaCaptureType)type
decisionHandler:
(void (^)(CWVPermissionDecision decision))
decisionHandler {
NSString* mediaCaptureType;
switch (type) {
case CWVMediaCaptureTypeCamera:
mediaCaptureType = @"Camera";
break;
case CWVMediaCaptureTypeMicrophone:
mediaCaptureType = @"Microphone";
break;
case CWVMediaCaptureTypeCameraAndMicrophone:
mediaCaptureType = @"Camera and Microphone";
break;
}
NSString* title =
[NSString stringWithFormat:@"Request %@ Permission", mediaCaptureType];
UIAlertController* alertController =
[UIAlertController alertControllerWithTitle:title
message:nil
preferredStyle:UIAlertControllerStyleAlert];
[alertController
addAction:[UIAlertAction
actionWithTitle:@"Grant"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
decisionHandler(CWVPermissionDecisionGrant);
}]];
[alertController
addAction:[UIAlertAction
actionWithTitle:@"Deny"
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction* action) {
decisionHandler(CWVPermissionDecisionDeny);
}]];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)webView:(CWVWebView*)webView
contextMenuConfigurationForElement:(CWVHTMLElement*)element
completionHandler:(void (^)(UIContextMenuConfiguration*))
completionHandler {
void (^copyHandler)(UIAction*) = ^(UIAction* action) {
NSDictionary* item = @{
(NSString*)(UTTypeURL) : element.hyperlink.absoluteString,
(NSString*)(UTTypeUTF8PlainText) : [element.hyperlink.absoluteString
dataUsingEncoding:NSUTF8StringEncoding],
};
[[UIPasteboard generalPasteboard] setItems:@[ item ]];
};
UIContextMenuConfiguration* configuration = [UIContextMenuConfiguration
configurationWithIdentifier:nil
previewProvider:^{
UIViewController* controller = [[UIViewController alloc] init];
CGRect frame = CGRectMake(10, 200, 200, 21);
UILabel* label = [[UILabel alloc] initWithFrame:frame];
label.text = @"iOS13 Preview Page";
[controller.view addSubview:label];
return controller;
}
actionProvider:^(id _) {
NSArray* actions = @[
[UIAction actionWithTitle:@"Copy Link"
image:nil
identifier:nil
handler:copyHandler],
[UIAction actionWithTitle:@"Cancel"
image:nil
identifier:nil
handler:^(id ignore){
}]
];
NSString* menuTitle =
[NSString stringWithFormat:@"iOS13 Context Menu: %@",
element.hyperlink.absoluteString];
return [UIMenu menuWithTitle:menuTitle children:actions];
}];
completionHandler(configuration);
}
- (void)webView:(CWVWebView*)webView
contextMenuWillCommitWithAnimator:
(id<UIContextMenuInteractionCommitAnimating>)animator {
NSLog(@"webView:contextMenuWillCommitWithAnimator:");
}
- (void)webView:(CWVWebView*)webView
runJavaScriptAlertPanelWithMessage:(NSString*)message
pageURL:(NSURL*)URL
completionHandler:(void (^)(void))handler {
UIAlertController* alert =
[UIAlertController alertControllerWithTitle:nil
message:message
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Ok"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
handler();
}]];
[self presentViewController:alert animated:YES completion:nil];
}
- (void)webView:(CWVWebView*)webView
runJavaScriptConfirmPanelWithMessage:(NSString*)message
pageURL:(NSURL*)URL
completionHandler:(void (^)(BOOL))handler {
UIAlertController* alert =
[UIAlertController alertControllerWithTitle:nil
message:message
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Ok"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
handler(YES);
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:^(UIAlertAction* action) {
handler(NO);
}]];
[self presentViewController:alert animated:YES completion:nil];
}
- (void)webView:(CWVWebView*)webView
runJavaScriptTextInputPanelWithPrompt:(NSString*)prompt
defaultText:(NSString*)defaultText
pageURL:(NSURL*)URL
completionHandler:(void (^)(NSString*))handler {
UIAlertController* alert =
[UIAlertController alertControllerWithTitle:nil
message:prompt
preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler:^(UITextField* textField) {
textField.text = defaultText;
textField.accessibilityIdentifier =
kWebViewShellJavaScriptDialogTextFieldAccessibilityIdentifier;
}];
__weak UIAlertController* weakAlert = alert;
[alert addAction:[UIAlertAction
actionWithTitle:@"Ok"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
NSString* textInput =
weakAlert.textFields.firstObject.text;
handler(textInput);
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:^(UIAlertAction* action) {
handler(nil);
}]];
[self presentViewController:alert animated:YES completion:nil];
}
- (void)webView:(CWVWebView*)webView
didLoadFavicons:(NSArray<CWVFavicon*>*)favIcons {
NSLog(@"%@", NSStringFromSelector(_cmd));
}
#pragma mark CWVNavigationDelegate methods
// Deprecated: this method will not work when `-[CWVNavigationDelegate
// webView:decidePolicyForNavigationAction:decisionHandler:]` is implemented
- (BOOL)webView:(CWVWebView*)webView
shouldStartLoadWithRequest:(NSURLRequest*)request
navigationType:(CWVNavigationType)navigationType {
NSLog(@"%@", NSStringFromSelector(_cmd));
return YES;
}
// Deprecated: this method will not work when `-[CWVNavigationDelegate
// webView:decidePolicyForNavigationResponse:decisionHandler:]` is implemented
- (BOOL)webView:(CWVWebView*)webView
shouldContinueLoadWithResponse:(NSURLResponse*)response
forMainFrame:(BOOL)forMainFrame {
NSLog(@"%@", NSStringFromSelector(_cmd));
return YES;
}
- (void)webView:(CWVWebView*)webView
decidePolicyForNavigationAction:(CWVNavigationAction*)navigationAction
decisionHandler:
(void (^)(CWVNavigationActionPolicy))decisionHandler {
NSLog(@"%@", NSStringFromSelector(_cmd));
decisionHandler(CWVNavigationActionPolicyAllow);
}
- (void)webView:(CWVWebView*)webView
decidePolicyForNavigationResponse:(CWVNavigationResponse*)navigationResponse
decisionHandler:(void (^)(CWVNavigationResponsePolicy))
decisionHandler {
NSLog(@"%@", NSStringFromSelector(_cmd));
decisionHandler(CWVNavigationResponsePolicyAllow);
}
- (void)webViewDidStartNavigation:(CWVWebView*)webView {
NSLog(@"%@", NSStringFromSelector(_cmd));
[self updateToolbar];
}
- (void)webViewDidCommitNavigation:(CWVWebView*)webView {
NSLog(@"%@", NSStringFromSelector(_cmd));
[self updateToolbar];
}
- (void)webViewDidFinishNavigation:(CWVWebView*)webView {
NSLog(@"%@", NSStringFromSelector(_cmd));
// TODO(crbug.com/41294395): Add some visual indication that the page load has
// finished.
[self updateToolbar];
}
- (void)webView:(CWVWebView*)webView
didFailNavigationWithError:(NSError*)error {
NSLog(@"%@", NSStringFromSelector(_cmd));
[self updateToolbar];
}
- (void)webView:(CWVWebView*)webView
handleSSLErrorWithHandler:(CWVSSLErrorHandler*)handler {
NSLog(@"%@", NSStringFromSelector(_cmd));
[handler displayErrorPageWithHTML:handler.error.localizedDescription];
if (!handler.overridable) {
return;
}
UIAlertController* alertController =
[self actionSheetWithTitle:@"SSL error encountered"
message:@"Would you like to continue anyways?"];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Yes"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[handler overrideErrorAndReloadPage];
}]];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)webView:(CWVWebView*)webView
handleLookalikeURLWithHandler:(CWVLookalikeURLHandler*)handler {
NSLog(@"%@", NSStringFromSelector(_cmd));
NSString* html =
[NSString stringWithFormat:@"%@ requested, did you mean %@?",
handler.requestURL, handler.safeURL];
[handler displayInterstitialPageWithHTML:html];
UIAlertController* alertController =
[self actionSheetWithTitle:@"Lookalike URL encountered"
message:@"Choose how to proceed."];
[alertController
addAction:
[UIAlertAction
actionWithTitle:@"Proceed to request URL"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
CWVLookalikeURLHandlerDecision decision =
CWVLookalikeURLHandlerDecisionProceedToRequestURL;
[handler commitDecision:decision];
}]];
[alertController
addAction:
[UIAlertAction
actionWithTitle:@"Proceed to safe URL"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[handler
commitDecision:
CWVLookalikeURLHandlerDecisionProceedToSafeURL];
}]];
[alertController
addAction:
[UIAlertAction
actionWithTitle:@"Go back or close"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[handler
commitDecision:
CWVLookalikeURLHandlerDecisionGoBackOrClose];
}]];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)webView:(CWVWebView*)webView
handleUnsafeURLWithHandler:(CWVUnsafeURLHandler*)handler {
NSLog(@"%@", NSStringFromSelector(_cmd));
NSString* html =
[NSString stringWithFormat:@"%@ requested %@ which might be unsafe.",
handler.mainFrameURL, handler.requestURL];
[handler displayInterstitialPageWithHTML:html];
UIAlertController* alertController =
[self actionSheetWithTitle:@"Unsafe URL encountered"
message:@"Choose how to proceed."];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Proceed"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[handler proceed];
}]];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Go back or close"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[handler goBack];
}]];
[alertController
addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)webViewWebContentProcessDidTerminate:(CWVWebView*)webView {
NSLog(@"%@", NSStringFromSelector(_cmd));
}
- (void)webView:(CWVWebView*)webView
didRequestDownloadWithTask:(CWVDownloadTask*)task {
NSLog(@"%@", NSStringFromSelector(_cmd));
self.downloadTask = task;
NSString* documentDirectoryPath = NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, YES)[0];
self.downloadFilePath = [documentDirectoryPath
stringByAppendingPathComponent:task.suggestedFileName];
task.delegate = self;
[task startDownloadToLocalFileAtPath:self.downloadFilePath];
}
#pragma mark CWVAutofillDataManagerObserver
- (void)autofillDataManagerDataDidChange:
(CWVAutofillDataManager*)autofillDataManager {
NSLog(@"%@", NSStringFromSelector(_cmd));
}
- (void)autofillDataManager:(CWVAutofillDataManager*)autofillDataManager
didChangePasswordsByAdding:(NSArray<CWVPassword*>*)added
updating:(NSArray<CWVPassword*>*)updated
removing:(NSArray<CWVPassword*>*)removed {
NSLog(@"%@: added %@, updated %@, and removed %@ passwords",
NSStringFromSelector(_cmd), @(added.count), @(updated.count),
@(removed.count));
}
#pragma mark CWVDownloadTaskDelegate
- (void)downloadTask:(CWVDownloadTask*)downloadTask
didFinishWithError:(nullable NSError*)error {
NSLog(@"%@", NSStringFromSelector(_cmd));
if (!error) {
NSURL* url = [NSURL fileURLWithPath:self.downloadFilePath];
self.documentInteractionController =
[UIDocumentInteractionController interactionControllerWithURL:url];
[self.documentInteractionController presentOptionsMenuFromRect:CGRectZero
inView:self.view
animated:YES];
}
self.downloadTask = nil;
self.downloadFilePath = nil;
}
- (void)downloadTaskProgressDidChange:(CWVDownloadTask*)downloadTask {
NSLog(@"%@", NSStringFromSelector(_cmd));
}
#pragma mark CWVSyncControllerDelegate
- (void)syncControllerDidStartSync:(CWVSyncController*)syncController {
NSLog(@"%@", NSStringFromSelector(_cmd));
}
- (void)syncController:(CWVSyncController*)syncController
didFailWithError:(NSError*)error {
NSLog(@"%@:%@", NSStringFromSelector(_cmd), error);
}
- (void)syncControllerDidStopSync:(CWVSyncController*)syncController {
NSLog(@"%@", NSStringFromSelector(_cmd));
}
- (void)syncControllerDidUpdateState:(CWVSyncController*)syncController {
NSLog(@"%@", NSStringFromSelector(_cmd));
}
#pragma mark CWVLeakCheckServiceObserver
- (void)leakCheckServiceDidChangeState:(CWVLeakCheckService*)leakCheckService {
NSLog(@"%@:%d", NSStringFromSelector(_cmd), (int)leakCheckService.state);
if (leakCheckService.state != CWVLeakCheckServiceStateRunning) {
[self.pendingLeakChecks removeAllObjects];
}
}
- (void)leakCheckService:(CWVLeakCheckService*)leakCheckService
didCheckCredential:(CWVLeakCheckCredential*)credential
isLeaked:(BOOL)isLeaked {
NSMutableArray<CWVPassword*>* passwordsForCredential =
[self.pendingLeakChecks objectForKey:credential];
if (!passwordsForCredential) {
NSLog(@"No passwords for CWVLeakCheckCredential!");
return;
}
[self.pendingLeakChecks removeObjectForKey:credential];
NSMutableArray<NSString*>* passwordDescriptions = [passwordsForCredential
valueForKey:NSStringFromSelector(@selector(debugDescription))];
NSString* passwordsDescription =
[passwordDescriptions componentsJoinedByString:@"\n\n"];
NSString* message = [NSString
stringWithFormat:@"Leak check returned %@ for %@ passwords:\n%@",
isLeaked ? @"LEAKED" : @"OK",
@(passwordsForCredential.count), passwordsDescription];
NSLog(@"%@", message);
NSLog(@"%@ Leak checks remaining...", @(self.pendingLeakChecks.count));
}
#pragma mark UIScrollViewDelegate
- (void)scrollViewWillBeginDragging:(UIScrollView*)scrollView {
NSLog(@"%@", NSStringFromSelector(_cmd));
}
@end