File: omnibox_util.cc

package info (click to toggle)
chromium 139.0.7258.127-1~deb13u1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,096 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (403 lines) | stat: -rw-r--r-- 15,993 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/app_list/search/omnibox/omnibox_util.h"

#include <algorithm>
#include <string>

#include "base/containers/flat_set.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ash/app_list/search/omnibox/omnibox_result.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/omnibox/browser/autocomplete_controller.h"
#include "components/omnibox/browser/favicon_cache.h"
#include "components/omnibox/common/omnibox_feature_configs.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "url/gurl.h"

namespace app_list {

namespace {

using crosapi::mojom::SearchResult;
using crosapi::mojom::SearchResultPtr;
using RemoteConsumer = mojo::Remote<crosapi::mojom::SearchResultConsumer>;
using RequestSource = SearchTermsData::RequestSource;

SearchResult::AnswerType MatchTypeToAnswerType(const int type) {
  switch (static_cast<omnibox::AnswerType>(type)) {
    case omnibox::ANSWER_TYPE_WEATHER:
      return SearchResult::AnswerType::kWeather;
    case omnibox::ANSWER_TYPE_CURRENCY:
      return SearchResult::AnswerType::kCurrency;
    case omnibox::ANSWER_TYPE_DICTIONARY:
      return SearchResult::AnswerType::kDictionary;
    case omnibox::ANSWER_TYPE_FINANCE:
      return SearchResult::AnswerType::kFinance;
    case omnibox::ANSWER_TYPE_SUNRISE_SUNSET:
      return SearchResult::AnswerType::kSunrise;
    case omnibox::ANSWER_TYPE_TRANSLATION:
      return SearchResult::AnswerType::kTranslation;
    case omnibox::ANSWER_TYPE_WHEN_IS:
      return SearchResult::AnswerType::kWhenIs;
    default:
      return SearchResult::AnswerType::kDefaultAnswer;
  }
}

SearchResult::OmniboxType MatchTypeToOmniboxType(
    const AutocompleteMatchType::Type type) {
  switch (type) {
    case AutocompleteMatchType::URL_WHAT_YOU_TYPED:
    case AutocompleteMatchType::HISTORY_URL:
    case AutocompleteMatchType::HISTORY_TITLE:
    case AutocompleteMatchType::HISTORY_BODY:
    case AutocompleteMatchType::HISTORY_KEYWORD:
    case AutocompleteMatchType::HISTORY_EMBEDDINGS:
    case AutocompleteMatchType::NAVSUGGEST:
    case AutocompleteMatchType::BOOKMARK_TITLE:
    case AutocompleteMatchType::NAVSUGGEST_PERSONALIZED:
    case AutocompleteMatchType::CLIPBOARD_URL:
    case AutocompleteMatchType::PHYSICAL_WEB_DEPRECATED:
    case AutocompleteMatchType::PHYSICAL_WEB_OVERFLOW_DEPRECATED:
    case AutocompleteMatchType::TAB_SEARCH_DEPRECATED:
    case AutocompleteMatchType::DOCUMENT_SUGGESTION:
    case AutocompleteMatchType::PEDAL:
    case AutocompleteMatchType::HISTORY_CLUSTER:
    case AutocompleteMatchType::STARTER_PACK:
    case AutocompleteMatchType::HISTORY_EMBEDDINGS_ANSWER:
      return SearchResult::OmniboxType::kDomain;

    case AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED:
    case AutocompleteMatchType::SEARCH_SUGGEST:
    case AutocompleteMatchType::SEARCH_SUGGEST_ENTITY:
    case AutocompleteMatchType::SEARCH_SUGGEST_TAIL:
    case AutocompleteMatchType::SEARCH_SUGGEST_PROFILE:
    case AutocompleteMatchType::SEARCH_OTHER_ENGINE:
    case AutocompleteMatchType::CONTACT_DEPRECATED:
    case AutocompleteMatchType::VOICE_SUGGEST:
    case AutocompleteMatchType::CLIPBOARD_TEXT:
    case AutocompleteMatchType::CLIPBOARD_IMAGE:
      return SearchResult::OmniboxType::kSearch;

    case AutocompleteMatchType::SEARCH_HISTORY:
    case AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED:
      return SearchResult::OmniboxType::kHistory;

    case AutocompleteMatchType::OPEN_TAB:
      return SearchResult::OmniboxType::kOpenTab;

    // Currently unhandled enum values.
    // If you came here from a compile error, please contact
    // chromeos-launcher-search@google.com to determine what the correct
    // `OmniboxType` should be.
    case AutocompleteMatchType::EXTENSION_APP_DEPRECATED:
    case AutocompleteMatchType::CALCULATOR:
    case AutocompleteMatchType::NULL_RESULT_MESSAGE:
    case AutocompleteMatchType::FEATURED_ENTERPRISE_SEARCH:
    // TILE types seem to be mobile-only.
    case AutocompleteMatchType::TILE_SUGGESTION:
    case AutocompleteMatchType::TILE_NAVSUGGEST:
    case AutocompleteMatchType::TILE_MOST_VISITED_SITE:
    case AutocompleteMatchType::TILE_REPEATABLE_QUERY:
      LOG(ERROR) << "Unhandled AutocompleteMatchType value: "
                 << AutocompleteMatchType::ToString(type);
      return SearchResult::OmniboxType::kDomain;

    // NUM_TYPES is not a valid enumerator value, so fall through below.
    case AutocompleteMatchType::NUM_TYPES:
    default:
      break;
  }
  // https://abseil.io/tips/147: Handle non-enumerator values.
  NOTREACHED() << "Unexpected AutocompleteMatchType value: "
               << static_cast<int>(type);
}

SearchResult::MetricsType MatchTypeToMetricsType(
    AutocompleteMatchType::Type type) {
  switch (type) {
    case AutocompleteMatchType::URL_WHAT_YOU_TYPED:
      return SearchResult::MetricsType::kWhatYouTyped;
    case AutocompleteMatchType::HISTORY_URL:
      // A recently-visited URL that is also a bookmark is handled manually when
      // constructing the result.
      return SearchResult::MetricsType::kRecentlyVisitedWebsite;
    case AutocompleteMatchType::HISTORY_TITLE:
      return SearchResult::MetricsType::kHistoryTitle;
    case AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED:
      return SearchResult::MetricsType::kSearchWhatYouTyped;
    case AutocompleteMatchType::SEARCH_HISTORY:
      return SearchResult::MetricsType::kSearchHistory;
    case AutocompleteMatchType::SEARCH_SUGGEST:
      return SearchResult::MetricsType::kSearchSuggest;
    case AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED:
      return SearchResult::MetricsType::kSearchSuggestPersonalized;
    case AutocompleteMatchType::BOOKMARK_TITLE:
      return SearchResult::MetricsType::kBookmark;
    case AutocompleteMatchType::SEARCH_SUGGEST_ENTITY:
      return SearchResult::MetricsType::kSearchSuggestEntity;
    case AutocompleteMatchType::NAVSUGGEST:
      return SearchResult::MetricsType::kNavSuggest;
    case AutocompleteMatchType::CALCULATOR:
      return SearchResult::MetricsType::kCalculator;
    default:
      return SearchResult::MetricsType::kUnset;
  }
}

SearchResult::TextType ColorTypeToType(
    omnibox::FormattedString::ColorType type) {
  switch (type) {
    case omnibox::FormattedString::COLOR_ON_SURFACE_POSITIVE:
      return SearchResult::TextType::kPositive;
    case omnibox::FormattedString::COLOR_ON_SURFACE_NEGATIVE:
      return SearchResult::TextType::kNegative;
    default:
      return SearchResult::TextType::kUnset;
  }
}

SearchResult::TextType ClassesToType(
    const ACMatchClassifications& text_classes) {
  // Only retain the URL class, other classes are either ignored. Tag indices
  // are also ignored since they will apply to the entire text.
  for (const auto& text_class : text_classes) {
    if (text_class.style & ACMatchClassification::URL) {
      return SearchResult::TextType::kUrl;
    }
  }

  return SearchResult::TextType::kUnset;
}

SearchResultPtr CreateBaseResult(const AutocompleteMatch& match,
                                 AutocompleteController* controller,
                                 const AutocompleteInput& input) {
  AutocompleteMatch match_copy = match;
  SearchResultPtr result = SearchResult::New();

  if (controller && match_copy.search_terms_args) {
    match_copy.search_terms_args->request_source = RequestSource::CROS_APP_LIST;
    controller->SetMatchDestinationURL(&match_copy);
  }

  result->type = crosapi::mojom::SearchResultType::kOmniboxResult;
  result->relevance = match_copy.relevance;
  result->destination_url = match_copy.destination_url;

  if (controller && match_copy.stripped_destination_url.spec().empty()) {
    match_copy.ComputeStrippedDestinationURL(
        input,
        controller->autocomplete_provider_client()->GetTemplateURLService());
  }
  result->stripped_destination_url = match_copy.stripped_destination_url;

  if (ui::PageTransitionCoreTypeIs(
          match_copy.transition,
          ui::PageTransition::PAGE_TRANSITION_GENERATED)) {
    result->page_transition = SearchResult::PageTransition::kGenerated;
  } else {
    result->page_transition = SearchResult::PageTransition::kTyped;
  }

  result->is_omnibox_search = AutocompleteMatch::IsSearchType(match_copy.type)
                                  ? SearchResult::OptionalBool::kTrue
                                  : SearchResult::OptionalBool::kFalse;
  return result;
}

}  // namespace

using CrosApiSearchResult = crosapi::mojom::SearchResult;

ash::SearchResultTags TagsForText(const std::u16string& text,
                                  CrosApiSearchResult::TextType type) {
  ash::SearchResultTags tags;
  const auto length = text.length();
  switch (type) {
    case CrosApiSearchResult::TextType::kPositive:
      tags.emplace_back(ash::SearchResultTag::GREEN, 0, length);
      break;
    case CrosApiSearchResult::TextType::kNegative:
      tags.emplace_back(ash::SearchResultTag::RED, 0, length);
      break;
    case CrosApiSearchResult::TextType::kUrl:
      tags.emplace_back(ash::SearchResultTag::URL, 0, length);
      break;
    default:
      break;
  }
  return tags;
}

bool IsDriveUrl(const GURL& url) {
  // Returns true if the |url| points to a Drive Web host.
  const std::string& host = url.host();
  return host == "drive.google.com" || host == "docs.google.com";
}

void RemoveDuplicateResults(
    std::vector<std::unique_ptr<OmniboxResult>>& results) {
  // Sort the results by deduplication priority and then filter from left to
  // right. This ensures that higher priority results are retained.
  sort(results.begin(), results.end(),
       [](const std::unique_ptr<OmniboxResult>& a,
          const std::unique_ptr<OmniboxResult>& b) {
         return a->dedup_priority() > b->dedup_priority();
       });

  base::flat_set<std::string> seen_ids;
  for (auto iter = results.begin(); iter != results.end();) {
    bool inserted = seen_ids.insert((*iter)->id()).second;
    if (!inserted) {
      // C++11:: The return value of erase(iter) is an iterator pointing to the
      // next element in the container.
      iter = results.erase(iter);
    } else {
      ++iter;
    }
  }
}

// TODO(crbug.com/371119767): Remove the crosapi usage here, as part of the
// Lacros and crosapi sunsetting plan.
//
// Convert from our Mojo page transition type into the UI equivalent.
ui::PageTransition PageTransitionToUiPageTransition(
    SearchResult::PageTransition transition) {
  switch (transition) {
    case SearchResult::PageTransition::kTyped:
      return ui::PAGE_TRANSITION_TYPED;
    case SearchResult::PageTransition::kGenerated:
      return ui::PAGE_TRANSITION_GENERATED;
    default:
      NOTREACHED();
  }
}

SearchResultPtr CreateAnswerResult(const AutocompleteMatch& match,
                                   AutocompleteController* controller,
                                   std::u16string_view query,
                                   const AutocompleteInput& input) {
  SearchResultPtr result = CreateBaseResult(match, controller, input);

  result->is_answer = SearchResult::OptionalBool::kTrue;

  // Special case: calculator results (are the only answer results to) have no
  // explicit answer data.
  if (match.answer_type == omnibox::ANSWER_TYPE_UNSPECIFIED) {
    DCHECK_EQ(match.type, AutocompleteMatchType::CALCULATOR);
    result->answer_type = SearchResult::AnswerType::kCalculator;

    // Calculator results come in two forms:
    // 1) Answer in |contents|, empty |description|,
    // 2) Query in |contents|, answer in |description|.
    // For case 1, we should manually populate the query.
    if (match.description.empty()) {
      result->contents = std::u16string(query);
      result->contents_type = SearchResult::TextType::kUnset;
      result->description = match.contents;
      result->description_type = ClassesToType(match.contents_class);
    } else {
      result->contents = match.contents;
      result->contents_type = ClassesToType(match.contents_class);
      result->description = match.description;
      result->description_type = ClassesToType(match.description_class);
    }

    return result;
  }

  result->answer_type = MatchTypeToAnswerType(match.answer_type);
  result->contents = match.contents;

  const auto& headline = match.answer_template->answers(0).headline();
  if (headline.fragments_size() > 1) {
    // Only use the second fragment as the first is equivalent to
    // |match.contents|.
    result->additional_contents =
        base::UTF8ToUTF16(headline.fragments(1).text());
    result->additional_contents_type =
        ColorTypeToType(headline.fragments(1).color());
  }
  const auto& subhead = match.answer_template->answers(0).subhead();
  if (subhead.fragments_size() > 0) {
    result->description = base::UTF8ToUTF16(subhead.fragments(0).text());
    result->description_type = ColorTypeToType(subhead.fragments(0).color());
  }
  if (subhead.fragments_size() > 1) {
    result->additional_description =
        base::UTF8ToUTF16(subhead.fragments(1).text());
    result->additional_description_type =
        ColorTypeToType(subhead.fragments(1).color());
  }
  if (result->answer_type == SearchResult::AnswerType::kWeather) {
    result->image_url = GURL(match.answer_template->answers(0).image().url());
    result->description_a11y_label = base::UTF8ToUTF16(subhead.a11y_text());
  }

  return result;
}

SearchResultPtr CreateResult(const AutocompleteMatch& match,
                             AutocompleteController* controller,
                             FaviconCache* favicon_cache,
                             bookmarks::BookmarkModel* bookmark_model,
                             const AutocompleteInput& input) {
  SearchResultPtr result = CreateBaseResult(match, controller, input);

  result->metrics_type = MatchTypeToMetricsType(match.type);
  result->is_answer = SearchResult::OptionalBool::kFalse;
  result->contents = match.contents;
  result->contents_type = ClassesToType(match.contents_class);
  result->description = match.description;
  result->description_type = ClassesToType(match.description_class);

  // This may not be the final type. Bookmarks take precedence.
  result->omnibox_type = MatchTypeToOmniboxType(match.type);

  if (match.type == AutocompleteMatchType::SEARCH_SUGGEST_ENTITY &&
      !match.image_url.is_empty()) {
    result->image_url = match.image_url;
  } else {
    // Set the favicon if this result is eligible.
    bool use_favicon =
        result->omnibox_type == SearchResult::OmniboxType::kDomain ||
        result->omnibox_type == SearchResult::OmniboxType::kOpenTab;
    if (use_favicon && favicon_cache) {
      // Provide hook by which a result object can receive an
      // asychronously-fetched favicon. Use a pointer so that our callback can
      // own the remote interface.
      RemoteConsumer consumer;
      result->receiver = consumer.BindNewPipeAndPassReceiver();
      auto emit_favicon = base::BindOnce(
          [](RemoteConsumer consumer, const gfx::Image& icon) {
            consumer->OnFaviconReceived(icon.AsImageSkia());
          },
          std::move(consumer));

      const auto icon = favicon_cache->GetFaviconForPageUrl(
          match.destination_url, std::move(emit_favicon));
      if (!icon.IsEmpty()) {
        result->favicon = icon.AsImageSkia();
      }
    }

    // Otherwise, set the bookmark type if this result is eligible.
    if (result->favicon.isNull() && bookmark_model &&
        bookmark_model->IsBookmarked(match.destination_url)) {
      result->omnibox_type = SearchResult::OmniboxType::kBookmark;
      result->metrics_type = SearchResult::MetricsType::kBookmark;
    }
  }

  return result;
}

}  // namespace app_list