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// This file defines helper functions shared by the various implementations
6// of OmniboxView.
7
8#include "chrome/browser/ui/omnibox/omnibox_view.h"
9
10#include "base/strings/string16.h"
11#include "base/strings/string_util.h"
12#include "base/strings/utf_string_conversions.h"
13#include "chrome/browser/search/search.h"
14#include "chrome/browser/search_engines/template_url_service_factory.h"
15#include "chrome/browser/ui/omnibox/omnibox_edit_controller.h"
16#include "chrome/browser/ui/toolbar/toolbar_model.h"
17#include "chrome/grit/generated_resources.h"
18#include "components/omnibox/autocomplete_match.h"
19#include "components/search_engines/template_url.h"
20#include "components/search_engines/template_url_service.h"
21#include "grit/components_scaled_resources.h"
22#include "grit/theme_resources.h"
23#include "ui/base/clipboard/clipboard.h"
24#include "ui/base/l10n/l10n_util.h"
25
26// static
27base::string16 OmniboxView::StripJavascriptSchemas(const base::string16& text) {
28  const base::string16 kJsPrefix(
29      base::ASCIIToUTF16(url::kJavaScriptScheme) + base::ASCIIToUTF16(":"));
30  base::string16 out(text);
31  while (StartsWith(out, kJsPrefix, false)) {
32    base::TrimWhitespace(out.substr(kJsPrefix.length()), base::TRIM_LEADING,
33                         &out);
34  }
35  return out;
36}
37
38// static
39base::string16 OmniboxView::SanitizeTextForPaste(const base::string16& text) {
40  // Check for non-newline whitespace; if found, collapse whitespace runs down
41  // to single spaces.
42  // TODO(shess): It may also make sense to ignore leading or
43  // trailing whitespace when making this determination.
44  for (size_t i = 0; i < text.size(); ++i) {
45    if (IsWhitespace(text[i]) && text[i] != '\n' && text[i] != '\r') {
46      const base::string16 collapsed = base::CollapseWhitespace(text, false);
47      // If the user is pasting all-whitespace, paste a single space
48      // rather than nothing, since pasting nothing feels broken.
49      return collapsed.empty() ?
50          base::ASCIIToUTF16(" ") : StripJavascriptSchemas(collapsed);
51    }
52  }
53
54  // Otherwise, all whitespace is newlines; remove it entirely.
55  return StripJavascriptSchemas(base::CollapseWhitespace(text, true));
56}
57
58// static
59base::string16 OmniboxView::GetClipboardText() {
60  // Try text format.
61  ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
62  if (clipboard->IsFormatAvailable(ui::Clipboard::GetPlainTextWFormatType(),
63                                   ui::CLIPBOARD_TYPE_COPY_PASTE)) {
64    base::string16 text;
65    clipboard->ReadText(ui::CLIPBOARD_TYPE_COPY_PASTE, &text);
66    return SanitizeTextForPaste(text);
67  }
68
69  // Try bookmark format.
70  //
71  // It is tempting to try bookmark format first, but the URL we get out of a
72  // bookmark has been cannonicalized via GURL.  This means if a user copies
73  // and pastes from the URL bar to itself, the text will get fixed up and
74  // cannonicalized, which is not what the user expects.  By pasting in this
75  // order, we are sure to paste what the user copied.
76  if (clipboard->IsFormatAvailable(ui::Clipboard::GetUrlWFormatType(),
77                                   ui::CLIPBOARD_TYPE_COPY_PASTE)) {
78    std::string url_str;
79    clipboard->ReadBookmark(NULL, &url_str);
80    // pass resulting url string through GURL to normalize
81    GURL url(url_str);
82    if (url.is_valid())
83      return StripJavascriptSchemas(base::UTF8ToUTF16(url.spec()));
84  }
85
86  return base::string16();
87}
88
89OmniboxView::~OmniboxView() {
90}
91
92void OmniboxView::HandleOriginChipMouseRelease() {
93  // Only hide if there isn't any current text in the Omnibox (e.g. search
94  // terms).
95  if (controller()->GetToolbarModel()->GetText().empty())
96    controller()->HideOriginChip();
97}
98
99void OmniboxView::OnDidKillFocus() {
100  if (chrome::ShouldDisplayOriginChip() && !model()->user_input_in_progress())
101    controller()->ShowOriginChip();
102}
103
104void OmniboxView::OpenMatch(const AutocompleteMatch& match,
105                            WindowOpenDisposition disposition,
106                            const GURL& alternate_nav_url,
107                            const base::string16& pasted_text,
108                            size_t selected_line) {
109  // Invalid URLs such as chrome://history can end up here.
110  if (!match.destination_url.is_valid() || !model_)
111    return;
112  model_->OpenMatch(
113      match, disposition, alternate_nav_url, pasted_text, selected_line);
114  OnMatchOpened(match, controller_->GetWebContents());
115}
116
117bool OmniboxView::IsEditingOrEmpty() const {
118  return (model_.get() && model_->user_input_in_progress()) ||
119      (GetOmniboxTextLength() == 0);
120}
121
122int OmniboxView::GetIcon() const {
123  if (!IsEditingOrEmpty())
124    return controller_->GetToolbarModel()->GetIcon();
125  int id = AutocompleteMatch::TypeToIcon(model_.get() ?
126      model_->CurrentTextType() : AutocompleteMatchType::URL_WHAT_YOU_TYPED);
127  return (id == IDR_OMNIBOX_HTTP) ? IDR_LOCATION_BAR_HTTP : id;
128}
129
130base::string16 OmniboxView::GetHintText() const {
131  // If the user is in keyword mode (the "Search <some site>:" chip is showing)
132  // then it doesn't make sense to show the "Search <default search engine>"
133  // hint text.
134  if (model_->is_keyword_selected())
135    return base::string16();
136
137  // Attempt to determine the default search provider and use that in the hint
138  // text.
139  TemplateURLService* template_url_service =
140      TemplateURLServiceFactory::GetForProfile(model_->profile());
141  if (template_url_service) {
142    TemplateURL* template_url =
143        template_url_service->GetDefaultSearchProvider();
144    if (template_url)
145      return l10n_util::GetStringFUTF16(
146          IDS_OMNIBOX_EMPTY_HINT_WITH_DEFAULT_SEARCH_PROVIDER,
147          template_url->AdjustedShortNameForLocaleDirection());
148  }
149
150  // Otherwise return a hint based on there being no default search provider.
151  return l10n_util::GetStringUTF16(
152      IDS_OMNIBOX_EMPTY_HINT_NO_DEFAULT_SEARCH_PROVIDER);
153}
154
155void OmniboxView::SetUserText(const base::string16& text) {
156  SetUserText(text, text, true);
157}
158
159void OmniboxView::SetUserText(const base::string16& text,
160                              const base::string16& display_text,
161                              bool update_popup) {
162  if (model_.get())
163    model_->SetUserText(text);
164  SetWindowTextAndCaretPos(display_text, display_text.length(), update_popup,
165                           true);
166}
167
168void OmniboxView::ShowURL() {
169  SetFocus();
170  controller_->GetToolbarModel()->set_origin_chip_enabled(false);
171  controller_->GetToolbarModel()->set_url_replacement_enabled(false);
172  model_->UpdatePermanentText();
173  RevertWithoutResettingSearchTermReplacement();
174  SelectAll(true);
175}
176
177void OmniboxView::HideURL() {
178  controller_->GetToolbarModel()->set_origin_chip_enabled(true);
179  controller_->GetToolbarModel()->set_url_replacement_enabled(true);
180  model_->UpdatePermanentText();
181  RevertWithoutResettingSearchTermReplacement();
182}
183
184void OmniboxView::RevertAll() {
185  controller_->GetToolbarModel()->set_origin_chip_enabled(true);
186  controller_->GetToolbarModel()->set_url_replacement_enabled(true);
187  RevertWithoutResettingSearchTermReplacement();
188}
189
190void OmniboxView::RevertWithoutResettingSearchTermReplacement() {
191  CloseOmniboxPopup();
192  if (model_.get())
193    model_->Revert();
194  TextChanged();
195}
196
197void OmniboxView::CloseOmniboxPopup() {
198  if (model_.get())
199    model_->StopAutocomplete();
200}
201
202bool OmniboxView::IsImeShowingPopup() const {
203  // Default to claiming that the IME is not showing a popup, since hiding the
204  // omnibox dropdown is a bad user experience when we don't know for sure that
205  // we have to.
206  return false;
207}
208
209void OmniboxView::ShowImeIfNeeded() {
210}
211
212bool OmniboxView::IsIndicatingQueryRefinement() const {
213  // The default implementation always returns false.  Mobile ports can override
214  // this method and implement as needed.
215  return false;
216}
217
218void OmniboxView::OnMatchOpened(const AutocompleteMatch& match,
219                                content::WebContents* web_contents) {
220}
221
222OmniboxView::OmniboxView(Profile* profile,
223                         OmniboxEditController* controller,
224                         CommandUpdater* command_updater)
225    : controller_(controller),
226      command_updater_(command_updater) {
227  // |profile| can be NULL in tests.
228  if (profile)
229    model_.reset(new OmniboxEditModel(this, controller, profile));
230}
231
232void OmniboxView::TextChanged() {
233  EmphasizeURLComponents();
234  if (model_.get())
235    model_->OnChanged();
236}
237