blob: 8b30bdfe6873601ec1c7ed3a7cc32028031bb801 [file] [log] [blame]
// 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];
}
}