// 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/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/browser_context.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_view.h" #include "extensions/browser/event_router.h" #include "extensions/browser/extension_function_dispatcher.h" #include "extensions/browser/extension_system.h" #include "extensions/browser/quota_service.h" #include "grit/generated_resources.h" #include "ui/base/l10n/l10n_util.h" #if defined(OS_WIN) #include "ui/aura/remote_window_tree_host_win.h" #endif namespace extensions { namespace keys = bookmark_api_constants; namespace bookmarks = api::bookmarks; using base::TimeDelta; using bookmarks::BookmarkTreeNode; using content::BrowserContext; using content::BrowserThread; using content::WebContents; typedef QuotaLimitHeuristic::Bucket Bucket; typedef QuotaLimitHeuristic::Config Config; typedef QuotaLimitHeuristic::BucketList BucketList; typedef QuotaService::TimedLimit TimedLimit; typedef QuotaService::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(GetProfile()); 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(GetExtension()), content::Details(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; } const BookmarkNode* BookmarksFunction::GetBookmarkNodeFromId( const std::string& id_string) { int64 id; if (!GetBookmarkIdAsInt64(id_string, &id)) return NULL; BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile()); const BookmarkNode* node = model->GetNodeByID(id); if (!node) error_ = keys::kNoNodeError; return node; } bool BookmarksFunction::EditBookmarksEnabled() { PrefService* prefs = user_prefs::UserPrefs::Get(GetProfile()); if (prefs->GetBoolean(prefs::kEditBookmarksEnabled)) return true; error_ = keys::kEditBookmarksDisabled; return false; } void BookmarksFunction::BookmarkModelChanged() { } void BookmarksFunction::BookmarkModelLoaded(BookmarkModel* model, bool ids_reassigned) { model->RemoveObserver(this); Run(); Release(); // Balanced in Run(). } BookmarkEventRouter::BookmarkEventRouter(BrowserContext* context, BookmarkModel* model) : browser_context_(context), model_(model) { model_->AddObserver(this); } BookmarkEventRouter::~BookmarkEventRouter() { if (model_) { model_->RemoveObserver(this); } } void BookmarkEventRouter::DispatchEvent( const std::string& event_name, scoped_ptr event_args) { if (extensions::ExtensionSystem::Get(browser_context_)->event_router()) { extensions::ExtensionSystem::Get(browser_context_) ->event_router() ->BroadcastEvent(make_scoped_ptr( new extensions::Event(event_name, event_args.Pass()))); } } void BookmarkEventRouter::BookmarkModelLoaded(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) { const BookmarkNode* node = new_parent->GetChild(new_index); bookmarks::OnMoved::MoveInfo move_info; move_info.parent_id = base::Int64ToString(new_parent->id()); move_info.index = new_index; move_info.old_parent_id = base::Int64ToString(old_parent->id()); move_info.old_index = old_index; DispatchEvent( bookmarks::OnMoved::kEventName, bookmarks::OnMoved::Create(base::Int64ToString(node->id()), move_info)); } void BookmarkEventRouter::BookmarkNodeAdded(BookmarkModel* model, const BookmarkNode* parent, int index) { const BookmarkNode* node = parent->GetChild(index); scoped_ptr tree_node( bookmark_api_helpers::GetBookmarkTreeNode(node, false, false)); DispatchEvent(bookmarks::OnCreated::kEventName, bookmarks::OnCreated::Create(base::Int64ToString(node->id()), *tree_node)); } void BookmarkEventRouter::BookmarkNodeRemoved(BookmarkModel* model, const BookmarkNode* parent, int index, const BookmarkNode* node) { bookmarks::OnRemoved::RemoveInfo remove_info; remove_info.parent_id = base::Int64ToString(parent->id()); remove_info.index = index; DispatchEvent(bookmarks::OnRemoved::kEventName, bookmarks::OnRemoved::Create(base::Int64ToString(node->id()), remove_info)); } 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) { // 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. bookmarks::OnChanged::ChangeInfo change_info; change_info.title = base::UTF16ToUTF8(node->GetTitle()); change_info.url.reset(new std::string(node->url().spec())); DispatchEvent(bookmarks::OnChanged::kEventName, bookmarks::OnChanged::Create(base::Int64ToString(node->id()), change_info)); } void BookmarkEventRouter::BookmarkNodeFaviconChanged(BookmarkModel* model, const BookmarkNode* node) { // TODO(erikkay) anything we should do here? } void BookmarkEventRouter::BookmarkNodeChildrenReordered( BookmarkModel* model, const BookmarkNode* node) { bookmarks::OnChildrenReordered::ReorderInfo reorder_info; int childCount = node->child_count(); for (int i = 0; i < childCount; ++i) { const BookmarkNode* child = node->GetChild(i); reorder_info.child_ids.push_back(base::Int64ToString(child->id())); } DispatchEvent(bookmarks::OnChildrenReordered::kEventName, bookmarks::OnChildrenReordered::Create( base::Int64ToString(node->id()), reorder_info)); } void BookmarkEventRouter::ExtensiveBookmarkChangesBeginning( BookmarkModel* model) { DispatchEvent(bookmarks::OnImportBegan::kEventName, bookmarks::OnImportBegan::Create()); } void BookmarkEventRouter::ExtensiveBookmarkChangesEnded(BookmarkModel* model) { DispatchEvent(bookmarks::OnImportEnded::kEventName, bookmarks::OnImportEnded::Create()); } BookmarksAPI::BookmarksAPI(BrowserContext* context) : browser_context_(context) { EventRouter* event_router = ExtensionSystem::Get(browser_context_)->event_router(); event_router->RegisterObserver(this, bookmarks::OnCreated::kEventName); event_router->RegisterObserver(this, bookmarks::OnRemoved::kEventName); event_router->RegisterObserver(this, bookmarks::OnChanged::kEventName); event_router->RegisterObserver(this, bookmarks::OnMoved::kEventName); event_router->RegisterObserver(this, bookmarks::OnChildrenReordered::kEventName); event_router->RegisterObserver(this, bookmarks::OnImportBegan::kEventName); event_router->RegisterObserver(this, bookmarks::OnImportEnded::kEventName); } BookmarksAPI::~BookmarksAPI() { } void BookmarksAPI::Shutdown() { ExtensionSystem::Get(browser_context_)->event_router()->UnregisterObserver( this); } static base::LazyInstance > g_factory = LAZY_INSTANCE_INITIALIZER; // static BrowserContextKeyedAPIFactory* BookmarksAPI::GetFactoryInstance() { return g_factory.Pointer(); } void BookmarksAPI::OnListenerAdded(const EventListenerInfo& details) { bookmark_event_router_.reset(new BookmarkEventRouter( browser_context_, BookmarkModelFactory::GetForProfile( Profile::FromBrowserContext(browser_context_)))); ExtensionSystem::Get(browser_context_)->event_router()->UnregisterObserver( this); } bool BookmarksGetFunction::RunImpl() { scoped_ptr params( bookmarks::Get::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); std::vector > nodes; if (params->id_or_id_list.as_strings) { std::vector& 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) { const BookmarkNode* node = GetBookmarkNodeFromId(ids[i]); if (!node) return false; bookmark_api_helpers::AddNode(node, &nodes, false); } } else { const BookmarkNode* node = GetBookmarkNodeFromId(*params->id_or_id_list.as_string); if (!node) return false; bookmark_api_helpers::AddNode(node, &nodes, false); } results_ = bookmarks::Get::Results::Create(nodes); return true; } bool BookmarksGetChildrenFunction::RunImpl() { scoped_ptr params( bookmarks::GetChildren::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); const BookmarkNode* node = GetBookmarkNodeFromId(params->id); if (!node) return false; std::vector > nodes; 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 params( bookmarks::GetRecent::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); if (params->number_of_items < 1) return false; std::vector nodes; bookmark_utils::GetMostRecentlyAddedEntries( BookmarkModelFactory::GetForProfile(GetProfile()), params->number_of_items, &nodes); std::vector > tree_nodes; std::vector::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 > nodes; const BookmarkNode* node = BookmarkModelFactory::GetForProfile(GetProfile())->root_node(); bookmark_api_helpers::AddNode(node, &nodes, true); results_ = bookmarks::GetTree::Results::Create(nodes); return true; } bool BookmarksGetSubTreeFunction::RunImpl() { scoped_ptr params( bookmarks::GetSubTree::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); const BookmarkNode* node = GetBookmarkNodeFromId(params->id); if (!node) return false; std::vector > nodes; bookmark_api_helpers::AddNode(node, &nodes, true); results_ = bookmarks::GetSubTree::Results::Create(nodes); return true; } bool BookmarksSearchFunction::RunImpl() { scoped_ptr params( bookmarks::Search::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); PrefService* prefs = user_prefs::UserPrefs::Get(GetProfile()); std::string lang = prefs->GetString(prefs::kAcceptLanguages); std::vector nodes; if (params->query.as_string) { bookmark_utils::QueryFields query; query.word_phrase_query.reset( new base::string16(base::UTF8ToUTF16(*params->query.as_string))); bookmark_utils::GetBookmarksMatchingProperties( BookmarkModelFactory::GetForProfile(GetProfile()), query, std::numeric_limits::max(), lang, &nodes); } else { DCHECK(params->query.as_object); const bookmarks::Search::Params::Query::Object& object = *params->query.as_object; bookmark_utils::QueryFields query; if (object.query) { query.word_phrase_query.reset( new base::string16(base::UTF8ToUTF16(*object.query))); } if (object.url) query.url.reset(new base::string16(base::UTF8ToUTF16(*object.url))); if (object.title) query.title.reset(new base::string16(base::UTF8ToUTF16(*object.title))); bookmark_utils::GetBookmarksMatchingProperties( BookmarkModelFactory::GetForProfile(GetProfile()), query, std::numeric_limits::max(), lang, &nodes); } std::vector > tree_nodes; for (std::vector::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* 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 params( bookmarks::Remove::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); int64 id; if (!GetBookmarkIdAsInt64(params->id, &id)) return false; bool recursive = false; if (name() == BookmarksRemoveTreeFunction::function_name()) recursive = true; BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile()); if (!bookmark_api_helpers::RemoveNode(model, id, recursive, &error_)) return false; return true; } bool BookmarksCreateFunction::RunImpl() { if (!EditBookmarksEnabled()) return false; scoped_ptr params( bookmarks::Create::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile()); 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; } } base::string16 title; // Optional. if (params->bookmark.title.get()) title = base::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 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* 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 params( bookmarks::Move::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); const BookmarkNode* node = GetBookmarkNodeFromId(params->id); if (!node) return false; BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile()); 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 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* 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 params( bookmarks::Update::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); // Optional but we need to distinguish non present from an empty title. base::string16 title; bool has_title = false; if (params->changes.title.get()) { title = base::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 = GetBookmarkNodeFromId(params->id); if (!node) return false; BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile()); 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 tree_node( bookmark_api_helpers::GetBookmarkTreeNode(node, false, false)); results_ = bookmarks::Update::Results::Create(*tree_node); return true; } // Mapper superclass for BookmarkFunctions. template 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 buckets_; }; // Mapper for 'bookmarks.create'. Maps "same input to bookmarks.create" to a // unique bucket. class CreateBookmarkBucketMapper : public BookmarkBucketMapper { public: explicit CreateBookmarkBucketMapper(BrowserContext* context) : browser_context_(context) {} // 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::FromBrowserContext(browser_context_)); 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 = base::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: BrowserContext* browser_context_; }; // Mapper for 'bookmarks.remove'. class RemoveBookmarksBucketMapper : public BookmarkBucketMapper { public: explicit RemoveBookmarksBucketMapper(BrowserContext* context) : browser_context_(context) {} virtual void GetBucketsForArgs(const base::ListValue* args, BucketList* buckets) OVERRIDE { typedef std::list 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::FromBrowserContext(browser_context_)); const BookmarkNode* node = model->GetNodeByID(*it); if (!node || node->is_root()) return; std::string bucket_id; bucket_id += base::UTF16ToUTF8(node->parent()->GetTitle()); bucket_id += base::UTF16ToUTF8(node->GetTitle()); bucket_id += node->url().spec(); buckets->push_back(GetBucket(base::SHA1HashString(bucket_id))); } } private: BrowserContext* browser_context_; }; // 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 BookmarkIdMapper : public BookmarkBucketMapper { public: typedef std::list 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 static void Build(QuotaLimitHeuristics* heuristics) { BuildWithMappers(heuristics, new BookmarkIdMapper(), new BookmarkIdMapper()); } // For bookmarks.create. static void BuildForCreate(QuotaLimitHeuristics* heuristics, BrowserContext* context) { BuildWithMappers(heuristics, new CreateBookmarkBucketMapper(context), new CreateBookmarkBucketMapper(context)); } // For bookmarks.remove. static void BuildForRemove(QuotaLimitHeuristics* heuristics, BrowserContext* context) { BuildWithMappers(heuristics, new RemoveBookmarksBucketMapper(context), new RemoveBookmarksBucketMapper(context)); } 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, GetProfile()); } void BookmarksMoveFunction::GetQuotaLimitHeuristics( QuotaLimitHeuristics* heuristics) const { BookmarksQuotaLimitFactory::Build(heuristics); } void BookmarksUpdateFunction::GetQuotaLimitHeuristics( QuotaLimitHeuristics* heuristics) const { BookmarksQuotaLimitFactory::Build(heuristics); }; void BookmarksCreateFunction::GetQuotaLimitHeuristics( QuotaLimitHeuristics* heuristics) const { BookmarksQuotaLimitFactory::BuildForCreate(heuristics, GetProfile()); } 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) if (!owning_window && chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH) owning_window = aura::RemoteWindowTreeHostWin::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, base::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& 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, GetProfile(), importer::FAVORITES, new ProfileWriter(GetProfile())); 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(GetProfile(), path, NULL); #endif Release(); // Balanced in BookmarksIOFunction::SelectFile() } } // namespace extensions