OLD | NEW |
| (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 | |
OLD | NEW |