Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1391)

Side by Side Diff: chrome/browser/extensions/api/bookmarks/bookmarks_api.cc

Issue 11778097: Revert revision 176015 (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: Created 7 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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.
4
5 #include "chrome/browser/extensions/api/bookmarks/bookmarks_api.h"
6
7 #include "base/bind.h"
8 #include "base/file_path.h"
9 #include "base/i18n/file_util_icu.h"
10 #include "base/i18n/time_formatting.h"
11 #include "base/json/json_writer.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/path_service.h"
14 #include "base/prefs/public/pref_service_base.h"
15 #include "base/sha1.h"
16 #include "base/stl_util.h"
17 #include "base/string16.h"
18 #include "base/string_number_conversions.h"
19 #include "base/string_util.h"
20 #include "base/time.h"
21 #include "base/utf_string_conversions.h"
22 #include "chrome/browser/bookmarks/bookmark_codec.h"
23 #include "chrome/browser/bookmarks/bookmark_html_writer.h"
24 #include "chrome/browser/bookmarks/bookmark_model.h"
25 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
26 #include "chrome/browser/bookmarks/bookmark_utils.h"
27 #include "chrome/browser/extensions/api/bookmarks/bookmark_api_constants.h"
28 #include "chrome/browser/extensions/api/bookmarks/bookmark_api_helpers.h"
29 #include "chrome/browser/extensions/event_router.h"
30 #include "chrome/browser/extensions/extension_function_dispatcher.h"
31 #include "chrome/browser/extensions/extension_system.h"
32 #include "chrome/browser/extensions/extensions_quota_service.h"
33 #include "chrome/browser/importer/importer_data_types.h"
34 #include "chrome/browser/importer/importer_host.h"
35 #include "chrome/browser/profiles/profile.h"
36 #include "chrome/browser/ui/chrome_select_file_policy.h"
37 #include "chrome/common/chrome_notification_types.h"
38 #include "chrome/common/chrome_paths.h"
39 #include "chrome/common/extensions/api/bookmarks.h"
40 #include "chrome/common/pref_names.h"
41 #include "content/public/browser/notification_service.h"
42 #include "grit/generated_resources.h"
43 #include "ui/base/l10n/l10n_util.h"
44
45 namespace extensions {
46
47 namespace keys = bookmark_api_constants;
48 namespace bookmarks = api::bookmarks;
49
50 using base::TimeDelta;
51 using bookmarks::BookmarkTreeNode;
52 using content::BrowserThread;
53 using content::WebContents;
54
55 typedef QuotaLimitHeuristic::Bucket Bucket;
56 typedef QuotaLimitHeuristic::Config Config;
57 typedef QuotaLimitHeuristic::BucketList BucketList;
58 typedef ExtensionsQuotaService::TimedLimit TimedLimit;
59 typedef ExtensionsQuotaService::SustainedLimit SustainedLimit;
60 typedef QuotaLimitHeuristic::BucketMapper BucketMapper;
61
62 namespace {
63
64 // Generates a default path (including a default filename) that will be
65 // used for pre-populating the "Export Bookmarks" file chooser dialog box.
66 FilePath GetDefaultFilepathForBookmarkExport() {
67 base::Time time = base::Time::Now();
68
69 // Concatenate a date stamp to the filename.
70 #if defined(OS_POSIX)
71 FilePath::StringType filename =
72 l10n_util::GetStringFUTF8(IDS_EXPORT_BOOKMARKS_DEFAULT_FILENAME,
73 base::TimeFormatShortDateNumeric(time));
74 #elif defined(OS_WIN)
75 FilePath::StringType filename =
76 l10n_util::GetStringFUTF16(IDS_EXPORT_BOOKMARKS_DEFAULT_FILENAME,
77 base::TimeFormatShortDateNumeric(time));
78 #endif
79
80 file_util::ReplaceIllegalCharactersInPath(&filename, '_');
81
82 FilePath default_path;
83 PathService::Get(chrome::DIR_USER_DOCUMENTS, &default_path);
84 return default_path.Append(filename);
85 }
86
87 } // namespace
88
89 void BookmarksFunction::Run() {
90 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
91 if (!model->IsLoaded()) {
92 // Bookmarks are not ready yet. We'll wait.
93 registrar_.Add(
94 this, chrome::NOTIFICATION_BOOKMARK_MODEL_LOADED,
95 content::NotificationService::AllBrowserContextsAndSources());
96 AddRef(); // Balanced in Observe().
97 return;
98 }
99
100 bool success = RunImpl();
101 if (success) {
102 content::NotificationService::current()->Notify(
103 chrome::NOTIFICATION_EXTENSION_BOOKMARKS_API_INVOKED,
104 content::Source<const Extension>(GetExtension()),
105 content::Details<const BookmarksFunction>(this));
106 }
107 SendResponse(success);
108 }
109
110 bool BookmarksFunction::GetBookmarkIdAsInt64(
111 const std::string& id_string, int64* id) {
112 if (base::StringToInt64(id_string, id))
113 return true;
114
115 error_ = keys::kInvalidIdError;
116 return false;
117 }
118
119 bool BookmarksFunction::EditBookmarksEnabled() {
120 PrefServiceBase* prefs = PrefServiceBase::FromBrowserContext(profile_);
121 if (prefs->GetBoolean(prefs::kEditBookmarksEnabled))
122 return true;
123 error_ = keys::kEditBookmarksDisabled;
124 return false;
125 }
126
127 void BookmarksFunction::Observe(int type,
128 const content::NotificationSource& source,
129 const content::NotificationDetails& details) {
130 DCHECK(type == chrome::NOTIFICATION_BOOKMARK_MODEL_LOADED);
131 Profile* source_profile = content::Source<Profile>(source).ptr();
132 if (!source_profile || !source_profile->IsSameProfile(profile()))
133 return;
134
135 DCHECK(BookmarkModelFactory::GetForProfile(profile())->IsLoaded());
136 Run();
137 Release(); // Balanced in Run().
138 }
139
140 BookmarkEventRouter::BookmarkEventRouter(BookmarkModel* model) : model_(model) {
141 model_->AddObserver(this);
142 }
143
144 BookmarkEventRouter::~BookmarkEventRouter() {
145 if (model_) {
146 model_->RemoveObserver(this);
147 }
148 }
149
150 void BookmarkEventRouter::DispatchEvent(
151 Profile* profile,
152 const char* event_name,
153 scoped_ptr<ListValue> event_args) {
154 if (extensions::ExtensionSystem::Get(profile)->event_router()) {
155 extensions::ExtensionSystem::Get(profile)->event_router()->BroadcastEvent(
156 make_scoped_ptr(new extensions::Event(event_name, event_args.Pass())));
157 }
158 }
159
160 void BookmarkEventRouter::Loaded(BookmarkModel* model, bool ids_reassigned) {
161 // TODO(erikkay): Perhaps we should send this event down to the extension
162 // so they know when it's safe to use the API?
163 }
164
165 void BookmarkEventRouter::BookmarkModelBeingDeleted(BookmarkModel* model) {
166 model_ = NULL;
167 }
168
169 void BookmarkEventRouter::BookmarkNodeMoved(BookmarkModel* model,
170 const BookmarkNode* old_parent,
171 int old_index,
172 const BookmarkNode* new_parent,
173 int new_index) {
174 scoped_ptr<ListValue> args(new ListValue());
175 const BookmarkNode* node = new_parent->GetChild(new_index);
176 args->Append(new StringValue(base::Int64ToString(node->id())));
177 DictionaryValue* object_args = new DictionaryValue();
178 object_args->SetString(keys::kParentIdKey,
179 base::Int64ToString(new_parent->id()));
180 object_args->SetInteger(keys::kIndexKey, new_index);
181 object_args->SetString(keys::kOldParentIdKey,
182 base::Int64ToString(old_parent->id()));
183 object_args->SetInteger(keys::kOldIndexKey, old_index);
184 args->Append(object_args);
185
186 DispatchEvent(model->profile(), keys::kOnBookmarkMoved, args.Pass());
187 }
188
189 void BookmarkEventRouter::BookmarkNodeAdded(BookmarkModel* model,
190 const BookmarkNode* parent,
191 int index) {
192 scoped_ptr<ListValue> args(new ListValue());
193 const BookmarkNode* node = parent->GetChild(index);
194 args->Append(new StringValue(base::Int64ToString(node->id())));
195 scoped_ptr<BookmarkTreeNode> tree_node(
196 bookmark_api_helpers::GetBookmarkTreeNode(node, false, false));
197 args->Append(tree_node->ToValue().release());
198
199 DispatchEvent(model->profile(), keys::kOnBookmarkCreated, args.Pass());
200 }
201
202 void BookmarkEventRouter::BookmarkNodeRemoved(BookmarkModel* model,
203 const BookmarkNode* parent,
204 int index,
205 const BookmarkNode* node) {
206 scoped_ptr<ListValue> args(new ListValue());
207 args->Append(new StringValue(base::Int64ToString(node->id())));
208 DictionaryValue* object_args = new DictionaryValue();
209 object_args->SetString(keys::kParentIdKey,
210 base::Int64ToString(parent->id()));
211 object_args->SetInteger(keys::kIndexKey, index);
212 args->Append(object_args);
213
214 DispatchEvent(model->profile(), keys::kOnBookmarkRemoved, args.Pass());
215 }
216
217 void BookmarkEventRouter::BookmarkNodeChanged(BookmarkModel* model,
218 const BookmarkNode* node) {
219 scoped_ptr<ListValue> args(new ListValue());
220 args->Append(new StringValue(base::Int64ToString(node->id())));
221
222 // TODO(erikkay) The only three things that BookmarkModel sends this
223 // notification for are title, url and favicon. Since we're currently
224 // ignoring favicon and since the notification doesn't say which one anyway,
225 // for now we only include title and url. The ideal thing would be to change
226 // BookmarkModel to indicate what changed.
227 DictionaryValue* object_args = new DictionaryValue();
228 object_args->SetString(keys::kTitleKey, node->GetTitle());
229 if (node->is_url())
230 object_args->SetString(keys::kUrlKey, node->url().spec());
231 args->Append(object_args);
232
233 DispatchEvent(model->profile(), keys::kOnBookmarkChanged, args.Pass());
234 }
235
236 void BookmarkEventRouter::BookmarkNodeFaviconChanged(BookmarkModel* model,
237 const BookmarkNode* node) {
238 // TODO(erikkay) anything we should do here?
239 }
240
241 void BookmarkEventRouter::BookmarkNodeChildrenReordered(
242 BookmarkModel* model,
243 const BookmarkNode* node) {
244 scoped_ptr<ListValue> args(new ListValue());
245 args->Append(new StringValue(base::Int64ToString(node->id())));
246 int childCount = node->child_count();
247 ListValue* children = new ListValue();
248 for (int i = 0; i < childCount; ++i) {
249 const BookmarkNode* child = node->GetChild(i);
250 Value* child_id = new StringValue(base::Int64ToString(child->id()));
251 children->Append(child_id);
252 }
253 DictionaryValue* reorder_info = new DictionaryValue();
254 reorder_info->Set(keys::kChildIdsKey, children);
255 args->Append(reorder_info);
256
257 DispatchEvent(model->profile(), keys::kOnBookmarkChildrenReordered,
258 args.Pass());
259 }
260
261 void BookmarkEventRouter::ExtensiveBookmarkChangesBeginning(
262 BookmarkModel* model) {
263 scoped_ptr<ListValue> args(new ListValue());
264 DispatchEvent(model->profile(),
265 keys::kOnBookmarkImportBegan,
266 args.Pass());
267 }
268
269 void BookmarkEventRouter::ExtensiveBookmarkChangesEnded(BookmarkModel* model) {
270 scoped_ptr<ListValue> args(new ListValue());
271 DispatchEvent(model->profile(),
272 keys::kOnBookmarkImportEnded,
273 args.Pass());
274 }
275
276 BookmarkAPI::BookmarkAPI(Profile* profile) : profile_(profile) {
277 ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
278 this, keys::kOnBookmarkCreated);
279 ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
280 this, keys::kOnBookmarkRemoved);
281 ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
282 this, keys::kOnBookmarkChanged);
283 ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
284 this, keys::kOnBookmarkMoved);
285 ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
286 this, keys::kOnBookmarkChildrenReordered);
287 ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
288 this, keys::kOnBookmarkImportBegan);
289 ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
290 this, keys::kOnBookmarkImportEnded);
291 }
292
293 BookmarkAPI::~BookmarkAPI() {
294 }
295
296 void BookmarkAPI::Shutdown() {
297 ExtensionSystem::Get(profile_)->event_router()->UnregisterObserver(this);
298 }
299
300 void BookmarkAPI::OnListenerAdded(const EventListenerInfo& details) {
301 bookmark_event_router_.reset(new BookmarkEventRouter(
302 BookmarkModelFactory::GetForProfile(profile_)));
303 ExtensionSystem::Get(profile_)->event_router()->UnregisterObserver(this);
304 }
305
306 bool BookmarksGetTreeFunction::RunImpl() {
307 scoped_ptr<bookmarks::Get::Params> params(
308 bookmarks::Get::Params::Create(*args_));
309 EXTENSION_FUNCTION_VALIDATE(params.get());
310
311 std::vector<linked_ptr<BookmarkTreeNode> > nodes;
312 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
313 if (params->id_or_id_list_type ==
314 bookmarks::Get::Params::ID_OR_ID_LIST_ARRAY) {
315 std::vector<std::string>* ids = params->id_or_id_list_array.get();
316 size_t count = ids->size();
317 EXTENSION_FUNCTION_VALIDATE(count > 0);
318 for (size_t i = 0; i < count; ++i) {
319 int64 id;
320 if (!GetBookmarkIdAsInt64(ids->at(i), &id))
321 return false;
322 const BookmarkNode* node = model->GetNodeByID(id);
323 if (!node) {
324 error_ = keys::kNoNodeError;
325 return false;
326 } else {
327 bookmark_api_helpers::AddNode(node, &nodes, false);
328 }
329 }
330 } else {
331 int64 id;
332 if (!GetBookmarkIdAsInt64(*params->id_or_id_list_string, &id))
333 return false;
334 const BookmarkNode* node = model->GetNodeByID(id);
335 if (!node) {
336 error_ = keys::kNoNodeError;
337 return false;
338 }
339 bookmark_api_helpers::AddNode(node, &nodes, false);
340 }
341
342 results_ = bookmarks::Get::Results::Create(nodes);
343 return true;
344 }
345
346 bool BookmarksGetChildrenFunction::RunImpl() {
347 scoped_ptr<bookmarks::GetChildren::Params> params(
348 bookmarks::GetChildren::Params::Create(*args_));
349 EXTENSION_FUNCTION_VALIDATE(params.get());
350
351 int64 id;
352 if (!GetBookmarkIdAsInt64(params->id, &id))
353 return false;
354
355 std::vector<linked_ptr<BookmarkTreeNode> > nodes;
356 const BookmarkNode* node =
357 BookmarkModelFactory::GetForProfile(profile())->GetNodeByID(id);
358 if (!node) {
359 error_ = keys::kNoNodeError;
360 return false;
361 }
362 int child_count = node->child_count();
363 for (int i = 0; i < child_count; ++i) {
364 const BookmarkNode* child = node->GetChild(i);
365 bookmark_api_helpers::AddNode(child, &nodes, false);
366 }
367
368 results_ = bookmarks::GetChildren::Results::Create(nodes);
369 return true;
370 }
371
372 bool BookmarksGetFunction::RunImpl() {
373 scoped_ptr<bookmarks::GetRecent::Params> params(
374 bookmarks::GetRecent::Params::Create(*args_));
375 EXTENSION_FUNCTION_VALIDATE(params.get());
376 if (params->number_of_items < 1)
377 return false;
378
379 std::vector<const BookmarkNode*> nodes;
380 bookmark_utils::GetMostRecentlyAddedEntries(
381 BookmarkModelFactory::GetForProfile(profile()),
382 params->number_of_items,
383 &nodes);
384
385 std::vector<linked_ptr<BookmarkTreeNode> > tree_nodes;
386 std::vector<const BookmarkNode*>::iterator i = nodes.begin();
387 for (; i != nodes.end(); ++i) {
388 const BookmarkNode* node = *i;
389 bookmark_api_helpers::AddNode(node, &tree_nodes, false);
390 }
391
392 results_ = bookmarks::GetRecent::Results::Create(tree_nodes);
393 return true;
394 }
395
396 bool BookmarksGetSubTreeFunction::RunImpl() {
397 std::vector<linked_ptr<BookmarkTreeNode> > nodes;
398 const BookmarkNode* node =
399 BookmarkModelFactory::GetForProfile(profile())->root_node();
400 bookmark_api_helpers::AddNode(node, &nodes, true);
401 results_ = bookmarks::GetTree::Results::Create(nodes);
402 return true;
403 }
404
405 bool BookmarksGetRecentFunction::RunImpl() {
406 scoped_ptr<bookmarks::GetSubTree::Params> params(
407 bookmarks::GetSubTree::Params::Create(*args_));
408 EXTENSION_FUNCTION_VALIDATE(params.get());
409
410 int64 id;
411 if (!GetBookmarkIdAsInt64(params->id, &id))
412 return false;
413
414 const BookmarkNode* node =
415 BookmarkModelFactory::GetForProfile(profile())->GetNodeByID(id);
416 if (!node) {
417 error_ = keys::kNoNodeError;
418 return false;
419 }
420
421 std::vector<linked_ptr<BookmarkTreeNode> > nodes;
422 bookmark_api_helpers::AddNode(node, &nodes, true);
423 results_ = bookmarks::GetSubTree::Results::Create(nodes);
424 return true;
425 }
426
427 bool BookmarksSearchFunction::RunImpl() {
428 scoped_ptr<bookmarks::Search::Params> params(
429 bookmarks::Search::Params::Create(*args_));
430 EXTENSION_FUNCTION_VALIDATE(params.get());
431
432 PrefServiceBase* prefs = PrefServiceBase::FromBrowserContext(profile_);
433 std::string lang = prefs->GetString(prefs::kAcceptLanguages);
434 std::vector<const BookmarkNode*> nodes;
435 bookmark_utils::GetBookmarksContainingText(
436 BookmarkModelFactory::GetForProfile(profile()),
437 UTF8ToUTF16(params->query),
438 std::numeric_limits<int>::max(),
439 lang,
440 &nodes);
441
442 std::vector<linked_ptr<BookmarkTreeNode> > tree_nodes;
443 for (std::vector<const BookmarkNode*>::iterator node_iter = nodes.begin();
444 node_iter != nodes.end(); ++node_iter) {
445 bookmark_api_helpers::AddNode(*node_iter, &tree_nodes, false);
446 }
447
448 results_ = bookmarks::Search::Results::Create(tree_nodes);
449 return true;
450 }
451
452 // static
453 bool BookmarksRemoveFunction::ExtractIds(const ListValue* args,
454 std::list<int64>* ids,
455 bool* invalid_id) {
456 std::string id_string;
457 if (!args->GetString(0, &id_string))
458 return false;
459 int64 id;
460 if (base::StringToInt64(id_string, &id))
461 ids->push_back(id);
462 else
463 *invalid_id = true;
464 return true;
465 }
466
467 bool BookmarksRemoveFunction::RunImpl() {
468 if (!EditBookmarksEnabled())
469 return false;
470
471 scoped_ptr<bookmarks::Remove::Params> params(
472 bookmarks::Remove::Params::Create(*args_));
473 EXTENSION_FUNCTION_VALIDATE(params.get());
474
475 int64 id;
476 if (!base::StringToInt64(params->id, &id)) {
477 error_ = keys::kInvalidIdError;
478 return false;
479 }
480
481 bool recursive = false;
482 if (name() == BookmarksRemoveTreeFunction::function_name())
483 recursive = true;
484
485 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
486 if (!bookmark_api_helpers::RemoveNode(model, id, recursive, &error_))
487 return false;
488
489 return true;
490 }
491
492 bool BookmarksCreateFunction::RunImpl() {
493 if (!EditBookmarksEnabled())
494 return false;
495
496 scoped_ptr<bookmarks::Create::Params> params(
497 bookmarks::Create::Params::Create(*args_));
498 EXTENSION_FUNCTION_VALIDATE(params.get());
499
500 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
501 int64 parentId;
502
503 if (!params->bookmark.parent_id.get()) {
504 // Optional, default to "other bookmarks".
505 parentId = model->other_node()->id();
506 } else {
507 if (!GetBookmarkIdAsInt64(*params->bookmark.parent_id, &parentId))
508 return false;
509 }
510 const BookmarkNode* parent = model->GetNodeByID(parentId);
511 if (!parent) {
512 error_ = keys::kNoParentError;
513 return false;
514 }
515 if (parent->is_root()) { // Can't create children of the root.
516 error_ = keys::kModifySpecialError;
517 return false;
518 }
519
520 int index;
521 if (!params->bookmark.index.get()) { // Optional (defaults to end).
522 index = parent->child_count();
523 } else {
524 index = *params->bookmark.index;
525 if (index > parent->child_count() || index < 0) {
526 error_ = keys::kInvalidIndexError;
527 return false;
528 }
529 }
530
531 string16 title; // Optional.
532 if (params->bookmark.title.get())
533 title = UTF8ToUTF16(*params->bookmark.title.get());
534
535 std::string url_string; // Optional.
536 if (params->bookmark.url.get())
537 url_string = *params->bookmark.url.get();
538
539 GURL url(url_string);
540 if (!url_string.empty() && !url.is_valid()) {
541 error_ = keys::kInvalidUrlError;
542 return false;
543 }
544
545 const BookmarkNode* node;
546 if (url_string.length())
547 node = model->AddURL(parent, index, title, url);
548 else
549 node = model->AddFolder(parent, index, title);
550 DCHECK(node);
551 if (!node) {
552 error_ = keys::kNoNodeError;
553 return false;
554 }
555
556 scoped_ptr<BookmarkTreeNode> ret(
557 bookmark_api_helpers::GetBookmarkTreeNode(node, false, false));
558 results_ = bookmarks::Create::Results::Create(*ret);
559
560 return true;
561 }
562
563 // static
564 bool BookmarksMoveFunction::ExtractIds(const ListValue* args,
565 std::list<int64>* ids,
566 bool* invalid_id) {
567 // For now, Move accepts ID parameters in the same way as an Update.
568 return BookmarksUpdateFunction::ExtractIds(args, ids, invalid_id);
569 }
570
571 bool BookmarksMoveFunction::RunImpl() {
572 if (!EditBookmarksEnabled())
573 return false;
574
575 scoped_ptr<bookmarks::Move::Params> params(
576 bookmarks::Move::Params::Create(*args_));
577 EXTENSION_FUNCTION_VALIDATE(params.get());
578
579 int64 id;
580 if (!base::StringToInt64(params->id, &id)) {
581 error_ = keys::kInvalidIdError;
582 return false;
583 }
584
585 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
586 const BookmarkNode* node = model->GetNodeByID(id);
587 if (!node) {
588 error_ = keys::kNoNodeError;
589 return false;
590 }
591 if (model->is_permanent_node(node)) {
592 error_ = keys::kModifySpecialError;
593 return false;
594 }
595
596 const BookmarkNode* parent = NULL;
597 if (!params->destination.parent_id.get()) {
598 // Optional, defaults to current parent.
599 parent = node->parent();
600 } else {
601 int64 parentId;
602 if (!GetBookmarkIdAsInt64(*params->destination.parent_id, &parentId))
603 return false;
604
605 parent = model->GetNodeByID(parentId);
606 }
607 if (!parent) {
608 error_ = keys::kNoParentError;
609 // TODO(erikkay) return an error message.
610 return false;
611 }
612 if (parent == model->root_node()) {
613 error_ = keys::kModifySpecialError;
614 return false;
615 }
616
617 int index;
618 if (params->destination.index.get()) { // Optional (defaults to end).
619 index = *params->destination.index;
620 if (index > parent->child_count() || index < 0) {
621 error_ = keys::kInvalidIndexError;
622 return false;
623 }
624 } else {
625 index = parent->child_count();
626 }
627
628 model->Move(node, parent, index);
629
630 scoped_ptr<BookmarkTreeNode> tree_node(
631 bookmark_api_helpers::GetBookmarkTreeNode(node, false, false));
632 results_ = bookmarks::Move::Results::Create(*tree_node);
633
634 return true;
635 }
636
637 // static
638 bool BookmarksUpdateFunction::ExtractIds(const ListValue* args,
639 std::list<int64>* ids,
640 bool* invalid_id) {
641 // For now, Update accepts ID parameters in the same way as an Remove.
642 return BookmarksRemoveFunction::ExtractIds(args, ids, invalid_id);
643 }
644
645 bool BookmarksUpdateFunction::RunImpl() {
646 if (!EditBookmarksEnabled())
647 return false;
648
649 scoped_ptr<bookmarks::Update::Params> params(
650 bookmarks::Update::Params::Create(*args_));
651 EXTENSION_FUNCTION_VALIDATE(params.get());
652
653 int64 id;
654 if (!base::StringToInt64(params->id, &id)) {
655 error_ = keys::kInvalidIdError;
656 return false;
657 }
658
659 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
660
661 // Optional but we need to distinguish non present from an empty title.
662 string16 title;
663 bool has_title = false;
664 if (params->changes.title.get()) {
665 title = UTF8ToUTF16(*params->changes.title);
666 has_title = true;
667 }
668
669 // Optional.
670 std::string url_string;
671 if (params->changes.url.get())
672 url_string = *params->changes.url;
673 GURL url(url_string);
674 if (!url_string.empty() && !url.is_valid()) {
675 error_ = keys::kInvalidUrlError;
676 return false;
677 }
678
679 const BookmarkNode* node = model->GetNodeByID(id);
680 if (!node) {
681 error_ = keys::kNoNodeError;
682 return false;
683 }
684 if (model->is_permanent_node(node)) {
685 error_ = keys::kModifySpecialError;
686 return false;
687 }
688 if (has_title)
689 model->SetTitle(node, title);
690 if (!url.is_empty())
691 model->SetURL(node, url);
692
693 scoped_ptr<BookmarkTreeNode> tree_node(
694 bookmark_api_helpers::GetBookmarkTreeNode(node, false, false));
695 results_ = bookmarks::Update::Results::Create(*tree_node);
696 return true;
697 }
698
699 // Mapper superclass for BookmarkFunctions.
700 template <typename BucketIdType>
701 class BookmarkBucketMapper : public BucketMapper {
702 public:
703 virtual ~BookmarkBucketMapper() { STLDeleteValues(&buckets_); }
704 protected:
705 Bucket* GetBucket(const BucketIdType& id) {
706 Bucket* b = buckets_[id];
707 if (b == NULL) {
708 b = new Bucket();
709 buckets_[id] = b;
710 }
711 return b;
712 }
713 private:
714 std::map<BucketIdType, Bucket*> buckets_;
715 };
716
717 // Mapper for 'bookmarks.create'. Maps "same input to bookmarks.create" to a
718 // unique bucket.
719 class CreateBookmarkBucketMapper : public BookmarkBucketMapper<std::string> {
720 public:
721 explicit CreateBookmarkBucketMapper(Profile* profile) : profile_(profile) {}
722 // TODO(tim): This should share code with BookmarksCreateFunction::RunImpl,
723 // but I can't figure out a good way to do that with all the macros.
724 virtual void GetBucketsForArgs(const ListValue* args, BucketList* buckets) {
725 const DictionaryValue* json;
726 if (!args->GetDictionary(0, &json))
727 return;
728
729 std::string parent_id;
730 if (json->HasKey(keys::kParentIdKey)) {
731 if (!json->GetString(keys::kParentIdKey, &parent_id))
732 return;
733 }
734 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
735
736 int64 parent_id_int64;
737 base::StringToInt64(parent_id, &parent_id_int64);
738 const BookmarkNode* parent = model->GetNodeByID(parent_id_int64);
739 if (!parent)
740 return;
741
742 std::string bucket_id = UTF16ToUTF8(parent->GetTitle());
743 std::string title;
744 json->GetString(keys::kTitleKey, &title);
745 std::string url_string;
746 json->GetString(keys::kUrlKey, &url_string);
747
748 bucket_id += title;
749 bucket_id += url_string;
750 // 20 bytes (SHA1 hash length) is very likely less than most of the
751 // |bucket_id| strings we construct here, so we hash it to save space.
752 buckets->push_back(GetBucket(base::SHA1HashString(bucket_id)));
753 }
754 private:
755 Profile* profile_;
756 };
757
758 // Mapper for 'bookmarks.remove'.
759 class RemoveBookmarksBucketMapper : public BookmarkBucketMapper<std::string> {
760 public:
761 explicit RemoveBookmarksBucketMapper(Profile* profile) : profile_(profile) {}
762 virtual void GetBucketsForArgs(const ListValue* args, BucketList* buckets) {
763 typedef std::list<int64> IdList;
764 IdList ids;
765 bool invalid_id = false;
766 if (!BookmarksRemoveFunction::ExtractIds(args, &ids, &invalid_id) ||
767 invalid_id) {
768 return;
769 }
770
771 for (IdList::iterator it = ids.begin(); it != ids.end(); ++it) {
772 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
773 const BookmarkNode* node = model->GetNodeByID(*it);
774 if (!node || node->is_root())
775 return;
776
777 std::string bucket_id;
778 bucket_id += UTF16ToUTF8(node->parent()->GetTitle());
779 bucket_id += UTF16ToUTF8(node->GetTitle());
780 bucket_id += node->url().spec();
781 buckets->push_back(GetBucket(base::SHA1HashString(bucket_id)));
782 }
783 }
784 private:
785 Profile* profile_;
786 };
787
788 // Mapper for any bookmark function accepting bookmark IDs as parameters, where
789 // a distinct ID corresponds to a single item in terms of quota limiting. This
790 // is inappropriate for bookmarks.remove, for example, since repeated removals
791 // of the same item will actually have a different ID each time.
792 template <class FunctionType>
793 class BookmarkIdMapper : public BookmarkBucketMapper<int64> {
794 public:
795 typedef std::list<int64> IdList;
796 virtual void GetBucketsForArgs(const ListValue* args, BucketList* buckets) {
797 IdList ids;
798 bool invalid_id = false;
799 if (!FunctionType::ExtractIds(args, &ids, &invalid_id) || invalid_id)
800 return;
801 for (IdList::iterator it = ids.begin(); it != ids.end(); ++it)
802 buckets->push_back(GetBucket(*it));
803 }
804 };
805
806 // Builds heuristics for all BookmarkFunctions using specialized BucketMappers.
807 class BookmarksQuotaLimitFactory {
808 public:
809 // For id-based bookmark functions.
810 template <class FunctionType>
811 static void Build(QuotaLimitHeuristics* heuristics) {
812 BuildWithMappers(heuristics, new BookmarkIdMapper<FunctionType>(),
813 new BookmarkIdMapper<FunctionType>());
814 }
815
816 // For bookmarks.create.
817 static void BuildForCreate(QuotaLimitHeuristics* heuristics,
818 Profile* profile) {
819 BuildWithMappers(heuristics, new CreateBookmarkBucketMapper(profile),
820 new CreateBookmarkBucketMapper(profile));
821 }
822
823 // For bookmarks.remove.
824 static void BuildForRemove(QuotaLimitHeuristics* heuristics,
825 Profile* profile) {
826 BuildWithMappers(heuristics, new RemoveBookmarksBucketMapper(profile),
827 new RemoveBookmarksBucketMapper(profile));
828 }
829
830 private:
831 static void BuildWithMappers(QuotaLimitHeuristics* heuristics,
832 BucketMapper* short_mapper, BucketMapper* long_mapper) {
833 const Config kSustainedLimitConfig = {
834 // See bookmarks.json for current value.
835 bookmarks::MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE,
836 TimeDelta::FromMinutes(1)
837 };
838 heuristics->push_back(new SustainedLimit(
839 TimeDelta::FromMinutes(10),
840 kSustainedLimitConfig,
841 short_mapper,
842 "MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE"));
843
844 const Config kTimedLimitConfig = {
845 // See bookmarks.json for current value.
846 bookmarks::MAX_WRITE_OPERATIONS_PER_HOUR,
847 TimeDelta::FromHours(1)
848 };
849 heuristics->push_back(new TimedLimit(
850 kTimedLimitConfig,
851 long_mapper,
852 "MAX_WRITE_OPERATIONS_PER_HOUR"));
853 }
854
855 DISALLOW_IMPLICIT_CONSTRUCTORS(BookmarksQuotaLimitFactory);
856 };
857
858 // And finally, building the individual heuristics for each function.
859 void BookmarksRemoveFunction::GetQuotaLimitHeuristics(
860 QuotaLimitHeuristics* heuristics) const {
861 BookmarksQuotaLimitFactory::BuildForRemove(heuristics, profile());
862 }
863
864 void BookmarksMoveFunction::GetQuotaLimitHeuristics(
865 QuotaLimitHeuristics* heuristics) const {
866 BookmarksQuotaLimitFactory::Build<BookmarksMoveFunction>(heuristics);
867 }
868
869 void BookmarksUpdateFunction::GetQuotaLimitHeuristics(
870 QuotaLimitHeuristics* heuristics) const {
871 BookmarksQuotaLimitFactory::Build<BookmarksUpdateFunction>(heuristics);
872 };
873
874 void BookmarksCreateFunction::GetQuotaLimitHeuristics(
875 QuotaLimitHeuristics* heuristics) const {
876 BookmarksQuotaLimitFactory::BuildForCreate(heuristics, profile());
877 }
878
879 BookmarksIOFunction::BookmarksIOFunction() {}
880
881 BookmarksIOFunction::~BookmarksIOFunction() {
882 // There may be pending file dialogs, we need to tell them that we've gone
883 // away so they don't try and call back to us.
884 if (select_file_dialog_.get())
885 select_file_dialog_->ListenerDestroyed();
886 }
887
888 void BookmarksIOFunction::SelectFile(ui::SelectFileDialog::Type type) {
889 // GetDefaultFilepathForBookmarkExport() might have to touch the filesystem
890 // (stat or access, for example), so this requires a thread with IO allowed.
891 if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) {
892 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
893 base::Bind(&BookmarksIOFunction::SelectFile, this, type));
894 return;
895 }
896
897 // Pre-populating the filename field in case this is a SELECT_SAVEAS_FILE
898 // dialog. If not, there is no filename field in the dialog box.
899 FilePath default_path;
900 if (type == ui::SelectFileDialog::SELECT_SAVEAS_FILE)
901 default_path = GetDefaultFilepathForBookmarkExport();
902 else
903 DCHECK(type == ui::SelectFileDialog::SELECT_OPEN_FILE);
904
905 // After getting the |default_path|, ask the UI to display the file dialog.
906 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
907 base::Bind(&BookmarksIOFunction::ShowSelectFileDialog, this,
908 type, default_path));
909 }
910
911 void BookmarksIOFunction::ShowSelectFileDialog(ui::SelectFileDialog::Type type,
912 const FilePath& default_path) {
913 // Balanced in one of the three callbacks of SelectFileDialog:
914 // either FileSelectionCanceled, MultiFilesSelected, or FileSelected
915 AddRef();
916
917 WebContents* web_contents = dispatcher()->delegate()->
918 GetAssociatedWebContents();
919
920 select_file_dialog_ = ui::SelectFileDialog::Create(
921 this, new ChromeSelectFilePolicy(web_contents));
922 ui::SelectFileDialog::FileTypeInfo file_type_info;
923 file_type_info.extensions.resize(1);
924 file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("html"));
925 // TODO(kinaba): http://crbug.com/140425. Turn file_type_info.support_gdata
926 // on for saving once Google Drive client on ChromeOS supports it.
927 if (type == ui::SelectFileDialog::SELECT_OPEN_FILE)
928 file_type_info.support_gdata = true;
929 // |web_contents| can be NULL (for background pages), which is fine. In such
930 // a case if file-selection dialogs are forbidden by policy, we will not
931 // show an InfoBar, which is better than letting one appear out of the blue.
932 select_file_dialog_->SelectFile(type,
933 string16(),
934 default_path,
935 &file_type_info,
936 0,
937 FILE_PATH_LITERAL(""),
938 NULL,
939 NULL);
940 }
941
942 void BookmarksIOFunction::FileSelectionCanceled(void* params) {
943 Release(); // Balanced in BookmarksIOFunction::SelectFile()
944 }
945
946 void BookmarksIOFunction::MultiFilesSelected(
947 const std::vector<FilePath>& files, void* params) {
948 Release(); // Balanced in BookmarsIOFunction::SelectFile()
949 NOTREACHED() << "Should not be able to select multiple files";
950 }
951
952 bool BookmarksImportFunction::RunImpl() {
953 if (!EditBookmarksEnabled())
954 return false;
955 SelectFile(ui::SelectFileDialog::SELECT_OPEN_FILE);
956 return true;
957 }
958
959 void BookmarksImportFunction::FileSelected(const FilePath& path,
960 int index,
961 void* params) {
962 #if !defined(OS_ANDROID)
963 // Android does not have support for the standard importers.
964 // TODO(jgreenwald): remove ifdef once extensions are no longer built on
965 // Android.
966 scoped_refptr<ImporterHost> importer_host(new ImporterHost);
967 importer::SourceProfile source_profile;
968 source_profile.importer_type = importer::TYPE_BOOKMARKS_FILE;
969 source_profile.source_path = path;
970 importer_host->StartImportSettings(source_profile,
971 profile(),
972 importer::FAVORITES,
973 new ProfileWriter(profile()),
974 true);
975 #endif
976 Release(); // Balanced in BookmarksIOFunction::SelectFile()
977 }
978
979 bool BookmarksExportFunction::RunImpl() {
980 SelectFile(ui::SelectFileDialog::SELECT_SAVEAS_FILE);
981 return true;
982 }
983
984 void BookmarksExportFunction::FileSelected(const FilePath& path,
985 int index,
986 void* params) {
987 #if !defined(OS_ANDROID)
988 // Android does not have support for the standard exporter.
989 // TODO(jgreenwald): remove ifdef once extensions are no longer built on
990 // Android.
991 bookmark_html_writer::WriteBookmarks(profile(), path, NULL);
992 #endif
993 Release(); // Balanced in BookmarksIOFunction::SelectFile()
994 }
995
996 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698