Home | History | Annotate | Download | only in omnibox
      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/ui/webui/omnibox/omnibox_ui_handler.h"
      6 
      7 #include <string>
      8 
      9 #include "base/auto_reset.h"
     10 #include "base/bind.h"
     11 #include "base/strings/string16.h"
     12 #include "base/strings/stringprintf.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "base/time/time.h"
     15 #include "base/values.h"
     16 #include "chrome/browser/autocomplete/autocomplete_classifier.h"
     17 #include "chrome/browser/autocomplete/autocomplete_controller.h"
     18 #include "chrome/browser/autocomplete/autocomplete_input.h"
     19 #include "chrome/browser/autocomplete/autocomplete_match.h"
     20 #include "chrome/browser/autocomplete/autocomplete_provider.h"
     21 #include "chrome/browser/history/history_service.h"
     22 #include "chrome/browser/history/history_service_factory.h"
     23 #include "chrome/browser/history/url_database.h"
     24 #include "chrome/browser/search/search.h"
     25 #include "chrome/browser/search_engines/template_url.h"
     26 #include "content/public/browser/web_ui.h"
     27 
     28 OmniboxUIHandler::OmniboxUIHandler(Profile* profile): profile_(profile) {
     29   ResetController();
     30 }
     31 
     32 OmniboxUIHandler::~OmniboxUIHandler() {}
     33 
     34 void OmniboxUIHandler::RegisterMessages() {
     35   web_ui()->RegisterMessageCallback("startOmniboxQuery",
     36       base::Bind(&OmniboxUIHandler::StartOmniboxQuery,
     37                  base::Unretained(this)));
     38 }
     39 
     40 // This function gets called when the AutocompleteController possibly
     41 // has new results.  We package those results in a DictionaryValue
     42 // object result_to_output and call the javascript function
     43 // handleNewAutocompleteResult.  Here's an example populated
     44 // result_to_output object:
     45 // {
     46 //   'done': false,
     47 //   'time_since_omnibox_started_ms': 15,
     48 //   'host': 'mai',
     49 //   'is_typed_host': false,
     50 //   'combined_results' : {
     51 //     'num_items': 4,
     52 //     'item_0': {
     53 //       'destination_url': 'http://mail.google.com',
     54 //       'provider_name': 'HistoryURL',
     55 //       'relevance': 1410,
     56 //       ...
     57 //     }
     58 //     'item_1: {
     59 //       ...
     60 //     }
     61 //     ...
     62 //   }
     63 //   'results_by_provider': {
     64 //     'HistoryURL' : {
     65 //       'num_items': 3,
     66 //         ...
     67 //       }
     68 //     'Search' : {
     69 //       'num_items': 1,
     70 //       ...
     71 //     }
     72 //     ...
     73 //   }
     74 // }
     75 // For reference, the javascript code that unpacks this object and
     76 // displays it is in chrome/browser/resources/omnibox.js
     77 void OmniboxUIHandler::OnResultChanged(bool default_match_changed) {
     78   base::DictionaryValue result_to_output;
     79   // Fill in general information.
     80   result_to_output.SetBoolean("done", controller_->done());
     81   result_to_output.SetInteger("time_since_omnibox_started_ms",
     82       (base::Time::Now() - time_omnibox_started_).InMilliseconds());
     83   const string16& host = controller_->input().text().substr(
     84       controller_->input().parts().host.begin,
     85       controller_->input().parts().host.len);
     86   result_to_output.SetString("host", host);
     87   bool is_typed_host;
     88   if (LookupIsTypedHost(host, &is_typed_host)) {
     89     // If we successfully looked up whether the host part of the omnibox
     90     // input (this interprets the input as a host plus optional path) as
     91     // a typed host, then record this information in the output.
     92     result_to_output.SetBoolean("is_typed_host", is_typed_host);
     93   }
     94   // Fill in the merged/combined results the controller has provided.
     95   AddResultToDictionary("combined_results", controller_->result().begin(),
     96                         controller_->result().end(), &result_to_output);
     97   // Fill results from each individual provider as well.
     98   for (ACProviders::const_iterator it(controller_->providers()->begin());
     99        it != controller_->providers()->end(); ++it) {
    100     AddResultToDictionary(
    101         std::string("results_by_provider.") + (*it)->GetName(),
    102         (*it)->matches().begin(), (*it)->matches().end(), &result_to_output);
    103   }
    104   // Add done; send the results.
    105   web_ui()->CallJavascriptFunction("omniboxDebug.handleNewAutocompleteResult",
    106                                    result_to_output);
    107 }
    108 
    109 // For details on the format of the DictionaryValue that this function
    110 // populates, see the comments by OnResultChanged().
    111 void OmniboxUIHandler::AddResultToDictionary(const std::string& prefix,
    112                                              ACMatches::const_iterator it,
    113                                              ACMatches::const_iterator end,
    114                                              base::DictionaryValue* output) {
    115   int i = 0;
    116   for (; it != end; ++it, ++i) {
    117     std::string item_prefix(prefix + base::StringPrintf(".item_%d", i));
    118     if (it->provider != NULL) {
    119       output->SetString(item_prefix + ".provider_name",
    120                         it->provider->GetName());
    121       output->SetBoolean(item_prefix + ".provider_done", it->provider->done());
    122     }
    123     output->SetInteger(item_prefix + ".relevance", it->relevance);
    124     output->SetBoolean(item_prefix + ".deletable", it->deletable);
    125     output->SetString(item_prefix + ".fill_into_edit", it->fill_into_edit);
    126     output->SetString(item_prefix + ".inline_autocompletion",
    127                        it->inline_autocompletion);
    128     output->SetString(item_prefix + ".destination_url",
    129                       it->destination_url.spec());
    130     output->SetString(item_prefix + ".contents", it->contents);
    131     // At this time, we're not bothering to send along the long vector that
    132     // represent contents classification.  i.e., for each character, what
    133     // type of text it is.
    134     output->SetString(item_prefix + ".description", it->description);
    135     // At this time, we're not bothering to send along the long vector that
    136     // represents description classification.  i.e., for each character, what
    137     // type of text it is.
    138     output->SetInteger(item_prefix + ".transition", it->transition);
    139     output->SetBoolean(item_prefix + ".is_history_what_you_typed_match",
    140                        it->is_history_what_you_typed_match);
    141     output->SetString(item_prefix + ".type",
    142                       AutocompleteMatchType::ToString(it->type));
    143     if (it->associated_keyword.get() != NULL) {
    144       output->SetString(item_prefix + ".associated_keyword",
    145                         it->associated_keyword->keyword);
    146     }
    147     output->SetString(item_prefix + ".keyword", it->keyword);
    148     output->SetBoolean(item_prefix + ".starred", it->starred);
    149     output->SetBoolean(item_prefix + ".from_previous", it->from_previous);
    150     for (AutocompleteMatch::AdditionalInfo::const_iterator j =
    151          it->additional_info.begin(); j != it->additional_info.end(); ++j) {
    152       output->SetString(item_prefix + ".additional_info." + j->first,
    153                         j->second);
    154     }
    155   }
    156   output->SetInteger(prefix + ".num_items", i);
    157 }
    158 
    159 bool OmniboxUIHandler::LookupIsTypedHost(const string16& host,
    160                                          bool* is_typed_host) const {
    161   HistoryService* const history_service =
    162       HistoryServiceFactory::GetForProfile(profile_,
    163                                            Profile::EXPLICIT_ACCESS);
    164   if (!history_service)
    165     return false;
    166   history::URLDatabase* url_db = history_service->InMemoryDatabase();
    167   if (!url_db)
    168     return false;
    169   *is_typed_host = url_db->IsTypedHost(UTF16ToUTF8(host));
    170   return true;
    171 }
    172 
    173 void OmniboxUIHandler::StartOmniboxQuery(const base::ListValue* input) {
    174   DCHECK_EQ(4u, input->GetSize());
    175   string16 input_string;
    176   bool return_val = input->GetString(0, &input_string);
    177   DCHECK(return_val);
    178   int cursor_position;
    179   return_val = input->GetInteger(1, &cursor_position);
    180   DCHECK(return_val);
    181   bool prevent_inline_autocomplete;
    182   return_val = input->GetBoolean(2, &prevent_inline_autocomplete);
    183   DCHECK(return_val);
    184   bool prefer_keyword;
    185   return_val = input->GetBoolean(3, &prefer_keyword);
    186   DCHECK(return_val);
    187   // Reset the controller.  If we don't do this, then the
    188   // AutocompleteController might inappropriately set its |minimal_changes|
    189   // variable (or something else) and some providers will short-circuit
    190   // important logic and return stale results.  In short, we want the
    191   // actual results to not depend on the state of the previous request.
    192   ResetController();
    193   time_omnibox_started_ = base::Time::Now();
    194   controller_->Start(AutocompleteInput(
    195       input_string,
    196       cursor_position,
    197       string16(),  // user's desired tld (top-level domain)
    198       GURL(),
    199       AutocompleteInput::INVALID_SPEC,
    200       prevent_inline_autocomplete,
    201       prefer_keyword,
    202       true,  // allow exact keyword matches
    203       AutocompleteInput::ALL_MATCHES));  // want all matches
    204 }
    205 
    206 void OmniboxUIHandler::ResetController() {
    207   controller_.reset(new AutocompleteController(profile_, this,
    208       chrome::IsInstantExtendedAPIEnabled() ?
    209           AutocompleteClassifier::kInstantExtendedOmniboxProviders :
    210           AutocompleteClassifier::kDefaultOmniboxProviders));
    211 }
    212