Chromium Code Reviews| Index: chrome/browser/ui/webui/ntp/android/bookmarks_handler.cc |
| diff --git a/chrome/browser/ui/webui/ntp/android/bookmarks_handler.cc b/chrome/browser/ui/webui/ntp/android/bookmarks_handler.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..7f34d1cc8d3958e2e6371c72da145122ab89f7f9 |
| --- /dev/null |
| +++ b/chrome/browser/ui/webui/ntp/android/bookmarks_handler.cc |
| @@ -0,0 +1,383 @@ |
| +// 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/ui/webui/ntp/android/bookmarks_handler.h" |
| + |
| +#include "base/logging.h" |
| +#include "base/memory/ref_counted_memory.h" |
| +#include "base/string_number_conversions.h" |
| +#include "base/string_util.h" |
| +#include "chrome/browser/android/tab_android.h" |
| +#include "chrome/browser/bookmarks/bookmark_model.h" |
| +#include "chrome/browser/bookmarks/bookmark_model_factory.h" |
| +#include "chrome/browser/profiles/profile.h" |
| +#include "chrome/browser/profiles/profile_manager.h" |
| +#include "chrome/browser/ui/webui/chrome_url_data_manager.h" |
| +#include "chrome/browser/ui/webui/favicon_source.h" |
| +#include "content/public/browser/browser_thread.h" |
| +#include "content/public/browser/web_contents.h" |
| +#include "third_party/skia/include/core/SkBitmap.h" |
| +#include "ui/gfx/codec/png_codec.h" |
| +#include "ui/gfx/color_analysis.h" |
| + |
| +using base::Int64ToString; |
| +using content::BrowserThread; |
| + |
| +namespace { |
| + |
| +static const char* kParentIdParam = "parent_id"; |
| +static const char* kNodeIdParam = "node_id"; |
| + |
| +// Parses a bookmark ID passed back from the NTP. The IDs differ from the |
| +// normal int64 bookmark ID because we prepend a "p" if the ID represents a |
| +// partner bookmark. |
| +bool ParseNtpBookmarkId(const ListValue* args, |
| + int64* out_id, |
| + bool* out_is_partner) { |
| + std::string string_id; |
| + if (!args->GetString(0, &string_id)) |
| + return false; |
|
Evan Stade
2012/08/14 22:04:54
\n
Ted C
2012/08/14 23:24:28
Done.
|
| + if (string_id.empty()) |
| + return false; |
|
Evan Stade
2012/08/14 22:04:54
\n
Ted C
2012/08/14 23:24:28
Is your preference to have blank lines between dif
Evan Stade
2012/08/15 00:05:10
really I just want there to be more vertical spaci
|
| + if (StartsWithASCII(string_id, "p", true)) { |
| + *out_is_partner = true; |
| + return base::StringToInt64(string_id.substr(1), out_id); |
| + } else { |
|
Evan Stade
2012/08/15 00:05:10
you don't need this else because the if has a retu
Ted C
2012/08/15 21:21:00
Done.
|
| + *out_is_partner = false; |
| + return base::StringToInt64(string_id, out_id); |
| + } |
| +} |
| + |
| +std::string BookmarkTypeAsString(BookmarkNode::Type type) { |
| + switch (type) { |
| + case BookmarkNode::URL: |
| + return "URL"; |
| + case BookmarkNode::FOLDER: |
| + return "FOLDER"; |
| + case BookmarkNode::BOOKMARK_BAR: |
| + return "BOOKMARK_BAR"; |
| + case BookmarkNode::OTHER_NODE: |
| + return "OTHER_NODE"; |
| + case BookmarkNode::MOBILE: |
| + return "MOBILE"; |
| + default: |
| + return "UNKNOWN"; |
| + } |
| +} |
| + |
| +SkColor GetDominantColorForFavicon(scoped_refptr<base::RefCountedMemory> png) { |
| + color_utils::GridSampler sampler; |
| + // 100 here is the darkness_limit which represents the minimum sum of the RGB |
| + // components that is acceptable as a color choice. This can be from 0 to 765. |
| + // 665 here is the brightness_limit represents the maximum sum of the RGB |
| + // components that is acceptable as a color choice. This can be from 0 to 765. |
| + return color_utils::CalculateKMeanColorOfPNG(png, 100, 665, sampler); |
| +} |
| + |
| +} // namespace |
| + |
| +BookmarksHandler::BookmarksHandler() |
| + : bookmark_model_(NULL), |
| + partner_bookmarks_shim_(NULL), |
| + extensive_changes_(false), |
| + creating_shortcut_(false) { |
| +} |
| + |
| +BookmarksHandler::~BookmarksHandler() { |
| + if (bookmark_model_) |
| + bookmark_model_->RemoveObserver(this); |
|
Evan Stade
2012/08/14 22:04:54
\n
Ted C
2012/08/14 23:24:28
Done.
|
| + if (partner_bookmarks_shim_) |
| + partner_bookmarks_shim_->RemoveObserver(this); |
| +} |
| + |
| +void BookmarksHandler::RegisterMessages() { |
| + // Listen for the bookmark change. We need the both bookmark and folder |
| + // change, the NotificationService is not sufficient. |
| + Profile* profile = Profile::FromBrowserContext( |
| + web_ui()->GetWebContents()->GetBrowserContext()); |
| + |
| + ChromeURLDataManager::AddDataSource(profile, |
| + new FaviconSource(profile, FaviconSource::ANY)); |
| + |
| + bookmark_model_ = BookmarkModelFactory::GetForProfile(profile); |
| + if (bookmark_model_) { |
| + bookmark_model_->AddObserver(this); |
| + // Since a sync or import could have started before this class is |
| + // initialized, we need to make sure that our initial state is |
| + // up to date. |
| + extensive_changes_ = bookmark_model_->IsDoingExtensiveChanges(); |
| + } |
|
Evan Stade
2012/08/14 22:04:54
\n
Ted C
2012/08/14 23:24:28
Done.
|
| + // Create the partner Bookmarks shim as early as possible (but don't attach). |
| + if (!partner_bookmarks_shim_) { |
| + partner_bookmarks_shim_ = PartnerBookmarksShim::GetInstance(); |
| + partner_bookmarks_shim_->AddObserver(this); |
| + } |
| + |
| + // Register ourselves as the handler for the bookmark javascript callbacks. |
| + web_ui()->RegisterMessageCallback("getBookmarks", |
| + base::Bind(&BookmarksHandler::HandleGetBookmarks, |
| + base::Unretained(this))); |
| + web_ui()->RegisterMessageCallback("deleteBookmark", |
| + base::Bind(&BookmarksHandler::HandleDeleteBookmark, |
| + base::Unretained(this))); |
| + web_ui()->RegisterMessageCallback("shortcutToBookmark", |
| + base::Bind(&BookmarksHandler::HandleShortcutToBookmark, |
| + base::Unretained(this))); |
| +} |
| + |
| +void BookmarksHandler::HandleGetBookmarks(const ListValue* args) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + Profile* profile = Profile::FromBrowserContext( |
| + web_ui()->GetWebContents()->GetBrowserContext()); |
| + if (!BookmarkModelFactory::GetForProfile(profile)->IsLoaded()) |
| + return; // is handled in Loaded(). |
| + |
| + // Attach the Partner Bookmarks shim under the Mobile Bookmarks. |
| + // Cannot do this earlier because mobile_node is not yet set. |
| + DCHECK(partner_bookmarks_shim_ != NULL); |
| + if (bookmark_model_) |
|
Evan Stade
2012/08/15 00:05:10
curlies are required if the contents of the condit
Ted C
2012/08/15 21:21:00
Done.
|
| + partner_bookmarks_shim_->AttachTo( |
| + bookmark_model_, bookmark_model_->mobile_node()); |
| + if (!partner_bookmarks_shim_->IsLoaded()) |
| + return; // is handled with a PartnerShimLoaded() callback |
| + |
| + int64 id; |
| + bool is_partner; |
| + if (args && !args->empty() && ParseNtpBookmarkId(args, &id, &is_partner)) |
| + QueryBookmarkFolder(id, is_partner); |
| + else |
| + QueryInitialBookmarks(); |
| +} |
| + |
| +void BookmarksHandler::HandleDeleteBookmark(const ListValue* args) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + int64 id; |
| + bool is_partner; |
| + if (args && !args->empty() && ParseNtpBookmarkId(args, &id, &is_partner)) { |
| + DCHECK(!is_partner); |
| + const BookmarkNode* node = bookmark_model_->GetNodeByID(id); |
| + if (node && node->parent()) { |
| + const BookmarkNode* parent_node = node->parent(); |
| + bookmark_model_->Remove(parent_node, parent_node->GetIndexOf(node)); |
| + } |
| + } |
| +} |
| + |
| +std::string BookmarksHandler::GetBookmarkIdForNtp(const BookmarkNode* node) { |
| + if (partner_bookmarks_shim_->IsPartnerBookmark(node)) |
| + return "p" + Int64ToString(node->id()); |
|
Evan Stade
2012/08/15 00:05:10
ternary operator here:
return (partner ? "p" : ""
Ted C
2012/08/15 21:21:00
Done.
|
| + return Int64ToString(node->id()); |
| +} |
| + |
| +void BookmarksHandler::SetParentInBookmarksResult(const BookmarkNode& parent, |
| + DictionaryValue* result) { |
| + result->SetString(kParentIdParam, GetBookmarkIdForNtp(&parent)); |
| +} |
| + |
| +void BookmarksHandler::PopulateBookmark(const BookmarkNode* node, |
| + ListValue* result) { |
| + if (!result) |
| + return; |
|
Evan Stade
2012/08/14 22:04:54
\n
Ted C
2012/08/14 23:24:28
Done.
|
| + DictionaryValue* filler_value = new DictionaryValue(); |
| + filler_value->SetString("title", node->GetTitle()); |
| + // Mark reserved system nodes and partner bookmarks as uneditable |
| + // (i.e. the bookmark bar along with the "Other Bookmarks" folder). |
| + filler_value->SetBoolean("editable", |
| + partner_bookmarks_shim_->IsBookmarkEditable(node)); |
| + if (node->is_url()) { |
| + filler_value->SetBoolean("folder", false); |
| + filler_value->SetString("url", node->url().spec()); |
| + } else { |
| + filler_value->SetBoolean("folder", true); |
| + } |
| + filler_value->SetString("id", GetBookmarkIdForNtp(node)); |
| + filler_value->SetString("type", BookmarkTypeAsString(node->type())); |
| + result->Append(filler_value); |
| +} |
| + |
| +void BookmarksHandler::PopulateBookmarksInFolder( |
| + const BookmarkNode* folder, |
| + const scoped_ptr<DictionaryValue>& result) { |
| + scoped_ptr<ListValue> bookmarks(new ListValue); |
| + |
| + for (int i = 0; i < folder->child_count(); i++) { |
| + const BookmarkNode* bookmark= |
| + (const BookmarkNode*) folder->GetChild(i); |
| + PopulateBookmark(bookmark, bookmarks.get()); |
| + } |
| + |
| + // Make sure we iterate over the partner's attach point |
| + DCHECK(partner_bookmarks_shim_ != NULL); |
| + if (partner_bookmarks_shim_->HasPartnerBookmarks() && |
| + folder == partner_bookmarks_shim_->get_attach_point()) |
|
Evan Stade
2012/08/14 22:04:54
curlies
Ted C
2012/08/14 23:24:28
Done.
|
| + PopulateBookmark( |
| + partner_bookmarks_shim_->GetPartnerBookmarksRoot(), bookmarks.get()); |
| + |
| + scoped_ptr<ListValue> folder_hierarchy(new ListValue); |
| + const BookmarkNode* parent = partner_bookmarks_shim_->GetParentOf(folder); |
| + |
| + while (parent != NULL) { |
| + scoped_ptr<DictionaryValue> hierarchy_entry(new DictionaryValue); |
| + if (partner_bookmarks_shim_->IsRootNode(parent)) |
| + hierarchy_entry->SetBoolean("root", true); |
|
Evan Stade
2012/08/14 22:04:54
\n
Ted C
2012/08/14 23:24:28
Done.
|
| + hierarchy_entry->SetString("title", parent->GetTitle()); |
| + hierarchy_entry->SetString("id", GetBookmarkIdForNtp(parent)); |
| + folder_hierarchy->Append(hierarchy_entry.release()); |
| + parent = partner_bookmarks_shim_->GetParentOf(parent); |
| + } |
| + |
| + result->SetString("title", folder->GetTitle()); |
| + result->SetString("id", GetBookmarkIdForNtp(folder)); |
| + result->SetBoolean("root", partner_bookmarks_shim_->IsRootNode(folder)); |
| + result->Set("bookmarks", bookmarks.release()); |
| + result->Set("hierarchy", folder_hierarchy.release()); |
| +} |
| + |
| +void BookmarksHandler::QueryBookmarkFolder(const int64& folder_id, |
| + bool is_partner_bookmark) { |
| + DCHECK(partner_bookmarks_shim_ != NULL); |
| + const BookmarkNode* bookmarks = |
| + partner_bookmarks_shim_->GetNodeByID(folder_id, is_partner_bookmark); |
| + if (bookmarks) { |
| + scoped_ptr<DictionaryValue> result(new DictionaryValue); |
| + PopulateBookmarksInFolder(bookmarks, result); |
| + SendResult(result); |
| + } else { |
| + // If we receive an ID that no longer maps to a bookmark folder, just |
| + // return the initial bookmark folder. |
| + QueryInitialBookmarks(); |
| + } |
| +} |
| + |
| +void BookmarksHandler::QueryInitialBookmarks() { |
| + scoped_ptr<DictionaryValue> result(new DictionaryValue); |
| + DCHECK(partner_bookmarks_shim_ != NULL); |
| + PopulateBookmarksInFolder( |
| + // We have to go to the partner Root if it exists |
| + partner_bookmarks_shim_->HasPartnerBookmarks() ? |
| + partner_bookmarks_shim_->GetPartnerBookmarksRoot() : |
| + bookmark_model_->mobile_node(), |
| + result); |
| + SendResult(result); |
| +} |
| + |
| +void BookmarksHandler::SendResult(const scoped_ptr<DictionaryValue>& result) { |
| + if (result.get()) |
| + web_ui()->CallJavascriptFunction("ntp.bookmarks", *(result.get())); |
| +} |
| + |
| +void BookmarksHandler::Loaded(BookmarkModel* model, |
| + bool ids_reassigned) { |
| + BookmarkModelChanged(); |
|
Evan Stade
2012/08/14 22:04:54
the NTP may not be ready yet. This should not do a
Ted C
2012/08/14 23:24:28
Added a flag to track whether get has been called
|
| +} |
| + |
| +void BookmarksHandler::PartnerShimLoaded(PartnerBookmarksShim* shim) { |
| + BookmarkModelChanged(); |
|
Evan Stade
2012/08/14 22:04:54
ditto (I guess)
|
| +} |
| + |
| +void BookmarksHandler::ShimBeingDeleted(PartnerBookmarksShim* shim) { |
| + partner_bookmarks_shim_ = NULL; |
| +} |
| + |
| +void BookmarksHandler::ExtensiveBookmarkChangesBeginning(BookmarkModel* model) { |
| + extensive_changes_ = true; |
| +} |
| + |
| +void BookmarksHandler::ExtensiveBookmarkChangesEnded(BookmarkModel* model) { |
| + extensive_changes_ = false; |
| + BookmarkModelChanged(); |
| +} |
| + |
| +void BookmarksHandler::BookmarkNodeRemoved(BookmarkModel* model, |
| + const BookmarkNode* parent, |
| + int old_index, |
| + const BookmarkNode* node) { |
| + scoped_ptr<DictionaryValue> result(new DictionaryValue); |
| + SetParentInBookmarksResult(*parent, result.get()); |
| + result->SetString(kNodeIdParam, Int64ToString(node->id())); |
| + NotifyModelChanged(*(result.get())); |
|
Evan Stade
2012/08/14 22:04:54
you can just do *result
however, I don't understa
Ted C
2012/08/14 23:24:28
Done.
|
| +} |
| + |
| +void BookmarksHandler::BookmarkNodeAdded(BookmarkModel* model, |
| + const BookmarkNode* parent, |
| + int index) { |
| + scoped_ptr<DictionaryValue> result(new DictionaryValue); |
| + SetParentInBookmarksResult(*parent, result.get()); |
| + NotifyModelChanged(*(result.get())); |
| +} |
| + |
| +void BookmarksHandler::BookmarkNodeChanged(BookmarkModel* model, |
| + const BookmarkNode* node) { |
| + scoped_ptr<DictionaryValue> result(new DictionaryValue); |
| + DCHECK(partner_bookmarks_shim_ != NULL); |
|
Evan Stade
2012/08/14 22:04:54
no need for !=
Ted C
2012/08/14 23:24:28
Done.
|
| + DCHECK(!partner_bookmarks_shim_->IsPartnerBookmark(node)); |
|
Evan Stade
2012/08/14 22:04:54
DCHECKS go at very beginning.
Ted C
2012/08/14 23:24:28
Done.
|
| + SetParentInBookmarksResult(*(node->parent()), result.get()); |
| + result->SetString(kNodeIdParam, Int64ToString(node->id())); |
| + NotifyModelChanged(*(result.get())); |
| +} |
| + |
| +void BookmarksHandler::BookmarkModelChanged() { |
| + if (!extensive_changes_) |
| + web_ui()->CallJavascriptFunction("ntp.bookmarkChanged"); |
| +} |
| + |
| +void BookmarksHandler::NotifyModelChanged(const DictionaryValue& status) { |
| + if (!extensive_changes_) |
| + web_ui()->CallJavascriptFunction("ntp.bookmarkChanged", status); |
| +} |
| + |
| +void BookmarksHandler::HandleShortcutToBookmark(const ListValue* args) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + Profile* profile = Profile::FromBrowserContext( |
| + web_ui()->GetWebContents()->GetBrowserContext()); |
| + if (!profile) |
| + return; |
| + |
| + int64 id; |
| + bool is_partner; |
| + if (args && !args->empty() && ParseNtpBookmarkId(args, &id, &is_partner)) { |
| + DCHECK(partner_bookmarks_shim_ != NULL); |
| + BookmarkNode* node = const_cast<BookmarkNode*>( |
| + partner_bookmarks_shim_->GetNodeByID(id, is_partner)); |
| + if (!node) |
| + return; |
|
Evan Stade
2012/08/14 22:04:54
\n
Ted C
2012/08/14 23:24:28
Done.
|
| + creating_shortcut_ = true; |
| + FaviconService* favicon_service = profile->GetFaviconService( |
| + Profile::EXPLICIT_ACCESS); |
| + FaviconService::Handle handle = favicon_service->GetFaviconForURL( |
| + node->url(), |
| + history::FAVICON | history::TOUCH_ICON, |
| + &cancelable_consumer_, |
| + base::Bind(&BookmarksHandler::OnFaviconDataAvailable, |
| + base::Unretained(this))); |
| + cancelable_consumer_.SetClientData(favicon_service, handle, node); |
| + } |
| +} |
| + |
| +void BookmarksHandler::OnFaviconDataAvailable( |
| + FaviconService::Handle handle, |
| + history::FaviconData favicon) { |
| + if (!creating_shortcut_) |
| + return; |
|
Evan Stade
2012/08/14 22:04:54
\n
Ted C
2012/08/14 23:24:28
Done.
|
| + SkColor color = SK_ColorWHITE; |
| + SkBitmap favicon_bitmap; |
| + if (favicon.is_valid()) { |
| + color = GetDominantColorForFavicon(favicon.image_data); |
| + gfx::PNGCodec::Decode(favicon.image_data->front(), |
| + favicon.image_data->size(), |
| + &favicon_bitmap); |
| + } |
|
Evan Stade
2012/08/14 22:04:54
\n
Ted C
2012/08/14 23:24:28
Done.
|
| + Profile* profile = Profile::FromBrowserContext( |
| + web_ui()->GetWebContents()->GetBrowserContext()); |
| + BookmarkNode* node = cancelable_consumer_.GetClientData( |
| + profile->GetFaviconService(Profile::EXPLICIT_ACCESS), handle); |
| + |
| + TabAndroid* tab = TabAndroid::FromWebContents( |
| + web_ui()->GetWebContents()); |
| + if (tab) |
|
Evan Stade
2012/08/14 22:04:54
curlies
Ted C
2012/08/14 23:24:28
Done.
|
| + tab->AddShortcutToBookmark(node->url(), node->GetTitle(), |
| + favicon_bitmap, SkColorGetR(color), |
| + SkColorGetG(color), SkColorGetB(color)); |
| + creating_shortcut_ = false; |
| +} |