Home | History | Annotate | Download | only in autocomplete
      1 // Copyright (c) 2012 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 "chrome/browser/autocomplete/extension_app_provider.h"
      6 
      7 #include <algorithm>
      8 #include <cmath>
      9 
     10 #include "base/strings/string16.h"
     11 #include "base/strings/utf_string_conversions.h"
     12 #include "chrome/browser/chrome_notification_types.h"
     13 #include "chrome/browser/extensions/extension_service.h"
     14 #include "chrome/browser/extensions/extension_ui_util.h"
     15 #include "chrome/browser/extensions/extension_util.h"
     16 #include "chrome/browser/history/history_service.h"
     17 #include "chrome/browser/history/history_service_factory.h"
     18 #include "chrome/browser/history/url_database.h"
     19 #include "chrome/browser/profiles/profile.h"
     20 #include "chrome/browser/ui/extensions/application_launch.h"
     21 #include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
     22 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
     23 #include "components/metrics/proto/omnibox_input_type.pb.h"
     24 #include "content/public/browser/notification_source.h"
     25 #include "extensions/browser/extension_registry.h"
     26 #include "extensions/browser/extension_system.h"
     27 #include "extensions/common/extension.h"
     28 #include "extensions/common/extension_set.h"
     29 #include "ui/base/l10n/l10n_util.h"
     30 
     31 ExtensionAppProvider::ExtensionAppProvider(
     32     AutocompleteProviderListener* listener,
     33     Profile* profile)
     34     : AutocompleteProvider(listener, profile,
     35           AutocompleteProvider::TYPE_EXTENSION_APP) {
     36   // Notifications of extensions loading and unloading always come from the
     37   // non-incognito profile, but we need to see them regardless, as the incognito
     38   // windows can be affected.
     39   registrar_.Add(this,
     40                  chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
     41                  content::Source<Profile>(profile_->GetOriginalProfile()));
     42   registrar_.Add(this,
     43                  chrome::NOTIFICATION_EXTENSION_UNINSTALLED_DEPRECATED,
     44                  content::Source<Profile>(profile_->GetOriginalProfile()));
     45   RefreshAppList();
     46 }
     47 
     48 // static.
     49 void ExtensionAppProvider::LaunchAppFromOmnibox(
     50     const AutocompleteMatch& match,
     51     Profile* profile,
     52     WindowOpenDisposition disposition) {
     53   const extensions::Extension* extension =
     54       extensions::ExtensionRegistry::Get(profile)
     55           ->enabled_extensions().GetAppByURL(match.destination_url);
     56   // While the Omnibox popup is open, the extension can be updated, changing
     57   // its URL and leaving us with no extension being found. In this case, we
     58   // ignore the request.
     59   if (!extension)
     60     return;
     61 
     62   CoreAppLauncherHandler::RecordAppLaunchType(
     63       extension_misc::APP_LAUNCH_OMNIBOX_APP,
     64       extension->GetType());
     65 
     66   OpenApplication(AppLaunchParams(profile, extension, disposition));
     67 }
     68 
     69 void ExtensionAppProvider::AddExtensionAppForTesting(
     70     const ExtensionApp& extension_app) {
     71   extension_apps_.push_back(extension_app);
     72 }
     73 
     74 AutocompleteMatch ExtensionAppProvider::CreateAutocompleteMatch(
     75     const AutocompleteInput& input,
     76     const ExtensionApp& app,
     77     size_t name_match_index,
     78     size_t url_match_index) {
     79   // TODO(finnur): Figure out what type to return here, might want to have
     80   // the extension icon/a generic icon show up in the Omnibox.
     81   AutocompleteMatch match(this, 0, false,
     82                           AutocompleteMatchType::EXTENSION_APP);
     83   match.fill_into_edit =
     84       app.should_match_against_launch_url ? app.launch_url : input.text();
     85   match.destination_url = GURL(app.launch_url);
     86   match.allowed_to_be_default_match = true;
     87   match.contents = AutocompleteMatch::SanitizeString(app.name);
     88   AutocompleteMatch::ClassifyLocationInString(name_match_index,
     89       input.text().length(), app.name.length(), ACMatchClassification::NONE,
     90       &match.contents_class);
     91   if (app.should_match_against_launch_url) {
     92     match.description = app.launch_url;
     93     AutocompleteMatch::ClassifyLocationInString(url_match_index,
     94         input.text().length(), app.launch_url.length(),
     95         ACMatchClassification::URL, &match.description_class);
     96   }
     97   match.relevance = CalculateRelevance(
     98       input.type(),
     99       input.text().length(),
    100       name_match_index != base::string16::npos ?
    101           app.name.length() : app.launch_url.length(),
    102       match.destination_url);
    103   return match;
    104 }
    105 
    106 void ExtensionAppProvider::Start(const AutocompleteInput& input,
    107                                  bool minimal_changes) {
    108   matches_.clear();
    109 
    110   if ((input.type() == metrics::OmniboxInputType::INVALID) ||
    111       (input.type() == metrics::OmniboxInputType::FORCED_QUERY))
    112     return;
    113 
    114   if (input.text().empty())
    115     return;
    116 
    117   for (ExtensionApps::const_iterator app = extension_apps_.begin();
    118        app != extension_apps_.end(); ++app) {
    119     // See if the input matches this extension application.
    120     const base::string16& name = app->name;
    121     base::string16::const_iterator name_iter =
    122         std::search(name.begin(), name.end(),
    123                     input.text().begin(), input.text().end(),
    124                     base::CaseInsensitiveCompare<base::char16>());
    125     bool matches_name = name_iter != name.end();
    126     size_t name_match_index = matches_name ?
    127         static_cast<size_t>(name_iter - name.begin()) : base::string16::npos;
    128 
    129     bool matches_url = false;
    130     size_t url_match_index = base::string16::npos;
    131     if (app->should_match_against_launch_url) {
    132       const base::string16& url = app->launch_url;
    133       base::string16::const_iterator url_iter =
    134           std::search(url.begin(), url.end(),
    135                       input.text().begin(), input.text().end(),
    136                       base::CaseInsensitiveCompare<base::char16>());
    137       matches_url = (url_iter != url.end()) &&
    138           (input.type() != metrics::OmniboxInputType::FORCED_QUERY);
    139       url_match_index = matches_url ?
    140           static_cast<size_t>(url_iter - url.begin()) : base::string16::npos;
    141     }
    142 
    143     if (matches_name || matches_url) {
    144       // We have a match, might be a partial match.
    145       matches_.push_back(CreateAutocompleteMatch(
    146           input, *app, name_match_index, url_match_index));
    147     }
    148   }
    149 }
    150 
    151 ExtensionAppProvider::~ExtensionAppProvider() {
    152 }
    153 
    154 void ExtensionAppProvider::RefreshAppList() {
    155   ExtensionService* extension_service =
    156       extensions::ExtensionSystem::Get(profile_)->extension_service();
    157   if (!extension_service)
    158     return;  // During testing, there is no extension service.
    159   const extensions::ExtensionSet* extensions = extension_service->extensions();
    160   extension_apps_.clear();
    161   for (extensions::ExtensionSet::const_iterator iter = extensions->begin();
    162        iter != extensions->end(); ++iter) {
    163     const extensions::Extension* app = iter->get();
    164     if (!extensions::ui_util::ShouldDisplayInAppLauncher(app, profile_))
    165       continue;
    166     // Note: Apps that appear in the NTP only are not added here since this
    167     // provider is currently only used in the app launcher.
    168 
    169     if (profile_->IsOffTheRecord() &&
    170         !extensions::util::CanLoadInIncognito(app, profile_))
    171       continue;
    172 
    173     GURL launch_url = app->is_platform_app() ?
    174         app->url() : extensions::AppLaunchInfo::GetFullLaunchURL(app);
    175     DCHECK(launch_url.is_valid());
    176 
    177     ExtensionApp extension_app = {
    178         base::UTF8ToUTF16(app->name()),
    179         base::UTF8ToUTF16(launch_url.spec()),
    180         // Only hosted apps have recognizable URLs that users might type in,
    181         // packaged apps and hosted apps use chrome-extension:// URLs that are
    182         // normally not shown to users.
    183         app->is_hosted_app()
    184     };
    185     extension_apps_.push_back(extension_app);
    186   }
    187 }
    188 
    189 void ExtensionAppProvider::Observe(int type,
    190                                    const content::NotificationSource& source,
    191                                    const content::NotificationDetails& details) {
    192   RefreshAppList();
    193 }
    194 
    195 int ExtensionAppProvider::CalculateRelevance(
    196     metrics::OmniboxInputType::Type type,
    197     int input_length,
    198     int target_length,
    199     const GURL& url) {
    200   // If you update the algorithm here, please remember to update the tables in
    201   // autocomplete.h also.
    202   const int kMaxRelevance = 1425;
    203 
    204   if (input_length == target_length)
    205     return kMaxRelevance;
    206 
    207   // We give a boost proportionally based on how much of the input matches the
    208   // app name, up to a maximum close to 200 (we can be close to, but we'll never
    209   // reach 200 because the 100% match is taken care of above).
    210   double fraction_boost = static_cast<double>(200) *
    211                           input_length / target_length;
    212 
    213   // We also give a boost relative to how often the user has previously typed
    214   // the Extension App URL/selected the Extension App suggestion from this
    215   // provider (boost is between 200-400).
    216   double type_count_boost = 0;
    217   HistoryService* const history_service =
    218       HistoryServiceFactory::GetForProfile(profile_, Profile::EXPLICIT_ACCESS);
    219   history::URLDatabase* url_db = history_service ?
    220       history_service->InMemoryDatabase() : NULL;
    221   if (url_db) {
    222     history::URLRow info;
    223     url_db->GetRowForURL(url, &info);
    224     type_count_boost =
    225         400 * (1.0 - (std::pow(static_cast<double>(2), -info.typed_count())));
    226   }
    227   int relevance = 575 + static_cast<int>(type_count_boost) +
    228                         static_cast<int>(fraction_boost);
    229   DCHECK_LE(relevance, kMaxRelevance);
    230   return relevance;
    231 }
    232