| // Copyright (c) 2012 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 <UIKit/UIKit.h> |
| |
| #include "base/command_line.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/strings/string16.h" |
| #include "components/omnibox/browser/omnibox_edit_model.h" |
| #include "components/security_state/core/security_state_ui.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "components/toolbar/toolbar_model.h" |
| #include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| #include "ios/chrome/browser/chrome_url_constants.h" |
| #include "ios/chrome/browser/experimental_flags.h" |
| #include "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h" |
| #include "ios/chrome/browser/ui/commands/ios_command_ids.h" |
| #include "ios/chrome/browser/ui/omnibox/location_bar_view_ios.h" |
| #import "ios/chrome/browser/ui/omnibox/omnibox_text_field_ios.h" |
| #include "ios/chrome/browser/ui/omnibox/omnibox_view_ios.h" |
| #include "ios/chrome/browser/ui/ui_util.h" |
| #import "ios/chrome/browser/ui/uikit_ui_util.h" |
| #include "ios/chrome/grit/ios_strings.h" |
| #include "ios/chrome/grit/ios_theme_resources.h" |
| #import "ios/third_party/material_roboto_font_loader_ios/src/src/MaterialRobotoFontLoader.h" |
| #include "ios/web/public/navigation_item.h" |
| #include "ios/web/public/navigation_manager.h" |
| #include "ios/web/public/ssl_status.h" |
| #include "ios/web/public/web_state/web_state.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| namespace { |
| const CGFloat kClearTextButtonWidth = 28; |
| const CGFloat kClearTextButtonHeight = 28; |
| |
| // Workaround for https://crbug.com/527084 . If there is connection |
| // information, always show the icon. Remove this once connection info |
| // is available via other UI: https://crbug.com/533581 |
| bool DoesCurrentPageHaveCertInfo(web::WebState* webState) { |
| if (!webState) |
| return false; |
| web::NavigationManager* navigationMangager = webState->GetNavigationManager(); |
| if (!navigationMangager) |
| return false; |
| web::NavigationItem* visibleItem = navigationMangager->GetVisibleItem(); |
| if (!visibleItem) |
| return false; |
| |
| const web::SSLStatus& SSLStatus = visibleItem->GetSSL(); |
| // Evaluation of |security_style| SSLStatus field in WKWebView based app is an |
| // asynchronous operation, so for a short period of time SSLStatus may have |
| // non-null certificate and SECURITY_STYLE_UNKNOWN |security_style|. |
| return SSLStatus.certificate && |
| SSLStatus.security_style != web::SECURITY_STYLE_UNKNOWN; |
| } |
| |
| // Returns whether the |webState| is presenting an offline page. |
| bool IsCurrentPageOffline(web::WebState* webState) { |
| if (!webState) |
| return false; |
| auto* navigationManager = webState->GetNavigationManager(); |
| auto* visibleItem = navigationManager->GetVisibleItem(); |
| if (!visibleItem) |
| return false; |
| const GURL& url = visibleItem->GetURL(); |
| return url.SchemeIs(kChromeUIScheme) && url.host() == kChromeUIOfflineHost; |
| } |
| |
| } // namespace |
| |
| // An ObjC bridge class to allow taps on the clear button to be sent to a C++ |
| // class. |
| @interface OmniboxClearButtonBridge : NSObject |
| |
| - (instancetype)initWithOmniboxView:(OmniboxViewIOS*)omniboxView |
| NS_DESIGNATED_INITIALIZER; |
| |
| - (instancetype)init NS_UNAVAILABLE; |
| |
| - (void)clearText; |
| |
| @end |
| |
| @implementation OmniboxClearButtonBridge { |
| OmniboxViewIOS* _omniboxView; |
| } |
| |
| - (instancetype)initWithOmniboxView:(OmniboxViewIOS*)omniboxView { |
| self = [super init]; |
| if (self) { |
| _omniboxView = omniboxView; |
| } |
| return self; |
| } |
| |
| - (instancetype)init { |
| NOTREACHED(); |
| return nil; |
| } |
| |
| - (void)clearText { |
| _omniboxView->ClearText(); |
| } |
| |
| @end |
| |
| LocationBarViewIOS::LocationBarViewIOS(OmniboxTextFieldIOS* field, |
| ios::ChromeBrowserState* browser_state, |
| id<PreloadProvider> preloader, |
| id<OmniboxPopupPositioner> positioner, |
| id<LocationBarDelegate> delegate) |
| : edit_view_(new OmniboxViewIOS(field, |
| this, |
| browser_state, |
| preloader, |
| positioner)), |
| field_(field), |
| delegate_(delegate) { |
| DCHECK([delegate_ toolbarModel]); |
| show_hint_text_ = true; |
| |
| InstallLocationIcon(); |
| CreateClearTextIcon(browser_state->IsOffTheRecord()); |
| } |
| |
| LocationBarViewIOS::~LocationBarViewIOS() {} |
| |
| void LocationBarViewIOS::HideKeyboardAndEndEditing() { |
| edit_view_->HideKeyboardAndEndEditing(); |
| } |
| |
| void LocationBarViewIOS::SetShouldShowHintText(bool show_hint_text) { |
| show_hint_text_ = show_hint_text; |
| } |
| |
| const OmniboxView* LocationBarViewIOS::GetLocationEntry() const { |
| return edit_view_.get(); |
| } |
| |
| OmniboxView* LocationBarViewIOS::GetLocationEntry() { |
| return edit_view_.get(); |
| } |
| |
| void LocationBarViewIOS::OnToolbarUpdated() { |
| edit_view_->UpdateAppearance(); |
| OnChanged(); |
| } |
| |
| void LocationBarViewIOS::OnAutocompleteAccept( |
| const GURL& gurl, |
| WindowOpenDisposition disposition, |
| ui::PageTransition transition, |
| AutocompleteMatchType::Type type) { |
| if (gurl.is_valid()) { |
| transition = ui::PageTransitionFromInt( |
| transition | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR); |
| [delegate_ loadGURLFromLocationBar:gurl transition:transition]; |
| } |
| } |
| |
| void LocationBarViewIOS::OnChanged() { |
| const bool page_is_offline = IsCurrentPageOffline(GetWebState()); |
| const int resource_id = edit_view_->GetIcon(page_is_offline); |
| [field_ setPlaceholderImage:resource_id]; |
| |
| // TODO(rohitrao): Can we get focus information from somewhere other than the |
| // model? |
| if (!IsIPadIdiom() && !edit_view_->model()->has_focus()) { |
| ToolbarModel* toolbarModel = [delegate_ toolbarModel]; |
| if (toolbarModel) { |
| bool show_icon_for_state = security_state::ShouldAlwaysShowIcon( |
| toolbarModel->GetSecurityLevel(false)); |
| bool page_has_downgraded_HTTPS = |
| experimental_flags::IsPageIconForDowngradedHTTPSEnabled() && |
| DoesCurrentPageHaveCertInfo(GetWebState()); |
| if (show_icon_for_state || page_has_downgraded_HTTPS || page_is_offline) { |
| [field_ showPlaceholderImage]; |
| is_showing_placeholder_while_collapsed_ = true; |
| } else { |
| [field_ hidePlaceholderImage]; |
| is_showing_placeholder_while_collapsed_ = false; |
| } |
| } |
| } |
| UpdateRightDecorations(); |
| [delegate_ locationBarChanged]; |
| |
| NSString* placeholderText = |
| show_hint_text_ ? l10n_util::GetNSString(IDS_OMNIBOX_EMPTY_HINT) : nil; |
| [field_ setPlaceholder:placeholderText]; |
| } |
| |
| bool LocationBarViewIOS::IsShowingPlaceholderWhileCollapsed() { |
| return is_showing_placeholder_while_collapsed_; |
| } |
| |
| void LocationBarViewIOS::OnInputInProgress(bool in_progress) { |
| if ([delegate_ toolbarModel]) |
| [delegate_ toolbarModel]->set_input_in_progress(in_progress); |
| if (in_progress) |
| [delegate_ locationBarBeganEdit]; |
| } |
| |
| void LocationBarViewIOS::OnKillFocus() { |
| // Hide the location icon on phone. A subsequent call to OnChanged() will |
| // bring the icon back if needed. |
| if (!IsIPadIdiom()) { |
| [field_ hidePlaceholderImage]; |
| is_showing_placeholder_while_collapsed_ = false; |
| } |
| |
| // Update the placeholder icon. |
| const int resource_id = |
| edit_view_->GetIcon(IsCurrentPageOffline(GetWebState())); |
| [field_ setPlaceholderImage:resource_id]; |
| |
| // Show the placeholder text on iPad. |
| if (IsIPadIdiom()) { |
| NSString* placeholderText = l10n_util::GetNSString(IDS_OMNIBOX_EMPTY_HINT); |
| [field_ setPlaceholder:placeholderText]; |
| } |
| |
| UpdateRightDecorations(); |
| [delegate_ locationBarHasResignedFirstResponder]; |
| } |
| |
| void LocationBarViewIOS::OnSetFocus() { |
| // Show the location icon on phone. |
| if (!IsIPadIdiom()) |
| [field_ showPlaceholderImage]; |
| |
| // Update the placeholder icon. |
| const int resource_id = |
| edit_view_->GetIcon(IsCurrentPageOffline(GetWebState())); |
| [field_ setPlaceholderImage:resource_id]; |
| |
| // Hide the placeholder text on iPad. |
| if (IsIPadIdiom()) { |
| [field_ setPlaceholder:nil]; |
| } |
| UpdateRightDecorations(); |
| [delegate_ locationBarHasBecomeFirstResponder]; |
| } |
| |
| const ToolbarModel* LocationBarViewIOS::GetToolbarModel() const { |
| return [delegate_ toolbarModel]; |
| } |
| |
| ToolbarModel* LocationBarViewIOS::GetToolbarModel() { |
| return [delegate_ toolbarModel]; |
| } |
| |
| web::WebState* LocationBarViewIOS::GetWebState() { |
| return [delegate_ getWebState]; |
| } |
| |
| void LocationBarViewIOS::InstallLocationIcon() { |
| // Set the placeholder for empty omnibox. |
| UIButton* button = [UIButton buttonWithType:UIButtonTypeCustom]; |
| UIImage* image = NativeImage(IDR_IOS_OMNIBOX_SEARCH); |
| [button setImage:image forState:UIControlStateNormal]; |
| [button setFrame:CGRectMake(0, 0, image.size.width, image.size.height)]; |
| [button addTarget:nil |
| action:@selector(chromeExecuteCommand:) |
| forControlEvents:UIControlEventTouchUpInside]; |
| [button setTag:IDC_SHOW_PAGE_INFO]; |
| SetA11yLabelAndUiAutomationName( |
| button, IDS_IOS_PAGE_INFO_SECURITY_BUTTON_ACCESSIBILITY_LABEL, |
| @"Page Security Info"); |
| [button setIsAccessibilityElement:YES]; |
| |
| // Set chip text options. |
| [button setTitleColor:[UIColor colorWithWhite:0.631 alpha:1] |
| forState:UIControlStateNormal]; |
| [button titleLabel].font = |
| [[MDFRobotoFontLoader sharedInstance] regularFontOfSize:12]; |
| [field_ setLeftView:button]; |
| |
| // The placeholder image is only shown when in edit mode on iPhone, and always |
| // shown on iPad. |
| if (IsIPadIdiom()) |
| [field_ setLeftViewMode:UITextFieldViewModeAlways]; |
| else |
| [field_ setLeftViewMode:UITextFieldViewModeNever]; |
| } |
| |
| void LocationBarViewIOS::CreateClearTextIcon(bool is_incognito) { |
| UIButton* button = [UIButton buttonWithType:UIButtonTypeCustom]; |
| UIImage* omniBoxClearImage = is_incognito |
| ? NativeImage(IDR_IOS_OMNIBOX_CLEAR_OTR) |
| : NativeImage(IDR_IOS_OMNIBOX_CLEAR); |
| UIImage* omniBoxClearPressedImage = |
| is_incognito ? NativeImage(IDR_IOS_OMNIBOX_CLEAR_OTR_PRESSED) |
| : NativeImage(IDR_IOS_OMNIBOX_CLEAR_PRESSED); |
| [button setImage:omniBoxClearImage forState:UIControlStateNormal]; |
| [button setImage:omniBoxClearPressedImage forState:UIControlStateHighlighted]; |
| |
| CGRect frame = CGRectZero; |
| frame.size = CGSizeMake(kClearTextButtonWidth, kClearTextButtonHeight); |
| [button setFrame:frame]; |
| |
| clear_button_bridge_.reset( |
| [[OmniboxClearButtonBridge alloc] initWithOmniboxView:edit_view_.get()]); |
| [button addTarget:clear_button_bridge_ |
| action:@selector(clearText) |
| forControlEvents:UIControlEventTouchUpInside]; |
| clear_text_button_.reset(button); |
| |
| SetA11yLabelAndUiAutomationName(clear_text_button_, |
| IDS_IOS_ACCNAME_CLEAR_TEXT, @"Clear Text"); |
| } |
| |
| void LocationBarViewIOS::UpdateRightDecorations() { |
| DCHECK(clear_text_button_); |
| if (!edit_view_->model()->has_focus()) { |
| // Do nothing for iPhone. The right view will be set to nil after the |
| // omnibox animation is completed. |
| if (IsIPadIdiom()) |
| [field_ setRightView:nil]; |
| } else if ([field_ displayedText].empty() && |
| ![field_ isShowingQueryRefinementChip]) { |
| [field_ setRightView:nil]; |
| } else { |
| [field_ setRightView:clear_text_button_]; |
| [clear_text_button_ setAlpha:1]; |
| } |
| } |