diff options
author | yfriedman@chromium.org <yfriedman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-15 08:03:22 +0000 |
---|---|---|
committer | yfriedman@chromium.org <yfriedman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-15 08:03:22 +0000 |
commit | 29bf8f4e29de59621652339a1a39bbf7bfb0d6b1 (patch) | |
tree | 20044466f8de1f73f7ec1967f0f5d2c342b35a46 | |
parent | a3100203f089d73eeb9ce4a071cbae1a84a70d40 (diff) | |
download | chromium_src-29bf8f4e29de59621652339a1a39bbf7bfb0d6b1.zip chromium_src-29bf8f4e29de59621652339a1a39bbf7bfb0d6b1.tar.gz chromium_src-29bf8f4e29de59621652339a1a39bbf7bfb0d6b1.tar.bz2 |
[dom_distiller] Add support for incremental viewer.
The viewed page can now be rendered all at once, or with a single page
update, followed by additional page content sent via HTML. While more
pages are loading, a progress indicator is present.
The title for a page was added to DistilledPageProto so its accessible
during partial distillation. This was already stored in
DistilledPageData so it's just moved.
BUG=319881
Review URL: https://codereview.chromium.org/260073009
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@270622 0039d316-1c4b-4281-b951-d872f2087c98
19 files changed, 481 insertions, 55 deletions
diff --git a/build/ios/grit_whitelist.txt b/build/ios/grit_whitelist.txt index a9b99ac..8c8b537 100644 --- a/build/ios/grit_whitelist.txt +++ b/build/ios/grit_whitelist.txt @@ -240,6 +240,7 @@ IDS_DISABLE_TOUCH_ADJUSTMENT_DESCRIPTION IDS_DISABLE_TOUCH_ADJUSTMENT_NAME IDS_DOM_DISTILLER_VIEWER_FAILED_TO_FIND_ARTICLE_CONTENT IDS_DOM_DISTILLER_VIEWER_FAILED_TO_FIND_ARTICLE_TITLE +IDS_DOM_DISTILLER_VIEWER_LOADING_STRING IDS_DOM_DISTILLER_VIEWER_NO_DATA_CONTENT IDS_DOM_DISTILLER_VIEWER_NO_DATA_TITLE IDS_DOM_DISTILLER_VIEWER_VIEW_ORIGINAL diff --git a/chrome/browser/dom_distiller/dom_distiller_viewer_source_browsertest.cc b/chrome/browser/dom_distiller/dom_distiller_viewer_source_browsertest.cc index 98a0a69..ae6e19e 100644 --- a/chrome/browser/dom_distiller/dom_distiller_viewer_source_browsertest.cc +++ b/chrome/browser/dom_distiller/dom_distiller_viewer_source_browsertest.cc @@ -6,6 +6,8 @@ #include "base/command_line.h" #include "base/guid.h" +#include "base/path_service.h" +#include "base/strings/utf_string_conversions.h" #include "chrome/browser/dom_distiller/dom_distiller_service_factory.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" @@ -21,23 +23,39 @@ #include "components/dom_distiller/core/dom_distiller_test_util.h" #include "components/dom_distiller/core/fake_db.h" #include "components/dom_distiller/core/fake_distiller.h" +#include "components/dom_distiller/core/fake_distiller_page.h" #include "components/dom_distiller/core/task_tracker.h" +#include "components/dom_distiller/core/url_constants.h" #include "components/dom_distiller/core/url_utils.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/url_data_source.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_observer.h" +#include "content/public/test/browser_test_utils.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace dom_distiller { using test::FakeDB; using test::FakeDistiller; +using test::MockDistillerPage; using test::MockDistillerFactory; +using test::MockDistillerPageFactory; using test::util::CreateStoreWithFakeDB; +using testing::HasSubstr; +using testing::Not; namespace { +const char kGetLoadIndicatorClassName[] = + "window.domAutomationController.send(" + "document.getElementById('loadingIndicator').className)"; + +const char kGetContent[] = + "window.domAutomationController.send(" + "document.getElementById('content').innerHTML)"; + void AddEntry(const ArticleEntry& e, FakeDB::EntryMap* map) { (*map)[e.entry_id()] = e; } @@ -122,34 +140,41 @@ class DomDistillerViewerSourceBrowserTest : public InProcessBrowserTest { static KeyedService* Build(content::BrowserContext* context) { FakeDB* fake_db = new FakeDB(database_model_); - MockDistillerFactory* factory = new MockDistillerFactory(); + distiller_factory_ = new MockDistillerFactory(); + MockDistillerPageFactory* distiller_page_factory_ = + new MockDistillerPageFactory(); DomDistillerContextKeyedService* service = new DomDistillerContextKeyedService( scoped_ptr<DomDistillerStoreInterface>( CreateStoreWithFakeDB(fake_db, FakeDB::EntryMap())), - scoped_ptr<DistillerFactory>(factory), - scoped_ptr<DistillerPageFactory>()); + scoped_ptr<DistillerFactory>(distiller_factory_), + scoped_ptr<DistillerPageFactory>(distiller_page_factory_)); + MockDistillerPage* distiller_page = new MockDistillerPage(); + EXPECT_CALL(*distiller_page_factory_, CreateDistillerPageImpl()) + .WillOnce(testing::Return(distiller_page)); fake_db->InitCallback(true); fake_db->LoadCallback(true); if (expect_distillation_) { // There will only be destillation of an article if the database contains // the article. FakeDistiller* distiller = new FakeDistiller(true); - EXPECT_CALL(*factory, CreateDistillerImpl()) + EXPECT_CALL(*distiller_factory_, CreateDistillerImpl()) .WillOnce(testing::Return(distiller)); } return service; } void ViewSingleDistilledPage(const GURL& url); - // Database entries. static FakeDB::EntryMap* database_model_; static bool expect_distillation_; + static MockDistillerFactory* distiller_factory_; }; FakeDB::EntryMap* DomDistillerViewerSourceBrowserTest::database_model_; bool DomDistillerViewerSourceBrowserTest::expect_distillation_ = false; +MockDistillerFactory* DomDistillerViewerSourceBrowserTest::distiller_factory_ = + NULL; // The DomDistillerViewerSource renders untrusted content, so ensure no bindings // are enabled when the article exists in the database. @@ -226,7 +251,7 @@ IN_PROC_BROWSER_TEST_F(DomDistillerViewerSourceBrowserTest, LoadSuccessObserver observer(contents); // Navigate to a URL which the source should respond to with CSS. - std::string url_without_scheme = "://foobar/readability.css"; + std::string url_without_scheme = std::string("://foobar/") + kViewerCssPath; GURL url(chrome::kDomDistillerScheme + url_without_scheme); ui_test_utils::NavigateToURL(browser(), url); @@ -241,4 +266,102 @@ IN_PROC_BROWSER_TEST_F(DomDistillerViewerSourceBrowserTest, EXPECT_EQ("text/css", observer.web_contents()->GetContentsMimeType()); } + +IN_PROC_BROWSER_TEST_F(DomDistillerViewerSourceBrowserTest, + MultiPageArticle) { + expect_distillation_ = false; + dom_distiller::DomDistillerServiceFactory::GetInstance() + ->SetTestingFactoryAndUse(browser()->profile(), &Build); + + scoped_refptr<content::MessageLoopRunner> distillation_done_runner = + new content::MessageLoopRunner; + + FakeDistiller* distiller = new FakeDistiller( + false, + distillation_done_runner->QuitClosure()); + EXPECT_CALL(*distiller_factory_, CreateDistillerImpl()) + .WillOnce(testing::Return(distiller)); + + // Setup observer to inspect the RenderViewHost after committed navigation. + content::WebContents* contents = + browser()->tab_strip_model()->GetActiveWebContents(); + LoadSuccessObserver observer(contents); + + // Navigate to a URL and wait for the distiller to flush contents to the page. + GURL url(dom_distiller::url_utils::GetDistillerViewUrlFromUrl( + chrome::kDomDistillerScheme, GURL("http://urlthatlooksvalid.com"))); + chrome::NavigateParams params(browser(), url, content::PAGE_TRANSITION_TYPED); + chrome::Navigate(¶ms); + distillation_done_runner->Run(); + + // Fake a multi-page response from distiller. + + std::vector<scoped_refptr<ArticleDistillationUpdate::RefCountedPageProto> > + update_pages; + scoped_ptr<DistilledArticleProto> article(new DistilledArticleProto()); + + // Flush page 1. + { + scoped_refptr<base::RefCountedData<DistilledPageProto> > page_proto = + new base::RefCountedData<DistilledPageProto>(); + page_proto->data.set_url("http://foobar.1.html"); + page_proto->data.set_html("<div>Page 1 content</div>"); + update_pages.push_back(page_proto); + *(article->add_pages()) = page_proto->data; + + ArticleDistillationUpdate update(update_pages, true, false); + distiller->RunDistillerUpdateCallback(update); + + // Wait for the page load to complete as the first page completes the root + // document. + content::WaitForLoadStop(contents); + + std::string result; + EXPECT_TRUE(content::ExecuteScriptAndExtractString( + contents, kGetLoadIndicatorClassName , &result)); + EXPECT_EQ("visible", result); + + EXPECT_TRUE(content::ExecuteScriptAndExtractString( + contents, kGetContent , &result)); + EXPECT_THAT(result, HasSubstr("Page 1 content")); + EXPECT_THAT(result, Not(HasSubstr("Page 2 content"))); + } + + // Flush page 2. + { + scoped_refptr<base::RefCountedData<DistilledPageProto> > page_proto = + new base::RefCountedData<DistilledPageProto>(); + page_proto->data.set_url("http://foobar.2.html"); + page_proto->data.set_html("<div>Page 2 content</div>"); + update_pages.push_back(page_proto); + *(article->add_pages()) = page_proto->data; + + ArticleDistillationUpdate update(update_pages, false, false); + distiller->RunDistillerUpdateCallback(update); + + std::string result; + EXPECT_TRUE(content::ExecuteScriptAndExtractString( + contents, kGetLoadIndicatorClassName , &result)); + EXPECT_EQ("visible", result); + + EXPECT_TRUE(content::ExecuteScriptAndExtractString( + contents, kGetContent , &result)); + EXPECT_THAT(result, HasSubstr("Page 1 content")); + EXPECT_THAT(result, HasSubstr("Page 2 content")); + } + + // Complete the load. + distiller->RunDistillerCallback(article.Pass()); + base::RunLoop().RunUntilIdle(); + + std::string result; + EXPECT_TRUE(content::ExecuteScriptAndExtractString( + contents, kGetLoadIndicatorClassName, &result)); + EXPECT_EQ("hidden", result); + EXPECT_TRUE(content::ExecuteScriptAndExtractString( + contents, kGetContent , &result)); + EXPECT_THAT(result, HasSubstr("Page 1 content")); + EXPECT_THAT(result, HasSubstr("Page 2 content")); +} + } // namespace dom_distiller diff --git a/components/dom_distiller/content/dom_distiller_viewer_source.cc b/components/dom_distiller/content/dom_distiller_viewer_source.cc index c3351c2..22ceab6 100644 --- a/components/dom_distiller/content/dom_distiller_viewer_source.cc +++ b/components/dom_distiller/content/dom_distiller_viewer_source.cc @@ -11,21 +11,32 @@ #include "base/memory/ref_counted_memory.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" +#include "base/strings/utf_string_conversions.h" #include "components/dom_distiller/core/task_tracker.h" #include "components/dom_distiller/core/url_constants.h" #include "components/dom_distiller/core/viewer.h" +#include "content/public/browser/navigation_details.h" +#include "content/public/browser/navigation_entry.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_observer.h" +#include "net/base/url_util.h" #include "net/url_request/url_request.h" namespace dom_distiller { // Handles receiving data asynchronously for a specific entry, and passing -// it along to the data callback for the data source. +// it along to the data callback for the data source. Lifetime matches that of +// the current main frame's page in the Viewer instance. class DomDistillerViewerSource::RequestViewerHandle - : public ViewRequestDelegate { + : public ViewRequestDelegate, + public content::WebContentsObserver { public: explicit RequestViewerHandle( + content::WebContents* web_contents, + const std::string& expected_scheme, + const std::string& expected_request_path, const content::URLDataSource::GotDataCallback& callback); virtual ~RequestViewerHandle(); @@ -38,33 +49,182 @@ class DomDistillerViewerSource::RequestViewerHandle void TakeViewerHandle(scoped_ptr<ViewerHandle> viewer_handle); + // WebContentsObserver: + virtual void DidNavigateMainFrame( + const content::LoadCommittedDetails& details, + const content::FrameNavigateParams& params) OVERRIDE; + virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE; + virtual void WebContentsDestroyed() OVERRIDE; + virtual void DidFinishLoad( + int64 frame_id, + const GURL& validated_url, + bool is_main_frame, + content::RenderViewHost* render_view_host) OVERRIDE; + private: + // Sends JavaScript to the attached Viewer, buffering data if the viewer isn't + // ready. + void SendJavaScript(const std::string& buffer); + + // Cancels the current view request. Once called, no updates will be + // propagated to the view, and the request to DomDistillerService will be + // cancelled. + void Cancel(); + // The handle to the view request towards the DomDistillerService. It // needs to be kept around to ensure the distillation request finishes. scoped_ptr<ViewerHandle> viewer_handle_; - // This holds the callback to where the data retrieved is sent back. + // WebContents associated with the Viewer's render process. + content::WebContents* web_contents_; + + // The scheme hosting the current view request; + std::string expected_scheme_; + + // The query path for the current view request. + std::string expected_request_path_; + + // Holds the callback to where the data retrieved is sent back. content::URLDataSource::GotDataCallback callback_; + + // Number of pages of the distilled article content that have been rendered by + // the viewer. + int page_count_; + + // Whether the page is sufficiently initialized to handle updates from the + // distiller. + bool waiting_for_page_ready_; + + // Temporary store of pending JavaScript if the page isn't ready to receive + // data from distillation. + std::string buffer_; }; DomDistillerViewerSource::RequestViewerHandle::RequestViewerHandle( + content::WebContents* web_contents, + const std::string& expected_scheme, + const std::string& expected_request_path, const content::URLDataSource::GotDataCallback& callback) - : callback_(callback) { + : web_contents_(web_contents), + expected_scheme_(expected_scheme), + expected_request_path_(expected_request_path), + callback_(callback), + page_count_(0), + waiting_for_page_ready_(true) { + content::WebContentsObserver::Observe(web_contents_); } DomDistillerViewerSource::RequestViewerHandle::~RequestViewerHandle() { + // Balanced with constructor although can be a no-op if frame navigated away. + content::WebContentsObserver::Observe(NULL); +} + +void DomDistillerViewerSource::RequestViewerHandle::SendJavaScript( + const std::string& buffer) { + if (waiting_for_page_ready_) { + buffer_ += buffer; + } else { + if (web_contents_) { + web_contents_->GetMainFrame()->ExecuteJavaScript( + base::UTF8ToUTF16(buffer)); + } + } +} + +void DomDistillerViewerSource::RequestViewerHandle::DidNavigateMainFrame( + const content::LoadCommittedDetails& details, + const content::FrameNavigateParams& params) { + const GURL& navigation = details.entry->GetURL(); + if (details.is_in_page || ( + navigation.SchemeIs(expected_scheme_.c_str()) && + expected_request_path_ == navigation.query())) { + // In-page navigations, as well as the main view request can be ignored. + return; + } + + Cancel(); + +} + +void DomDistillerViewerSource::RequestViewerHandle::RenderProcessGone( + base::TerminationStatus status) { + Cancel(); +} + +void DomDistillerViewerSource::RequestViewerHandle::WebContentsDestroyed() { + Cancel(); +} + +void DomDistillerViewerSource::RequestViewerHandle::Cancel() { + // Ensure we don't send any incremental updates to the Viewer. + web_contents_ = NULL; + + // No need to listen for notifications. + content::WebContentsObserver::Observe(NULL); + + // Schedule the Viewer for deletion. Ensures distillation is cancelled, and + // any pending data stored in |buffer_| is released. + base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); +} + +void DomDistillerViewerSource::RequestViewerHandle::DidFinishLoad( + int64 frame_id, + const GURL& validated_url, + bool is_main_frame, + content::RenderViewHost* render_view_host) { + if (!is_main_frame || web_contents_ == NULL) { + return; + } + waiting_for_page_ready_ = false; + if (buffer_.empty()) { + return; + } + if (web_contents_) { + web_contents_->GetMainFrame()->ExecuteJavaScript( + base::UTF8ToUTF16(buffer_)); + } + buffer_.clear(); } void DomDistillerViewerSource::RequestViewerHandle::OnArticleReady( const DistilledArticleProto* article_proto) { - std::string unsafe_page_html = viewer::GetUnsafeHtml(article_proto); - callback_.Run(base::RefCountedString::TakeString(&unsafe_page_html)); - base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); + if (page_count_ == 0) { + // This is a single-page article. + std::string unsafe_page_html = viewer::GetUnsafeArticleHtml(article_proto); + callback_.Run(base::RefCountedString::TakeString(&unsafe_page_html)); + } else if (page_count_ == article_proto->pages_size()) { + // We may still be showing the "Loading" indicator. + SendJavaScript(viewer::GetToggleLoadingIndicatorJs(true)); + } else { + // It's possible that we didn't get some incremental updates from the + // distiller. Ensure all remaining pages are flushed to the viewer. + for (;page_count_ < article_proto->pages_size(); page_count_++) { + const DistilledPageProto& page = article_proto->pages(page_count_); + SendJavaScript( + viewer::GetUnsafeIncrementalDistilledPageJs( + &page, + page_count_ == article_proto->pages_size())); + } + } + // No need to hold on to the ViewerHandle now that distillation is complete. + viewer_handle_.reset(); } void DomDistillerViewerSource::RequestViewerHandle::OnArticleUpdated( ArticleDistillationUpdate article_update) { - // TODO(nyquist): Add support for displaying pages incrementally. + for (;page_count_ < static_cast<int>(article_update.GetPagesSize()); + page_count_++) { + const DistilledPageProto& page = + article_update.GetDistilledPage(page_count_); + if (page_count_ == 0) { + // This is the first page, so send Viewer page scaffolding too. + std::string unsafe_page_html = viewer::GetUnsafePartialArticleHtml(&page); + callback_.Run(base::RefCountedString::TakeString(&unsafe_page_html)); + } else { + SendJavaScript( + viewer::GetUnsafeIncrementalDistilledPageJs(&page, false)); + } + } } void DomDistillerViewerSource::RequestViewerHandle::TakeViewerHandle( @@ -98,14 +258,23 @@ void DomDistillerViewerSource::StartDataRequest( DCHECK(render_view_host); CHECK_EQ(0, render_view_host->GetEnabledBindings()); - if (kCssPath == path) { + if (kViewerCssPath == path) { std::string css = viewer::GetCss(); callback.Run(base::RefCountedString::TakeString(&css)); return; } - + if (kViewerJsPath == path) { + std::string js = viewer::GetJavaScript(); + callback.Run(base::RefCountedString::TakeString(&js)); + return; + } + content::WebContents* web_contents = + content::WebContents::FromRenderFrameHost( + content::RenderFrameHost::FromID(render_process_id, + render_frame_id)); + DCHECK(web_contents); RequestViewerHandle* request_viewer_handle = - new RequestViewerHandle(callback); + new RequestViewerHandle(web_contents, scheme_, path.substr(1), callback); scoped_ptr<ViewerHandle> viewer_handle = viewer::CreateViewRequest( dom_distiller_service_, path, request_viewer_handle); @@ -127,8 +296,12 @@ void DomDistillerViewerSource::StartDataRequest( std::string DomDistillerViewerSource::GetMimeType( const std::string& path) const { - if (path == kCssPath) + if (kViewerCssPath == path) { return "text/css"; + } + if (kViewerJsPath == path) { + return "text/javascript"; + } return "text/html"; } diff --git a/components/dom_distiller/content/dom_distiller_viewer_source_unittest.cc b/components/dom_distiller/content/dom_distiller_viewer_source_unittest.cc index 1938dd8..55e5b4b 100644 --- a/components/dom_distiller/content/dom_distiller_viewer_source_unittest.cc +++ b/components/dom_distiller/content/dom_distiller_viewer_source_unittest.cc @@ -22,8 +22,10 @@ class DomDistillerViewerSourceTest : public testing::Test { }; TEST_F(DomDistillerViewerSourceTest, TestMimeType) { - EXPECT_EQ("text/css", source_.get()->GetMimeType(kCssPath)); + EXPECT_EQ("text/css", source_.get()->GetMimeType(kViewerCssPath)); EXPECT_EQ("text/html", source_.get()->GetMimeType("anythingelse")); + EXPECT_EQ("text/javascript", source_.get()->GetMimeType(kViewerJsPath)); + } } // namespace dom_distiller diff --git a/components/dom_distiller/content/resources/dom_distiller_viewer.html b/components/dom_distiller/content/resources/dom_distiller_viewer.html index 03929c4..13e58f8 100644 --- a/components/dom_distiller/content/resources/dom_distiller_viewer.html +++ b/components/dom_distiller/content/resources/dom_distiller_viewer.html @@ -9,21 +9,21 @@ found in the LICENSE file. <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"> <title>$1</title> - <link rel="stylesheet" type="text/css" href="/$2"> + <link rel="stylesheet" href="/$2"> + <script src="/$3"></script> </head> <body> <div id="mainContent"> - <div id="article"> - <article> - <header> - <h1>$3</h1> - </header> - <div id="content">$4</div> - </article> - </div> + <article> + <header> + <h1>$4</h1> + </header> + <div id="content">$5</div> + </article> </div> + <div id="loadingIndicator" class="$6">$7</div> <div> - <a href="$5">$6</a> + <a href="$8">$9</a> </div> </body> </html> diff --git a/components/dom_distiller/content/resources/dom_distiller_viewer.js b/components/dom_distiller/content/resources/dom_distiller_viewer.js new file mode 100644 index 0000000..854e1d0 --- /dev/null +++ b/components/dom_distiller/content/resources/dom_distiller_viewer.js @@ -0,0 +1,14 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +function addToPage(html) { + var div = document.createElement('div'); + div.innerHTML = html; + document.getElementById('content').appendChild(div); +} + +function showLoadingIndicator(isLastPage) { + document.getElementById('loadingIndicator').className = + isLastPage ? 'hidden' : 'visible'; +}
\ No newline at end of file diff --git a/components/dom_distiller/core/css/distilledpage.css b/components/dom_distiller/core/css/distilledpage.css index a71a814..fc7b565 100644 --- a/components/dom_distiller/core/css/distilledpage.css +++ b/components/dom_distiller/core/css/distilledpage.css @@ -14,3 +14,6 @@ img{max-width:100%;} .margin-x-wide{width:35%;} /* Override html styling attributes */ table,tr,td{background-color:transparent!important;} + +.hidden{display:none} +.visible{display:block}
\ No newline at end of file diff --git a/components/dom_distiller/core/distiller.cc b/components/dom_distiller/core/distiller.cc index 0b97958..070e681 100644 --- a/components/dom_distiller/core/distiller.cc +++ b/components/dom_distiller/core/distiller.cc @@ -140,8 +140,7 @@ void DistillerImpl::OnPageDistillationFinished( page_data->distilled_page_proto = new base::RefCountedData<DistilledPageProto>(); page_data->page_num = page_num; - page_data->title = distilled_page->title; - + page_data->distilled_page_proto->data.set_title(distilled_page->title); page_data->distilled_page_proto->data.set_url(page_url.spec()); page_data->distilled_page_proto->data.set_html(distilled_page->html); @@ -266,7 +265,7 @@ void DistillerImpl::RunDistillerCallbackIfDone() { *(article_proto->add_pages()) = page_data->distilled_page_proto->data; if (first_page) { - article_proto->set_title(page_data->title); + article_proto->set_title(page_data->distilled_page_proto->data.title()); first_page = false; } diff --git a/components/dom_distiller/core/distiller.h b/components/dom_distiller/core/distiller.h index 9e0eb84..106967b 100644 --- a/components/dom_distiller/core/distiller.h +++ b/components/dom_distiller/core/distiller.h @@ -91,7 +91,6 @@ class DistillerImpl : public Distiller { virtual ~DistilledPageData(); // Relative page number of the page. int page_num; - std::string title; ScopedVector<DistillerURLFetcher> image_fetchers_; scoped_refptr<base::RefCountedData<DistilledPageProto> > distilled_page_proto; diff --git a/components/dom_distiller/core/fake_db.cc b/components/dom_distiller/core/fake_db.cc index 2b9b1f8..5ada8f0 100644 --- a/components/dom_distiller/core/fake_db.cc +++ b/components/dom_distiller/core/fake_db.cc @@ -15,7 +15,7 @@ FakeDB::~FakeDB() {} void FakeDB::Init(const base::FilePath& database_dir, DomDistillerDatabaseInterface::InitCallback callback) { - dir_ = database_dir; + dir_ = database_dir; init_callback_ = callback; } diff --git a/components/dom_distiller/core/fake_distiller.cc b/components/dom_distiller/core/fake_distiller.cc index bb5ef92..8211032 100644 --- a/components/dom_distiller/core/fake_distiller.cc +++ b/components/dom_distiller/core/fake_distiller.cc @@ -6,6 +6,7 @@ #include "base/auto_reset.h" #include "base/bind.h" +#include "base/callback_helpers.h" #include "base/message_loop/message_loop.h" #include "testing/gtest/include/gtest/gtest.h" @@ -21,6 +22,15 @@ FakeDistiller::FakeDistiller(bool execute_callback) EXPECT_CALL(*this, Die()).Times(testing::AnyNumber()); } +FakeDistiller::FakeDistiller( + bool execute_callback, + const base::Closure& distillation_initiated_callback) + : execute_callback_(execute_callback), + destruction_allowed_(true), + distillation_initiated_callback_(distillation_initiated_callback) { + EXPECT_CALL(*this, Die()).Times(testing::AnyNumber()); +} + FakeDistiller::~FakeDistiller() { EXPECT_TRUE(destruction_allowed_); Die(); @@ -34,6 +44,9 @@ void FakeDistiller::DistillPage( url_ = url; article_callback_ = article_callback; page_callback_ = page_callback; + if (!distillation_initiated_callback_.is_null()) { + base::ResetAndReturn(&distillation_initiated_callback_).Run(); + } if (execute_callback_) { scoped_ptr<DistilledArticleProto> proto(new DistilledArticleProto); proto->add_pages()->set_url(url_.spec()); @@ -49,6 +62,12 @@ void FakeDistiller::RunDistillerCallback( PostDistillerCallback(proto.Pass()); } +void FakeDistiller::RunDistillerUpdateCallback( + const ArticleDistillationUpdate& update) { + page_callback_.Run(update); +} + + void FakeDistiller::PostDistillerCallback( scoped_ptr<DistilledArticleProto> proto) { base::MessageLoop::current()->PostTask( diff --git a/components/dom_distiller/core/fake_distiller.h b/components/dom_distiller/core/fake_distiller.h index 8a3dc36..62cbcd7 100644 --- a/components/dom_distiller/core/fake_distiller.h +++ b/components/dom_distiller/core/fake_distiller.h @@ -5,6 +5,7 @@ #ifndef COMPONENTS_DOM_DISTILLER_CORE_FAKE_DISTILLER_H_ #define COMPONENTS_DOM_DISTILLER_CORE_FAKE_DISTILLER_H_ +#include "base/callback.h" #include "components/dom_distiller/core/article_distillation_update.h" #include "components/dom_distiller/core/article_entry.h" #include "components/dom_distiller/core/distiller.h" @@ -32,6 +33,10 @@ class FakeDistiller : public Distiller { // immediately be posted to execute the callback with a simple // DistilledArticleProto. explicit FakeDistiller(bool execute_callback); + // TODO(yfriedman): Drop execute_callback from this and give the option of + // "auto-distilling" or calling the provided closure. + explicit FakeDistiller(bool execute_callback, + const base::Closure& distillation_initiated_callback); virtual ~FakeDistiller(); MOCK_METHOD0(Die, void()); @@ -42,6 +47,7 @@ class FakeDistiller : public Distiller { const DistillationUpdateCallback& page_callback) OVERRIDE; void RunDistillerCallback(scoped_ptr<DistilledArticleProto> proto); + void RunDistillerUpdateCallback(const ArticleDistillationUpdate& update); GURL GetUrl() { return url_; } @@ -57,8 +63,9 @@ class FakeDistiller : public Distiller { GURL url_; DistillationFinishedCallback article_callback_; DistillationUpdateCallback page_callback_; - bool destruction_allowed_; + // Used to notify when distillation is complete. + base::Closure distillation_initiated_callback_; }; } // namespace test diff --git a/components/dom_distiller/core/proto/distilled_page.proto b/components/dom_distiller/core/proto/distilled_page.proto index ba71617..b12098d 100644 --- a/components/dom_distiller/core/proto/distilled_page.proto +++ b/components/dom_distiller/core/proto/distilled_page.proto @@ -28,4 +28,7 @@ message DistilledPageProto { // The images referenced in the HTML. repeated Image image = 4; -} + + // Title for the current page. + optional string title = 5; +}
\ No newline at end of file diff --git a/components/dom_distiller/core/url_constants.cc b/components/dom_distiller/core/url_constants.cc index f890807..4d94361 100644 --- a/components/dom_distiller/core/url_constants.cc +++ b/components/dom_distiller/core/url_constants.cc @@ -8,6 +8,7 @@ namespace dom_distiller { const char kEntryIdKey[] = "entry_id"; const char kUrlKey[] = "url"; -const char kCssPath[] = "readability.css"; +const char kViewerCssPath[] = "dom_distiller_viewer.css"; +const char kViewerJsPath[] = "dom_distiller_viewer.js"; } // namespace dom_distiller diff --git a/components/dom_distiller/core/url_constants.h b/components/dom_distiller/core/url_constants.h index a724a22..71a8a8b 100644 --- a/components/dom_distiller/core/url_constants.h +++ b/components/dom_distiller/core/url_constants.h @@ -9,7 +9,8 @@ namespace dom_distiller { extern const char kEntryIdKey[]; extern const char kUrlKey[]; -extern const char kCssPath[]; +extern const char kViewerCssPath[]; +extern const char kViewerJsPath[]; } // namespace dom_distiller diff --git a/components/dom_distiller/core/viewer.cc b/components/dom_distiller/core/viewer.cc index 365a536..072cff8 100644 --- a/components/dom_distiller/core/viewer.cc +++ b/components/dom_distiller/core/viewer.cc @@ -7,6 +7,7 @@ #include <string> #include <vector> +#include "base/json/json_writer.h" #include "base/memory/scoped_ptr.h" #include "base/strings/string_util.h" #include "components/dom_distiller/core/dom_distiller_service.h" @@ -27,20 +28,26 @@ namespace dom_distiller { namespace { -std::string ReplaceHtmlTemplateValues(const std::string& title, - const std::string& content, - const std::string& original_url) { +std::string ReplaceHtmlTemplateValues( + const std::string& title, + const std::string& content, + const std::string& loading_indicator_class, + const std::string& original_url) { base::StringPiece html_template = ResourceBundle::GetSharedInstance().GetRawDataResource( IDR_DOM_DISTILLER_VIEWER_HTML); std::vector<std::string> substitutions; - substitutions.push_back(title); // $1 - substitutions.push_back(kCssPath); // $2 - substitutions.push_back(title); // $3 - substitutions.push_back(content); // $4 - substitutions.push_back(original_url); // $5 + substitutions.push_back(title); // $1 + substitutions.push_back(kViewerCssPath); // $2 + substitutions.push_back(kViewerJsPath); // $3 + substitutions.push_back(title); // $4 + substitutions.push_back(content); // $5 + substitutions.push_back(loading_indicator_class); // $6 substitutions.push_back( - l10n_util::GetStringUTF8(IDS_DOM_DISTILLER_VIEWER_VIEW_ORIGINAL)); // $6 + l10n_util::GetStringUTF8(IDS_DOM_DISTILLER_VIEWER_LOADING_STRING)); // $7 + substitutions.push_back(original_url); // $8 + substitutions.push_back( + l10n_util::GetStringUTF8(IDS_DOM_DISTILLER_VIEWER_VIEW_ORIGINAL)); // $9 return ReplaceStringPlaceholders(html_template, substitutions, NULL); } @@ -48,15 +55,48 @@ std::string ReplaceHtmlTemplateValues(const std::string& title, namespace viewer { -const std::string GetUnsafeHtml(const DistilledArticleProto* article_proto) { +const std::string GetUnsafeIncrementalDistilledPageJs( + const DistilledPageProto* page_proto, + const bool is_last_page) { + std::string output; + base::StringValue value(page_proto->html()); + base::JSONWriter::Write(&value, &output); + std::string page_update("addToPage("); + page_update += output + ");"; + return page_update + GetToggleLoadingIndicatorJs( + is_last_page); + +} + +const std::string GetToggleLoadingIndicatorJs(const bool is_last_page) { + if (is_last_page) + return "showLoadingIndicator(true);"; + else + return "showLoadingIndicator(false);"; +} + +const std::string GetUnsafePartialArticleHtml( + const DistilledPageProto* page_proto) { + DCHECK(page_proto); + std::string title = net::EscapeForHTML(page_proto->title()); + std::ostringstream unsafe_output_stream; + unsafe_output_stream << page_proto->html(); + std::string unsafe_article_html = unsafe_output_stream.str(); + std::string original_url = page_proto->url(); + return ReplaceHtmlTemplateValues(title, + unsafe_article_html, + "visible", + original_url); +} + +const std::string GetUnsafeArticleHtml( + const DistilledArticleProto* article_proto) { DCHECK(article_proto); std::string title; std::string unsafe_article_html; if (article_proto->has_title() && article_proto->pages_size() > 0 && article_proto->pages(0).has_html()) { title = net::EscapeForHTML(article_proto->title()); - // TODO(shashishekhar): Add support for correcting displaying multiple pages - // after discussing the right way to display them. std::ostringstream unsafe_output_stream; for (int page_num = 0; page_num < article_proto->pages_size(); ++page_num) { unsafe_output_stream << article_proto->pages(page_num).html(); @@ -73,7 +113,10 @@ const std::string GetUnsafeHtml(const DistilledArticleProto* article_proto) { original_url = article_proto->pages(0).url(); } - return ReplaceHtmlTemplateValues(title, unsafe_article_html, original_url); + return ReplaceHtmlTemplateValues(title, + unsafe_article_html, + "hidden", + original_url); } const std::string GetErrorPageHtml() { @@ -81,7 +124,7 @@ const std::string GetErrorPageHtml() { IDS_DOM_DISTILLER_VIEWER_FAILED_TO_FIND_ARTICLE_TITLE); std::string content = l10n_util::GetStringUTF8( IDS_DOM_DISTILLER_VIEWER_FAILED_TO_FIND_ARTICLE_CONTENT); - return ReplaceHtmlTemplateValues(title, content, ""); + return ReplaceHtmlTemplateValues(title, content, "hidden", ""); } const std::string GetCss() { @@ -90,6 +133,12 @@ const std::string GetCss() { .as_string(); } +const std::string GetJavaScript() { + return ResourceBundle::GetSharedInstance() + .GetRawDataResource(IDR_DOM_DISTILLER_VIEWER_JS) + .as_string(); +} + scoped_ptr<ViewerHandle> CreateViewRequest( DomDistillerServiceInterface* dom_distiller_service, const std::string& path, diff --git a/components/dom_distiller/core/viewer.h b/components/dom_distiller/core/viewer.h index 61aac14..98a7e99 100644 --- a/components/dom_distiller/core/viewer.h +++ b/components/dom_distiller/core/viewer.h @@ -10,10 +10,12 @@ #include "base/basictypes.h" #include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" namespace dom_distiller { class DistilledArticleProto; +class DistilledPageProto; class DomDistillerServiceInterface; class ViewerHandle; class ViewRequestDelegate; @@ -21,9 +23,32 @@ class ViewRequestDelegate; namespace viewer { // Returns a full HTML page based on the given |article_proto|. This is supposed -// to displayed to the end user. The returned HTML should be considered unsafe, -// so callers must ensure rendering it does not compromise Chrome. -const std::string GetUnsafeHtml(const DistilledArticleProto* article_proto); +// to be displayed to the end user. The returned HTML should be considered +// unsafe, so callers must ensure rendering it does not compromise Chrome. +const std::string GetUnsafeArticleHtml( + const DistilledArticleProto* article_proto); + +// Returns the base Viewer HTML page based on the given |page_proto|. This is +// supposed to be displayed to the end user. The returned HTML should be +// considered unsafe, so callers must ensure rendering it does not compromise +// Chrome. The difference from |GetUnsafeArticleHtml| is that this can be used +// for displaying an in-flight distillation instead of waiting for the full +// article. +const std::string GetUnsafePartialArticleHtml( + const DistilledPageProto* page_proto); + +// Returns a JavaScript blob for updating a partial view request with additional +// distilled content. Meant for use when viewing a slow or long multi-page +// article. |is_last_page| indicates whether this is the last page of the +// article. +const std::string GetUnsafeIncrementalDistilledPageJs( + const DistilledPageProto* page_proto, + const bool is_last_page); + +// Returns a JavaScript blob for controlling the "in-progress" indicator when +// viewing a partially-distilled page. |is_last_page| indicates whether this is +// the last page of the article (i.e. loading indicator should be removed). +const std::string GetToggleLoadingIndicatorJs(const bool is_last_page); // Returns a full HTML page which displays a generic error. const std::string GetErrorPageHtml(); @@ -31,6 +56,9 @@ const std::string GetErrorPageHtml(); // Returns the default CSS to be used for a viewer. const std::string GetCss(); +// Returns the default JS to be used for a viewer. +const std::string GetJavaScript(); + // Based on the given path, calls into the DomDistillerServiceInterface for // viewing distilled content based on the |path|. scoped_ptr<ViewerHandle> CreateViewRequest( diff --git a/components/dom_distiller_strings.grdp b/components/dom_distiller_strings.grdp index d8c9eb2..ee0f390 100644 --- a/components/dom_distiller_strings.grdp +++ b/components/dom_distiller_strings.grdp @@ -40,5 +40,8 @@ <message name="IDS_DOM_DISTILLER_VIEWER_VIEW_ORIGINAL" desc="The text to show on a link of an article to view the original page."> View Original </message> + <message name="IDS_DOM_DISTILLER_VIEWER_LOADING_STRING" desc="The text to show indicating that the viewer is still loading more content from the article."> + Loading... + </message> </grit-part> diff --git a/components/resources/dom_distiller_resources.grdp b/components/resources/dom_distiller_resources.grdp index edb3489..122b800 100644 --- a/components/resources/dom_distiller_resources.grdp +++ b/components/resources/dom_distiller_resources.grdp @@ -4,6 +4,7 @@ <include name="IDR_ABOUT_DOM_DISTILLER_CSS" file="../dom_distiller/webui/resources/about_dom_distiller.css" type="BINDATA" /> <include name="IDR_ABOUT_DOM_DISTILLER_JS" file="../dom_distiller/webui/resources/about_dom_distiller.js" type="BINDATA" /> <include name="IDR_DOM_DISTILLER_VIEWER_HTML" file="../dom_distiller/content/resources/dom_distiller_viewer.html" type="BINDATA" /> + <include name="IDR_DOM_DISTILLER_VIEWER_JS" file="../dom_distiller/content/resources/dom_distiller_viewer.js" type="BINDATA" /> <include name="IDR_DISTILLER_JS" file="../dom_distiller/core/javascript/domdistiller.js" flattenhtml="true" type="BINDATA" /> <include name="IDR_DISTILLER_CSS" file="../dom_distiller/core/css/distilledpage.css" type="BINDATA" /> </grit-part> |