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/bookmarks/bookmark_html_writer.h" 6 7 #include "base/files/scoped_temp_dir.h" 8 #include "base/i18n/time_formatting.h" 9 #include "base/path_service.h" 10 #include "base/run_loop.h" 11 #include "base/strings/string16.h" 12 #include "base/strings/string_util.h" 13 #include "base/strings/utf_string_conversions.h" 14 #include "base/time/time.h" 15 #include "chrome/browser/bookmarks/bookmark_model_factory.h" 16 #include "chrome/browser/favicon/favicon_service.h" 17 #include "chrome/browser/favicon/favicon_service_factory.h" 18 #include "chrome/browser/history/history_service.h" 19 #include "chrome/browser/history/history_service_factory.h" 20 #include "chrome/common/importer/imported_bookmark_entry.h" 21 #include "chrome/common/importer/imported_favicon_usage.h" 22 #include "chrome/test/base/testing_profile.h" 23 #include "chrome/utility/importer/bookmark_html_reader.h" 24 #include "components/bookmarks/browser/bookmark_model.h" 25 #include "components/bookmarks/test/bookmark_test_helpers.h" 26 #include "content/public/test/test_browser_thread_bundle.h" 27 #include "grit/components_strings.h" 28 #include "testing/gtest/include/gtest/gtest.h" 29 #include "third_party/skia/include/core/SkBitmap.h" 30 #include "ui/base/l10n/l10n_util.h" 31 #include "ui/gfx/codec/png_codec.h" 32 33 namespace { 34 35 const int kIconWidth = 16; 36 const int kIconHeight = 16; 37 38 void MakeTestSkBitmap(int w, int h, SkBitmap* bmp) { 39 bmp->allocN32Pixels(w, h); 40 41 uint32_t* src_data = bmp->getAddr32(0, 0); 42 for (int i = 0; i < w * h; i++) { 43 src_data[i] = SkPreMultiplyARGB(i % 255, i % 250, i % 245, i % 240); 44 } 45 } 46 47 } // namespace 48 49 class BookmarkHTMLWriterTest : public testing::Test { 50 protected: 51 virtual void SetUp() { 52 testing::Test::SetUp(); 53 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 54 path_ = temp_dir_.path().AppendASCII("bookmarks.html"); 55 } 56 57 // Converts an ImportedBookmarkEntry to a string suitable for assertion 58 // testing. 59 base::string16 BookmarkEntryToString(const ImportedBookmarkEntry& entry) { 60 base::string16 result; 61 result.append(base::ASCIIToUTF16("on_toolbar=")); 62 if (entry.in_toolbar) 63 result.append(base::ASCIIToUTF16("true")); 64 else 65 result.append(base::ASCIIToUTF16("false")); 66 67 result.append(base::ASCIIToUTF16(" url=") + 68 base::UTF8ToUTF16(entry.url.spec())); 69 70 result.append(base::ASCIIToUTF16(" path=")); 71 for (size_t i = 0; i < entry.path.size(); ++i) { 72 if (i != 0) 73 result.append(base::ASCIIToUTF16("/")); 74 result.append(entry.path[i]); 75 } 76 77 result.append(base::ASCIIToUTF16(" title=")); 78 result.append(entry.title); 79 80 result.append(base::ASCIIToUTF16(" time=")); 81 result.append(base::TimeFormatFriendlyDateAndTime(entry.creation_time)); 82 return result; 83 } 84 85 // Creates a set of bookmark values to a string for assertion testing. 86 base::string16 BookmarkValuesToString(bool on_toolbar, 87 const GURL& url, 88 const base::string16& title, 89 base::Time creation_time, 90 const base::string16& f1, 91 const base::string16& f2, 92 const base::string16& f3) { 93 ImportedBookmarkEntry entry; 94 entry.in_toolbar = on_toolbar; 95 entry.url = url; 96 if (!f1.empty()) { 97 entry.path.push_back(f1); 98 if (!f2.empty()) { 99 entry.path.push_back(f2); 100 if (!f3.empty()) 101 entry.path.push_back(f3); 102 } 103 } 104 entry.title = title; 105 entry.creation_time = creation_time; 106 return BookmarkEntryToString(entry); 107 } 108 109 void AssertBookmarkEntryEquals(const ImportedBookmarkEntry& entry, 110 bool on_toolbar, 111 const GURL& url, 112 const base::string16& title, 113 base::Time creation_time, 114 const base::string16& f1, 115 const base::string16& f2, 116 const base::string16& f3) { 117 EXPECT_EQ(BookmarkValuesToString(on_toolbar, url, title, creation_time, 118 f1, f2, f3), 119 BookmarkEntryToString(entry)); 120 } 121 122 base::ScopedTempDir temp_dir_; 123 base::FilePath path_; 124 }; 125 126 // Class that will notify message loop when file is written. 127 class BookmarksObserver : public BookmarksExportObserver { 128 public: 129 explicit BookmarksObserver(base::RunLoop* loop) : loop_(loop) { 130 DCHECK(loop); 131 } 132 133 virtual void OnExportFinished() OVERRIDE { 134 loop_->Quit(); 135 } 136 137 private: 138 base::RunLoop* loop_; 139 140 DISALLOW_COPY_AND_ASSIGN(BookmarksObserver); 141 }; 142 143 // Tests bookmark_html_writer by populating a BookmarkModel, writing it out by 144 // way of bookmark_html_writer, then using the importer to read it back in. 145 TEST_F(BookmarkHTMLWriterTest, Test) { 146 content::TestBrowserThreadBundle thread_bundle; 147 148 TestingProfile profile; 149 ASSERT_TRUE(profile.CreateHistoryService(true, false)); 150 profile.BlockUntilHistoryProcessesPendingRequests(); 151 profile.CreateFaviconService(); 152 profile.CreateBookmarkModel(true); 153 154 BookmarkModel* model = BookmarkModelFactory::GetForProfile(&profile); 155 test::WaitForBookmarkModelToLoad(model); 156 157 // Create test PNG representing favicon for url1. 158 SkBitmap bitmap; 159 MakeTestSkBitmap(kIconWidth, kIconHeight, &bitmap); 160 std::vector<unsigned char> icon_data; 161 gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &icon_data); 162 163 // Populate the BookmarkModel. This creates the following bookmark structure: 164 // Bookmarks bar 165 // F1 166 // url1 167 // F2 168 // url2 169 // url3 170 // url4 171 // Other 172 // url1 173 // url2 174 // F3 175 // F4 176 // url1 177 // Mobile 178 // url1 179 // <bookmark without a title.> 180 base::string16 f1_title = base::ASCIIToUTF16("F\"&;<1\""); 181 base::string16 f2_title = base::ASCIIToUTF16("F2"); 182 base::string16 f3_title = base::ASCIIToUTF16("F 3"); 183 base::string16 f4_title = base::ASCIIToUTF16("F4"); 184 base::string16 url1_title = base::ASCIIToUTF16("url 1"); 185 base::string16 url2_title = base::ASCIIToUTF16("url&2"); 186 base::string16 url3_title = base::ASCIIToUTF16("url\"3"); 187 base::string16 url4_title = base::ASCIIToUTF16("url\"&;"); 188 base::string16 unnamed_bookmark_title = base::ASCIIToUTF16(""); 189 GURL url1("http://url1"); 190 GURL url1_favicon("http://url1/icon.ico"); 191 GURL url2("http://url2"); 192 GURL url3("http://url3"); 193 GURL url4("javascript:alert(\"Hello!\");"); 194 GURL unnamed_bookmark_url("about:blank"); 195 base::Time t1(base::Time::Now()); 196 base::Time t2(t1 + base::TimeDelta::FromHours(1)); 197 base::Time t3(t1 + base::TimeDelta::FromHours(1)); 198 base::Time t4(t1 + base::TimeDelta::FromHours(1)); 199 const BookmarkNode* f1 = model->AddFolder( 200 model->bookmark_bar_node(), 0, f1_title); 201 model->AddURLWithCreationTimeAndMetaInfo(f1, 0, url1_title, url1, t1, NULL); 202 HistoryServiceFactory::GetForProfile(&profile, Profile::EXPLICIT_ACCESS)-> 203 AddPage(url1, base::Time::Now(), history::SOURCE_BROWSED); 204 FaviconServiceFactory::GetForProfile(&profile, Profile::EXPLICIT_ACCESS) 205 ->SetFavicons(url1, 206 url1_favicon, 207 favicon_base::FAVICON, 208 gfx::Image::CreateFrom1xBitmap(bitmap)); 209 const BookmarkNode* f2 = model->AddFolder(f1, 1, f2_title); 210 model->AddURLWithCreationTimeAndMetaInfo(f2, 0, url2_title, url2, t2, NULL); 211 model->AddURLWithCreationTimeAndMetaInfo( 212 model->bookmark_bar_node(), 1, url3_title, url3, t3, NULL); 213 214 model->AddURLWithCreationTimeAndMetaInfo( 215 model->other_node(), 0, url1_title, url1, t1, NULL); 216 model->AddURLWithCreationTimeAndMetaInfo( 217 model->other_node(), 1, url2_title, url2, t2, NULL); 218 const BookmarkNode* f3 = model->AddFolder(model->other_node(), 2, f3_title); 219 const BookmarkNode* f4 = model->AddFolder(f3, 0, f4_title); 220 model->AddURLWithCreationTimeAndMetaInfo(f4, 0, url1_title, url1, t1, NULL); 221 model->AddURLWithCreationTimeAndMetaInfo( 222 model->bookmark_bar_node(), 2, url4_title, url4, t4, NULL); 223 model->AddURLWithCreationTimeAndMetaInfo( 224 model->mobile_node(), 0, url1_title, url1, t1, NULL); 225 model->AddURLWithCreationTimeAndMetaInfo(model->mobile_node(), 226 1, 227 unnamed_bookmark_title, 228 unnamed_bookmark_url, 229 t2, 230 NULL); 231 232 base::RunLoop run_loop; 233 234 // Write to a temp file. 235 BookmarksObserver observer(&run_loop); 236 bookmark_html_writer::WriteBookmarks(&profile, path_, &observer); 237 run_loop.Run(); 238 239 // Clear favicon so that it would be read from file. 240 FaviconServiceFactory::GetForProfile(&profile, Profile::EXPLICIT_ACCESS) 241 ->SetFavicons(url1, url1_favicon, favicon_base::FAVICON, gfx::Image()); 242 243 // Read the bookmarks back in. 244 std::vector<ImportedBookmarkEntry> parsed_bookmarks; 245 std::vector<ImportedFaviconUsage> favicons; 246 bookmark_html_reader::ImportBookmarksFile(base::Callback<bool(void)>(), 247 base::Callback<bool(const GURL&)>(), 248 path_, 249 &parsed_bookmarks, 250 &favicons); 251 252 // Check loaded favicon (url1 is represented by 4 separate bookmarks). 253 EXPECT_EQ(4U, favicons.size()); 254 for (size_t i = 0; i < favicons.size(); i++) { 255 if (url1_favicon == favicons[i].favicon_url) { 256 EXPECT_EQ(1U, favicons[i].urls.size()); 257 std::set<GURL>::const_iterator iter = favicons[i].urls.find(url1); 258 ASSERT_TRUE(iter != favicons[i].urls.end()); 259 ASSERT_TRUE(*iter == url1); 260 ASSERT_TRUE(favicons[i].png_data == icon_data); 261 } 262 } 263 264 // Verify we got back what we wrote. 265 ASSERT_EQ(9U, parsed_bookmarks.size()); 266 // Windows and ChromeOS builds use Sentence case. 267 base::string16 bookmark_folder_name = 268 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_FOLDER_NAME); 269 AssertBookmarkEntryEquals(parsed_bookmarks[0], true, url1, url1_title, t1, 270 bookmark_folder_name, f1_title, base::string16()); 271 AssertBookmarkEntryEquals(parsed_bookmarks[1], true, url2, url2_title, t2, 272 bookmark_folder_name, f1_title, f2_title); 273 AssertBookmarkEntryEquals(parsed_bookmarks[2], true, url3, url3_title, t3, 274 bookmark_folder_name, base::string16(), 275 base::string16()); 276 AssertBookmarkEntryEquals(parsed_bookmarks[3], true, url4, url4_title, t4, 277 bookmark_folder_name, base::string16(), 278 base::string16()); 279 AssertBookmarkEntryEquals(parsed_bookmarks[4], false, url1, url1_title, t1, 280 base::string16(), base::string16(), 281 base::string16()); 282 AssertBookmarkEntryEquals(parsed_bookmarks[5], false, url2, url2_title, t2, 283 base::string16(), base::string16(), 284 base::string16()); 285 AssertBookmarkEntryEquals(parsed_bookmarks[6], false, url1, url1_title, t1, 286 f3_title, f4_title, base::string16()); 287 AssertBookmarkEntryEquals(parsed_bookmarks[7], false, url1, url1_title, t1, 288 base::string16(), base::string16(), 289 base::string16()); 290 AssertBookmarkEntryEquals(parsed_bookmarks[8], false, unnamed_bookmark_url, 291 unnamed_bookmark_title, t2, 292 base::string16(), base::string16(), 293 base::string16()); 294 } 295