1 // Copyright 2013 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 <algorithm> 6 #include <map> 7 #include <string> 8 #include <vector> 9 10 #include "base/bind.h" 11 #include "base/bind_helpers.h" 12 #include "base/location.h" 13 #include "base/memory/scoped_ptr.h" 14 #include "base/message_loop/message_loop.h" 15 #include "base/path_service.h" 16 #include "base/strings/string_number_conversions.h" 17 #include "base/values.h" 18 #include "components/dom_distiller/core/article_distillation_update.h" 19 #include "components/dom_distiller/core/distiller.h" 20 #include "components/dom_distiller/core/distiller_page.h" 21 #include "components/dom_distiller/core/fake_distiller_page.h" 22 #include "components/dom_distiller/core/proto/distilled_article.pb.h" 23 #include "components/dom_distiller/core/proto/distilled_page.pb.h" 24 #include "net/url_request/url_request_context_getter.h" 25 #include "testing/gmock/include/gmock/gmock.h" 26 #include "testing/gtest/include/gtest/gtest.h" 27 #include "third_party/dom_distiller_js/dom_distiller.pb.h" 28 #include "third_party/dom_distiller_js/dom_distiller_json_converter.h" 29 #include "ui/base/resource/resource_bundle.h" 30 31 using std::vector; 32 using std::string; 33 using ::testing::Invoke; 34 using ::testing::Return; 35 using ::testing::_; 36 37 using dom_distiller::proto::DomDistillerOptions; 38 using dom_distiller::proto::DomDistillerResult; 39 40 namespace { 41 const char kTitle[] = "Title"; 42 const char kContent[] = "Content"; 43 const char kURL[] = "http://a.com/"; 44 const size_t kTotalImages = 2; 45 const char* kImageURLs[kTotalImages] = {"http://a.com/img1.jpg", 46 "http://a.com/img2.jpg"}; 47 const char* kImageData[kTotalImages] = {"abcde", "12345"}; 48 const char kDebugLog[] = "Debug Log"; 49 50 const string GetImageName(int page_num, int image_num) { 51 return base::IntToString(page_num) + "_" + base::IntToString(image_num); 52 } 53 54 scoped_ptr<base::Value> CreateDistilledValueReturnedFromJS( 55 const string& title, 56 const string& content, 57 const vector<int>& image_indices, 58 const string& next_page_url, 59 const string& prev_page_url = "") { 60 DomDistillerResult result; 61 result.set_title(title); 62 result.mutable_distilled_content()->set_html(content); 63 result.mutable_pagination_info()->set_next_page(next_page_url); 64 result.mutable_pagination_info()->set_prev_page(prev_page_url); 65 66 for (size_t i = 0; i < image_indices.size(); ++i) { 67 result.add_image_urls(kImageURLs[image_indices[i]]); 68 } 69 70 return dom_distiller::proto::json::DomDistillerResult::WriteToValue(result); 71 } 72 73 // Return the sequence in which Distiller will distill pages. 74 // Note: ignores any delays due to fetching images etc. 75 vector<int> GetPagesInSequence(int start_page_num, int num_pages) { 76 // Distiller prefers distilling past pages first. E.g. when distillation 77 // starts on page 2 then pages are distilled in the order: 2, 1, 0, 3, 4. 78 vector<int> page_nums; 79 for (int page = start_page_num; page >= 0; --page) 80 page_nums.push_back(page); 81 for (int page = start_page_num + 1; page < num_pages; ++page) 82 page_nums.push_back(page); 83 return page_nums; 84 } 85 86 struct MultipageDistillerData { 87 public: 88 MultipageDistillerData() {} 89 ~MultipageDistillerData() {} 90 vector<string> page_urls; 91 vector<string> content; 92 vector<vector<int> > image_ids; 93 // The Javascript values returned by mock distiller. 94 ScopedVector<base::Value> distilled_values; 95 96 private: 97 DISALLOW_COPY_AND_ASSIGN(MultipageDistillerData); 98 }; 99 100 void VerifyIncrementalUpdatesMatch( 101 const MultipageDistillerData* distiller_data, 102 int num_pages_in_article, 103 const vector<dom_distiller::ArticleDistillationUpdate>& incremental_updates, 104 int start_page_num) { 105 vector<int> page_seq = 106 GetPagesInSequence(start_page_num, num_pages_in_article); 107 // Updates should contain a list of pages. Pages in an update should be in 108 // the correct ascending page order regardless of |start_page_num|. 109 // E.g. if distillation starts at page 2 of a 3 page article, the updates 110 // will be [[2], [1, 2], [1, 2, 3]]. This example assumes that image fetches 111 // do not delay distillation of a page. There can be scenarios when image 112 // fetch delays distillation of a page (E.g. 1 is delayed due to image 113 // fetches so updates can be in this order [[2], [2,3], [1,2,3]]. 114 for (size_t update_count = 0; update_count < incremental_updates.size(); 115 ++update_count) { 116 const dom_distiller::ArticleDistillationUpdate& update = 117 incremental_updates[update_count]; 118 EXPECT_EQ(update_count + 1, update.GetPagesSize()); 119 120 vector<int> expected_page_nums_in_update( 121 page_seq.begin(), page_seq.begin() + update.GetPagesSize()); 122 std::sort(expected_page_nums_in_update.begin(), 123 expected_page_nums_in_update.end()); 124 125 // If we already got the first page then there is no previous page. 126 EXPECT_EQ((expected_page_nums_in_update[0] != 0), update.HasPrevPage()); 127 128 // if we already got the last page then there is no next page. 129 EXPECT_EQ( 130 (*expected_page_nums_in_update.rbegin()) != num_pages_in_article - 1, 131 update.HasNextPage()); 132 for (size_t j = 0; j < update.GetPagesSize(); ++j) { 133 int actual_page_num = expected_page_nums_in_update[j]; 134 EXPECT_EQ(distiller_data->page_urls[actual_page_num], 135 update.GetDistilledPage(j).url()); 136 EXPECT_EQ(distiller_data->content[actual_page_num], 137 update.GetDistilledPage(j).html()); 138 } 139 } 140 } 141 142 scoped_ptr<MultipageDistillerData> CreateMultipageDistillerDataWithoutImages( 143 size_t pages_size) { 144 scoped_ptr<MultipageDistillerData> result(new MultipageDistillerData()); 145 string url_prefix = "http://a.com/"; 146 for (size_t page_num = 0; page_num < pages_size; ++page_num) { 147 result->page_urls.push_back(url_prefix + base::IntToString(page_num)); 148 result->content.push_back("Content for page:" + 149 base::IntToString(page_num)); 150 result->image_ids.push_back(vector<int>()); 151 string next_page_url = (page_num + 1 < pages_size) 152 ? url_prefix + base::IntToString(page_num + 1) 153 : ""; 154 string prev_page_url = 155 (page_num > 0) ? result->page_urls[page_num - 1] : ""; 156 scoped_ptr<base::Value> distilled_value = 157 CreateDistilledValueReturnedFromJS(kTitle, 158 result->content[page_num], 159 result->image_ids[page_num], 160 next_page_url, 161 prev_page_url); 162 result->distilled_values.push_back(distilled_value.release()); 163 } 164 return result.Pass(); 165 } 166 167 void VerifyArticleProtoMatchesMultipageData( 168 const dom_distiller::DistilledArticleProto* article_proto, 169 const MultipageDistillerData* distiller_data, 170 size_t pages_size) { 171 ASSERT_EQ(pages_size, static_cast<size_t>(article_proto->pages_size())); 172 EXPECT_EQ(kTitle, article_proto->title()); 173 for (size_t page_num = 0; page_num < pages_size; ++page_num) { 174 const dom_distiller::DistilledPageProto& page = 175 article_proto->pages(page_num); 176 EXPECT_EQ(distiller_data->content[page_num], page.html()); 177 EXPECT_EQ(distiller_data->page_urls[page_num], page.url()); 178 EXPECT_EQ(distiller_data->image_ids[page_num].size(), 179 static_cast<size_t>(page.image_size())); 180 const vector<int>& image_ids_for_page = distiller_data->image_ids[page_num]; 181 for (size_t img_num = 0; img_num < image_ids_for_page.size(); ++img_num) { 182 EXPECT_EQ(kImageData[image_ids_for_page[img_num]], 183 page.image(img_num).data()); 184 EXPECT_EQ(GetImageName(page_num + 1, img_num), 185 page.image(img_num).name()); 186 } 187 } 188 } 189 190 void AddComponentsResources() { 191 base::FilePath pak_file; 192 base::FilePath pak_dir; 193 PathService::Get(base::DIR_MODULE, &pak_dir); 194 pak_file = pak_dir.Append(FILE_PATH_LITERAL("components_resources.pak")); 195 ui::ResourceBundle::GetSharedInstance().AddDataPackFromPath( 196 pak_file, ui::SCALE_FACTOR_NONE); 197 } 198 199 } // namespace 200 201 namespace dom_distiller { 202 203 using test::MockDistillerPage; 204 using test::MockDistillerPageFactory; 205 206 class TestDistillerURLFetcher : public DistillerURLFetcher { 207 public: 208 explicit TestDistillerURLFetcher(bool delay_fetch) 209 : DistillerURLFetcher(NULL), delay_fetch_(delay_fetch) { 210 responses_[kImageURLs[0]] = string(kImageData[0]); 211 responses_[kImageURLs[1]] = string(kImageData[1]); 212 } 213 214 virtual void FetchURL(const string& url, 215 const URLFetcherCallback& callback) OVERRIDE { 216 ASSERT_FALSE(callback.is_null()); 217 url_ = url; 218 callback_ = callback; 219 if (!delay_fetch_) { 220 PostCallbackTask(); 221 } 222 } 223 224 void PostCallbackTask() { 225 ASSERT_TRUE(base::MessageLoop::current()); 226 ASSERT_FALSE(callback_.is_null()); 227 base::MessageLoop::current()->PostTask( 228 FROM_HERE, base::Bind(callback_, responses_[url_])); 229 } 230 231 private: 232 std::map<string, string> responses_; 233 string url_; 234 URLFetcherCallback callback_; 235 bool delay_fetch_; 236 }; 237 238 class TestDistillerURLFetcherFactory : public DistillerURLFetcherFactory { 239 public: 240 TestDistillerURLFetcherFactory() : DistillerURLFetcherFactory(NULL) {} 241 242 virtual ~TestDistillerURLFetcherFactory() {} 243 virtual DistillerURLFetcher* CreateDistillerURLFetcher() const OVERRIDE { 244 return new TestDistillerURLFetcher(false); 245 } 246 }; 247 248 class MockDistillerURLFetcherFactory : public DistillerURLFetcherFactory { 249 public: 250 MockDistillerURLFetcherFactory() : DistillerURLFetcherFactory(NULL) {} 251 virtual ~MockDistillerURLFetcherFactory() {} 252 253 MOCK_CONST_METHOD0(CreateDistillerURLFetcher, DistillerURLFetcher*()); 254 }; 255 256 class DistillerTest : public testing::Test { 257 public: 258 virtual ~DistillerTest() {} 259 260 virtual void SetUp() OVERRIDE { 261 AddComponentsResources(); 262 } 263 264 void OnDistillArticleDone(scoped_ptr<DistilledArticleProto> proto) { 265 article_proto_ = proto.Pass(); 266 } 267 268 void OnDistillArticleUpdate(const ArticleDistillationUpdate& article_update) { 269 in_sequence_updates_.push_back(article_update); 270 } 271 272 void DistillPage(const std::string& url, 273 scoped_ptr<DistillerPage> distiller_page) { 274 distiller_->DistillPage(GURL(url), 275 distiller_page.Pass(), 276 base::Bind(&DistillerTest::OnDistillArticleDone, 277 base::Unretained(this)), 278 base::Bind(&DistillerTest::OnDistillArticleUpdate, 279 base::Unretained(this))); 280 } 281 282 protected: 283 scoped_ptr<DistillerImpl> distiller_; 284 scoped_ptr<DistilledArticleProto> article_proto_; 285 std::vector<ArticleDistillationUpdate> in_sequence_updates_; 286 MockDistillerPageFactory page_factory_; 287 TestDistillerURLFetcherFactory url_fetcher_factory_; 288 }; 289 290 ACTION_P3(DistillerPageOnDistillationDone, distiller_page, url, result) { 291 distiller_page->OnDistillationDone(url, result); 292 } 293 294 scoped_ptr<DistillerPage> CreateMockDistillerPage(const base::Value* result, 295 const GURL& url) { 296 MockDistillerPage* distiller_page = new MockDistillerPage(); 297 EXPECT_CALL(*distiller_page, DistillPageImpl(url, _)) 298 .WillOnce(DistillerPageOnDistillationDone(distiller_page, url, result)); 299 return scoped_ptr<DistillerPage>(distiller_page).Pass(); 300 } 301 302 scoped_ptr<DistillerPage> CreateMockDistillerPageWithPendingJSCallback( 303 MockDistillerPage** distiller_page_ptr, 304 const GURL& url) { 305 MockDistillerPage* distiller_page = new MockDistillerPage(); 306 *distiller_page_ptr = distiller_page; 307 EXPECT_CALL(*distiller_page, DistillPageImpl(url, _)); 308 return scoped_ptr<DistillerPage>(distiller_page).Pass(); 309 } 310 311 scoped_ptr<DistillerPage> CreateMockDistillerPages( 312 MultipageDistillerData* distiller_data, 313 size_t pages_size, 314 int start_page_num) { 315 MockDistillerPage* distiller_page = new MockDistillerPage(); 316 { 317 testing::InSequence s; 318 vector<int> page_nums = GetPagesInSequence(start_page_num, pages_size); 319 for (size_t page_num = 0; page_num < pages_size; ++page_num) { 320 int page = page_nums[page_num]; 321 GURL url = GURL(distiller_data->page_urls[page]); 322 EXPECT_CALL(*distiller_page, DistillPageImpl(url, _)) 323 .WillOnce(DistillerPageOnDistillationDone( 324 distiller_page, url, distiller_data->distilled_values[page])); 325 } 326 } 327 return scoped_ptr<DistillerPage>(distiller_page).Pass(); 328 } 329 330 TEST_F(DistillerTest, DistillPage) { 331 base::MessageLoopForUI loop; 332 scoped_ptr<base::Value> result = 333 CreateDistilledValueReturnedFromJS(kTitle, kContent, vector<int>(), ""); 334 distiller_.reset( 335 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions())); 336 DistillPage(kURL, CreateMockDistillerPage(result.get(), GURL(kURL)).Pass()); 337 base::MessageLoop::current()->RunUntilIdle(); 338 EXPECT_EQ(kTitle, article_proto_->title()); 339 ASSERT_EQ(article_proto_->pages_size(), 1); 340 const DistilledPageProto& first_page = article_proto_->pages(0); 341 EXPECT_EQ(kContent, first_page.html()); 342 EXPECT_EQ(kURL, first_page.url()); 343 } 344 345 TEST_F(DistillerTest, DistillPageWithDebugInfo) { 346 base::MessageLoopForUI loop; 347 DomDistillerResult dd_result; 348 dd_result.mutable_debug_info()->set_log(kDebugLog); 349 scoped_ptr<base::Value> result = 350 dom_distiller::proto::json::DomDistillerResult::WriteToValue(dd_result); 351 distiller_.reset( 352 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions())); 353 DistillPage(kURL, CreateMockDistillerPage(result.get(), GURL(kURL)).Pass()); 354 base::MessageLoop::current()->RunUntilIdle(); 355 const DistilledPageProto& first_page = article_proto_->pages(0); 356 EXPECT_EQ(kDebugLog, first_page.debug_info().log()); 357 } 358 359 TEST_F(DistillerTest, DistillPageWithImages) { 360 base::MessageLoopForUI loop; 361 vector<int> image_indices; 362 image_indices.push_back(0); 363 image_indices.push_back(1); 364 scoped_ptr<base::Value> result = 365 CreateDistilledValueReturnedFromJS(kTitle, kContent, image_indices, ""); 366 distiller_.reset( 367 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions())); 368 DistillPage(kURL, CreateMockDistillerPage(result.get(), GURL(kURL)).Pass()); 369 base::MessageLoop::current()->RunUntilIdle(); 370 EXPECT_EQ(kTitle, article_proto_->title()); 371 ASSERT_EQ(article_proto_->pages_size(), 1); 372 const DistilledPageProto& first_page = article_proto_->pages(0); 373 EXPECT_EQ(kContent, first_page.html()); 374 EXPECT_EQ(kURL, first_page.url()); 375 ASSERT_EQ(2, first_page.image_size()); 376 EXPECT_EQ(kImageData[0], first_page.image(0).data()); 377 EXPECT_EQ(GetImageName(1, 0), first_page.image(0).name()); 378 EXPECT_EQ(kImageData[1], first_page.image(1).data()); 379 EXPECT_EQ(GetImageName(1, 1), first_page.image(1).name()); 380 } 381 382 TEST_F(DistillerTest, DistillMultiplePages) { 383 base::MessageLoopForUI loop; 384 const size_t kNumPages = 8; 385 scoped_ptr<MultipageDistillerData> distiller_data = 386 CreateMultipageDistillerDataWithoutImages(kNumPages); 387 388 // Add images. 389 int next_image_number = 0; 390 for (size_t page_num = 0; page_num < kNumPages; ++page_num) { 391 // Each page has different number of images. 392 size_t tot_images = (page_num + kTotalImages) % (kTotalImages + 1); 393 vector<int> image_indices; 394 for (size_t img_num = 0; img_num < tot_images; img_num++) { 395 image_indices.push_back(next_image_number); 396 next_image_number = (next_image_number + 1) % kTotalImages; 397 } 398 distiller_data->image_ids.push_back(image_indices); 399 } 400 401 distiller_.reset( 402 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions())); 403 DistillPage( 404 distiller_data->page_urls[0], 405 CreateMockDistillerPages(distiller_data.get(), kNumPages, 0).Pass()); 406 base::MessageLoop::current()->RunUntilIdle(); 407 VerifyArticleProtoMatchesMultipageData( 408 article_proto_.get(), distiller_data.get(), kNumPages); 409 } 410 411 TEST_F(DistillerTest, DistillLinkLoop) { 412 base::MessageLoopForUI loop; 413 // Create a loop, the next page is same as the current page. This could 414 // happen if javascript misparses a next page link. 415 scoped_ptr<base::Value> result = 416 CreateDistilledValueReturnedFromJS(kTitle, kContent, vector<int>(), kURL); 417 distiller_.reset( 418 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions())); 419 DistillPage(kURL, CreateMockDistillerPage(result.get(), GURL(kURL)).Pass()); 420 base::MessageLoop::current()->RunUntilIdle(); 421 EXPECT_EQ(kTitle, article_proto_->title()); 422 EXPECT_EQ(article_proto_->pages_size(), 1); 423 } 424 425 TEST_F(DistillerTest, CheckMaxPageLimitExtraPage) { 426 base::MessageLoopForUI loop; 427 const size_t kMaxPagesInArticle = 10; 428 scoped_ptr<MultipageDistillerData> distiller_data = 429 CreateMultipageDistillerDataWithoutImages(kMaxPagesInArticle); 430 431 // Note: Next page url of the last page of article is set. So distiller will 432 // try to do kMaxPagesInArticle + 1 calls if the max article limit does not 433 // work. 434 scoped_ptr<base::Value> last_page_data = 435 CreateDistilledValueReturnedFromJS( 436 kTitle, 437 distiller_data->content[kMaxPagesInArticle - 1], 438 vector<int>(), 439 "", 440 distiller_data->page_urls[kMaxPagesInArticle - 2]); 441 442 distiller_data->distilled_values.pop_back(); 443 distiller_data->distilled_values.push_back(last_page_data.release()); 444 445 distiller_.reset( 446 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions())); 447 448 distiller_->SetMaxNumPagesInArticle(kMaxPagesInArticle); 449 450 DistillPage(distiller_data->page_urls[0], 451 CreateMockDistillerPages( 452 distiller_data.get(), kMaxPagesInArticle, 0).Pass()); 453 base::MessageLoop::current()->RunUntilIdle(); 454 EXPECT_EQ(kTitle, article_proto_->title()); 455 EXPECT_EQ(kMaxPagesInArticle, 456 static_cast<size_t>(article_proto_->pages_size())); 457 } 458 459 TEST_F(DistillerTest, CheckMaxPageLimitExactLimit) { 460 base::MessageLoopForUI loop; 461 const size_t kMaxPagesInArticle = 10; 462 scoped_ptr<MultipageDistillerData> distiller_data = 463 CreateMultipageDistillerDataWithoutImages(kMaxPagesInArticle); 464 465 distiller_.reset( 466 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions())); 467 468 // Check if distilling an article with exactly the page limit works. 469 distiller_->SetMaxNumPagesInArticle(kMaxPagesInArticle); 470 471 DistillPage(distiller_data->page_urls[0], 472 CreateMockDistillerPages( 473 distiller_data.get(), kMaxPagesInArticle, 0).Pass()); 474 base::MessageLoop::current()->RunUntilIdle(); 475 EXPECT_EQ(kTitle, article_proto_->title()); 476 EXPECT_EQ(kMaxPagesInArticle, 477 static_cast<size_t>(article_proto_->pages_size())); 478 } 479 480 TEST_F(DistillerTest, SinglePageDistillationFailure) { 481 base::MessageLoopForUI loop; 482 // To simulate failure return a null value. 483 scoped_ptr<base::Value> nullValue(base::Value::CreateNullValue()); 484 distiller_.reset( 485 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions())); 486 DistillPage(kURL, 487 CreateMockDistillerPage(nullValue.get(), GURL(kURL)).Pass()); 488 base::MessageLoop::current()->RunUntilIdle(); 489 EXPECT_EQ("", article_proto_->title()); 490 EXPECT_EQ(0, article_proto_->pages_size()); 491 } 492 493 TEST_F(DistillerTest, MultiplePagesDistillationFailure) { 494 base::MessageLoopForUI loop; 495 const size_t kNumPages = 8; 496 scoped_ptr<MultipageDistillerData> distiller_data = 497 CreateMultipageDistillerDataWithoutImages(kNumPages); 498 499 // The page number of the failed page. 500 size_t failed_page_num = 3; 501 // reset distilled data of the failed page. 502 distiller_data->distilled_values.erase( 503 distiller_data->distilled_values.begin() + failed_page_num); 504 distiller_data->distilled_values.insert( 505 distiller_data->distilled_values.begin() + failed_page_num, 506 base::Value::CreateNullValue()); 507 // Expect only calls till the failed page number. 508 distiller_.reset( 509 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions())); 510 DistillPage(distiller_data->page_urls[0], 511 CreateMockDistillerPages( 512 distiller_data.get(), failed_page_num + 1, 0).Pass()); 513 base::MessageLoop::current()->RunUntilIdle(); 514 EXPECT_EQ(kTitle, article_proto_->title()); 515 VerifyArticleProtoMatchesMultipageData( 516 article_proto_.get(), distiller_data.get(), failed_page_num); 517 } 518 519 TEST_F(DistillerTest, DistillPreviousPage) { 520 base::MessageLoopForUI loop; 521 const size_t kNumPages = 8; 522 523 // The page number of the article on which distillation starts. 524 int start_page_num = 3; 525 scoped_ptr<MultipageDistillerData> distiller_data = 526 CreateMultipageDistillerDataWithoutImages(kNumPages); 527 528 distiller_.reset( 529 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions())); 530 DistillPage(distiller_data->page_urls[start_page_num], 531 CreateMockDistillerPages( 532 distiller_data.get(), kNumPages, start_page_num).Pass()); 533 base::MessageLoop::current()->RunUntilIdle(); 534 VerifyArticleProtoMatchesMultipageData( 535 article_proto_.get(), distiller_data.get(), kNumPages); 536 } 537 538 TEST_F(DistillerTest, IncrementalUpdates) { 539 base::MessageLoopForUI loop; 540 const size_t kNumPages = 8; 541 542 // The page number of the article on which distillation starts. 543 int start_page_num = 3; 544 scoped_ptr<MultipageDistillerData> distiller_data = 545 CreateMultipageDistillerDataWithoutImages(kNumPages); 546 547 distiller_.reset( 548 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions())); 549 DistillPage(distiller_data->page_urls[start_page_num], 550 CreateMockDistillerPages( 551 distiller_data.get(), kNumPages, start_page_num).Pass()); 552 base::MessageLoop::current()->RunUntilIdle(); 553 EXPECT_EQ(kTitle, article_proto_->title()); 554 ASSERT_EQ(kNumPages, static_cast<size_t>(article_proto_->pages_size())); 555 EXPECT_EQ(kNumPages, in_sequence_updates_.size()); 556 557 VerifyIncrementalUpdatesMatch( 558 distiller_data.get(), kNumPages, in_sequence_updates_, start_page_num); 559 } 560 561 TEST_F(DistillerTest, IncrementalUpdatesDoNotDeleteFinalArticle) { 562 base::MessageLoopForUI loop; 563 const size_t kNumPages = 8; 564 int start_page_num = 3; 565 scoped_ptr<MultipageDistillerData> distiller_data = 566 CreateMultipageDistillerDataWithoutImages(kNumPages); 567 568 distiller_.reset( 569 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions())); 570 DistillPage(distiller_data->page_urls[start_page_num], 571 CreateMockDistillerPages( 572 distiller_data.get(), kNumPages, start_page_num).Pass()); 573 base::MessageLoop::current()->RunUntilIdle(); 574 EXPECT_EQ(kNumPages, in_sequence_updates_.size()); 575 576 in_sequence_updates_.clear(); 577 578 // Should still be able to access article and pages. 579 VerifyArticleProtoMatchesMultipageData( 580 article_proto_.get(), distiller_data.get(), kNumPages); 581 } 582 583 TEST_F(DistillerTest, DeletingArticleDoesNotInterfereWithUpdates) { 584 base::MessageLoopForUI loop; 585 const size_t kNumPages = 8; 586 scoped_ptr<MultipageDistillerData> distiller_data = 587 CreateMultipageDistillerDataWithoutImages(kNumPages); 588 // The page number of the article on which distillation starts. 589 int start_page_num = 3; 590 591 distiller_.reset( 592 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions())); 593 DistillPage(distiller_data->page_urls[start_page_num], 594 CreateMockDistillerPages( 595 distiller_data.get(), kNumPages, start_page_num).Pass()); 596 base::MessageLoop::current()->RunUntilIdle(); 597 EXPECT_EQ(kNumPages, in_sequence_updates_.size()); 598 EXPECT_EQ(kTitle, article_proto_->title()); 599 ASSERT_EQ(kNumPages, static_cast<size_t>(article_proto_->pages_size())); 600 601 // Delete the article. 602 article_proto_.reset(); 603 VerifyIncrementalUpdatesMatch( 604 distiller_data.get(), kNumPages, in_sequence_updates_, start_page_num); 605 } 606 607 TEST_F(DistillerTest, CancelWithDelayedImageFetchCallback) { 608 base::MessageLoopForUI loop; 609 vector<int> image_indices; 610 image_indices.push_back(0); 611 scoped_ptr<base::Value> distilled_value = 612 CreateDistilledValueReturnedFromJS(kTitle, kContent, image_indices, ""); 613 TestDistillerURLFetcher* delayed_fetcher = new TestDistillerURLFetcher(true); 614 MockDistillerURLFetcherFactory mock_url_fetcher_factory; 615 EXPECT_CALL(mock_url_fetcher_factory, CreateDistillerURLFetcher()) 616 .WillOnce(Return(delayed_fetcher)); 617 distiller_.reset( 618 new DistillerImpl(mock_url_fetcher_factory, DomDistillerOptions())); 619 DistillPage( 620 kURL, CreateMockDistillerPage(distilled_value.get(), GURL(kURL)).Pass()); 621 base::MessageLoop::current()->RunUntilIdle(); 622 623 // Post callback from the url fetcher and then delete the distiller. 624 delayed_fetcher->PostCallbackTask(); 625 distiller_.reset(); 626 627 base::MessageLoop::current()->RunUntilIdle(); 628 } 629 630 TEST_F(DistillerTest, CancelWithDelayedJSCallback) { 631 base::MessageLoopForUI loop; 632 scoped_ptr<base::Value> distilled_value = 633 CreateDistilledValueReturnedFromJS(kTitle, kContent, vector<int>(), ""); 634 MockDistillerPage* distiller_page = NULL; 635 distiller_.reset( 636 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions())); 637 DistillPage(kURL, 638 CreateMockDistillerPageWithPendingJSCallback(&distiller_page, 639 GURL(kURL))); 640 base::MessageLoop::current()->RunUntilIdle(); 641 642 ASSERT_TRUE(distiller_page); 643 // Post the task to execute javascript and then delete the distiller. 644 distiller_page->OnDistillationDone(GURL(kURL), distilled_value.get()); 645 distiller_.reset(); 646 647 base::MessageLoop::current()->RunUntilIdle(); 648 } 649 650 } // namespace dom_distiller 651