// Copyright 2014 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/web_view/shell/shell_view_controller.h"

#import <MobileCoreServices/MobileCoreServices.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"

#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif

// Externed accessibility identifier.
NSString* const kWebViewShellBackButtonAccessibilityLabel = @"Back";
NSString* const kWebViewShellForwardButtonAccessibilityLabel = @"Forward";
NSString* const kWebViewShellAddressFieldAccessibilityLabel = @"Address field";
NSString* const kWebViewShellJavaScriptDialogTextFieldAccessibilityIdentifier =
    @"WebViewShellJavaScriptDialogTextFieldAccessibilityIdentifier";

@interface ShellViewController () <CWVDownloadTaskDelegate,
                                   CWVNavigationDelegate,
                                   CWVUIDelegate,
                                   CWVScriptCommandHandler,
                                   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;
@property(nonatomic, strong) ShellAuthService* authService;
// The newly opened popup windows e.g., by JavaScript function "window.open()",
// HTML "<a target='_blank'>".
@property(nonatomic, strong) NSMutableArray<CWVWebView*>* popupWebViews;

- (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 popupWebViews = _popupWebViews;

- (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 = UIKeyboardTypeURL;
  _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.chromeLongPressAndForceTouchHandlingEnabled = NO;

  _authService = [[ShellAuthService alloc] init];
  CWVSyncController.dataSource = _authService;

  CWVWebViewConfiguration* configuration =
      [CWVWebViewConfiguration defaultConfiguration];
  configuration.syncController.delegate = self;
  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* action) {
                                   [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 {
  CWVAutofillDataManager* dataManager =
      _webView.configuration.autofillDataManager;
  [dataManager fetchPasswordsWithCompletionHandler:^(
                   NSArray<CWVPassword*>* _Nonnull passwords) {
    NSMutableArray<NSString*>* descriptions = [passwords
        valueForKey:NSStringFromSelector(@selector(debugDescription))];
    NSString* message = [descriptions componentsJoinedByString:@"\n\n"];

    UIAlertController* alertController = [self actionSheetWithTitle:@"Passwords"
                                                            message:message];
    for (CWVPassword* password in passwords) {
      NSString* title = [NSString
          stringWithFormat:@"Delete %@", @([passwords indexOfObject:password])];
      UIAlertAction* action =
          [UIAlertAction actionWithTitle:title
                                   style:UIAlertActionStyleDefault
                                 handler:^(UIAlertAction* action) {
                                   [dataManager deletePassword:password];
                                 }];
      [alertController addAction:action];
    }
    [alertController
        addAction:[UIAlertAction actionWithTitle:@"Done"
                                           style:UIAlertActionStyleCancel
                                         handler:nil]];
    [self presentViewController:alertController animated:YES completion:nil];
  }];
}

- (void)showSyncMenu {
  UIAlertController* alertController = [self actionSheetWithTitle:@"Sync menu"
                                                          message:nil];

  CWVSyncController* syncController = _webView.configuration.syncController;
  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 {
    }
  } 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:@"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.chromeLongPressAndForceTouchHandlingEnabled;
  NSString* contextMenuSwitchActionTitle = [NSString
      stringWithFormat:@"Use %@ context menu",
                       chromeContextMenuEnabled ? @"system" : @"Chrome"];
  [alertController
      addAction:[UIAlertAction
                    actionWithTitle:contextMenuSwitchActionTitle
                              style:UIAlertActionStyleDefault
                            handler:^(UIAlertAction* action) {
                              CWVWebView
                                  .chromeLongPressAndForceTouchHandlingEnabled =
                                  !chromeContextMenuEnabled;
                              NSLog(@"Chrome context menu is %@ now.",
                                    !chromeContextMenuEnabled ? @"OFF" : @"ON");
                              CWVWebViewConfiguration* configuration =
                                  weakSelf.webView.configuration;
                              [weakSelf removeWebView];
                              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];
                                       }]];

  if (self.downloadTask) {
    [alertController
        addAction:[UIAlertAction actionWithTitle:@"Cancel download"
                                           style:UIAlertActionStyleDefault
                                         handler:^(UIAlertAction* action) {
                                           [weakSelf.downloadTask cancel];
                                         }]];
  }

  [self presentViewController:alertController animated:YES completion:nil];
}

- (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 incognitoConfiguration]
                    : [CWVWebViewConfiguration defaultConfiguration];
  self.webView = [self createWebViewWithConfiguration:newConfiguration];
}

- (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/+/master: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 addScriptCommandHandler:self commandPrefix:@"test"];

  return webView;
}

- (void)removeWebView {
  [_webView removeFromSuperview];
  [_webView removeObserver:self forKeyPath:@"canGoBack"];
  [_webView removeObserver:self forKeyPath:@"canGoForward"];
  [_webView removeObserver:self forKeyPath:@"loading"];
  [_webView removeScriptCommandHandlerForCommandPrefix:@"test"];

  _webView = nil;
}

- (void)dealloc {
  [_webView removeObserver:self forKeyPath:@"canGoBack"];
  [_webView removeObserver:self forKeyPath:@"canGoForward"];
  [_webView removeObserver:self forKeyPath:@"loading"];
  [_webView removeScriptCommandHandlerForCommandPrefix:@"test"];
}

- (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 {
  UIAlertController* alertController = [UIAlertController
      alertControllerWithTitle:title
                       message:message
                preferredStyle:UIAlertControllerStyleActionSheet];
  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
    runContextMenuWithTitle:(NSString*)menuTitle
             forHTMLElement:(CWVHTMLElement*)element
                     inView:(UIView*)view
        userGestureLocation:(CGPoint)location {
  if (!element.hyperlink) {
    return;
  }

  UIAlertController* alert = [UIAlertController
      alertControllerWithTitle:menuTitle
                       message:nil
                preferredStyle:UIAlertControllerStyleActionSheet];
  alert.popoverPresentationController.sourceView = view;
  alert.popoverPresentationController.sourceRect =
      CGRectMake(location.x, location.y, 1.0, 1.0);

  void (^copyHandler)(UIAlertAction*) = ^(UIAlertAction* action) {
    NSDictionary* item = @{
      (NSString*)(kUTTypeURL) : element.hyperlink,
      (NSString*)(kUTTypeUTF8PlainText) : [[element.hyperlink absoluteString]
          dataUsingEncoding:NSUTF8StringEncoding],
    };
    [[UIPasteboard generalPasteboard] setItems:@[ item ]];
  };
  [alert addAction:[UIAlertAction actionWithTitle:@"Copy Link"
                                            style:UIAlertActionStyleDefault
                                          handler:copyHandler]];

  [alert addAction:[UIAlertAction actionWithTitle:@"Cancel"
                                            style:UIAlertActionStyleCancel
                                          handler:nil]];

  [self presentViewController:alert animated:YES completion:nil];
}

- (void)webView:(CWVWebView*)webView
    contextMenuConfigurationForLinkWithURL:(NSURL*)linkURL
                         completionHandler:
                             (void (^)(UIContextMenuConfiguration*))
                                 completionHandler API_AVAILABLE(ios(13.0)) {
  void (^copyHandler)(UIAction*) = ^(UIAction* action) {
    NSDictionary* item = @{
      (NSString*)(kUTTypeURL) : linkURL.absoluteString,
      (NSString*)(kUTTypeUTF8PlainText) :
          [linkURL.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 _){
                            }]
        ];
        NSString* menuTitle = [NSString
            stringWithFormat:@"iOS13 Context Menu: %@", linkURL.absoluteString];
        return [UIMenu menuWithTitle:menuTitle children:actions];
      }];

  completionHandler(configuration);
}

- (void)webView:(CWVWebView*)webView
    contextMenuWillPresentForLinkWithURL:(NSURL*)linkURL
    API_AVAILABLE(ios(13.0)) {
  NSLog(@"webView:contextMenuWillPresentForLinkWithURL: %@",
        linkURL.absoluteString);
}

- (void)webView:(CWVWebView*)webView
    contextMenuForLinkWithURL:(NSURL*)linkURL
       willCommitWithAnimator:
           (id<UIContextMenuInteractionCommitAnimating>)animator
    API_AVAILABLE(ios(13.0)) {
  NSLog(@"webView:contextMenuForLinkWithURL:willCommitWithAnimator: %@",
        linkURL.absoluteString);
}

- (void)webView:(CWVWebView*)webView
    contextMenuDidEndForLinkWithURL:(NSURL*)linkURL API_AVAILABLE(ios(13.0)) {
  NSLog(@"webView:contextMenuDidEndForLinkWithURL: %@", linkURL.absoluteString);
}

- (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

- (BOOL)webView:(CWVWebView*)webView
    shouldStartLoadWithRequest:(NSURLRequest*)request
                navigationType:(CWVNavigationType)navigationType {
  NSLog(@"%@", NSStringFromSelector(_cmd));
  return YES;
}

- (BOOL)webView:(CWVWebView*)webView
    shouldContinueLoadWithResponse:(NSURLResponse*)response
                      forMainFrame:(BOOL)forMainFrame {
  NSLog(@"%@", NSStringFromSelector(_cmd));
  return YES;
}

- (void)webViewDidStartProvisionalNavigation:(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/679895): 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)webViewWebContentProcessDidTerminate:(CWVWebView*)webView {
  NSLog(@"%@", NSStringFromSelector(_cmd));
}

- (BOOL)webView:(CWVWebView*)webView
    shouldPreviewElement:(CWVPreviewElementInfo*)elementInfo {
  NSLog(@"%@", NSStringFromSelector(_cmd));
  return YES;
}

- (UIViewController*)webView:(CWVWebView*)webView
    previewingViewControllerForElement:(CWVPreviewElementInfo*)elementInfo {
  NSLog(@"%@", NSStringFromSelector(_cmd));
  return nil;
}

- (void)webView:(CWVWebView*)webView
    commitPreviewingViewController:(UIViewController*)previewingViewController {
  NSLog(@"%@", NSStringFromSelector(_cmd));
}

- (void)webView:(CWVWebView*)webView
    didFailNavigationWithSSLError:(NSError*)error
                      overridable:(BOOL)overridable
                  decisionHandler:
                      (void (^)(CWVSSLErrorDecision))decisionHandler {
  NSLog(@"%@", NSStringFromSelector(_cmd));
  decisionHandler(CWVSSLErrorDecisionDoNothing);
}

- (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 CWVScriptCommandHandler

- (BOOL)webView:(CWVWebView*)webView
    handleScriptCommand:(nonnull CWVScriptCommand*)command
          fromMainFrame:(BOOL)fromMainFrame {
  NSLog(@"%@ command.content=%@", NSStringFromSelector(_cmd), command.content);
  return YES;
}

#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));
}

#pragma mark UIScrollViewDelegate

- (void)scrollViewWillBeginDragging:(UIScrollView*)scrollView {
  NSLog(@"%@", NSStringFromSelector(_cmd));
}

@end
