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