blob: d3b3af555b0e4874d807133626b1b1f4cdcbeace [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/bookmarks/bookmarks_api.h"
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/i18n/file_util_icu.h"
#include "base/i18n/time_formatting.h"
#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/memory/scoped_ptr.h"
#include "base/path_service.h"
#include "base/prefs/pref_service.h"
#include "base/sha1.h"
#include "base/stl_util.h"
#include "base/strings/string16.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "chrome/browser/bookmarks/bookmark_html_writer.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/bookmarks/bookmark_utils.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/api/bookmarks/bookmark_api_constants.h"
#include "chrome/browser/extensions/api/bookmarks/bookmark_api_helpers.h"
#include "chrome/browser/extensions/event_router.h"
#include "chrome/browser/extensions/extension_function_dispatcher.h"
#include "chrome/browser/extensions/extension_system.h"
#include "chrome/browser/extensions/extensions_quota_service.h"
#include "chrome/browser/importer/external_process_importer_host.h"
#include "chrome/browser/importer/importer_uma.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/chrome_select_file_policy.h"
#include "chrome/browser/ui/host_desktop.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/api/bookmarks.h"
#include "chrome/common/importer/importer_data_types.h"
#include "chrome/common/pref_names.h"
#include "components/user_prefs/user_prefs.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_view.h"
#include "grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
#if defined(OS_WIN) && defined(USE_AURA)
#include "ui/aura/remote_root_window_host_win.h"
#endif
namespace extensions {
namespace keys = bookmark_api_constants;
namespace bookmarks = api::bookmarks;
using base::TimeDelta;
using bookmarks::BookmarkTreeNode;
using content::BrowserThread;
using content::WebContents;
typedef QuotaLimitHeuristic::Bucket Bucket;
typedef QuotaLimitHeuristic::Config Config;
typedef QuotaLimitHeuristic::BucketList BucketList;
typedef ExtensionsQuotaService::TimedLimit TimedLimit;
typedef ExtensionsQuotaService::SustainedLimit SustainedLimit;
typedef QuotaLimitHeuristic::BucketMapper BucketMapper;
namespace {
// Generates a default path (including a default filename) that will be
// used for pre-populating the "Export Bookmarks" file chooser dialog box.
base::FilePath GetDefaultFilepathForBookmarkExport() {
base::Time time = base::Time::Now();
// Concatenate a date stamp to the filename.
#if defined(OS_POSIX)
base::FilePath::StringType filename =
l10n_util::GetStringFUTF8(IDS_EXPORT_BOOKMARKS_DEFAULT_FILENAME,
base::TimeFormatShortDateNumeric(time));
#elif defined(OS_WIN)
base::FilePath::StringType filename =
l10n_util::GetStringFUTF16(IDS_EXPORT_BOOKMARKS_DEFAULT_FILENAME,
base::TimeFormatShortDateNumeric(time));
#endif
file_util::ReplaceIllegalCharactersInPath(&filename, '_');
base::FilePath default_path;
PathService::Get(chrome::DIR_USER_DOCUMENTS, &default_path);
return default_path.Append(filename);
}
} // namespace
void BookmarksFunction::Run() {
BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
if (!model->loaded()) {
// Bookmarks are not ready yet. We'll wait.
model->AddObserver(this);
AddRef(); // Balanced in Loaded().
return;
}
bool success = RunImpl();
if (success) {
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_EXTENSION_BOOKMARKS_API_INVOKED,
content::Source<const Extension>(GetExtension()),
content::Details<const BookmarksFunction>(this));
}
SendResponse(success);
}
bool BookmarksFunction::GetBookmarkIdAsInt64(const std::string& id_string,
int64* id) {
if (base::StringToInt64(id_string, id))
return true;
error_ = keys::kInvalidIdError;
return false;
}
bool BookmarksFunction::EditBookmarksEnabled() {
PrefService* prefs = user_prefs::UserPrefs::Get(profile_);
if (prefs->GetBoolean(prefs::kEditBookmarksEnabled))
return true;
error_ = keys::kEditBookmarksDisabled;
return false;
}
void BookmarksFunction::BookmarkModelChanged() {
}
void BookmarksFunction::Loaded(BookmarkModel* model, bool ids_reassigned) {
model->RemoveObserver(this);
Run();
Release(); // Balanced in Run().
}
BookmarkEventRouter::BookmarkEventRouter(Profile* profile,
BookmarkModel* model)
: profile_(profile),
model_(model) {
model_->AddObserver(this);
}
BookmarkEventRouter::~BookmarkEventRouter() {
if (model_) {
model_->RemoveObserver(this);
}
}
void BookmarkEventRouter::DispatchEvent(
const std::string& event_name,
scoped_ptr<base::ListValue> event_args) {
if (extensions::ExtensionSystem::Get(profile_)->event_router()) {
extensions::ExtensionSystem::Get(profile_)->event_router()->BroadcastEvent(
make_scoped_ptr(new extensions::Event(event_name, event_args.Pass())));
}
}
void BookmarkEventRouter::Loaded(BookmarkModel* model, bool ids_reassigned) {
// TODO(erikkay): Perhaps we should send this event down to the extension
// so they know when it's safe to use the API?
}
void BookmarkEventRouter::BookmarkModelBeingDeleted(BookmarkModel* model) {
model_ = NULL;
}
void BookmarkEventRouter::BookmarkNodeMoved(BookmarkModel* model,
const BookmarkNode* old_parent,
int old_index,
const BookmarkNode* new_parent,
int new_index) {
scoped_ptr<base::ListValue> args(new base::ListValue());
const BookmarkNode* node = new_parent->GetChild(new_index);
args->Append(new base::StringValue(base::Int64ToString(node->id())));
base::DictionaryValue* object_args = new base::DictionaryValue();
object_args->SetString(keys::kParentIdKey,
base::Int64ToString(new_parent->id()));
object_args->SetInteger(keys::kIndexKey, new_index);
object_args->SetString(keys::kOldParentIdKey,
base::Int64ToString(old_parent->id()));
object_args->SetInteger(keys::kOldIndexKey, old_index);
args->Append(object_args);
DispatchEvent(bookmarks::OnMoved::kEventName, args.Pass());
}
void BookmarkEventRouter::BookmarkNodeAdded(BookmarkModel* model,
const BookmarkNode* parent,
int index) {
scoped_ptr<base::ListValue> args(new base::ListValue());
const BookmarkNode* node = parent->GetChild(index);
args->Append(new base::StringValue(base::Int64ToString(node->id())));
scoped_ptr<BookmarkTreeNode> tree_node(
bookmark_api_helpers::GetBookmarkTreeNode(node, false, false));
args->Append(tree_node->ToValue().release());
DispatchEvent(bookmarks::OnCreated::kEventName, args.Pass());
}
void BookmarkEventRouter::BookmarkNodeRemoved(BookmarkModel* model,
const BookmarkNode* parent,
int index,
const BookmarkNode* node) {
scoped_ptr<base::ListValue> args(new base::ListValue());
args->Append(new base::StringValue(base::Int64ToString(node->id())));
base::DictionaryValue* object_args = new base::DictionaryValue();
object_args->SetString(keys::kParentIdKey,
base::Int64ToString(parent->id()));
object_args->SetInteger(keys::kIndexKey, index);
args->Append(object_args);
DispatchEvent(bookmarks::OnRemoved::kEventName, args.Pass());
}
void BookmarkEventRouter::BookmarkAllNodesRemoved(BookmarkModel* model) {
NOTREACHED();
// TODO(shashishekhar) Currently this notification is only used on Android,
// which does not support extensions. If Desktop needs to support this, add
// a new event to the extensions api.
}
void BookmarkEventRouter::BookmarkNodeChanged(BookmarkModel* model,
const BookmarkNode* node) {
scoped_ptr<base::ListValue> args(new base::ListValue());
args->Append(new base::StringValue(base::Int64ToString(node->id())));
// TODO(erikkay) The only three things that BookmarkModel sends this
// notification for are title, url and favicon. Since we're currently
// ignoring favicon and since the notification doesn't say which one anyway,
// for now we only include title and url. The ideal thing would be to change
// BookmarkModel to indicate what changed.
base::DictionaryValue* object_args = new base::DictionaryValue();
object_args->SetString(keys::kTitleKey, node->GetTitle());
if (node->is_url())
object_args->SetString(keys::kUrlKey, node->url().spec());
args->Append(object_args);
DispatchEvent(bookmarks::OnChanged::kEventName, args.Pass());
}
void BookmarkEventRouter::BookmarkNodeFaviconChanged(BookmarkModel* model,
const BookmarkNode* node) {
// TODO(erikkay) anything we should do here?
}
void BookmarkEventRouter::BookmarkNodeChildrenReordered(
BookmarkModel* model,
const BookmarkNode* node) {
scoped_ptr<base::ListValue> args(new base::ListValue());
args->Append(new base::StringValue(base::Int64ToString(node->id())));
int childCount = node->child_count();
base::ListValue* children = new base::ListValue();
for (int i = 0; i < childCount; ++i) {
const BookmarkNode* child = node->GetChild(i);
base::Value* child_id =
new base::StringValue(base::Int64ToString(child->id()));
children->Append(child_id);
}
base::DictionaryValue* reorder_info = new base::DictionaryValue();
reorder_info->Set(keys::kChildIdsKey, children);
args->Append(reorder_info);
DispatchEvent(bookmarks::OnChildrenReordered::kEventName, args.Pass());
}
void BookmarkEventRouter::ExtensiveBookmarkChangesBeginning(
BookmarkModel* model) {
scoped_ptr<base::ListValue> args(new base::ListValue());
DispatchEvent(bookmarks::OnImportBegan::kEventName, args.Pass());
}
void BookmarkEventRouter::ExtensiveBookmarkChangesEnded(BookmarkModel* model) {
scoped_ptr<base::ListValue> args(new base::ListValue());
DispatchEvent(bookmarks::OnImportEnded::kEventName, args.Pass());
}
BookmarksAPI::BookmarksAPI(Profile* profile) : profile_(profile) {
ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
this, bookmarks::OnCreated::kEventName);
ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
this, bookmarks::OnRemoved::kEventName);
ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
this, bookmarks::OnChanged::kEventName);
ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
this, bookmarks::OnMoved::kEventName);
ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
this, bookmarks::OnChildrenReordered::kEventName);
ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
this, bookmarks::OnImportBegan::kEventName);
ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
this, bookmarks::OnImportEnded::kEventName);
}
BookmarksAPI::~BookmarksAPI() {
}
void BookmarksAPI::Shutdown() {
ExtensionSystem::Get(profile_)->event_router()->UnregisterObserver(this);
}
static base::LazyInstance<ProfileKeyedAPIFactory<BookmarksAPI> >
g_factory = LAZY_INSTANCE_INITIALIZER;
// static
ProfileKeyedAPIFactory<BookmarksAPI>* BookmarksAPI::GetFactoryInstance() {
return &g_factory.Get();
}
void BookmarksAPI::OnListenerAdded(const EventListenerInfo& details) {
bookmark_event_router_.reset(new BookmarkEventRouter(profile_,
BookmarkModelFactory::GetForProfile(profile_)));
ExtensionSystem::Get(profile_)->event_router()->UnregisterObserver(this);
}
bool BookmarksGetFunction::RunImpl() {
scoped_ptr<bookmarks::Get::Params> params(
bookmarks::Get::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
std::vector<linked_ptr<BookmarkTreeNode> > nodes;
BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
if (params->id_or_id_list.as_strings) {
std::vector<std::string>& ids = *params->id_or_id_list.as_strings;
size_t count = ids.size();
EXTENSION_FUNCTION_VALIDATE(count > 0);
for (size_t i = 0; i < count; ++i) {
int64 id;
if (!GetBookmarkIdAsInt64(ids[i], &id))
return false;
const BookmarkNode* node = model->GetNodeByID(id);
if (!node) {
error_ = keys::kNoNodeError;
return false;
} else {
bookmark_api_helpers::AddNode(node, &nodes, false);
}
}
} else {
int64 id;
if (!GetBookmarkIdAsInt64(*params->id_or_id_list.as_string, &id))
return false;
const BookmarkNode* node = model->GetNodeByID(id);
if (!node) {
error_ = keys::kNoNodeError;
return false;
}
bookmark_api_helpers::AddNode(node, &nodes, false);
}
results_ = bookmarks::Get::Results::Create(nodes);
return true;
}
bool BookmarksGetChildrenFunction::RunImpl() {
scoped_ptr<bookmarks::GetChildren::Params> params(
bookmarks::GetChildren::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
int64 id;
if (!GetBookmarkIdAsInt64(params->id, &id))
return false;
std::vector<linked_ptr<BookmarkTreeNode> > nodes;
const BookmarkNode* node =
BookmarkModelFactory::GetForProfile(profile())->GetNodeByID(id);
if (!node) {
error_ = keys::kNoNodeError;
return false;
}
int child_count = node->child_count();
for (int i = 0; i < child_count; ++i) {
const BookmarkNode* child = node->GetChild(i);
bookmark_api_helpers::AddNode(child, &nodes, false);
}
results_ = bookmarks::GetChildren::Results::Create(nodes);
return true;
}
bool BookmarksGetRecentFunction::RunImpl() {
scoped_ptr<bookmarks::GetRecent::Params> params(
bookmarks::GetRecent::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
if (params->number_of_items < 1)
return false;
std::vector<const BookmarkNode*> nodes;
bookmark_utils::GetMostRecentlyAddedEntries(
BookmarkModelFactory::GetForProfile(profile()),
params->number_of_items,
&nodes);
std::vector<linked_ptr<BookmarkTreeNode> > tree_nodes;
std::vector<const BookmarkNode*>::iterator i = nodes.begin();
for (; i != nodes.end(); ++i) {
const BookmarkNode* node = *i;
bookmark_api_helpers::AddNode(node, &tree_nodes, false);
}
results_ = bookmarks::GetRecent::Results::Create(tree_nodes);
return true;
}
bool BookmarksGetTreeFunction::RunImpl() {
std::vector<linked_ptr<BookmarkTreeNode> > nodes;
const BookmarkNode* node =
BookmarkModelFactory::GetForProfile(profile())->root_node();
bookmark_api_helpers::AddNode(node, &nodes, true);
results_ = bookmarks::GetTree::Results::Create(nodes);
return true;
}
bool BookmarksGetSubTreeFunction::RunImpl() {
scoped_ptr<bookmarks::GetSubTree::Params> params(
bookmarks::GetSubTree::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
int64 id;
if (!GetBookmarkIdAsInt64(params->id, &id))
return false;
const BookmarkNode* node =
BookmarkModelFactory::GetForProfile(profile())->GetNodeByID(id);
if (!node) {
error_ = keys::kNoNodeError;
return false;
}
std::vector<linked_ptr<BookmarkTreeNode> > nodes;
bookmark_api_helpers::AddNode(node, &nodes, true);
results_ = bookmarks::GetSubTree::Results::Create(nodes);
return true;
}
bool BookmarksSearchFunction::RunImpl() {
scoped_ptr<bookmarks::Search::Params> params(
bookmarks::Search::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
PrefService* prefs = user_prefs::UserPrefs::Get(profile_);
std::string lang = prefs->GetString(prefs::kAcceptLanguages);
std::vector<const BookmarkNode*> nodes;
bookmark_utils::GetBookmarksContainingText(
BookmarkModelFactory::GetForProfile(profile()),
UTF8ToUTF16(params->query),
std::numeric_limits<int>::max(),
lang,
&nodes);
std::vector<linked_ptr<BookmarkTreeNode> > tree_nodes;
for (std::vector<const BookmarkNode*>::iterator node_iter = nodes.begin();
node_iter != nodes.end(); ++node_iter) {
bookmark_api_helpers::AddNode(*node_iter, &tree_nodes, false);
}
results_ = bookmarks::Search::Results::Create(tree_nodes);
return true;
}
// static
bool BookmarksRemoveFunction::ExtractIds(const base::ListValue* args,
std::list<int64>* ids,
bool* invalid_id) {
std::string id_string;
if (!args->GetString(0, &id_string))
return false;
int64 id;
if (base::StringToInt64(id_string, &id))
ids->push_back(id);
else
*invalid_id = true;
return true;
}
bool BookmarksRemoveFunction::RunImpl() {
if (!EditBookmarksEnabled())
return false;
scoped_ptr<bookmarks::Remove::Params> params(
bookmarks::Remove::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
int64 id;
if (!base::StringToInt64(params->id, &id)) {
error_ = keys::kInvalidIdError;
return false;
}
bool recursive = false;
if (name() == BookmarksRemoveTreeFunction::function_name())
recursive = true;
BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
if (!bookmark_api_helpers::RemoveNode(model, id, recursive, &error_))
return false;
return true;
}
bool BookmarksCreateFunction::RunImpl() {
if (!EditBookmarksEnabled())
return false;
scoped_ptr<bookmarks::Create::Params> params(
bookmarks::Create::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
int64 parentId;
if (!params->bookmark.parent_id.get()) {
// Optional, default to "other bookmarks".
parentId = model->other_node()->id();
} else {
if (!GetBookmarkIdAsInt64(*params->bookmark.parent_id, &parentId))
return false;
}
const BookmarkNode* parent = model->GetNodeByID(parentId);
if (!parent) {
error_ = keys::kNoParentError;
return false;
}
if (parent->is_root()) { // Can't create children of the root.
error_ = keys::kModifySpecialError;
return false;
}
int index;
if (!params->bookmark.index.get()) { // Optional (defaults to end).
index = parent->child_count();
} else {
index = *params->bookmark.index;
if (index > parent->child_count() || index < 0) {
error_ = keys::kInvalidIndexError;
return false;
}
}
string16 title; // Optional.
if (params->bookmark.title.get())
title = UTF8ToUTF16(*params->bookmark.title.get());
std::string url_string; // Optional.
if (params->bookmark.url.get())
url_string = *params->bookmark.url.get();
GURL url(url_string);
if (!url_string.empty() && !url.is_valid()) {
error_ = keys::kInvalidUrlError;
return false;
}
const BookmarkNode* node;
if (url_string.length())
node = model->AddURL(parent, index, title, url);
else
node = model->AddFolder(parent, index, title);
DCHECK(node);
if (!node) {
error_ = keys::kNoNodeError;
return false;
}
scoped_ptr<BookmarkTreeNode> ret(
bookmark_api_helpers::GetBookmarkTreeNode(node, false, false));
results_ = bookmarks::Create::Results::Create(*ret);
return true;
}
// static
bool BookmarksMoveFunction::ExtractIds(const base::ListValue* args,
std::list<int64>* ids,
bool* invalid_id) {
// For now, Move accepts ID parameters in the same way as an Update.
return BookmarksUpdateFunction::ExtractIds(args, ids, invalid_id);
}
bool BookmarksMoveFunction::RunImpl() {
if (!EditBookmarksEnabled())
return false;
scoped_ptr<bookmarks::Move::Params> params(
bookmarks::Move::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
int64 id;
if (!base::StringToInt64(params->id, &id)) {
error_ = keys::kInvalidIdError;
return false;
}
BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
const BookmarkNode* node = model->GetNodeByID(id);
if (!node) {
error_ = keys::kNoNodeError;
return false;
}
if (model->is_permanent_node(node)) {
error_ = keys::kModifySpecialError;
return false;
}
const BookmarkNode* parent = NULL;
if (!params->destination.parent_id.get()) {
// Optional, defaults to current parent.
parent = node->parent();
} else {
int64 parentId;
if (!GetBookmarkIdAsInt64(*params->destination.parent_id, &parentId))
return false;
parent = model->GetNodeByID(parentId);
}
if (!parent) {
error_ = keys::kNoParentError;
// TODO(erikkay) return an error message.
return false;
}
if (parent == model->root_node()) {
error_ = keys::kModifySpecialError;
return false;
}
int index;
if (params->destination.index.get()) { // Optional (defaults to end).
index = *params->destination.index;
if (index > parent->child_count() || index < 0) {
error_ = keys::kInvalidIndexError;
return false;
}
} else {
index = parent->child_count();
}
model->Move(node, parent, index);
scoped_ptr<BookmarkTreeNode> tree_node(
bookmark_api_helpers::GetBookmarkTreeNode(node, false, false));
results_ = bookmarks::Move::Results::Create(*tree_node);
return true;
}
// static
bool BookmarksUpdateFunction::ExtractIds(const base::ListValue* args,
std::list<int64>* ids,
bool* invalid_id) {
// For now, Update accepts ID parameters in the same way as an Remove.
return BookmarksRemoveFunction::ExtractIds(args, ids, invalid_id);
}
bool BookmarksUpdateFunction::RunImpl() {
if (!EditBookmarksEnabled())
return false;
scoped_ptr<bookmarks::Update::Params> params(
bookmarks::Update::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
int64 id;
if (!base::StringToInt64(params->id, &id)) {
error_ = keys::kInvalidIdError;
return false;
}
BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
// Optional but we need to distinguish non present from an empty title.
string16 title;
bool has_title = false;
if (params->changes.title.get()) {
title = UTF8ToUTF16(*params->changes.title);
has_title = true;
}
// Optional.
std::string url_string;
if (params->changes.url.get())
url_string = *params->changes.url;
GURL url(url_string);
if (!url_string.empty() && !url.is_valid()) {
error_ = keys::kInvalidUrlError;
return false;
}
const BookmarkNode* node = model->GetNodeByID(id);
if (!node) {
error_ = keys::kNoNodeError;
return false;
}
if (model->is_permanent_node(node)) {
error_ = keys::kModifySpecialError;
return false;
}
if (has_title)
model->SetTitle(node, title);
if (!url.is_empty())
model->SetURL(node, url);
scoped_ptr<BookmarkTreeNode> tree_node(
bookmark_api_helpers::GetBookmarkTreeNode(node, false, false));
results_ = bookmarks::Update::Results::Create(*tree_node);
return true;
}
// Mapper superclass for BookmarkFunctions.
template <typename BucketIdType>
class BookmarkBucketMapper : public BucketMapper {
public:
virtual ~BookmarkBucketMapper() { STLDeleteValues(&buckets_); }
protected:
Bucket* GetBucket(const BucketIdType& id) {
Bucket* b = buckets_[id];
if (b == NULL) {
b = new Bucket();
buckets_[id] = b;
}
return b;
}
private:
std::map<BucketIdType, Bucket*> buckets_;
};
// Mapper for 'bookmarks.create'. Maps "same input to bookmarks.create" to a
// unique bucket.
class CreateBookmarkBucketMapper : public BookmarkBucketMapper<std::string> {
public:
explicit CreateBookmarkBucketMapper(Profile* profile) : profile_(profile) {}
// TODO(tim): This should share code with BookmarksCreateFunction::RunImpl,
// but I can't figure out a good way to do that with all the macros.
virtual void GetBucketsForArgs(const base::ListValue* args,
BucketList* buckets) OVERRIDE {
const base::DictionaryValue* json;
if (!args->GetDictionary(0, &json))
return;
std::string parent_id;
if (json->HasKey(keys::kParentIdKey)) {
if (!json->GetString(keys::kParentIdKey, &parent_id))
return;
}
BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
int64 parent_id_int64;
base::StringToInt64(parent_id, &parent_id_int64);
const BookmarkNode* parent = model->GetNodeByID(parent_id_int64);
if (!parent)
return;
std::string bucket_id = UTF16ToUTF8(parent->GetTitle());
std::string title;
json->GetString(keys::kTitleKey, &title);
std::string url_string;
json->GetString(keys::kUrlKey, &url_string);
bucket_id += title;
bucket_id += url_string;
// 20 bytes (SHA1 hash length) is very likely less than most of the
// |bucket_id| strings we construct here, so we hash it to save space.
buckets->push_back(GetBucket(base::SHA1HashString(bucket_id)));
}
private:
Profile* profile_;
};
// Mapper for 'bookmarks.remove'.
class RemoveBookmarksBucketMapper : public BookmarkBucketMapper<std::string> {
public:
explicit RemoveBookmarksBucketMapper(Profile* profile) : profile_(profile) {}
virtual void GetBucketsForArgs(const base::ListValue* args,
BucketList* buckets) OVERRIDE {
typedef std::list<int64> IdList;
IdList ids;
bool invalid_id = false;
if (!BookmarksRemoveFunction::ExtractIds(args, &ids, &invalid_id) ||
invalid_id) {
return;
}
for (IdList::iterator it = ids.begin(); it != ids.end(); ++it) {
BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
const BookmarkNode* node = model->GetNodeByID(*it);
if (!node || node->is_root())
return;
std::string bucket_id;
bucket_id += UTF16ToUTF8(node->parent()->GetTitle());
bucket_id += UTF16ToUTF8(node->GetTitle());
bucket_id += node->url().spec();
buckets->push_back(GetBucket(base::SHA1HashString(bucket_id)));
}
}
private:
Profile* profile_;
};
// Mapper for any bookmark function accepting bookmark IDs as parameters, where
// a distinct ID corresponds to a single item in terms of quota limiting. This
// is inappropriate for bookmarks.remove, for example, since repeated removals
// of the same item will actually have a different ID each time.
template <class FunctionType>
class BookmarkIdMapper : public BookmarkBucketMapper<int64> {
public:
typedef std::list<int64> IdList;
virtual void GetBucketsForArgs(const base::ListValue* args,
BucketList* buckets) {
IdList ids;
bool invalid_id = false;
if (!FunctionType::ExtractIds(args, &ids, &invalid_id) || invalid_id)
return;
for (IdList::iterator it = ids.begin(); it != ids.end(); ++it)
buckets->push_back(GetBucket(*it));
}
};
// Builds heuristics for all BookmarkFunctions using specialized BucketMappers.
class BookmarksQuotaLimitFactory {
public:
// For id-based bookmark functions.
template <class FunctionType>
static void Build(QuotaLimitHeuristics* heuristics) {
BuildWithMappers(heuristics, new BookmarkIdMapper<FunctionType>(),
new BookmarkIdMapper<FunctionType>());
}
// For bookmarks.create.
static void BuildForCreate(QuotaLimitHeuristics* heuristics,
Profile* profile) {
BuildWithMappers(heuristics, new CreateBookmarkBucketMapper(profile),
new CreateBookmarkBucketMapper(profile));
}
// For bookmarks.remove.
static void BuildForRemove(QuotaLimitHeuristics* heuristics,
Profile* profile) {
BuildWithMappers(heuristics, new RemoveBookmarksBucketMapper(profile),
new RemoveBookmarksBucketMapper(profile));
}
private:
static void BuildWithMappers(QuotaLimitHeuristics* heuristics,
BucketMapper* short_mapper, BucketMapper* long_mapper) {
const Config kSustainedLimitConfig = {
// See bookmarks.json for current value.
bookmarks::MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE,
TimeDelta::FromMinutes(1)
};
heuristics->push_back(new SustainedLimit(
TimeDelta::FromMinutes(10),
kSustainedLimitConfig,
short_mapper,
"MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE"));
const Config kTimedLimitConfig = {
// See bookmarks.json for current value.
bookmarks::MAX_WRITE_OPERATIONS_PER_HOUR,
TimeDelta::FromHours(1)
};
heuristics->push_back(new TimedLimit(
kTimedLimitConfig,
long_mapper,
"MAX_WRITE_OPERATIONS_PER_HOUR"));
}
DISALLOW_IMPLICIT_CONSTRUCTORS(BookmarksQuotaLimitFactory);
};
// And finally, building the individual heuristics for each function.
void BookmarksRemoveFunction::GetQuotaLimitHeuristics(
QuotaLimitHeuristics* heuristics) const {
BookmarksQuotaLimitFactory::BuildForRemove(heuristics, profile());
}
void BookmarksMoveFunction::GetQuotaLimitHeuristics(
QuotaLimitHeuristics* heuristics) const {
BookmarksQuotaLimitFactory::Build<BookmarksMoveFunction>(heuristics);
}
void BookmarksUpdateFunction::GetQuotaLimitHeuristics(
QuotaLimitHeuristics* heuristics) const {
BookmarksQuotaLimitFactory::Build<BookmarksUpdateFunction>(heuristics);
};
void BookmarksCreateFunction::GetQuotaLimitHeuristics(
QuotaLimitHeuristics* heuristics) const {
BookmarksQuotaLimitFactory::BuildForCreate(heuristics, profile());
}
BookmarksIOFunction::BookmarksIOFunction() {}
BookmarksIOFunction::~BookmarksIOFunction() {
// There may be pending file dialogs, we need to tell them that we've gone
// away so they don't try and call back to us.
if (select_file_dialog_.get())
select_file_dialog_->ListenerDestroyed();
}
void BookmarksIOFunction::SelectFile(ui::SelectFileDialog::Type type) {
// GetDefaultFilepathForBookmarkExport() might have to touch the filesystem
// (stat or access, for example), so this requires a thread with IO allowed.
if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) {
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
base::Bind(&BookmarksIOFunction::SelectFile, this, type));
return;
}
// Pre-populating the filename field in case this is a SELECT_SAVEAS_FILE
// dialog. If not, there is no filename field in the dialog box.
base::FilePath default_path;
if (type == ui::SelectFileDialog::SELECT_SAVEAS_FILE)
default_path = GetDefaultFilepathForBookmarkExport();
else
DCHECK(type == ui::SelectFileDialog::SELECT_OPEN_FILE);
// After getting the |default_path|, ask the UI to display the file dialog.
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&BookmarksIOFunction::ShowSelectFileDialog, this,
type, default_path));
}
void BookmarksIOFunction::ShowSelectFileDialog(
ui::SelectFileDialog::Type type,
const base::FilePath& default_path) {
if (!dispatcher())
return; // Extension was unloaded.
// Balanced in one of the three callbacks of SelectFileDialog:
// either FileSelectionCanceled, MultiFilesSelected, or FileSelected
AddRef();
WebContents* web_contents = dispatcher()->delegate()->
GetAssociatedWebContents();
select_file_dialog_ = ui::SelectFileDialog::Create(
this, new ChromeSelectFilePolicy(web_contents));
ui::SelectFileDialog::FileTypeInfo file_type_info;
file_type_info.extensions.resize(1);
file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("html"));
gfx::NativeWindow owning_window = web_contents ?
platform_util::GetTopLevel(web_contents->GetView()->GetNativeView())
: NULL;
#if defined(OS_WIN) && defined(USE_AURA)
if (!owning_window &&
chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH)
owning_window = aura::RemoteRootWindowHostWin::Instance()->GetAshWindow();
#endif
// |web_contents| can be NULL (for background pages), which is fine. In such
// a case if file-selection dialogs are forbidden by policy, we will not
// show an InfoBar, which is better than letting one appear out of the blue.
select_file_dialog_->SelectFile(type,
string16(),
default_path,
&file_type_info,
0,
base::FilePath::StringType(),
owning_window,
NULL);
}
void BookmarksIOFunction::FileSelectionCanceled(void* params) {
Release(); // Balanced in BookmarksIOFunction::SelectFile()
}
void BookmarksIOFunction::MultiFilesSelected(
const std::vector<base::FilePath>& files, void* params) {
Release(); // Balanced in BookmarsIOFunction::SelectFile()
NOTREACHED() << "Should not be able to select multiple files";
}
bool BookmarksImportFunction::RunImpl() {
if (!EditBookmarksEnabled())
return false;
SelectFile(ui::SelectFileDialog::SELECT_OPEN_FILE);
return true;
}
void BookmarksImportFunction::FileSelected(const base::FilePath& path,
int index,
void* params) {
#if !defined(OS_ANDROID)
// Android does not have support for the standard importers.
// TODO(jgreenwald): remove ifdef once extensions are no longer built on
// Android.
// Deletes itself.
ExternalProcessImporterHost* importer_host = new ExternalProcessImporterHost;
importer::SourceProfile source_profile;
source_profile.importer_type = importer::TYPE_BOOKMARKS_FILE;
source_profile.source_path = path;
importer_host->StartImportSettings(source_profile,
profile(),
importer::FAVORITES,
new ProfileWriter(profile()));
importer::LogImporterUseToMetrics("BookmarksAPI",
importer::TYPE_BOOKMARKS_FILE);
#endif
Release(); // Balanced in BookmarksIOFunction::SelectFile()
}
bool BookmarksExportFunction::RunImpl() {
SelectFile(ui::SelectFileDialog::SELECT_SAVEAS_FILE);
return true;
}
void BookmarksExportFunction::FileSelected(const base::FilePath& path,
int index,
void* params) {
#if !defined(OS_ANDROID)
// Android does not have support for the standard exporter.
// TODO(jgreenwald): remove ifdef once extensions are no longer built on
// Android.
bookmark_html_writer::WriteBookmarks(profile(), path, NULL);
#endif
Release(); // Balanced in BookmarksIOFunction::SelectFile()
}
} // namespace extensions