blob: 41b79cc0bc4e198e148064354f55c566a7b3fa31 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/webui/omnibox/omnibox_page_handler.h"
#include <stddef.h>
#include <string>
#include <utility>
#include "base/auto_reset.h"
#include "base/base64.h"
#include "base/bind.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/autocomplete/chrome_autocomplete_provider_client.h"
#include "chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.h"
#include "chrome/browser/bitmap_fetcher/bitmap_fetcher_service.h"
#include "chrome/browser/bitmap_fetcher/bitmap_fetcher_service_factory.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search/search.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/browser/url_database.h"
#include "components/omnibox/browser/autocomplete_classifier.h"
#include "components/omnibox/browser/autocomplete_controller.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/omnibox/browser/autocomplete_provider.h"
#include "components/omnibox/browser/omnibox_controller_emitter.h"
#include "components/search_engines/template_url.h"
#include "content/public/browser/web_ui.h"
#include "third_party/metrics_proto/omnibox_event.pb.h"
#include "ui/gfx/image/image.h"
using bookmarks::BookmarkModel;
using OmniboxPageImageCallback =
base::RepeatingCallback<void(const SkBitmap& bitmap)>;
namespace {
using ImageReciever = std::function<void(std::string)>;
class OmniboxPageImageObserver : public BitmapFetcherService::Observer {
public:
explicit OmniboxPageImageObserver(ImageReciever image_reciever)
: image_reciever_(image_reciever) {}
void OnImageChanged(BitmapFetcherService::RequestId request_id,
const SkBitmap& bitmap) override {
auto data = gfx::Image::CreateFrom1xBitmap(bitmap).As1xPNGBytes();
std::string base_64;
base::Base64Encode(base::StringPiece(data->front_as<char>(), data->size()),
&base_64);
const char kDataUrlPrefix[] = "data:image/png;base64,";
std::string data_url = GURL(kDataUrlPrefix + base_64).spec();
image_reciever_(data_url);
}
private:
const ImageReciever image_reciever_;
DISALLOW_COPY_AND_ASSIGN(OmniboxPageImageObserver);
};
std::string SuggestionAnswerTypeToString(int answer_type) {
switch (answer_type) {
case SuggestionAnswer::ANSWER_TYPE_INVALID:
return "invalid";
case SuggestionAnswer::ANSWER_TYPE_DICTIONARY:
return "dictionary";
case SuggestionAnswer::ANSWER_TYPE_FINANCE:
return "finance";
case SuggestionAnswer::ANSWER_TYPE_KNOWLEDGE_GRAPH:
return "knowledge graph";
case SuggestionAnswer::ANSWER_TYPE_LOCAL:
return "local";
case SuggestionAnswer::ANSWER_TYPE_SPORTS:
return "sports";
case SuggestionAnswer::ANSWER_TYPE_SUNRISE:
return "sunrise";
case SuggestionAnswer::ANSWER_TYPE_TRANSLATION:
return "translation";
case SuggestionAnswer::ANSWER_TYPE_WEATHER:
return "weather";
case SuggestionAnswer::ANSWER_TYPE_WHEN_IS:
return "when is";
case SuggestionAnswer::ANSWER_TYPE_CURRENCY:
return "currency";
case SuggestionAnswer::ANSWER_TYPE_LOCAL_TIME:
return "local time";
case SuggestionAnswer::ANSWER_TYPE_PLAY_INSTALL:
return "play install";
default:
return std::to_string(answer_type);
}
}
std::string SuggestionAnswerImageLineToString(
const SuggestionAnswer::ImageLine& image_line) {
std::string string;
for (auto text_field : image_line.text_fields())
string += base::UTF16ToUTF8(text_field.text());
if (image_line.additional_text())
string += " " + base::UTF16ToUTF8(image_line.additional_text()->text());
if (image_line.status_text())
string += " " + base::UTF16ToUTF8(image_line.status_text()->text());
return string;
}
} // namespace
namespace mojo {
template <>
struct TypeConverter<std::vector<mojom::AutocompleteAdditionalInfoPtr>,
AutocompleteMatch::AdditionalInfo> {
static std::vector<mojom::AutocompleteAdditionalInfoPtr> Convert(
const AutocompleteMatch::AdditionalInfo& input) {
std::vector<mojom::AutocompleteAdditionalInfoPtr> array(input.size());
size_t index = 0;
for (auto i = input.begin(); i != input.end(); ++i, index++) {
mojom::AutocompleteAdditionalInfoPtr item(
mojom::AutocompleteAdditionalInfo::New());
item->key = i->first;
item->value = i->second;
array[index] = std::move(item);
}
return array;
}
};
template <>
struct TypeConverter<mojom::AutocompleteMatchPtr, AutocompleteMatch> {
static mojom::AutocompleteMatchPtr Convert(const AutocompleteMatch& input) {
mojom::AutocompleteMatchPtr result(mojom::AutocompleteMatch::New());
if (input.provider != NULL) {
result->provider_name = std::string(input.provider->GetName());
result->provider_done = input.provider->done();
}
result->relevance = input.relevance;
result->deletable = input.deletable;
result->fill_into_edit = base::UTF16ToUTF8(input.fill_into_edit);
result->inline_autocompletion =
base::UTF16ToUTF8(input.inline_autocompletion);
result->destination_url = input.destination_url.spec();
result->image = input.ImageUrl().spec().c_str();
result->contents = base::UTF16ToUTF8(input.contents);
// At this time, we're not bothering to send along the long vector that
// represent contents classification. i.e., for each character, what
// type of text it is.
result->description = base::UTF16ToUTF8(input.description);
// At this time, we're not bothering to send along the long vector that
// represents description classification. i.e., for each character, what
// type of text it is.
if (input.answer) {
result->answer =
SuggestionAnswerImageLineToString(input.answer->first_line()) +
" / " +
SuggestionAnswerImageLineToString(input.answer->second_line()) +
" / " + SuggestionAnswerTypeToString(input.answer->type());
}
result->transition =
ui::PageTransitionGetCoreTransitionString(input.transition);
result->allowed_to_be_default_match = input.allowed_to_be_default_match;
result->type = AutocompleteMatchType::ToString(input.type);
result->is_search_type = AutocompleteMatch::IsSearchType(input.type);
result->has_tab_match = input.has_tab_match;
if (input.associated_keyword.get() != NULL) {
result->associated_keyword =
base::UTF16ToUTF8(input.associated_keyword->keyword);
}
result->keyword = base::UTF16ToUTF8(input.keyword);
result->duplicates = static_cast<int32_t>(input.duplicate_matches.size());
result->from_previous = input.from_previous;
result->additional_info =
mojo::ConvertTo<std::vector<mojom::AutocompleteAdditionalInfoPtr>>(
input.additional_info);
return result;
}
};
template <>
struct TypeConverter<mojom::AutocompleteResultsForProviderPtr,
scoped_refptr<AutocompleteProvider>> {
static mojom::AutocompleteResultsForProviderPtr Convert(
const scoped_refptr<AutocompleteProvider>& input) {
mojom::AutocompleteResultsForProviderPtr result(
mojom::AutocompleteResultsForProvider::New());
result->provider_name = input->GetName();
result->results = mojo::ConvertTo<std::vector<mojom::AutocompleteMatchPtr>>(
input->matches());
return result;
}
};
} // namespace mojo
OmniboxPageHandler::OmniboxPageHandler(
Profile* profile,
mojo::InterfaceRequest<mojom::OmniboxPageHandler> request)
: profile_(profile), binding_(this, std::move(request)), observer_(this) {
observer_.Add(OmniboxControllerEmitter::GetForBrowserContext(profile_));
ResetController();
}
OmniboxPageHandler::~OmniboxPageHandler() {}
void OmniboxPageHandler::OnResultChanged(bool default_match_changed) {
OnOmniboxResultChanged(default_match_changed, controller_.get());
}
void OmniboxPageHandler::OnOmniboxQuery(AutocompleteController* controller) {
page_->HandleNewAutocompleteQuery(controller == controller_.get());
}
void OmniboxPageHandler::OnOmniboxResultChanged(
bool default_match_changed,
AutocompleteController* controller) {
mojom::OmniboxResultPtr result(mojom::OmniboxResult::New());
result->done = controller->done();
result->time_since_omnibox_started_ms =
(base::Time::Now() - time_omnibox_started_).InMilliseconds();
const base::string16 host =
input_.text().substr(input_.parts().host.begin, input_.parts().host.len);
result->host = base::UTF16ToUTF8(host);
result->type = AutocompleteInput::TypeToString(input_.type());
bool is_typed_host;
if (!LookupIsTypedHost(host, &is_typed_host))
is_typed_host = false;
result->is_typed_host = is_typed_host;
{
// Copy to an ACMatches to make conversion easier. Since this isn't
// performance critical we don't worry about the cost here.
ACMatches matches(controller->result().begin(), controller->result().end());
result->combined_results =
mojo::ConvertTo<std::vector<mojom::AutocompleteMatchPtr>>(matches);
}
result->results_by_provider =
mojo::ConvertTo<std::vector<mojom::AutocompleteResultsForProviderPtr>>(
controller->providers());
// Fill AutocompleteMatch::starred.
BookmarkModel* bookmark_model =
BookmarkModelFactory::GetForBrowserContext(profile_);
if (bookmark_model) {
for (size_t i = 0; i < result->combined_results.size(); ++i) {
result->combined_results[i]->starred = bookmark_model->IsBookmarked(
GURL(result->combined_results[i]->destination_url));
}
for (size_t i = 0; i < result->results_by_provider.size(); ++i) {
const mojom::AutocompleteResultsForProvider& result_by_provider =
*result->results_by_provider[i];
for (size_t j = 0; j < result_by_provider.results.size(); ++j) {
result_by_provider.results[j]->starred = bookmark_model->IsBookmarked(
GURL(result_by_provider.results[j]->destination_url));
}
}
}
// Obtain a vector of all image urls required.
std::vector<std::string> image_urls;
for (size_t i = 0; i < result->combined_results.size(); ++i)
image_urls.push_back(result->combined_results[i]->image);
for (size_t i = 0; i < result->results_by_provider.size(); ++i) {
const mojom::AutocompleteResultsForProvider& result_by_provider =
*result->results_by_provider[i];
for (size_t j = 0; j < result_by_provider.results.size(); ++j)
image_urls.push_back(result_by_provider.results[j]->image);
}
page_->HandleNewAutocompleteResult(std::move(result),
controller == controller_.get());
// Fill in image data
BitmapFetcherService* image_service =
BitmapFetcherServiceFactory::GetForBrowserContext(profile_);
if (!image_service)
return;
for (std::string image_url : image_urls) {
const ImageReciever handleAnswerImageData = [this,
image_url](std::string data) {
page_->HandleAnswerImageData(image_url, data);
};
if (!image_url.empty()) {
// TODO(jdonnelly, rhalavati, manukh): Create a helper function with
// Callback to create annotation and pass it to image_service, merging
// the annotations in omnibox_page_handler.cc, chrome_omnibox_client.cc,
// and chrome_autocomplete_provider_client.cc.
auto traffic_annotation = net::DefineNetworkTrafficAnnotation(
"omnibox_debug_results_change", R"(
semantics {
sender: "Omnibox"
description:
"Chromium provides answers in the suggestion list for "
"certain queries that user types in the omnibox. This request "
"retrieves a small image (for example, an icon illustrating "
"the current weather conditions) when this can add information "
"to an answer."
trigger:
"Change of results for the query typed by the user in the "
"omnibox."
data:
"The only data sent is the path to an image. No user data is "
"included, although some might be inferrable (e.g. whether the "
"weather is sunny or rainy in the user's current location) "
"from the name of the image in the path."
destination: WEBSITE
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting:
"You can enable or disable this feature via 'Use a prediction "
"service to help complete searches and URLs typed in the "
"address bar.' in Chromium's settings under Advanced. The "
"feature is enabled by default."
chrome_policy {
SearchSuggestEnabled {
policy_options {mode: MANDATORY}
SearchSuggestEnabled: false
}
}
})");
image_service->RequestImage(
GURL(image_url), new OmniboxPageImageObserver(handleAnswerImageData),
traffic_annotation);
}
}
}
bool OmniboxPageHandler::LookupIsTypedHost(const base::string16& host,
bool* is_typed_host) const {
history::HistoryService* const history_service =
HistoryServiceFactory::GetForProfile(profile_,
ServiceAccessType::EXPLICIT_ACCESS);
if (!history_service)
return false;
history::URLDatabase* url_db = history_service->InMemoryDatabase();
if (!url_db)
return false;
*is_typed_host =
url_db->IsTypedHost(base::UTF16ToUTF8(host), /*scheme=*/nullptr);
return true;
}
void OmniboxPageHandler::SetClientPage(mojom::OmniboxPagePtr page) {
page_ = std::move(page);
}
void OmniboxPageHandler::StartOmniboxQuery(const std::string& input_string,
bool reset_autocomplete_controller,
int32_t cursor_position,
bool zero_suggest,
bool prevent_inline_autocomplete,
bool prefer_keyword,
const std::string& current_url,
int32_t page_classification) {
// Reset the controller. If we don't do this, then the
// AutocompleteController might inappropriately set its |minimal_changes|
// variable (or something else) and some providers will short-circuit
// important logic and return stale results. In short, we want the
// actual results to not depend on the state of the previous request.
if (reset_autocomplete_controller)
ResetController();
// TODO (manukh): OmniboxPageHandler::StartOmniboxQuery is invoked only for
// queries from the debug page and not for queries from the browser omnibox.
// time_omnibox_started_ and input_ are therefore not set for browser omnibox
// queries, resulting in inaccurate time_since_omnibox_started_ms, host, type,
// and is_typed_host values in the result object being sent to the debug page.
// For the user, this means the 'details' section is mostly inaccurate for
// browser omnibox queries.
time_omnibox_started_ = base::Time::Now();
input_ = AutocompleteInput(
base::UTF8ToUTF16(input_string), cursor_position,
static_cast<metrics::OmniboxEventProto::PageClassification>(
page_classification),
ChromeAutocompleteSchemeClassifier(profile_));
GURL current_url_gurl{current_url};
if (current_url_gurl.is_valid())
input_.set_current_url(current_url_gurl);
input_.set_current_title(base::UTF8ToUTF16(current_url));
input_.set_prevent_inline_autocomplete(prevent_inline_autocomplete);
input_.set_prefer_keyword(prefer_keyword);
input_.set_from_omnibox_focus(zero_suggest);
OnOmniboxQuery(controller_.get());
controller_->Start(input_);
}
void OmniboxPageHandler::ResetController() {
controller_.reset(new AutocompleteController(
std::make_unique<ChromeAutocompleteProviderClient>(profile_), this,
AutocompleteClassifier::DefaultOmniboxProviders()));
}