summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/android/offline_pages/offline_page_tab_helper.cc117
-rw-r--r--chrome/browser/android/offline_pages/offline_page_tab_helper.h54
-rw-r--r--chrome/browser/android/offline_pages/offline_page_tab_helper_unittest.cc195
-rw-r--r--chrome/browser/ui/tab_helpers.cc2
-rw-r--r--chrome/chrome_browser.gypi2
-rw-r--r--chrome/chrome_tests_unit.gypi1
-rw-r--r--tools/metrics/histograms/histograms.xml18
7 files changed, 389 insertions, 0 deletions
diff --git a/chrome/browser/android/offline_pages/offline_page_tab_helper.cc b/chrome/browser/android/offline_pages/offline_page_tab_helper.cc
new file mode 100644
index 0000000..c0e6e3b
--- /dev/null
+++ b/chrome/browser/android/offline_pages/offline_page_tab_helper.cc
@@ -0,0 +1,117 @@
+// Copyright 2016 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/android/offline_pages/offline_page_tab_helper.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/thread_task_runner_handle.h"
+#include "chrome/browser/android/offline_pages/offline_page_utils.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/navigation_controller.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents.h"
+#include "net/base/net_errors.h"
+#include "net/base/network_change_notifier.h"
+
+DEFINE_WEB_CONTENTS_USER_DATA_KEY(offline_pages::OfflinePageTabHelper);
+
+namespace offline_pages {
+
+OfflinePageTabHelper::OfflinePageTabHelper(content::WebContents* web_contents)
+ : content::WebContentsObserver(web_contents),
+ weak_ptr_factory_(this) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+}
+
+OfflinePageTabHelper::~OfflinePageTabHelper() {}
+
+void OfflinePageTabHelper::DidStartNavigation(
+ content::NavigationHandle* navigation_handle) {
+ GURL last_redirect_from_url_copy = last_redirect_from_url_;
+ last_redirect_from_url_ = GURL();
+
+ // Skips non-main frame.
+ if (!navigation_handle->IsInMainFrame())
+ return;
+
+ // Redirecting to online version will only take effect when there is network
+ // connection.
+ if (net::NetworkChangeNotifier::IsOffline())
+ return;
+
+ // Skips if not loading an offline copy of saved page.
+ GURL online_url = offline_pages::OfflinePageUtils::GetOnlineURLForOfflineURL(
+ web_contents()->GetBrowserContext(), navigation_handle->GetURL());
+ if (!online_url.is_valid())
+ return;
+
+ // Avoids looping between online and offline redirections.
+ if (last_redirect_from_url_copy == online_url)
+ return;
+ last_redirect_from_url_ = navigation_handle->GetURL();
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&OfflinePageTabHelper::RedirectFromOfflineToOnline,
+ weak_ptr_factory_.GetWeakPtr(),
+ online_url));
+}
+
+void OfflinePageTabHelper::DidFailProvisionalLoad(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& validated_url,
+ int error_code,
+ const base::string16& error_description,
+ bool was_ignored_by_handler) {
+ GURL last_redirect_from_url_copy = last_redirect_from_url_;
+ last_redirect_from_url_ = GURL();
+
+ // Skips non-main frame or load failure other than no network.
+ if (error_code != net::ERR_INTERNET_DISCONNECTED ||
+ render_frame_host->GetParent() != nullptr) {
+ return;
+ }
+
+ // Redirecting to offline version will only take effect when there is no
+ // network connection.
+ if (!net::NetworkChangeNotifier::IsOffline())
+ return;
+
+ // Skips if not loading an online version of saved page.
+ GURL offline_url = offline_pages::OfflinePageUtils::GetOfflineURLForOnlineURL(
+ web_contents()->GetBrowserContext(), validated_url);
+ if (!offline_url.is_valid())
+ return;
+
+ // Avoids looping between online and offline redirections.
+ if (last_redirect_from_url_copy == offline_url)
+ return;
+ last_redirect_from_url_ = validated_url;
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&OfflinePageTabHelper::RedirectFromOnlineToOffline,
+ weak_ptr_factory_.GetWeakPtr(),
+ offline_url));
+}
+
+void OfflinePageTabHelper::RedirectFromOfflineToOnline(const GURL& online_url) {
+ UMA_HISTOGRAM_COUNTS("OfflinePages.RedirectToOnlineCount", 1);
+ content::NavigationController::LoadURLParams load_params(online_url);
+ load_params.transition_type = ui::PAGE_TRANSITION_CLIENT_REDIRECT;
+ web_contents()->GetController().LoadURLWithParams(load_params);
+}
+
+void OfflinePageTabHelper::RedirectFromOnlineToOffline(
+ const GURL& offline_url) {
+ UMA_HISTOGRAM_COUNTS("OfflinePages.RedirectToOfflineCount", 1);
+ content::NavigationController::LoadURLParams load_params(offline_url);
+ load_params.transition_type = ui::PAGE_TRANSITION_CLIENT_REDIRECT;
+ web_contents()->GetController().LoadURLWithParams(load_params);
+}
+
+} // namespace offline_pages
diff --git a/chrome/browser/android/offline_pages/offline_page_tab_helper.h b/chrome/browser/android/offline_pages/offline_page_tab_helper.h
new file mode 100644
index 0000000..2339197
--- /dev/null
+++ b/chrome/browser/android/offline_pages/offline_page_tab_helper.h
@@ -0,0 +1,54 @@
+// Copyright 2016 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_ANDROID_OFFLINE_PAGES_OFFLINE_PAGE_TAB_HELPER_H_
+#define CHROME_BROWSER_ANDROID_OFFLINE_PAGES_OFFLINE_PAGE_TAB_HELPER_H_
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+#include "url/gurl.h"
+
+namespace content {
+class WebContents;
+}
+
+namespace offline_pages {
+
+// Per-tab class to manage switch between online version and offline version.
+class OfflinePageTabHelper :
+ public content::WebContentsObserver,
+ public content::WebContentsUserData<OfflinePageTabHelper> {
+ public:
+ ~OfflinePageTabHelper() override;
+
+ private:
+ friend class content::WebContentsUserData<OfflinePageTabHelper>;
+ friend class OfflinePageTabHelperTest;
+
+ explicit OfflinePageTabHelper(content::WebContents* web_contents);
+
+ // Overridden from content::WebContentsObserver:
+ void DidStartNavigation(
+ content::NavigationHandle* navigation_handle) override;
+ void DidFailProvisionalLoad(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& validated_url,
+ int error_code,
+ const base::string16& error_description,
+ bool was_ignored_by_handler) override;
+
+ void RedirectFromOfflineToOnline(const GURL& online_url);
+ void RedirectFromOnlineToOffline(const GURL& offline_url);
+
+ GURL last_redirect_from_url_;
+ base::WeakPtrFactory<OfflinePageTabHelper> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(OfflinePageTabHelper);
+};
+
+} // namespace offline_pages
+
+#endif // CHROME_BROWSER_ANDROID_OFFLINE_PAGES_OFFLINE_PAGE_TAB_HELPER_H_
diff --git a/chrome/browser/android/offline_pages/offline_page_tab_helper_unittest.cc b/chrome/browser/android/offline_pages/offline_page_tab_helper_unittest.cc
new file mode 100644
index 0000000..3501f0e
--- /dev/null
+++ b/chrome/browser/android/offline_pages/offline_page_tab_helper_unittest.cc
@@ -0,0 +1,195 @@
+// Copyright 2016 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 "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/run_loop.h"
+#include "chrome/browser/android/offline_pages/offline_page_model_factory.h"
+#include "chrome/browser/android/offline_pages/offline_page_tab_helper.h"
+#include "chrome/browser/android/offline_pages/test_offline_page_model_builder.h"
+#include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "components/offline_pages/offline_page_item.h"
+#include "components/offline_pages/offline_page_model.h"
+#include "components/offline_pages/offline_page_switches.h"
+#include "components/offline_pages/offline_page_test_archiver.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/web_contents.h"
+#include "net/base/net_errors.h"
+#include "net/base/network_change_notifier.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace offline_pages {
+
+namespace {
+
+const GURL kTestPageUrl("http://test.org/page1");
+const int64_t kTestPageBookmarkId = 1234;
+const int64_t kTestFileSize = 876543LL;
+
+class TestNetworkChangeNotifier : public net::NetworkChangeNotifier {
+ public:
+ TestNetworkChangeNotifier() : online_(true) {}
+
+ net::NetworkChangeNotifier::ConnectionType GetCurrentConnectionType()
+ const override {
+ return online_ ? net::NetworkChangeNotifier::CONNECTION_UNKNOWN
+ : net::NetworkChangeNotifier::CONNECTION_NONE;
+ }
+
+ void set_online(bool online) { online_ = online; }
+
+ private:
+ bool online_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestNetworkChangeNotifier);
+};
+
+} // namespace
+
+class OfflinePageTabHelperTest :
+ public ChromeRenderViewHostTestHarness,
+ public OfflinePageTestArchiver::Observer,
+ public base::SupportsWeakPtr<OfflinePageTabHelperTest> {
+ public:
+ OfflinePageTabHelperTest()
+ : network_change_notifier_(new TestNetworkChangeNotifier()) {}
+ ~OfflinePageTabHelperTest() override {}
+
+ void SetUp() override;
+ void TearDown() override;
+
+ void RunUntilIdle();
+ void SimulateHasNetworkConnectivity(bool has_connectivity);
+ void StartLoad(const GURL& url);
+ void CommitLoad(const GURL& url);
+ void FailLoad(const GURL& url);
+
+ OfflinePageTabHelper* offline_page_tab_helper() const {
+ return offline_page_tab_helper_;
+ }
+
+ private:
+ // OfflinePageTestArchiver::Observer implementation:
+ void SetLastPathCreatedByArchiver(const base::FilePath& file_path) override;
+
+ scoped_ptr<OfflinePageTestArchiver> BuildArchiver(
+ const GURL& url,
+ const base::FilePath& file_name);
+ void OnSavePageDone(OfflinePageModel::SavePageResult result);
+
+ scoped_ptr<TestNetworkChangeNotifier> network_change_notifier_;
+ OfflinePageTabHelper* offline_page_tab_helper_; // Not owned.
+
+ DISALLOW_COPY_AND_ASSIGN(OfflinePageTabHelperTest);
+};
+
+void OfflinePageTabHelperTest::SetUp() {
+ // Creates a test web contents.
+ content::RenderViewHostTestHarness::SetUp();
+ OfflinePageTabHelper::CreateForWebContents(web_contents());
+ offline_page_tab_helper_ =
+ OfflinePageTabHelper::FromWebContents(web_contents());
+
+ // Enables offline pages feature.
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableOfflinePages);
+
+ // Sets up the factory for testing.
+ OfflinePageModelFactory::GetInstance()->SetTestingFactoryAndUse(
+ browser_context(), BuildTestOfflinePageModel);
+ RunUntilIdle();
+
+ // Saves an offline page.
+ OfflinePageModel* model =
+ OfflinePageModelFactory::GetForBrowserContext(browser_context());
+ scoped_ptr<OfflinePageTestArchiver> archiver(BuildArchiver(
+ kTestPageUrl, base::FilePath(FILE_PATH_LITERAL("page1.mhtml"))));
+ model->SavePage(
+ kTestPageUrl, kTestPageBookmarkId, std::move(archiver),
+ base::Bind(&OfflinePageTabHelperTest::OnSavePageDone, AsWeakPtr()));
+ RunUntilIdle();
+}
+
+void OfflinePageTabHelperTest::TearDown() {
+ content::RenderViewHostTestHarness::TearDown();
+}
+
+void OfflinePageTabHelperTest::RunUntilIdle() {
+ base::RunLoop().RunUntilIdle();
+}
+
+void OfflinePageTabHelperTest::SimulateHasNetworkConnectivity(bool online) {
+ network_change_notifier_->set_online(online);
+}
+
+void OfflinePageTabHelperTest::StartLoad(const GURL& url) {
+ controller().LoadURL(url, content::Referrer(), ui::PAGE_TRANSITION_TYPED,
+ std::string());
+}
+
+void OfflinePageTabHelperTest::CommitLoad(const GURL& url) {
+ int entry_id = controller().GetPendingEntry()->GetUniqueID();
+ content::RenderFrameHostTester::For(main_rfh())->SendNavigate(
+ 0, entry_id, true, url);
+}
+
+void OfflinePageTabHelperTest::FailLoad(const GURL& url) {
+ content::RenderFrameHostTester::For(main_rfh())->SimulateNavigationError(
+ url, net::ERR_INTERNET_DISCONNECTED);
+ // Gives a chance to run delayed task to do redirection.
+ RunUntilIdle();
+}
+
+void OfflinePageTabHelperTest::SetLastPathCreatedByArchiver(
+ const base::FilePath& file_path) {
+}
+
+scoped_ptr<OfflinePageTestArchiver> OfflinePageTabHelperTest::BuildArchiver(
+ const GURL& url,
+ const base::FilePath& file_name) {
+ scoped_ptr<OfflinePageTestArchiver> archiver(new OfflinePageTestArchiver(
+ this, url, OfflinePageArchiver::ArchiverResult::SUCCESSFULLY_CREATED,
+ kTestFileSize, base::ThreadTaskRunnerHandle::Get()));
+ archiver->set_filename(file_name);
+ return archiver;
+}
+
+void OfflinePageTabHelperTest::OnSavePageDone(
+ OfflinePageModel::SavePageResult result) {
+}
+
+TEST_F(OfflinePageTabHelperTest, SwitchToOnlineFromOffline) {
+ SimulateHasNetworkConnectivity(true);
+
+ OfflinePageModel* model =
+ OfflinePageModelFactory::GetForBrowserContext(browser_context());
+ const OfflinePageItem* page = model->GetPageByBookmarkId(kTestPageBookmarkId);
+ GURL offline_url = page->GetOfflineURL();
+ GURL online_url = page->url;
+
+ StartLoad(offline_url);
+ CommitLoad(online_url);
+ EXPECT_EQ(online_url, controller().GetLastCommittedEntry()->GetURL());
+}
+
+TEST_F(OfflinePageTabHelperTest, SwitchToOfflineFromOnline) {
+ SimulateHasNetworkConnectivity(false);
+
+ OfflinePageModel* model =
+ OfflinePageModelFactory::GetForBrowserContext(browser_context());
+ const OfflinePageItem* page = model->GetPageByBookmarkId(kTestPageBookmarkId);
+ GURL offline_url = page->GetOfflineURL();
+ GURL online_url = page->url;
+
+ StartLoad(online_url);
+ EXPECT_EQ(online_url, controller().GetPendingEntry()->GetURL());
+
+ FailLoad(online_url);
+ EXPECT_EQ(offline_url, controller().GetPendingEntry()->GetURL());
+}
+
+} // namespace offline_pages
diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc
index 97a1586..906defa 100644
--- a/chrome/browser/ui/tab_helpers.cc
+++ b/chrome/browser/ui/tab_helpers.cc
@@ -53,6 +53,7 @@
#if BUILDFLAG(ANDROID_JAVA_UI)
#include "chrome/browser/android/data_usage/data_use_tab_helper.h"
+#include "chrome/browser/android/offline_pages/offline_page_tab_helper.h"
#include "chrome/browser/android/voice_search_tab_helper.h"
#include "chrome/browser/android/webapps/single_tab_mode_tab_helper.h"
#include "chrome/browser/ui/android/context_menu_helper.h"
@@ -179,6 +180,7 @@ void TabHelpers::AttachTabHelpers(WebContents* web_contents) {
#if BUILDFLAG(ANDROID_JAVA_UI)
ContextMenuHelper::CreateForWebContents(web_contents);
DataUseTabHelper::CreateForWebContents(web_contents);
+ offline_pages::OfflinePageTabHelper::CreateForWebContents(web_contents);
SingleTabModeTabHelper::CreateForWebContents(web_contents);
ViewAndroidHelper::CreateForWebContents(web_contents);
VoiceSearchTabHelper::CreateForWebContents(web_contents);
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index b39aaee..ec0ffa5 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -1498,6 +1498,8 @@
'browser/android/offline_pages/offline_page_mhtml_archiver.h',
'browser/android/offline_pages/offline_page_model_factory.cc',
'browser/android/offline_pages/offline_page_model_factory.h',
+ 'browser/android/offline_pages/offline_page_tab_helper.cc',
+ 'browser/android/offline_pages/offline_page_tab_helper.h',
'browser/android/offline_pages/offline_page_utils.cc',
'browser/android/offline_pages/offline_page_utils.h',
],
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index 14ad8a7..4d98dc9 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -1643,6 +1643,7 @@
# Sources for Offline pages. For now only for Android.
'chrome_unit_tests_offline_pages_sources': [
'browser/android/offline_pages/offline_page_mhtml_archiver_unittest.cc',
+ 'browser/android/offline_pages/offline_page_tab_helper_unittest.cc',
'browser/android/offline_pages/offline_page_utils_unittest.cc',
'browser/android/offline_pages/test_offline_page_model_builder.cc',
'browser/android/offline_pages/test_offline_page_model_builder.h',
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 30d96cf..b4817c6 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -32224,6 +32224,24 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries.
<summary>Size of the saved copy of an offline page.</summary>
</histogram>
+<histogram name="OfflinePages.RedirectToOfflineCount" units="count">
+ <owner>jianli@chromium.org</owner>
+ <summary>
+ Number of times an offline copy was loaded instead when the user is trying
+ to load the online version of a saved page and there is no network
+ connection.
+ </summary>
+</histogram>
+
+<histogram name="OfflinePages.RedirectToOnlineCount" units="count">
+ <owner>jianli@chromium.org</owner>
+ <summary>
+ Number of times an online version was loaded instead when the user is trying
+ to load the offline copy of a saved page and there is network connection.
+ connection.
+ </summary>
+</histogram>
+
<histogram name="OfflinePages.SavedPageCount" units="pages">
<owner>jianli@chromium.org</owner>
<summary>