root/chrome/browser/extensions/api/bookmarks/bookmarks_api.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. GetDefaultFilepathForBookmarkExport
  2. Run
  3. GetBookmarkIdAsInt64
  4. GetBookmarkNodeFromId
  5. EditBookmarksEnabled
  6. BookmarkModelChanged
  7. BookmarkModelLoaded
  8. model_
  9. DispatchEvent
  10. BookmarkModelLoaded
  11. BookmarkModelBeingDeleted
  12. BookmarkNodeMoved
  13. BookmarkNodeAdded
  14. BookmarkNodeRemoved
  15. BookmarkAllNodesRemoved
  16. BookmarkNodeChanged
  17. BookmarkNodeFaviconChanged
  18. BookmarkNodeChildrenReordered
  19. ExtensiveBookmarkChangesBeginning
  20. ExtensiveBookmarkChangesEnded
  21. Shutdown
  22. GetFactoryInstance
  23. OnListenerAdded
  24. RunImpl
  25. RunImpl
  26. RunImpl
  27. RunImpl
  28. RunImpl
  29. RunImpl
  30. ExtractIds
  31. RunImpl
  32. RunImpl
  33. ExtractIds
  34. RunImpl
  35. ExtractIds
  36. RunImpl
  37. GetBucket
  38. GetBucketsForArgs
  39. GetBucketsForArgs
  40. GetBucketsForArgs
  41. Build
  42. BuildForCreate
  43. BuildForRemove
  44. BuildWithMappers
  45. GetQuotaLimitHeuristics
  46. GetQuotaLimitHeuristics
  47. GetQuotaLimitHeuristics
  48. GetQuotaLimitHeuristics
  49. SelectFile
  50. ShowSelectFileDialog
  51. FileSelectionCanceled
  52. MultiFilesSelected
  53. RunImpl
  54. FileSelected
  55. RunImpl
  56. FileSelected

// 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<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;
}

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<base::ListValue> 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<BookmarkTreeNode> 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());
  if (node->is_url())
    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<BrowserContextKeyedAPIFactory<BookmarksAPI> >
    g_factory = LAZY_INSTANCE_INITIALIZER;

// static
BrowserContextKeyedAPIFactory<BookmarksAPI>*
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<bookmarks::Get::Params> params(
      bookmarks::Get::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());

  std::vector<linked_ptr<BookmarkTreeNode> > nodes;
  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) {
      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<bookmarks::GetChildren::Params> params(
      bookmarks::GetChildren::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());

  const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
  if (!node)
    return false;

  std::vector<linked_ptr<BookmarkTreeNode> > 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<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(GetProfile()),
      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(GetProfile())->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());

  const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
  if (!node)
    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(GetProfile());
  std::string lang = prefs->GetString(prefs::kAcceptLanguages);
  std::vector<const BookmarkNode*> 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<int>::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<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 (!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<bookmarks::Create::Params> 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<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());

  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<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());

  // 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<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(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<std::string> {
 public:
  explicit RemoveBookmarksBucketMapper(BrowserContext* context)
      : browser_context_(context) {}
  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::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 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,
                             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<BookmarksMoveFunction>(heuristics);
}

void BookmarksUpdateFunction::GetQuotaLimitHeuristics(
    QuotaLimitHeuristics* heuristics) const {
  BookmarksQuotaLimitFactory::Build<BookmarksUpdateFunction>(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<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,
                                     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

/* [<][>][^][v][top][bottom][index][help] */