1 // 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_provider.h"
6
7 #include <algorithm>
8 #include <memory>
9 #include <utility>
10 #include <vector>
11
12 #include "base/bind.h"
13 #include "base/feature_list.h"
14 #include "base/memory/ref_counted_memory.h"
15 #include "base/memory/weak_ptr.h"
16 #include "base/metrics/field_trial_params.h"
17 #include "base/metrics/histogram_functions.h"
18 #include "base/metrics/histogram_macros.h"
19 #include "base/metrics/user_metrics.h"
20 #include "base/optional.h"
21 #include "base/strings/utf_string_conversions.h"
22 #include "base/task/post_task.h"
23 #include "base/task/thread_pool.h"
24 #include "build/build_config.h"
25 #include "components/omnibox/browser/autocomplete_input.h"
26 #include "components/omnibox/browser/autocomplete_match.h"
27 #include "components/omnibox/browser/autocomplete_provider_client.h"
28 #include "components/omnibox/browser/autocomplete_provider_listener.h"
29 #include "components/omnibox/browser/verbatim_match.h"
30 #include "components/omnibox/common/omnibox_features.h"
31 #include "components/open_from_clipboard/clipboard_recent_content.h"
32 #include "components/search_engines/omnibox_focus_type.h"
33 #include "components/search_engines/template_url_service.h"
34 #include "components/strings/grit/components_strings.h"
35 #include "components/url_formatter/url_formatter.h"
36 #include "ui/base/l10n/l10n_util.h"
37 #include "ui/gfx/image/image_skia.h"
38 #include "ui/gfx/image/image_util.h"
39
40 namespace {
41
42 const size_t kMaxClipboardSuggestionShownNumTimesSimpleSize = 20;
43
44 // Clipboard suggestions should be placed above search and url suggestions
45 // (including MostVisited tiles), but below query tiles.
46 const int kClipboardMatchRelevanceScore = 1501;
47
IsMatchDeletionEnabled()48 bool IsMatchDeletionEnabled() {
49 return base::FeatureList::IsEnabled(
50 omnibox::kOmniboxRemoveSuggestionsFromClipboard);
51 }
52
RecordCreatingClipboardSuggestionMetrics(size_t current_url_suggested_times,bool matches_is_empty,AutocompleteMatchType::Type match_type,const base::TimeDelta clipboard_contents_age)53 void 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
RecordDeletingClipboardSuggestionMetrics(AutocompleteMatchType::Type match_type,const base::TimeDelta clipboard_contents_age)101 void RecordDeletingClipboardSuggestionMetrics(
102 AutocompleteMatchType::Type match_type,
103 const base::TimeDelta clipboard_contents_age) {
104 base::RecordAction(
105 base::UserMetricsAction("Omnibox.ClipboardSuggestionRemoved"));
106
107 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);
115 } else if (match_type == AutocompleteMatchType::CLIPBOARD_IMAGE) {
116 UMA_HISTOGRAM_LONG_TIMES_100("Omnibox.ClipboardSuggestionRemovedAge.IMAGE",
117 clipboard_contents_age);
118 }
119 }
120
121 } // namespace
122
ClipboardProvider(AutocompleteProviderClient * client,AutocompleteProviderListener * listener,HistoryURLProvider * history_url_provider,ClipboardRecentContent * clipboard_content)123 ClipboardProvider::ClipboardProvider(AutocompleteProviderClient* client,
124 AutocompleteProviderListener* listener,
125 HistoryURLProvider* history_url_provider,
126 ClipboardRecentContent* clipboard_content)
127 : AutocompleteProvider(AutocompleteProvider::TYPE_CLIPBOARD),
128 client_(client),
129 listener_(listener),
130 clipboard_content_(clipboard_content),
131 history_url_provider_(history_url_provider),
132 current_url_suggested_times_(0),
133 field_trial_triggered_(false),
134 field_trial_triggered_in_session_(false) {
135 DCHECK(clipboard_content_);
136 }
137
~ClipboardProvider()138 ClipboardProvider::~ClipboardProvider() {}
139
Start(const AutocompleteInput & input,bool minimal_changes)140 void ClipboardProvider::Start(const AutocompleteInput& input,
141 bool minimal_changes) {
142 matches_.clear();
143 field_trial_triggered_ = false;
144
145 // If the user started typing, do not offer clipboard based match.
146 if (input.focus_type() == OmniboxFocusType::DEFAULT)
147 return;
148
149 // Image matched was kicked off asynchronously, so proceed when that ends.
150 if (CreateImageMatch(input))
151 return;
152
153 bool read_clipboard_content = false;
154 bool read_clipboard_url;
155 base::Optional<AutocompleteMatch> optional_match =
156 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 }
163
164 if (optional_match) {
165 AddCreatedMatchWithTracking(input, std::move(optional_match).value(),
166 clipboard_content_->GetClipboardContentAge());
167 return;
168 }
169
170 // 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
179 // will return false/base::nullopt. Instead, check the existence of content
180 // 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);
184 }
185
Stop(bool clear_cached_results,bool due_to_user_inactivity)186 void ClipboardProvider::Stop(bool clear_cached_results,
187 bool due_to_user_inactivity) {
188 callback_weak_ptr_factory_.InvalidateWeakPtrs();
189 AutocompleteProvider::Stop(clear_cached_results, due_to_user_inactivity);
190 }
191
DeleteMatch(const AutocompleteMatch & match)192 void ClipboardProvider::DeleteMatch(const AutocompleteMatch& match) {
193 RecordDeletingClipboardSuggestionMetrics(
194 match.type, clipboard_content_->GetClipboardContentAge());
195 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
AddProviderInfo(ProvidersInfo * provider_info) const203 void 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
ResetSession()231 void ClipboardProvider::ResetSession() {
232 field_trial_triggered_ = false;
233 field_trial_triggered_in_session_ = false;
234 }
235
AddCreatedMatchWithTracking(const AutocompleteInput & input,const AutocompleteMatch & match,const base::TimeDelta clipboard_contents_age)236 void ClipboardProvider::AddCreatedMatchWithTracking(
237 const AutocompleteInput& input,
238 const AutocompleteMatch& match,
239 const base::TimeDelta clipboard_contents_age) {
240 // 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.
243 if (match.destination_url == current_url_suggested_) {
244 current_url_suggested_times_++;
245 } else {
246 current_url_suggested_ = match.destination_url;
247 current_url_suggested_times_ = 1;
248 }
249
250 // 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()) {
253 const base::string16 description =
254 (base::FeatureList::IsEnabled(omnibox::kDisplayTitleForCurrentUrl))
255 ? input.current_title()
256 : base::string16();
257 AutocompleteMatch verbatim_match =
258 VerbatimMatchForURL(client_, input, input.current_url(), description,
259 history_url_provider_, -1);
260 matches_.push_back(verbatim_match);
261 }
262
263 RecordCreatingClipboardSuggestionMetrics(current_url_suggested_times_,
264 matches_.empty(), match.type,
265 clipboard_contents_age);
266
267 matches_.push_back(match);
268 }
269
TemplateURLSupportsTextSearch()270 bool ClipboardProvider::TemplateURLSupportsTextSearch() {
271 TemplateURLService* url_service = client_->GetTemplateURLService();
272 const TemplateURL* default_url = url_service->GetDefaultSearchProvider();
273 if (!default_url)
274 return false;
275
276 DCHECK(!default_url->url().empty());
277 DCHECK(default_url->url_ref().IsValid(url_service->search_terms_data()));
278 return true;
279 }
280
TemplateURLSupportsImageSearch()281 bool ClipboardProvider::TemplateURLSupportsImageSearch() {
282 TemplateURLService* url_service = client_->GetTemplateURLService();
283 const TemplateURL* default_url = url_service->GetDefaultSearchProvider();
284
285 return default_url && !default_url->image_url().empty() &&
286 default_url->image_url_ref().IsValid(url_service->search_terms_data());
287 }
288
CheckClipboardContent(const AutocompleteInput & input)289 void ClipboardProvider::CheckClipboardContent(const AutocompleteInput& input) {
290 std::set<ClipboardContentType> desired_types;
291 desired_types.insert(ClipboardContentType::URL);
292
293 if (TemplateURLSupportsTextSearch()) {
294 desired_types.insert(ClipboardContentType::Text);
295 }
296
297 if (TemplateURLSupportsImageSearch()) {
298 desired_types.insert(ClipboardContentType::Image);
299 }
300
301 // We want to get the age here because the contents of the clipboard could
302 // change after this point. We want the age of the contents we actually use,
303 // not the age of whatever's on the clipboard when the histogram is created
304 // (i.e when the match is created).
305 base::TimeDelta clipboard_contents_age =
306 clipboard_content_->GetClipboardContentAge();
307 clipboard_content_->HasRecentContentFromClipboard(
308 desired_types,
309 base::BindOnce(&ClipboardProvider::OnReceiveClipboardContent,
310 callback_weak_ptr_factory_.GetWeakPtr(), input,
311 clipboard_contents_age));
312 }
313
OnReceiveClipboardContent(const AutocompleteInput & input,base::TimeDelta clipboard_contents_age,std::set<ClipboardContentType> matched_types)314 void ClipboardProvider::OnReceiveClipboardContent(
315 const AutocompleteInput& input,
316 base::TimeDelta clipboard_contents_age,
317 std::set<ClipboardContentType> matched_types) {
318 if (matched_types.find(ClipboardContentType::Image) != matched_types.end()) {
319 // The image content will be added in later. If the image is large, encoding
320 // the image may take some time, so just be wary whenever that step happens
321 // (e.g OmniboxView::OpenMatch).
322 AutocompleteMatch match = NewBlankImageMatch();
323 field_trial_triggered_ = true;
324 field_trial_triggered_in_session_ = true;
325 AddCreatedMatchWithTracking(input, match, clipboard_contents_age);
326 listener_->OnProviderUpdate(true);
327 } else if (matched_types.find(ClipboardContentType::URL) !=
328 matched_types.end()) {
329 AutocompleteMatch match = NewBlankURLMatch();
330 AddCreatedMatchWithTracking(input, match, clipboard_contents_age);
331 listener_->OnProviderUpdate(true);
332 } else if (matched_types.find(ClipboardContentType::Text) !=
333 matched_types.end()) {
334 AutocompleteMatch match = NewBlankTextMatch();
335 AddCreatedMatchWithTracking(input, match, clipboard_contents_age);
336 listener_->OnProviderUpdate(true);
337 }
338 done_ = true;
339 }
340
CreateURLMatch(const AutocompleteInput & input,bool * read_clipboard_content)341 base::Optional<AutocompleteMatch> ClipboardProvider::CreateURLMatch(
342 const AutocompleteInput& input,
343 bool* read_clipboard_content) {
344 *read_clipboard_content = false;
345 // The clipboard does not contain a URL worth suggesting.
346 base::Optional<GURL> optional_gurl =
347 clipboard_content_->GetRecentURLFromClipboard();
348 if (!optional_gurl)
349 return base::nullopt;
350
351 *read_clipboard_content = true;
352 GURL url = std::move(optional_gurl).value();
353
354 // The URL on the page is the same as the URL in the clipboard. Don't
355 // bother suggesting it.
356 if (url == input.current_url())
357 return base::nullopt;
358
359 return NewClipboardURLMatch(url);
360 }
361
CreateTextMatch(const AutocompleteInput & input,bool * read_clipboard_content)362 base::Optional<AutocompleteMatch> ClipboardProvider::CreateTextMatch(
363 const AutocompleteInput& input,
364 bool* read_clipboard_content) {
365 *read_clipboard_content = false;
366 base::Optional<base::string16> optional_text =
367 clipboard_content_->GetRecentTextFromClipboard();
368 if (!optional_text)
369 return base::nullopt;
370
371 *read_clipboard_content = true;
372 base::string16 text = std::move(optional_text).value();
373
374 // The clipboard can contain the empty string, which shouldn't be suggested.
375 if (text.empty())
376 return base::nullopt;
377
378 // The text in the clipboard is a url. We don't want to prompt the user to
379 // search for a url.
380 if (GURL(text).is_valid())
381 return base::nullopt;
382
383 return NewClipboardTextMatch(text);
384 }
385
CreateImageMatch(const AutocompleteInput & input)386 bool ClipboardProvider::CreateImageMatch(const AutocompleteInput& input) {
387 if (!clipboard_content_->HasRecentImageFromClipboard()) {
388 return false;
389 }
390
391 if (!TemplateURLSupportsImageSearch()) {
392 return false;
393 }
394
395 done_ = false;
396
397 // We want to get the age here because the contents of the clipboard could
398 // change after this point. We want the age of the image we actually use, not
399 // the age of whatever's on the clipboard when the histogram is created (i.e
400 // when the match is created).
401 base::TimeDelta clipboard_contents_age =
402 clipboard_content_->GetClipboardContentAge();
403 clipboard_content_->GetRecentImageFromClipboard(base::BindOnce(
404 &ClipboardProvider::CreateImageMatchCallback,
405 callback_weak_ptr_factory_.GetWeakPtr(), input, clipboard_contents_age));
406 return true;
407 }
408
CreateImageMatchCallback(const AutocompleteInput & input,const base::TimeDelta clipboard_contents_age,base::Optional<gfx::Image> optional_image)409 void ClipboardProvider::CreateImageMatchCallback(
410 const AutocompleteInput& input,
411 const base::TimeDelta clipboard_contents_age,
412 base::Optional<gfx::Image> optional_image) {
413 if (!optional_image) {
414 return;
415 }
416 NewClipboardImageMatch(
417 optional_image.value(),
418 base::BindOnce(&ClipboardProvider::AddImageMatchCallback,
419 callback_weak_ptr_factory_.GetWeakPtr(), input,
420 clipboard_contents_age));
421 }
AddImageMatchCallback(const AutocompleteInput & input,const base::TimeDelta clipboard_contents_age,base::Optional<AutocompleteMatch> match)422 void ClipboardProvider::AddImageMatchCallback(
423 const AutocompleteInput& input,
424 const base::TimeDelta clipboard_contents_age,
425 base::Optional<AutocompleteMatch> match) {
426 if (!match) {
427 return;
428 }
429 AddCreatedMatchWithTracking(input, match.value(), clipboard_contents_age);
430 listener_->OnProviderUpdate(true);
431 done_ = true;
432 }
433
NewBlankURLMatch()434 AutocompleteMatch ClipboardProvider::NewBlankURLMatch() {
435 AutocompleteMatch match(this, kClipboardMatchRelevanceScore,
436 IsMatchDeletionEnabled(),
437 AutocompleteMatchType::CLIPBOARD_URL);
438
439 match.description.assign(l10n_util::GetStringUTF16(IDS_LINK_FROM_CLIPBOARD));
440 if (!match.description.empty())
441 match.description_class.push_back({0, ACMatchClassification::NONE});
442 return match;
443 }
444
NewClipboardURLMatch(GURL url)445 AutocompleteMatch ClipboardProvider::NewClipboardURLMatch(GURL url) {
446 DCHECK(url.is_valid());
447
448 AutocompleteMatch match = NewBlankURLMatch();
449
450 match.destination_url = url;
451
452 // Because the user did not type a related input to get this clipboard
453 // suggestion, preserve the subdomain so the user has extra context.
454 auto format_types = AutocompleteMatch::GetFormatTypes(false, true);
455 match.contents.assign(url_formatter::FormatUrl(
456 url, format_types, net::UnescapeRule::SPACES, nullptr, nullptr, nullptr));
457 if (!match.contents.empty())
458 match.contents_class.push_back({0, ACMatchClassification::URL});
459 match.fill_into_edit =
460 AutocompleteInput::FormattedStringWithEquivalentMeaning(
461 url, match.contents, client_->GetSchemeClassifier(), nullptr);
462 return match;
463 }
464
NewBlankTextMatch()465 AutocompleteMatch ClipboardProvider::NewBlankTextMatch() {
466 AutocompleteMatch match(this, kClipboardMatchRelevanceScore,
467 IsMatchDeletionEnabled(),
468 AutocompleteMatchType::CLIPBOARD_TEXT);
469
470 match.description.assign(l10n_util::GetStringUTF16(IDS_TEXT_FROM_CLIPBOARD));
471 if (!match.description.empty())
472 match.description_class.push_back({0, ACMatchClassification::NONE});
473
474 match.transition = ui::PAGE_TRANSITION_GENERATED;
475 return match;
476 }
477
NewClipboardTextMatch(base::string16 text)478 base::Optional<AutocompleteMatch> ClipboardProvider::NewClipboardTextMatch(
479 base::string16 text) {
480 // The text in the clipboard is a url. We don't want to prompt the user to
481 // search for a url.
482 if (GURL(text).is_valid())
483 return base::nullopt;
484
485 AutocompleteMatch match = NewBlankTextMatch();
486 match.fill_into_edit = text;
487
488 TemplateURLService* url_service = client_->GetTemplateURLService();
489 const TemplateURL* default_url = url_service->GetDefaultSearchProvider();
490 if (!default_url)
491 return base::nullopt;
492
493 DCHECK(!default_url->url().empty());
494 DCHECK(default_url->url_ref().IsValid(url_service->search_terms_data()));
495 TemplateURLRef::SearchTermsArgs search_args(text);
496 GURL result(default_url->url_ref().ReplaceSearchTerms(
497 search_args, url_service->search_terms_data()));
498
499 match.destination_url = result;
500 match.contents.assign(l10n_util::GetStringFUTF16(
501 IDS_COPIED_TEXT_FROM_CLIPBOARD, AutocompleteMatch::SanitizeString(text)));
502 if (!match.contents.empty())
503 match.contents_class.push_back({0, ACMatchClassification::NONE});
504
505 match.keyword = default_url->keyword();
506
507 return match;
508 }
509
NewBlankImageMatch()510 AutocompleteMatch ClipboardProvider::NewBlankImageMatch() {
511 AutocompleteMatch match(this, kClipboardMatchRelevanceScore,
512 IsMatchDeletionEnabled(),
513 AutocompleteMatchType::CLIPBOARD_IMAGE);
514
515 match.description.assign(l10n_util::GetStringUTF16(IDS_IMAGE_FROM_CLIPBOARD));
516 if (!match.description.empty())
517 match.description_class.push_back({0, ACMatchClassification::NONE});
518
519 // This will end up being something like "Search for Copied Image." This may
520 // seem strange to use for |fill_into_edit|, but it is because iOS requires
521 // some text in the text field for the Enter key to work when using keyboard
522 // navigation.
523 match.fill_into_edit = match.description;
524 match.transition = ui::PAGE_TRANSITION_GENERATED;
525
526 return match;
527 }
528
NewClipboardImageMatch(gfx::Image image,ClipboardImageMatchCallback callback)529 void ClipboardProvider::NewClipboardImageMatch(
530 gfx::Image image,
531 ClipboardImageMatchCallback callback) {
532 clipboard_content_->GetRecentImageFromClipboard(base::BindOnce(
533 &ClipboardProvider::OnReceiveImage,
534 callback_weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
535 }
536
OnReceiveImage(ClipboardImageMatchCallback callback,base::Optional<gfx::Image> optional_image)537 void ClipboardProvider::OnReceiveImage(
538 ClipboardImageMatchCallback callback,
539 base::Optional<gfx::Image> optional_image) {
540 // ImageSkia::ToImageSkia should only be called if the gfx::Image is
541 // non-empty. It is unclear when the clipboard returns a non-optional but
542 // empty image. See crbug.com/1136759 for more details.
543 if (!optional_image || optional_image.value().IsEmpty()) {
544 std::move(callback).Run(base::nullopt);
545 return;
546 }
547 gfx::ImageSkia image_skia = *optional_image.value().ToImageSkia();
548 image_skia.MakeThreadSafe();
549 base::ThreadPool::PostTaskAndReplyWithResult(
550 FROM_HERE,
551 base::BindOnce(&ClipboardProvider::EncodeClipboardImage, image_skia),
552 base::BindOnce(&ClipboardProvider::ConstructImageMatchCallback,
553 callback_weak_ptr_factory_.GetWeakPtr(),
554 std::move(callback)));
555 }
556
EncodeClipboardImage(gfx::ImageSkia image_skia)557 scoped_refptr<base::RefCountedMemory> ClipboardProvider::EncodeClipboardImage(
558 gfx::ImageSkia image_skia) {
559 gfx::Image resized_image =
560 gfx::ResizedImageForSearchByImage(gfx::Image(image_skia));
561 return resized_image.As1xPNGBytes();
562 }
563
ConstructImageMatchCallback(ClipboardImageMatchCallback callback,scoped_refptr<base::RefCountedMemory> image_bytes)564 void ClipboardProvider::ConstructImageMatchCallback(
565 ClipboardImageMatchCallback callback,
566 scoped_refptr<base::RefCountedMemory> image_bytes) {
567 TemplateURLService* url_service = client_->GetTemplateURLService();
568 const TemplateURL* default_url = url_service->GetDefaultSearchProvider();
569 DCHECK(default_url);
570
571 AutocompleteMatch match = NewBlankImageMatch();
572
573 match.search_terms_args =
574 std::make_unique<TemplateURLRef::SearchTermsArgs>(base::ASCIIToUTF16(""));
575 match.search_terms_args->image_thumbnail_content.assign(
576 image_bytes->front_as<char>(), image_bytes->size());
577 TemplateURLRef::PostContent post_content;
578 GURL result(default_url->image_url_ref().ReplaceSearchTerms(
579 *match.search_terms_args.get(), url_service->search_terms_data(),
580 &post_content));
581
582 if (!base::FeatureList::IsEnabled(omnibox::kImageSearchSuggestionThumbnail)) {
583 // If Omnibox image suggestion do not need thumbnail, release memory.
584 match.search_terms_args.reset();
585 }
586 match.destination_url = result;
587 match.post_content =
588 std::make_unique<TemplateURLRef::PostContent>(post_content);
589
590 std::move(callback).Run(match);
591 }
592