Commit 19298d79 authored by nyquist@chromium.org's avatar nyquist@chromium.org

Add support for distilling current WebContents

This CL adds the utilities needed for using the current WebContents when
distilling web pages.

BUG=361939

Review URL: https://codereview.chromium.org/266073003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@272611 0039d316-1c4b-4281-b951-d872f2087c98
parent 0ecd06fe
// 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.
package org.chromium.chrome.browser.dom_distiller;
import org.chromium.base.JNINamespace;
import org.chromium.content_public.browser.WebContents;
/**
* A helper class for using the DOM Distiller.
*/
@JNINamespace("android")
public class DomDistillerTabUtils {
private DomDistillerTabUtils() {
}
/**
* Creates a new WebContents and navigates the {@link WebContents} to view the URL of the
* current page, while in the background starts distilling the current page. This method takes
* ownership over the old WebContents after swapping in the new one.
*
* @param webContents the WebContents to distill.
*/
public static void distillCurrentPageAndView(WebContents webContents) {
nativeDistillCurrentPageAndView(webContents);
}
private static native void nativeDistillCurrentPageAndView(WebContents webContents);
}
......@@ -38,6 +38,7 @@
#include "chrome/browser/android/url_utilities.h"
#include "chrome/browser/android/voice_search_tab_helper.h"
#include "chrome/browser/autofill/android/personal_data_manager_android.h"
#include "chrome/browser/dom_distiller/tab_utils_android.h"
#include "chrome/browser/history/android/sqlite_cursor.h"
#include "chrome/browser/invalidation/invalidation_controller_android.h"
#include "chrome/browser/lifetime/application_lifetime_android.h"
......@@ -124,6 +125,7 @@ static base::android::RegistrationMethod kChromeRegisteredMethods[] = {
{ "ContextMenuHelper", RegisterContextMenuHelper },
{ "DataReductionProxySettings", DataReductionProxySettingsAndroid::Register },
{ "DevToolsServer", RegisterDevToolsServer },
{ "DomDistillerTabUtils", RegisterDomDistillerTabUtils },
{ "ExternalPrerenderRequestHandler",
prerender::ExternalPrerenderHandlerAndroid::
RegisterExternalPrerenderHandlerAndroid },
......
......@@ -7,6 +7,7 @@
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/dom_distiller/dom_distiller_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "components/dom_distiller/core/distiller_page.h"
#include "components/dom_distiller/core/dom_distiller_service.h"
#include "content/public/browser/notification_source.h"
......@@ -78,6 +79,12 @@ LazyDomDistillerService::CreateDefaultDistillerPage() {
return instance()->CreateDefaultDistillerPage();
}
scoped_ptr<DistillerPage>
LazyDomDistillerService::CreateDefaultDistillerPageWithHandle(
scoped_ptr<SourcePageHandle> handle) {
return instance()->CreateDefaultDistillerPageWithHandle(handle.Pass());
}
void LazyDomDistillerService::AddObserver(DomDistillerObserver* observer) {
instance()->AddObserver(observer);
}
......
......@@ -50,6 +50,8 @@ class LazyDomDistillerService : public DomDistillerServiceInterface,
scoped_ptr<DistillerPage> distiller_page,
const GURL& url) OVERRIDE;
virtual scoped_ptr<DistillerPage> CreateDefaultDistillerPage() OVERRIDE;
virtual scoped_ptr<DistillerPage> CreateDefaultDistillerPageWithHandle(
scoped_ptr<SourcePageHandle> handle) OVERRIDE;
virtual void AddObserver(DomDistillerObserver* observer) OVERRIDE;
virtual void RemoveObserver(DomDistillerObserver* observer) OVERRIDE;
......
// 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.
#include "chrome/browser/dom_distiller/tab_utils.h"
#include "base/message_loop/message_loop.h"
#include "chrome/browser/dom_distiller/dom_distiller_service_factory.h"
#include "chrome/browser/ui/tab_contents/core_tab_helper.h"
#include "chrome/browser/ui/tab_contents/core_tab_helper_delegate.h"
#include "chrome/common/url_constants.h"
#include "components/dom_distiller/content/distiller_page_web_contents.h"
#include "components/dom_distiller/core/distiller_page.h"
#include "components/dom_distiller/core/dom_distiller_service.h"
#include "components/dom_distiller/core/task_tracker.h"
#include "components/dom_distiller/core/url_utils.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
namespace {
using dom_distiller::ViewRequestDelegate;
using dom_distiller::DistilledArticleProto;
using dom_distiller::ArticleDistillationUpdate;
using dom_distiller::ViewerHandle;
using dom_distiller::SourcePageHandleWebContents;
using dom_distiller::DomDistillerService;
using dom_distiller::DomDistillerServiceFactory;
using dom_distiller::DistillerPage;
using dom_distiller::SourcePageHandle;
// An no-op ViewRequestDelegate which holds a ViewerHandle and deletes itself
// after the WebContents navigates or goes away. This class is a band-aid to
// keep a TaskTracker around until the distillation starts from the viewer.
class SelfDeletingRequestDelegate : public ViewRequestDelegate,
public content::WebContentsObserver {
public:
explicit SelfDeletingRequestDelegate(content::WebContents* web_contents);
virtual ~SelfDeletingRequestDelegate();
// ViewRequestDelegate implementation.
virtual void OnArticleReady(
const DistilledArticleProto* article_proto) OVERRIDE;
virtual void OnArticleUpdated(
ArticleDistillationUpdate article_update) OVERRIDE;
// content::WebContentsObserver implementation.
virtual void DidNavigateMainFrame(
const content::LoadCommittedDetails& details,
const content::FrameNavigateParams& params) OVERRIDE;
virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE;
virtual void WebContentsDestroyed() OVERRIDE;
// Takes ownership of the ViewerHandle to keep distillation alive until |this|
// is deleted.
void TakeViewerHandle(scoped_ptr<ViewerHandle> viewer_handle);
private:
// 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_;
// The WebContents this class is tracking.
content::WebContents* web_contents_;
};
void SelfDeletingRequestDelegate::DidNavigateMainFrame(
const content::LoadCommittedDetails& details,
const content::FrameNavigateParams& params) {
base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
}
void SelfDeletingRequestDelegate::RenderProcessGone(
base::TerminationStatus status) {
base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
}
void SelfDeletingRequestDelegate::WebContentsDestroyed() {
base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
}
SelfDeletingRequestDelegate::SelfDeletingRequestDelegate(
content::WebContents* web_contents)
: web_contents_(web_contents) {
content::WebContentsObserver::Observe(web_contents_);
}
SelfDeletingRequestDelegate::~SelfDeletingRequestDelegate() {
content::WebContentsObserver::Observe(NULL);
}
void SelfDeletingRequestDelegate::OnArticleReady(
const DistilledArticleProto* article_proto) {
}
void SelfDeletingRequestDelegate::OnArticleUpdated(
ArticleDistillationUpdate article_update) {
}
void SelfDeletingRequestDelegate::TakeViewerHandle(
scoped_ptr<ViewerHandle> viewer_handle) {
viewer_handle_ = viewer_handle.Pass();
}
// Start loading the viewer URL of the current page in |web_contents|.
void StartNavigationToDistillerViewer(content::WebContents* web_contents,
const GURL& url) {
GURL viewer_url = dom_distiller::url_utils::GetDistillerViewUrlFromUrl(
chrome::kDomDistillerScheme, url);
content::NavigationController::LoadURLParams params(viewer_url);
params.transition_type = content::PAGE_TRANSITION_AUTO_BOOKMARK;
web_contents->GetController().LoadURLWithParams(params);
}
void StartDistillation(content::WebContents* web_contents) {
// Start distillation using |web_contents|, and ensure ViewerHandle stays
// around until the viewer requests distillation.
SelfDeletingRequestDelegate* view_request_delegate =
new SelfDeletingRequestDelegate(web_contents);
scoped_ptr<content::WebContents> old_web_contents_sptr(web_contents);
scoped_ptr<SourcePageHandleWebContents> source_page_handle(
new SourcePageHandleWebContents(old_web_contents_sptr.Pass()));
DomDistillerService* dom_distiller_service =
DomDistillerServiceFactory::GetForBrowserContext(
web_contents->GetBrowserContext());
scoped_ptr<DistillerPage> distiller_page =
dom_distiller_service->CreateDefaultDistillerPageWithHandle(
source_page_handle.PassAs<SourcePageHandle>())
.Pass();
const GURL& last_committed_url = web_contents->GetLastCommittedURL();
scoped_ptr<ViewerHandle> viewer_handle = dom_distiller_service->ViewUrl(
view_request_delegate, distiller_page.Pass(), last_committed_url);
view_request_delegate->TakeViewerHandle(viewer_handle.Pass());
}
} // namespace
void DistillCurrentPageAndView(content::WebContents* old_web_contents) {
DCHECK(old_web_contents);
// Create new WebContents.
content::WebContents::CreateParams create_params(
old_web_contents->GetBrowserContext());
content::WebContents* new_web_contents =
content::WebContents::Create(create_params);
DCHECK(new_web_contents);
// Copy all navigation state from the old WebContents to the new one.
new_web_contents->GetController().CopyStateFrom(
old_web_contents->GetController());
CoreTabHelper::FromWebContents(old_web_contents)->delegate()->SwapTabContents(
old_web_contents, new_web_contents, false, false);
StartNavigationToDistillerViewer(new_web_contents,
old_web_contents->GetLastCommittedURL());
StartDistillation(old_web_contents);
}
// 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.
#ifndef CHROME_BROWSER_DOM_DISTILLER_TAB_UTILS_H_
#define CHROME_BROWSER_DOM_DISTILLER_TAB_UTILS_H_
namespace content {
class WebContents;
} // namespace content
// Creates a new WebContents and navigates it to view the URL of the current
// page, while in the background starts distilling the current page. This method
// takes ownership over the old WebContents after swapping in the new one.
void DistillCurrentPageAndView(content::WebContents* old_web_contents);
#endif // CHROME_BROWSER_DOM_DISTILLER_TAB_UTILS_H_
// 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.
#include "chrome/browser/dom_distiller/tab_utils_android.h"
#include <string>
#include "chrome/browser/dom_distiller/tab_utils.h"
#include "content/public/browser/web_contents.h"
#include "jni/DomDistillerTabUtils_jni.h"
namespace android {
void DistillCurrentPageAndView(JNIEnv* env,
jclass clazz,
jobject j_web_contents) {
content::WebContents* web_contents =
content::WebContents::FromJavaWebContents(j_web_contents);
::DistillCurrentPageAndView(web_contents);
}
} // namespace android
bool RegisterDomDistillerTabUtils(JNIEnv* env) {
return android::RegisterNativesImpl(env);
}
// 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.
#ifndef CHROME_BROWSER_DOM_DISTILLER_TAB_UTILS_ANDROID_H_
#define CHROME_BROWSER_DOM_DISTILLER_TAB_UTILS_ANDROID_H_
#include <jni.h>
// Register JNI methods.
bool RegisterDomDistillerTabUtils(JNIEnv* env);
#endif // CHROME_BROWSER_DOM_DISTILLER_TAB_UTILS_ANDROID_H_
// 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.
#include <string.h>
#include "base/command_line.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/dom_distiller/dom_distiller_service_factory.h"
#include "chrome/browser/dom_distiller/tab_utils.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/dom_distiller/content/web_contents_main_frame_observer.h"
#include "components/dom_distiller/core/dom_distiller_service.h"
#include "components/dom_distiller/core/task_tracker.h"
#include "components/dom_distiller/core/url_utils.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace dom_distiller {
namespace {
const char* kSimpleArticlePath = "/dom_distiller/simple_article.html";
} // namespace
class DomDistillerTabUtilsBrowserTest : public InProcessBrowserTest {
public:
virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
command_line->AppendSwitch(switches::kEnableDomDistiller);
}
};
class WebContentsMainFrameHelper : public content::WebContentsObserver {
public:
WebContentsMainFrameHelper(content::WebContents* web_contents,
const base::Closure& callback)
: callback_(callback) {
content::WebContentsObserver::Observe(web_contents);
}
virtual void DidFinishLoad(
int64 frame_id,
const GURL& validated_url,
bool is_main_frame,
content::RenderViewHost* render_view_host) OVERRIDE {
if (is_main_frame)
callback_.Run();
}
private:
base::Closure callback_;
};
IN_PROC_BROWSER_TEST_F(DomDistillerTabUtilsBrowserTest, TestSwapWebContents) {
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
content::WebContents* initial_web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
const GURL& article_url = embedded_test_server()->GetURL(kSimpleArticlePath);
// This blocks until the navigation has completely finished.
ui_test_utils::NavigateToURL(browser(), article_url);
DistillCurrentPageAndView(initial_web_contents);
// Wait until the new WebContents has fully navigated.
content::WebContents* after_web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(after_web_contents != NULL);
base::RunLoop new_url_loaded_runner;
scoped_ptr<WebContentsMainFrameHelper> distilled_page_loaded(
new WebContentsMainFrameHelper(after_web_contents,
new_url_loaded_runner.QuitClosure()));
new_url_loaded_runner.Run();
// Verify the new URL is showing distilled content in a new WebContents.
EXPECT_NE(initial_web_contents, after_web_contents);
EXPECT_TRUE(after_web_contents->GetLastCommittedURL().SchemeIs(
chrome::kDomDistillerScheme));
EXPECT_EQ("Test Page Title",
base::UTF16ToUTF8(after_web_contents->GetTitle()));
}
} // namespace dom_distiller
......@@ -29,6 +29,7 @@
#include "chrome/common/chrome_switches.h"
#include "components/autofill/content/browser/content_autofill_driver.h"
#include "components/autofill/core/browser/autofill_manager.h"
#include "components/dom_distiller/content/web_contents_main_frame_observer.h"
#include "components/password_manager/core/browser/password_manager.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/view_type_utils.h"
......@@ -189,6 +190,12 @@ void TabHelpers::AttachTabHelpers(WebContents* web_contents) {
#endif // defined(ENABLE_FULL_PRINTING)
#endif // defined(ENABLE_PRINTING) && !defined(OS_ANDROID)
if (CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableDomDistiller)) {
dom_distiller::WebContentsMainFrameObserver::CreateForWebContents(
web_contents);
}
#if defined(ENABLE_ONE_CLICK_SIGNIN)
// If this is not an incognito window, setup to handle one-click login.
// We don't want to check that the profile is already connected at this time
......
......@@ -540,6 +540,10 @@
'browser/dom_distiller/dom_distiller_service_factory.h',
'browser/dom_distiller/lazy_dom_distiller_service.cc',
'browser/dom_distiller/lazy_dom_distiller_service.h',
'browser/dom_distiller/tab_utils.cc',
'browser/dom_distiller/tab_utils.h',
'browser/dom_distiller/tab_utils_android.cc',
'browser/dom_distiller/tab_utils_android.h',
'browser/download/all_download_item_notifier.cc',
'browser/download/all_download_item_notifier.h',
'browser/download/chrome_download_manager_delegate.cc',
......@@ -3610,6 +3614,7 @@
'android/java/src/org/chromium/chrome/browser/DevToolsServer.java',
'android/java/src/org/chromium/chrome/browser/database/SQLiteCursor.java',
'android/java/src/org/chromium/chrome/browser/dom_distiller/DomDistillerFeedbackReporter.java',
'android/java/src/org/chromium/chrome/browser/dom_distiller/DomDistillerTabUtils.java',
'android/java/src/org/chromium/chrome/browser/favicon/FaviconHelper.java',
'android/java/src/org/chromium/chrome/browser/FieldTrialHelper.java',
'android/java/src/org/chromium/chrome/browser/ForeignSessionHelper.java',
......
......@@ -1024,6 +1024,7 @@
'browser/devtools/device/port_forwarding_browsertest.cc',
'browser/devtools/devtools_sanity_browsertest.cc',
'browser/dom_distiller/dom_distiller_viewer_source_browsertest.cc',
'browser/dom_distiller/tab_utils_browsertest.cc',
'browser/do_not_track_browsertest.cc',
'browser/download/download_browsertest.cc',
'browser/download/download_browsertest.h',
......
<html>
<head><title>Test Page Title</title></head>
<body>
<div>
<p>Lorem ipsum dolor sit amet, at alia aliquip vel. Quas inani labore an vel. Sed an nemore minimum accusata. Sint inermis tacimates est ex, ad movet iracundia mei, delicata iracundia laboramus ei eos. Illud principes complectitur te nec, ius alienum insolens ea, cu quo oratio omnesque.
<p>Lorem ipsum dolor sit amet, at alia aliquip vel. Quas inani labore an vel. Sed an nemore minimum accusata. Sint inermis tacimates est ex, ad movet iracundia mei, delicata iracundia laboramus ei eos. Illud principes complectitur te nec, ius alienum insolens ea, cu quo oratio omnesque.
<p>Lorem ipsum dolor sit amet, at alia aliquip vel. Quas inani labore an vel. Sed an nemore minimum accusata. Sint inermis tacimates est ex, ad movet iracundia mei, delicata iracundia laboramus ei eos. Illud principes complectitur te nec, ius alienum insolens ea, cu quo oratio omnesque.
</div>
</body>
</html>
......@@ -156,6 +156,8 @@
'dom_distiller/content/distiller_page_web_contents.h',
'dom_distiller/content/dom_distiller_viewer_source.cc',
'dom_distiller/content/dom_distiller_viewer_source.h',
'dom_distiller/content/web_contents_main_frame_observer.cc',
'dom_distiller/content/web_contents_main_frame_observer.h',
],
},
],
......
......@@ -7,7 +7,9 @@
#include "base/callback.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "components/dom_distiller/content/web_contents_main_frame_observer.h"
#include "components/dom_distiller/core/distiller_page.h"
#include "components/dom_distiller/core/dom_distiller_service.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/render_frame_host.h"
......@@ -18,18 +20,48 @@
namespace dom_distiller {
SourcePageHandleWebContents::SourcePageHandleWebContents(
scoped_ptr<content::WebContents> web_contents)
: web_contents_(web_contents.Pass()) {
DCHECK(web_contents_);
}
SourcePageHandleWebContents::~SourcePageHandleWebContents() {
}
scoped_ptr<content::WebContents> SourcePageHandleWebContents::GetWebContents() {
return web_contents_.Pass();
}
scoped_ptr<DistillerPage> DistillerPageWebContentsFactory::CreateDistillerPage()
const {
DCHECK(browser_context_);
return scoped_ptr<DistillerPage>(
new DistillerPageWebContents(browser_context_));
return scoped_ptr<DistillerPage>(new DistillerPageWebContents(
browser_context_, scoped_ptr<SourcePageHandleWebContents>()));
}
scoped_ptr<DistillerPage>
DistillerPageWebContentsFactory::CreateDistillerPageWithHandle(
scoped_ptr<SourcePageHandle> handle) const {
DCHECK(browser_context_);
scoped_ptr<SourcePageHandleWebContents> web_contents_handle =
scoped_ptr<SourcePageHandleWebContents>(
static_cast<SourcePageHandleWebContents*>(handle.release()));
return scoped_ptr<DistillerPage>(new DistillerPageWebContents(
browser_context_, web_contents_handle.Pass()));
}
DistillerPageWebContents::DistillerPageWebContents(
content::BrowserContext* browser_context)
: state_(IDLE), browser_context_(browser_context) {}
content::BrowserContext* browser_context,
scoped_ptr<SourcePageHandleWebContents> optional_web_contents_handle)
: state_(IDLE), browser_context_(browser_context) {
if (optional_web_contents_handle) {
web_contents_ = optional_web_contents_handle->GetWebContents().Pass();
}
}
DistillerPageWebContents::~DistillerPageWebContents() {}
DistillerPageWebContents::~DistillerPageWebContents() {
}
void DistillerPageWebContents::DistillPageImpl(const GURL& url,
const std::string& script) {
......@@ -38,6 +70,31 @@ void DistillerPageWebContents::DistillPageImpl(const GURL& url,
state_ = LOADING_PAGE;
script_ = script;
if (web_contents_ && web_contents_->GetLastCommittedURL() == url) {
WebContentsMainFrameObserver* main_frame_observer =
WebContentsMainFrameObserver::FromWebContents(web_contents_.get());
if (main_frame_observer && main_frame_observer->is_initialized()) {
if (main_frame_observer->is_document_loaded_in_main_frame()) {
// Main frame has already loaded for the current WebContents, so execute
// JavaScript immediately.
ExecuteJavaScript();
} else {
// Main frame document has not loaded yet, so wait until it has before
// executing JavaScript. It will trigger after DocumentLoadedInFrame is
// called for the main frame.
content::WebContentsObserver::Observe(web_contents_.get());
}
} else {
// The WebContentsMainFrameObserver has not been correctly initialized,
// so fall back to creating a new WebContents.
CreateNewWebContents(url);
}
} else {
CreateNewWebContents(url);
}
}
void DistillerPageWebContents::CreateNewWebContents(const GURL& url) {
// Create new WebContents to use for distilling the content.
content::WebContents::CreateParams create_params(browser_context_);
create_params.initially_hidden = true;
......@@ -54,8 +111,6 @@ void DistillerPageWebContents::DocumentLoadedInFrame(
int64 frame_id,
RenderViewHost* render_view_host) {
if (frame_id == web_contents_->GetMainFrame()->GetRoutingID()) {
content::WebContentsObserver::Observe(NULL);
web_contents_->Stop();
ExecuteJavaScript();
}
}
......@@ -81,6 +136,8 @@ void DistillerPageWebContents::ExecuteJavaScript() {
DCHECK(frame);
DCHECK_EQ(LOADING_PAGE, state_);
state_ = EXECUTING_JAVASCRIPT;
content::WebContentsObserver::Observe(NULL);
web_contents_->Stop();
DVLOG(1) << "Beginning distillation";
frame->ExecuteJavaScript(
base::UTF8ToUTF16(script_),
......
......@@ -21,7 +21,18 @@ using content::RenderViewHost;
namespace dom_distiller {
class DistillerContext;
class SourcePageHandleWebContents : public SourcePageHandle {
public:
explicit SourcePageHandleWebContents(
scoped_ptr<content::WebContents> web_contents);
virtual ~SourcePageHandleWebContents();
scoped_ptr<content::WebContents> GetWebContents();
private:
// The WebContents this class owns.
scoped_ptr<content::WebContents> web_contents_;
};
class DistillerPageWebContentsFactory : public DistillerPageFactory {
public:
......@@ -31,6 +42,8 @@ class DistillerPageWebContentsFactory : public DistillerPageFactory {
virtual ~DistillerPageWebContentsFactory() {}
virtual scoped_ptr<DistillerPage> CreateDistillerPage() const OVERRIDE;
virtual scoped_ptr<DistillerPage> CreateDistillerPageWithHandle(
scoped_ptr<SourcePageHandle> handle) const OVERRIDE;
private:
content::BrowserContext* browser_context_;
......@@ -39,7 +52,9 @@ class DistillerPageWebContentsFactory : public DistillerPageFactory {
class DistillerPageWebContents : public DistillerPage,
public content::WebContentsObserver {
public:
DistillerPageWebContents(content::BrowserContext* browser_context);
DistillerPageWebContents(
content::BrowserContext* browser_context,
scoped_ptr<SourcePageHandleWebContents> optional_web_contents_handle);
virtual ~DistillerPageWebContents();
// content::WebContentsObserver implementation.
......@@ -58,6 +73,8 @@ class DistillerPageWebContents : public DistillerPage,
const std::string& script) OVERRIDE;
private:
friend class TestDistillerPageWebContents;
enum State {
// The page distiller is idle.
IDLE,
......@@ -70,6 +87,10 @@ class DistillerPageWebContents : public DistillerPage,
EXECUTING_JAVASCRIPT
};
// Creates a new WebContents, adds |this| as an observer, and loads the
// |url|.
virtual void CreateNewWebContents(const GURL& url);
// Injects and executes JavaScript in the context of a loaded page. This
// must only be called after the page has successfully loaded.
void ExecuteJavaScript();
......
......@@ -7,8 +7,12 @@
#include "base/run_loop.h"
#include "base/values.h"
#include "components/dom_distiller/content/distiller_page_web_contents.h"
#include "components/dom_distiller/content/web_contents_main_frame_observer.h"
#include "components/dom_distiller/core/distiller_page.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/test/content_browser_test.h"
#include "content/shell/browser/shell.h"
#include "grit/component_resources.h"
......@@ -23,6 +27,8 @@ using testing::Not;
namespace dom_distiller {
const char* kSimpleArticlePath = "/simple_article.html";
class DistillerPageWebContentsTest : public ContentBrowserTest {
public:
// ContentBrowserTest:
......@@ -66,18 +72,108 @@ class DistillerPageWebContentsTest : public ContentBrowserTest {
}
protected:
void RunUseCurrentWebContentsTest(const std::string& url,
bool expect_new_web_contents,
bool setup_main_frame_observer,
bool wait_for_document_loaded);
DistillerPageWebContents* distiller_page_;
base::Closure quit_closure_;
scoped_ptr<DistilledPageInfo> page_info_;
};
// Use this class to be able to leak the WebContents, which is needed for when
// the current WebContents is used for distillation.
class TestDistillerPageWebContents : public DistillerPageWebContents {
public:
TestDistillerPageWebContents(
content::BrowserContext* browser_context,
scoped_ptr<SourcePageHandleWebContents> optional_web_contents_handle,
bool expect_new_web_contents)
: DistillerPageWebContents(browser_context,
optional_web_contents_handle.Pass()),
expect_new_web_contents_(expect_new_web_contents),
new_web_contents_created_(false) {}
virtual void CreateNewWebContents(const GURL& url) OVERRIDE {
ASSERT_EQ(true, expect_new_web_contents_);
new_web_contents_created_ = true;
// DistillerPageWebContents::CreateNewWebContents resets the scoped_ptr to
// the WebContents, so intentionally leak WebContents here, since it is
// owned by the shell.
content::WebContents* web_contents = web_contents_.release();
web_contents->GetLastCommittedURL();
DistillerPageWebContents::CreateNewWebContents(url);
}
virtual ~TestDistillerPageWebContents() {
if (!expect_new_web_contents_) {
// Intentionally leaking WebContents, since it is owned by the shell.
content::WebContents* web_contents = web_contents_.release();
web_contents->GetLastCommittedURL();
}
}
bool new_web_contents_created() { return new_web_contents_created_; }
private:
bool expect_new_web_contents_;
bool new_web_contents_created_;
};
// Helper class to know how far in the loading process the current WebContents
// has come. It will call the callback either after
// DidCommitProvisionalLoadForFrame or DocumentLoadedInFrame is called for the
// main frame, based on the value of |wait_for_document_loaded|.
class WebContentsMainFrameHelper : public content::WebContentsObserver {
public:
WebContentsMainFrameHelper(content::WebContents* web_contents,
const base::Closure& callback,
bool wait_for_document_loaded)
: web_contents_(web_contents),
callback_(callback),
wait_for_document_loaded_(wait_for_document_loaded) {
content::WebContentsObserver::Observe(web_contents);
}
virtual void DidCommitProvisionalLoadForFrame(
int64 frame_id,
const base::string16& frame_unique_name,
bool is_main_frame,
const GURL& url,
content::PageTransition transition_type,
content::RenderViewHost* render_view_host) OVERRIDE {
if (wait_for_document_loaded_)
return;
if (is_main_frame)
callback_.Run();
}
virtual void DocumentLoadedInFrame(
int64 frame_id,
content::RenderViewHost* render_view_host) OVERRIDE {
if (wait_for_document_loaded_) {
if (web_contents_ &&
frame_id == web_contents_->GetMainFrame()->GetRoutingID()) {
callback_.Run();
}
}
}
private:
content::WebContents* web_contents_;
base::Closure callback_;
bool wait_for_document_loaded_;
};
IN_PROC_BROWSER_TEST_F(DistillerPageWebContentsTest, BasicDistillationWorks) {
DistillerPageWebContents distiller_page(
shell()->web_contents()->GetBrowserContext());
shell()->web_contents()->GetBrowserContext(),
scoped_ptr<SourcePageHandleWebContents>());
distiller_page_ = &distiller_page;
base::RunLoop run_loop;
DistillPage(run_loop.QuitClosure(), "/simple_article.html");
DistillPage(run_loop.QuitClosure(), kSimpleArticlePath);
run_loop.Run();
EXPECT_EQ("Test Page Title", page_info_.get()->title);
......@@ -89,11 +185,12 @@ IN_PROC_BROWSER_TEST_F(DistillerPageWebContentsTest, BasicDistillationWorks) {
IN_PROC_BROWSER_TEST_F(DistillerPageWebContentsTest, HandlesRelativeLinks) {
DistillerPageWebContents distiller_page(
shell()->web_contents()->GetBrowserContext());
shell()->web_contents()->GetBrowserContext(),
scoped_ptr<SourcePageHandleWebContents>());
distiller_page_ = &distiller_page;
base::RunLoop run_loop;
DistillPage(run_loop.QuitClosure(), "/simple_article.html");
DistillPage(run_loop.QuitClosure(), kSimpleArticlePath);
run_loop.Run();
// A relative link should've been updated.
......@@ -105,11 +202,12 @@ IN_PROC_BROWSER_TEST_F(DistillerPageWebContentsTest, HandlesRelativeLinks) {
IN_PROC_BROWSER_TEST_F(DistillerPageWebContentsTest, HandlesRelativeImages) {
DistillerPageWebContents distiller_page(
shell()->web_contents()->GetBrowserContext());
shell()->web_contents()->GetBrowserContext(),
scoped_ptr<SourcePageHandleWebContents>());
distiller_page_ = &distiller_page;
base::RunLoop run_loop;
DistillPage(run_loop.QuitClosure(), "/simple_article.html");
DistillPage(run_loop.QuitClosure(), kSimpleArticlePath);
run_loop.Run();
// A relative link should've been updated.
......@@ -121,7 +219,8 @@ IN_PROC_BROWSER_TEST_F(DistillerPageWebContentsTest, HandlesRelativeImages) {
IN_PROC_BROWSER_TEST_F(DistillerPageWebContentsTest, VisibilityDetection) {
DistillerPageWebContents distiller_page(
shell()->web_contents()->GetBrowserContext());
shell()->web_contents()->GetBrowserContext(),
scoped_ptr<SourcePageHandleWebContents>());
distiller_page_ = &distiller_page;
// visble_style.html and invisible_style.html only differ by the visibility
......@@ -142,4 +241,92 @@ IN_PROC_BROWSER_TEST_F(DistillerPageWebContentsTest, VisibilityDetection) {
}
}
IN_PROC_BROWSER_TEST_F(DistillerPageWebContentsTest,
UsingCurrentWebContentsWrongUrl) {
std::string url("/bogus");
bool expect_new_web_contents = true;
bool setup_main_frame_observer = true;
bool wait_for_document_loaded = true;
RunUseCurrentWebContentsTest(url,
expect_new_web_contents,
setup_main_frame_observer,
wait_for_document_loaded);
}
IN_PROC_BROWSER_TEST_F(DistillerPageWebContentsTest,
UsingCurrentWebContentsNoMainFrameObserver) {
std::string url(kSimpleArticlePath);
bool expect_new_web_contents = true;
bool setup_main_frame_observer = false;
bool wait_for_document_loaded = true;
RunUseCurrentWebContentsTest(url,
expect_new_web_contents,
setup_main_frame_observer,
wait_for_document_loaded);
}
IN_PROC_BROWSER_TEST_F(DistillerPageWebContentsTest,
UsingCurrentWebContentsNotFinishedLoadingYet) {
std::string url(kSimpleArticlePath);
bool expect_new_web_contents = false;
bool setup_main_frame_observer = true;
bool wait_for_document_loaded = false;
RunUseCurrentWebContentsTest(url,
expect_new_web_contents,
setup_main_frame_observer,
wait_for_document_loaded);
}
IN_PROC_BROWSER_TEST_F(DistillerPageWebContentsTest,
UsingCurrentWebContentsReadyForDistillation) {
std::string url(kSimpleArticlePath);
bool expect_new_web_contents = false;
bool setup_main_frame_observer = true;
bool wait_for_document_loaded = true;
RunUseCurrentWebContentsTest(url,
expect_new_web_contents,
setup_main_frame_observer,
wait_for_document_loaded);
}
void DistillerPageWebContentsTest::RunUseCurrentWebContentsTest(
const std::string& url,
bool expect_new_web_contents,
bool setup_main_frame_observer,
bool wait_for_document_loaded) {
content::WebContents* current_web_contents = shell()->web_contents();
if (setup_main_frame_observer) {
dom_distiller::WebContentsMainFrameObserver::CreateForWebContents(
current_web_contents);
}
base::RunLoop url_loaded_runner;
WebContentsMainFrameHelper main_frame_loaded(current_web_contents,
url_loaded_runner.QuitClosure(),
wait_for_document_loaded);
current_web_contents->GetController().LoadURL(
embedded_test_server()->GetURL(url),
content::Referrer(),
content::PAGE_TRANSITION_TYPED,
std::string());
url_loaded_runner.Run();
scoped_ptr<content::WebContents> old_web_contents_sptr(current_web_contents);
scoped_ptr<SourcePageHandleWebContents> source_page_handle(
new SourcePageHandleWebContents(old_web_contents_sptr.Pass()));
TestDistillerPageWebContents distiller_page(
shell()->web_contents()->GetBrowserContext(),
source_page_handle.Pass(),
expect_new_web_contents);
distiller_page_ = &distiller_page;
base::RunLoop run_loop;
DistillPage(run_loop.QuitClosure(), kSimpleArticlePath);
run_loop.Run();
// Sanity check of distillation process.
EXPECT_EQ(expect_new_web_contents, distiller_page.new_web_contents_created());
EXPECT_EQ("Test Page Title", page_info_.get()->title);
}
} // namespace dom_distiller
// 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.
#include "components/dom_distiller/content/web_contents_main_frame_observer.h"
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
DEFINE_WEB_CONTENTS_USER_DATA_KEY(dom_distiller::WebContentsMainFrameObserver);
namespace dom_distiller {
WebContentsMainFrameObserver::WebContentsMainFrameObserver(
content::WebContents* web_contents)
: is_document_loaded_in_main_frame_(false),
is_initialized_(false),
web_contents_(web_contents) {
content::WebContentsObserver::Observe(web_contents);
}
WebContentsMainFrameObserver::~WebContentsMainFrameObserver() {
CleanUp();
}
void WebContentsMainFrameObserver::DocumentLoadedInFrame(
int64 frame_id,
content::RenderViewHost* render_view_host) {
if (web_contents_ &&
frame_id == web_contents_->GetMainFrame()->GetRoutingID()) {
is_document_loaded_in_main_frame_ = true;
}
}
void WebContentsMainFrameObserver::DidNavigateMainFrame(
const content::LoadCommittedDetails& details,
const content::FrameNavigateParams& params) {
if (details.is_main_frame) {
is_document_loaded_in_main_frame_ = false;
is_initialized_ = true;
}
}
void WebContentsMainFrameObserver::RenderProcessGone(
base::TerminationStatus status) {
CleanUp();
}
void WebContentsMainFrameObserver::WebContentsDestroyed() {
CleanUp();
}
void WebContentsMainFrameObserver::CleanUp() {
content::WebContentsObserver::Observe(NULL);
web_contents_ = NULL;
}
} // namespace dom_distiller
// 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.
#ifndef COMPONENTS_DOM_DISTILLER_CONTENT_WEB_CONTENTS_MAIN_FRAME_OBSERVER_H_
#define COMPONENTS_DOM_DISTILLER_CONTENT_WEB_CONTENTS_MAIN_FRAME_OBSERVER_H_
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
namespace dom_distiller {
// Tracks whether DocumentLoadedInFrame has been called for the main frame for
// the current main frame for the given WebContents. It removes itself as an
// observer if the WebContents is destroyed or the render process is gone.
class WebContentsMainFrameObserver
: public content::WebContentsObserver,
public content::WebContentsUserData<WebContentsMainFrameObserver> {
public:
virtual ~WebContentsMainFrameObserver();
bool is_document_loaded_in_main_frame() {
return is_document_loaded_in_main_frame_;
}
bool is_initialized() { return is_initialized_; }
// content::WebContentsObserver implementation.
virtual void DocumentLoadedInFrame(
int64 frame_id,
content::RenderViewHost* render_view_host) OVERRIDE;
virtual void DidNavigateMainFrame(
const content::LoadCommittedDetails& details,
const content::FrameNavigateParams& params) OVERRIDE;
virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE;
virtual void WebContentsDestroyed() OVERRIDE;
private:
explicit WebContentsMainFrameObserver(content::WebContents* web_contents);
friend class content::WebContentsUserData<WebContentsMainFrameObserver>;
// Removes the observer and clears the WebContents member.
void CleanUp();
// Whether DocumentLoadedInFrame has been called for the tracked WebContents
// for the current main frame. This is cleared when the main frame navigates,
// and set again when DocumentLoadedInFrame is called for the main frame.
bool is_document_loaded_in_main_frame_;
// Whether this object has been correctly initialized. This is set as soon as
// at least one call to DidNavigateMainFrame has happened.
bool is_initialized_;
// The WebContents this class is tracking.
content::WebContents* web_contents_;
DISALLOW_COPY_AND_ASSIGN(WebContentsMainFrameObserver);
};
} // namespace dom_distiller
#endif // COMPONENTS_DOM_DISTILLER_CONTENT_WEB_CONTENTS_MAIN_FRAME_OBSERVER_H_
......@@ -29,6 +29,11 @@ struct DistilledPageInfo {
DISALLOW_COPY_AND_ASSIGN(DistilledPageInfo);
};
class SourcePageHandle {
public:
virtual ~SourcePageHandle() {}
};
// Injects JavaScript into a page, and uses it to extract and return long-form
// content. The class can be reused to load and distill multiple pages,
// following the state transitions described along with the class's states.
......@@ -62,10 +67,6 @@ class DistillerPage {
// should be the same regardless of the DistillerPage implementation.
virtual void DistillPageImpl(const GURL& url, const std::string& script) = 0;
// Called by |ExecuteJavaScript| to carry out platform-specific instructions
// to inject and execute JavaScript within the context of the loaded page.
//virtual void ExecuteJavaScriptImpl() = 0;
private:
bool ready_;
DistillerPageCallback distiller_page_callback_;
......@@ -81,6 +82,8 @@ class DistillerPageFactory {
// should be very cheap, since the pages can be thrown away without being
// used.
virtual scoped_ptr<DistillerPage> CreateDistillerPage() const = 0;
virtual scoped_ptr<DistillerPage> CreateDistillerPageWithHandle(
scoped_ptr<SourcePageHandle> handle) const = 0;
};
} // namespace dom_distiller
......
......@@ -46,7 +46,8 @@ DomDistillerService::DomDistillerService(
distiller_page_factory_(distiller_page_factory.Pass()) {
}
DomDistillerService::~DomDistillerService() {}
DomDistillerService::~DomDistillerService() {
}
syncer::SyncableService* DomDistillerService::GetSyncableService() const {
return store_->GetSyncableService();
......@@ -56,6 +57,13 @@ scoped_ptr<DistillerPage> DomDistillerService::CreateDefaultDistillerPage() {
return distiller_page_factory_->CreateDistillerPage().Pass();
}
scoped_ptr<DistillerPage>
DomDistillerService::CreateDefaultDistillerPageWithHandle(
scoped_ptr<SourcePageHandle> handle) {
return distiller_page_factory_->CreateDistillerPageWithHandle(handle.Pass())
.Pass();
}
const std::string DomDistillerService::AddToList(
const GURL& url,
scoped_ptr<DistillerPage> distiller_page,
......
......@@ -84,6 +84,8 @@ class DomDistillerServiceInterface {
// Creates a default DistillerPage.
virtual scoped_ptr<DistillerPage> CreateDefaultDistillerPage() = 0;
virtual scoped_ptr<DistillerPage> CreateDefaultDistillerPageWithHandle(
scoped_ptr<SourcePageHandle> handle) = 0;
virtual void AddObserver(DomDistillerObserver* observer) = 0;
virtual void RemoveObserver(DomDistillerObserver* observer) = 0;
......@@ -121,6 +123,8 @@ class DomDistillerService : public DomDistillerServiceInterface {
scoped_ptr<DistillerPage> distiller_page,
const GURL& url) OVERRIDE;
virtual scoped_ptr<DistillerPage> CreateDefaultDistillerPage() OVERRIDE;
virtual scoped_ptr<DistillerPage> CreateDefaultDistillerPageWithHandle(
scoped_ptr<SourcePageHandle> handle) OVERRIDE;
virtual void AddObserver(DomDistillerObserver* observer) OVERRIDE;
virtual void RemoveObserver(DomDistillerObserver* observer) OVERRIDE;
......
......@@ -19,6 +19,10 @@ class MockDistillerPageFactory : public DistillerPageFactory {
virtual scoped_ptr<DistillerPage> CreateDistillerPage() const OVERRIDE {
return scoped_ptr<DistillerPage>(CreateDistillerPageImpl());
}
virtual scoped_ptr<DistillerPage> CreateDistillerPageWithHandle(
scoped_ptr<SourcePageHandle> handle) const OVERRIDE {
return scoped_ptr<DistillerPage>(CreateDistillerPageImpl());
}
};
class MockDistillerPage : public DistillerPage {
......
......@@ -62,6 +62,10 @@ class TestDomDistillerService : public DomDistillerServiceInterface {
virtual scoped_ptr<DistillerPage> CreateDefaultDistillerPage() {
return scoped_ptr<DistillerPage>();
}
virtual scoped_ptr<DistillerPage> CreateDefaultDistillerPageWithHandle(
scoped_ptr<SourcePageHandle> handle) {
return scoped_ptr<DistillerPage>();
}
};
class DomDistillerViewerTest : public testing::Test {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment