| // Copyright 2012 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/extensions/api/omnibox/omnibox_api.h" | 
 |  | 
 | #include <stddef.h> | 
 |  | 
 | #include <memory> | 
 | #include <optional> | 
 | #include <string> | 
 | #include <string_view> | 
 | #include <utility> | 
 | #include <vector> | 
 |  | 
 | #include "base/functional/bind.h" | 
 | #include "base/lazy_instance.h" | 
 | #include "base/strings/stringprintf.h" | 
 | #include "base/strings/utf_string_conversions.h" | 
 | #include "base/values.h" | 
 | #include "build/build_config.h" | 
 | #include "chrome/browser/extensions/permissions/active_tab_permission_granter.h" | 
 | #include "chrome/browser/omnibox/omnibox_input_watcher_factory.h" | 
 | #include "chrome/browser/omnibox/omnibox_suggestions_watcher_factory.h" | 
 | #include "chrome/browser/profiles/profile.h" | 
 | #include "chrome/browser/search_engines/template_url_service_factory.h" | 
 | #include "chrome/common/extensions/api/omnibox.h" | 
 | #include "chrome/common/extensions/api/omnibox/omnibox_handler.h" | 
 | #include "components/omnibox/browser/omnibox_input_watcher.h" | 
 | #include "components/omnibox/browser/omnibox_suggestions_watcher.h" | 
 | #include "components/search_engines/template_url.h" | 
 | #include "components/search_engines/template_url_service.h" | 
 | #include "extensions/browser/event_router.h" | 
 | #include "extensions/browser/extension_prefs.h" | 
 | #include "extensions/browser/extension_prefs_factory.h" | 
 | #include "extensions/browser/icon_util.h" | 
 | #include "extensions/browser/install_prefs_helper.h" | 
 | #include "extensions/common/extension_features.h" | 
 | #include "extensions/common/extension_id.h" | 
 | #include "extensions/common/mojom/api_permission_id.mojom.h" | 
 | #include "extensions/common/permissions/permissions_data.h" | 
 | #include "ui/gfx/image/image.h" | 
 |  | 
 | namespace extensions { | 
 |  | 
 | namespace omnibox = api::omnibox; | 
 | namespace SendSuggestions = omnibox::SendSuggestions; | 
 | namespace SetDefaultSuggestion = omnibox::SetDefaultSuggestion; | 
 |  | 
 | namespace { | 
 |  | 
 | const char kSuggestionContent[] = "content"; | 
 | const char kCurrentTabDisposition[] = "currentTab"; | 
 | const char kForegroundTabDisposition[] = "newForegroundTab"; | 
 | const char kBackgroundTabDisposition[] = "newBackgroundTab"; | 
 |  | 
 | // Pref key for omnibox.setDefaultSuggestion. | 
 | const char kOmniboxDefaultSuggestion[] = "omnibox_default_suggestion"; | 
 |  | 
 | std::optional<omnibox::SuggestResult> GetOmniboxDefaultSuggestion( | 
 |     Profile* profile, | 
 |     const ExtensionId& extension_id) { | 
 |   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile); | 
 |   if (!prefs) { | 
 |     return std::nullopt; | 
 |   } | 
 |  | 
 |   const base::Value::Dict* dict = | 
 |       prefs->ReadPrefAsDict(extension_id, kOmniboxDefaultSuggestion); | 
 |   if (!dict) { | 
 |     return std::nullopt; | 
 |   } | 
 |   return omnibox::SuggestResult::FromValue(*dict); | 
 | } | 
 |  | 
 | // Tries to set the omnibox default suggestion; returns true on success or | 
 | // false on failure. | 
 | bool SetOmniboxDefaultSuggestion( | 
 |     Profile* profile, | 
 |     const ExtensionId& extension_id, | 
 |     const omnibox::DefaultSuggestResult& suggestion) { | 
 |   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile); | 
 |   if (!prefs) | 
 |     return false; | 
 |  | 
 |   base::Value::Dict dict = suggestion.ToValue(); | 
 |   // Add the content field so that the dictionary can be used to populate an | 
 |   // omnibox::SuggestResult. | 
 |   dict.Set(kSuggestionContent, base::Value(base::Value::Type::STRING)); | 
 |   prefs->UpdateExtensionPref(extension_id, kOmniboxDefaultSuggestion, | 
 |                              base::Value(std::move(dict))); | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | // Returns a string used as a template URL string of the extension. | 
 | std::string GetTemplateURLStringForExtension(const ExtensionId& extension_id) { | 
 |   // This URL is not actually used for navigation. It holds the extension's ID. | 
 |   return std::string(extensions::kExtensionScheme) + "://" + | 
 |       extension_id + "/?q={searchTerms}"; | 
 | } | 
 |  | 
 | bool IsUnscopedModeAllowed(const Extension* extension) { | 
 |   // The extension can use unscoepd mode if the feature is enabled and the | 
 |   // permission has been granted. | 
 |   return base::FeatureList::IsEnabled( | 
 |              extensions_features::kExperimentalOmniboxLabs) && | 
 |          extension->permissions_data()->HasAPIPermission( | 
 |              mojom::APIPermissionID::kOmniboxDirectInput); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | // static | 
 | void ExtensionOmniboxEventRouter::OnInputStarted( | 
 |     Profile* profile, | 
 |     const ExtensionId& extension_id) { | 
 |   auto event = std::make_unique<Event>(events::OMNIBOX_ON_INPUT_STARTED, | 
 |                                        omnibox::OnInputStarted::kEventName, | 
 |                                        base::Value::List(), profile); | 
 |   EventRouter::Get(profile) | 
 |       ->DispatchEventToExtension(extension_id, std::move(event)); | 
 | } | 
 |  | 
 | // static | 
 | bool ExtensionOmniboxEventRouter::OnInputChanged( | 
 |     Profile* profile, | 
 |     const ExtensionId& extension_id, | 
 |     const std::string& input, | 
 |     int suggest_id) { | 
 |   EventRouter* event_router = EventRouter::Get(profile); | 
 |   if (!event_router->ExtensionHasEventListener( | 
 |           extension_id, omnibox::OnInputChanged::kEventName)) | 
 |     return false; | 
 |  | 
 |   base::Value::List args; | 
 |   args.Append(input); | 
 |   args.Append(suggest_id); | 
 |  | 
 |   auto event = std::make_unique<Event>(events::OMNIBOX_ON_INPUT_CHANGED, | 
 |                                        omnibox::OnInputChanged::kEventName, | 
 |                                        std::move(args), profile); | 
 |   event_router->DispatchEventToExtension(extension_id, std::move(event)); | 
 |   return true; | 
 | } | 
 |  | 
 | // static | 
 | void ExtensionOmniboxEventRouter::OnInputEntered( | 
 |     content::WebContents* web_contents, | 
 |     const ExtensionId& extension_id, | 
 |     const std::string& input, | 
 |     WindowOpenDisposition disposition) { | 
 |   Profile* profile = | 
 |       Profile::FromBrowserContext(web_contents->GetBrowserContext()); | 
 |  | 
 |   const Extension* extension = | 
 |       ExtensionRegistry::Get(profile)->enabled_extensions().GetByID( | 
 |           extension_id); | 
 |   CHECK(extension); | 
 |   extensions::ActiveTabPermissionGranter::FromWebContents(web_contents) | 
 |       ->GrantIfRequested(extension); | 
 |  | 
 |   base::Value::List args; | 
 |   args.Append(input); | 
 |   if (disposition == WindowOpenDisposition::NEW_FOREGROUND_TAB) | 
 |     args.Append(kForegroundTabDisposition); | 
 |   else if (disposition == WindowOpenDisposition::NEW_BACKGROUND_TAB) | 
 |     args.Append(kBackgroundTabDisposition); | 
 |   else | 
 |     args.Append(kCurrentTabDisposition); | 
 |  | 
 |   auto event = std::make_unique<Event>(events::OMNIBOX_ON_INPUT_ENTERED, | 
 |                                        omnibox::OnInputEntered::kEventName, | 
 |                                        std::move(args), profile); | 
 |   event->user_gesture = EventRouter::UserGestureState::kEnabled; | 
 |   EventRouter::Get(profile) | 
 |       ->DispatchEventToExtension(extension_id, std::move(event)); | 
 |  | 
 |   OmniboxInputWatcherFactory::GetForBrowserContext(profile) | 
 |       ->NotifyInputEntered(); | 
 | } | 
 |  | 
 | // static | 
 | void ExtensionOmniboxEventRouter::OnInputCancelled( | 
 |     Profile* profile, | 
 |     const ExtensionId& extension_id) { | 
 |   auto event = std::make_unique<Event>(events::OMNIBOX_ON_INPUT_CANCELLED, | 
 |                                        omnibox::OnInputCancelled::kEventName, | 
 |                                        base::Value::List(), profile); | 
 |   EventRouter::Get(profile) | 
 |       ->DispatchEventToExtension(extension_id, std::move(event)); | 
 | } | 
 |  | 
 | void ExtensionOmniboxEventRouter::OnDeleteSuggestion( | 
 |     Profile* profile, | 
 |     const ExtensionId& extension_id, | 
 |     const std::string& suggestion_text) { | 
 |   base::Value::List args; | 
 |   args.Append(suggestion_text); | 
 |  | 
 |   auto event = std::make_unique<Event>(events::OMNIBOX_ON_DELETE_SUGGESTION, | 
 |                                        omnibox::OnDeleteSuggestion::kEventName, | 
 |                                        std::move(args), profile); | 
 |  | 
 |   EventRouter::Get(profile)->DispatchEventToExtension(extension_id, | 
 |                                                       std::move(event)); | 
 | } | 
 |  | 
 | // static | 
 | void ExtensionOmniboxEventRouter::OnActionExecuted( | 
 |     Profile* profile, | 
 |     const ExtensionId& extension_id, | 
 |     const std::string& action_name, | 
 |     const std::string& content) { | 
 |   EventRouter* event_router = EventRouter::Get(profile); | 
 |   if (!event_router->ExtensionHasEventListener( | 
 |           extension_id, omnibox::OnActionExecuted::kEventName)) { | 
 |     return; | 
 |   } | 
 |  | 
 |   omnibox::ActionExecution action_execution; | 
 |   action_execution.action_name = action_name; | 
 |   action_execution.content = content; | 
 |   auto event = std::make_unique<Event>( | 
 |       events::OMNIBOX_ON_ACTION_EXECUTED, omnibox::OnActionExecuted::kEventName, | 
 |       omnibox::OnActionExecuted::Create(std::move(action_execution)), profile); | 
 |   event->user_gesture = EventRouter::UserGestureState::kEnabled; | 
 |   event_router->DispatchEventToExtension(extension_id, std::move(event)); | 
 | } | 
 |  | 
 | OmniboxAPI::OmniboxAPI(content::BrowserContext* context) | 
 |     : profile_(Profile::FromBrowserContext(context)), | 
 |       url_service_(TemplateURLServiceFactory::GetForProfile(profile_)) { | 
 |   extension_registry_observation_.Observe(ExtensionRegistry::Get(profile_)); | 
 |   if (url_service_) { | 
 |     template_url_subscription_ = | 
 |         url_service_->RegisterOnLoadedCallback(base::BindOnce( | 
 |             &OmniboxAPI::OnTemplateURLsLoaded, base::Unretained(this))); | 
 |   } | 
 |  | 
 |   // Use monochrome icons for Omnibox icons. | 
 |   omnibox_icon_manager_.set_monochrome(true); | 
 |  | 
 |   permissions_manager_observation_.Observe(PermissionsManager::Get(profile_)); | 
 | } | 
 |  | 
 | void OmniboxAPI::Shutdown() { | 
 |   template_url_subscription_ = {}; | 
 |   permissions_manager_observation_.Reset(); | 
 | } | 
 |  | 
 | OmniboxAPI::~OmniboxAPI() = default; | 
 |  | 
 | static base::LazyInstance<BrowserContextKeyedAPIFactory<OmniboxAPI>>:: | 
 |     DestructorAtExit g_omnibox_api_factory = LAZY_INSTANCE_INITIALIZER; | 
 |  | 
 | // static | 
 | BrowserContextKeyedAPIFactory<OmniboxAPI>* OmniboxAPI::GetFactoryInstance() { | 
 |   return g_omnibox_api_factory.Pointer(); | 
 | } | 
 |  | 
 | // static | 
 | OmniboxAPI* OmniboxAPI::Get(content::BrowserContext* context) { | 
 |   return BrowserContextKeyedAPIFactory<OmniboxAPI>::Get(context); | 
 | } | 
 |  | 
 | void OmniboxAPI::OnExtensionLoaded(content::BrowserContext* browser_context, | 
 |                                    const Extension* extension) { | 
 |   const std::string& keyword = OmniboxInfo::GetKeyword(extension); | 
 |   if (!keyword.empty()) { | 
 |     // Load the omnibox icon so it will be ready to display in the URL bar. | 
 |     omnibox_icon_manager_.LoadIcon(profile_, extension); | 
 |     if (url_service_) { | 
 |       url_service_->Load(); | 
 |       if (url_service_->loaded()) { | 
 |         url_service_->RegisterExtensionControlledTURL( | 
 |             extension->id(), extension->short_name(), keyword, | 
 |             GetTemplateURLStringForExtension(extension->id()), | 
 |             GetLastUpdateTime(ExtensionPrefs::Get(profile_), extension->id()), | 
 |             IsUnscopedModeAllowed(extension)); | 
 |       } else { | 
 |         pending_extensions_.insert(extension); | 
 |       } | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void OmniboxAPI::OnExtensionUnloaded(content::BrowserContext* browser_context, | 
 |                                      const Extension* extension, | 
 |                                      UnloadedExtensionReason reason) { | 
 |   if (!OmniboxInfo::GetKeyword(extension).empty() && url_service_) { | 
 |     if (url_service_->loaded()) { | 
 |       url_service_->RemoveExtensionControlledTURL( | 
 |           extension->id(), TemplateURL::OMNIBOX_API_EXTENSION); | 
 |     } else { | 
 |       pending_extensions_.erase(extension); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void OmniboxAPI::OnExtensionPermissionsUpdated( | 
 |     const Extension& extension, | 
 |     const PermissionSet& permissions, | 
 |     PermissionsManager::UpdateReason reason) { | 
 |   if (!permissions.HasAPIPermission( | 
 |           mojom::APIPermissionID::kOmniboxDirectInput)) { | 
 |     return; | 
 |   } | 
 |  | 
 |   if (reason == PermissionsManager::UpdateReason::kAdded && | 
 |       base::FeatureList::IsEnabled( | 
 |           extensions_features::kExperimentalOmniboxLabs)) { | 
 |     url_service_->AddToUnscopedModeExtensionIds(extension.id()); | 
 |   } else if (reason == PermissionsManager::UpdateReason::kRemoved) { | 
 |     url_service_->RemoveFromUnscopedModeExtensionIdsIfPresent(extension.id()); | 
 |   } | 
 | } | 
 |  | 
 | gfx::Image OmniboxAPI::GetOmniboxIcon(const ExtensionId& extension_id) { | 
 |   return omnibox_icon_manager_.GetIcon(extension_id); | 
 | } | 
 |  | 
 | void OmniboxAPI::OnTemplateURLsLoaded() { | 
 |   // Register keywords for pending extensions. | 
 |   template_url_subscription_ = {}; | 
 |   for (const Extension* i : pending_extensions_) { | 
 |     url_service_->RegisterExtensionControlledTURL( | 
 |         i->id(), i->short_name(), OmniboxInfo::GetKeyword(i), | 
 |         GetTemplateURLStringForExtension(i->id()), | 
 |         GetLastUpdateTime(ExtensionPrefs::Get(profile_), i->id()), | 
 |         IsUnscopedModeAllowed(i)); | 
 |   } | 
 |   pending_extensions_.clear(); | 
 | } | 
 |  | 
 | template <> | 
 | void BrowserContextKeyedAPIFactory<OmniboxAPI>::DeclareFactoryDependencies() { | 
 |   DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory()); | 
 |   DependsOn(ExtensionPrefsFactory::GetInstance()); | 
 |   DependsOn(TemplateURLServiceFactory::GetInstance()); | 
 |   DependsOn(PermissionsManager::GetFactory()); | 
 | } | 
 |  | 
 | OmniboxSendSuggestionsFunction::OmniboxSendSuggestionsFunction() = default; | 
 | OmniboxSendSuggestionsFunction::~OmniboxSendSuggestionsFunction() = default; | 
 |  | 
 | ExtensionFunction::ResponseAction OmniboxSendSuggestionsFunction::Run() { | 
 |   std::optional<api::omnibox::SendSuggestions::Params> params = | 
 |       SendSuggestions::Params::Create(args()); | 
 |   EXTENSION_FUNCTION_VALIDATE(params); | 
 |   request_id_ = params->request_id; | 
 |  | 
 |   if (!params->suggest_results.empty()) { | 
 |     std::vector<std::string_view> inputs; | 
 |     inputs.reserve(params->suggest_results.size()); | 
 |     for (const auto& suggestion : params->suggest_results) { | 
 |       std::vector<ExtensionSuggestion::Action> actions; | 
 |       inputs.push_back(suggestion.description); | 
 |       if (suggestion.actions) { | 
 |         if (!IsUnscopedModeAllowed(extension())) { | 
 |           return RespondNow( | 
 |               Error(ExtensionOmniboxEventRouter:: | 
 |                         kActionsRequireDirectInputPermissionError)); | 
 |         } | 
 |         if (suggestion.actions->size() > | 
 |             ExtensionOmniboxEventRouter::kMaxSuggestionActions) { | 
 |           return RespondNow(Error(base::StringPrintf( | 
 |               ExtensionOmniboxEventRouter::kMaxSuggestionActionsExceededError, | 
 |               suggestion.actions->size(), | 
 |               ExtensionOmniboxEventRouter::kMaxSuggestionActions))); | 
 |         } | 
 |         actions.reserve(suggestion.actions->size()); | 
 |         for (const auto& action : *suggestion.actions) { | 
 |           base::Value::Dict canvas_set = | 
 |               action.icon ? action.icon->ToValue() : base::Value::Dict(); | 
 |           gfx::ImageSkia image_skia; | 
 |           if (!canvas_set.empty()) { | 
 |             base::Value::Dict& image_data = *canvas_set.FindDict("imageData"); | 
 |             // The image data should have been verified by the pre-validation | 
 |             // param update. | 
 |             CHECK(!image_data.empty()); | 
 |             // TODO(crbug.com/408069174): Move ParseIconFromCanvasDictionary | 
 |             // outside `ExtensionAction` into a common file. | 
 |             if (extensions::ParseIconFromCanvasDictionary(image_data, | 
 |                                                           &image_skia) != | 
 |                 extensions::IconParseResult::kSuccess) { | 
 |               return RespondNow(Error(base::StringPrintf( | 
 |                   ExtensionOmniboxEventRouter::kActionIconError, | 
 |                   suggestion.description, action.name))); | 
 |             } | 
 |           } | 
 |           actions.emplace_back(action.name, action.label, action.tooltip_text, | 
 |                                gfx::Image(image_skia)); | 
 |         } | 
 |       } | 
 |  | 
 |       const std::vector<api::omnibox::MatchClassification> empty_styles; | 
 |       const std::vector<api::omnibox::MatchClassification>* styles_ptr = | 
 |           suggestion.description_styles ? &suggestion.description_styles.value() | 
 |                                         : &empty_styles; | 
 |       extension_suggestions_.emplace_back( | 
 |           suggestion.content, suggestion.description, | 
 |           suggestion.deletable.value_or(false), | 
 |           StyleTypesToACMatchClassifications(styles_ptr, | 
 |                                              suggestion.description), | 
 |           std::move(actions), suggestion.icon_url); | 
 |     } | 
 |  | 
 |     if (is_from_service_worker()) { | 
 |       ParseDescriptionsAndStyles( | 
 |           inputs, | 
 |           base::BindOnce( | 
 |               &OmniboxSendSuggestionsFunction::OnParsedDescriptionsAndStyles, | 
 |               this)); | 
 |       return RespondLater(); | 
 |     } | 
 |   } | 
 |  | 
 |   NotifySuggestionsReady(); | 
 |   return RespondNow(NoArguments()); | 
 | } | 
 |  | 
 | void OmniboxSendSuggestionsFunction::OnParsedDescriptionsAndStyles( | 
 |     DescriptionAndStylesResult result) { | 
 |   DCHECK_NE(0u, extension_suggestions_.size()); | 
 |   // Since the XML parsing happens asynchronously, the browser context can be | 
 |   // torn down in the interim. If this happens, early-out. | 
 |   if (!browser_context()) { | 
 |     return; | 
 |   } | 
 |  | 
 |   if (!result.error.empty()) { | 
 |     Respond(Error(std::move(result.error))); | 
 |     return; | 
 |   } | 
 |  | 
 |   if (result.descriptions_and_styles.size() != extension_suggestions_.size()) { | 
 |     // This can technically happen if the extension provided input that mucked | 
 |     // with our XML parsing (see suggestion_parser_unittest.cc). This isn't a | 
 |     // security concern, but would mean that our mapping to record the other | 
 |     // fields in the suggestion are mismatched. Abort. Since there's no | 
 |     // legitimate case for this happening, just emit a generic error message. | 
 |     Respond(Error("Invalid input.")); | 
 |     return; | 
 |   } | 
 |  | 
 |   for (size_t i = 0; i < extension_suggestions_.size(); ++i) { | 
 |     extension_suggestions_[i].description = | 
 |         base::UTF16ToUTF8(result.descriptions_and_styles[i].description); | 
 |     extension_suggestions_[i].match_classifications = | 
 |         StyleTypesToACMatchClassifications( | 
 |             &result.descriptions_and_styles[i].styles, | 
 |             extension_suggestions_[i].description); | 
 |   } | 
 |  | 
 |   NotifySuggestionsReady(); | 
 |   Respond(NoArguments()); | 
 | } | 
 |  | 
 | void OmniboxSendSuggestionsFunction::NotifySuggestionsReady() { | 
 |   Profile* profile = | 
 |       Profile::FromBrowserContext(browser_context())->GetOriginalProfile(); | 
 |   OmniboxSuggestionsWatcherFactory::GetForBrowserContext(profile) | 
 |       ->NotifySuggestionsReady(extension_suggestions_, request_id_, | 
 |                                extension_id()); | 
 | } | 
 |  | 
 | ExtensionFunction::ResponseAction OmniboxSetDefaultSuggestionFunction::Run() { | 
 |   std::optional<SetDefaultSuggestion::Params> params = | 
 |       SetDefaultSuggestion::Params::Create(args()); | 
 |   EXTENSION_FUNCTION_VALIDATE(params); | 
 |  | 
 |   if (!params->suggestion.description_styles) { | 
 |     ParseDescriptionAndStyles( | 
 |         params->suggestion.description, | 
 |         base::BindOnce( | 
 |             &OmniboxSetDefaultSuggestionFunction::OnParsedDescriptionAndStyles, | 
 |             this)); | 
 |     return RespondLater(); | 
 |   } | 
 |  | 
 |   SetDefaultSuggestion(params->suggestion); | 
 |   return RespondNow(NoArguments()); | 
 | } | 
 |  | 
 | void OmniboxSetDefaultSuggestionFunction::OnParsedDescriptionAndStyles( | 
 |     DescriptionAndStylesResult result) { | 
 |   if (!result.error.empty()) { | 
 |     Respond(Error(std::move(result.error))); | 
 |     return; | 
 |   } | 
 |  | 
 |   DCHECK_EQ(1u, result.descriptions_and_styles.size()); | 
 |   DescriptionAndStyles& single_result = result.descriptions_and_styles[0]; | 
 |  | 
 |   omnibox::DefaultSuggestResult default_suggestion; | 
 |   default_suggestion.description = base::UTF16ToUTF8(single_result.description); | 
 |   default_suggestion.description_styles.emplace(); | 
 |   default_suggestion.description_styles->swap(single_result.styles); | 
 |   SetDefaultSuggestion(default_suggestion); | 
 |   Respond(NoArguments()); | 
 | } | 
 |  | 
 | void OmniboxSetDefaultSuggestionFunction::SetDefaultSuggestion( | 
 |     const omnibox::DefaultSuggestResult& suggestion) { | 
 |   Profile* profile = Profile::FromBrowserContext(browser_context()); | 
 |   if (SetOmniboxDefaultSuggestion(profile, extension_id(), suggestion)) { | 
 |     OmniboxSuggestionsWatcherFactory::GetForBrowserContext( | 
 |         profile->GetOriginalProfile()) | 
 |         ->NotifyDefaultSuggestionChanged(); | 
 |   } | 
 | } | 
 |  | 
 | // This function converts style information populated by the JSON schema | 
 | // compiler into an ACMatchClassifications object. | 
 | ACMatchClassifications StyleTypesToACMatchClassifications( | 
 |     const std::vector<omnibox::MatchClassification>* description_styles, | 
 |     const std::string& suggestion_description) { | 
 |   ACMatchClassifications match_classifications; | 
 |   if (!description_styles->empty()) { | 
 |     std::u16string description = base::UTF8ToUTF16(suggestion_description); | 
 |     std::vector<int> styles(description.length(), 0); | 
 |  | 
 |     for (const omnibox::MatchClassification& style : *description_styles) { | 
 |       int length = style.length ? *style.length : description.length(); | 
 |       size_t offset = style.offset >= 0 | 
 |                           ? style.offset | 
 |                           : std::max(0, static_cast<int>(description.length()) + | 
 |                                             style.offset); | 
 |  | 
 |       int type_class; | 
 |       switch (style.type) { | 
 |         case omnibox::DescriptionStyleType::kUrl: | 
 |           type_class = AutocompleteMatch::ACMatchClassification::URL; | 
 |           break; | 
 |         case omnibox::DescriptionStyleType::kMatch: | 
 |           type_class = AutocompleteMatch::ACMatchClassification::MATCH; | 
 |           break; | 
 |         case omnibox::DescriptionStyleType::kDim: | 
 |           type_class = AutocompleteMatch::ACMatchClassification::DIM; | 
 |           break; | 
 |         default: | 
 |           type_class = AutocompleteMatch::ACMatchClassification::NONE; | 
 |           return match_classifications; | 
 |       } | 
 |  | 
 |       for (size_t j = offset; j < offset + length && j < styles.size(); ++j) | 
 |         styles[j] |= type_class; | 
 |     } | 
 |  | 
 |     for (size_t i = 0; i < styles.size(); ++i) { | 
 |       if (i == 0 || styles[i] != styles[i-1]) | 
 |         match_classifications.push_back( | 
 |             ACMatchClassification(i, styles[i])); | 
 |     } | 
 |   } else { | 
 |     match_classifications.push_back( | 
 |         ACMatchClassification(0, ACMatchClassification::NONE)); | 
 |   } | 
 |  | 
 |   return match_classifications; | 
 | } | 
 |  | 
 | void ApplyDefaultSuggestionForExtensionKeyword( | 
 |     Profile* profile, | 
 |     const TemplateURL* keyword, | 
 |     const std::u16string& remaining_input, | 
 |     AutocompleteMatch* match) { | 
 |   DCHECK(keyword->type() == TemplateURL::OMNIBOX_API_EXTENSION); | 
 |  | 
 |   std::optional<omnibox::SuggestResult> suggestion( | 
 |       GetOmniboxDefaultSuggestion(profile, keyword->GetExtensionId())); | 
 |   if (!suggestion || suggestion->description.empty()) | 
 |     return;  // fall back to the universal default | 
 |  | 
 |   const std::u16string kPlaceholderText(u"%s"); | 
 |   const std::u16string kReplacementText(u"<input>"); | 
 |  | 
 |   std::u16string description = base::UTF8ToUTF16(suggestion->description); | 
 |   ACMatchClassifications& description_styles = match->contents_class; | 
 |  | 
 |   const std::vector<api::omnibox::MatchClassification> empty_styles; | 
 |   const std::vector<api::omnibox::MatchClassification>* styles_list = | 
 |       suggestion->description_styles ? &suggestion->description_styles.value() | 
 |                                      : &empty_styles; | 
 |   description_styles = | 
 |       StyleTypesToACMatchClassifications(styles_list, suggestion->description); | 
 |  | 
 |   // Replace "%s" with the user's input and adjust the style offsets to the | 
 |   // new length of the description. | 
 |   size_t placeholder(description.find(kPlaceholderText, 0)); | 
 |   if (placeholder != std::u16string::npos) { | 
 |     std::u16string replacement = | 
 |         remaining_input.empty() ? kReplacementText : remaining_input; | 
 |     description.replace(placeholder, kPlaceholderText.length(), replacement); | 
 |  | 
 |     for (auto& description_style : description_styles) { | 
 |       if (description_style.offset > placeholder) | 
 |         description_style.offset += replacement.length() - 2; | 
 |     } | 
 |   } | 
 |  | 
 |   match->contents.assign(description); | 
 | } | 
 |  | 
 | }  // namespace extensions |