summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoryfriedman@chromium.org <yfriedman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-05-15 08:03:22 +0000
committeryfriedman@chromium.org <yfriedman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-05-15 08:03:22 +0000
commit29bf8f4e29de59621652339a1a39bbf7bfb0d6b1 (patch)
tree20044466f8de1f73f7ec1967f0f5d2c342b35a46
parenta3100203f089d73eeb9ce4a071cbae1a84a70d40 (diff)
downloadchromium_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
-rw-r--r--build/ios/grit_whitelist.txt1
-rw-r--r--chrome/browser/dom_distiller/dom_distiller_viewer_source_browsertest.cc135
-rw-r--r--components/dom_distiller/content/dom_distiller_viewer_source.cc197
-rw-r--r--components/dom_distiller/content/dom_distiller_viewer_source_unittest.cc4
-rw-r--r--components/dom_distiller/content/resources/dom_distiller_viewer.html20
-rw-r--r--components/dom_distiller/content/resources/dom_distiller_viewer.js14
-rw-r--r--components/dom_distiller/core/css/distilledpage.css3
-rw-r--r--components/dom_distiller/core/distiller.cc5
-rw-r--r--components/dom_distiller/core/distiller.h1
-rw-r--r--components/dom_distiller/core/fake_db.cc2
-rw-r--r--components/dom_distiller/core/fake_distiller.cc19
-rw-r--r--components/dom_distiller/core/fake_distiller.h9
-rw-r--r--components/dom_distiller/core/proto/distilled_page.proto5
-rw-r--r--components/dom_distiller/core/url_constants.cc3
-rw-r--r--components/dom_distiller/core/url_constants.h3
-rw-r--r--components/dom_distiller/core/viewer.cc77
-rw-r--r--components/dom_distiller/core/viewer.h34
-rw-r--r--components/dom_distiller_strings.grdp3
-rw-r--r--components/resources/dom_distiller_resources.grdp1
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(&params);
+ 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>