Adding instrumentation to locate the source of jankiness
[chromium-blink-merge.git] / chrome / browser / bookmarks / bookmark_html_writer.cc
blobcdf97470fc1e2082adaf3fb231ad284f85c7fdeb
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.h"
21 #include "chrome/browser/favicon/favicon_service_factory.h"
22 #include "components/bookmarks/browser/bookmark_codec.h"
23 #include "components/bookmarks/browser/bookmark_model.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 content::BrowserThread;
35 namespace {
37 static BookmarkFaviconFetcher* fetcher = NULL;
39 // File header.
40 const char kHeader[] =
41 "<!DOCTYPE NETSCAPE-Bookmark-file-1>\r\n"
42 "<!-- This is an automatically generated file.\r\n"
43 " It will be read and overwritten.\r\n"
44 " DO NOT EDIT! -->\r\n"
45 "<META HTTP-EQUIV=\"Content-Type\""
46 " CONTENT=\"text/html; charset=UTF-8\">\r\n"
47 "<TITLE>Bookmarks</TITLE>\r\n"
48 "<H1>Bookmarks</H1>\r\n"
49 "<DL><p>\r\n";
51 // Newline separator.
52 const char kNewline[] = "\r\n";
54 // The following are used for bookmarks.
56 // Start of a bookmark.
57 const char kBookmarkStart[] = "<DT><A HREF=\"";
58 // After kBookmarkStart.
59 const char kAddDate[] = "\" ADD_DATE=\"";
60 // After kAddDate.
61 const char kIcon[] = "\" ICON=\"";
62 // After kIcon.
63 const char kBookmarkAttributeEnd[] = "\">";
64 // End of a bookmark.
65 const char kBookmarkEnd[] = "</A>";
67 // The following are used when writing folders.
69 // Start of a folder.
70 const char kFolderStart[] = "<DT><H3 ADD_DATE=\"";
71 // After kFolderStart.
72 const char kLastModified[] = "\" LAST_MODIFIED=\"";
73 // After kLastModified when writing the bookmark bar.
74 const char kBookmarkBar[] = "\" PERSONAL_TOOLBAR_FOLDER=\"true\">";
75 // After kLastModified when writing a user created folder.
76 const char kFolderAttributeEnd[] = "\">";
77 // End of the folder.
78 const char kFolderEnd[] = "</H3>";
79 // Start of the children of a folder.
80 const char kFolderChildren[] = "<DL><p>";
81 // End of the children for a folder.
82 const char kFolderChildrenEnd[] = "</DL><p>";
84 // Number of characters to indent by.
85 const size_t kIndentSize = 4;
87 // Class responsible for the actual writing. Takes ownership of favicons_map.
88 class Writer : public base::RefCountedThreadSafe<Writer> {
89 public:
90 Writer(base::Value* bookmarks,
91 const base::FilePath& path,
92 BookmarkFaviconFetcher::URLFaviconMap* favicons_map,
93 BookmarksExportObserver* observer)
94 : bookmarks_(bookmarks),
95 path_(path),
96 favicons_map_(favicons_map),
97 observer_(observer) {
100 // Writing bookmarks and favicons data to file.
101 void DoWrite() {
102 if (!OpenFile())
103 return;
105 base::Value* roots = NULL;
106 if (!Write(kHeader) ||
107 bookmarks_->GetType() != base::Value::TYPE_DICTIONARY ||
108 !static_cast<base::DictionaryValue*>(bookmarks_.get())->Get(
109 BookmarkCodec::kRootsKey, &roots) ||
110 roots->GetType() != base::Value::TYPE_DICTIONARY) {
111 NOTREACHED();
112 return;
115 base::DictionaryValue* roots_d_value =
116 static_cast<base::DictionaryValue*>(roots);
117 base::Value* root_folder_value;
118 base::Value* other_folder_value = NULL;
119 base::Value* mobile_folder_value = NULL;
120 if (!roots_d_value->Get(BookmarkCodec::kRootFolderNameKey,
121 &root_folder_value) ||
122 root_folder_value->GetType() != base::Value::TYPE_DICTIONARY ||
123 !roots_d_value->Get(BookmarkCodec::kOtherBookmarkFolderNameKey,
124 &other_folder_value) ||
125 other_folder_value->GetType() != base::Value::TYPE_DICTIONARY ||
126 !roots_d_value->Get(BookmarkCodec::kMobileBookmarkFolderNameKey,
127 &mobile_folder_value) ||
128 mobile_folder_value->GetType() != base::Value::TYPE_DICTIONARY) {
129 NOTREACHED();
130 return; // Invalid type for root folder and/or other folder.
133 IncrementIndent();
135 if (!WriteNode(*static_cast<base::DictionaryValue*>(root_folder_value),
136 BookmarkNode::BOOKMARK_BAR) ||
137 !WriteNode(*static_cast<base::DictionaryValue*>(other_folder_value),
138 BookmarkNode::OTHER_NODE) ||
139 !WriteNode(*static_cast<base::DictionaryValue*>(mobile_folder_value),
140 BookmarkNode::MOBILE)) {
141 return;
144 DecrementIndent();
146 Write(kFolderChildrenEnd);
147 Write(kNewline);
148 // File close is forced so that unit test could read it.
149 file_.reset();
151 NotifyOnFinish();
154 private:
155 friend class base::RefCountedThreadSafe<Writer>;
157 // Types of text being written out. The type dictates how the text is
158 // escaped.
159 enum TextType {
160 // The text is the value of an html attribute, eg foo in
161 // <a href="foo">.
162 ATTRIBUTE_VALUE,
164 // Actual content, eg foo in <h1>foo</h2>.
165 CONTENT
168 ~Writer() {}
170 // Opens the file, returning true on success.
171 bool OpenFile() {
172 int flags = base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE;
173 file_.reset(new base::File(path_, flags));
174 return file_->IsValid();
177 // Increments the indent.
178 void IncrementIndent() {
179 indent_.resize(indent_.size() + kIndentSize, ' ');
182 // Decrements the indent.
183 void DecrementIndent() {
184 DCHECK(!indent_.empty());
185 indent_.resize(indent_.size() - kIndentSize, ' ');
188 // Called at the end of the export process.
189 void NotifyOnFinish() {
190 if (observer_ != NULL) {
191 observer_->OnExportFinished();
195 // Writes raw text out returning true on success. This does not escape
196 // the text in anyway.
197 bool Write(const std::string& text) {
198 if (!text.length())
199 return true;
200 size_t wrote = file_->WriteAtCurrentPos(text.c_str(), text.length());
201 bool result = (wrote == text.length());
202 DCHECK(result);
203 return result;
206 // Writes out the text string (as UTF8). The text is escaped based on
207 // type.
208 bool Write(const std::string& text, TextType type) {
209 DCHECK(base::IsStringUTF8(text));
210 std::string utf8_string;
212 switch (type) {
213 case ATTRIBUTE_VALUE:
214 // Convert " to &quot;
215 utf8_string = text;
216 ReplaceSubstringsAfterOffset(&utf8_string, 0, "\"", "&quot;");
217 break;
219 case CONTENT:
220 utf8_string = net::EscapeForHTML(text);
221 break;
223 default:
224 NOTREACHED();
227 return Write(utf8_string);
230 // Indents the current line.
231 bool WriteIndent() {
232 return Write(indent_);
235 // Converts a time string written to the JSON codec into a time_t string
236 // (used by bookmarks.html) and writes it.
237 bool WriteTime(const std::string& time_string) {
238 int64 internal_value;
239 base::StringToInt64(time_string, &internal_value);
240 return Write(base::Int64ToString(
241 base::Time::FromInternalValue(internal_value).ToTimeT()));
244 // Writes the node and all its children, returning true on success.
245 bool WriteNode(const base::DictionaryValue& value,
246 BookmarkNode::Type folder_type) {
247 std::string title, date_added_string, type_string;
248 if (!value.GetString(BookmarkCodec::kNameKey, &title) ||
249 !value.GetString(BookmarkCodec::kDateAddedKey, &date_added_string) ||
250 !value.GetString(BookmarkCodec::kTypeKey, &type_string) ||
251 (type_string != BookmarkCodec::kTypeURL &&
252 type_string != BookmarkCodec::kTypeFolder)) {
253 NOTREACHED();
254 return false;
257 if (type_string == BookmarkCodec::kTypeURL) {
258 std::string url_string;
259 if (!value.GetString(BookmarkCodec::kURLKey, &url_string)) {
260 NOTREACHED();
261 return false;
264 std::string favicon_string;
265 BookmarkFaviconFetcher::URLFaviconMap::iterator itr =
266 favicons_map_->find(url_string);
267 if (itr != favicons_map_->end()) {
268 scoped_refptr<base::RefCountedMemory> data(itr->second.get());
269 std::string favicon_base64_encoded;
270 base::Base64Encode(std::string(data->front_as<char>(), data->size()),
271 &favicon_base64_encoded);
272 GURL favicon_url("data:image/png;base64," + favicon_base64_encoded);
273 favicon_string = favicon_url.spec();
276 if (!WriteIndent() ||
277 !Write(kBookmarkStart) ||
278 !Write(url_string, ATTRIBUTE_VALUE) ||
279 !Write(kAddDate) ||
280 !WriteTime(date_added_string) ||
281 (!favicon_string.empty() &&
282 (!Write(kIcon) ||
283 !Write(favicon_string, ATTRIBUTE_VALUE))) ||
284 !Write(kBookmarkAttributeEnd) ||
285 !Write(title, CONTENT) ||
286 !Write(kBookmarkEnd) ||
287 !Write(kNewline)) {
288 return false;
290 return true;
293 // Folder.
294 std::string last_modified_date;
295 const base::Value* child_values = NULL;
296 if (!value.GetString(BookmarkCodec::kDateModifiedKey,
297 &last_modified_date) ||
298 !value.Get(BookmarkCodec::kChildrenKey, &child_values) ||
299 child_values->GetType() != base::Value::TYPE_LIST) {
300 NOTREACHED();
301 return false;
303 if (folder_type != BookmarkNode::OTHER_NODE &&
304 folder_type != BookmarkNode::MOBILE) {
305 // The other/mobile folder name are not written out. This gives the effect
306 // of making the contents of the 'other folder' be a sibling to the
307 // bookmark bar folder.
308 if (!WriteIndent() ||
309 !Write(kFolderStart) ||
310 !WriteTime(date_added_string) ||
311 !Write(kLastModified) ||
312 !WriteTime(last_modified_date)) {
313 return false;
315 if (folder_type == BookmarkNode::BOOKMARK_BAR) {
316 if (!Write(kBookmarkBar))
317 return false;
318 title = l10n_util::GetStringUTF8(IDS_BOOKMARK_BAR_FOLDER_NAME);
319 } else if (!Write(kFolderAttributeEnd)) {
320 return false;
322 if (!Write(title, CONTENT) ||
323 !Write(kFolderEnd) ||
324 !Write(kNewline) ||
325 !WriteIndent() ||
326 !Write(kFolderChildren) ||
327 !Write(kNewline)) {
328 return false;
330 IncrementIndent();
333 // Write the children.
334 const base::ListValue* children =
335 static_cast<const base::ListValue*>(child_values);
336 for (size_t i = 0; i < children->GetSize(); ++i) {
337 const base::Value* child_value;
338 if (!children->Get(i, &child_value) ||
339 child_value->GetType() != base::Value::TYPE_DICTIONARY) {
340 NOTREACHED();
341 return false;
343 if (!WriteNode(*static_cast<const base::DictionaryValue*>(child_value),
344 BookmarkNode::FOLDER)) {
345 return false;
348 if (folder_type != BookmarkNode::OTHER_NODE &&
349 folder_type != BookmarkNode::MOBILE) {
350 // Close out the folder.
351 DecrementIndent();
352 if (!WriteIndent() ||
353 !Write(kFolderChildrenEnd) ||
354 !Write(kNewline)) {
355 return false;
358 return true;
361 // The BookmarkModel as a base::Value. This value was generated from the
362 // BookmarkCodec.
363 scoped_ptr<base::Value> bookmarks_;
365 // Path we're writing to.
366 base::FilePath path_;
368 // Map that stores favicon per URL.
369 scoped_ptr<BookmarkFaviconFetcher::URLFaviconMap> favicons_map_;
371 // Observer to be notified on finish.
372 BookmarksExportObserver* observer_;
374 // File we're writing to.
375 scoped_ptr<base::File> file_;
377 // How much we indent when writing a bookmark/folder. This is modified
378 // via IncrementIndent and DecrementIndent.
379 std::string indent_;
381 DISALLOW_COPY_AND_ASSIGN(Writer);
384 } // namespace
386 BookmarkFaviconFetcher::BookmarkFaviconFetcher(
387 Profile* profile,
388 const base::FilePath& path,
389 BookmarksExportObserver* observer)
390 : profile_(profile),
391 path_(path),
392 observer_(observer) {
393 favicons_map_.reset(new URLFaviconMap());
394 registrar_.Add(this,
395 chrome::NOTIFICATION_PROFILE_DESTROYED,
396 content::Source<Profile>(profile_));
399 BookmarkFaviconFetcher::~BookmarkFaviconFetcher() {
402 void BookmarkFaviconFetcher::ExportBookmarks() {
403 ExtractUrls(BookmarkModelFactory::GetForProfile(
404 profile_)->bookmark_bar_node());
405 ExtractUrls(BookmarkModelFactory::GetForProfile(profile_)->other_node());
406 ExtractUrls(BookmarkModelFactory::GetForProfile(profile_)->mobile_node());
407 if (!bookmark_urls_.empty())
408 FetchNextFavicon();
409 else
410 ExecuteWriter();
413 void BookmarkFaviconFetcher::Observe(
414 int type,
415 const content::NotificationSource& source,
416 const content::NotificationDetails& details) {
417 if (chrome::NOTIFICATION_PROFILE_DESTROYED == type && fetcher != NULL) {
418 base::MessageLoop::current()->DeleteSoon(FROM_HERE, fetcher);
419 fetcher = NULL;
423 void BookmarkFaviconFetcher::ExtractUrls(const BookmarkNode* node) {
424 if (node->is_url()) {
425 std::string url = node->url().spec();
426 if (!url.empty())
427 bookmark_urls_.push_back(url);
428 } else {
429 for (int i = 0; i < node->child_count(); ++i)
430 ExtractUrls(node->GetChild(i));
434 void BookmarkFaviconFetcher::ExecuteWriter() {
435 // BookmarkModel isn't thread safe (nor would we want to lock it down
436 // for the duration of the write), as such we make a copy of the
437 // BookmarkModel using BookmarkCodec then write from that.
438 BookmarkCodec codec;
439 BrowserThread::PostTask(
440 BrowserThread::FILE, FROM_HERE,
441 base::Bind(&Writer::DoWrite,
442 new Writer(codec.Encode(BookmarkModelFactory::GetForProfile(
443 profile_)),
444 path_, favicons_map_.release(), observer_)));
445 if (fetcher != NULL) {
446 base::MessageLoop::current()->DeleteSoon(FROM_HERE, fetcher);
447 fetcher = NULL;
451 bool BookmarkFaviconFetcher::FetchNextFavicon() {
452 if (bookmark_urls_.empty()) {
453 return false;
455 do {
456 std::string url = bookmark_urls_.front();
457 // Filter out urls that we've already got favicon for.
458 URLFaviconMap::const_iterator iter = favicons_map_->find(url);
459 if (favicons_map_->end() == iter) {
460 FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
461 profile_, Profile::EXPLICIT_ACCESS);
462 favicon_service->GetRawFaviconForPageURL(
463 GURL(url),
464 favicon_base::FAVICON,
465 gfx::kFaviconSize,
466 base::Bind(&BookmarkFaviconFetcher::OnFaviconDataAvailable,
467 base::Unretained(this)),
468 &cancelable_task_tracker_);
469 return true;
470 } else {
471 bookmark_urls_.pop_front();
473 } while (!bookmark_urls_.empty());
474 return false;
477 void BookmarkFaviconFetcher::OnFaviconDataAvailable(
478 const favicon_base::FaviconRawBitmapResult& bitmap_result) {
479 GURL url;
480 if (!bookmark_urls_.empty()) {
481 url = GURL(bookmark_urls_.front());
482 bookmark_urls_.pop_front();
484 if (bitmap_result.is_valid() && !url.is_empty()) {
485 favicons_map_->insert(
486 make_pair(url.spec(), bitmap_result.bitmap_data));
489 if (FetchNextFavicon()) {
490 return;
492 ExecuteWriter();
495 namespace bookmark_html_writer {
497 void WriteBookmarks(Profile* profile,
498 const base::FilePath& path,
499 BookmarksExportObserver* observer) {
500 // BookmarkModel isn't thread safe (nor would we want to lock it down
501 // for the duration of the write), as such we make a copy of the
502 // BookmarkModel using BookmarkCodec then write from that.
503 if (fetcher == NULL) {
504 fetcher = new BookmarkFaviconFetcher(profile, path, observer);
505 fetcher->ExportBookmarks();
509 } // namespace bookmark_html_writer