blob: 7fb0965ae29dde578106192703b8a47113279116 [file] [log] [blame]
sdefresne70948d62015-08-11 10:46:351// Copyright 2015 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 "components/omnibox/browser/clipboard_url_provider.h"
6
mpearsonea5016a2017-06-01 22:20:327#include <algorithm>
8
gcomanici8cabc77f2017-04-27 20:04:549#include "base/feature_list.h"
sdefresne70948d62015-08-11 10:46:3510#include "base/logging.h"
Ilya Sherman1edb6f182017-12-12 04:00:4211#include "base/metrics/histogram_functions.h"
mpearsonab3761492017-03-31 17:29:5112#include "base/metrics/histogram_macros.h"
Robbie Gibsone6915ce12018-12-26 12:08:1013#include "base/optional.h"
sdefresne70948d62015-08-11 10:46:3514#include "base/strings/utf_string_conversions.h"
15#include "components/omnibox/browser/autocomplete_input.h"
16#include "components/omnibox/browser/autocomplete_provider_client.h"
gcomanici8cabc77f2017-04-27 20:04:5417#include "components/omnibox/browser/omnibox_field_trial.h"
sdefresne70948d62015-08-11 10:46:3518#include "components/omnibox/browser/verbatim_match.h"
19#include "components/open_from_clipboard/clipboard_recent_content.h"
Robbie Gibsone6915ce12018-12-26 12:08:1020#include "components/search_engines/template_url_service.h"
thakisfe8fa0a2017-02-23 19:46:3621#include "components/strings/grit/components_strings.h"
sdefresne70948d62015-08-11 10:46:3522#include "components/url_formatter/url_formatter.h"
sdefresne70948d62015-08-11 10:46:3523#include "ui/base/l10n/l10n_util.h"
24
25ClipboardURLProvider::ClipboardURLProvider(
26 AutocompleteProviderClient* client,
mpearson931028c2016-07-01 18:55:1127 HistoryURLProvider* history_url_provider,
sdefresne70948d62015-08-11 10:46:3528 ClipboardRecentContent* clipboard_content)
29 : AutocompleteProvider(AutocompleteProvider::TYPE_CLIPBOARD_URL),
30 client_(client),
mpearson931028c2016-07-01 18:55:1131 clipboard_content_(clipboard_content),
mpearsonea5016a2017-06-01 22:20:3232 history_url_provider_(history_url_provider),
33 current_url_suggested_times_(0) {
sdefresne70948d62015-08-11 10:46:3534 DCHECK(clipboard_content_);
35}
36
37ClipboardURLProvider::~ClipboardURLProvider() {}
38
39void ClipboardURLProvider::Start(const AutocompleteInput& input,
40 bool minimal_changes) {
41 matches_.clear();
jif3986ea502016-07-13 13:43:4242
43 // If the user started typing, do not offer clipboard based match.
sdefresne70948d62015-08-11 10:46:3544 if (!input.from_omnibox_focus())
45 return;
46
Robbie Gibsone13f2e62019-01-09 11:00:2447 base::Optional<AutocompleteMatch> optional_match = CreateImageMatch(input);
48 if (!optional_match) {
49 optional_match = CreateURLMatch(input);
50 }
Robbie Gibsone6915ce12018-12-26 12:08:1051 if (!optional_match) {
52 optional_match = CreateTextMatch(input);
53 }
54 // The clipboard does not contain any suggestions
55 if (!optional_match) {
mpearsonea5016a2017-06-01 22:20:3256 return;
57 }
sdefresne70948d62015-08-11 10:46:3558
jif3986ea502016-07-13 13:43:4259 // If the omnibox is not empty, add a default match.
60 // This match will be opened when the user presses "Enter".
61 if (!input.text().empty()) {
gcomanici8cabc77f2017-04-27 20:04:5462 const base::string16 description =
63 (base::FeatureList::IsEnabled(omnibox::kDisplayTitleForCurrentUrl))
64 ? input.current_title()
65 : base::string16();
66 AutocompleteMatch verbatim_match =
67 VerbatimMatchForURL(client_, input, input.current_url(), description,
68 history_url_provider_, -1);
sdefresne70948d62015-08-11 10:46:3569 matches_.push_back(verbatim_match);
jif3986ea502016-07-13 13:43:4270 }
mpearsonab3761492017-03-31 17:29:5171 UMA_HISTOGRAM_BOOLEAN("Omnibox.ClipboardSuggestionShownWithCurrentURL",
72 !matches_.empty());
mpearsond3dcdb02017-04-08 01:11:0373 UMA_HISTOGRAM_LONG_TIMES_100("Omnibox.ClipboardSuggestionShownAge",
74 clipboard_content_->GetClipboardContentAge());
Robbie Gibsone6915ce12018-12-26 12:08:1075
76 matches_.emplace_back(std::move(optional_match).value());
77}
78
79base::Optional<AutocompleteMatch> ClipboardURLProvider::CreateURLMatch(
80 const AutocompleteInput& input) {
81 // The clipboard does not contain a URL worth suggesting.
82 base::Optional<GURL> optional_gurl =
83 clipboard_content_->GetRecentURLFromClipboard();
84 if (!optional_gurl) {
85 current_url_suggested_ = GURL();
86 current_url_suggested_times_ = 0;
87 return base::nullopt;
88 }
89 GURL url = std::move(optional_gurl).value();
90
91 // The URL on the page is the same as the URL in the clipboard. Don't
92 // bother suggesting it.
93 if (url == input.current_url())
94 return base::nullopt;
95
96 DCHECK(url.is_valid());
97
mpearsonea5016a2017-06-01 22:20:3298 // Record the number of times the currently-offered URL has been suggested.
99 // This only works over this run of Chrome; if the URL was in the clipboard
100 // on a previous run, those offerings will not be counted.
101 if (url == current_url_suggested_) {
102 current_url_suggested_times_++;
103 } else {
104 current_url_suggested_ = url;
105 current_url_suggested_times_ = 1;
106 }
Ilya Sherman1edb6f182017-12-12 04:00:42107 base::UmaHistogramSparse(
mpearsonea5016a2017-06-01 22:20:32108 "Omnibox.ClipboardSuggestionShownNumTimes",
109 std::min(current_url_suggested_times_, static_cast<size_t>(20)));
sdefresne70948d62015-08-11 10:46:35110
jif3986ea502016-07-13 13:43:42111 // Add the clipboard match. The relevance is 800 to beat ZeroSuggest results.
112 AutocompleteMatch match(this, 800, false, AutocompleteMatchType::CLIPBOARD);
sdefresne70948d62015-08-11 10:46:35113 match.destination_url = url;
Tommy C. Lic68f2e42017-12-19 20:43:45114 // Because the user did not type a related input to get this clipboard
Tommy C. Li21da43522018-11-20 16:35:28115 // suggestion, preserve the subdomain so the user has extra context.
116 auto format_types = AutocompleteMatch::GetFormatTypes(false, true);
tommycli72014f62017-06-29 21:42:16117 match.contents.assign(url_formatter::FormatUrl(
118 url, format_types, net::UnescapeRule::SPACES, nullptr, nullptr, nullptr));
sdefresne70948d62015-08-11 10:46:35119 AutocompleteMatch::ClassifyLocationInString(
120 base::string16::npos, 0, match.contents.length(),
121 ACMatchClassification::URL, &match.contents_class);
122
123 match.description.assign(l10n_util::GetStringUTF16(IDS_LINK_FROM_CLIPBOARD));
124 AutocompleteMatch::ClassifyLocationInString(
125 base::string16::npos, 0, match.description.length(),
126 ACMatchClassification::NONE, &match.description_class);
127
Robbie Gibsone6915ce12018-12-26 12:08:10128 return match;
129}
130
131base::Optional<AutocompleteMatch> ClipboardURLProvider::CreateTextMatch(
132 const AutocompleteInput& input) {
133 // Only try text match if feature is enabled
134 if (!base::FeatureList::IsEnabled(omnibox::kCopiedTextBehavior)) {
135 return base::nullopt;
136 }
137
138 base::Optional<base::string16> optional_text =
139 clipboard_content_->GetRecentTextFromClipboard();
140 if (!optional_text) {
141 return base::nullopt;
142 }
143 base::string16 text = std::move(optional_text).value();
144
145 DCHECK(!text.empty());
146
147 // Add the clipboard match. The relevance is 800 to beat ZeroSuggest results.
148 AutocompleteMatch match(this, 800, false, AutocompleteMatchType::CLIPBOARD);
149 TemplateURLService* url_service = client_->GetTemplateURLService();
150 const TemplateURL* default_url = url_service->GetDefaultSearchProvider();
151 DCHECK(!default_url->url().empty());
152 DCHECK(default_url->url_ref().IsValid(url_service->search_terms_data()));
153 TemplateURLRef::SearchTermsArgs search_args(text);
154 GURL result(default_url->url_ref().ReplaceSearchTerms(
155 search_args, url_service->search_terms_data()));
156 match.destination_url = result;
157 match.contents.assign(l10n_util::GetStringFUTF16(
158 IDS_COPIED_TEXT_FROM_CLIPBOARD, AutocompleteMatch::SanitizeString(text)));
159 AutocompleteMatch::ClassifyLocationInString(
160 base::string16::npos, 0, match.contents.length(),
Robbie Gibsona249a0552019-01-02 10:27:48161 ACMatchClassification::NONE, &match.contents_class);
Robbie Gibsone6915ce12018-12-26 12:08:10162
163 match.description.assign(l10n_util::GetStringUTF16(IDS_TEXT_FROM_CLIPBOARD));
164 AutocompleteMatch::ClassifyLocationInString(
165 base::string16::npos, 0, match.description.length(),
166 ACMatchClassification::NONE, &match.description_class);
167
168 return match;
sdefresne70948d62015-08-11 10:46:35169}
mpearsonea5016a2017-06-01 22:20:32170
Robbie Gibsone13f2e62019-01-09 11:00:24171base::Optional<AutocompleteMatch> ClipboardURLProvider::CreateImageMatch(
172 const AutocompleteInput& input) {
173 // Only try image match if feature is enabled
174 if (!base::FeatureList::IsEnabled(omnibox::kCopiedTextBehavior)) {
175 return base::nullopt;
176 }
177
178 // Make sure current provider supports image search
179 TemplateURLService* url_service = client_->GetTemplateURLService();
180 const TemplateURL* default_url = url_service->GetDefaultSearchProvider();
181
182 if (!default_url || default_url->image_url().empty() ||
183 !default_url->image_url_ref().IsValid(url_service->search_terms_data())) {
184 return base::nullopt;
185 }
186
187 if (!clipboard_content_->GetRecentImageFromClipboard()) {
188 return base::nullopt;
189 }
190
191 // Add the clipboard match. The relevance is 800 to beat ZeroSuggest results.
192 AutocompleteMatch match(this, 800, false, AutocompleteMatchType::CLIPBOARD);
193
194 match.description.assign(l10n_util::GetStringUTF16(IDS_IMAGE_FROM_CLIPBOARD));
195 AutocompleteMatch::ClassifyLocationInString(
196 base::string16::npos, 0, match.description.length(),
197 ACMatchClassification::NONE, &match.description_class);
198
199 match.contents.assign(l10n_util::GetStringFUTF16(IDS_SEARCH_WEB_FOR_IMAGE,
200 default_url->short_name()));
201 AutocompleteMatch::ClassifyLocationInString(
202 base::string16::npos, 0, match.contents.length(),
203 ACMatchClassification::NONE, &match.contents_class);
204
205 return match;
206}
207
mpearsonea5016a2017-06-01 22:20:32208void ClipboardURLProvider::AddProviderInfo(ProvidersInfo* provider_info) const {
209 // If a URL wasn't suggested on this most recent focus event, don't bother
210 // setting |times_returned_results_in_session|, as in effect this URL has
211 // never been suggested during the current session. (For the purpose of
212 // this provider, we define a session as intervals between when a URL
213 // clipboard suggestion changes.)
214 if (current_url_suggested_times_ == 0)
215 return;
216 provider_info->push_back(metrics::OmniboxEventProto_ProviderInfo());
217 metrics::OmniboxEventProto_ProviderInfo& new_entry = provider_info->back();
218 new_entry.set_provider(AsOmniboxEventProviderType());
219 new_entry.set_times_returned_results_in_session(current_url_suggested_times_);
220}