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