// Copyright (c) 2011 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/extension_omnibox_api.h"
#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/extensions/extension_event_router.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url.h"
#include "content/common/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 {
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 kDescriptionStylesType[] = "type";
const char kDescriptionStylesOffset[] = "offset";
const char kDescriptionStylesLength[] = "length";
static base::LazyInstance<PropertyAccessor<ExtensionOmniboxSuggestion> >
g_extension_omnibox_suggestion_property_accessor(base::LINKER_INITIALIZED);
PropertyAccessor<ExtensionOmniboxSuggestion>& GetPropertyAccessor() {
return g_extension_omnibox_suggestion_property_accessor.Get();
}
// Returns the suggestion object set by the extension via the
// omnibox.setDefaultSuggestion call, or NULL if it was never set.
const ExtensionOmniboxSuggestion* GetDefaultSuggestionForExtension(
Profile* profile, const std::string& extension_id) {
const Extension* extension =
profile->GetExtensionService()->GetExtensionById(extension_id, false);
if (!extension)
return NULL;
return GetPropertyAccessor().GetProperty(
profile->GetExtensionService()->GetPropertyBag(extension));
}
}; // 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, false, &json_args);
profile->GetExtensionEventRouter()->DispatchEventToExtension(
extension_id, events::kOnInputChanged, json_args, profile, GURL());
return true;
}
// static
void ExtensionOmniboxEventRouter::OnInputEntered(
Profile* profile, const std::string& extension_id,
const std::string& input) {
ListValue args;
args.Set(0, Value::CreateStringValue(input));
std::string json_args;
base::JSONWriter::Write(&args, false, &json_args);
profile->GetExtensionEventRouter()->DispatchEventToExtension(
extension_id, events::kOnInputEntered, json_args, profile, GURL());
NotificationService::current()->Notify(
NotificationType::EXTENSION_OMNIBOX_INPUT_ENTERED,
Source<Profile>(profile), 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_value->GetString(
kSuggestionContent, &suggestion.content));
EXTENSION_FUNCTION_VALIDATE(suggestion_value->GetString(
kSuggestionDescription, &suggestion.description));
if (suggestion_value->HasKey(kSuggestionDescriptionStyles)) {
ListValue* styles;
EXTENSION_FUNCTION_VALIDATE(
suggestion_value->GetList(kSuggestionDescriptionStyles, &styles));
EXTENSION_FUNCTION_VALIDATE(suggestion.ReadStylesFromValue(*styles));
} else {
suggestion.description_styles.clear();
suggestion.description_styles.push_back(
ACMatchClassification(0, ACMatchClassification::NONE));
}
}
NotificationService::current()->Notify(
NotificationType::EXTENSION_OMNIBOX_SUGGESTIONS_READY,
Source<Profile>(profile_),
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_value->GetString(
kSuggestionDescription, &suggestion.description));
if (suggestion_value->HasKey(kSuggestionDescriptionStyles)) {
ListValue* styles;
EXTENSION_FUNCTION_VALIDATE(
suggestion_value->GetList(kSuggestionDescriptionStyles, &styles));
EXTENSION_FUNCTION_VALIDATE(suggestion.ReadStylesFromValue(*styles));
} else {
suggestion.description_styles.clear();
suggestion.description_styles.push_back(
ACMatchClassification(0, ACMatchClassification::NONE));
}
// Store the suggestion in the extension's runtime data.
GetPropertyAccessor().SetProperty(
profile_->GetExtensionService()->GetPropertyBag(GetExtension()),
suggestion);
NotificationService::current()->Notify(
NotificationType::EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED,
Source<Profile>(profile_),
NotificationService::NoDetails());
return true;
}
ExtensionOmniboxSuggestion::ExtensionOmniboxSuggestion() {}
ExtensionOmniboxSuggestion::~ExtensionOmniboxSuggestion() {}
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) {
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;
}
ExtensionOmniboxSuggestions::ExtensionOmniboxSuggestions() : request_id(0) {}
ExtensionOmniboxSuggestions::~ExtensionOmniboxSuggestions() {}
void ApplyDefaultSuggestionForExtensionKeyword(
Profile* profile,
const TemplateURL* keyword,
const string16& remaining_input,
AutocompleteMatch* match) {
DCHECK(keyword->IsExtensionKeyword());
const ExtensionOmniboxSuggestion* suggestion =
GetDefaultSuggestionForExtension(profile, keyword->GetExtensionId());
if (!suggestion)
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);
}