Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(228)

Unified Diff: chrome/browser/android/offline_pages/offline_page_request_handler.cc

Issue 2245733004: Serve offline page for online URL on disconnected or bad networks (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Address feedback Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/android/offline_pages/offline_page_request_handler.cc
diff --git a/chrome/browser/android/offline_pages/offline_page_request_handler.cc b/chrome/browser/android/offline_pages/offline_page_request_handler.cc
index 0e9056c5c7865f3bcc91d783838862c1968bf5ed..ab395e43e4c640df5ea395467734838892b8a9a7 100644
--- a/chrome/browser/android/offline_pages/offline_page_request_handler.cc
+++ b/chrome/browser/android/offline_pages/offline_page_request_handler.cc
@@ -4,15 +4,45 @@
#include "chrome/browser/android/offline_pages/offline_page_request_handler.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/string_util.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/offline_page_utils.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
#include "components/offline_pages/offline_page_feature.h"
+#include "components/offline_pages/offline_page_model.h"
+#include "components/previews/previews_experiments.h"
#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/web_contents.h"
+#include "net/base/network_change_notifier.h"
+#include "net/nqe/network_quality_estimator.h"
#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_interceptor.h"
+#include "net/url_request/url_request_job.h"
namespace offline_pages {
+const char kLoadingOfflinePageHeader[] = "X-chromium-offline";
+const char kLoadingOfflinePageReason[] = "reason=";
+const char kLoadingOfflinePageDueToNetError[] = "error";
+
namespace {
+// This enum is used to tell all possible outcomes of handling network requests
+// that might serve offline contents.
+enum class RequestResult {
+ OFFLINE_PAGE_SERVED,
+ NO_TAB_ID,
+ NO_WEB_CONTENTS,
+ PAGE_NOT_FRESH,
+ OFFLINE_PAGE_NOT_FOUND
+};
+
int kUserDataKey; // Only address is used.
// An interceptor to hijack requests and potentially service them based on
@@ -43,6 +73,193 @@ class OfflinePageRequestInterceptor : public net::URLRequestInterceptor {
DISALLOW_COPY_AND_ASSIGN(OfflinePageRequestInterceptor);
};
+NetworkState GetNetworkState(net::URLRequest* request) {
Dmitry Titov 2016/08/16 20:02:55 This can be split into several functions and then
jianli 2016/08/18 22:46:35 Done.
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+
+ // If extra header kLoadingOfflinePageHeader is present, offline page should
+ // be loaded if available.
+ bool has_offline_header = false;
+ std::string value;
+ if (request->extra_request_headers().GetHeader(kLoadingOfflinePageHeader,
+ &value)) {
+ std::string reason;
+ base::StringTokenizer tokenizer(value, ", ");
+ while (tokenizer.GetNext()) {
+ if (base::StartsWith(tokenizer.token(),
+ kLoadingOfflinePageReason,
+ base::CompareCase::INSENSITIVE_ASCII)) {
+ reason = tokenizer.token().substr(
+ arraysize(kLoadingOfflinePageReason) - 1);
+ break;
+ }
+ }
+ if (base::EqualsCaseInsensitiveASCII(reason,
+ kLoadingOfflinePageDueToNetError)) {
+ return NetworkState::FLAKY_NETWORK;
+ }
+ has_offline_header = true;
+ }
+
+ if (net::NetworkChangeNotifier::IsOffline())
+ return NetworkState::DISCONNECTED_NETWORK;
+
+ // NetworkQualityEstimator only works when it is enabled.
+ if (previews::IsOfflinePreviewsEnabled()) {
+ net::NetworkQualityEstimator* network_quality_estimator =
+ request->context() ? request->context()->network_quality_estimator()
+ : nullptr;
+ if (network_quality_estimator) {
+ net::EffectiveConnectionType effective_connection_type =
+ network_quality_estimator->GetEffectiveConnectionType();
+ if (effective_connection_type >= net::EFFECTIVE_CONNECTION_TYPE_OFFLINE &&
+ effective_connection_type <= net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G) {
+ return NetworkState::PROHIBITIVELY_SLOW_NETWORK;
+ }
+ }
+ }
+
+ return has_offline_header ? NetworkState::FORCE_OFFLINE_ON_CONNECTED_NETWORK
+ : NetworkState::CONNECTED_NETWORK;
+}
+
+AggregatedRequestResult RequestResultToAggregatedRequestResult(
+ RequestResult request_result, NetworkState network_state) {
+ if (request_result == RequestResult::NO_TAB_ID)
+ return AggregatedRequestResult::NO_TAB_ID;
+
+ if (request_result == RequestResult::NO_WEB_CONTENTS)
+ return AggregatedRequestResult::NO_WEB_CONTENTS;
+
+ if (request_result == RequestResult::PAGE_NOT_FRESH) {
+ DCHECK_EQ(NetworkState::PROHIBITIVELY_SLOW_NETWORK, network_state);
+ return
+ AggregatedRequestResult::PAGE_NOT_FRESH_ON_PROHIBITIVELY_SLOW_NETWORK;
+ }
+
+ if (request_result == RequestResult::OFFLINE_PAGE_NOT_FOUND) {
+ switch (network_state) {
+ case NetworkState::DISCONNECTED_NETWORK:
+ return AggregatedRequestResult::PAGE_NOT_FOUND_ON_DISCONNECTED_NETWORK;
+ case NetworkState::PROHIBITIVELY_SLOW_NETWORK:
+ return AggregatedRequestResult::
+ PAGE_NOT_FOUND_ON_PROHIBITIVELY_SLOW_NETWORK;
+ case NetworkState::FLAKY_NETWORK:
+ return AggregatedRequestResult::PAGE_NOT_FOUND_ON_FLAKY_NETWORK;
+ case NetworkState::FORCE_OFFLINE_ON_CONNECTED_NETWORK:
+ return AggregatedRequestResult::PAGE_NOT_FOUND_ON_CONNECTED_NETWORK;
+ default:
+ NOTREACHED();
+ }
+ }
+
+ DCHECK_EQ(RequestResult::OFFLINE_PAGE_SERVED, request_result);
+ switch (network_state) {
+ case NetworkState::DISCONNECTED_NETWORK:
+ return AggregatedRequestResult::SHOW_OFFLINE_ON_DISCONNECTED_NETWORK;
+ case NetworkState::PROHIBITIVELY_SLOW_NETWORK:
+ return AggregatedRequestResult::
+ SHOW_OFFLINE_ON_PROHIBITIVELY_SLOW_NETWORK;
+ case NetworkState::FLAKY_NETWORK:
+ return AggregatedRequestResult::SHOW_OFFLINE_ON_FLAKY_NETWORK;
+ case NetworkState::CONNECTED_NETWORK:
+ return AggregatedRequestResult::SHOW_OFFLINE_ON_CONNECTED_NETWORK;
+ case NetworkState::FORCE_OFFLINE_ON_CONNECTED_NETWORK:
+ return AggregatedRequestResult::SHOW_OFFLINE_ON_CONNECTED_NETWORK;
+ default:
+ NOTREACHED();
+ }
+
+ return AggregatedRequestResult::AGGREGATED_REQUEST_RESULT_MAX;
+}
+
+void ReportRequestResult(RequestResult request_result,
+ NetworkState network_state) {
+ OfflinePageRequestHandler::ReportAggregatedRequestResult(
+ RequestResultToAggregatedRequestResult(request_result, network_state));
+}
+
+void NotifyOfflineFilePathOnIO(base::WeakPtr<OfflinePageRequestJob> job,
+ const base::FilePath& offline_file_path) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+
+ if (!job)
+ return;
+
+ if (offline_file_path.empty())
+ job->FallbackToDefault();
+ else
+ job->SetOfflineFilePath(offline_file_path);
+}
+
+// Notifies OfflinePageRequestJob about the offline file path. Note that the
+// file path may be empty if not found or on error.
+void NotifyOfflineFilePathOnUI(base::WeakPtr<OfflinePageRequestJob> job,
+ const base::FilePath& offline_file_path) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ // Delegates to IO thread since OfflinePageRequestJob should only be accessed
+ // from IO thread.
+ content::BrowserThread::PostTask(
+ content::BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&NotifyOfflineFilePathOnIO, job, offline_file_path));
+}
+
+// Handles the result of finding an offline page.
+void SelectPageForOnlineURLDone(
+ NetworkState network_state,
+ base::WeakPtr<OfflinePageRequestJob> job,
+ content::ResourceRequestInfo::WebContentsGetter web_contents_getter,
+ const OfflinePageItem* offline_page){
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ RequestResult request_result;
+ base::FilePath offline_file_path;
+
+ // If offline page is not found, fall through with empty file path such that
+ // OfflinePageRequestJob will be notified on failure.
+ if (offline_page) {
Dmitry Titov 2016/08/16 20:02:55 early return would reduce the nesting level here.
jianli 2016/08/18 22:46:35 Done.
+ // If the page is being loaded on a slow network, only use the offline page
+ // if it was created within the past day.
+ // TODO(romax): Make the constant be policy driven.
+ if (network_state == NetworkState::PROHIBITIVELY_SLOW_NETWORK &&
+ base::Time::Now() - offline_page->creation_time >
+ base::TimeDelta::FromDays(1)) {
+ request_result = RequestResult::PAGE_NOT_FRESH;
Dmitry Titov 2016/08/16 20:02:55 It would be probbaly more readable if this code ha
jianli 2016/08/18 22:46:35 Done.
+ } else {
+ // |web_contents_getter| is passed from IO thread. We neeed to check if
+ // web contents is still valid.
+ content::WebContents* web_contents = web_contents_getter.Run();
+ if (web_contents) {
+ request_result = RequestResult::OFFLINE_PAGE_SERVED;
+
+ offline_file_path = offline_page->file_path;
+
+ // Since offline page will be loaded, it should be marked as accessed.
+ OfflinePageModel* offline_page_model =
+ OfflinePageModelFactory::GetForBrowserContext(
+ web_contents->GetBrowserContext());
+ DCHECK(offline_page_model);
+ offline_page_model->MarkPageAccessed(offline_page->offline_id);
+
+ // Save an cached copy of OfflinePageItem such that Tab code can get
+ // the loaded offline page immediately.
+ OfflinePageTabHelper* tab_helper =
+ OfflinePageTabHelper::FromWebContents(web_contents);
+ DCHECK(tab_helper);
+ tab_helper->SetOfflinePage(*offline_page);
+ } else {
+ request_result = RequestResult::NO_WEB_CONTENTS;
+ }
+ }
+ } else {
+ request_result = RequestResult::OFFLINE_PAGE_NOT_FOUND;
+ }
+
+ ReportRequestResult(request_result, network_state);
+ NotifyOfflineFilePathOnUI(job, offline_file_path);
+}
+
} // namespace
// static
@@ -64,9 +281,15 @@ void OfflinePageRequestHandler::InitializeHandler(
if (request->method() != "GET")
return;
+ // Bail out if no need to load offline page.
+ NetworkState network_state = GetNetworkState(request);
+ if (network_state == NetworkState::CONNECTED_NETWORK)
+ return;
+
// Any more precise checks to see if the request should be intercepted are
// asynchronous, so just create our handler in all cases.
- request->SetUserData(&kUserDataKey, new OfflinePageRequestHandler());
+ request->SetUserData(&kUserDataKey,
+ new OfflinePageRequestHandler(network_state));
mmenke 2016/08/17 17:09:28 Rather than attach an object that does stuff to th
jianli 2016/08/18 22:46:35 Done.
}
// static
@@ -86,20 +309,94 @@ OfflinePageRequestHandler::CreateInterceptor(void* profile_id) {
new OfflinePageRequestInterceptor(profile_id));
}
-OfflinePageRequestHandler::OfflinePageRequestHandler() {
+// static
+void OfflinePageRequestHandler::ReportAggregatedRequestResult(
+ AggregatedRequestResult result) {
+ UMA_HISTOGRAM_ENUMERATION("OfflinePages.AggregatedRequestResult",
+ static_cast<int>(result),
+ static_cast<int>(AggregatedRequestResult::AGGREGATED_REQUEST_RESULT_MAX));
+}
+
+OfflinePageRequestHandler::OfflinePageRequestHandler(NetworkState network_state)
+ : network_state_(network_state),
+ weak_ptr_factory_(this) {
+ DCHECK_NE(NetworkState::CONNECTED_NETWORK, network_state);
}
OfflinePageRequestHandler::~OfflinePageRequestHandler() {
}
+void OfflinePageRequestHandler::OnPrepareToRestart() {
+ use_default_ = true;
+ offline_job_.reset();
+}
+
net::URLRequestJob* OfflinePageRequestHandler::MaybeCreateJob(
net::URLRequest* request,
net::NetworkDelegate* network_delegate,
void* profile_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
- // TODO(jianli): to be implemented.
- return nullptr;
+ // Fall back to default which is set when offline page is not found.
+ if (use_default_)
+ return nullptr;
+
+ OfflinePageRequestJob* offline_job =
+ new OfflinePageRequestJob(request, network_delegate, this);
+ offline_job_ = offline_job->GetWeakPtr();
+
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&OfflinePageRequestHandler::GetOfflineFileURL,
mmenke 2016/08/17 17:09:28 This do-two-things-at-once behavior seems weird to
jianli 2016/08/18 22:46:35 Done.
+ weak_ptr_factory_.GetWeakPtr(),
+ request->url(),
+ profile_id,
+ content::ResourceRequestInfo::ForRequest(request)->
+ GetWebContentsGetterForRequest()));
+
+ return offline_job_.get();
+}
+
+void OfflinePageRequestHandler::GetOfflineFileURL(
+ const GURL& online_url,
+ void* profile_id,
+ content::ResourceRequestInfo::WebContentsGetter web_contents_getter){
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ // |profile_id| needs to be checked with ProfileManager::IsValidProfile
+ // before using it.
+ if (!g_browser_process->profile_manager()->IsValidProfile(profile_id)) {
+ FailedToGetOfflineFileURL();
+ return;
+ }
+ Profile* profile = reinterpret_cast<Profile*>(profile_id);
+
+ content::WebContents* web_contents = web_contents_getter.Run();
mmenke 2016/08/17 17:09:28 web_contents_getter can return nullptr - the tab c
jianli 2016/08/18 22:46:35 Done.
+ int tab_id;
+ if (!OfflinePageUtils::GetTabId(web_contents, &tab_id)) {
+ ReportRequestResult(RequestResult::NO_TAB_ID, network_state_);
+ FailedToGetOfflineFileURL();
+ return;
+ }
+
+ OfflinePageUtils::SelectPageForOnlineURL(
+ profile,
+ online_url,
+ tab_id,
+ base::Bind(&SelectPageForOnlineURLDone,
+ network_state_,
+ offline_job_,
+ web_contents_getter));
+}
+
+void OfflinePageRequestHandler::FailedToGetOfflineFileURL() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ // Proceed with empty file path in order to notify the OfflinePageRequestJob
+ // about the failure.
+ base::FilePath empty_file_path;
+ NotifyOfflineFilePathOnUI(offline_job_, empty_file_path);
mmenke 2016/08/17 17:09:28 BUG: we're on the UI thread, this class is owned
jianli 2016/08/18 22:46:35 Done.
}
} // namespace offline_pages

Powered by Google App Engine
This is Rietveld 408576698