Home | History | Annotate | Download | only in omnibox
      1 // Copyright (c) 2012 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 #include "chrome/browser/ui/views/omnibox/omnibox_view_views.h"
      6 
      7 #include <set>
      8 
      9 #include "base/command_line.h"
     10 #include "base/logging.h"
     11 #include "base/metrics/histogram.h"
     12 #include "base/strings/string_util.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "chrome/app/chrome_command_ids.h"
     15 #include "chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.h"
     16 #include "chrome/browser/command_updater.h"
     17 #include "chrome/browser/search/search.h"
     18 #include "chrome/browser/ui/omnibox/omnibox_edit_controller.h"
     19 #include "chrome/browser/ui/omnibox/omnibox_edit_model.h"
     20 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
     21 #include "chrome/browser/ui/view_ids.h"
     22 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
     23 #include "chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.h"
     24 #include "chrome/browser/ui/views/settings_api_bubble_helper_views.h"
     25 #include "chrome/browser/ui/views/website_settings/website_settings_popup_view.h"
     26 #include "chrome/grit/generated_resources.h"
     27 #include "components/bookmarks/browser/bookmark_node_data.h"
     28 #include "components/omnibox/autocomplete_input.h"
     29 #include "components/omnibox/autocomplete_match.h"
     30 #include "components/omnibox/omnibox_field_trial.h"
     31 #include "content/public/browser/web_contents.h"
     32 #include "extensions/common/constants.h"
     33 #include "net/base/escape.h"
     34 #include "third_party/skia/include/core/SkColor.h"
     35 #include "ui/accessibility/ax_view_state.h"
     36 #include "ui/aura/client/focus_client.h"
     37 #include "ui/aura/window_event_dispatcher.h"
     38 #include "ui/base/clipboard/scoped_clipboard_writer.h"
     39 #include "ui/base/dragdrop/drag_drop_types.h"
     40 #include "ui/base/dragdrop/os_exchange_data.h"
     41 #include "ui/base/ime/text_input_client.h"
     42 #include "ui/base/ime/text_input_type.h"
     43 #include "ui/base/l10n/l10n_util.h"
     44 #include "ui/base/models/simple_menu_model.h"
     45 #include "ui/compositor/layer.h"
     46 #include "ui/events/event.h"
     47 #include "ui/gfx/canvas.h"
     48 #include "ui/gfx/font_list.h"
     49 #include "ui/gfx/selection_model.h"
     50 #include "ui/strings/grit/ui_strings.h"
     51 #include "ui/views/border.h"
     52 #include "ui/views/button_drag_utils.h"
     53 #include "ui/views/controls/textfield/textfield.h"
     54 #include "ui/views/ime/input_method.h"
     55 #include "ui/views/layout/fill_layout.h"
     56 #include "ui/views/views_delegate.h"
     57 #include "ui/views/widget/widget.h"
     58 #include "url/gurl.h"
     59 
     60 #if defined(OS_WIN)
     61 #include "chrome/browser/browser_process.h"
     62 #endif
     63 
     64 using bookmarks::BookmarkNodeData;
     65 
     66 namespace {
     67 
     68 // OmniboxState ---------------------------------------------------------------
     69 
     70 // Stores omnibox state for each tab.
     71 struct OmniboxState : public base::SupportsUserData::Data {
     72   static const char kKey[];
     73 
     74   OmniboxState(const OmniboxEditModel::State& model_state,
     75                const gfx::Range& selection,
     76                const gfx::Range& saved_selection_for_focus_change);
     77   virtual ~OmniboxState();
     78 
     79   const OmniboxEditModel::State model_state;
     80 
     81   // We store both the actual selection and any saved selection (for when the
     82   // omnibox is not focused).  This allows us to properly handle cases like
     83   // selecting text, tabbing out of the omnibox, switching tabs away and back,
     84   // and tabbing back into the omnibox.
     85   const gfx::Range selection;
     86   const gfx::Range saved_selection_for_focus_change;
     87 };
     88 
     89 // static
     90 const char OmniboxState::kKey[] = "OmniboxState";
     91 
     92 OmniboxState::OmniboxState(const OmniboxEditModel::State& model_state,
     93                            const gfx::Range& selection,
     94                            const gfx::Range& saved_selection_for_focus_change)
     95     : model_state(model_state),
     96       selection(selection),
     97       saved_selection_for_focus_change(saved_selection_for_focus_change) {
     98 }
     99 
    100 OmniboxState::~OmniboxState() {
    101 }
    102 
    103 
    104 // Helpers --------------------------------------------------------------------
    105 
    106 // We'd like to set the text input type to TEXT_INPUT_TYPE_URL, because this
    107 // triggers URL-specific layout in software keyboards, e.g. adding top-level "/"
    108 // and ".com" keys for English.  However, this also causes IMEs to default to
    109 // Latin character mode, which makes entering search queries difficult for IME
    110 // users.  Therefore, we try to guess whether an IME will be used based on the
    111 // application language, and set the input type accordingly.
    112 ui::TextInputType DetermineTextInputType() {
    113 #if defined(OS_WIN)
    114   DCHECK(g_browser_process);
    115   const std::string& locale = g_browser_process->GetApplicationLocale();
    116   const std::string& language = locale.substr(0, 2);
    117   // Assume CJK + Thai users are using an IME.
    118   if (language == "ja" ||
    119       language == "ko" ||
    120       language == "th" ||
    121       language == "zh")
    122     return ui::TEXT_INPUT_TYPE_SEARCH;
    123 #endif
    124   return ui::TEXT_INPUT_TYPE_URL;
    125 }
    126 
    127 }  // namespace
    128 
    129 
    130 // OmniboxViewViews -----------------------------------------------------------
    131 
    132 // static
    133 const char OmniboxViewViews::kViewClassName[] = "OmniboxViewViews";
    134 
    135 OmniboxViewViews::OmniboxViewViews(OmniboxEditController* controller,
    136                                    Profile* profile,
    137                                    CommandUpdater* command_updater,
    138                                    bool popup_window_mode,
    139                                    LocationBarView* location_bar,
    140                                    const gfx::FontList& font_list)
    141     : OmniboxView(profile, controller, command_updater),
    142       popup_window_mode_(popup_window_mode),
    143       security_level_(ToolbarModel::NONE),
    144       saved_selection_for_focus_change_(gfx::Range::InvalidRange()),
    145       ime_composing_before_change_(false),
    146       delete_at_end_pressed_(false),
    147       location_bar_view_(location_bar),
    148       ime_candidate_window_open_(false),
    149       select_all_on_mouse_release_(false),
    150       select_all_on_gesture_tap_(false),
    151       weak_ptr_factory_(this) {
    152   SetBorder(views::Border::NullBorder());
    153   set_id(VIEW_ID_OMNIBOX);
    154   SetFontList(font_list);
    155 }
    156 
    157 OmniboxViewViews::~OmniboxViewViews() {
    158 #if defined(OS_CHROMEOS)
    159   chromeos::input_method::InputMethodManager::Get()->
    160       RemoveCandidateWindowObserver(this);
    161 #endif
    162 
    163   // Explicitly teardown members which have a reference to us.  Just to be safe
    164   // we want them to be destroyed before destroying any other internal state.
    165   popup_view_.reset();
    166 }
    167 
    168 void OmniboxViewViews::Init() {
    169   set_controller(this);
    170   SetTextInputType(DetermineTextInputType());
    171 
    172   if (popup_window_mode_)
    173     SetReadOnly(true);
    174 
    175   // Initialize the popup view using the same font.
    176   popup_view_.reset(OmniboxPopupContentsView::Create(
    177       GetFontList(), this, model(), location_bar_view_));
    178 
    179 #if defined(OS_CHROMEOS)
    180   chromeos::input_method::InputMethodManager::Get()->
    181       AddCandidateWindowObserver(this);
    182 #endif
    183 }
    184 
    185 void OmniboxViewViews::SaveStateToTab(content::WebContents* tab) {
    186   DCHECK(tab);
    187 
    188   // We don't want to keep the IME status, so force quit the current
    189   // session here.  It may affect the selection status, so order is
    190   // also important.
    191   if (IsIMEComposing()) {
    192     GetTextInputClient()->ConfirmCompositionText();
    193     GetInputMethod()->CancelComposition(this);
    194   }
    195 
    196   // NOTE: GetStateForTabSwitch() may affect GetSelectedRange(), so order is
    197   // important.
    198   OmniboxEditModel::State state = model()->GetStateForTabSwitch();
    199   tab->SetUserData(OmniboxState::kKey, new OmniboxState(
    200       state, GetSelectedRange(), saved_selection_for_focus_change_));
    201 }
    202 
    203 void OmniboxViewViews::OnTabChanged(const content::WebContents* web_contents) {
    204   security_level_ = controller()->GetToolbarModel()->GetSecurityLevel(false);
    205 
    206   const OmniboxState* state = static_cast<OmniboxState*>(
    207       web_contents->GetUserData(&OmniboxState::kKey));
    208   model()->RestoreState(state ? &state->model_state : NULL);
    209   if (state) {
    210     // This assumes that the omnibox has already been focused or blurred as
    211     // appropriate; otherwise, a subsequent OnFocus() or OnBlur() call could
    212     // goof up the selection.  See comments at the end of
    213     // BrowserView::ActiveTabChanged().
    214     SelectRange(state->selection);
    215     saved_selection_for_focus_change_ = state->saved_selection_for_focus_change;
    216   }
    217 
    218   // TODO(msw|oshima): Consider saving/restoring edit history.
    219   ClearEditHistory();
    220 }
    221 
    222 void OmniboxViewViews::Update() {
    223   UpdatePlaceholderText();
    224 
    225   const ToolbarModel::SecurityLevel old_security_level = security_level_;
    226   security_level_ = controller()->GetToolbarModel()->GetSecurityLevel(false);
    227   if (model()->UpdatePermanentText()) {
    228     // Something visibly changed.  Re-enable URL replacement.
    229     controller()->GetToolbarModel()->set_url_replacement_enabled(true);
    230     controller()->GetToolbarModel()->set_origin_chip_enabled(true);
    231     model()->UpdatePermanentText();
    232 
    233     // Select all the new text if the user had all the old text selected, or if
    234     // there was no previous text (for new tab page URL replacement extensions).
    235     // This makes one particular case better: the user clicks in the box to
    236     // change it right before the permanent URL is changed.  Since the new URL
    237     // is still fully selected, the user's typing will replace the edit contents
    238     // as they'd intended.
    239     const bool was_select_all = IsSelectAll();
    240     const bool was_reversed = GetSelectedRange().is_reversed();
    241 
    242     RevertAll();
    243 
    244     // Only select all when we have focus.  If we don't have focus, selecting
    245     // all is unnecessary since the selection will change on regaining focus,
    246     // and can in fact cause artifacts, e.g. if the user is on the NTP and
    247     // clicks a link to navigate, causing |was_select_all| to be vacuously true
    248     // for the empty omnibox, and we then select all here, leading to the
    249     // trailing portion of a long URL being scrolled into view.  We could try
    250     // and address cases like this, but it seems better to just not muck with
    251     // things when the omnibox isn't focused to begin with.
    252     if (was_select_all && model()->has_focus())
    253       SelectAll(was_reversed);
    254   } else if (old_security_level != security_level_) {
    255     EmphasizeURLComponents();
    256   }
    257 }
    258 
    259 void OmniboxViewViews::UpdatePlaceholderText() {
    260   if (chrome::ShouldDisplayOriginChip() ||
    261       OmniboxFieldTrial::DisplayHintTextWhenPossible())
    262     set_placeholder_text(GetHintText());
    263 }
    264 
    265 base::string16 OmniboxViewViews::GetText() const {
    266   // TODO(oshima): IME support
    267   return text();
    268 }
    269 
    270 void OmniboxViewViews::SetUserText(const base::string16& text,
    271                                    const base::string16& display_text,
    272                                    bool update_popup) {
    273   saved_selection_for_focus_change_ = gfx::Range::InvalidRange();
    274   OmniboxView::SetUserText(text, display_text, update_popup);
    275 }
    276 
    277 void OmniboxViewViews::SetForcedQuery() {
    278   const base::string16 current_text(text());
    279   const size_t start = current_text.find_first_not_of(base::kWhitespaceUTF16);
    280   if (start == base::string16::npos || (current_text[start] != '?'))
    281     OmniboxView::SetUserText(base::ASCIIToUTF16("?"));
    282   else
    283     SelectRange(gfx::Range(current_text.size(), start + 1));
    284 }
    285 
    286 void OmniboxViewViews::GetSelectionBounds(
    287     base::string16::size_type* start,
    288     base::string16::size_type* end) const {
    289   const gfx::Range range = GetSelectedRange();
    290   *start = static_cast<size_t>(range.start());
    291   *end = static_cast<size_t>(range.end());
    292 }
    293 
    294 void OmniboxViewViews::SelectAll(bool reversed) {
    295   views::Textfield::SelectAll(reversed);
    296 }
    297 
    298 void OmniboxViewViews::RevertAll() {
    299   saved_selection_for_focus_change_ = gfx::Range::InvalidRange();
    300   OmniboxView::RevertAll();
    301 }
    302 
    303 void OmniboxViewViews::SetFocus() {
    304   RequestFocus();
    305   // Restore caret visibility if focus is explicitly requested. This is
    306   // necessary because if we already have invisible focus, the RequestFocus()
    307   // call above will short-circuit, preventing us from reaching
    308   // OmniboxEditModel::OnSetFocus(), which handles restoring visibility when the
    309   // omnibox regains focus after losing focus.
    310   model()->SetCaretVisibility(true);
    311 }
    312 
    313 int OmniboxViewViews::GetTextWidth() const {
    314   // Returns the width necessary to display the current text, including any
    315   // necessary space for the cursor or border/margin.
    316   return GetRenderText()->GetContentWidth() + GetInsets().width();
    317 }
    318 
    319 bool OmniboxViewViews::IsImeComposing() const {
    320   return IsIMEComposing();
    321 }
    322 
    323 gfx::Size OmniboxViewViews::GetMinimumSize() const {
    324   const int kMinCharacters = 10;
    325   return gfx::Size(
    326       GetFontList().GetExpectedTextWidth(kMinCharacters) + GetInsets().width(),
    327       GetPreferredSize().height());
    328 }
    329 
    330 void OmniboxViewViews::OnNativeThemeChanged(const ui::NativeTheme* theme) {
    331   views::Textfield::OnNativeThemeChanged(theme);
    332   SetBackgroundColor(location_bar_view_->GetColor(
    333       ToolbarModel::NONE, LocationBarView::BACKGROUND));
    334   EmphasizeURLComponents();
    335 }
    336 
    337 void OmniboxViewViews::ExecuteCommand(int command_id, int event_flags) {
    338   // In the base class, touch text selection is deactivated when a command is
    339   // executed. Since we are not always calling the base class implementation
    340   // here, we need to deactivate touch text selection here, too.
    341   DestroyTouchSelection();
    342   switch (command_id) {
    343     // These commands don't invoke the popup via OnBefore/AfterPossibleChange().
    344     case IDS_PASTE_AND_GO:
    345       model()->PasteAndGo(GetClipboardText());
    346       break;
    347     case IDS_SHOW_URL:
    348       controller()->ShowURL();
    349       break;
    350     case IDC_EDIT_SEARCH_ENGINES:
    351       command_updater()->ExecuteCommand(command_id);
    352       break;
    353     case IDS_MOVE_DOWN:
    354     case IDS_MOVE_UP:
    355       model()->OnUpOrDownKeyPressed(command_id == IDS_MOVE_DOWN ? 1 : -1);
    356       break;
    357 
    358     default:
    359       OnBeforePossibleChange();
    360       if (command_id == IDS_APP_PASTE)
    361         OnPaste();
    362       else if (Textfield::IsCommandIdEnabled(command_id))
    363         Textfield::ExecuteCommand(command_id, event_flags);
    364       else
    365         command_updater()->ExecuteCommand(command_id);
    366       OnAfterPossibleChange();
    367       break;
    368   }
    369 }
    370 
    371 void OmniboxViewViews::SetTextAndSelectedRange(const base::string16& text,
    372                                                const gfx::Range& range) {
    373   SetText(text);
    374   SelectRange(range);
    375 }
    376 
    377 base::string16 OmniboxViewViews::GetSelectedText() const {
    378   // TODO(oshima): Support IME.
    379   return views::Textfield::GetSelectedText();
    380 }
    381 
    382 void OmniboxViewViews::OnPaste() {
    383   const base::string16 text(GetClipboardText());
    384   if (!text.empty()) {
    385     // Record this paste, so we can do different behavior.
    386     model()->OnPaste();
    387     // Force a Paste operation to trigger the text_changed code in
    388     // OnAfterPossibleChange(), even if identical contents are pasted.
    389     text_before_change_.clear();
    390     InsertOrReplaceText(text);
    391   }
    392 }
    393 
    394 bool OmniboxViewViews::HandleEarlyTabActions(const ui::KeyEvent& event) {
    395   // This must run before acclerator handling invokes a focus change on tab.
    396   // Note the parallel with SkipDefaultKeyEventProcessing above.
    397   if (!views::FocusManager::IsTabTraversalKeyEvent(event))
    398     return false;
    399 
    400   if (model()->is_keyword_hint() && !event.IsShiftDown()) {
    401     model()->AcceptKeyword(ENTERED_KEYWORD_MODE_VIA_TAB);
    402     return true;
    403   }
    404 
    405   if (!model()->popup_model()->IsOpen())
    406     return false;
    407 
    408   if (event.IsShiftDown() &&
    409       (model()->popup_model()->selected_line_state() ==
    410           OmniboxPopupModel::KEYWORD))
    411     model()->ClearKeyword(text());
    412   else
    413     model()->OnUpOrDownKeyPressed(event.IsShiftDown() ? -1 : 1);
    414 
    415   return true;
    416 }
    417 
    418 void OmniboxViewViews::AccessibilitySetValue(const base::string16& new_value) {
    419   SetUserText(new_value, new_value, true);
    420 }
    421 
    422 void OmniboxViewViews::SetWindowTextAndCaretPos(const base::string16& text,
    423                                                 size_t caret_pos,
    424                                                 bool update_popup,
    425                                                 bool notify_text_changed) {
    426   const gfx::Range range(caret_pos, caret_pos);
    427   SetTextAndSelectedRange(text, range);
    428 
    429   if (update_popup)
    430     UpdatePopup();
    431 
    432   if (notify_text_changed)
    433     TextChanged();
    434 }
    435 
    436 bool OmniboxViewViews::IsSelectAll() const {
    437   // TODO(oshima): IME support.
    438   return text() == GetSelectedText();
    439 }
    440 
    441 bool OmniboxViewViews::DeleteAtEndPressed() {
    442   return delete_at_end_pressed_;
    443 }
    444 
    445 void OmniboxViewViews::UpdatePopup() {
    446   model()->SetInputInProgress(true);
    447   if (!model()->has_focus())
    448     return;
    449 
    450   // Prevent inline autocomplete when the caret isn't at the end of the text.
    451   const gfx::Range sel = GetSelectedRange();
    452   model()->StartAutocomplete(!sel.is_empty(), sel.GetMax() < text().length());
    453 }
    454 
    455 void OmniboxViewViews::ApplyCaretVisibility() {
    456   SetCursorEnabled(model()->is_caret_visible());
    457 }
    458 
    459 void OmniboxViewViews::OnTemporaryTextMaybeChanged(
    460     const base::string16& display_text,
    461     bool save_original_selection,
    462     bool notify_text_changed) {
    463   if (save_original_selection)
    464     saved_temporary_selection_ = GetSelectedRange();
    465 
    466   SetWindowTextAndCaretPos(display_text, display_text.length(), false,
    467                            notify_text_changed);
    468 }
    469 
    470 bool OmniboxViewViews::OnInlineAutocompleteTextMaybeChanged(
    471     const base::string16& display_text,
    472     size_t user_text_length) {
    473   if (display_text == text())
    474     return false;
    475 
    476   if (IsIMEComposing()) {
    477     location_bar_view_->SetImeInlineAutocompletion(
    478         display_text.substr(user_text_length));
    479   } else {
    480     gfx::Range range(display_text.size(), user_text_length);
    481     SetTextAndSelectedRange(display_text, range);
    482   }
    483   TextChanged();
    484   return true;
    485 }
    486 
    487 void OmniboxViewViews::OnInlineAutocompleteTextCleared() {
    488   // Hide the inline autocompletion for IME users.
    489   location_bar_view_->SetImeInlineAutocompletion(base::string16());
    490 }
    491 
    492 void OmniboxViewViews::OnRevertTemporaryText() {
    493   SelectRange(saved_temporary_selection_);
    494   // We got here because the user hit the Escape key. We explicitly don't call
    495   // TextChanged(), since OmniboxPopupModel::ResetToDefaultMatch() has already
    496   // been called by now, and it would've called TextChanged() if it was
    497   // warranted.
    498 }
    499 
    500 void OmniboxViewViews::OnBeforePossibleChange() {
    501   // Record our state.
    502   text_before_change_ = text();
    503   sel_before_change_ = GetSelectedRange();
    504   ime_composing_before_change_ = IsIMEComposing();
    505 }
    506 
    507 bool OmniboxViewViews::OnAfterPossibleChange() {
    508   // See if the text or selection have changed since OnBeforePossibleChange().
    509   const base::string16 new_text = text();
    510   const gfx::Range new_sel = GetSelectedRange();
    511   const bool text_changed = (new_text != text_before_change_) ||
    512       (ime_composing_before_change_ != IsIMEComposing());
    513   const bool selection_differs =
    514       !((sel_before_change_.is_empty() && new_sel.is_empty()) ||
    515         sel_before_change_.EqualsIgnoringDirection(new_sel));
    516 
    517   // When the user has deleted text, we don't allow inline autocomplete.  Make
    518   // sure to not flag cases like selecting part of the text and then pasting
    519   // (or typing) the prefix of that selection.  (We detect these by making
    520   // sure the caret, which should be after any insertion, hasn't moved
    521   // forward of the old selection start.)
    522   const bool just_deleted_text =
    523       (text_before_change_.length() > new_text.length()) &&
    524       (new_sel.start() <= sel_before_change_.GetMin());
    525 
    526   const bool something_changed = model()->OnAfterPossibleChange(
    527       text_before_change_, new_text, new_sel.start(), new_sel.end(),
    528       selection_differs, text_changed, just_deleted_text, !IsIMEComposing());
    529 
    530   // If only selection was changed, we don't need to call model()'s
    531   // OnChanged() method, which is called in TextChanged().
    532   // But we still need to call EmphasizeURLComponents() to make sure the text
    533   // attributes are updated correctly.
    534   if (something_changed && text_changed)
    535     TextChanged();
    536   else if (selection_differs)
    537     EmphasizeURLComponents();
    538   else if (delete_at_end_pressed_)
    539     model()->OnChanged();
    540 
    541   return something_changed;
    542 }
    543 
    544 gfx::NativeView OmniboxViewViews::GetNativeView() const {
    545   return GetWidget()->GetNativeView();
    546 }
    547 
    548 gfx::NativeView OmniboxViewViews::GetRelativeWindowForPopup() const {
    549   return GetWidget()->GetTopLevelWidget()->GetNativeView();
    550 }
    551 
    552 void OmniboxViewViews::SetGrayTextAutocompletion(const base::string16& input) {
    553   location_bar_view_->SetGrayTextAutocompletion(input);
    554 }
    555 
    556 base::string16 OmniboxViewViews::GetGrayTextAutocompletion() const {
    557   return location_bar_view_->GetGrayTextAutocompletion();
    558 }
    559 
    560 int OmniboxViewViews::GetWidth() const {
    561   return location_bar_view_->width();
    562 }
    563 
    564 bool OmniboxViewViews::IsImeShowingPopup() const {
    565 #if defined(OS_CHROMEOS)
    566   return ime_candidate_window_open_;
    567 #else
    568   const views::InputMethod* input_method = this->GetInputMethod();
    569   return input_method && input_method->IsCandidatePopupOpen();
    570 #endif
    571 }
    572 
    573 void OmniboxViewViews::ShowImeIfNeeded() {
    574   GetInputMethod()->ShowImeIfNeeded();
    575 }
    576 
    577 void OmniboxViewViews::OnMatchOpened(const AutocompleteMatch& match,
    578                                      content::WebContents* web_contents) {
    579   extensions::MaybeShowExtensionControlledSearchNotification(
    580       profile(), web_contents, match);
    581 }
    582 
    583 int OmniboxViewViews::GetOmniboxTextLength() const {
    584   // TODO(oshima): Support IME.
    585   return static_cast<int>(text().length());
    586 }
    587 
    588 void OmniboxViewViews::EmphasizeURLComponents() {
    589   // See whether the contents are a URL with a non-empty host portion, which we
    590   // should emphasize.  To check for a URL, rather than using the type returned
    591   // by Parse(), ask the model, which will check the desired page transition for
    592   // this input.  This can tell us whether an UNKNOWN input string is going to
    593   // be treated as a search or a navigation, and is the same method the Paste
    594   // And Go system uses.
    595   url::Component scheme, host;
    596   AutocompleteInput::ParseForEmphasizeComponents(
    597       text(), ChromeAutocompleteSchemeClassifier(profile()), &scheme, &host);
    598   bool grey_out_url = text().substr(scheme.begin, scheme.len) ==
    599       base::UTF8ToUTF16(extensions::kExtensionScheme);
    600   bool grey_base = model()->CurrentTextIsURL() &&
    601       (host.is_nonempty() || grey_out_url);
    602   SetColor(location_bar_view_->GetColor(
    603       security_level_,
    604       grey_base ? LocationBarView::DEEMPHASIZED_TEXT : LocationBarView::TEXT));
    605   if (grey_base && !grey_out_url) {
    606     ApplyColor(
    607         location_bar_view_->GetColor(security_level_, LocationBarView::TEXT),
    608         gfx::Range(host.begin, host.end()));
    609   }
    610 
    611   // Emphasize the scheme for security UI display purposes (if necessary).
    612   // Note that we check CurrentTextIsURL() because if we're replacing search
    613   // URLs with search terms, we may have a non-URL even when the user is not
    614   // editing; and in some cases, e.g. for "site:foo.com" searches, the parser
    615   // may have incorrectly identified a qualifier as a scheme.
    616   SetStyle(gfx::DIAGONAL_STRIKE, false);
    617   if (!model()->user_input_in_progress() && model()->CurrentTextIsURL() &&
    618       scheme.is_nonempty() && (security_level_ != ToolbarModel::NONE)) {
    619     SkColor security_color = location_bar_view_->GetColor(
    620         security_level_, LocationBarView::SECURITY_TEXT);
    621     const bool strike = (security_level_ == ToolbarModel::SECURITY_ERROR);
    622     const gfx::Range scheme_range(scheme.begin, scheme.end());
    623     ApplyColor(security_color, scheme_range);
    624     ApplyStyle(gfx::DIAGONAL_STRIKE, strike, scheme_range);
    625   }
    626 }
    627 
    628 bool OmniboxViewViews::OnKeyReleased(const ui::KeyEvent& event) {
    629   // The omnibox contents may change while the control key is pressed.
    630   if (event.key_code() == ui::VKEY_CONTROL)
    631     model()->OnControlKeyChanged(false);
    632   return views::Textfield::OnKeyReleased(event);
    633 }
    634 
    635 bool OmniboxViewViews::IsItemForCommandIdDynamic(int command_id) const {
    636   return command_id == IDS_PASTE_AND_GO;
    637 }
    638 
    639 base::string16 OmniboxViewViews::GetLabelForCommandId(int command_id) const {
    640   DCHECK_EQ(IDS_PASTE_AND_GO, command_id);
    641   return l10n_util::GetStringUTF16(
    642       model()->IsPasteAndSearch(GetClipboardText()) ?
    643           IDS_PASTE_AND_SEARCH : IDS_PASTE_AND_GO);
    644 }
    645 
    646 const char* OmniboxViewViews::GetClassName() const {
    647   return kViewClassName;
    648 }
    649 
    650 bool OmniboxViewViews::OnMousePressed(const ui::MouseEvent& event) {
    651   select_all_on_mouse_release_ =
    652       (event.IsOnlyLeftMouseButton() || event.IsOnlyRightMouseButton()) &&
    653       (!HasFocus() || (model()->focus_state() == OMNIBOX_FOCUS_INVISIBLE));
    654   if (select_all_on_mouse_release_) {
    655     // Restore caret visibility whenever the user clicks in the omnibox in a way
    656     // that would give it focus.  We must handle this case separately here
    657     // because if the omnibox currently has invisible focus, the mouse event
    658     // won't trigger either SetFocus() or OmniboxEditModel::OnSetFocus().
    659     model()->SetCaretVisibility(true);
    660 
    661     // When we're going to select all on mouse release, invalidate any saved
    662     // selection lest restoring it fights with the "select all" action.  It's
    663     // possible to later set select_all_on_mouse_release_ back to false, but
    664     // that happens for things like dragging, which are cases where having
    665     // invalidated this saved selection is still OK.
    666     saved_selection_for_focus_change_ = gfx::Range::InvalidRange();
    667   }
    668   return views::Textfield::OnMousePressed(event);
    669 }
    670 
    671 bool OmniboxViewViews::OnMouseDragged(const ui::MouseEvent& event) {
    672   if (ExceededDragThreshold(event.location() - last_click_location()))
    673     select_all_on_mouse_release_ = false;
    674 
    675   if (HasTextBeingDragged())
    676     CloseOmniboxPopup();
    677 
    678   return views::Textfield::OnMouseDragged(event);
    679 }
    680 
    681 void OmniboxViewViews::OnMouseReleased(const ui::MouseEvent& event) {
    682   views::Textfield::OnMouseReleased(event);
    683   if (event.IsOnlyLeftMouseButton() || event.IsOnlyRightMouseButton()) {
    684     // When the user has clicked and released to give us focus, select all
    685     // unless we're omitting the URL (in which case refining an existing query
    686     // is common enough that we do click-to-place-cursor).
    687     if (select_all_on_mouse_release_ &&
    688         !controller()->GetToolbarModel()->WouldReplaceURL()) {
    689       // Select all in the reverse direction so as not to scroll the caret
    690       // into view and shift the contents jarringly.
    691       SelectAll(true);
    692     }
    693 
    694     HandleOriginChipMouseRelease();
    695   }
    696   select_all_on_mouse_release_ = false;
    697 }
    698 
    699 bool OmniboxViewViews::OnKeyPressed(const ui::KeyEvent& event) {
    700   // Skip processing of [Alt]+<num-pad digit> Unicode alt key codes.
    701   // Otherwise, if num-lock is off, the events are handled as [Up], [Down], etc.
    702   if (event.IsUnicodeKeyCode())
    703     return views::Textfield::OnKeyPressed(event);
    704 
    705   const bool shift = event.IsShiftDown();
    706   const bool control = event.IsControlDown();
    707   const bool alt = event.IsAltDown() || event.IsAltGrDown();
    708   switch (event.key_code()) {
    709     case ui::VKEY_RETURN:
    710       model()->AcceptInput(alt ? NEW_FOREGROUND_TAB : CURRENT_TAB, false);
    711       return true;
    712     case ui::VKEY_ESCAPE:
    713       return model()->OnEscapeKeyPressed();
    714     case ui::VKEY_CONTROL:
    715       model()->OnControlKeyChanged(true);
    716       break;
    717     case ui::VKEY_DELETE:
    718       if (shift && model()->popup_model()->IsOpen())
    719         model()->popup_model()->TryDeletingCurrentItem();
    720       break;
    721     case ui::VKEY_UP:
    722       if (!read_only()) {
    723         model()->OnUpOrDownKeyPressed(-1);
    724         return true;
    725       }
    726       break;
    727     case ui::VKEY_DOWN:
    728       if (!read_only()) {
    729         model()->OnUpOrDownKeyPressed(1);
    730         return true;
    731       }
    732       break;
    733     case ui::VKEY_PRIOR:
    734       if (control || alt || shift)
    735         return false;
    736       model()->OnUpOrDownKeyPressed(-1 * model()->result().size());
    737       return true;
    738     case ui::VKEY_NEXT:
    739       if (control || alt || shift)
    740         return false;
    741       model()->OnUpOrDownKeyPressed(model()->result().size());
    742       return true;
    743     case ui::VKEY_V:
    744       if (control && !alt && !read_only()) {
    745         ExecuteCommand(IDS_APP_PASTE, 0);
    746         return true;
    747       }
    748       break;
    749     case ui::VKEY_INSERT:
    750       if (shift && !control && !read_only()) {
    751         ExecuteCommand(IDS_APP_PASTE, 0);
    752         return true;
    753       }
    754       break;
    755     default:
    756       break;
    757   }
    758 
    759   return views::Textfield::OnKeyPressed(event) || HandleEarlyTabActions(event);
    760 }
    761 
    762 void OmniboxViewViews::OnGestureEvent(ui::GestureEvent* event) {
    763   if (!HasFocus() && event->type() == ui::ET_GESTURE_TAP_DOWN) {
    764     select_all_on_gesture_tap_ = true;
    765 
    766     // If we're trying to select all on tap, invalidate any saved selection lest
    767     // restoring it fights with the "select all" action.
    768     saved_selection_for_focus_change_ = gfx::Range::InvalidRange();
    769   }
    770 
    771   views::Textfield::OnGestureEvent(event);
    772 
    773   if (select_all_on_gesture_tap_ && event->type() == ui::ET_GESTURE_TAP)
    774     SelectAll(true);
    775 
    776   if (event->type() == ui::ET_GESTURE_TAP ||
    777       event->type() == ui::ET_GESTURE_TAP_CANCEL ||
    778       event->type() == ui::ET_GESTURE_TWO_FINGER_TAP ||
    779       event->type() == ui::ET_GESTURE_SCROLL_BEGIN ||
    780       event->type() == ui::ET_GESTURE_PINCH_BEGIN ||
    781       event->type() == ui::ET_GESTURE_LONG_PRESS ||
    782       event->type() == ui::ET_GESTURE_LONG_TAP) {
    783     select_all_on_gesture_tap_ = false;
    784   }
    785 }
    786 
    787 void OmniboxViewViews::AboutToRequestFocusFromTabTraversal(bool reverse) {
    788   views::Textfield::AboutToRequestFocusFromTabTraversal(reverse);
    789   // Tabbing into the omnibox should affect the origin chip in the same way
    790   // clicking it should.
    791   HandleOriginChipMouseRelease();
    792 }
    793 
    794 bool OmniboxViewViews::SkipDefaultKeyEventProcessing(
    795     const ui::KeyEvent& event) {
    796   if (views::FocusManager::IsTabTraversalKeyEvent(event) &&
    797       ((model()->is_keyword_hint() && !event.IsShiftDown()) ||
    798        model()->popup_model()->IsOpen())) {
    799     return true;
    800   }
    801   return Textfield::SkipDefaultKeyEventProcessing(event);
    802 }
    803 
    804 void OmniboxViewViews::GetAccessibleState(ui::AXViewState* state) {
    805   state->role = ui::AX_ROLE_TEXT_FIELD;
    806   state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_LOCATION);
    807   state->value = GetText();
    808 
    809   base::string16::size_type entry_start;
    810   base::string16::size_type entry_end;
    811   GetSelectionBounds(&entry_start, &entry_end);
    812   state->selection_start = entry_start;
    813   state->selection_end = entry_end;
    814 
    815   if (popup_window_mode_) {
    816     state->AddStateFlag(ui::AX_STATE_READ_ONLY);
    817   } else {
    818     state->set_value_callback =
    819         base::Bind(&OmniboxViewViews::AccessibilitySetValue,
    820                    weak_ptr_factory_.GetWeakPtr());
    821   }
    822 }
    823 
    824 void OmniboxViewViews::OnFocus() {
    825   views::Textfield::OnFocus();
    826   // TODO(oshima): Get control key state.
    827   model()->OnSetFocus(false);
    828   // Don't call controller()->OnSetFocus, this view has already acquired focus.
    829 
    830   // Restore the selection we saved in OnBlur() if it's still valid.
    831   if (saved_selection_for_focus_change_.IsValid()) {
    832     SelectRange(saved_selection_for_focus_change_);
    833     saved_selection_for_focus_change_ = gfx::Range::InvalidRange();
    834   }
    835 }
    836 
    837 void OmniboxViewViews::OnBlur() {
    838   // Save the user's existing selection to restore it later.
    839   saved_selection_for_focus_change_ = GetSelectedRange();
    840 
    841   views::Textfield::OnBlur();
    842   gfx::NativeView native_view = NULL;
    843   views::Widget* widget = GetWidget();
    844   if (widget) {
    845     aura::client::FocusClient* client =
    846         aura::client::GetFocusClient(widget->GetNativeView());
    847     if (client)
    848       native_view = client->GetFocusedWindow();
    849   }
    850   model()->OnWillKillFocus(native_view);
    851   // Close the popup.
    852   CloseOmniboxPopup();
    853 
    854   // Tell the model to reset itself.
    855   model()->OnKillFocus();
    856 
    857   // Ignore loss of focus if we lost focus because the website settings popup
    858   // is open. When the popup is destroyed, focus will return to the Omnibox.
    859   if (!WebsiteSettingsPopupView::IsPopupShowing())
    860     OnDidKillFocus();
    861 
    862   // Make sure the beginning of the text is visible.
    863   SelectRange(gfx::Range(0));
    864 }
    865 
    866 bool OmniboxViewViews::IsCommandIdEnabled(int command_id) const {
    867   if (command_id == IDS_APP_PASTE)
    868     return !read_only() && !GetClipboardText().empty();
    869   if (command_id == IDS_PASTE_AND_GO)
    870     return !read_only() && model()->CanPasteAndGo(GetClipboardText());
    871   if (command_id == IDS_SHOW_URL)
    872     return controller()->GetToolbarModel()->WouldReplaceURL();
    873   return command_id == IDS_MOVE_DOWN || command_id == IDS_MOVE_UP ||
    874          Textfield::IsCommandIdEnabled(command_id) ||
    875          command_updater()->IsCommandEnabled(command_id);
    876 }
    877 
    878 base::string16 OmniboxViewViews::GetSelectionClipboardText() const {
    879   return SanitizeTextForPaste(Textfield::GetSelectionClipboardText());
    880 }
    881 
    882 #if defined(OS_CHROMEOS)
    883 void OmniboxViewViews::CandidateWindowOpened(
    884       chromeos::input_method::InputMethodManager* manager) {
    885   ime_candidate_window_open_ = true;
    886 }
    887 
    888 void OmniboxViewViews::CandidateWindowClosed(
    889       chromeos::input_method::InputMethodManager* manager) {
    890   ime_candidate_window_open_ = false;
    891 }
    892 #endif
    893 
    894 void OmniboxViewViews::ContentsChanged(views::Textfield* sender,
    895                                        const base::string16& new_contents) {
    896 }
    897 
    898 bool OmniboxViewViews::HandleKeyEvent(views::Textfield* textfield,
    899                                       const ui::KeyEvent& event) {
    900   delete_at_end_pressed_ = false;
    901 
    902   if (event.key_code() == ui::VKEY_BACK) {
    903     // No extra handling is needed in keyword search mode, if there is a
    904     // non-empty selection, or if the cursor is not leading the text.
    905     if (model()->is_keyword_hint() || model()->keyword().empty() ||
    906         HasSelection() || GetCursorPosition() != 0)
    907       return false;
    908     model()->ClearKeyword(text());
    909     return true;
    910   }
    911 
    912   if (event.key_code() == ui::VKEY_DELETE && !event.IsAltDown()) {
    913     delete_at_end_pressed_ =
    914         (!HasSelection() && GetCursorPosition() == text().length());
    915   }
    916 
    917   // Handle the right-arrow key for LTR text and the left-arrow key for RTL text
    918   // if there is gray text that needs to be committed.
    919   if (GetCursorPosition() == text().length()) {
    920     base::i18n::TextDirection direction = GetTextDirection();
    921     if ((direction == base::i18n::LEFT_TO_RIGHT &&
    922          event.key_code() == ui::VKEY_RIGHT) ||
    923         (direction == base::i18n::RIGHT_TO_LEFT &&
    924          event.key_code() == ui::VKEY_LEFT)) {
    925       return model()->CommitSuggestedText();
    926     }
    927   }
    928 
    929   return false;
    930 }
    931 
    932 void OmniboxViewViews::OnBeforeUserAction(views::Textfield* sender) {
    933   OnBeforePossibleChange();
    934 }
    935 
    936 void OmniboxViewViews::OnAfterUserAction(views::Textfield* sender) {
    937   OnAfterPossibleChange();
    938 }
    939 
    940 void OmniboxViewViews::OnAfterCutOrCopy(ui::ClipboardType clipboard_type) {
    941   ui::Clipboard* cb = ui::Clipboard::GetForCurrentThread();
    942   base::string16 selected_text;
    943   cb->ReadText(clipboard_type, &selected_text);
    944   GURL url;
    945   bool write_url;
    946   model()->AdjustTextForCopy(GetSelectedRange().GetMin(), IsSelectAll(),
    947                              &selected_text, &url, &write_url);
    948   if (IsSelectAll())
    949     UMA_HISTOGRAM_COUNTS(OmniboxEditModel::kCutOrCopyAllTextHistogram, 1);
    950 
    951   if (write_url) {
    952     BookmarkNodeData data;
    953     data.ReadFromTuple(url, selected_text);
    954     data.WriteToClipboard(clipboard_type);
    955   } else {
    956     ui::ScopedClipboardWriter scoped_clipboard_writer(clipboard_type);
    957     scoped_clipboard_writer.WriteText(selected_text);
    958   }
    959 }
    960 
    961 void OmniboxViewViews::OnWriteDragData(ui::OSExchangeData* data) {
    962   GURL url;
    963   bool write_url;
    964   bool is_all_selected = IsSelectAll();
    965   base::string16 selected_text = GetSelectedText();
    966   model()->AdjustTextForCopy(GetSelectedRange().GetMin(), is_all_selected,
    967                              &selected_text, &url, &write_url);
    968   data->SetString(selected_text);
    969   if (write_url) {
    970     gfx::Image favicon;
    971     base::string16 title = selected_text;
    972     if (is_all_selected)
    973       model()->GetDataForURLExport(&url, &title, &favicon);
    974     button_drag_utils::SetURLAndDragImage(url, title, favicon.AsImageSkia(),
    975                                           NULL, data, GetWidget());
    976     data->SetURL(url, title);
    977   }
    978 }
    979 
    980 void OmniboxViewViews::OnGetDragOperationsForTextfield(int* drag_operations) {
    981   base::string16 selected_text = GetSelectedText();
    982   GURL url;
    983   bool write_url;
    984   model()->AdjustTextForCopy(GetSelectedRange().GetMin(), IsSelectAll(),
    985                              &selected_text, &url, &write_url);
    986   if (write_url)
    987     *drag_operations |= ui::DragDropTypes::DRAG_LINK;
    988 }
    989 
    990 void OmniboxViewViews::AppendDropFormats(
    991     int* formats,
    992     std::set<ui::OSExchangeData::CustomFormat>* custom_formats) {
    993   *formats = *formats | ui::OSExchangeData::URL;
    994 }
    995 
    996 int OmniboxViewViews::OnDrop(const ui::OSExchangeData& data) {
    997   if (HasTextBeingDragged())
    998     return ui::DragDropTypes::DRAG_NONE;
    999 
   1000   if (data.HasURL(ui::OSExchangeData::CONVERT_FILENAMES)) {
   1001     GURL url;
   1002     base::string16 title;
   1003     if (data.GetURLAndTitle(
   1004             ui::OSExchangeData::CONVERT_FILENAMES, &url, &title)) {
   1005       base::string16 text(
   1006           StripJavascriptSchemas(base::UTF8ToUTF16(url.spec())));
   1007       if (model()->CanPasteAndGo(text)) {
   1008         model()->PasteAndGo(text);
   1009         return ui::DragDropTypes::DRAG_COPY;
   1010       }
   1011     }
   1012   } else if (data.HasString()) {
   1013     base::string16 text;
   1014     if (data.GetString(&text)) {
   1015       base::string16 collapsed_text(base::CollapseWhitespace(text, true));
   1016       if (model()->CanPasteAndGo(collapsed_text))
   1017         model()->PasteAndGo(collapsed_text);
   1018       return ui::DragDropTypes::DRAG_COPY;
   1019     }
   1020   }
   1021 
   1022   return ui::DragDropTypes::DRAG_NONE;
   1023 }
   1024 
   1025 void OmniboxViewViews::UpdateContextMenu(ui::SimpleMenuModel* menu_contents) {
   1026   int paste_position = menu_contents->GetIndexOfCommandId(IDS_APP_PASTE);
   1027   DCHECK_GE(paste_position, 0);
   1028   menu_contents->InsertItemWithStringIdAt(
   1029       paste_position + 1, IDS_PASTE_AND_GO, IDS_PASTE_AND_GO);
   1030 
   1031   menu_contents->AddSeparator(ui::NORMAL_SEPARATOR);
   1032 
   1033   if (chrome::IsQueryExtractionEnabled() || chrome::ShouldDisplayOriginChip()) {
   1034     int select_all_position = menu_contents->GetIndexOfCommandId(
   1035         IDS_APP_SELECT_ALL);
   1036     DCHECK_GE(select_all_position, 0);
   1037     menu_contents->InsertItemWithStringIdAt(
   1038         select_all_position + 1, IDS_SHOW_URL, IDS_SHOW_URL);
   1039   }
   1040 
   1041   // Minor note: We use IDC_ for command id here while the underlying textfield
   1042   // is using IDS_ for all its command ids. This is because views cannot depend
   1043   // on IDC_ for now.
   1044   menu_contents->AddItemWithStringId(IDC_EDIT_SEARCH_ENGINES,
   1045       IDS_EDIT_SEARCH_ENGINES);
   1046 }
   1047