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
|
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_OMNIBOX_BROWSER_ENTERPRISE_SEARCH_AGGREGATOR_PROVIDER_H_
#define COMPONENTS_OMNIBOX_BROWSER_ENTERPRISE_SEARCH_AGGREGATOR_PROVIDER_H_
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <vector>
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "base/values.h"
#include "components/omnibox/browser/autocomplete_enums.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/omnibox/browser/autocomplete_provider.h"
#include "components/omnibox/browser/autocomplete_provider_client.h"
#include "components/omnibox/browser/autocomplete_provider_debouncer.h"
#include "services/data_decoder/public/cpp/data_decoder.h"
namespace network {
class SimpleURLLoader;
}
class AutocompleteInput;
class AutocompleteProviderClient;
class AutocompleteProviderDebouncer;
class AutocompleteProviderListener;
class TemplateURL;
class TemplateURLService;
class EnterpriseSearchAggregatorProvider : public AutocompleteProvider {
public:
using SuggestionType = AutocompleteMatch::EnterpriseSearchAggregatorType;
// Relevance along with info for `AutocompleteMatch::additional_info`.
struct RelevanceData {
int relevance;
size_t strong_word_matches;
size_t weak_word_matches;
std::string source;
};
EnterpriseSearchAggregatorProvider(AutocompleteProviderClient* client,
AutocompleteProviderListener* listener);
// AutocompleteProvider:
void Start(const AutocompleteInput& input, bool minimal_changes) override;
void Stop(AutocompleteStopReason stop_reason) override;
private:
friend class FakeEnterpriseSearchAggregatorProvider;
// Tracks state for `Request` declared below.
enum class RequestState {
kNotStarted,
kStarted,
kCompleted,
};
// When parsing response JSONs, we need to track not only the parsed matches
// but also how many results the response included. We can't simply use
// `matches.size()`, because some response results might be filtered out.
// `RequestParsed` is a helper to track those 2.
struct RequestParsed {
RequestParsed();
RequestParsed(std::vector<AutocompleteMatch> matches, size_t result_count);
RequestParsed(RequestParsed&&) noexcept;
RequestParsed(const RequestParsed&) = delete;
~RequestParsed();
RequestParsed& operator=(RequestParsed&&) noexcept;
RequestParsed& operator=(const RequestParsed&) = delete;
// When `multiple_requests` is false, the single request will be parsed in 3
// calls to `ParseResultList()`. `Append()` simply merges the `matches` and
// sums the `result_count`s.
void Append(RequestParsed parsed);
std::vector<AutocompleteMatch> matches;
size_t result_count = 0;
};
// The provider makes multiple async requests in parallel. This helper handles
// the callbacks, logging, and caching for each request.
class Request {
public:
explicit Request(std::vector<SuggestionType> types);
~Request();
Request(Request&&);
Request(const Request&) = delete;
// Whether this request should be made. E.g. query requests are not allowed
// unscoped.
bool Allowed(bool in_keyword_mode) const;
// Clears most state. Conditionally doesn't clear `matches` in order to
// support caching. `Reset(false)` should be called before `OnStart()` is
// called. `Reset(true)` will immediately clear cached matches; it should
// only be called if the request will not be started and completed; e.g.
// unscoped query request. Will log the previous request if it's being
// interrupted; i.e. `OnCompleted()` hadn't been called.
void Reset(bool clear_cached_matches);
// Called when the real request starts; after the auth request completes.
// Should be called before `OnCompleted()` is called.
void OnStart(std::unique_ptr<network::SimpleURLLoader> loader);
// Called when the real request completes. Will replace cached matches and
// log.
void OnCompleted(RequestParsed parsed);
// Logs how long it has been since a request started at `start_time`. Sliced
// by request type and completion.
static void LogResponseTime(const std::string& type_histogram_suffix,
bool interrupted,
base::TimeTicks start_time);
// Logs how many results a response contained. Sliced by request type. Not
// logged for interrupted requests.
static void LogResultCount(const std::string& type_histogram_suffix,
int count);
const std::vector<SuggestionType> Types() const;
// Map `types_` to the integers the backend understands. Some types like
// `CONTENT` map to multiple backend types. And `types_` itself is a vector
// Hence this returns a vector.
std::vector<int> BackendSuggestionTypes() const;
RequestState State() const;
base::TimeTicks StartTime() const;
const std::vector<AutocompleteMatch>& Matches() const;
int ResultCount() const;
private:
// Log all of this request's metrics on completion or interruption; i.e.
// response time and result count.
void Log(bool interrupted) const;
// Map `types_` to a histogram suffix for slicing.
std::string TypeHistogramSuffix() const;
// The type of suggestions to request.
// TODO(manukh): After launching multiple_requests, this can be a single
// value instead of a vector.
const std::vector<SuggestionType> types_;
// State of request. `start_time_` and `loader_` can't differentiate between
// `kNotStarted` and `kCompleted`, so this explicit `state_` is necessary.
RequestState state_ = RequestState::kCompleted;
// Start time of ongoing request. Null before requests start and after they
// complete.
base::TimeTicks start_time_;
// Not null for ongoing requests. Null before requests start and after they
// complete.
std::unique_ptr<network::SimpleURLLoader> loader_;
RequestParsed parsed_;
};
~EnterpriseSearchAggregatorProvider() override;
// Determines whether the profile/session/window meet the feature
// prerequisites.
bool IsProviderAllowed(const AutocompleteInput& input);
// Called by `debouncer_`, queued when `Start()` is called.
void Run();
// Callback for when the loader is available with a valid token. Takes
// ownership of the loader.
void RequestStarted(int request_index,
std::unique_ptr<network::SimpleURLLoader> loader);
// Called when the network request for suggestions has completed.
// `request_index` corresponds to the type of request sent:
void RequestCompleted(int request_index,
const network::SimpleURLLoader* source,
int response_code,
std::unique_ptr<std::string> response_body);
// Callback for parsing the response JSON string into `base::Value` in an
// isolated process.
void OnJsonParsedIsolated(int request_index,
base::expected<base::Value, std::string> result);
// Called after parsing the response JSON string into `base::Value`, either in
// the main or an isolated process. Will use the `Parse*()` methods below to
// further parse the `base::Value` into `RequestParsed` and update
// `requests[request_index]` with the `RequestParsed.
void HandleParsedJson(int request_index,
const std::optional<base::Value::Dict>& response_value);
// Parses the response `base::Value` into `RequestParsed`.
RequestParsed ParseEnterpriseSearchAggregatorSearchResults(
const std::vector<SuggestionType>& suggestion_types,
const base::Value::Dict& root_val);
// Helper method to parse query, people, and content suggestions.
// - `input_words` is used for scoring matches.
// - `suggestion_type` is used for selecting which JSON fields to look for,
// scoring matches, and creating the match.
// - `is_navigation` is used for creating the match.
// Example:
// Given a `results` with one query suggestion:
// {
// "querySuggestions": [{
// "suggestion": "hello",
// "dataStore": [project/1]
// }]
// }.
// `matches` would contain one `match` with the following properties:
// - `match.type` = `AutocompleteMatchType::SEARCH_SUGGEST`,
// - `match.contents` = "hello",
// - `match.description` = "",
// - `match.destination_url` = `template_url->url()`,
// - `match.fill_to_edit` = `template_url->url()`,
// - `match.image_url` = `icon_url` from EnterpriseSearchAggregatorSettings
// policy,
// - `match.relevance` = 1001.
RequestParsed ParseResultList(std::set<std::u16string> input_words,
const base::Value::List* results,
SuggestionType suggestion_type,
bool is_navigation);
// Helper method to get `destination_url` based on `suggestion_type` for
// `CreateMatch()`.
std::string GetMatchDestinationUrl(const base::Value::Dict& result,
SuggestionType suggestion_type) const;
// Helper method to get `description` based on `suggestion_type` for
// `CreateMatch()`.
std::string GetMatchDescription(const base::Value::Dict& result,
SuggestionType suggestion_type) const;
// Helper method to get `contents` based on `suggestion_type` for
// `CreateMatch()`.
std::string GetMatchContents(const base::Value::Dict& result,
SuggestionType suggestion_type) const;
// Helper method to get a localized metadata string depending on which of
// `update_time`, `owner`, and `content_type_description` exist.
std::u16string GetLocalizedContentMetadata(
const std::u16string& update_time,
const std::u16string& owner,
const std::u16string& content_type_description) const;
// Helper method to get user-readable (e.g. 'chromium is awesome
// document') fields that can be used to compare input similarity.
// Non-user-readable fields (e.g. 'doc_id=123/locations/global') should be
// excluded because the input matching that would be a coincidence and not
// a sign the user wanted this suggestion. `GetMatchDescription()` and
// `GetMatchContents()`, and `GetEmailUsernames()` should be passed to
// `GetStrongScoringFields()`.
std::vector<std::string> GetStrongScoringFields(
const base::Value::Dict& result,
SuggestionType suggestion_type,
const std::string& contents,
const std::string& description,
const std::vector<std::u16string> email_usernames) const;
std::vector<std::string> GetWeakScoringFields(
const base::Value::Dict& result,
SuggestionType suggestion_type) const;
// Helper to create a match.
AutocompleteMatch CreateMatch(SuggestionType suggestion_type,
bool is_navigation,
RelevanceData relevance_data,
const std::string& destination_url,
const std::string& image_url,
const std::string& icon_url,
const std::u16string& description,
const std::u16string& contents,
const std::u16string& fill_into_edit);
// Aggregates the `RequestParsed`s of `requests_` into `matches_` and notifies
// the provider listener. Called multiple times in each autocomplete pass;
// once immediately when `Run()` is called to display the cached matches; then
// again as each request completes.
void AggregateMatches();
// Called when all `requests_` complete or are interrupted.
void LogAllRequests(bool interrupted);
// Owned by AutocompleteController.
const raw_ptr<AutocompleteProviderClient> client_;
// Used to ensure that we don't send multiple requests in quick succession.
std::unique_ptr<AutocompleteProviderDebouncer> debouncer_;
// Saved when starting a new autocomplete request so that they can be
// retrieved when responses return asynchronously.
AutocompleteInput adjusted_input_;
raw_ptr<const TemplateURL> template_url_;
raw_ptr<TemplateURLService> template_url_service_;
// See comment for `Request`. `requests_` are initialized in the provider
// constructor and reused throughout the provider's lifetime. This is
// necessary to cache results beyond a single autocomplete pass.
std::vector<Request> requests_;
base::WeakPtrFactory<EnterpriseSearchAggregatorProvider> weak_ptr_factory_{
this};
};
#endif // COMPONENTS_OMNIBOX_BROWSER_ENTERPRISE_SEARCH_AGGREGATOR_PROVIDER_H_
|