blob: 29f3ae8edf4c27bb94cc9e811e9fb30a64be818a [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/extensions/api/omnibox/omnibox_api.h"
#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/metrics/histogram.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/extensions/event_router.h"
#include "chrome/browser/extensions/extension_prefs.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_system.h"
#include "chrome/browser/extensions/tab_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url.h"
#include "chrome/browser/ui/tab_contents/tab_contents.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/extensions/extension_constants.h"
#include "content/public/browser/notification_service.h"
namespace events {
const char kOnInputStarted[] = "omnibox.onInputStarted";
const char kOnInputChanged[] = "omnibox.onInputChanged";
const char kOnInputEntered[] = "omnibox.onInputEntered";
const char kOnInputCancelled[] = "omnibox.onInputCancelled";
} // namespace events
namespace extensions {
namespace {
const char kDescriptionStylesOrderError[] =
"Suggestion descriptionStyles must be in increasing non-overlapping order.";
const char kDescriptionStylesLengthError[] =
"Suggestion descriptionStyles contains an offset longer than the"
" description text";
const char kSuggestionContent[] = "content";
const char kSuggestionDescription[] = "description";
const char kSuggestionDescriptionStyles[] = "descriptionStyles";
const char kSuggestionDescriptionStylesRaw[] = "descriptionStylesRaw";
const char kDescriptionStylesType[] = "type";
const char kDescriptionStylesOffset[] = "offset";
const char kDescriptionStylesLength[] = "length";
} // namespace
// static
void ExtensionOmniboxEventRouter::OnInputStarted(
Profile* profile, const std::string& extension_id) {
profile->GetExtensionEventRouter()->DispatchEventToExtension(
extension_id, events::kOnInputStarted, "[]", profile, GURL());
}
// static
bool ExtensionOmniboxEventRouter::OnInputChanged(
Profile* profile, const std::string& extension_id,
const std::string& input, int suggest_id) {
if (!profile->GetExtensionEventRouter()->ExtensionHasEventListener(
extension_id, events::kOnInputChanged))
return false;
ListValue args;
args.Set(0, Value::CreateStringValue(input));
args.Set(1, Value::CreateIntegerValue(suggest_id));
std::string json_args;
base::JSONWriter::Write(&args, &json_args);
profile->GetExtensionEventRouter()->DispatchEventToExtension(
extension_id, events::kOnInputChanged, json_args, profile, GURL());
return true;
}
// static
void ExtensionOmniboxEventRouter::OnInputEntered(
TabContents* tab_contents,
const std::string& extension_id,
const std::string& input) {
Profile* profile = tab_contents->profile();
const Extension* extension =
ExtensionSystem::Get(profile)->extension_service()->extensions()->
GetByID(extension_id);
CHECK(extension);
tab_contents->extension_tab_helper()->active_tab_permission_manager()->
GrantIfRequested(extension);
ListValue args;
args.Set(0, Value::CreateStringValue(input));
std::string json_args;
base::JSONWriter::Write(&args, &json_args);
profile->GetExtensionEventRouter()->DispatchEventToExtension(
extension_id, events::kOnInputEntered, json_args, profile, GURL());
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_EXTENSION_OMNIBOX_INPUT_ENTERED,
content::Source<Profile>(profile),
content::NotificationService::NoDetails());
}
// static
void ExtensionOmniboxEventRouter::OnInputCancelled(
Profile* profile, const std::string& extension_id) {
profile->GetExtensionEventRouter()->DispatchEventToExtension(
extension_id, events::kOnInputCancelled, "[]", profile, GURL());
}
bool OmniboxSendSuggestionsFunction::RunImpl() {
ExtensionOmniboxSuggestions suggestions;
ListValue* suggestions_value;
EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &suggestions.request_id));
EXTENSION_FUNCTION_VALIDATE(args_->GetList(1, &suggestions_value));
suggestions.suggestions.resize(suggestions_value->GetSize());
for (size_t i = 0; i < suggestions_value->GetSize(); ++i) {
ExtensionOmniboxSuggestion& suggestion = suggestions.suggestions[i];
DictionaryValue* suggestion_value;
EXTENSION_FUNCTION_VALIDATE(suggestions_value->GetDictionary(
i, &suggestion_value));
EXTENSION_FUNCTION_VALIDATE(suggestion.Populate(*suggestion_value, true));
}
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_EXTENSION_OMNIBOX_SUGGESTIONS_READY,
content::Source<Profile>(profile_->GetOriginalProfile()),
content::Details<ExtensionOmniboxSuggestions>(&suggestions));
return true;
}
bool OmniboxSetDefaultSuggestionFunction::RunImpl() {
ExtensionOmniboxSuggestion suggestion;
DictionaryValue* suggestion_value;
EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &suggestion_value));
EXTENSION_FUNCTION_VALIDATE(suggestion.Populate(*suggestion_value, false));
ExtensionPrefs* prefs =
ExtensionSystem::Get(profile())->extension_service()->extension_prefs();
if (prefs)
prefs->SetOmniboxDefaultSuggestion(extension_id(), suggestion);
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED,
content::Source<Profile>(profile_->GetOriginalProfile()),
content::NotificationService::NoDetails());
return true;
}
ExtensionOmniboxSuggestion::ExtensionOmniboxSuggestion() {}
ExtensionOmniboxSuggestion::~ExtensionOmniboxSuggestion() {}
bool ExtensionOmniboxSuggestion::Populate(const base::DictionaryValue& value,
bool require_content) {
if (!value.GetString(kSuggestionContent, &content) && require_content)
return false;
if (!value.GetString(kSuggestionDescription, &description))
return false;
description_styles.clear();
if (value.HasKey(kSuggestionDescriptionStyles)) {
// This version comes from the extension.
const ListValue* styles = NULL;
if (!value.GetList(kSuggestionDescriptionStyles, &styles) ||
!ReadStylesFromValue(*styles)) {
return false;
}
} else if (value.HasKey(kSuggestionDescriptionStylesRaw)) {
// This version comes from ToValue(), which we use to persist to disk.
const ListValue* styles = NULL;
if (!value.GetList(kSuggestionDescriptionStylesRaw, &styles) ||
styles->empty()) {
return false;
}
for (size_t i = 0; i < styles->GetSize(); ++i) {
const base::DictionaryValue* style = NULL;
int offset, type;
if (!styles->GetDictionary(i, &style))
return false;
if (!style->GetInteger(kDescriptionStylesType, &type))
return false;
if (!style->GetInteger(kDescriptionStylesOffset, &offset))
return false;
description_styles.push_back(ACMatchClassification(offset, type));
}
} else {
description_styles.push_back(
ACMatchClassification(0, ACMatchClassification::NONE));
}
return true;
}
bool ExtensionOmniboxSuggestion::ReadStylesFromValue(
const ListValue& styles_value) {
description_styles.clear();
// Step 1: Build a vector of styles, 1 per character of description text.
std::vector<int> styles;
styles.resize(description.length()); // sets all styles to 0
for (size_t i = 0; i < styles_value.GetSize(); ++i) {
const DictionaryValue* style;
std::string type;
int offset;
int length;
if (!styles_value.GetDictionary(i, &style))
return false;
if (!style->GetString(kDescriptionStylesType, &type))
return false;
if (!style->GetInteger(kDescriptionStylesOffset, &offset))
return false;
if (!style->GetInteger(kDescriptionStylesLength, &length) || length < 0)
length = description.length();
if (offset < 0)
offset = std::max(0, static_cast<int>(description.length()) + offset);
int type_class =
(type == "url") ? ACMatchClassification::URL :
(type == "match") ? ACMatchClassification::MATCH :
(type == "dim") ? ACMatchClassification::DIM : -1;
if (type_class == -1)
return false;
for (int j = offset;
j < offset + length && j < static_cast<int>(styles.size()); ++j)
styles[j] |= type_class;
}
// Step 2: Convert the vector into continuous runs of common styles.
for (size_t i = 0; i < styles.size(); ++i) {
if (i == 0 || styles[i] != styles[i-1])
description_styles.push_back(ACMatchClassification(i, styles[i]));
}
return true;
}
scoped_ptr<base::DictionaryValue> ExtensionOmniboxSuggestion::ToValue() const {
scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
value->SetString(kSuggestionContent, content);
value->SetString(kSuggestionDescription, description);
if (description_styles.size() > 0) {
base::ListValue* styles_value = new base::ListValue();
for (size_t i = 0; i < description_styles.size(); ++i) {
base::DictionaryValue* style = new base::DictionaryValue();
style->SetInteger(kDescriptionStylesOffset, description_styles[i].offset);
style->SetInteger(kDescriptionStylesType, description_styles[i].style);
styles_value->Append(style);
}
value->Set(kSuggestionDescriptionStylesRaw, styles_value);
}
return value.Pass();
}
ExtensionOmniboxSuggestions::ExtensionOmniboxSuggestions() : request_id(0) {}
ExtensionOmniboxSuggestions::~ExtensionOmniboxSuggestions() {}
void ApplyDefaultSuggestionForExtensionKeyword(
Profile* profile,
const TemplateURL* keyword,
const string16& remaining_input,
AutocompleteMatch* match) {
DCHECK(keyword->IsExtensionKeyword());
ExtensionPrefs* prefs =
ExtensionSystem::Get(profile)->extension_service()->extension_prefs();
if (!prefs)
return;
ExtensionOmniboxSuggestion suggestion =
prefs->GetOmniboxDefaultSuggestion(keyword->GetExtensionId());
if (suggestion.description.empty())
return; // fall back to the universal default
const string16 kPlaceholderText(ASCIIToUTF16("%s"));
const string16 kReplacementText(ASCIIToUTF16("<input>"));
string16 description = suggestion.description;
ACMatchClassifications& description_styles = match->contents_class;
description_styles = suggestion.description_styles;
// Replace "%s" with the user's input and adjust the style offsets to the
// new length of the description.
size_t placeholder(suggestion.description.find(kPlaceholderText, 0));
if (placeholder != string16::npos) {
string16 replacement =
remaining_input.empty() ? kReplacementText : remaining_input;
description.replace(placeholder, kPlaceholderText.length(), replacement);
for (size_t i = 0; i < description_styles.size(); ++i) {
if (description_styles[i].offset > placeholder)
description_styles[i].offset += replacement.length() - 2;
}
}
match->contents.assign(description);
}
} // namespace extensions