1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#import "ios/web_view/shell/shell_view_controller.h" 6 7#import <MobileCoreServices/MobileCoreServices.h> 8 9#import "ios/web_view/shell/shell_auth_service.h" 10#import "ios/web_view/shell/shell_autofill_delegate.h" 11#import "ios/web_view/shell/shell_translation_delegate.h" 12 13#if !defined(__has_feature) || !__has_feature(objc_arc) 14#error "This file requires ARC support." 15#endif 16 17// Externed accessibility identifier. 18NSString* const kWebViewShellBackButtonAccessibilityLabel = @"Back"; 19NSString* const kWebViewShellForwardButtonAccessibilityLabel = @"Forward"; 20NSString* const kWebViewShellAddressFieldAccessibilityLabel = @"Address field"; 21NSString* const kWebViewShellJavaScriptDialogTextFieldAccessibilityIdentifier = 22 @"WebViewShellJavaScriptDialogTextFieldAccessibilityIdentifier"; 23 24@interface ShellViewController () <CWVDownloadTaskDelegate, 25 CWVNavigationDelegate, 26 CWVUIDelegate, 27 CWVScriptCommandHandler, 28 CWVSyncControllerDelegate, 29 UIScrollViewDelegate, 30 UITextFieldDelegate> 31// Header containing navigation buttons and |field|. 32@property(nonatomic, strong) UIView* headerBackgroundView; 33// Header containing navigation buttons and |field|. 34@property(nonatomic, strong) UIView* headerContentView; 35// Button to navigate backwards. 36@property(nonatomic, strong) UIButton* backButton; 37// Button to navigate forwards. 38@property(nonatomic, strong) UIButton* forwardButton; 39// Button that either refresh the page or stops the page load. 40@property(nonatomic, strong) UIButton* reloadOrStopButton; 41// Button that shows the menu 42@property(nonatomic, strong) UIButton* menuButton; 43// Text field used for navigating to URLs. 44@property(nonatomic, strong) UITextField* field; 45// Container for |webView|. 46@property(nonatomic, strong) UIView* contentView; 47// Handles the autofill of the content displayed in |webView|. 48@property(nonatomic, strong) ShellAutofillDelegate* autofillDelegate; 49// Handles the translation of the content displayed in |webView|. 50@property(nonatomic, strong) ShellTranslationDelegate* translationDelegate; 51// The on-going download task if any. 52@property(nonatomic, strong, nullable) CWVDownloadTask* downloadTask; 53// The path to a local file which the download task is writing to. 54@property(nonatomic, strong, nullable) NSString* downloadFilePath; 55// A controller to show a "Share" menu for the downloaded file. 56@property(nonatomic, strong, nullable) 57 UIDocumentInteractionController* documentInteractionController; 58@property(nonatomic, strong) ShellAuthService* authService; 59// The newly opened popup windows e.g., by JavaScript function "window.open()", 60// HTML "<a target='_blank'>". 61@property(nonatomic, strong) NSMutableArray<CWVWebView*>* popupWebViews; 62 63- (void)back; 64- (void)forward; 65- (void)reloadOrStop; 66// Disconnects and release the |webView|. 67- (void)removeWebView; 68// Resets translate settings back to default. 69- (void)resetTranslateSettings; 70@end 71 72@implementation ShellViewController 73 74@synthesize autofillDelegate = _autofillDelegate; 75@synthesize backButton = _backButton; 76@synthesize contentView = _contentView; 77@synthesize field = _field; 78@synthesize forwardButton = _forwardButton; 79@synthesize reloadOrStopButton = _reloadOrStopButton; 80@synthesize menuButton = _menuButton; 81@synthesize headerBackgroundView = _headerBackgroundView; 82@synthesize headerContentView = _headerContentView; 83@synthesize webView = _webView; 84@synthesize translationDelegate = _translationDelegate; 85@synthesize downloadTask = _downloadTask; 86@synthesize downloadFilePath = _downloadFilePath; 87@synthesize documentInteractionController = _documentInteractionController; 88@synthesize authService = _authService; 89@synthesize popupWebViews = _popupWebViews; 90 91- (void)viewDidLoad { 92 [super viewDidLoad]; 93 94 self.popupWebViews = [[NSMutableArray alloc] init]; 95 96 // View creation. 97 self.headerBackgroundView = [[UIView alloc] init]; 98 self.headerContentView = [[UIView alloc] init]; 99 self.contentView = [[UIView alloc] init]; 100 self.backButton = [[UIButton alloc] init]; 101 self.forwardButton = [[UIButton alloc] init]; 102 self.reloadOrStopButton = [[UIButton alloc] init]; 103 self.menuButton = [[UIButton alloc] init]; 104 self.field = [[UITextField alloc] init]; 105 106 // View hierarchy. 107 [self.view addSubview:_headerBackgroundView]; 108 [self.view addSubview:_contentView]; 109 [_headerBackgroundView addSubview:_headerContentView]; 110 [_headerContentView addSubview:_backButton]; 111 [_headerContentView addSubview:_forwardButton]; 112 [_headerContentView addSubview:_reloadOrStopButton]; 113 [_headerContentView addSubview:_menuButton]; 114 [_headerContentView addSubview:_field]; 115 116 // Additional view setup. 117 _headerBackgroundView.backgroundColor = [UIColor colorWithRed:66.0 / 255.0 118 green:133.0 / 255.0 119 blue:244.0 / 255.0 120 alpha:1.0]; 121 122 [_backButton setImage:[UIImage imageNamed:@"ic_back"] 123 forState:UIControlStateNormal]; 124 _backButton.tintColor = [UIColor whiteColor]; 125 [_backButton addTarget:self 126 action:@selector(back) 127 forControlEvents:UIControlEventTouchUpInside]; 128 [_backButton addTarget:self 129 action:@selector(logBackStack) 130 forControlEvents:UIControlEventTouchDragOutside]; 131 [_backButton setAccessibilityLabel:kWebViewShellBackButtonAccessibilityLabel]; 132 133 [_forwardButton setImage:[UIImage imageNamed:@"ic_forward"] 134 forState:UIControlStateNormal]; 135 _forwardButton.tintColor = [UIColor whiteColor]; 136 [_forwardButton addTarget:self 137 action:@selector(forward) 138 forControlEvents:UIControlEventTouchUpInside]; 139 [_forwardButton addTarget:self 140 action:@selector(logForwardStack) 141 forControlEvents:UIControlEventTouchDragOutside]; 142 [_forwardButton 143 setAccessibilityLabel:kWebViewShellForwardButtonAccessibilityLabel]; 144 145 _reloadOrStopButton.tintColor = [UIColor whiteColor]; 146 [_reloadOrStopButton addTarget:self 147 action:@selector(reloadOrStop) 148 forControlEvents:UIControlEventTouchUpInside]; 149 150 _menuButton.tintColor = [UIColor whiteColor]; 151 [_menuButton setImage:[UIImage imageNamed:@"ic_menu"] 152 forState:UIControlStateNormal]; 153 [_menuButton addTarget:self 154 action:@selector(showMainMenu) 155 forControlEvents:UIControlEventTouchUpInside]; 156 157 _field.placeholder = @"Search or type URL"; 158 _field.backgroundColor = [UIColor whiteColor]; 159 _field.tintColor = _headerBackgroundView.backgroundColor; 160 [_field setContentHuggingPriority:UILayoutPriorityDefaultLow - 1 161 forAxis:UILayoutConstraintAxisHorizontal]; 162 _field.delegate = self; 163 _field.layer.cornerRadius = 2.0; 164 _field.keyboardType = UIKeyboardTypeURL; 165 _field.autocapitalizationType = UITextAutocapitalizationTypeNone; 166 _field.clearButtonMode = UITextFieldViewModeWhileEditing; 167 _field.autocorrectionType = UITextAutocorrectionTypeNo; 168 UIView* spacerView = [[UIView alloc] init]; 169 spacerView.frame = CGRectMake(0, 0, 8, 8); 170 _field.leftViewMode = UITextFieldViewModeAlways; 171 _field.leftView = spacerView; 172 173 // Constraints. 174 _headerBackgroundView.translatesAutoresizingMaskIntoConstraints = NO; 175 [NSLayoutConstraint activateConstraints:@[ 176 [_headerBackgroundView.topAnchor 177 constraintEqualToAnchor:self.view.topAnchor], 178 [_headerBackgroundView.leadingAnchor 179 constraintEqualToAnchor:self.view.leadingAnchor], 180 [_headerBackgroundView.trailingAnchor 181 constraintEqualToAnchor:self.view.trailingAnchor], 182 [_headerBackgroundView.bottomAnchor 183 constraintEqualToAnchor:_headerContentView.bottomAnchor], 184 ]]; 185 186 _headerContentView.translatesAutoresizingMaskIntoConstraints = NO; 187 [NSLayoutConstraint activateConstraints:@[ 188 [_headerContentView.topAnchor 189 constraintEqualToAnchor:_headerBackgroundView.safeAreaLayoutGuide 190 .topAnchor], 191 [_headerContentView.leadingAnchor 192 constraintEqualToAnchor:_headerBackgroundView.safeAreaLayoutGuide 193 .leadingAnchor], 194 [_headerContentView.trailingAnchor 195 constraintEqualToAnchor:_headerBackgroundView.safeAreaLayoutGuide 196 .trailingAnchor], 197 [_headerContentView.heightAnchor constraintEqualToConstant:56.0], 198 ]]; 199 200 _contentView.translatesAutoresizingMaskIntoConstraints = NO; 201 [NSLayoutConstraint activateConstraints:@[ 202 [_contentView.topAnchor 203 constraintEqualToAnchor:_headerBackgroundView.bottomAnchor], 204 [_contentView.leadingAnchor 205 constraintEqualToAnchor:self.view.leadingAnchor], 206 [_contentView.trailingAnchor 207 constraintEqualToAnchor:self.view.trailingAnchor], 208 [_contentView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor], 209 ]]; 210 211 _backButton.translatesAutoresizingMaskIntoConstraints = NO; 212 [NSLayoutConstraint activateConstraints:@[ 213 [_backButton.leadingAnchor 214 constraintEqualToAnchor:_headerContentView.safeAreaLayoutGuide 215 .leadingAnchor 216 constant:16.0], 217 [_backButton.centerYAnchor 218 constraintEqualToAnchor:_headerContentView.centerYAnchor], 219 ]]; 220 221 _forwardButton.translatesAutoresizingMaskIntoConstraints = NO; 222 [NSLayoutConstraint activateConstraints:@[ 223 [_forwardButton.leadingAnchor 224 constraintEqualToAnchor:_backButton.trailingAnchor 225 constant:16.0], 226 [_forwardButton.centerYAnchor 227 constraintEqualToAnchor:_headerContentView.centerYAnchor], 228 ]]; 229 230 _reloadOrStopButton.translatesAutoresizingMaskIntoConstraints = NO; 231 [NSLayoutConstraint activateConstraints:@[ 232 [_reloadOrStopButton.leadingAnchor 233 constraintEqualToAnchor:_forwardButton.trailingAnchor 234 constant:16.0], 235 [_reloadOrStopButton.centerYAnchor 236 constraintEqualToAnchor:_headerContentView.centerYAnchor], 237 ]]; 238 _menuButton.translatesAutoresizingMaskIntoConstraints = NO; 239 [NSLayoutConstraint activateConstraints:@[ 240 [_menuButton.leadingAnchor 241 constraintEqualToAnchor:_reloadOrStopButton.trailingAnchor 242 constant:16.0], 243 [_menuButton.centerYAnchor 244 constraintEqualToAnchor:_headerContentView.centerYAnchor], 245 ]]; 246 247 _field.translatesAutoresizingMaskIntoConstraints = NO; 248 [NSLayoutConstraint activateConstraints:@[ 249 [_field.leadingAnchor constraintEqualToAnchor:_menuButton.trailingAnchor 250 constant:16.0], 251 [_field.centerYAnchor 252 constraintEqualToAnchor:_headerContentView.centerYAnchor], 253 [_field.trailingAnchor 254 constraintEqualToAnchor:_headerContentView.safeAreaLayoutGuide 255 .trailingAnchor 256 constant:-16.0], 257 [_field.heightAnchor constraintEqualToConstant:32.0], 258 ]]; 259 260 [CWVWebView setUserAgentProduct:@"Dummy/1.0"]; 261 CWVWebView.chromeLongPressAndForceTouchHandlingEnabled = NO; 262 263 _authService = [[ShellAuthService alloc] init]; 264 CWVSyncController.dataSource = _authService; 265 266 CWVWebViewConfiguration* configuration = 267 [CWVWebViewConfiguration defaultConfiguration]; 268 configuration.syncController.delegate = self; 269 self.webView = [self createWebViewWithConfiguration:configuration]; 270} 271 272- (void)applicationFinishedRestoringState { 273 [super applicationFinishedRestoringState]; 274 275 // The scroll view is reset on state restoration. So the delegate must be 276 // reassigned. 277 self.webView.scrollView.delegate = self; 278} 279 280- (UIStatusBarStyle)preferredStatusBarStyle { 281 return UIStatusBarStyleLightContent; 282} 283 284- (void)observeValueForKeyPath:(NSString*)keyPath 285 ofObject:(id)object 286 change:(NSDictionary<NSKeyValueChangeKey, id>*)change 287 context:(void*)context { 288 if ([keyPath isEqualToString:@"canGoBack"]) { 289 _backButton.enabled = [_webView canGoBack]; 290 } else if ([keyPath isEqualToString:@"canGoForward"]) { 291 _forwardButton.enabled = [_webView canGoForward]; 292 } else if ([keyPath isEqualToString:@"loading"]) { 293 NSString* imageName = _webView.loading ? @"ic_stop" : @"ic_reload"; 294 [_reloadOrStopButton setImage:[UIImage imageNamed:imageName] 295 forState:UIControlStateNormal]; 296 } 297} 298 299- (void)back { 300 if ([_webView canGoBack]) { 301 [_webView goBack]; 302 } 303} 304 305- (void)logBackStack { 306 if (!_webView.canGoBack) { 307 return; 308 } 309 CWVBackForwardList* list = _webView.backForwardList; 310 CWVBackForwardListItemArray* backList = list.backList; 311 for (size_t i = 0; i < backList.count; i++) { 312 CWVBackForwardListItem* item = backList[i]; 313 NSLog(@"BackStack Item #%ld: <URL='%@', title='%@'>", i, item.URL, 314 item.title); 315 } 316} 317 318- (void)forward { 319 if ([_webView canGoForward]) { 320 [_webView goForward]; 321 } 322} 323 324- (void)logForwardStack { 325 if (!_webView.canGoForward) { 326 return; 327 } 328 CWVBackForwardList* list = _webView.backForwardList; 329 CWVBackForwardListItemArray* forwardList = list.forwardList; 330 for (size_t i = 0; i < forwardList.count; i++) { 331 CWVBackForwardListItem* item = forwardList[i]; 332 NSLog(@"ForwardStack Item #%ld: <URL='%@', title='%@'>", i, item.URL, 333 item.title); 334 } 335} 336 337- (void)reloadOrStop { 338 if (_webView.loading) { 339 [_webView stopLoading]; 340 } else { 341 [_webView reload]; 342 } 343} 344 345- (void)showAddressData { 346 CWVAutofillDataManager* dataManager = 347 _webView.configuration.autofillDataManager; 348 [dataManager fetchProfilesWithCompletionHandler:^( 349 NSArray<CWVAutofillProfile*>* _Nonnull profiles) { 350 NSMutableArray<NSString*>* descriptions = [profiles 351 valueForKey:NSStringFromSelector(@selector(debugDescription))]; 352 NSString* message = [descriptions componentsJoinedByString:@"\n\n"]; 353 UIAlertController* alertController = [self actionSheetWithTitle:@"Addresses" 354 message:message]; 355 for (CWVAutofillProfile* profile in profiles) { 356 NSString* title = [NSString 357 stringWithFormat:@"Delete %@", @([profiles indexOfObject:profile])]; 358 UIAlertAction* action = 359 [UIAlertAction actionWithTitle:title 360 style:UIAlertActionStyleDefault 361 handler:^(UIAlertAction* action) { 362 [dataManager deleteProfile:profile]; 363 }]; 364 [alertController addAction:action]; 365 } 366 [alertController 367 addAction:[UIAlertAction actionWithTitle:@"Done" 368 style:UIAlertActionStyleCancel 369 handler:nil]]; 370 371 [self presentViewController:alertController animated:YES completion:nil]; 372 }]; 373} 374 375- (void)showCreditCardData { 376 CWVAutofillDataManager* dataManager = 377 _webView.configuration.autofillDataManager; 378 [dataManager fetchCreditCardsWithCompletionHandler:^( 379 NSArray<CWVCreditCard*>* _Nonnull creditCards) { 380 NSMutableArray<NSString*>* descriptions = [creditCards 381 valueForKey:NSStringFromSelector(@selector(debugDescription))]; 382 NSString* message = [descriptions componentsJoinedByString:@"\n\n"]; 383 UIAlertController* alertController = 384 [self actionSheetWithTitle:@"Credit cards" message:message]; 385 __weak ShellViewController* weakSelf = self; 386 [alertController 387 addAction:[UIAlertAction 388 actionWithTitle:@"Manage Google pay cards" 389 style:UIAlertActionStyleDefault 390 handler:^(UIAlertAction* action) { 391 __weak ShellViewController* strongSelf = 392 weakSelf; 393 NSString* URL; 394 if ([CWVFlags sharedInstance] 395 .usesSyncAndWalletSandbox) { 396 URL = @"https://pay.sandbox.google.com/" 397 @"payments/home#paymentMethods"; 398 } else { 399 URL = @"https://pay.google.com/payments/" 400 @"home#paymentMethods"; 401 } 402 NSURLRequest* request = [NSURLRequest 403 requestWithURL:[NSURL URLWithString:URL]]; 404 [strongSelf.webView loadRequest:request]; 405 }]]; 406 [alertController 407 addAction:[UIAlertAction actionWithTitle:@"Done" 408 style:UIAlertActionStyleCancel 409 handler:nil]]; 410 [self presentViewController:alertController animated:YES completion:nil]; 411 }]; 412} 413 414- (void)showPasswordData { 415 CWVAutofillDataManager* dataManager = 416 _webView.configuration.autofillDataManager; 417 [dataManager fetchPasswordsWithCompletionHandler:^( 418 NSArray<CWVPassword*>* _Nonnull passwords) { 419 NSMutableArray<NSString*>* descriptions = [passwords 420 valueForKey:NSStringFromSelector(@selector(debugDescription))]; 421 NSString* message = [descriptions componentsJoinedByString:@"\n\n"]; 422 423 UIAlertController* alertController = [self actionSheetWithTitle:@"Passwords" 424 message:message]; 425 for (CWVPassword* password in passwords) { 426 NSString* title = [NSString 427 stringWithFormat:@"Delete %@", @([passwords indexOfObject:password])]; 428 UIAlertAction* action = 429 [UIAlertAction actionWithTitle:title 430 style:UIAlertActionStyleDefault 431 handler:^(UIAlertAction* action) { 432 [dataManager deletePassword:password]; 433 }]; 434 [alertController addAction:action]; 435 } 436 [alertController 437 addAction:[UIAlertAction actionWithTitle:@"Done" 438 style:UIAlertActionStyleCancel 439 handler:nil]]; 440 [self presentViewController:alertController animated:YES completion:nil]; 441 }]; 442} 443 444- (void)showSyncMenu { 445 UIAlertController* alertController = [self actionSheetWithTitle:@"Sync menu" 446 message:nil]; 447 448 CWVSyncController* syncController = _webView.configuration.syncController; 449 CWVIdentity* currentIdentity = syncController.currentIdentity; 450 __weak ShellViewController* weakSelf = self; 451 if (currentIdentity) { 452 NSString* title = [NSString 453 stringWithFormat:@"Stop syncing for %@", currentIdentity.email]; 454 [alertController 455 addAction:[UIAlertAction 456 actionWithTitle:title 457 style:UIAlertActionStyleDefault 458 handler:^(UIAlertAction* action) { 459 [syncController stopSyncAndClearIdentity]; 460 }]]; 461 462 if (syncController.passphraseNeeded) { 463 [alertController 464 addAction:[UIAlertAction 465 actionWithTitle:@"Unlock using passphrase" 466 style:UIAlertActionStyleDefault 467 handler:^(UIAlertAction* action) { 468 [weakSelf showPassphraseUnlockAlert]; 469 }]]; 470 } else { 471 } 472 } else { 473 for (CWVIdentity* identity in [_authService identities]) { 474 NSString* title = 475 [NSString stringWithFormat:@"Start sync with %@", identity.email]; 476 [alertController 477 addAction:[UIAlertAction 478 actionWithTitle:title 479 style:UIAlertActionStyleDefault 480 handler:^(UIAlertAction* action) { 481 [syncController 482 startSyncWithIdentity:identity]; 483 }]]; 484 } 485 486 NSString* sandboxTitle = [CWVFlags sharedInstance].usesSyncAndWalletSandbox 487 ? @"Use production sync/wallet" 488 : @"Use sandbox sync/wallet"; 489 [alertController 490 addAction:[UIAlertAction actionWithTitle:sandboxTitle 491 style:UIAlertActionStyleDefault 492 handler:^(UIAlertAction* action) { 493 [CWVFlags sharedInstance] 494 .usesSyncAndWalletSandbox ^= YES; 495 }]]; 496 } 497 [alertController 498 addAction:[UIAlertAction actionWithTitle:@"Show autofill data" 499 style:UIAlertActionStyleDefault 500 handler:^(UIAlertAction* action) { 501 [weakSelf showAddressData]; 502 }]]; 503 [alertController 504 addAction:[UIAlertAction actionWithTitle:@"Show credit card data" 505 style:UIAlertActionStyleDefault 506 handler:^(UIAlertAction* action) { 507 [weakSelf showCreditCardData]; 508 }]]; 509 [alertController 510 addAction:[UIAlertAction actionWithTitle:@"Show password data" 511 style:UIAlertActionStyleDefault 512 handler:^(UIAlertAction* action) { 513 [weakSelf showPasswordData]; 514 }]]; 515 [alertController 516 addAction:[UIAlertAction actionWithTitle:@"Cancel" 517 style:UIAlertActionStyleCancel 518 handler:nil]]; 519 520 [self presentViewController:alertController animated:YES completion:nil]; 521} 522 523- (void)showPassphraseUnlockAlert { 524 UIAlertController* alertController = 525 [UIAlertController alertControllerWithTitle:@"Unlock sync" 526 message:@"Enter passphrase" 527 preferredStyle:UIAlertControllerStyleAlert]; 528 529 __weak UIAlertController* weakAlertController = alertController; 530 CWVSyncController* syncController = _webView.configuration.syncController; 531 UIAlertAction* submit = [UIAlertAction 532 actionWithTitle:@"Unlock" 533 style:UIAlertActionStyleDefault 534 handler:^(UIAlertAction* action) { 535 UITextField* textField = 536 weakAlertController.textFields.firstObject; 537 NSString* passphrase = textField.text; 538 BOOL result = [syncController unlockWithPassphrase:passphrase]; 539 NSLog(@"Sync passphrase unlock result: %d", result); 540 }]; 541 542 [alertController addAction:submit]; 543 544 UIAlertAction* cancel = 545 [UIAlertAction actionWithTitle:@"Cancel" 546 style:UIAlertActionStyleCancel 547 handler:nil]; 548 [alertController addAction:cancel]; 549 550 [alertController 551 addTextFieldWithConfigurationHandler:^(UITextField* textField) { 552 textField.placeholder = @"passphrase"; 553 textField.keyboardType = UIKeyboardTypeDefault; 554 }]; 555 556 [self presentViewController:alertController animated:YES completion:nil]; 557} 558 559- (void)showMainMenu { 560 UIAlertController* alertController = [self actionSheetWithTitle:@"Main menu" 561 message:nil]; 562 [alertController 563 addAction:[UIAlertAction actionWithTitle:@"Cancel" 564 style:UIAlertActionStyleCancel 565 handler:nil]]; 566 567 __weak ShellViewController* weakSelf = self; 568 569 // Toggles the incognito mode. 570 NSString* incognitoActionTitle = _webView.configuration.persistent 571 ? @"Enter incognito" 572 : @"Exit incognito"; 573 [alertController 574 addAction:[UIAlertAction actionWithTitle:incognitoActionTitle 575 style:UIAlertActionStyleDefault 576 handler:^(UIAlertAction* action) { 577 [weakSelf toggleIncognito]; 578 }]]; 579 580 // Removes the web view from the view hierarchy, releases it, and recreates 581 // the web view with the same configuration. This is for testing deallocation 582 // and sharing configuration. 583 [alertController 584 addAction:[UIAlertAction 585 actionWithTitle:@"Recreate web view" 586 style:UIAlertActionStyleDefault 587 handler:^(UIAlertAction* action) { 588 CWVWebViewConfiguration* configuration = 589 weakSelf.webView.configuration; 590 [weakSelf removeWebView]; 591 weakSelf.webView = [weakSelf 592 createWebViewWithConfiguration:configuration]; 593 }]]; 594 595 // Developers can choose to use system or Chrome context menu in the shell 596 // app. This will also recreate the web view. 597 BOOL chromeContextMenuEnabled = 598 CWVWebView.chromeLongPressAndForceTouchHandlingEnabled; 599 NSString* contextMenuSwitchActionTitle = [NSString 600 stringWithFormat:@"Use %@ context menu", 601 chromeContextMenuEnabled ? @"system" : @"Chrome"]; 602 [alertController 603 addAction:[UIAlertAction 604 actionWithTitle:contextMenuSwitchActionTitle 605 style:UIAlertActionStyleDefault 606 handler:^(UIAlertAction* action) { 607 CWVWebView 608 .chromeLongPressAndForceTouchHandlingEnabled = 609 !chromeContextMenuEnabled; 610 NSLog(@"Chrome context menu is %@ now.", 611 !chromeContextMenuEnabled ? @"OFF" : @"ON"); 612 CWVWebViewConfiguration* configuration = 613 weakSelf.webView.configuration; 614 [weakSelf removeWebView]; 615 weakSelf.webView = [weakSelf 616 createWebViewWithConfiguration:configuration]; 617 }]]; 618 619 // Resets all translation settings to default values. 620 [alertController 621 addAction:[UIAlertAction actionWithTitle:@"Reset translate settings" 622 style:UIAlertActionStyleDefault 623 handler:^(UIAlertAction* action) { 624 [weakSelf resetTranslateSettings]; 625 }]]; 626 627 [alertController 628 addAction:[UIAlertAction actionWithTitle:@"Request translation offer" 629 style:UIAlertActionStyleDefault 630 handler:^(UIAlertAction* action) { 631 [weakSelf requestTranslationOffer]; 632 }]]; 633 634 // Shows sync menu. 635 [alertController 636 addAction:[UIAlertAction actionWithTitle:@"Sync menu" 637 style:UIAlertActionStyleDefault 638 handler:^(UIAlertAction* action) { 639 [weakSelf showSyncMenu]; 640 }]]; 641 642 if (self.downloadTask) { 643 [alertController 644 addAction:[UIAlertAction actionWithTitle:@"Cancel download" 645 style:UIAlertActionStyleDefault 646 handler:^(UIAlertAction* action) { 647 [weakSelf.downloadTask cancel]; 648 }]]; 649 } 650 651 [self presentViewController:alertController animated:YES completion:nil]; 652} 653 654- (void)resetTranslateSettings { 655 CWVWebViewConfiguration* configuration = 656 [CWVWebViewConfiguration defaultConfiguration]; 657 [configuration.preferences resetTranslationSettings]; 658} 659 660- (void)requestTranslationOffer { 661 BOOL offered = [_webView.translationController requestTranslationOffer]; 662 NSLog(@"Manual translation was offered: %d", offered); 663} 664 665- (void)toggleIncognito { 666 BOOL wasPersistent = _webView.configuration.persistent; 667 [self removeWebView]; 668 CWVWebViewConfiguration* newConfiguration = 669 wasPersistent ? [CWVWebViewConfiguration incognitoConfiguration] 670 : [CWVWebViewConfiguration defaultConfiguration]; 671 self.webView = [self createWebViewWithConfiguration:newConfiguration]; 672} 673 674- (CWVWebView*)createWebViewWithConfiguration: 675 (CWVWebViewConfiguration*)configuration { 676 // Set a non empty CGRect to avoid DCHECKs that occur when a load happens 677 // after state restoration, and before the view hierarchy is laid out for the 678 // first time. 679 // https://source.chromium.org/chromium/chromium/src/+/master:ios/web/web_state/ui/crw_web_request_controller.mm;l=518;drc=df887034106ef438611326745a7cd276eedd4953 680 CGRect frame = CGRectMake(0, 0, 1, 1); 681 CWVWebView* webView = [[CWVWebView alloc] initWithFrame:frame 682 configuration:configuration]; 683 [_contentView addSubview:webView]; 684 685 // Gives a restoration identifier so that state restoration works. 686 webView.restorationIdentifier = @"webView"; 687 688 // Configure delegates. 689 webView.navigationDelegate = self; 690 webView.UIDelegate = self; 691 _translationDelegate = [[ShellTranslationDelegate alloc] init]; 692 webView.translationController.delegate = _translationDelegate; 693 _autofillDelegate = [[ShellAutofillDelegate alloc] init]; 694 webView.autofillController.delegate = _autofillDelegate; 695 webView.scrollView.delegate = self; 696 697 // Constraints. 698 webView.translatesAutoresizingMaskIntoConstraints = NO; 699 [NSLayoutConstraint activateConstraints:@[ 700 [webView.topAnchor 701 constraintEqualToAnchor:_contentView.safeAreaLayoutGuide.topAnchor], 702 [webView.leadingAnchor 703 constraintEqualToAnchor:_contentView.safeAreaLayoutGuide.leadingAnchor], 704 [webView.trailingAnchor 705 constraintEqualToAnchor:_contentView.safeAreaLayoutGuide 706 .trailingAnchor], 707 [webView.bottomAnchor 708 constraintEqualToAnchor:_contentView.safeAreaLayoutGuide.bottomAnchor], 709 ]]; 710 711 [webView addObserver:self 712 forKeyPath:@"canGoBack" 713 options:NSKeyValueObservingOptionNew | 714 NSKeyValueObservingOptionInitial 715 context:nil]; 716 [webView addObserver:self 717 forKeyPath:@"canGoForward" 718 options:NSKeyValueObservingOptionNew | 719 NSKeyValueObservingOptionInitial 720 context:nil]; 721 [webView addObserver:self 722 forKeyPath:@"loading" 723 options:NSKeyValueObservingOptionNew | 724 NSKeyValueObservingOptionInitial 725 context:nil]; 726 727 [webView addScriptCommandHandler:self commandPrefix:@"test"]; 728 729 return webView; 730} 731 732- (void)removeWebView { 733 [_webView removeFromSuperview]; 734 [_webView removeObserver:self forKeyPath:@"canGoBack"]; 735 [_webView removeObserver:self forKeyPath:@"canGoForward"]; 736 [_webView removeObserver:self forKeyPath:@"loading"]; 737 [_webView removeScriptCommandHandlerForCommandPrefix:@"test"]; 738 739 _webView = nil; 740} 741 742- (void)dealloc { 743 [_webView removeObserver:self forKeyPath:@"canGoBack"]; 744 [_webView removeObserver:self forKeyPath:@"canGoForward"]; 745 [_webView removeObserver:self forKeyPath:@"loading"]; 746 [_webView removeScriptCommandHandlerForCommandPrefix:@"test"]; 747} 748 749- (BOOL)textFieldShouldReturn:(UITextField*)field { 750 NSURL* URL = [NSURL URLWithString:field.text]; 751 if (URL.scheme.length == 0) { 752 NSString* enteredText = field.text; 753 enteredText = 754 [enteredText stringByAddingPercentEncodingWithAllowedCharacters: 755 [NSCharacterSet URLQueryAllowedCharacterSet]]; 756 enteredText = [NSString 757 stringWithFormat:@"https://www.google.com/search?q=%@", enteredText]; 758 URL = [NSURL URLWithString:enteredText]; 759 } 760 NSURLRequest* request = [NSURLRequest requestWithURL:URL]; 761 [_webView loadRequest:request]; 762 [field resignFirstResponder]; 763 [self updateToolbar]; 764 return YES; 765} 766 767- (void)updateToolbar { 768 // Do not update the URL if the text field is currently being edited. 769 if ([_field isFirstResponder]) { 770 return; 771 } 772 773 [_field setText:[[_webView visibleURL] absoluteString]]; 774} 775 776- (UIAlertController*)actionSheetWithTitle:(nullable NSString*)title 777 message:(nullable NSString*)message { 778 UIAlertController* alertController = [UIAlertController 779 alertControllerWithTitle:title 780 message:message 781 preferredStyle:UIAlertControllerStyleActionSheet]; 782 alertController.popoverPresentationController.sourceView = _menuButton; 783 alertController.popoverPresentationController.sourceRect = 784 CGRectMake(CGRectGetWidth(_menuButton.bounds) / 2, 785 CGRectGetHeight(_menuButton.bounds), 1, 1); 786 return alertController; 787} 788 789- (void)closePopupWebView { 790 if (self.popupWebViews.count) { 791 [self.popupWebViews.lastObject removeFromSuperview]; 792 [self.popupWebViews removeLastObject]; 793 } 794} 795 796#pragma mark CWVUIDelegate methods 797 798- (CWVWebView*)webView:(CWVWebView*)webView 799 createWebViewWithConfiguration:(CWVWebViewConfiguration*)configuration 800 forNavigationAction:(CWVNavigationAction*)action { 801 NSLog(@"Create new CWVWebView for %@. User initiated? %@", action.request.URL, 802 action.userInitiated ? @"Yes" : @"No"); 803 804 CWVWebView* newWebView = [self createWebViewWithConfiguration:configuration]; 805 [self.popupWebViews addObject:newWebView]; 806 807 UIButton* closeWindowButton = [[UIButton alloc] init]; 808 [closeWindowButton setImage:[UIImage imageNamed:@"ic_stop"] 809 forState:UIControlStateNormal]; 810 closeWindowButton.tintColor = [UIColor blackColor]; 811 closeWindowButton.backgroundColor = [UIColor whiteColor]; 812 [closeWindowButton addTarget:self 813 action:@selector(closePopupWebView) 814 forControlEvents:UIControlEventTouchUpInside]; 815 816 [newWebView addSubview:closeWindowButton]; 817 closeWindowButton.translatesAutoresizingMaskIntoConstraints = NO; 818 [NSLayoutConstraint activateConstraints:@[ 819 [closeWindowButton.topAnchor constraintEqualToAnchor:newWebView.topAnchor 820 constant:16.0], 821 [closeWindowButton.centerXAnchor 822 constraintEqualToAnchor:newWebView.centerXAnchor], 823 ]]; 824 825 return newWebView; 826} 827 828- (void)webViewDidClose:(CWVWebView*)webView { 829 NSLog(@"webViewDidClose"); 830} 831 832- (void)webView:(CWVWebView*)webView 833 runContextMenuWithTitle:(NSString*)menuTitle 834 forHTMLElement:(CWVHTMLElement*)element 835 inView:(UIView*)view 836 userGestureLocation:(CGPoint)location { 837 if (!element.hyperlink) { 838 return; 839 } 840 841 UIAlertController* alert = [UIAlertController 842 alertControllerWithTitle:menuTitle 843 message:nil 844 preferredStyle:UIAlertControllerStyleActionSheet]; 845 alert.popoverPresentationController.sourceView = view; 846 alert.popoverPresentationController.sourceRect = 847 CGRectMake(location.x, location.y, 1.0, 1.0); 848 849 void (^copyHandler)(UIAlertAction*) = ^(UIAlertAction* action) { 850 NSDictionary* item = @{ 851 (NSString*)(kUTTypeURL) : element.hyperlink, 852 (NSString*)(kUTTypeUTF8PlainText) : [[element.hyperlink absoluteString] 853 dataUsingEncoding:NSUTF8StringEncoding], 854 }; 855 [[UIPasteboard generalPasteboard] setItems:@[ item ]]; 856 }; 857 [alert addAction:[UIAlertAction actionWithTitle:@"Copy Link" 858 style:UIAlertActionStyleDefault 859 handler:copyHandler]]; 860 861 [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" 862 style:UIAlertActionStyleCancel 863 handler:nil]]; 864 865 [self presentViewController:alert animated:YES completion:nil]; 866} 867 868- (void)webView:(CWVWebView*)webView 869 contextMenuConfigurationForLinkWithURL:(NSURL*)linkURL 870 completionHandler: 871 (void (^)(UIContextMenuConfiguration*)) 872 completionHandler API_AVAILABLE(ios(13.0)) { 873 void (^copyHandler)(UIAction*) = ^(UIAction* action) { 874 NSDictionary* item = @{ 875 (NSString*)(kUTTypeURL) : linkURL.absoluteString, 876 (NSString*)(kUTTypeUTF8PlainText) : 877 [linkURL.absoluteString dataUsingEncoding:NSUTF8StringEncoding], 878 }; 879 [[UIPasteboard generalPasteboard] setItems:@[ item ]]; 880 }; 881 882 UIContextMenuConfiguration* configuration = [UIContextMenuConfiguration 883 configurationWithIdentifier:nil 884 previewProvider:^{ 885 UIViewController* controller = [[UIViewController alloc] init]; 886 CGRect frame = CGRectMake(10, 200, 200, 21); 887 UILabel* label = [[UILabel alloc] initWithFrame:frame]; 888 label.text = @"iOS13 Preview Page"; 889 [controller.view addSubview:label]; 890 return controller; 891 } 892 actionProvider:^(id _) { 893 NSArray* actions = @[ 894 [UIAction actionWithTitle:@"Copy Link" 895 image:nil 896 identifier:nil 897 handler:copyHandler], 898 [UIAction actionWithTitle:@"Cancel" 899 image:nil 900 identifier:nil 901 handler:^(id _){ 902 }] 903 ]; 904 NSString* menuTitle = [NSString 905 stringWithFormat:@"iOS13 Context Menu: %@", linkURL.absoluteString]; 906 return [UIMenu menuWithTitle:menuTitle children:actions]; 907 }]; 908 909 completionHandler(configuration); 910} 911 912- (void)webView:(CWVWebView*)webView 913 contextMenuWillPresentForLinkWithURL:(NSURL*)linkURL 914 API_AVAILABLE(ios(13.0)) { 915 NSLog(@"webView:contextMenuWillPresentForLinkWithURL: %@", 916 linkURL.absoluteString); 917} 918 919- (void)webView:(CWVWebView*)webView 920 contextMenuForLinkWithURL:(NSURL*)linkURL 921 willCommitWithAnimator: 922 (id<UIContextMenuInteractionCommitAnimating>)animator 923 API_AVAILABLE(ios(13.0)) { 924 NSLog(@"webView:contextMenuForLinkWithURL:willCommitWithAnimator: %@", 925 linkURL.absoluteString); 926} 927 928- (void)webView:(CWVWebView*)webView 929 contextMenuDidEndForLinkWithURL:(NSURL*)linkURL API_AVAILABLE(ios(13.0)) { 930 NSLog(@"webView:contextMenuDidEndForLinkWithURL: %@", linkURL.absoluteString); 931} 932 933- (void)webView:(CWVWebView*)webView 934 runJavaScriptAlertPanelWithMessage:(NSString*)message 935 pageURL:(NSURL*)URL 936 completionHandler:(void (^)(void))handler { 937 UIAlertController* alert = 938 [UIAlertController alertControllerWithTitle:nil 939 message:message 940 preferredStyle:UIAlertControllerStyleAlert]; 941 942 [alert addAction:[UIAlertAction actionWithTitle:@"Ok" 943 style:UIAlertActionStyleDefault 944 handler:^(UIAlertAction* action) { 945 handler(); 946 }]]; 947 948 [self presentViewController:alert animated:YES completion:nil]; 949} 950 951- (void)webView:(CWVWebView*)webView 952 runJavaScriptConfirmPanelWithMessage:(NSString*)message 953 pageURL:(NSURL*)URL 954 completionHandler:(void (^)(BOOL))handler { 955 UIAlertController* alert = 956 [UIAlertController alertControllerWithTitle:nil 957 message:message 958 preferredStyle:UIAlertControllerStyleAlert]; 959 960 [alert addAction:[UIAlertAction actionWithTitle:@"Ok" 961 style:UIAlertActionStyleDefault 962 handler:^(UIAlertAction* action) { 963 handler(YES); 964 }]]; 965 [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" 966 style:UIAlertActionStyleCancel 967 handler:^(UIAlertAction* action) { 968 handler(NO); 969 }]]; 970 971 [self presentViewController:alert animated:YES completion:nil]; 972} 973 974- (void)webView:(CWVWebView*)webView 975 runJavaScriptTextInputPanelWithPrompt:(NSString*)prompt 976 defaultText:(NSString*)defaultText 977 pageURL:(NSURL*)URL 978 completionHandler:(void (^)(NSString*))handler { 979 UIAlertController* alert = 980 [UIAlertController alertControllerWithTitle:nil 981 message:prompt 982 preferredStyle:UIAlertControllerStyleAlert]; 983 984 [alert addTextFieldWithConfigurationHandler:^(UITextField* textField) { 985 textField.text = defaultText; 986 textField.accessibilityIdentifier = 987 kWebViewShellJavaScriptDialogTextFieldAccessibilityIdentifier; 988 }]; 989 990 __weak UIAlertController* weakAlert = alert; 991 [alert addAction:[UIAlertAction 992 actionWithTitle:@"Ok" 993 style:UIAlertActionStyleDefault 994 handler:^(UIAlertAction* action) { 995 NSString* textInput = 996 weakAlert.textFields.firstObject.text; 997 handler(textInput); 998 }]]; 999 [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" 1000 style:UIAlertActionStyleCancel 1001 handler:^(UIAlertAction* action) { 1002 handler(nil); 1003 }]]; 1004 1005 [self presentViewController:alert animated:YES completion:nil]; 1006} 1007 1008- (void)webView:(CWVWebView*)webView 1009 didLoadFavicons:(NSArray<CWVFavicon*>*)favIcons { 1010 NSLog(@"%@", NSStringFromSelector(_cmd)); 1011} 1012 1013#pragma mark CWVNavigationDelegate methods 1014 1015- (BOOL)webView:(CWVWebView*)webView 1016 shouldStartLoadWithRequest:(NSURLRequest*)request 1017 navigationType:(CWVNavigationType)navigationType { 1018 NSLog(@"%@", NSStringFromSelector(_cmd)); 1019 return YES; 1020} 1021 1022- (BOOL)webView:(CWVWebView*)webView 1023 shouldContinueLoadWithResponse:(NSURLResponse*)response 1024 forMainFrame:(BOOL)forMainFrame { 1025 NSLog(@"%@", NSStringFromSelector(_cmd)); 1026 return YES; 1027} 1028 1029- (void)webViewDidStartProvisionalNavigation:(CWVWebView*)webView { 1030 NSLog(@"%@", NSStringFromSelector(_cmd)); 1031 [self updateToolbar]; 1032} 1033 1034- (void)webViewDidCommitNavigation:(CWVWebView*)webView { 1035 NSLog(@"%@", NSStringFromSelector(_cmd)); 1036 [self updateToolbar]; 1037} 1038 1039- (void)webViewDidFinishNavigation:(CWVWebView*)webView { 1040 NSLog(@"%@", NSStringFromSelector(_cmd)); 1041 // TODO(crbug.com/679895): Add some visual indication that the page load has 1042 // finished. 1043 [self updateToolbar]; 1044} 1045 1046- (void)webView:(CWVWebView*)webView 1047 didFailNavigationWithError:(NSError*)error { 1048 NSLog(@"%@", NSStringFromSelector(_cmd)); 1049 [self updateToolbar]; 1050} 1051 1052- (void)webViewWebContentProcessDidTerminate:(CWVWebView*)webView { 1053 NSLog(@"%@", NSStringFromSelector(_cmd)); 1054} 1055 1056- (BOOL)webView:(CWVWebView*)webView 1057 shouldPreviewElement:(CWVPreviewElementInfo*)elementInfo { 1058 NSLog(@"%@", NSStringFromSelector(_cmd)); 1059 return YES; 1060} 1061 1062- (UIViewController*)webView:(CWVWebView*)webView 1063 previewingViewControllerForElement:(CWVPreviewElementInfo*)elementInfo { 1064 NSLog(@"%@", NSStringFromSelector(_cmd)); 1065 return nil; 1066} 1067 1068- (void)webView:(CWVWebView*)webView 1069 commitPreviewingViewController:(UIViewController*)previewingViewController { 1070 NSLog(@"%@", NSStringFromSelector(_cmd)); 1071} 1072 1073- (void)webView:(CWVWebView*)webView 1074 didFailNavigationWithSSLError:(NSError*)error 1075 overridable:(BOOL)overridable 1076 decisionHandler: 1077 (void (^)(CWVSSLErrorDecision))decisionHandler { 1078 NSLog(@"%@", NSStringFromSelector(_cmd)); 1079 decisionHandler(CWVSSLErrorDecisionDoNothing); 1080} 1081 1082- (void)webView:(CWVWebView*)webView 1083 didRequestDownloadWithTask:(CWVDownloadTask*)task { 1084 NSLog(@"%@", NSStringFromSelector(_cmd)); 1085 self.downloadTask = task; 1086 NSString* documentDirectoryPath = NSSearchPathForDirectoriesInDomains( 1087 NSDocumentDirectory, NSUserDomainMask, YES)[0]; 1088 self.downloadFilePath = [documentDirectoryPath 1089 stringByAppendingPathComponent:task.suggestedFileName]; 1090 task.delegate = self; 1091 [task startDownloadToLocalFileAtPath:self.downloadFilePath]; 1092} 1093 1094#pragma mark CWVScriptCommandHandler 1095 1096- (BOOL)webView:(CWVWebView*)webView 1097 handleScriptCommand:(nonnull CWVScriptCommand*)command 1098 fromMainFrame:(BOOL)fromMainFrame { 1099 NSLog(@"%@ command.content=%@", NSStringFromSelector(_cmd), command.content); 1100 return YES; 1101} 1102 1103#pragma mark CWVDownloadTaskDelegate 1104 1105- (void)downloadTask:(CWVDownloadTask*)downloadTask 1106 didFinishWithError:(nullable NSError*)error { 1107 NSLog(@"%@", NSStringFromSelector(_cmd)); 1108 if (!error) { 1109 NSURL* url = [NSURL fileURLWithPath:self.downloadFilePath]; 1110 self.documentInteractionController = 1111 [UIDocumentInteractionController interactionControllerWithURL:url]; 1112 [self.documentInteractionController presentOptionsMenuFromRect:CGRectZero 1113 inView:self.view 1114 animated:YES]; 1115 } 1116 self.downloadTask = nil; 1117 self.downloadFilePath = nil; 1118} 1119 1120- (void)downloadTaskProgressDidChange:(CWVDownloadTask*)downloadTask { 1121 NSLog(@"%@", NSStringFromSelector(_cmd)); 1122} 1123 1124#pragma mark CWVSyncControllerDelegate 1125 1126- (void)syncControllerDidStartSync:(CWVSyncController*)syncController { 1127 NSLog(@"%@", NSStringFromSelector(_cmd)); 1128} 1129 1130- (void)syncController:(CWVSyncController*)syncController 1131 didFailWithError:(NSError*)error { 1132 NSLog(@"%@:%@", NSStringFromSelector(_cmd), error); 1133} 1134 1135- (void)syncControllerDidStopSync:(CWVSyncController*)syncController { 1136 NSLog(@"%@", NSStringFromSelector(_cmd)); 1137} 1138 1139#pragma mark UIScrollViewDelegate 1140 1141- (void)scrollViewWillBeginDragging:(UIScrollView*)scrollView { 1142 NSLog(@"%@", NSStringFromSelector(_cmd)); 1143} 1144 1145@end 1146