blob: c889cd380c628f2b353ef9b7f0e9ef1512e03561 [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
Robbie Gibsonf4e78b82019-02-20 18:00:195#include "components/omnibox/browser/clipboard_provider.h"
sdefresne70948d62015-08-11 10:46:356
mpearsonea5016a2017-06-01 22:20:327#include <algorithm>
Robbie Gibson71b0c522019-02-13 00:05:588#include <memory>
9#include <utility>
Gang Wuba18dba2019-09-25 03:43:2110#include <vector>
mpearsonea5016a2017-06-01 22:20:3211
Robbie Gibson71b0c522019-02-13 00:05:5812#include "base/bind.h"
gcomanici8cabc77f2017-04-27 20:04:5413#include "base/feature_list.h"
Robbie Gibson71b0c522019-02-13 00:05:5814#include "base/memory/ref_counted_memory.h"
15#include "base/memory/weak_ptr.h"
Robbie Gibson1a9405b2019-02-21 12:20:4516#include "base/metrics/field_trial_params.h"
Ilya Sherman1edb6f182017-12-12 04:00:4217#include "base/metrics/histogram_functions.h"
mpearsonab3761492017-03-31 17:29:5118#include "base/metrics/histogram_macros.h"
Gang Wu730add3a2019-11-01 00:10:5519#include "base/metrics/user_metrics.h"
sdefresne70948d62015-08-11 10:46:3520#include "base/strings/utf_string_conversions.h"
Robbie Gibson71b0c522019-02-13 00:05:5821#include "base/task/post_task.h"
Gabriel Charetteafbec8752020-05-29 04:34:1122#include "base/task/thread_pool.h"
Gang Wu3804fbc262020-03-24 00:50:1423#include "build/build_config.h"
sdefresne70948d62015-08-11 10:46:3524#include "components/omnibox/browser/autocomplete_input.h"
Robbie Gibson71b0c522019-02-13 00:05:5825#include "components/omnibox/browser/autocomplete_match.h"
sdefresne70948d62015-08-11 10:46:3526#include "components/omnibox/browser/autocomplete_provider_client.h"
Robbie Gibson71b0c522019-02-13 00:05:5827#include "components/omnibox/browser/autocomplete_provider_listener.h"
sdefresne70948d62015-08-11 10:46:3528#include "components/omnibox/browser/verbatim_match.h"
Tomasz Wiszkowskid938a1112019-03-06 18:01:5729#include "components/omnibox/common/omnibox_features.h"
sdefresne70948d62015-08-11 10:46:3530#include "components/open_from_clipboard/clipboard_recent_content.h"
Tommy C. Li17b5ba112020-08-11 02:29:2131#include "components/search_engines/omnibox_focus_type.h"
Robbie Gibsone6915ce12018-12-26 12:08:1032#include "components/search_engines/template_url_service.h"
thakisfe8fa0a2017-02-23 19:46:3633#include "components/strings/grit/components_strings.h"
sdefresne70948d62015-08-11 10:46:3534#include "components/url_formatter/url_formatter.h"
Anton Bikineev1156b5f2021-05-15 22:35:3635#include "third_party/abseil-cpp/absl/types/optional.h"
sdefresne70948d62015-08-11 10:46:3536#include "ui/base/l10n/l10n_util.h"
Gang Wu05397842020-07-17 17:06:4437#include "ui/gfx/image/image_skia.h"
Robbie Gibson71b0c522019-02-13 00:05:5838#include "ui/gfx/image/image_util.h"
sdefresne70948d62015-08-11 10:46:3539
Gang Wuba18dba2019-09-25 03:43:2140namespace {
41
Gang Wu0db1f1fa2019-10-02 20:30:1042const size_t kMaxClipboardSuggestionShownNumTimesSimpleSize = 20;
43
Tomasz Wiszkowski4d43cef2020-11-24 19:47:0044// Clipboard suggestions should be placed above search and url suggestions
45// (including MostVisited tiles), but below query tiles.
46const int kClipboardMatchRelevanceScore = 1501;
Gang Wu64198f2002020-06-25 00:28:2747
Gang Wuba18dba2019-09-25 03:43:2148bool IsMatchDeletionEnabled() {
49 return base::FeatureList::IsEnabled(
50 omnibox::kOmniboxRemoveSuggestionsFromClipboard);
51}
52
Gang Wu0db1f1fa2019-10-02 20:30:1053void RecordCreatingClipboardSuggestionMetrics(
54 size_t current_url_suggested_times,
55 bool matches_is_empty,
56 AutocompleteMatchType::Type match_type,
57 const base::TimeDelta clipboard_contents_age) {
58 DCHECK(match_type == AutocompleteMatchType::CLIPBOARD_URL ||
59 match_type == AutocompleteMatchType::CLIPBOARD_TEXT ||
60 match_type == AutocompleteMatchType::CLIPBOARD_IMAGE);
61
62 base::UmaHistogramSparse(
63 "Omnibox.ClipboardSuggestionShownNumTimes",
64 std::min(current_url_suggested_times,
65 kMaxClipboardSuggestionShownNumTimesSimpleSize));
66 UMA_HISTOGRAM_BOOLEAN("Omnibox.ClipboardSuggestionShownWithCurrentURL",
67 !matches_is_empty);
68 UMA_HISTOGRAM_LONG_TIMES_100("Omnibox.ClipboardSuggestionShownAge",
69 clipboard_contents_age);
70 if (match_type == AutocompleteMatchType::CLIPBOARD_URL) {
71 base::UmaHistogramSparse(
72 "Omnibox.ClipboardSuggestionShownNumTimes.URL",
73 std::min(current_url_suggested_times,
74 kMaxClipboardSuggestionShownNumTimesSimpleSize));
75 UMA_HISTOGRAM_BOOLEAN("Omnibox.ClipboardSuggestionShownWithCurrentURL.URL",
76 !matches_is_empty);
77 UMA_HISTOGRAM_LONG_TIMES_100("Omnibox.ClipboardSuggestionShownAge.URL",
78 clipboard_contents_age);
79 } else if (match_type == AutocompleteMatchType::CLIPBOARD_TEXT) {
80 base::UmaHistogramSparse(
81 "Omnibox.ClipboardSuggestionShownNumTimes.TEXT",
82 std::min(current_url_suggested_times,
83 kMaxClipboardSuggestionShownNumTimesSimpleSize));
84 UMA_HISTOGRAM_BOOLEAN("Omnibox.ClipboardSuggestionShownWithCurrentURL.TEXT",
85 !matches_is_empty);
86 UMA_HISTOGRAM_LONG_TIMES_100("Omnibox.ClipboardSuggestionShownAge.TEXT",
87 clipboard_contents_age);
88 } else if (match_type == AutocompleteMatchType::CLIPBOARD_IMAGE) {
89 base::UmaHistogramSparse(
90 "Omnibox.ClipboardSuggestionShownNumTimes.IMAGE",
91 std::min(current_url_suggested_times,
92 kMaxClipboardSuggestionShownNumTimesSimpleSize));
93 UMA_HISTOGRAM_BOOLEAN(
94 "Omnibox.ClipboardSuggestionShownWithCurrentURL.IMAGE",
95 !matches_is_empty);
96 UMA_HISTOGRAM_LONG_TIMES_100("Omnibox.ClipboardSuggestionShownAge.IMAGE",
97 clipboard_contents_age);
98 }
99}
100
101void RecordDeletingClipboardSuggestionMetrics(
102 AutocompleteMatchType::Type match_type,
103 const base::TimeDelta clipboard_contents_age) {
Gang Wu730add3a2019-11-01 00:10:55104 base::RecordAction(
105 base::UserMetricsAction("Omnibox.ClipboardSuggestionRemoved"));
106
Gang Wu0db1f1fa2019-10-02 20:30:10107 UMA_HISTOGRAM_LONG_TIMES_100("Omnibox.ClipboardSuggestionRemovedAge",
108 clipboard_contents_age);
109 if (match_type == AutocompleteMatchType::CLIPBOARD_URL) {
110 UMA_HISTOGRAM_LONG_TIMES_100("Omnibox.ClipboardSuggestionRemovedAge.URL",
111 clipboard_contents_age);
112 } else if (match_type == AutocompleteMatchType::CLIPBOARD_TEXT) {
113 UMA_HISTOGRAM_LONG_TIMES_100("Omnibox.ClipboardSuggestionRemovedAge.TEXT",
114 clipboard_contents_age);
Gang Wu77cb1112020-04-06 23:21:43115 } else if (match_type == AutocompleteMatchType::CLIPBOARD_IMAGE) {
116 UMA_HISTOGRAM_LONG_TIMES_100("Omnibox.ClipboardSuggestionRemovedAge.IMAGE",
117 clipboard_contents_age);
Gang Wu0db1f1fa2019-10-02 20:30:10118 }
119}
120
Gang Wuba18dba2019-09-25 03:43:21121} // namespace
122
Robbie Gibsonf4e78b82019-02-20 18:00:19123ClipboardProvider::ClipboardProvider(AutocompleteProviderClient* client,
124 AutocompleteProviderListener* listener,
125 HistoryURLProvider* history_url_provider,
126 ClipboardRecentContent* clipboard_content)
127 : AutocompleteProvider(AutocompleteProvider::TYPE_CLIPBOARD),
sdefresne70948d62015-08-11 10:46:35128 client_(client),
Robbie Gibson71b0c522019-02-13 00:05:58129 listener_(listener),
mpearson931028c2016-07-01 18:55:11130 clipboard_content_(clipboard_content),
mpearsonea5016a2017-06-01 22:20:32131 history_url_provider_(history_url_provider),
Robbie Gibson71b0c522019-02-13 00:05:58132 current_url_suggested_times_(0),
Robbie Gibson1a9405b2019-02-21 12:20:45133 field_trial_triggered_(false),
Jeremy Roman5c341f6d2019-07-15 15:56:10134 field_trial_triggered_in_session_(false) {
sdefresne70948d62015-08-11 10:46:35135 DCHECK(clipboard_content_);
136}
137
Robbie Gibsonf4e78b82019-02-20 18:00:19138ClipboardProvider::~ClipboardProvider() {}
sdefresne70948d62015-08-11 10:46:35139
Robbie Gibsonf4e78b82019-02-20 18:00:19140void ClipboardProvider::Start(const AutocompleteInput& input,
141 bool minimal_changes) {
sdefresne70948d62015-08-11 10:46:35142 matches_.clear();
Robbie Gibson1a9405b2019-02-21 12:20:45143 field_trial_triggered_ = false;
jif3986ea502016-07-13 13:43:42144
145 // If the user started typing, do not offer clipboard based match.
Tommy C. Li17b5ba112020-08-11 02:29:21146 if (input.focus_type() == OmniboxFocusType::DEFAULT)
sdefresne70948d62015-08-11 10:46:35147 return;
Robbie Gibson71b0c522019-02-13 00:05:58148
149 // Image matched was kicked off asynchronously, so proceed when that ends.
Robbie Gibson200b1b62019-10-08 16:13:47150 if (CreateImageMatch(input))
Robbie Gibson71b0c522019-02-13 00:05:58151 return;
Robbie Gibson200b1b62019-10-08 16:13:47152
Robbie Gibsonc28f2fd42020-08-17 18:52:22153 bool read_clipboard_content = false;
154 bool read_clipboard_url;
Anton Bikineev1156b5f2021-05-15 22:35:36155 absl::optional<AutocompleteMatch> optional_match =
Robbie Gibsonc28f2fd42020-08-17 18:52:22156 CreateURLMatch(input, &read_clipboard_url);
157 read_clipboard_content |= read_clipboard_url;
158 if (!optional_match) {
159 bool read_clipboard_text;
160 optional_match = CreateTextMatch(input, &read_clipboard_text);
161 read_clipboard_content |= read_clipboard_text;
162 }
Robbie Gibson200b1b62019-10-08 16:13:47163
Robbie Gibsonc28f2fd42020-08-17 18:52:22164 if (optional_match) {
165 AddCreatedMatchWithTracking(input, std::move(optional_match).value(),
166 clipboard_content_->GetClipboardContentAge());
mpearsonea5016a2017-06-01 22:20:32167 return;
Robbie Gibsonc28f2fd42020-08-17 18:52:22168 }
sdefresne70948d62015-08-11 10:46:35169
Robbie Gibsonc28f2fd42020-08-17 18:52:22170 // If there was clipboard content, but no match, don't proceed. There was
171 // some other reason for not creating a match (e.g. copied URL but the URL was
172 // the same as the current URL).
173 if (read_clipboard_content) {
174 return;
175 }
176
177 // On iOS 14, accessing the clipboard contents shows a notification to the
178 // user. To avoid this, all the methods above will not check the contents and
Anton Bikineev1156b5f2021-05-15 22:35:36179 // will return false/absl::nullopt. Instead, check the existence of content
Robbie Gibsonc28f2fd42020-08-17 18:52:22180 // without accessing the actual content and create blank matches.
181 done_ = false;
182 // Image matched was kicked off asynchronously, so proceed when that ends.
183 CheckClipboardContent(input);
Robbie Gibson71b0c522019-02-13 00:05:58184}
185
Robbie Gibsonf4e78b82019-02-20 18:00:19186void ClipboardProvider::Stop(bool clear_cached_results,
187 bool due_to_user_inactivity) {
Robbie Gibson71b0c522019-02-13 00:05:58188 callback_weak_ptr_factory_.InvalidateWeakPtrs();
189 AutocompleteProvider::Stop(clear_cached_results, due_to_user_inactivity);
190}
191
Gang Wuba18dba2019-09-25 03:43:21192void ClipboardProvider::DeleteMatch(const AutocompleteMatch& match) {
Gang Wu0db1f1fa2019-10-02 20:30:10193 RecordDeletingClipboardSuggestionMetrics(
194 match.type, clipboard_content_->GetClipboardContentAge());
Gang Wuba18dba2019-09-25 03:43:21195 clipboard_content_->ClearClipboardContent();
196
197 const auto pred = [&match](const AutocompleteMatch& i) {
198 return i.contents == match.contents && i.type == match.type;
199 };
200 base::EraseIf(matches_, pred);
201}
202
203void ClipboardProvider::AddProviderInfo(ProvidersInfo* provider_info) const {
204 // If a URL wasn't suggested on this most recent focus event, don't bother
205 // setting |times_returned_results_in_session|, as in effect this URL has
206 // never been suggested during the current session. (For the purpose of
207 // this provider, we define a session as intervals between when a URL
208 // clipboard suggestion changes.)
209 if (current_url_suggested_times_ == 0)
210 return;
211 provider_info->push_back(metrics::OmniboxEventProto_ProviderInfo());
212 metrics::OmniboxEventProto_ProviderInfo& new_entry = provider_info->back();
213 new_entry.set_provider(AsOmniboxEventProviderType());
214 new_entry.set_provider_done(done_);
215 new_entry.set_times_returned_results_in_session(current_url_suggested_times_);
216
217 if (field_trial_triggered_ || field_trial_triggered_in_session_) {
218 std::vector<uint32_t> field_trial_hashes;
219 OmniboxFieldTrial::GetActiveSuggestFieldTrialHashes(&field_trial_hashes);
220 for (uint32_t trial : field_trial_hashes) {
221 if (field_trial_triggered_) {
222 new_entry.mutable_field_trial_triggered()->Add(trial);
223 }
224 if (field_trial_triggered_in_session_) {
225 new_entry.mutable_field_trial_triggered_in_session()->Add(trial);
226 }
227 }
228 }
229}
230
Robbie Gibson1a9405b2019-02-21 12:20:45231void ClipboardProvider::ResetSession() {
232 field_trial_triggered_ = false;
233 field_trial_triggered_in_session_ = false;
234}
235
Robbie Gibsonf4e78b82019-02-20 18:00:19236void ClipboardProvider::AddCreatedMatchWithTracking(
Robbie Gibson71b0c522019-02-13 00:05:58237 const AutocompleteInput& input,
Robbie Gibson50dce212019-02-19 16:42:25238 const AutocompleteMatch& match,
239 const base::TimeDelta clipboard_contents_age) {
Robbie Gibsond2a2eee2019-01-25 14:38:10240 // Record the number of times the currently-offered URL has been suggested.
241 // This only works over this run of Chrome; if the URL was in the clipboard
242 // on a previous run, those offerings will not be counted.
Robbie Gibson71b0c522019-02-13 00:05:58243 if (match.destination_url == current_url_suggested_) {
Robbie Gibsond2a2eee2019-01-25 14:38:10244 current_url_suggested_times_++;
245 } else {
Robbie Gibson71b0c522019-02-13 00:05:58246 current_url_suggested_ = match.destination_url;
Robbie Gibsond2a2eee2019-01-25 14:38:10247 current_url_suggested_times_ = 1;
248 }
249
jif3986ea502016-07-13 13:43:42250 // If the omnibox is not empty, add a default match.
251 // This match will be opened when the user presses "Enter".
252 if (!input.text().empty()) {
Jan Wilken Dörrie624543a2021-03-12 04:18:09253 const std::u16string description =
gcomanici8cabc77f2017-04-27 20:04:54254 (base::FeatureList::IsEnabled(omnibox::kDisplayTitleForCurrentUrl))
255 ? input.current_title()
Jan Wilken Dörrie624543a2021-03-12 04:18:09256 : std::u16string();
Tomasz Wiszkowski913b8062021-03-10 21:52:48257 AutocompleteMatch verbatim_match = VerbatimMatchForURL(
258 this, client_, input, input.current_url(), description, -1);
sdefresne70948d62015-08-11 10:46:35259 matches_.push_back(verbatim_match);
jif3986ea502016-07-13 13:43:42260 }
Gang Wu0db1f1fa2019-10-02 20:30:10261
262 RecordCreatingClipboardSuggestionMetrics(current_url_suggested_times_,
263 matches_.empty(), match.type,
264 clipboard_contents_age);
Robbie Gibsone6915ce12018-12-26 12:08:10265
manuk21aa24d2019-05-01 15:08:16266 matches_.push_back(match);
Robbie Gibsone6915ce12018-12-26 12:08:10267}
268
Robbie Gibsonc28f2fd42020-08-17 18:52:22269bool ClipboardProvider::TemplateURLSupportsTextSearch() {
270 TemplateURLService* url_service = client_->GetTemplateURLService();
271 const TemplateURL* default_url = url_service->GetDefaultSearchProvider();
272 if (!default_url)
273 return false;
274
275 DCHECK(!default_url->url().empty());
276 DCHECK(default_url->url_ref().IsValid(url_service->search_terms_data()));
277 return true;
278}
279
280bool ClipboardProvider::TemplateURLSupportsImageSearch() {
281 TemplateURLService* url_service = client_->GetTemplateURLService();
282 const TemplateURL* default_url = url_service->GetDefaultSearchProvider();
283
284 return default_url && !default_url->image_url().empty() &&
285 default_url->image_url_ref().IsValid(url_service->search_terms_data());
286}
287
288void ClipboardProvider::CheckClipboardContent(const AutocompleteInput& input) {
289 std::set<ClipboardContentType> desired_types;
290 desired_types.insert(ClipboardContentType::URL);
291
292 if (TemplateURLSupportsTextSearch()) {
293 desired_types.insert(ClipboardContentType::Text);
294 }
295
Gang Wua801f8b2020-11-03 18:23:13296 if (TemplateURLSupportsImageSearch()) {
Robbie Gibsonc28f2fd42020-08-17 18:52:22297 desired_types.insert(ClipboardContentType::Image);
298 }
299
300 // We want to get the age here because the contents of the clipboard could
301 // change after this point. We want the age of the contents we actually use,
302 // not the age of whatever's on the clipboard when the histogram is created
303 // (i.e when the match is created).
304 base::TimeDelta clipboard_contents_age =
305 clipboard_content_->GetClipboardContentAge();
306 clipboard_content_->HasRecentContentFromClipboard(
307 desired_types,
308 base::BindOnce(&ClipboardProvider::OnReceiveClipboardContent,
309 callback_weak_ptr_factory_.GetWeakPtr(), input,
310 clipboard_contents_age));
311}
312
313void ClipboardProvider::OnReceiveClipboardContent(
314 const AutocompleteInput& input,
315 base::TimeDelta clipboard_contents_age,
316 std::set<ClipboardContentType> matched_types) {
317 if (matched_types.find(ClipboardContentType::Image) != matched_types.end()) {
318 // The image content will be added in later. If the image is large, encoding
319 // the image may take some time, so just be wary whenever that step happens
320 // (e.g OmniboxView::OpenMatch).
321 AutocompleteMatch match = NewBlankImageMatch();
322 field_trial_triggered_ = true;
323 field_trial_triggered_in_session_ = true;
Gang Wua801f8b2020-11-03 18:23:13324 AddCreatedMatchWithTracking(input, match, clipboard_contents_age);
325 listener_->OnProviderUpdate(true);
Robbie Gibsonc28f2fd42020-08-17 18:52:22326 } else if (matched_types.find(ClipboardContentType::URL) !=
327 matched_types.end()) {
328 AutocompleteMatch match = NewBlankURLMatch();
329 AddCreatedMatchWithTracking(input, match, clipboard_contents_age);
330 listener_->OnProviderUpdate(true);
331 } else if (matched_types.find(ClipboardContentType::Text) !=
332 matched_types.end()) {
333 AutocompleteMatch match = NewBlankTextMatch();
334 AddCreatedMatchWithTracking(input, match, clipboard_contents_age);
335 listener_->OnProviderUpdate(true);
336 }
337 done_ = true;
338}
339
Anton Bikineev1156b5f2021-05-15 22:35:36340absl::optional<AutocompleteMatch> ClipboardProvider::CreateURLMatch(
Robbie Gibsonc28f2fd42020-08-17 18:52:22341 const AutocompleteInput& input,
342 bool* read_clipboard_content) {
343 *read_clipboard_content = false;
Gang Wu6b5405d72021-04-09 21:48:20344 if (base::FeatureList::IsEnabled(
345 omnibox::kClipboardSuggestionContentHidden)) {
Anton Bikineev1156b5f2021-05-15 22:35:36346 return absl::nullopt;
Gang Wu6b5405d72021-04-09 21:48:20347 }
Robbie Gibsone6915ce12018-12-26 12:08:10348 // The clipboard does not contain a URL worth suggesting.
Anton Bikineev1156b5f2021-05-15 22:35:36349 absl::optional<GURL> optional_gurl =
Robbie Gibsone6915ce12018-12-26 12:08:10350 clipboard_content_->GetRecentURLFromClipboard();
Robbie Gibson200b1b62019-10-08 16:13:47351 if (!optional_gurl)
Anton Bikineev1156b5f2021-05-15 22:35:36352 return absl::nullopt;
Robbie Gibson200b1b62019-10-08 16:13:47353
Robbie Gibsonc28f2fd42020-08-17 18:52:22354 *read_clipboard_content = true;
Robbie Gibsone6915ce12018-12-26 12:08:10355 GURL url = std::move(optional_gurl).value();
356
357 // The URL on the page is the same as the URL in the clipboard. Don't
358 // bother suggesting it.
359 if (url == input.current_url())
Anton Bikineev1156b5f2021-05-15 22:35:36360 return absl::nullopt;
Robbie Gibsone6915ce12018-12-26 12:08:10361
Robbie Gibsonc28f2fd42020-08-17 18:52:22362 return NewClipboardURLMatch(url);
363}
Robbie Gibsone6915ce12018-12-26 12:08:10364
Anton Bikineev1156b5f2021-05-15 22:35:36365absl::optional<AutocompleteMatch> ClipboardProvider::CreateTextMatch(
Robbie Gibsonc28f2fd42020-08-17 18:52:22366 const AutocompleteInput& input,
367 bool* read_clipboard_content) {
368 *read_clipboard_content = false;
Gang Wu6b5405d72021-04-09 21:48:20369 if (base::FeatureList::IsEnabled(
370 omnibox::kClipboardSuggestionContentHidden)) {
Anton Bikineev1156b5f2021-05-15 22:35:36371 return absl::nullopt;
Gang Wu6b5405d72021-04-09 21:48:20372 }
373
Anton Bikineev1156b5f2021-05-15 22:35:36374 absl::optional<std::u16string> optional_text =
Robbie Gibsonc28f2fd42020-08-17 18:52:22375 clipboard_content_->GetRecentTextFromClipboard();
376 if (!optional_text)
Anton Bikineev1156b5f2021-05-15 22:35:36377 return absl::nullopt;
Robbie Gibsonc28f2fd42020-08-17 18:52:22378
379 *read_clipboard_content = true;
Jan Wilken Dörrie624543a2021-03-12 04:18:09380 std::u16string text = std::move(optional_text).value();
Robbie Gibsonc28f2fd42020-08-17 18:52:22381
382 // The clipboard can contain the empty string, which shouldn't be suggested.
383 if (text.empty())
Anton Bikineev1156b5f2021-05-15 22:35:36384 return absl::nullopt;
Robbie Gibsonc28f2fd42020-08-17 18:52:22385
386 // The text in the clipboard is a url. We don't want to prompt the user to
387 // search for a url.
388 if (GURL(text).is_valid())
Anton Bikineev1156b5f2021-05-15 22:35:36389 return absl::nullopt;
Robbie Gibsonc28f2fd42020-08-17 18:52:22390
391 return NewClipboardTextMatch(text);
392}
393
394bool ClipboardProvider::CreateImageMatch(const AutocompleteInput& input) {
Gang Wu6b5405d72021-04-09 21:48:20395 if (base::FeatureList::IsEnabled(
396 omnibox::kClipboardSuggestionContentHidden)) {
397 return false;
398 }
399
Robbie Gibsonc28f2fd42020-08-17 18:52:22400 if (!clipboard_content_->HasRecentImageFromClipboard()) {
401 return false;
402 }
403
404 if (!TemplateURLSupportsImageSearch()) {
405 return false;
406 }
407
408 done_ = false;
409
410 // We want to get the age here because the contents of the clipboard could
411 // change after this point. We want the age of the image we actually use, not
412 // the age of whatever's on the clipboard when the histogram is created (i.e
413 // when the match is created).
414 base::TimeDelta clipboard_contents_age =
415 clipboard_content_->GetClipboardContentAge();
416 clipboard_content_->GetRecentImageFromClipboard(base::BindOnce(
417 &ClipboardProvider::CreateImageMatchCallback,
418 callback_weak_ptr_factory_.GetWeakPtr(), input, clipboard_contents_age));
419 return true;
420}
421
422void ClipboardProvider::CreateImageMatchCallback(
423 const AutocompleteInput& input,
424 const base::TimeDelta clipboard_contents_age,
Anton Bikineev1156b5f2021-05-15 22:35:36425 absl::optional<gfx::Image> optional_image) {
Robbie Gibsonc28f2fd42020-08-17 18:52:22426 NewClipboardImageMatch(
Gang Wu67a448d2021-04-06 17:15:57427 optional_image, base::BindOnce(&ClipboardProvider::AddImageMatchCallback,
428 callback_weak_ptr_factory_.GetWeakPtr(),
429 input, clipboard_contents_age));
Robbie Gibsonc28f2fd42020-08-17 18:52:22430}
Gang Wu67a448d2021-04-06 17:15:57431
Robbie Gibsonc28f2fd42020-08-17 18:52:22432void ClipboardProvider::AddImageMatchCallback(
433 const AutocompleteInput& input,
434 const base::TimeDelta clipboard_contents_age,
Anton Bikineev1156b5f2021-05-15 22:35:36435 absl::optional<AutocompleteMatch> match) {
Robbie Gibsonc28f2fd42020-08-17 18:52:22436 if (!match) {
437 return;
438 }
439 AddCreatedMatchWithTracking(input, match.value(), clipboard_contents_age);
440 listener_->OnProviderUpdate(true);
441 done_ = true;
442}
443
444AutocompleteMatch ClipboardProvider::NewBlankURLMatch() {
Gang Wu64198f2002020-06-25 00:28:27445 AutocompleteMatch match(this, kClipboardMatchRelevanceScore,
446 IsMatchDeletionEnabled(),
Robbie Gibson3e74f7d2019-01-21 14:03:00447 AutocompleteMatchType::CLIPBOARD_URL);
Robbie Gibsonc28f2fd42020-08-17 18:52:22448
449 match.description.assign(l10n_util::GetStringUTF16(IDS_LINK_FROM_CLIPBOARD));
450 if (!match.description.empty())
451 match.description_class.push_back({0, ACMatchClassification::NONE});
452 return match;
453}
454
455AutocompleteMatch ClipboardProvider::NewClipboardURLMatch(GURL url) {
456 DCHECK(url.is_valid());
457
458 AutocompleteMatch match = NewBlankURLMatch();
459
Gang Wu6b5405d72021-04-09 21:48:20460 UpdateClipboardURLContent(url, &match);
Robbie Gibson66a41d92019-11-27 10:04:11461
Robbie Gibson0ab91ac2020-08-13 15:06:05462 return match;
463}
Robbie Gibson146bab02019-04-02 22:29:34464
Robbie Gibsonc28f2fd42020-08-17 18:52:22465AutocompleteMatch ClipboardProvider::NewBlankTextMatch() {
466 AutocompleteMatch match(this, kClipboardMatchRelevanceScore,
467 IsMatchDeletionEnabled(),
468 AutocompleteMatchType::CLIPBOARD_TEXT);
Justin Cohen40f1bad2020-08-13 19:34:26469
Robbie Gibsonc28f2fd42020-08-17 18:52:22470 match.description.assign(l10n_util::GetStringUTF16(IDS_TEXT_FROM_CLIPBOARD));
471 if (!match.description.empty())
472 match.description_class.push_back({0, ACMatchClassification::NONE});
Justin Cohen40f1bad2020-08-13 19:34:26473
Robbie Gibsonc28f2fd42020-08-17 18:52:22474 match.transition = ui::PAGE_TRANSITION_GENERATED;
475 return match;
476}
Justin Cohen40f1bad2020-08-13 19:34:26477
Anton Bikineev1156b5f2021-05-15 22:35:36478absl::optional<AutocompleteMatch> ClipboardProvider::NewClipboardTextMatch(
Jan Wilken Dörrie624543a2021-03-12 04:18:09479 std::u16string text) {
Robbie Gibsonc28f2fd42020-08-17 18:52:22480 AutocompleteMatch match = NewBlankTextMatch();
Robbie Gibson66a41d92019-11-27 10:04:11481
Gang Wu6b5405d72021-04-09 21:48:20482 if (!UpdateClipboardTextContent(text, &match))
Anton Bikineev1156b5f2021-05-15 22:35:36483 return absl::nullopt;
Robbie Gibson200b1b62019-10-08 16:13:47484
Robbie Gibsonc28f2fd42020-08-17 18:52:22485 return match;
486}
487
488AutocompleteMatch ClipboardProvider::NewBlankImageMatch() {
489 AutocompleteMatch match(this, kClipboardMatchRelevanceScore,
490 IsMatchDeletionEnabled(),
491 AutocompleteMatchType::CLIPBOARD_IMAGE);
492
493 match.description.assign(l10n_util::GetStringUTF16(IDS_IMAGE_FROM_CLIPBOARD));
manuk21aa24d2019-05-01 15:08:16494 if (!match.description.empty())
495 match.description_class.push_back({0, ACMatchClassification::NONE});
Robbie Gibsone6915ce12018-12-26 12:08:10496
Robbie Gibsonc28f2fd42020-08-17 18:52:22497 // This will end up being something like "Search for Copied Image." This may
498 // seem strange to use for |fill_into_edit|, but it is because iOS requires
499 // some text in the text field for the Enter key to work when using keyboard
500 // navigation.
501 match.fill_into_edit = match.description;
Robbie Gibson71b0c522019-02-13 00:05:58502 match.transition = ui::PAGE_TRANSITION_GENERATED;
503
Robbie Gibsone6915ce12018-12-26 12:08:10504 return match;
sdefresne70948d62015-08-11 10:46:35505}
mpearsonea5016a2017-06-01 22:20:32506
Robbie Gibsonc28f2fd42020-08-17 18:52:22507void ClipboardProvider::NewClipboardImageMatch(
Anton Bikineev1156b5f2021-05-15 22:35:36508 absl::optional<gfx::Image> optional_image,
Robbie Gibsonc28f2fd42020-08-17 18:52:22509 ClipboardImageMatchCallback callback) {
Robbie Gibson25d0ca22020-10-19 17:00:32510 // ImageSkia::ToImageSkia should only be called if the gfx::Image is
511 // non-empty. It is unclear when the clipboard returns a non-optional but
512 // empty image. See crbug.com/1136759 for more details.
513 if (!optional_image || optional_image.value().IsEmpty()) {
Anton Bikineev1156b5f2021-05-15 22:35:36514 std::move(callback).Run(absl::nullopt);
Maksim Ivanov8f2cf7fa2020-08-31 16:39:38515 return;
516 }
Gang Wu05397842020-07-17 17:06:44517 gfx::ImageSkia image_skia = *optional_image.value().ToImageSkia();
518 image_skia.MakeThreadSafe();
Gabriel Charetteafbec8752020-05-29 04:34:11519 base::ThreadPool::PostTaskAndReplyWithResult(
Robbie Gibson71b0c522019-02-13 00:05:58520 FROM_HERE,
Gang Wu05397842020-07-17 17:06:44521 base::BindOnce(&ClipboardProvider::EncodeClipboardImage, image_skia),
Robbie Gibsonf4e78b82019-02-20 18:00:19522 base::BindOnce(&ClipboardProvider::ConstructImageMatchCallback,
Robbie Gibsonc28f2fd42020-08-17 18:52:22523 callback_weak_ptr_factory_.GetWeakPtr(),
524 std::move(callback)));
Robbie Gibson71b0c522019-02-13 00:05:58525}
526
Gang Wu6b5405d72021-04-09 21:48:20527void ClipboardProvider::UpdateClipboardMatchWithContent(
528 AutocompleteMatch* match,
529 ClipboardMatchCallback callback) {
530 DCHECK(match);
531 if (match->type == AutocompleteMatchType::CLIPBOARD_URL) {
532 clipboard_content_->GetRecentURLFromClipboard(base::BindOnce(
533 &ClipboardProvider::OnReceiveURLForMatchWithContent,
534 callback_weak_ptr_factory_.GetWeakPtr(), std::move(callback), match));
535 return;
536 } else if (match->type == AutocompleteMatchType::CLIPBOARD_TEXT) {
537 clipboard_content_->GetRecentTextFromClipboard(base::BindOnce(
538 &ClipboardProvider::OnReceiveTextForMatchWithContent,
539 callback_weak_ptr_factory_.GetWeakPtr(), std::move(callback), match));
540 return;
541 } else if (match->type == AutocompleteMatchType::CLIPBOARD_IMAGE) {
542 clipboard_content_->GetRecentImageFromClipboard(base::BindOnce(
543 &ClipboardProvider::OnReceiveImageForMatchWithContent,
544 callback_weak_ptr_factory_.GetWeakPtr(), std::move(callback), match));
545 return;
546 }
547}
548
Robbie Gibsonf4e78b82019-02-20 18:00:19549scoped_refptr<base::RefCountedMemory> ClipboardProvider::EncodeClipboardImage(
Gang Wu05397842020-07-17 17:06:44550 gfx::ImageSkia image_skia) {
551 gfx::Image resized_image =
552 gfx::ResizedImageForSearchByImage(gfx::Image(image_skia));
Robbie Gibson71b0c522019-02-13 00:05:58553 return resized_image.As1xPNGBytes();
554}
555
Robbie Gibsonf4e78b82019-02-20 18:00:19556void ClipboardProvider::ConstructImageMatchCallback(
Robbie Gibsonc28f2fd42020-08-17 18:52:22557 ClipboardImageMatchCallback callback,
Robbie Gibson71b0c522019-02-13 00:05:58558 scoped_refptr<base::RefCountedMemory> image_bytes) {
Robbie Gibsonc28f2fd42020-08-17 18:52:22559 TemplateURLService* url_service = client_->GetTemplateURLService();
Robbie Gibson71b0c522019-02-13 00:05:58560 const TemplateURL* default_url = url_service->GetDefaultSearchProvider();
Robbie Gibson200b1b62019-10-08 16:13:47561 DCHECK(default_url);
Gang Wu64198f2002020-06-25 00:28:27562
Robbie Gibsonc28f2fd42020-08-17 18:52:22563 AutocompleteMatch match = NewBlankImageMatch();
Robbie Gibson66a41d92019-11-27 10:04:11564
Gang Wuc0173f12020-05-27 06:10:15565 match.search_terms_args =
Jan Wilken Dörrie756999e2021-03-23 15:05:24566 std::make_unique<TemplateURLRef::SearchTermsArgs>(u"");
Gang Wuc0173f12020-05-27 06:10:15567 match.search_terms_args->image_thumbnail_content.assign(
568 image_bytes->front_as<char>(), image_bytes->size());
Robbie Gibson71b0c522019-02-13 00:05:58569 TemplateURLRef::PostContent post_content;
570 GURL result(default_url->image_url_ref().ReplaceSearchTerms(
Gang Wuc0173f12020-05-27 06:10:15571 *match.search_terms_args.get(), url_service->search_terms_data(),
572 &post_content));
573
Gang Wua801f8b2020-11-03 18:23:13574 if (!base::FeatureList::IsEnabled(omnibox::kImageSearchSuggestionThumbnail)) {
Gang Wuc0173f12020-05-27 06:10:15575 // If Omnibox image suggestion do not need thumbnail, release memory.
576 match.search_terms_args.reset();
577 }
Robbie Gibson71b0c522019-02-13 00:05:58578 match.destination_url = result;
579 match.post_content =
580 std::make_unique<TemplateURLRef::PostContent>(post_content);
581
Robbie Gibsonc28f2fd42020-08-17 18:52:22582 std::move(callback).Run(match);
Robbie Gibsone13f2e62019-01-09 11:00:24583}
Gang Wu6b5405d72021-04-09 21:48:20584
585void ClipboardProvider::OnReceiveURLForMatchWithContent(
586 ClipboardMatchCallback callback,
587 AutocompleteMatch* match,
Anton Bikineev1156b5f2021-05-15 22:35:36588 absl::optional<GURL> optional_gurl) {
Gang Wu6b5405d72021-04-09 21:48:20589 if (!optional_gurl)
590 return;
591
592 GURL url = std::move(optional_gurl).value();
593 UpdateClipboardURLContent(url, match);
594 std::move(callback).Run();
595}
596
597void ClipboardProvider::OnReceiveTextForMatchWithContent(
598 ClipboardMatchCallback callback,
599 AutocompleteMatch* match,
Anton Bikineev1156b5f2021-05-15 22:35:36600 absl::optional<std::u16string> optional_text) {
Gang Wu6b5405d72021-04-09 21:48:20601 if (!optional_text)
602 return;
603
604 std::u16string text = std::move(optional_text).value();
605 if (!UpdateClipboardTextContent(text, match))
606 return;
607
608 std::move(callback).Run();
609}
610
611void ClipboardProvider::OnReceiveImageForMatchWithContent(
612 ClipboardMatchCallback callback,
613 AutocompleteMatch* match,
Anton Bikineev1156b5f2021-05-15 22:35:36614 absl::optional<gfx::Image> optional_image) {
Gang Wu6b5405d72021-04-09 21:48:20615 if (!optional_image)
616 return;
617
618 gfx::Image image = std::move(optional_image).value();
619 NewClipboardImageMatch(
620 image,
621 base::BindOnce(&ClipboardProvider::OnReceiveImageMatchForMatchWithContent,
622 callback_weak_ptr_factory_.GetWeakPtr(),
623 std::move(callback), match));
624}
625
626void ClipboardProvider::OnReceiveImageMatchForMatchWithContent(
627 ClipboardMatchCallback callback,
628 AutocompleteMatch* match,
Anton Bikineev1156b5f2021-05-15 22:35:36629 absl::optional<AutocompleteMatch> optional_match) {
Gang Wu6b5405d72021-04-09 21:48:20630 DCHECK(match);
631 if (!optional_match)
632 return;
633
634 match->destination_url = std::move(optional_match->destination_url);
635 match->post_content = std::move(optional_match->post_content);
636 match->search_terms_args = std::move(optional_match->search_terms_args);
637
638 std::move(callback).Run();
639}
640
641void ClipboardProvider::UpdateClipboardURLContent(const GURL& url,
642 AutocompleteMatch* match) {
643 DCHECK(url.is_valid());
644 DCHECK(match);
645
646 match->destination_url = url;
647
648 // Because the user did not type a related input to get this clipboard
649 // suggestion, preserve the subdomain so the user has extra context.
650 auto format_types = AutocompleteMatch::GetFormatTypes(false, true);
651 match->contents.assign(url_formatter::FormatUrl(
652 url, format_types, net::UnescapeRule::SPACES, nullptr, nullptr, nullptr));
653 if (!match->contents.empty())
654 match->contents_class.push_back({0, ACMatchClassification::URL});
655 match->fill_into_edit =
656 AutocompleteInput::FormattedStringWithEquivalentMeaning(
657 url, match->contents, client_->GetSchemeClassifier(), nullptr);
658}
659
660bool ClipboardProvider::UpdateClipboardTextContent(const std::u16string& text,
661 AutocompleteMatch* match) {
662 DCHECK(match);
663
664 // The text in the clipboard is a url. We don't want to prompt the user to
665 // search for a url.
666 if (GURL(text).is_valid())
667 return false;
668
669 match->fill_into_edit = text;
670
671 TemplateURLService* url_service = client_->GetTemplateURLService();
672 const TemplateURL* default_url = url_service->GetDefaultSearchProvider();
673 if (!default_url)
674 return false;
675
676 DCHECK(!default_url->url().empty());
677 DCHECK(default_url->url_ref().IsValid(url_service->search_terms_data()));
678 TemplateURLRef::SearchTermsArgs search_args(text);
679 GURL result(default_url->url_ref().ReplaceSearchTerms(
680 search_args, url_service->search_terms_data()));
681
682 match->destination_url = result;
683 match->contents.assign(l10n_util::GetStringFUTF16(
684 IDS_COPIED_TEXT_FROM_CLIPBOARD, AutocompleteMatch::SanitizeString(text)));
685 if (!match->contents.empty())
686 match->contents_class.push_back({0, ACMatchClassification::NONE});
687
688 match->keyword = default_url->keyword();
689
690 return true;
691}