Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / webui / ntp / android / bookmarks_handler.cc
blob88ca506ca8cd0c96831c621ae958ba752332894e
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/webui/ntp/android/bookmarks_handler.h"
7 #include "base/logging.h"
8 #include "base/memory/ref_counted_memory.h"
9 #include "base/metrics/histogram.h"
10 #include "base/prefs/pref_service.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/string_util.h"
13 #include "chrome/browser/android/tab_android.h"
14 #include "chrome/browser/bookmarks/bookmark_model.h"
15 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
16 #include "chrome/browser/favicon/favicon_service_factory.h"
17 #include "chrome/browser/profiles/incognito_helpers.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/profiles/profile_manager.h"
20 #include "chrome/browser/ui/webui/favicon_source.h"
21 #include "chrome/common/pref_names.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "content/public/browser/url_data_source.h"
24 #include "content/public/browser/web_contents.h"
25 #include "third_party/skia/include/core/SkBitmap.h"
26 #include "ui/gfx/codec/png_codec.h"
27 #include "ui/gfx/color_analysis.h"
28 #include "ui/gfx/favicon_size.h"
30 using base::Int64ToString;
31 using content::BrowserThread;
33 namespace {
35 static const char* kParentIdParam = "parent_id";
36 static const char* kNodeIdParam = "node_id";
38 // Defines actions taken by the user over the partner bookmarks on NTP for
39 // NewTabPage.BookmarkActionAndroid histogram.
40 // Should be kept in sync with the values in histograms.xml.
41 enum PartnerBookmarkAction {
42 BOOKMARK_ACTION_DELETE_BOOKMARK_PARTNER = 0,
43 BOOKMARK_ACTION_DELETE_ROOT_FOLDER_PARTNER = 1,
44 BOOKMARK_ACTION_EDIT_BOOKMARK_PARTNER = 2,
45 BOOKMARK_ACTION_EDIT_ROOT_FOLDER_PARTNER = 3,
46 BOOKMARK_ACTION_BUCKET_BOUNDARY = 4
49 // Helper to record a bookmark action in BookmarkActionAndroid histogram.
50 void RecordBookmarkAction(PartnerBookmarkAction type) {
51 UMA_HISTOGRAM_ENUMERATION("NewTabPage.BookmarkActionAndroid", type,
52 BOOKMARK_ACTION_BUCKET_BOUNDARY);
55 std::string BookmarkTypeAsString(BookmarkNode::Type type) {
56 switch (type) {
57 case BookmarkNode::URL:
58 return "URL";
59 case BookmarkNode::FOLDER:
60 return "FOLDER";
61 case BookmarkNode::BOOKMARK_BAR:
62 return "BOOKMARK_BAR";
63 case BookmarkNode::OTHER_NODE:
64 return "OTHER_NODE";
65 case BookmarkNode::MOBILE:
66 return "MOBILE";
67 default:
68 return "UNKNOWN";
72 SkColor GetDominantColorForFavicon(scoped_refptr<base::RefCountedMemory> png) {
73 color_utils::GridSampler sampler;
74 // 100 here is the darkness_limit which represents the minimum sum of the RGB
75 // components that is acceptable as a color choice. This can be from 0 to 765.
76 // 665 here is the brightness_limit represents the maximum sum of the RGB
77 // components that is acceptable as a color choice. This can be from 0 to 765.
78 return color_utils::CalculateKMeanColorOfPNG(png, 100, 665, &sampler);
81 } // namespace
83 BookmarksHandler::BookmarksHandler()
84 : bookmark_model_(NULL),
85 partner_bookmarks_shim_(NULL),
86 bookmark_data_requested_(false),
87 extensive_changes_(false) {
90 BookmarksHandler::~BookmarksHandler() {
91 if (bookmark_model_)
92 bookmark_model_->RemoveObserver(this);
94 if (partner_bookmarks_shim_)
95 partner_bookmarks_shim_->RemoveObserver(this);
97 if (managed_bookmarks_shim_)
98 managed_bookmarks_shim_->RemoveObserver(this);
101 void BookmarksHandler::RegisterMessages() {
102 // Listen for the bookmark change. We need the both bookmark and folder
103 // change, the NotificationService is not sufficient.
104 Profile* profile = Profile::FromBrowserContext(
105 web_ui()->GetWebContents()->GetBrowserContext());
107 content::URLDataSource::Add(
108 profile, new FaviconSource(profile, FaviconSource::ANY));
110 bookmark_model_ = BookmarkModelFactory::GetForProfile(profile);
111 if (bookmark_model_) {
112 bookmark_model_->AddObserver(this);
113 // Since a sync or import could have started before this class is
114 // initialized, we need to make sure that our initial state is
115 // up to date.
116 extensive_changes_ = bookmark_model_->IsDoingExtensiveChanges();
119 // Create the partner Bookmarks shim as early as possible (but don't attach).
120 if (!partner_bookmarks_shim_) {
121 partner_bookmarks_shim_ = PartnerBookmarksShim::BuildForBrowserContext(
122 chrome::GetBrowserContextRedirectedInIncognito(
123 web_ui()->GetWebContents()->GetBrowserContext()));
124 partner_bookmarks_shim_->AddObserver(this);
127 managed_bookmarks_shim_.reset(new ManagedBookmarksShim(profile->GetPrefs()));
128 managed_bookmarks_shim_->AddObserver(this);
130 // Register ourselves as the handler for the bookmark javascript callbacks.
131 web_ui()->RegisterMessageCallback("getBookmarks",
132 base::Bind(&BookmarksHandler::HandleGetBookmarks,
133 base::Unretained(this)));
134 web_ui()->RegisterMessageCallback("deleteBookmark",
135 base::Bind(&BookmarksHandler::HandleDeleteBookmark,
136 base::Unretained(this)));
137 web_ui()->RegisterMessageCallback("editBookmark",
138 base::Bind(&BookmarksHandler::HandleEditBookmark,
139 base::Unretained(this)));
140 web_ui()->RegisterMessageCallback("createHomeScreenBookmarkShortcut",
141 base::Bind(&BookmarksHandler::HandleCreateHomeScreenBookmarkShortcut,
142 base::Unretained(this)));
145 void BookmarksHandler::HandleGetBookmarks(const base::ListValue* args) {
146 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
148 bookmark_data_requested_ = true;
149 if (!AreModelsLoaded())
150 return; // is handled in Loaded()/PartnerShimLoaded() callback.
152 const BookmarkNode* node = GetNodeByID(args);
153 if (node)
154 QueryBookmarkFolder(node);
155 else
156 QueryInitialBookmarks();
159 void BookmarksHandler::HandleDeleteBookmark(const base::ListValue* args) {
160 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
161 if (!AreModelsLoaded())
162 return;
164 const BookmarkNode* node = GetNodeByID(args);
165 if (!node)
166 return;
168 if (!IsEditable(node)) {
169 NOTREACHED();
170 return;
173 if (partner_bookmarks_shim_->IsPartnerBookmark(node)) {
174 if (partner_bookmarks_shim_->GetPartnerBookmarksRoot() == node)
175 RecordBookmarkAction(BOOKMARK_ACTION_DELETE_ROOT_FOLDER_PARTNER);
176 else
177 RecordBookmarkAction(BOOKMARK_ACTION_DELETE_BOOKMARK_PARTNER);
178 partner_bookmarks_shim_->RemoveBookmark(node);
179 return;
182 const BookmarkNode* parent_node = node->parent();
183 bookmark_model_->Remove(parent_node, parent_node->GetIndexOf(node));
186 void BookmarksHandler::HandleEditBookmark(const base::ListValue* args) {
187 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
188 if (!AreModelsLoaded())
189 return;
191 const BookmarkNode* node = GetNodeByID(args);
192 if (!node)
193 return;
195 if (!IsEditable(node)) {
196 NOTREACHED();
197 return;
200 TabAndroid* tab = TabAndroid::FromWebContents(web_ui()->GetWebContents());
201 if (tab) {
202 if (partner_bookmarks_shim_->IsPartnerBookmark(node)) {
203 if (partner_bookmarks_shim_->GetPartnerBookmarksRoot() == node)
204 RecordBookmarkAction(BOOKMARK_ACTION_EDIT_ROOT_FOLDER_PARTNER);
205 else
206 RecordBookmarkAction(BOOKMARK_ACTION_EDIT_BOOKMARK_PARTNER);
208 tab->EditBookmark(node->id(),
209 GetTitle(node),
210 node->is_folder(),
211 partner_bookmarks_shim_->IsPartnerBookmark(node));
215 bool BookmarksHandler::AreModelsLoaded() const {
216 Profile* profile = Profile::FromBrowserContext(
217 web_ui()->GetWebContents()->GetBrowserContext());
218 if (!profile)
219 return false;
221 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile);
222 if (!model || !model->loaded())
223 return false;
225 return partner_bookmarks_shim_ && partner_bookmarks_shim_->IsLoaded();
228 void BookmarksHandler::NotifyModelChanged(const base::DictionaryValue& status) {
229 DCHECK(AreModelsLoaded());
231 if (bookmark_data_requested_ && !extensive_changes_)
232 web_ui()->CallJavascriptFunction("ntp.bookmarkChanged", status);
235 std::string BookmarksHandler::GetBookmarkIdForNtp(const BookmarkNode* node) {
236 DCHECK(AreModelsLoaded());
238 std::string prefix;
239 if (partner_bookmarks_shim_->IsPartnerBookmark(node))
240 prefix = "p";
241 else if (managed_bookmarks_shim_->IsManagedBookmark(node))
242 prefix = "m";
243 return prefix + Int64ToString(node->id());
246 void BookmarksHandler::SetParentInBookmarksResult(
247 const BookmarkNode* parent,
248 base::DictionaryValue* result) {
249 result->SetString(kParentIdParam, GetBookmarkIdForNtp(parent));
252 void BookmarksHandler::PopulateBookmark(const BookmarkNode* node,
253 base::ListValue* result) {
254 if (!result)
255 return;
257 DCHECK(AreModelsLoaded());
258 if (!IsReachable(node))
259 return;
261 base::DictionaryValue* filler_value = new base::DictionaryValue();
262 filler_value->SetString("title", GetTitle(node));
263 filler_value->SetBoolean("editable", IsEditable(node));
264 if (node->is_url()) {
265 filler_value->SetBoolean("folder", false);
266 filler_value->SetString("url", node->url().spec());
267 } else {
268 filler_value->SetBoolean("folder", true);
270 filler_value->SetString("id", GetBookmarkIdForNtp(node));
271 filler_value->SetString("type", BookmarkTypeAsString(node->type()));
272 result->Append(filler_value);
275 void BookmarksHandler::PopulateBookmarksInFolder(
276 const BookmarkNode* folder,
277 base::DictionaryValue* result) {
278 DCHECK(AreModelsLoaded());
279 if (!IsReachable(folder))
280 return;
282 base::ListValue* bookmarks = new base::ListValue();
284 // If this is the Mobile bookmarks folder then add the "Managed bookmarks"
285 // folder first, so that it's the first entry.
286 if (bookmark_model_ && folder == bookmark_model_->mobile_node() &&
287 managed_bookmarks_shim_->HasManagedBookmarks()) {
288 PopulateBookmark(managed_bookmarks_shim_->GetManagedBookmarksRoot(),
289 bookmarks);
292 for (int i = 0; i < folder->child_count(); i++) {
293 const BookmarkNode* bookmark= folder->GetChild(i);
294 PopulateBookmark(bookmark, bookmarks);
297 // Make sure we iterate over the partner's attach point
298 if (bookmark_model_ && folder == bookmark_model_->mobile_node() &&
299 partner_bookmarks_shim_->HasPartnerBookmarks()) {
300 PopulateBookmark(partner_bookmarks_shim_->GetPartnerBookmarksRoot(),
301 bookmarks);
304 base::ListValue* folder_hierarchy = new base::ListValue();
305 const BookmarkNode* parent = GetParentOf(folder);
307 while (parent != NULL) {
308 base::DictionaryValue* hierarchy_entry = new base::DictionaryValue();
309 if (IsRoot(parent))
310 hierarchy_entry->SetBoolean("root", true);
312 hierarchy_entry->SetString("title", GetTitle(parent));
313 hierarchy_entry->SetString("id", GetBookmarkIdForNtp(parent));
314 folder_hierarchy->Append(hierarchy_entry);
315 parent = GetParentOf(parent);
318 result->SetString("title", GetTitle(folder));
319 result->SetString("id", GetBookmarkIdForNtp(folder));
320 result->SetBoolean("root", IsRoot(folder));
321 result->Set("bookmarks", bookmarks);
322 result->Set("hierarchy", folder_hierarchy);
325 void BookmarksHandler::QueryBookmarkFolder(const BookmarkNode* node) {
326 DCHECK(AreModelsLoaded());
327 if (node->is_folder() && IsReachable(node)) {
328 base::DictionaryValue result;
329 PopulateBookmarksInFolder(node, &result);
330 SendResult(result);
331 } else {
332 // If we receive an ID that no longer maps to a bookmark folder, just
333 // return the initial bookmark folder.
334 QueryInitialBookmarks();
338 void BookmarksHandler::QueryInitialBookmarks() {
339 DCHECK(AreModelsLoaded());
340 base::DictionaryValue result;
341 PopulateBookmarksInFolder(bookmark_model_->mobile_node(), &result);
342 SendResult(result);
345 void BookmarksHandler::SendResult(const base::DictionaryValue& result) {
346 web_ui()->CallJavascriptFunction("ntp.bookmarks", result);
349 void BookmarksHandler::BookmarkModelLoaded(BookmarkModel* model,
350 bool ids_reassigned) {
351 if (AreModelsLoaded())
352 BookmarkModelChanged();
355 void BookmarksHandler::PartnerShimChanged(PartnerBookmarksShim* shim) {
356 if (AreModelsLoaded())
357 BookmarkModelChanged();
360 void BookmarksHandler::PartnerShimLoaded(PartnerBookmarksShim* shim) {
361 if (AreModelsLoaded())
362 BookmarkModelChanged();
365 void BookmarksHandler::ShimBeingDeleted(PartnerBookmarksShim* shim) {
366 partner_bookmarks_shim_ = NULL;
369 void BookmarksHandler::OnManagedBookmarksChanged() {
370 if (AreModelsLoaded())
371 BookmarkModelChanged();
374 void BookmarksHandler::ExtensiveBookmarkChangesBeginning(BookmarkModel* model) {
375 extensive_changes_ = true;
378 void BookmarksHandler::ExtensiveBookmarkChangesEnded(BookmarkModel* model) {
379 extensive_changes_ = false;
380 if (AreModelsLoaded())
381 BookmarkModelChanged();
384 void BookmarksHandler::BookmarkNodeRemoved(BookmarkModel* model,
385 const BookmarkNode* parent,
386 int old_index,
387 const BookmarkNode* node) {
388 if (!AreModelsLoaded())
389 return;
391 base::DictionaryValue result;
392 SetParentInBookmarksResult(parent, &result);
393 result.SetString(kNodeIdParam, Int64ToString(node->id()));
394 NotifyModelChanged(result);
397 void BookmarksHandler::BookmarkAllNodesRemoved(BookmarkModel* model) {
398 if (!AreModelsLoaded())
399 return;
401 if (bookmark_data_requested_ && !extensive_changes_)
402 web_ui()->CallJavascriptFunction("ntp.bookmarkChanged");
405 void BookmarksHandler::BookmarkNodeAdded(
406 BookmarkModel* model, const BookmarkNode* parent, int index) {
407 if (!AreModelsLoaded())
408 return;
410 base::DictionaryValue result;
411 SetParentInBookmarksResult(parent, &result);
412 NotifyModelChanged(result);
415 void BookmarksHandler::BookmarkNodeChanged(BookmarkModel* model,
416 const BookmarkNode* node) {
417 if (!AreModelsLoaded())
418 return;
420 DCHECK(!partner_bookmarks_shim_->IsPartnerBookmark(node));
421 base::DictionaryValue result;
422 SetParentInBookmarksResult(node->parent(), &result);
423 result.SetString(kNodeIdParam, Int64ToString(node->id()));
424 NotifyModelChanged(result);
427 void BookmarksHandler::BookmarkModelChanged() {
428 if (!AreModelsLoaded())
429 return;
431 if (bookmark_data_requested_ && !extensive_changes_)
432 web_ui()->CallJavascriptFunction("ntp.bookmarkChanged");
435 void BookmarksHandler::HandleCreateHomeScreenBookmarkShortcut(
436 const base::ListValue* args) {
437 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
438 if (!AreModelsLoaded())
439 return;
441 Profile* profile = Profile::FromBrowserContext(
442 web_ui()->GetWebContents()->GetBrowserContext());
443 if (!profile)
444 return;
446 const BookmarkNode* node = GetNodeByID(args);
447 if (!node)
448 return;
450 FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
451 profile, Profile::EXPLICIT_ACCESS);
452 favicon_service->GetRawFaviconForURL(
453 FaviconService::FaviconForURLParams(
454 node->url(),
455 chrome::TOUCH_PRECOMPOSED_ICON | chrome::TOUCH_ICON |
456 chrome::FAVICON,
457 0), // request the largest icon.
458 ui::SCALE_FACTOR_100P, // density doesn't matter for the largest icon.
459 base::Bind(&BookmarksHandler::OnShortcutFaviconDataAvailable,
460 base::Unretained(this),
461 node),
462 &cancelable_task_tracker_);
465 void BookmarksHandler::OnShortcutFaviconDataAvailable(
466 const BookmarkNode* node,
467 const chrome::FaviconBitmapResult& bitmap_result) {
468 if (!AreModelsLoaded())
469 return;
471 SkColor color = SK_ColorWHITE;
472 SkBitmap favicon_bitmap;
473 if (bitmap_result.is_valid()) {
474 color = GetDominantColorForFavicon(bitmap_result.bitmap_data);
475 gfx::PNGCodec::Decode(bitmap_result.bitmap_data->front(),
476 bitmap_result.bitmap_data->size(),
477 &favicon_bitmap);
479 TabAndroid* tab = TabAndroid::FromWebContents(web_ui()->GetWebContents());
480 if (tab) {
481 tab->AddShortcutToBookmark(node->url(),
482 GetTitle(node),
483 favicon_bitmap, SkColorGetR(color),
484 SkColorGetG(color), SkColorGetB(color));
488 const BookmarkNode* BookmarksHandler::GetNodeByID(
489 const base::ListValue* args) const {
490 DCHECK(AreModelsLoaded());
492 // Parses a bookmark ID passed back from the NTP. The IDs differ from the
493 // normal int64 bookmark ID because we prepend a "p" if the ID represents a
494 // partner bookmark, and an "m" if the ID represents a managed bookmark.
496 if (!args || args->empty())
497 return NULL;
499 std::string string_id;
500 if (!args->GetString(0, &string_id) || string_id.empty()) {
501 NOTREACHED();
502 return NULL;
505 bool is_partner = string_id[0] == 'p';
506 bool is_managed = string_id[0] == 'm';
508 if (is_partner || is_managed)
509 string_id = string_id.substr(1);
511 int64 id = 0;
512 if (!base::StringToInt64(string_id, &id)) {
513 NOTREACHED();
514 return NULL;
517 if (is_managed)
518 return managed_bookmarks_shim_->GetNodeByID(id);
520 if (is_partner)
521 return partner_bookmarks_shim_->GetNodeByID(id);
523 return bookmark_model_->GetNodeByID(id);
526 const BookmarkNode* BookmarksHandler::GetParentOf(
527 const BookmarkNode* node) const {
528 DCHECK(AreModelsLoaded());
529 if (node == managed_bookmarks_shim_->GetManagedBookmarksRoot() ||
530 node == partner_bookmarks_shim_->GetPartnerBookmarksRoot()) {
531 return bookmark_model_->mobile_node();
534 return node->parent();
537 base::string16 BookmarksHandler::GetTitle(const BookmarkNode* node) const {
538 DCHECK(AreModelsLoaded());
539 if (partner_bookmarks_shim_->IsPartnerBookmark(node))
540 return partner_bookmarks_shim_->GetTitle(node);
542 return node->GetTitle();
545 bool BookmarksHandler::IsReachable(const BookmarkNode* node) const {
546 DCHECK(AreModelsLoaded());
547 if (!partner_bookmarks_shim_->IsPartnerBookmark(node))
548 return true;
550 return partner_bookmarks_shim_->IsReachable(node);
553 bool BookmarksHandler::IsEditable(const BookmarkNode* node) const {
554 DCHECK(AreModelsLoaded());
556 // Reserved system nodes and managed bookmarks are not editable.
557 // Additionally, bookmark editing may be completely disabled
558 // via a managed preference.
559 if (!node ||
560 (node->type() != BookmarkNode::FOLDER &&
561 node->type() != BookmarkNode::URL)) {
562 return false;
565 const PrefService* pref = Profile::FromBrowserContext(
566 web_ui()->GetWebContents()->GetBrowserContext())->GetPrefs();
567 if (!pref->GetBoolean(prefs::kEditBookmarksEnabled))
568 return false;
570 if (partner_bookmarks_shim_->IsPartnerBookmark(node))
571 return true;
573 return !managed_bookmarks_shim_->IsManagedBookmark(node);
576 bool BookmarksHandler::IsRoot(const BookmarkNode* node) const {
577 DCHECK(AreModelsLoaded());
579 return node->is_root() &&
580 node != partner_bookmarks_shim_->GetPartnerBookmarksRoot() &&
581 node != managed_bookmarks_shim_->GetManagedBookmarksRoot();