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
;
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
) {
57 case BookmarkNode::URL
:
59 case BookmarkNode::FOLDER
:
61 case BookmarkNode::BOOKMARK_BAR
:
62 return "BOOKMARK_BAR";
63 case BookmarkNode::OTHER_NODE
:
65 case BookmarkNode::MOBILE
:
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
);
83 BookmarksHandler::BookmarksHandler()
84 : bookmark_model_(NULL
),
85 partner_bookmarks_shim_(NULL
),
86 bookmark_data_requested_(false),
87 extensive_changes_(false) {
90 BookmarksHandler::~BookmarksHandler() {
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
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
);
154 QueryBookmarkFolder(node
);
156 QueryInitialBookmarks();
159 void BookmarksHandler::HandleDeleteBookmark(const base::ListValue
* args
) {
160 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
161 if (!AreModelsLoaded())
164 const BookmarkNode
* node
= GetNodeByID(args
);
168 if (!IsEditable(node
)) {
173 if (partner_bookmarks_shim_
->IsPartnerBookmark(node
)) {
174 if (partner_bookmarks_shim_
->GetPartnerBookmarksRoot() == node
)
175 RecordBookmarkAction(BOOKMARK_ACTION_DELETE_ROOT_FOLDER_PARTNER
);
177 RecordBookmarkAction(BOOKMARK_ACTION_DELETE_BOOKMARK_PARTNER
);
178 partner_bookmarks_shim_
->RemoveBookmark(node
);
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())
191 const BookmarkNode
* node
= GetNodeByID(args
);
195 if (!IsEditable(node
)) {
200 TabAndroid
* tab
= TabAndroid::FromWebContents(web_ui()->GetWebContents());
202 if (partner_bookmarks_shim_
->IsPartnerBookmark(node
)) {
203 if (partner_bookmarks_shim_
->GetPartnerBookmarksRoot() == node
)
204 RecordBookmarkAction(BOOKMARK_ACTION_EDIT_ROOT_FOLDER_PARTNER
);
206 RecordBookmarkAction(BOOKMARK_ACTION_EDIT_BOOKMARK_PARTNER
);
208 tab
->EditBookmark(node
->id(),
211 partner_bookmarks_shim_
->IsPartnerBookmark(node
));
215 bool BookmarksHandler::AreModelsLoaded() const {
216 Profile
* profile
= Profile::FromBrowserContext(
217 web_ui()->GetWebContents()->GetBrowserContext());
221 BookmarkModel
* model
= BookmarkModelFactory::GetForProfile(profile
);
222 if (!model
|| !model
->loaded())
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());
239 if (partner_bookmarks_shim_
->IsPartnerBookmark(node
))
241 else if (managed_bookmarks_shim_
->IsManagedBookmark(node
))
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
) {
257 DCHECK(AreModelsLoaded());
258 if (!IsReachable(node
))
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());
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
))
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(),
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(),
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();
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
);
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
);
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
,
387 const BookmarkNode
* node
) {
388 if (!AreModelsLoaded())
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())
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())
410 base::DictionaryValue result
;
411 SetParentInBookmarksResult(parent
, &result
);
412 NotifyModelChanged(result
);
415 void BookmarksHandler::BookmarkNodeChanged(BookmarkModel
* model
,
416 const BookmarkNode
* node
) {
417 if (!AreModelsLoaded())
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())
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())
441 Profile
* profile
= Profile::FromBrowserContext(
442 web_ui()->GetWebContents()->GetBrowserContext());
446 const BookmarkNode
* node
= GetNodeByID(args
);
450 FaviconService
* favicon_service
= FaviconServiceFactory::GetForProfile(
451 profile
, Profile::EXPLICIT_ACCESS
);
452 favicon_service
->GetRawFaviconForURL(
453 FaviconService::FaviconForURLParams(
455 chrome::TOUCH_PRECOMPOSED_ICON
| chrome::TOUCH_ICON
|
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),
462 &cancelable_task_tracker_
);
465 void BookmarksHandler::OnShortcutFaviconDataAvailable(
466 const BookmarkNode
* node
,
467 const chrome::FaviconBitmapResult
& bitmap_result
) {
468 if (!AreModelsLoaded())
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(),
479 TabAndroid
* tab
= TabAndroid::FromWebContents(web_ui()->GetWebContents());
481 tab
->AddShortcutToBookmark(node
->url(),
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())
499 std::string string_id
;
500 if (!args
->GetString(0, &string_id
) || string_id
.empty()) {
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);
512 if (!base::StringToInt64(string_id
, &id
)) {
518 return managed_bookmarks_shim_
->GetNodeByID(id
);
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
))
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.
560 (node
->type() != BookmarkNode::FOLDER
&&
561 node
->type() != BookmarkNode::URL
)) {
565 const PrefService
* pref
= Profile::FromBrowserContext(
566 web_ui()->GetWebContents()->GetBrowserContext())->GetPrefs();
567 if (!pref
->GetBoolean(prefs::kEditBookmarksEnabled
))
570 if (partner_bookmarks_shim_
->IsPartnerBookmark(node
))
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();