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