Make castv2 performance test work.
[chromium-blink-merge.git] / chrome / browser / bookmarks / bookmark_html_writer.cc
blobaeba2a8bf27f48cd7d39be03377b8398e452d736
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/bookmarks/bookmark_html_writer.h"
7 #include "base/base64.h"
8 #include "base/bind.h"
9 #include "base/bind_helpers.h"
10 #include "base/callback.h"
11 #include "base/files/file.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_util.h"
16 #include "base/time/time.h"
17 #include "base/values.h"
18 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
19 #include "chrome/browser/chrome_notification_types.h"
20 #include "chrome/browser/favicon/favicon_service_factory.h"
21 #include "components/bookmarks/browser/bookmark_codec.h"
22 #include "components/bookmarks/browser/bookmark_model.h"
23 #include "components/favicon/core/favicon_service.h"
24 #include "components/favicon_base/favicon_types.h"
25 #include "content/public/browser/browser_thread.h"
26 #include "content/public/browser/notification_source.h"
27 #include "grit/components_strings.h"
28 #include "net/base/escape.h"
29 #include "ui/base/l10n/l10n_util.h"
30 #include "ui/gfx/favicon_size.h"
32 using bookmarks::BookmarkCodec;
33 using bookmarks::BookmarkNode;
34 using content::BrowserThread;
36 namespace {
38 static BookmarkFaviconFetcher* fetcher = NULL;
40 // File header.
41 const char kHeader[] =
42 "<!DOCTYPE NETSCAPE-Bookmark-file-1>\r\n"
43 "<!-- This is an automatically generated file.\r\n"
44 " It will be read and overwritten.\r\n"
45 " DO NOT EDIT! -->\r\n"
46 "<META HTTP-EQUIV=\"Content-Type\""
47 " CONTENT=\"text/html; charset=UTF-8\">\r\n"
48 "<TITLE>Bookmarks</TITLE>\r\n"
49 "<H1>Bookmarks</H1>\r\n"
50 "<DL><p>\r\n";
52 // Newline separator.
53 const char kNewline[] = "\r\n";
55 // The following are used for bookmarks.
57 // Start of a bookmark.
58 const char kBookmarkStart[] = "<DT><A HREF=\"";
59 // After kBookmarkStart.
60 const char kAddDate[] = "\" ADD_DATE=\"";
61 // After kAddDate.
62 const char kIcon[] = "\" ICON=\"";
63 // After kIcon.
64 const char kBookmarkAttributeEnd[] = "\">";
65 // End of a bookmark.
66 const char kBookmarkEnd[] = "</A>";
68 // The following are used when writing folders.
70 // Start of a folder.
71 const char kFolderStart[] = "<DT><H3 ADD_DATE=\"";
72 // After kFolderStart.
73 const char kLastModified[] = "\" LAST_MODIFIED=\"";
74 // After kLastModified when writing the bookmark bar.
75 const char kBookmarkBar[] = "\" PERSONAL_TOOLBAR_FOLDER=\"true\">";
76 // After kLastModified when writing a user created folder.
77 const char kFolderAttributeEnd[] = "\">";
78 // End of the folder.
79 const char kFolderEnd[] = "</H3>";
80 // Start of the children of a folder.
81 const char kFolderChildren[] = "<DL><p>";
82 // End of the children for a folder.
83 const char kFolderChildrenEnd[] = "</DL><p>";
85 // Number of characters to indent by.
86 const size_t kIndentSize = 4;
88 // Class responsible for the actual writing. Takes ownership of favicons_map.
89 class Writer : public base::RefCountedThreadSafe<Writer> {
90 public:
91 Writer(base::Value* bookmarks,
92 const base::FilePath& path,
93 BookmarkFaviconFetcher::URLFaviconMap* favicons_map,
94 BookmarksExportObserver* observer)
95 : bookmarks_(bookmarks),
96 path_(path),
97 favicons_map_(favicons_map),
98 observer_(observer) {
101 // Writing bookmarks and favicons data to file.
102 void DoWrite() {
103 if (!OpenFile())
104 return;
106 base::Value* roots = NULL;
107 if (!Write(kHeader) ||
108 bookmarks_->GetType() != base::Value::TYPE_DICTIONARY ||
109 !static_cast<base::DictionaryValue*>(bookmarks_.get())->Get(
110 BookmarkCodec::kRootsKey, &roots) ||
111 roots->GetType() != base::Value::TYPE_DICTIONARY) {
112 NOTREACHED();
113 return;
116 base::DictionaryValue* roots_d_value =
117 static_cast<base::DictionaryValue*>(roots);
118 base::Value* root_folder_value;
119 base::Value* other_folder_value = NULL;
120 base::Value* mobile_folder_value = NULL;
121 if (!roots_d_value->Get(BookmarkCodec::kRootFolderNameKey,
122 &root_folder_value) ||
123 root_folder_value->GetType() != base::Value::TYPE_DICTIONARY ||
124 !roots_d_value->Get(BookmarkCodec::kOtherBookmarkFolderNameKey,
125 &other_folder_value) ||
126 other_folder_value->GetType() != base::Value::TYPE_DICTIONARY ||
127 !roots_d_value->Get(BookmarkCodec::kMobileBookmarkFolderNameKey,
128 &mobile_folder_value) ||
129 mobile_folder_value->GetType() != base::Value::TYPE_DICTIONARY) {
130 NOTREACHED();
131 return; // Invalid type for root folder and/or other folder.
134 IncrementIndent();
136 if (!WriteNode(*static_cast<base::DictionaryValue*>(root_folder_value),
137 BookmarkNode::BOOKMARK_BAR) ||
138 !WriteNode(*static_cast<base::DictionaryValue*>(other_folder_value),
139 BookmarkNode::OTHER_NODE) ||
140 !WriteNode(*static_cast<base::DictionaryValue*>(mobile_folder_value),
141 BookmarkNode::MOBILE)) {
142 return;
145 DecrementIndent();
147 Write(kFolderChildrenEnd);
148 Write(kNewline);
149 // File close is forced so that unit test could read it.
150 file_.reset();
152 NotifyOnFinish();
155 private:
156 friend class base::RefCountedThreadSafe<Writer>;
158 // Types of text being written out. The type dictates how the text is
159 // escaped.
160 enum TextType {
161 // The text is the value of an html attribute, eg foo in
162 // <a href="foo">.
163 ATTRIBUTE_VALUE,
165 // Actual content, eg foo in <h1>foo</h2>.
166 CONTENT
169 ~Writer() {}
171 // Opens the file, returning true on success.
172 bool OpenFile() {
173 int flags = base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE;
174 file_.reset(new base::File(path_, flags));
175 return file_->IsValid();
178 // Increments the indent.
179 void IncrementIndent() {
180 indent_.resize(indent_.size() + kIndentSize, ' ');
183 // Decrements the indent.
184 void DecrementIndent() {
185 DCHECK(!indent_.empty());
186 indent_.resize(indent_.size() - kIndentSize, ' ');
189 // Called at the end of the export process.
190 void NotifyOnFinish() {
191 if (observer_ != NULL) {
192 observer_->OnExportFinished();
196 // Writes raw text out returning true on success. This does not escape
197 // the text in anyway.
198 bool Write(const std::string& text) {
199 if (!text.length())
200 return true;
201 size_t wrote = file_->WriteAtCurrentPos(text.c_str(), text.length());
202 bool result = (wrote == text.length());
203 DCHECK(result);
204 return result;
207 // Writes out the text string (as UTF8). The text is escaped based on
208 // type.
209 bool Write(const std::string& text, TextType type) {
210 DCHECK(base::IsStringUTF8(text));
211 std::string utf8_string;
213 switch (type) {
214 case ATTRIBUTE_VALUE:
215 // Convert " to &quot;
216 utf8_string = text;
217 ReplaceSubstringsAfterOffset(&utf8_string, 0, "\"", "&quot;");
218 break;
220 case CONTENT:
221 utf8_string = net::EscapeForHTML(text);
222 break;
224 default:
225 NOTREACHED();
228 return Write(utf8_string);
231 // Indents the current line.
232 bool WriteIndent() {
233 return Write(indent_);
236 // Converts a time string written to the JSON codec into a time_t string
237 // (used by bookmarks.html) and writes it.
238 bool WriteTime(const std::string& time_string) {
239 int64 internal_value;
240 base::StringToInt64(time_string, &internal_value);
241 return Write(base::Int64ToString(
242 base::Time::FromInternalValue(internal_value).ToTimeT()));
245 // Writes the node and all its children, returning true on success.
246 bool WriteNode(const base::DictionaryValue& value,
247 BookmarkNode::Type folder_type) {
248 std::string title, date_added_string, type_string;
249 if (!value.GetString(BookmarkCodec::kNameKey, &title) ||
250 !value.GetString(BookmarkCodec::kDateAddedKey, &date_added_string) ||
251 !value.GetString(BookmarkCodec::kTypeKey, &type_string) ||
252 (type_string != BookmarkCodec::kTypeURL &&
253 type_string != BookmarkCodec::kTypeFolder)) {
254 NOTREACHED();
255 return false;
258 if (type_string == BookmarkCodec::kTypeURL) {
259 std::string url_string;
260 if (!value.GetString(BookmarkCodec::kURLKey, &url_string)) {
261 NOTREACHED();
262 return false;
265 std::string favicon_string;
266 BookmarkFaviconFetcher::URLFaviconMap::iterator itr =
267 favicons_map_->find(url_string);
268 if (itr != favicons_map_->end()) {
269 scoped_refptr<base::RefCountedMemory> data(itr->second.get());
270 std::string favicon_base64_encoded;
271 base::Base64Encode(std::string(data->front_as<char>(), data->size()),
272 &favicon_base64_encoded);
273 GURL favicon_url("data:image/png;base64," + favicon_base64_encoded);
274 favicon_string = favicon_url.spec();
277 if (!WriteIndent() ||
278 !Write(kBookmarkStart) ||
279 !Write(url_string, ATTRIBUTE_VALUE) ||
280 !Write(kAddDate) ||
281 !WriteTime(date_added_string) ||
282 (!favicon_string.empty() &&
283 (!Write(kIcon) ||
284 !Write(favicon_string, ATTRIBUTE_VALUE))) ||
285 !Write(kBookmarkAttributeEnd) ||
286 !Write(title, CONTENT) ||
287 !Write(kBookmarkEnd) ||
288 !Write(kNewline)) {
289 return false;
291 return true;
294 // Folder.
295 std::string last_modified_date;
296 const base::Value* child_values = NULL;
297 if (!value.GetString(BookmarkCodec::kDateModifiedKey,
298 &last_modified_date) ||
299 !value.Get(BookmarkCodec::kChildrenKey, &child_values) ||
300 child_values->GetType() != base::Value::TYPE_LIST) {
301 NOTREACHED();
302 return false;
304 if (folder_type != BookmarkNode::OTHER_NODE &&
305 folder_type != BookmarkNode::MOBILE) {
306 // The other/mobile folder name are not written out. This gives the effect
307 // of making the contents of the 'other folder' be a sibling to the
308 // bookmark bar folder.
309 if (!WriteIndent() ||
310 !Write(kFolderStart) ||
311 !WriteTime(date_added_string) ||
312 !Write(kLastModified) ||
313 !WriteTime(last_modified_date)) {
314 return false;
316 if (folder_type == BookmarkNode::BOOKMARK_BAR) {
317 if (!Write(kBookmarkBar))
318 return false;
319 title = l10n_util::GetStringUTF8(IDS_BOOKMARK_BAR_FOLDER_NAME);
320 } else if (!Write(kFolderAttributeEnd)) {
321 return false;
323 if (!Write(title, CONTENT) ||
324 !Write(kFolderEnd) ||
325 !Write(kNewline) ||
326 !WriteIndent() ||
327 !Write(kFolderChildren) ||
328 !Write(kNewline)) {
329 return false;
331 IncrementIndent();
334 // Write the children.
335 const base::ListValue* children =
336 static_cast<const base::ListValue*>(child_values);
337 for (size_t i = 0; i < children->GetSize(); ++i) {
338 const base::Value* child_value;
339 if (!children->Get(i, &child_value) ||
340 child_value->GetType() != base::Value::TYPE_DICTIONARY) {
341 NOTREACHED();
342 return false;
344 if (!WriteNode(*static_cast<const base::DictionaryValue*>(child_value),
345 BookmarkNode::FOLDER)) {
346 return false;
349 if (folder_type != BookmarkNode::OTHER_NODE &&
350 folder_type != BookmarkNode::MOBILE) {
351 // Close out the folder.
352 DecrementIndent();
353 if (!WriteIndent() ||
354 !Write(kFolderChildrenEnd) ||
355 !Write(kNewline)) {
356 return false;
359 return true;
362 // The BookmarkModel as a base::Value. This value was generated from the
363 // BookmarkCodec.
364 scoped_ptr<base::Value> bookmarks_;
366 // Path we're writing to.
367 base::FilePath path_;
369 // Map that stores favicon per URL.
370 scoped_ptr<BookmarkFaviconFetcher::URLFaviconMap> favicons_map_;
372 // Observer to be notified on finish.
373 BookmarksExportObserver* observer_;
375 // File we're writing to.
376 scoped_ptr<base::File> file_;
378 // How much we indent when writing a bookmark/folder. This is modified
379 // via IncrementIndent and DecrementIndent.
380 std::string indent_;
382 DISALLOW_COPY_AND_ASSIGN(Writer);
385 } // namespace
387 BookmarkFaviconFetcher::BookmarkFaviconFetcher(
388 Profile* profile,
389 const base::FilePath& path,
390 BookmarksExportObserver* observer)
391 : profile_(profile),
392 path_(path),
393 observer_(observer) {
394 favicons_map_.reset(new URLFaviconMap());
395 registrar_.Add(this,
396 chrome::NOTIFICATION_PROFILE_DESTROYED,
397 content::Source<Profile>(profile_));
400 BookmarkFaviconFetcher::~BookmarkFaviconFetcher() {
403 void BookmarkFaviconFetcher::ExportBookmarks() {
404 ExtractUrls(BookmarkModelFactory::GetForProfile(
405 profile_)->bookmark_bar_node());
406 ExtractUrls(BookmarkModelFactory::GetForProfile(profile_)->other_node());
407 ExtractUrls(BookmarkModelFactory::GetForProfile(profile_)->mobile_node());
408 if (!bookmark_urls_.empty())
409 FetchNextFavicon();
410 else
411 ExecuteWriter();
414 void BookmarkFaviconFetcher::Observe(
415 int type,
416 const content::NotificationSource& source,
417 const content::NotificationDetails& details) {
418 if (chrome::NOTIFICATION_PROFILE_DESTROYED == type && fetcher != NULL) {
419 base::MessageLoop::current()->DeleteSoon(FROM_HERE, fetcher);
420 fetcher = NULL;
424 void BookmarkFaviconFetcher::ExtractUrls(const BookmarkNode* node) {
425 if (node->is_url()) {
426 std::string url = node->url().spec();
427 if (!url.empty())
428 bookmark_urls_.push_back(url);
429 } else {
430 for (int i = 0; i < node->child_count(); ++i)
431 ExtractUrls(node->GetChild(i));
435 void BookmarkFaviconFetcher::ExecuteWriter() {
436 // BookmarkModel isn't thread safe (nor would we want to lock it down
437 // for the duration of the write), as such we make a copy of the
438 // BookmarkModel using BookmarkCodec then write from that.
439 BookmarkCodec codec;
440 BrowserThread::PostTask(
441 BrowserThread::FILE, FROM_HERE,
442 base::Bind(&Writer::DoWrite,
443 new Writer(codec.Encode(BookmarkModelFactory::GetForProfile(
444 profile_)),
445 path_, favicons_map_.release(), observer_)));
446 if (fetcher != NULL) {
447 base::MessageLoop::current()->DeleteSoon(FROM_HERE, fetcher);
448 fetcher = NULL;
452 bool BookmarkFaviconFetcher::FetchNextFavicon() {
453 if (bookmark_urls_.empty()) {
454 return false;
456 do {
457 std::string url = bookmark_urls_.front();
458 // Filter out urls that we've already got favicon for.
459 URLFaviconMap::const_iterator iter = favicons_map_->find(url);
460 if (favicons_map_->end() == iter) {
461 favicon::FaviconService* favicon_service =
462 FaviconServiceFactory::GetForProfile(
463 profile_, ServiceAccessType::EXPLICIT_ACCESS);
464 favicon_service->GetRawFaviconForPageURL(
465 GURL(url),
466 favicon_base::FAVICON,
467 gfx::kFaviconSize,
468 base::Bind(&BookmarkFaviconFetcher::OnFaviconDataAvailable,
469 base::Unretained(this)),
470 &cancelable_task_tracker_);
471 return true;
472 } else {
473 bookmark_urls_.pop_front();
475 } while (!bookmark_urls_.empty());
476 return false;
479 void BookmarkFaviconFetcher::OnFaviconDataAvailable(
480 const favicon_base::FaviconRawBitmapResult& bitmap_result) {
481 GURL url;
482 if (!bookmark_urls_.empty()) {
483 url = GURL(bookmark_urls_.front());
484 bookmark_urls_.pop_front();
486 if (bitmap_result.is_valid() && !url.is_empty()) {
487 favicons_map_->insert(
488 make_pair(url.spec(), bitmap_result.bitmap_data));
491 if (FetchNextFavicon()) {
492 return;
494 ExecuteWriter();
497 namespace bookmark_html_writer {
499 void WriteBookmarks(Profile* profile,
500 const base::FilePath& path,
501 BookmarksExportObserver* observer) {
502 // BookmarkModel isn't thread safe (nor would we want to lock it down
503 // for the duration of the write), as such we make a copy of the
504 // BookmarkModel using BookmarkCodec then write from that.
505 if (fetcher == NULL) {
506 fetcher = new BookmarkFaviconFetcher(profile, path, observer);
507 fetcher->ExportBookmarks();
511 } // namespace bookmark_html_writer