| Index: ios/chrome/browser/ui/omnibox/omnibox_view_ios.mm
|
| diff --git a/ios/chrome/browser/ui/omnibox/omnibox_view_ios.mm b/ios/chrome/browser/ui/omnibox/omnibox_view_ios.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..47bf4aebde093f33a43e9ae01a3fd16b7527d748
|
| --- /dev/null
|
| +++ b/ios/chrome/browser/ui/omnibox/omnibox_view_ios.mm
|
| @@ -0,0 +1,875 @@
|
| +// 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.
|
| +
|
| +#include "ios/chrome/browser/ui/omnibox/omnibox_view_ios.h"
|
| +
|
| +#import <CoreText/CoreText.h>
|
| +#import <MobileCoreServices/MobileCoreServices.h>
|
| +
|
| +#include "base/command_line.h"
|
| +#include "base/ios/device_util.h"
|
| +#include "base/memory/ptr_util.h"
|
| +#include "base/metrics/user_metrics.h"
|
| +#include "base/metrics/user_metrics_action.h"
|
| +#include "base/strings/string16.h"
|
| +#include "base/strings/sys_string_conversions.h"
|
| +#include "components/omnibox/browser/autocomplete_input.h"
|
| +#include "components/omnibox/browser/autocomplete_match.h"
|
| +#include "components/omnibox/browser/omnibox_edit_model.h"
|
| +#include "components/omnibox/browser/omnibox_popup_model.h"
|
| +#include "components/toolbar/toolbar_model.h"
|
| +#include "ios/chrome/browser/autocomplete/autocomplete_scheme_classifier_impl.h"
|
| +#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
|
| +#include "ios/chrome/browser/ui/omnibox/chrome_omnibox_client_ios.h"
|
| +#include "ios/chrome/browser/ui/omnibox/omnibox_popup_view_ios.h"
|
| +#include "ios/chrome/browser/ui/omnibox/omnibox_util.h"
|
| +#include "ios/chrome/browser/ui/omnibox/preload_provider.h"
|
| +#include "ios/chrome/browser/ui/omnibox/web_omnibox_edit_controller.h"
|
| +#include "ios/chrome/browser/ui/ui_util.h"
|
| +#include "ios/chrome/grit/ios_theme_resources.h"
|
| +#include "ios/web/public/referrer.h"
|
| +#import "net/base/mac/url_conversions.h"
|
| +#include "skia/ext/skia_utils_ios.h"
|
| +#include "ui/base/page_transition_types.h"
|
| +#include "ui/base/resource/resource_bundle.h"
|
| +#include "ui/base/window_open_disposition.h"
|
| +#include "ui/gfx/color_palette.h"
|
| +#include "ui/gfx/image/image.h"
|
| +
|
| +using base::UserMetricsAction;
|
| +
|
| +namespace {
|
| +// The color of the rest of the URL (i.e. after the TLD) in the omnibox.
|
| +UIColor* BaseTextColor() {
|
| + return [UIColor colorWithWhite:(161 / 255.0) alpha:1.0];
|
| +}
|
| +
|
| +// The color of the https when there is an error.
|
| +UIColor* ErrorTextColor() {
|
| + return skia::UIColorFromSkColor(gfx::kGoogleRed700);
|
| +}
|
| +
|
| +// The color of the https when there is a warning.
|
| +UIColor* WarningTextColor() {
|
| + return skia::UIColorFromSkColor(gfx::kGoogleYellow700);
|
| +}
|
| +
|
| +// The color of the https when there is not an error.
|
| +UIColor* SecureTextColor() {
|
| + return skia::UIColorFromSkColor(gfx::kGoogleGreen700);
|
| +}
|
| +
|
| +// The color of the https when highlighted in incognito.
|
| +UIColor* IncognitoSecureTextColor() {
|
| + return [UIColor colorWithWhite:(255 / 255.0) alpha:1.0];
|
| +}
|
| +
|
| +// Helper to make converting url_parse ranges to NSRange easier to
|
| +// read.
|
| +NSRange ComponentToNSRange(const url::Component& component) {
|
| + return NSMakeRange(static_cast<NSInteger>(component.begin),
|
| + static_cast<NSInteger>(component.len));
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +// Simple Obj-C object to forward UITextFieldDelegate method calls back to the
|
| +// OmniboxViewIOS.
|
| +@interface AutocompleteTextFieldDelegate : NSObject<OmniboxTextFieldDelegate> {
|
| + @private
|
| + OmniboxViewIOS* editView_; // weak, owns us
|
| +
|
| + // YES if we are already forwarding an OnDidChange() message to the edit view.
|
| + // Needed to prevent infinite recursion.
|
| + // TODO(rohitrao): There must be a better way.
|
| + BOOL forwardingOnDidChange_;
|
| +
|
| + // YES if this text field is currently processing a user-initiated event,
|
| + // such as typing in the omnibox or pressing the clear button. Used to
|
| + // distinguish between calls to textDidChange that are triggered by the user
|
| + // typing vs by calls to setText.
|
| + BOOL processingUserEvent_;
|
| +}
|
| +@end
|
| +
|
| +@implementation AutocompleteTextFieldDelegate
|
| +- (id)initWithEditView:(OmniboxViewIOS*)editView {
|
| + if ((self = [super init])) {
|
| + editView_ = editView;
|
| + forwardingOnDidChange_ = NO;
|
| + processingUserEvent_ = NO;
|
| + }
|
| + return self;
|
| +}
|
| +
|
| +- (BOOL)textField:(UITextField*)textField
|
| + shouldChangeCharactersInRange:(NSRange)range
|
| + replacementString:(NSString*)newText {
|
| + processingUserEvent_ = editView_->OnWillChange(range, newText);
|
| + return processingUserEvent_;
|
| +}
|
| +
|
| +- (void)textFieldDidChange:(id)sender {
|
| + if (forwardingOnDidChange_)
|
| + return;
|
| +
|
| + BOOL savedProcessingUserEvent = processingUserEvent_;
|
| + processingUserEvent_ = NO;
|
| + forwardingOnDidChange_ = YES;
|
| + editView_->OnDidChange(savedProcessingUserEvent);
|
| + forwardingOnDidChange_ = NO;
|
| +}
|
| +
|
| +// Delegate method for UITextField, called when user presses the "go" button.
|
| +- (BOOL)textFieldShouldReturn:(UITextField*)textField {
|
| + editView_->OnAccept();
|
| + return NO;
|
| +}
|
| +
|
| +// Always update the text field colors when we start editing. It's possible
|
| +// for this method to be called when we are already editing (popup focus
|
| +// change). In this case, OnDidBeginEditing will be called multiple times.
|
| +// If that becomes an issue a boolean should be added to track editing state.
|
| +- (void)textFieldDidBeginEditing:(UITextField*)textField {
|
| + editView_->OnDidBeginEditing();
|
| +}
|
| +
|
| +// On phone, the omnibox may still be editing when the popup is open, so end
|
| +// editing is called directly in OnDidEndEditing.
|
| +- (void)textFieldDidEndEditing:(UITextField*)textField {
|
| + if (!IsIPadIdiom() && editView_->IsPopupOpen())
|
| + return;
|
| +
|
| + editView_->OnDidEndEditing();
|
| +}
|
| +
|
| +// When editing, forward the message on to |editView_|.
|
| +- (BOOL)textFieldShouldClear:(UITextField*)textField {
|
| + editView_->OnClear();
|
| + processingUserEvent_ = YES;
|
| + return YES;
|
| +}
|
| +
|
| +- (BOOL)onCopy {
|
| + return editView_->OnCopy();
|
| +}
|
| +
|
| +- (BOOL)onCopyURL {
|
| + return editView_->OnCopyURL();
|
| +}
|
| +
|
| +- (BOOL)canCopyURL {
|
| + return editView_->CanCopyURL();
|
| +}
|
| +
|
| +- (void)willPaste {
|
| + editView_->WillPaste();
|
| +}
|
| +
|
| +- (void)onDeleteBackward {
|
| + editView_->OnDeleteBackward();
|
| +}
|
| +
|
| +@end
|
| +
|
| +OmniboxViewIOS::OmniboxViewIOS(OmniboxTextFieldIOS* field,
|
| + WebOmniboxEditController* controller,
|
| + ios::ChromeBrowserState* browser_state,
|
| + id<PreloadProvider> preloader,
|
| + id<OmniboxPopupPositioner> positioner)
|
| + : OmniboxView(
|
| + controller,
|
| + base::MakeUnique<ChromeOmniboxClientIOS>(controller, browser_state)),
|
| + browser_state_(browser_state),
|
| + field_([field retain]),
|
| + controller_(controller),
|
| + preloader_(preloader),
|
| + ignore_popup_updates_(false) {
|
| + popup_view_.reset(new OmniboxPopupViewIOS(this, model(), positioner));
|
| + field_delegate_.reset(
|
| + [[AutocompleteTextFieldDelegate alloc] initWithEditView:this]);
|
| + [field_ setDelegate:field_delegate_];
|
| + [field_ addTarget:field_delegate_
|
| + action:@selector(textFieldDidChange:)
|
| + forControlEvents:UIControlEventEditingChanged];
|
| +}
|
| +
|
| +OmniboxViewIOS::~OmniboxViewIOS() {
|
| + // |field_| outlives this object.
|
| + [field_ setDelegate:nil];
|
| +
|
| + [field_ removeTarget:field_delegate_
|
| + action:@selector(textFieldDidChange:)
|
| + forControlEvents:UIControlEventEditingChanged];
|
| +
|
| + // Destroy the model, in case it tries to call back into us when destroyed.
|
| + popup_view_.reset();
|
| +}
|
| +
|
| +void OmniboxViewIOS::OpenMatch(const AutocompleteMatch& match,
|
| + WindowOpenDisposition disposition,
|
| + const GURL& alternate_nav_url,
|
| + const base::string16& pasted_text,
|
| + size_t selected_line) {
|
| + // It may be unsafe to modify the contents of the field.
|
| + if (ShouldIgnoreUserInputDueToPendingVoiceSearch()) {
|
| + return;
|
| + }
|
| +
|
| + OmniboxView::OpenMatch(match, disposition, alternate_nav_url, pasted_text,
|
| + selected_line);
|
| +}
|
| +
|
| +base::string16 OmniboxViewIOS::GetText() const {
|
| + return [field_ displayedText];
|
| +}
|
| +
|
| +void OmniboxViewIOS::SetWindowTextAndCaretPos(const base::string16& text,
|
| + size_t caret_pos,
|
| + bool update_popup,
|
| + bool notify_text_changed) {
|
| + // Do not call SetUserText() here, as the user has not triggered this change.
|
| + // Instead, set the field's text directly.
|
| + // TODO(justincohen): b/5244062 Temporary fix to set the text_field value
|
| + // before model()->CurrentTextIsUrl(), since that pulls from text_field.value.
|
| + [field_ setText:base::SysUTF16ToNSString(text)];
|
| +
|
| + NSAttributedString* as = ApplyTextAttributes(text);
|
| + [field_ setText:as userTextLength:[as length]];
|
| +
|
| + if (update_popup)
|
| + UpdatePopup();
|
| +
|
| + if (notify_text_changed)
|
| + model()->OnChanged();
|
| +}
|
| +
|
| +void OmniboxViewIOS::RevertAll() {
|
| + ignore_popup_updates_ = true;
|
| + OmniboxView::RevertAll();
|
| + ignore_popup_updates_ = false;
|
| +}
|
| +
|
| +void OmniboxViewIOS::UpdatePopup() {
|
| + model()->SetInputInProgress(true);
|
| +
|
| + if (!model()->has_focus())
|
| + return;
|
| +
|
| + // Prevent inline-autocomplete if the IME is currently composing or if the
|
| + // cursor is not at the end of the text.
|
| + bool prevent_inline_autocomplete =
|
| + IsImeComposing() ||
|
| + NSMaxRange(current_selection_) != [[field_ text] length];
|
| + model()->StartAutocomplete(current_selection_.length != 0,
|
| + prevent_inline_autocomplete);
|
| + popup_view_->SetTextAlignment([field_ bestTextAlignment]);
|
| +}
|
| +
|
| +void OmniboxViewIOS::OnTemporaryTextMaybeChanged(
|
| + const base::string16& display_text,
|
| + bool save_original_selection,
|
| + bool notify_text_changed) {
|
| + SetWindowTextAndCaretPos(display_text, display_text.size(), false, false);
|
| + model()->OnChanged();
|
| +}
|
| +
|
| +bool OmniboxViewIOS::OnInlineAutocompleteTextMaybeChanged(
|
| + const base::string16& display_text,
|
| + size_t user_text_length) {
|
| + if (display_text == GetText())
|
| + return false;
|
| +
|
| + NSAttributedString* as = ApplyTextAttributes(display_text);
|
| + [field_ setText:as userTextLength:user_text_length];
|
| + model()->OnChanged();
|
| + return true;
|
| +}
|
| +
|
| +void OmniboxViewIOS::OnBeforePossibleChange() {
|
| + GetState(&state_before_change_);
|
| + marked_text_before_change_.reset([[field_ markedText] copy]);
|
| +}
|
| +
|
| +bool OmniboxViewIOS::OnAfterPossibleChange(bool allow_keyword_ui_change) {
|
| + State new_state;
|
| + GetState(&new_state);
|
| + // Manually update the selection state after calling GetState().
|
| + new_state.sel_start = current_selection_.location;
|
| + new_state.sel_end = current_selection_.location + current_selection_.length;
|
| +
|
| + OmniboxView::StateChanges state_changes =
|
| + GetStateChanges(state_before_change_, new_state);
|
| +
|
| + // iOS does not supports KeywordProvider, so never allow keyword UI changes.
|
| + const bool something_changed =
|
| + model()->OnAfterPossibleChange(state_changes, allow_keyword_ui_change);
|
| +
|
| + model()->OnChanged();
|
| +
|
| + // TODO(justincohen): Find a different place to call this. Give the omnibox
|
| + // a chance to update the alignment for a text direction change.
|
| + [field_ updateTextDirection];
|
| + return something_changed;
|
| +}
|
| +
|
| +bool OmniboxViewIOS::IsImeComposing() const {
|
| + return [field_ markedTextRange] != nil;
|
| +}
|
| +
|
| +bool OmniboxViewIOS::IsIndicatingQueryRefinement() const {
|
| + return [field_ isShowingQueryRefinementChip];
|
| +}
|
| +
|
| +bool OmniboxViewIOS::IsSelectAll() const {
|
| + return false;
|
| +}
|
| +
|
| +bool OmniboxViewIOS::DeleteAtEndPressed() {
|
| + return false;
|
| +}
|
| +
|
| +void OmniboxViewIOS::GetSelectionBounds(base::string16::size_type* start,
|
| + base::string16::size_type* end) const {
|
| + *start = *end = 0;
|
| +}
|
| +
|
| +gfx::NativeView OmniboxViewIOS::GetNativeView() const {
|
| + return nullptr;
|
| +}
|
| +
|
| +gfx::NativeView OmniboxViewIOS::GetRelativeWindowForPopup() const {
|
| + return nullptr;
|
| +}
|
| +
|
| +int OmniboxViewIOS::GetTextWidth() const {
|
| + return 0;
|
| +}
|
| +
|
| +// TODO(crbug.com/329527): [Merge r241107] implement OmniboxViewIOS::GetWidth().
|
| +int OmniboxViewIOS::GetWidth() const {
|
| + return 0;
|
| +}
|
| +
|
| +void OmniboxViewIOS::OnDidBeginEditing() {
|
| + // If Open from Clipboard offers a suggestion, the popup may be opened when
|
| + // |OnSetFocus| is called on the model. The state of the popup is saved early
|
| + // to ignore that case.
|
| + bool popup_was_open_before_editing_began = popup_view_->IsOpen();
|
| +
|
| + // Text attributes (e.g. text color) should not be shown while editing, so
|
| + // strip them out by calling setText (as opposed to setAttributedText).
|
| + [field_ setText:[field_ text]];
|
| + [field_ enableLeftViewButton:NO];
|
| + OnBeforePossibleChange();
|
| + // In the case where the user taps the fakebox on the Google landing page,
|
| + // the WebToolbarController invokes OnSetFocus before calling
|
| + // becomeFirstResponder on OmniboxTextFieldIOS (which leads to this method
|
| + // beting invoked) so there is no need to call OnSetFocus again. In fact,
|
| + // calling OnSetFocus again here would reset the caret visibility to true and
|
| + // it would be impossible to tell that the omnibox was focused by a tap in the
|
| + // fakebox instead of the omnibox.
|
| + if (!model()->has_focus()) {
|
| + model()->OnSetFocus(false);
|
| + }
|
| +
|
| + // If the omnibox is displaying a URL and the popup is not showing, set the
|
| + // field into pre-editing state. If the omnibox is displaying search terms,
|
| + // leave the default behavior of positioning the cursor at the end of the
|
| + // text. If the popup is already open, that means that the omnibox is
|
| + // regaining focus after a popup scroll took focus away, so the pre-edit
|
| + // behavior should not be invoked.
|
| + if (!popup_was_open_before_editing_began)
|
| + [field_ enterPreEditState];
|
| +
|
| + // The controller looks at the current pre-edit state, so the call to
|
| + // OnSetFocus() must come after entering pre-edit.
|
| + controller_->OnSetFocus();
|
| +}
|
| +
|
| +void OmniboxViewIOS::OnDidEndEditing() {
|
| + CloseOmniboxPopup();
|
| + [field_ enableLeftViewButton:YES];
|
| + [field_ setChipText:@""];
|
| + model()->OnWillKillFocus();
|
| + model()->OnKillFocus();
|
| + if ([field_ isPreEditing])
|
| + [field_ exitPreEditState];
|
| +
|
| + // The controller looks at the current pre-edit state, so the call to
|
| + // OnKillFocus() must come after exiting pre-edit.
|
| + controller_->OnKillFocus();
|
| +
|
| + // Cancel any outstanding preload requests.
|
| + [preloader_ cancelPrerender];
|
| + [preloader_ cancelPrefetch];
|
| +
|
| + // Blow away any in-progress edits.
|
| + RevertAll();
|
| + DCHECK(![field_ hasAutocompleteText]);
|
| +}
|
| +
|
| +bool OmniboxViewIOS::OnWillChange(NSRange range, NSString* new_text) {
|
| + bool ok_to_change = true;
|
| +
|
| + // It may be unsafe to modify the contents of the field.
|
| + if (ShouldIgnoreUserInputDueToPendingVoiceSearch()) {
|
| + return false;
|
| + }
|
| +
|
| + if ([field_ isPreEditing]) {
|
| + [field_ setClearingPreEditText:YES];
|
| +
|
| + // Exit the pre-editing state in OnWillChange() instead of OnDidChange(), as
|
| + // that allows IME to continue working. The following code clears the text
|
| + // field but continues the normal text editing flow, so UIKit behaves as
|
| + // though the user had typed into an empty field.
|
| + [field_ exitPreEditState];
|
| +
|
| + // Clearing the text field will trigger a call to OnDidChange(). This is
|
| + // ok, because the autocomplete system will process it as if the user had
|
| + // deleted all the omnibox text.
|
| + [field_ setText:@""];
|
| +
|
| + // Reset |range| to be of zero-length at location zero, as the field is now
|
| + // cleared.
|
| + range = NSMakeRange(0, 0);
|
| + }
|
| +
|
| + // Figure out the old and current (new) selections. Assume the new selection
|
| + // will be of zero-length, located at the end of |new_text|.
|
| + NSRange old_range = range;
|
| + NSRange new_range = NSMakeRange(range.location + [new_text length], 0);
|
| +
|
| + // We may need to fix up the old and new ranges in the case where autocomplete
|
| + // text was showing. If there is autocomplete text, assume it was selected.
|
| + // If the change is deleting one character from the end of the actual text,
|
| + // disallow the change, but clear the autocomplete text and call OnDidChange
|
| + // directly. If there is autocomplete text AND a text field selection, or if
|
| + // the user entered multiple characters, clear the autocomplete text and
|
| + // pretend it never existed.
|
| + if ([field_ hasAutocompleteText]) {
|
| + bool adding_text = (range.length < [new_text length]);
|
| + bool deleting_text = (range.length > [new_text length]);
|
| +
|
| + if (adding_text) {
|
| + // TODO(rohitrao): What about cases where [new_text length] > 1? This
|
| + // could happen if an IME completion inserts multiple characters at once,
|
| + // or if the user pastes some text in. Let's loosen this test to allow
|
| + // multiple characters, as long as the "old range" ends at the end of the
|
| + // permanent text.
|
| + if ([new_text length] == 1 && range.location == [[field_ text] length]) {
|
| + old_range = NSMakeRange([[field_ text] length],
|
| + [field_ autocompleteText].length());
|
| + }
|
| + } else if (deleting_text) {
|
| + if ([new_text length] == 0 &&
|
| + range.location == [[field_ text] length] - 1) {
|
| + ok_to_change = false;
|
| + }
|
| + }
|
| + }
|
| +
|
| + // Update variables needed by OnDidChange() and GetState().
|
| + old_selection_ = old_range;
|
| + current_selection_ = new_range;
|
| +
|
| + // Store the displayed text. Older version of Chrome used to clear the
|
| + // autocomplete text here as well, but on iOS7 doing this causes the inline
|
| + // autocomplete text to flicker, so the call was moved to the start on
|
| + // OnDidChange().
|
| + GetState(&state_before_change_);
|
| + // Manually update the selection state after calling GetState().
|
| + state_before_change_.sel_start = old_selection_.location;
|
| + state_before_change_.sel_end =
|
| + old_selection_.location + old_selection_.length;
|
| +
|
| + if (!ok_to_change) {
|
| + // Force a change in the autocomplete system, since we won't get an
|
| + // OnDidChange() message.
|
| + OnDidChange(true);
|
| + }
|
| +
|
| + return ok_to_change;
|
| +}
|
| +
|
| +void OmniboxViewIOS::OnDidChange(bool processing_user_event) {
|
| + // Sanitize pasted text.
|
| + if (model()->is_pasting()) {
|
| + base::string16 pastedText = base::SysNSStringToUTF16([field_ text]);
|
| + base::string16 newText = OmniboxView::SanitizeTextForPaste(pastedText);
|
| + if (pastedText != newText) {
|
| + [field_ setText:SysUTF16ToNSString(newText)];
|
| + }
|
| + }
|
| +
|
| + // Clear the autocomplete text, since the omnibox model does not expect to see
|
| + // it in OnAfterPossibleChange(). Clearing the text here should not cause
|
| + // flicker as the UI will not get a chance to redraw before the new
|
| + // autocomplete text is set by the model.
|
| + [field_ clearAutocompleteText];
|
| + [field_ setClearingPreEditText:NO];
|
| +
|
| + // Generally do not notify the autocomplete system of a text change unless the
|
| + // change was a direct result of a user event. One exception is if the marked
|
| + // text changed, which could happen through a delayed IME recognition
|
| + // callback. iOS4 does not provide API access to marked text, so use
|
| + // |IsImeComposing()| as a proxy.
|
| + bool proceed_without_user_event = false;
|
| +
|
| + // The IME exception does not work for Korean text, because Korean does not
|
| + // seem to ever have marked text. It simply replaces or modifies previous
|
| + // characters as you type. Always proceed without user input on iOS7 if the
|
| + // Korean keyboard is currently active. (This Korean exception is not
|
| + // possible on iOS6 due to crbug.com/285294.)
|
| + NSString* current_language = [[field_ textInputMode] primaryLanguage];
|
| +
|
| + if ([current_language hasPrefix:@"ko-"]) {
|
| + proceed_without_user_event = true;
|
| + } else {
|
| + NSString* current_marked_text = [field_ markedText];
|
| +
|
| + // The IME exception kicks in if the current marked text is not equal to the
|
| + // previous marked text. Two nil strings should be considered equal, so
|
| + // There is logic to avoid calling into isEqual: in that case.
|
| + proceed_without_user_event =
|
| + (marked_text_before_change_ || current_marked_text) &&
|
| + ![current_marked_text isEqual:marked_text_before_change_];
|
| + }
|
| +
|
| + if (!processing_user_event && !proceed_without_user_event)
|
| + return;
|
| +
|
| + // TODO(crbug.com/564599): OnAfterPossibleChange() now takes an argument. It
|
| + // use to not take an argument and was defaulting to false, so as it is
|
| + // unclear what the correct value is, using what was that before seems
|
| + // consistent.
|
| + OnAfterPossibleChange(false);
|
| + OnBeforePossibleChange();
|
| +}
|
| +
|
| +void OmniboxViewIOS::OnAccept() {
|
| + // It may be unsafe to modify the contents of the field.
|
| + // Note that by happy coincidence, the |textFieldDidReturn| delegate method
|
| + // always returns NO, which is the desired behavior in this situation.
|
| + if (ShouldIgnoreUserInputDueToPendingVoiceSearch()) {
|
| + return;
|
| + }
|
| +
|
| + base::RecordAction(UserMetricsAction("MobileOmniboxUse"));
|
| +
|
| + WindowOpenDisposition disposition = WindowOpenDisposition::CURRENT_TAB;
|
| + model()->AcceptInput(disposition, false);
|
| + RevertAll();
|
| +}
|
| +
|
| +void OmniboxViewIOS::OnClear() {
|
| + [field_ clearAutocompleteText];
|
| + [field_ exitPreEditState];
|
| +}
|
| +
|
| +bool OmniboxViewIOS::OnCopy() {
|
| + UIPasteboard* board = [UIPasteboard generalPasteboard];
|
| + UITextRange* selected_range = [field_ selectedTextRange];
|
| + base::string16 text =
|
| + base::SysNSStringToUTF16([field_ textInRange:selected_range]);
|
| +
|
| + UITextPosition* start = [field_ beginningOfDocument];
|
| + UITextPosition* end = [field_ endOfDocument];
|
| + BOOL is_select_all = ([field_ comparePosition:[selected_range start]
|
| + toPosition:start] == NSOrderedSame) &&
|
| + ([field_ comparePosition:[selected_range end]
|
| + toPosition:end] == NSOrderedSame);
|
| +
|
| + // The following call to |-offsetFromPosition:toPosition:| gives the offset in
|
| + // terms of the number of "visible characters." The documentation does not
|
| + // specify whether this means glyphs or UTF16 chars. This does not matter for
|
| + // the current implementation of AdjustTextForCopy(), but it may become an
|
| + // issue at some point.
|
| + NSInteger start_location =
|
| + [field_ offsetFromPosition:start toPosition:[selected_range start]];
|
| +
|
| + GURL url;
|
| + bool write_url = false;
|
| + // Don't adjust the text (e.g. add http://) if the omnibox is currently
|
| + // showing non-URL text (e.g. search terms instead of the URL).
|
| + if (!CanCopyURL()) {
|
| + model()->AdjustTextForCopy(start_location, is_select_all, &text, &url,
|
| + &write_url);
|
| + }
|
| +
|
| + // Create the pasteboard item manually because the pasteboard expects a single
|
| + // item with multiple representations. This is expressed as a single
|
| + // NSDictionary with multiple keys, one for each representation.
|
| + NSMutableDictionary* item = [NSMutableDictionary dictionaryWithCapacity:2];
|
| + [item setObject:base::SysUTF16ToNSString(text)
|
| + forKey:(NSString*)kUTTypePlainText];
|
| +
|
| + if (write_url)
|
| + [item setObject:net::NSURLWithGURL(url) forKey:(NSString*)kUTTypeURL];
|
| +
|
| + board.items = [NSArray arrayWithObject:item];
|
| + return true;
|
| +}
|
| +
|
| +bool OmniboxViewIOS::OnCopyURL() {
|
| + // Create the pasteboard item manually because the pasteboard expects a single
|
| + // item with multiple representations. This is expressed as a single
|
| + // NSDictionary with multiple keys, one for each representation.
|
| + GURL url = controller_->GetToolbarModel()->GetURL();
|
| + NSDictionary* item = @{
|
| + (NSString*)kUTTypePlainText : base::SysUTF8ToNSString(url.spec()),
|
| + (NSString*)kUTTypeURL : net::NSURLWithGURL(url)
|
| + };
|
| + [UIPasteboard generalPasteboard].items = [NSArray arrayWithObject:item];
|
| + return true;
|
| +}
|
| +
|
| +bool OmniboxViewIOS::CanCopyURL() {
|
| + return false;
|
| +}
|
| +
|
| +void OmniboxViewIOS::WillPaste() {
|
| + model()->OnPaste();
|
| +}
|
| +
|
| +NSAttributedString* OmniboxViewIOS::ApplyTextAttributes(
|
| + const base::string16& text) {
|
| + NSMutableAttributedString* as = [[[NSMutableAttributedString alloc]
|
| + initWithString:base::SysUTF16ToNSString(text)] autorelease];
|
| + url::Component scheme, host;
|
| + AutocompleteInput::ParseForEmphasizeComponents(
|
| + text, AutocompleteSchemeClassifierImpl(), &scheme, &host);
|
| +
|
| + const bool emphasize = model()->CurrentTextIsURL() && (host.len > 0);
|
| + if (emphasize) {
|
| + [as addAttribute:NSForegroundColorAttributeName
|
| + value:BaseTextColor()
|
| + range:NSMakeRange(0, [as length])];
|
| +
|
| + [as addAttribute:NSForegroundColorAttributeName
|
| + value:[field_ displayedTextColor]
|
| + range:ComponentToNSRange(host)];
|
| +
|
| + if (scheme.len > 0) {
|
| + UIColor* color = nil;
|
| + switch (controller_->GetToolbarModel()->GetSecurityLevel(false)) {
|
| + case security_state::NONE:
|
| + break;
|
| + case security_state::SECURITY_WARNING:
|
| + // Don't color strikethrough schemes. See https://crbug.com/635004#c6
|
| + if (![field_ incognito])
|
| + color = WarningTextColor();
|
| + [as addAttribute:NSStrikethroughStyleAttributeName
|
| + value:[NSNumber numberWithInteger:NSUnderlineStyleSingle]
|
| + range:ComponentToNSRange(scheme)];
|
| + break;
|
| + case security_state::SECURE:
|
| + case security_state::EV_SECURE:
|
| + color = [field_ incognito] ? IncognitoSecureTextColor()
|
| + : SecureTextColor();
|
| + break;
|
| + case security_state::DANGEROUS:
|
| + // Don't color strikethrough schemes. See https://crbug.com/635004#c6
|
| + if (![field_ incognito])
|
| + color = ErrorTextColor();
|
| + [as addAttribute:NSStrikethroughStyleAttributeName
|
| + value:[NSNumber numberWithInteger:NSUnderlineStyleSingle]
|
| + range:ComponentToNSRange(scheme)];
|
| + break;
|
| + case security_state::HTTP_SHOW_WARNING:
|
| + case security_state::SECURE_WITH_POLICY_INSTALLED_CERT:
|
| + NOTREACHED();
|
| + }
|
| + if (color) {
|
| + [as addAttribute:NSForegroundColorAttributeName
|
| + value:color
|
| + range:ComponentToNSRange(scheme)];
|
| + }
|
| + }
|
| + }
|
| + return as;
|
| +}
|
| +
|
| +void OmniboxViewIOS::UpdateAppearance() {
|
| + // If Siri is thinking, treat that as user input being in progress. It is
|
| + // unsafe to modify the text field while voice entry is pending.
|
| + if (model()->UpdatePermanentText()) {
|
| + // Revert everything to the baseline look.
|
| + RevertAll();
|
| + } else if (!model()->has_focus() &&
|
| + !ShouldIgnoreUserInputDueToPendingVoiceSearch()) {
|
| + // Only update the chip text if the omnibox is not currently focused.
|
| + [field_ setChipText:@""];
|
| +
|
| + // Even if the change wasn't "user visible" to the model, it still may be
|
| + // necessary to re-color to the URL string. Only do this if the omnibox is
|
| + // not currently focused.
|
| + base::string16 text =
|
| + controller_->GetToolbarModel()->GetFormattedURL(nullptr);
|
| + NSAttributedString* as = ApplyTextAttributes(text);
|
| + [field_ setText:as userTextLength:[as length]];
|
| + }
|
| +}
|
| +
|
| +void OmniboxViewIOS::OnDeleteBackward() {
|
| + if ([field_ text].length == 0) {
|
| + // If the user taps backspace while the pre-edit text is showing,
|
| + // OnWillChange is invoked before this method and sets the text to an empty
|
| + // string, so use the |clearingPreEditText| to determine if the chip should
|
| + // be cleared or not.
|
| + if ([field_ clearingPreEditText]) {
|
| + // In the case where backspace is tapped while in pre-edit mode,
|
| + // OnWillChange is called but OnDidChange is never called so ensure the
|
| + // clearingPreEditText flag is set to false again.
|
| + [field_ setClearingPreEditText:NO];
|
| + // Explicitly set the input-in-progress flag. Normally this is set via
|
| + // in model()->OnAfterPossibleChange, but in this case the text has been
|
| + // set to the empty string by OnWillChange so when OnAfterPossibleChange
|
| + // checks if the text has changed it does not see any difference so it
|
| + // never sets the input-in-progress flag.
|
| + model()->SetInputInProgress(YES);
|
| + } else {
|
| + RemoveQueryRefinementChip();
|
| + }
|
| + }
|
| +}
|
| +
|
| +void OmniboxViewIOS::ClearText() {
|
| + // It may be unsafe to modify the contents of the field.
|
| + if (ShouldIgnoreUserInputDueToPendingVoiceSearch()) {
|
| + return;
|
| + }
|
| +
|
| + // Ensure omnibox is first responder. This will bring up the keyboard so the
|
| + // user can start typing a new query.
|
| + if (![field_ isFirstResponder])
|
| + [field_ becomeFirstResponder];
|
| + if ([field_ text].length == 0) {
|
| + // If |field_| is empty, remove the query refinement chip.
|
| + RemoveQueryRefinementChip();
|
| + } else {
|
| + // Otherwise, just remove the text in the omnibox.
|
| + // Since iOS 6, calling |setText| does not trigger |textDidChange| so it
|
| + // must be called explicitly.
|
| + OnClear();
|
| + [field_ setText:@""];
|
| + OnDidChange(YES);
|
| + }
|
| + // Calling OnDidChange() can trigger a scroll event, which removes focus from
|
| + // the omnibox.
|
| + [field_ becomeFirstResponder];
|
| +}
|
| +
|
| +void OmniboxViewIOS::RemoveQueryRefinementChip() {
|
| + [field_ setChipText:nil];
|
| + controller_->OnChanged();
|
| +}
|
| +
|
| +bool OmniboxViewIOS::ShouldIgnoreUserInputDueToPendingVoiceSearch() {
|
| + // When the response of the iOS voice entry is pending a spinning wheel is
|
| + // visible. The spinner's location is marked in [self text] as a Unicode
|
| + // "Object Replacement Character".
|
| + // http://www.fileformat.info/info/unicode/char/fffc/index.htm
|
| + NSString* objectReplacementChar =
|
| + [NSString stringWithFormat:@"%C", (unichar)0xFFFC];
|
| + return [[field_ text] rangeOfString:objectReplacementChar].length > 0;
|
| +}
|
| +
|
| +void OmniboxViewIOS::SetLeftImage(int imageId) {
|
| + [field_ setPlaceholderImage:imageId];
|
| +}
|
| +
|
| +void OmniboxViewIOS::HideKeyboardAndEndEditing() {
|
| + [field_ resignFirstResponder];
|
| +
|
| + // Handle the case where a phone-format ombniox has already resigned first
|
| + // responder because the popup was scrolled. If the model still has focus,
|
| + // dismiss again. This should only happen on iPhone.
|
| + if (model()->has_focus()) {
|
| + DCHECK(!IsIPadIdiom());
|
| + this->OnDidEndEditing();
|
| + }
|
| +}
|
| +
|
| +void OmniboxViewIOS::HideKeyboard() {
|
| + [field_ resignFirstResponder];
|
| +}
|
| +
|
| +void OmniboxViewIOS::FocusOmnibox() {
|
| + [field_ becomeFirstResponder];
|
| +}
|
| +
|
| +// Called whenever the popup results change. May trigger the URLs of
|
| +// autocomplete results to be prerendered or prefetched.
|
| +void OmniboxViewIOS::OnPopupResultsChanged(const AutocompleteResult& result) {
|
| + if (!ignore_popup_updates_ && !result.empty()) {
|
| + const AutocompleteMatch& match = result.match_at(0);
|
| + bool is_inline_autocomplete = !match.inline_autocompletion.empty();
|
| +
|
| + // TODO(rohitrao): When prerendering the result of a paste operation, we
|
| + // should change the transition to LINK instead of TYPED. b/6143631.
|
| +
|
| + // Only prerender HISTORY_URL matches, which come from the history DB. Do
|
| + // not prerender other types of matches, including matches from the search
|
| + // provider.
|
| + if (is_inline_autocomplete &&
|
| + match.type == AutocompleteMatchType::HISTORY_URL) {
|
| + ui::PageTransition transition = ui::PageTransitionFromInt(
|
| + match.transition | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
|
| + [preloader_ prerenderURL:match.destination_url
|
| + referrer:web::Referrer()
|
| + transition:transition
|
| + immediately:is_inline_autocomplete];
|
| + } else {
|
| + [preloader_ cancelPrerender];
|
| + }
|
| +
|
| + // If the first autocomplete result is a search suggestion, prefetch the
|
| + // corresponding search result page.
|
| + if (match.type == AutocompleteMatchType::SEARCH_SUGGEST) {
|
| + ui::PageTransition transition = ui::PageTransitionFromInt(
|
| + match.transition | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
|
| + [preloader_ prefetchURL:match.destination_url transition:transition];
|
| + } else {
|
| + [preloader_ cancelPrefetch];
|
| + }
|
| + }
|
| +}
|
| +
|
| +BOOL OmniboxViewIOS::IsPopupOpen() {
|
| + return popup_view_->IsOpen();
|
| +}
|
| +
|
| +int OmniboxViewIOS::GetIcon(bool offlinePage) const {
|
| + if (!IsEditingOrEmpty()) {
|
| + if (offlinePage) {
|
| + return IDR_IOS_OMNIBOX_OFFLINE;
|
| + }
|
| + return GetIconForSecurityState(
|
| + controller()->GetToolbarModel()->GetSecurityLevel(false));
|
| + }
|
| +
|
| + return GetIconForAutocompleteMatchType(
|
| + model() ? model()->CurrentTextType()
|
| + : AutocompleteMatchType::URL_WHAT_YOU_TYPED,
|
| + /* is_starred */ false, /* is_incognito */ false);
|
| +}
|
| +
|
| +int OmniboxViewIOS::GetOmniboxTextLength() const {
|
| + return [field_ displayedText].length();
|
| +}
|
| +
|
| +void OmniboxViewIOS::EmphasizeURLComponents() {
|
| +// TODO(rohitrao): Implement this function using code like below. This code
|
| +// is being left out for now because it was not present before the OmniboxView
|
| +// rewrite.
|
| +#if 0
|
| + // When editing is in progress, the url text is not colored, so there is
|
| + // nothing to emphasize. (Calling SetText() in that situation would also be
|
| + // harmful, as it would reset the carat position to the end of the text.)
|
| + if (!IsEditingOrEmpty())
|
| + SetText(GetText());
|
| +#endif
|
| +}
|
|
|