|  | // 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/shell/view_controller.h" | 
|  |  | 
|  | #import <MobileCoreServices/MobileCoreServices.h> | 
|  |  | 
|  | #include <stdint.h> | 
|  |  | 
|  | #include <memory> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/strings/sys_string_conversions.h" | 
|  | #import "ios/web/public/navigation/navigation_manager.h" | 
|  | #include "ios/web/public/navigation/referrer.h" | 
|  | #import "ios/web/public/ui/context_menu_params.h" | 
|  | #import "ios/web/public/web_state.h" | 
|  | #import "ios/web/public/web_state_delegate_bridge.h" | 
|  | #import "ios/web/public/web_state_observer_bridge.h" | 
|  | #import "net/base/mac/url_conversions.h" | 
|  | #include "ui/base/page_transition_types.h" | 
|  |  | 
|  | #if !defined(__has_feature) || !__has_feature(objc_arc) | 
|  | #error "This file requires ARC support." | 
|  | #endif | 
|  |  | 
|  | NSString* const kWebShellBackButtonAccessibilityLabel = @"Back"; | 
|  | NSString* const kWebShellForwardButtonAccessibilityLabel = @"Forward"; | 
|  | NSString* const kWebShellAddressFieldAccessibilityLabel = @"Address field"; | 
|  |  | 
|  | using web::NavigationManager; | 
|  |  | 
|  | @interface ViewController ()<CRWWebStateDelegate, | 
|  | CRWWebStateObserver, | 
|  | UITextFieldDelegate, | 
|  | UIToolbarDelegate> { | 
|  | web::BrowserState* _browserState; | 
|  | std::unique_ptr<web::WebState> _webState; | 
|  | std::unique_ptr<web::WebStateObserverBridge> _webStateObserver; | 
|  | std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate; | 
|  | } | 
|  | @property(nonatomic, assign, readonly) NavigationManager* navigationManager; | 
|  | @property(nonatomic, readwrite, strong) UITextField* field; | 
|  | @end | 
|  |  | 
|  | @implementation ViewController | 
|  |  | 
|  | @synthesize field = _field; | 
|  | @synthesize containerView = _containerView; | 
|  | @synthesize toolbarView = _toolbarView; | 
|  |  | 
|  | - (instancetype)initWithBrowserState:(web::BrowserState*)browserState { | 
|  | self = [super initWithNibName:nil bundle:nil]; | 
|  | if (self) { | 
|  | _browserState = browserState; | 
|  | } | 
|  | return self; | 
|  | } | 
|  |  | 
|  | - (void)dealloc { | 
|  | if (_webState) { | 
|  | _webState->RemoveObserver(_webStateObserver.get()); | 
|  | _webStateObserver.reset(); | 
|  | _webState.reset(); | 
|  | } | 
|  | } | 
|  |  | 
|  | - (void)viewDidLoad { | 
|  | [super viewDidLoad]; | 
|  |  | 
|  | CGRect bounds = self.view.bounds; | 
|  |  | 
|  | // Set up the toolbar. | 
|  | _toolbarView = [[UIToolbar alloc] init]; | 
|  | _toolbarView.barTintColor = | 
|  | [UIColor colorWithRed:0.337 green:0.467 blue:0.988 alpha:1.0]; | 
|  | _toolbarView.frame = CGRectMake(0, 20, CGRectGetWidth(bounds), 44); | 
|  | _toolbarView.autoresizingMask = | 
|  | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin; | 
|  | _toolbarView.delegate = self; | 
|  | [self.view addSubview:_toolbarView]; | 
|  |  | 
|  | // Set up the container view. | 
|  | _containerView = [[UIView alloc] init]; | 
|  | _containerView.frame = | 
|  | CGRectMake(0, 64, CGRectGetWidth(bounds), CGRectGetHeight(bounds) - 64); | 
|  | _containerView.backgroundColor = [UIColor lightGrayColor]; | 
|  | _containerView.autoresizingMask = | 
|  | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; | 
|  | [self.view addSubview:_containerView]; | 
|  |  | 
|  | // Set up the toolbar buttons. | 
|  | UIBarButtonItem* back = [[UIBarButtonItem alloc] | 
|  | initWithImage:[UIImage imageNamed:@"toolbar_back"] | 
|  | style:UIBarButtonItemStylePlain | 
|  | target:self | 
|  | action:@selector(back)]; | 
|  | [back setAccessibilityLabel:kWebShellBackButtonAccessibilityLabel]; | 
|  |  | 
|  | UIBarButtonItem* forward = [[UIBarButtonItem alloc] | 
|  | initWithImage:[UIImage imageNamed:@"toolbar_forward"] | 
|  | style:UIBarButtonItemStylePlain | 
|  | target:self | 
|  | action:@selector(forward)]; | 
|  | [forward setAccessibilityLabel:kWebShellForwardButtonAccessibilityLabel]; | 
|  |  | 
|  | UITextField* field = [[UITextField alloc] | 
|  | initWithFrame:CGRectMake(88, 6, CGRectGetWidth([_toolbarView frame]) - 98, | 
|  | 31)]; | 
|  | [field setDelegate:self]; | 
|  | [field setBackground:[[UIImage imageNamed:@"textfield_background"] | 
|  | resizableImageWithCapInsets:UIEdgeInsetsMake( | 
|  | 12, 12, 12, 12)]]; | 
|  | [field setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; | 
|  | [field setKeyboardType:UIKeyboardTypeWebSearch]; | 
|  | [field setAutocorrectionType:UITextAutocorrectionTypeNo]; | 
|  | [field setAccessibilityLabel:kWebShellAddressFieldAccessibilityLabel]; | 
|  | [field setClearButtonMode:UITextFieldViewModeWhileEditing]; | 
|  | self.field = field; | 
|  |  | 
|  | [_toolbarView setItems:@[ | 
|  | back, forward, [[UIBarButtonItem alloc] initWithCustomView:field] | 
|  | ]]; | 
|  |  | 
|  | web::WebState::CreateParams webStateCreateParams(_browserState); | 
|  | _webState = web::WebState::Create(webStateCreateParams); | 
|  |  | 
|  | _webStateObserver = std::make_unique<web::WebStateObserverBridge>(self); | 
|  | _webState->AddObserver(_webStateObserver.get()); | 
|  |  | 
|  | _webStateDelegate = std::make_unique<web::WebStateDelegateBridge>(self); | 
|  | _webState->SetDelegate(_webStateDelegate.get()); | 
|  |  | 
|  | UIView* view = _webState->GetView(); | 
|  | [view setFrame:[_containerView bounds]]; | 
|  | [_containerView addSubview:view]; | 
|  | } | 
|  |  | 
|  | - (NavigationManager*)navigationManager { | 
|  | return _webState->GetNavigationManager(); | 
|  | } | 
|  |  | 
|  | - (web::WebState*)webState { | 
|  | return _webState.get(); | 
|  | } | 
|  |  | 
|  | - (void)didReceiveMemoryWarning { | 
|  | [super didReceiveMemoryWarning]; | 
|  | } | 
|  |  | 
|  | - (UIBarPosition)positionForBar:(id<UIBarPositioning>)bar { | 
|  | if (bar == _toolbarView) { | 
|  | return UIBarPositionTopAttached; | 
|  | } | 
|  | return UIBarPositionAny; | 
|  | } | 
|  |  | 
|  | - (void)back { | 
|  | if (self.navigationManager->CanGoBack()) { | 
|  | self.navigationManager->GoBack(); | 
|  | } | 
|  | } | 
|  |  | 
|  | - (void)forward { | 
|  | if (self.navigationManager->CanGoForward()) { | 
|  | self.navigationManager->GoForward(); | 
|  | } | 
|  | } | 
|  |  | 
|  | - (BOOL)textFieldShouldReturn:(UITextField*)field { | 
|  | GURL URL = GURL(base::SysNSStringToUTF8([field text])); | 
|  |  | 
|  | // Do not try to load invalid URLs. | 
|  | if (URL.is_valid()) { | 
|  | NavigationManager::WebLoadParams params(URL); | 
|  | params.transition_type = ui::PAGE_TRANSITION_TYPED; | 
|  | self.navigationManager->LoadURLWithParams(params); | 
|  | } | 
|  |  | 
|  | [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; | 
|  | } | 
|  |  | 
|  | const GURL& visibleURL = _webState->GetVisibleURL(); | 
|  | [_field setText:base::SysUTF8ToNSString(visibleURL.spec())]; | 
|  | } | 
|  |  | 
|  | // ----------------------------------------------------------------------- | 
|  | #pragma mark Bikeshedding Implementation | 
|  |  | 
|  | // Overridden to allow this view controller to receive motion events by being | 
|  | // first responder when no other views are. | 
|  | - (BOOL)canBecomeFirstResponder { | 
|  | return YES; | 
|  | } | 
|  |  | 
|  | - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent*)event { | 
|  | if (event.subtype == UIEventSubtypeMotionShake) { | 
|  | [self updateToolbarColor]; | 
|  | } | 
|  | } | 
|  |  | 
|  | - (void)updateToolbarColor { | 
|  | // Cycle through the following set of colors: | 
|  | NSArray* colors = @[ | 
|  | // Vanilla Blue. | 
|  | [UIColor colorWithRed:0.337 green:0.467 blue:0.988 alpha:1.0], | 
|  | // Vanilla Red. | 
|  | [UIColor colorWithRed:0.898 green:0.110 blue:0.137 alpha:1.0], | 
|  | // Blue Grey. | 
|  | [UIColor colorWithRed:0.376 green:0.490 blue:0.545 alpha:1.0], | 
|  | // Brown. | 
|  | [UIColor colorWithRed:0.475 green:0.333 blue:0.282 alpha:1.0], | 
|  | // Purple. | 
|  | [UIColor colorWithRed:0.612 green:0.153 blue:0.690 alpha:1.0], | 
|  | // Teal. | 
|  | [UIColor colorWithRed:0.000 green:0.737 blue:0.831 alpha:1.0], | 
|  | // Deep Orange. | 
|  | [UIColor colorWithRed:1.000 green:0.341 blue:0.133 alpha:1.0], | 
|  | // Indigo. | 
|  | [UIColor colorWithRed:0.247 green:0.318 blue:0.710 alpha:1.0], | 
|  | // Vanilla Green. | 
|  | [UIColor colorWithRed:0.145 green:0.608 blue:0.141 alpha:1.0], | 
|  | // Pinkerton. | 
|  | [UIColor colorWithRed:0.914 green:0.118 blue:0.388 alpha:1.0], | 
|  | ]; | 
|  |  | 
|  | NSUInteger currentIndex = [colors indexOfObject:_toolbarView.barTintColor]; | 
|  | if (currentIndex == NSNotFound) { | 
|  | currentIndex = 0; | 
|  | } | 
|  | NSUInteger newIndex = currentIndex + 1; | 
|  | if (newIndex >= [colors count]) { | 
|  | // TODO(rohitrao): Out of colors!  Consider prompting the user to pick their | 
|  | // own color here.  Also consider allowing the user to choose the entire set | 
|  | // of colors or allowing the user to choose color randomization. | 
|  | newIndex = 0; | 
|  | } | 
|  | _toolbarView.barTintColor = [colors objectAtIndex:newIndex]; | 
|  | } | 
|  |  | 
|  | // ----------------------------------------------------------------------- | 
|  | // WebStateObserver implementation. | 
|  |  | 
|  | - (void)webState:(web::WebState*)webState | 
|  | didStartNavigation:(web::NavigationContext*)navigation { | 
|  | [self updateToolbar]; | 
|  | } | 
|  |  | 
|  | - (void)webState:(web::WebState*)webState | 
|  | didFinishNavigation:(web::NavigationContext*)navigation { | 
|  | [self updateToolbar]; | 
|  | } | 
|  |  | 
|  | - (void)webState:(web::WebState*)webState didLoadPageWithSuccess:(BOOL)success { | 
|  | DCHECK_EQ(_webState.get(), webState); | 
|  | [self updateToolbar]; | 
|  | } | 
|  |  | 
|  | // ----------------------------------------------------------------------- | 
|  | // WebStateDelegate implementation. | 
|  |  | 
|  | - (void)webState:(web::WebState*)webState | 
|  | handleContextMenu:(const web::ContextMenuParams&)params { | 
|  | GURL link = params.link_url; | 
|  | if (!link.is_valid()) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | UIAlertController* alert = [UIAlertController | 
|  | alertControllerWithTitle:params.menu_title | 
|  | message:nil | 
|  | preferredStyle:UIAlertControllerStyleActionSheet]; | 
|  | alert.popoverPresentationController.sourceView = params.view; | 
|  | alert.popoverPresentationController.sourceRect = | 
|  | CGRectMake(params.location.x, params.location.y, 1.0, 1.0); | 
|  |  | 
|  | void (^handler)(UIAlertAction*) = ^(UIAlertAction*) { | 
|  | NSDictionary* item = @{ | 
|  | static_cast<NSString*>(kUTTypeURL) : net::NSURLWithGURL(link), | 
|  | static_cast<NSString*>(kUTTypeUTF8PlainText) : [base::SysUTF8ToNSString( | 
|  | link.spec()) dataUsingEncoding:NSUTF8StringEncoding], | 
|  | }; | 
|  | [[UIPasteboard generalPasteboard] setItems:@[ item ]]; | 
|  | }; | 
|  | [alert addAction:[UIAlertAction actionWithTitle:@"Copy Link" | 
|  | style:UIAlertActionStyleDefault | 
|  | handler:handler]]; | 
|  |  | 
|  | [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" | 
|  | style:UIAlertActionStyleCancel | 
|  | handler:nil]]; | 
|  |  | 
|  | [self presentViewController:alert animated:YES completion:nil]; | 
|  | } | 
|  |  | 
|  | - (void)webStateDestroyed:(web::WebState*)webState { | 
|  | // The WebState is owned by the current instance, and the observer bridge | 
|  | // is unregistered before the WebState is destroyed, so this event should | 
|  | // never happen. | 
|  | NOTREACHED(); | 
|  | } | 
|  |  | 
|  | @end |