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