| // 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/offline_pages/offline_page_request_job.h" |
| |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/files/file_path.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/task_scheduler/post_task.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/offline_pages/offline_page_model_factory.h" |
| #include "chrome/browser/offline_pages/offline_page_tab_helper.h" |
| #include "chrome/browser/offline_pages/offline_page_utils.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/renderer_host/chrome_navigation_ui_data.h" |
| #include "components/offline_pages/core/client_namespace_constants.h" |
| #include "components/offline_pages/core/offline_page_model.h" |
| #include "components/offline_pages/core/request_header/offline_page_header.h" |
| #include "components/previews/core/previews_decider.h" |
| #include "components/previews/core/previews_experiments.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/resource_request_info.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/resource_type.h" |
| #include "net/base/network_change_notifier.h" |
| #include "net/http/http_request_headers.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/http/http_util.h" |
| #include "net/url_request/url_request.h" |
| #include "net/url_request/url_request_redirect_job.h" |
| #include "ui/base/page_transition_types.h" |
| #include "url/gurl.h" |
| |
| namespace offline_pages { |
| |
| namespace { |
| |
| enum class NetworkState { |
| // No network connection. |
| DISCONNECTED_NETWORK, |
| // Prohibitively slow means that the NetworkQualityEstimator reported a |
| // connection slow enough to warrant showing an offline page if available. |
| // This requires offline previews to be enabled and the URL of the request to |
| // be allowed by previews. |
| PROHIBITIVELY_SLOW_NETWORK, |
| // Network error received due to bad network, i.e. connected to a hotspot or |
| // proxy that does not have a working network. |
| FLAKY_NETWORK, |
| // Network is in working condition. |
| CONNECTED_NETWORK, |
| // Force to load the offline page if it is available, though network is in |
| // working condition. |
| FORCE_OFFLINE_ON_CONNECTED_NETWORK |
| }; |
| |
| // This enum is used to tell all possible outcomes of handling network requests |
| // that might serve offline contents. |
| enum class RequestResult { |
| // Offline page was shown for current URL. |
| OFFLINE_PAGE_SERVED, |
| // Redirected from original URL to final URL in preparation to show the |
| // offline page under final URL. OFFLINE_PAGE_SERVED is most likely to be |
| // reported next if no other error is encountered. |
| REDIRECTED, |
| // Tab was gone. |
| NO_TAB_ID, |
| // Web contents was gone. |
| NO_WEB_CONTENTS, |
| // The offline page found was not fresh enough, i.e. not created in the past |
| // day. This only applies in prohibitively slow network. |
| PAGE_NOT_FRESH, |
| // Offline page was not found, by searching with either final URL or original |
| // URL. |
| OFFLINE_PAGE_NOT_FOUND |
| }; |
| |
| const char kUserDataKey[] = "offline_page_key"; |
| |
| // Contains the info to handle offline page request. |
| class OfflinePageRequestInfo : public base::SupportsUserData::Data { |
| public: |
| OfflinePageRequestInfo() : use_default_(false) {} |
| ~OfflinePageRequestInfo() override {} |
| |
| static OfflinePageRequestInfo* GetFromRequest(net::URLRequest* request) { |
| return static_cast<OfflinePageRequestInfo*>( |
| request->GetUserData(&kUserDataKey)); |
| } |
| |
| bool use_default() const { return use_default_; } |
| void set_use_default(bool use_default) { use_default_ = use_default; } |
| |
| private: |
| // True if the next time this request is started, the request should be |
| // serviced from the default handler. |
| bool use_default_; |
| |
| DISALLOW_COPY_AND_ASSIGN(OfflinePageRequestInfo); |
| }; |
| |
| class DefaultDelegate : public OfflinePageRequestJob::Delegate { |
| public: |
| DefaultDelegate() {} |
| |
| content::ResourceRequestInfo::WebContentsGetter |
| GetWebContentsGetter(net::URLRequest* request) const override { |
| const content::ResourceRequestInfo* info = |
| content::ResourceRequestInfo::ForRequest(request); |
| return info ? info->GetWebContentsGetterForRequest() |
| : content::ResourceRequestInfo::WebContentsGetter(); |
| } |
| |
| OfflinePageRequestJob::Delegate::TabIdGetter GetTabIdGetter() const override { |
| return base::Bind(&DefaultDelegate::GetTabId); |
| } |
| |
| private: |
| static bool GetTabId(content::WebContents* web_contents, int* tab_id) { |
| return OfflinePageUtils::GetTabId(web_contents, tab_id); |
| } |
| |
| DISALLOW_COPY_AND_ASSIGN(DefaultDelegate); |
| }; |
| |
| NetworkState GetNetworkState(net::URLRequest* request, |
| const OfflinePageHeader& offline_header, |
| previews::PreviewsDecider* previews_decider) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| |
| if (offline_header.reason == OfflinePageHeader::Reason::NET_ERROR) |
| return NetworkState::FLAKY_NETWORK; |
| |
| if (net::NetworkChangeNotifier::IsOffline()) |
| return NetworkState::DISCONNECTED_NETWORK; |
| |
| // If RELOAD is present in the offline header, load the live page. |
| if (offline_header.reason == OfflinePageHeader::Reason::RELOAD) |
| return NetworkState::CONNECTED_NETWORK; |
| |
| // If other reason is present in the offline header, force loading the offline |
| // page even when the network is connected. |
| if (offline_header.reason != OfflinePageHeader::Reason::NONE) { |
| return NetworkState::FORCE_OFFLINE_ON_CONNECTED_NETWORK; |
| } |
| |
| // Checks if previews are allowed, the network is slow, and the request is |
| // allowed to be shown for previews. When reloading from an offline page or |
| // through other force checks, previews should not be considered; previews |
| // eligiblity is only checked when |offline_header.reason| is Reason::NONE. |
| if (previews_decider && previews_decider->ShouldAllowPreview( |
| *request, previews::PreviewsType::OFFLINE)) { |
| return NetworkState::PROHIBITIVELY_SLOW_NETWORK; |
| } |
| |
| // Otherwise, the network state is a good network. |
| return NetworkState::CONNECTED_NETWORK; |
| } |
| |
| OfflinePageRequestJob::AggregatedRequestResult |
| RequestResultToAggregatedRequestResult( |
| RequestResult request_result, NetworkState network_state) { |
| if (request_result == RequestResult::NO_TAB_ID) |
| return OfflinePageRequestJob::AggregatedRequestResult::NO_TAB_ID; |
| |
| if (request_result == RequestResult::NO_WEB_CONTENTS) |
| return OfflinePageRequestJob::AggregatedRequestResult::NO_WEB_CONTENTS; |
| |
| if (request_result == RequestResult::PAGE_NOT_FRESH) { |
| DCHECK_EQ(NetworkState::PROHIBITIVELY_SLOW_NETWORK, network_state); |
| return OfflinePageRequestJob::AggregatedRequestResult:: |
| PAGE_NOT_FRESH_ON_PROHIBITIVELY_SLOW_NETWORK; |
| } |
| |
| if (request_result == RequestResult::OFFLINE_PAGE_NOT_FOUND) { |
| switch (network_state) { |
| case NetworkState::DISCONNECTED_NETWORK: |
| return OfflinePageRequestJob::AggregatedRequestResult:: |
| PAGE_NOT_FOUND_ON_DISCONNECTED_NETWORK; |
| case NetworkState::PROHIBITIVELY_SLOW_NETWORK: |
| return OfflinePageRequestJob::AggregatedRequestResult:: |
| PAGE_NOT_FOUND_ON_PROHIBITIVELY_SLOW_NETWORK; |
| case NetworkState::FLAKY_NETWORK: |
| return OfflinePageRequestJob::AggregatedRequestResult:: |
| PAGE_NOT_FOUND_ON_FLAKY_NETWORK; |
| case NetworkState::FORCE_OFFLINE_ON_CONNECTED_NETWORK: |
| return OfflinePageRequestJob::AggregatedRequestResult:: |
| PAGE_NOT_FOUND_ON_CONNECTED_NETWORK; |
| case NetworkState::CONNECTED_NETWORK: |
| break; |
| } |
| NOTREACHED(); |
| } |
| |
| if (request_result == RequestResult::REDIRECTED) { |
| switch (network_state) { |
| case NetworkState::DISCONNECTED_NETWORK: |
| return OfflinePageRequestJob::AggregatedRequestResult:: |
| REDIRECTED_ON_DISCONNECTED_NETWORK; |
| case NetworkState::PROHIBITIVELY_SLOW_NETWORK: |
| return OfflinePageRequestJob::AggregatedRequestResult:: |
| REDIRECTED_ON_PROHIBITIVELY_SLOW_NETWORK; |
| case NetworkState::FLAKY_NETWORK: |
| return OfflinePageRequestJob::AggregatedRequestResult:: |
| REDIRECTED_ON_FLAKY_NETWORK; |
| case NetworkState::FORCE_OFFLINE_ON_CONNECTED_NETWORK: |
| return OfflinePageRequestJob::AggregatedRequestResult:: |
| REDIRECTED_ON_CONNECTED_NETWORK; |
| case NetworkState::CONNECTED_NETWORK: |
| break; |
| } |
| NOTREACHED(); |
| } |
| |
| DCHECK_EQ(RequestResult::OFFLINE_PAGE_SERVED, request_result); |
| DCHECK_NE(NetworkState::CONNECTED_NETWORK, network_state); |
| switch (network_state) { |
| case NetworkState::DISCONNECTED_NETWORK: |
| return OfflinePageRequestJob::AggregatedRequestResult:: |
| SHOW_OFFLINE_ON_DISCONNECTED_NETWORK; |
| case NetworkState::PROHIBITIVELY_SLOW_NETWORK: |
| return OfflinePageRequestJob::AggregatedRequestResult:: |
| SHOW_OFFLINE_ON_PROHIBITIVELY_SLOW_NETWORK; |
| case NetworkState::FLAKY_NETWORK: |
| return OfflinePageRequestJob::AggregatedRequestResult:: |
| SHOW_OFFLINE_ON_FLAKY_NETWORK; |
| case NetworkState::FORCE_OFFLINE_ON_CONNECTED_NETWORK: |
| return OfflinePageRequestJob::AggregatedRequestResult:: |
| SHOW_OFFLINE_ON_CONNECTED_NETWORK; |
| case NetworkState::CONNECTED_NETWORK: |
| break; |
| } |
| NOTREACHED(); |
| |
| return OfflinePageRequestJob::AggregatedRequestResult:: |
| AGGREGATED_REQUEST_RESULT_MAX; |
| } |
| |
| void ReportRequestResult(RequestResult request_result, |
| NetworkState network_state) { |
| OfflinePageRequestJob::ReportAggregatedRequestResult( |
| RequestResultToAggregatedRequestResult(request_result, network_state)); |
| } |
| |
| void ReportOfflinePageSize(NetworkState network_state, |
| const OfflinePageItem* offline_page) { |
| DCHECK(offline_page); |
| if (offline_page->client_id.name_space.empty()) |
| return; |
| |
| // The two histograms report values between 1KiB and 100MiB. |
| switch (network_state) { |
| case NetworkState::DISCONNECTED_NETWORK: // Fall-through |
| case NetworkState::PROHIBITIVELY_SLOW_NETWORK: // Fall-through |
| case NetworkState::FLAKY_NETWORK: |
| base::UmaHistogramCounts100000("OfflinePages.PageSizeOnAccess.Offline." + |
| offline_page->client_id.name_space, |
| offline_page->file_size / 1024); |
| return; |
| case NetworkState::FORCE_OFFLINE_ON_CONNECTED_NETWORK: |
| base::UmaHistogramCounts100000("OfflinePages.PageSizeOnAccess.Online." + |
| offline_page->client_id.name_space, |
| offline_page->file_size / 1024); |
| return; |
| case NetworkState::CONNECTED_NETWORK: |
| break; |
| } |
| NOTREACHED(); |
| } |
| |
| OfflinePageModel* GetOfflinePageModel( |
| content::ResourceRequestInfo::WebContentsGetter web_contents_getter) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| content::WebContents* web_contents = web_contents_getter.Run(); |
| return web_contents ? |
| OfflinePageModelFactory::GetForBrowserContext( |
| web_contents->GetBrowserContext()) : |
| nullptr; |
| } |
| |
| void NotifyOfflineFilePathOnIO(base::WeakPtr<OfflinePageRequestJob> job, |
| const std::string& name_space, |
| const base::FilePath& offline_file_path) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| |
| if (!job) |
| return; |
| job->OnOfflineFilePathAvailable(name_space, offline_file_path); |
| } |
| |
| void NotifyOfflineRedirectOnIO(base::WeakPtr<OfflinePageRequestJob> job, |
| const GURL& redirected_url) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| |
| if (!job) |
| return; |
| job->OnOfflineRedirectAvailabe(redirected_url); |
| } |
| |
| // 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 std::string& name_space, |
| 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, |
| name_space, offline_file_path)); |
| } |
| |
| // Notifies OfflinePageRequestJob about the redirected URL. Note that |
| // redirected_url should not be empty. |
| void NotifyOfflineRedirectOnUI(base::WeakPtr<OfflinePageRequestJob> job, |
| const GURL& redirected_url) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(!redirected_url.is_empty()); |
| |
| // Delegates to IO thread since OfflinePageRequestJob should only be accessed |
| // from IO thread. |
| content::BrowserThread::PostTask( |
| content::BrowserThread::IO, |
| FROM_HERE, |
| base::Bind(&NotifyOfflineRedirectOnIO, job, redirected_url)); |
| } |
| |
| // Finds the offline file path based on the select page result and network |
| // state and marks it as accessed. |
| RequestResult AccessOfflineFile( |
| const OfflinePageHeader& offline_header, |
| NetworkState network_state, |
| base::WeakPtr<OfflinePageRequestJob> job, |
| content::ResourceRequestInfo::WebContentsGetter web_contents_getter, |
| const OfflinePageItem* offline_page, |
| base::FilePath* offline_file_path) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| if (!offline_page) |
| return RequestResult::OFFLINE_PAGE_NOT_FOUND; |
| |
| // |web_contents_getter| is passed from IO thread. We need to check if |
| // web contents is still valid. |
| content::WebContents* web_contents = web_contents_getter.Run(); |
| if (!web_contents) |
| return RequestResult::NO_WEB_CONTENTS; |
| |
| // If the page is being loaded on a slow network, only use the offline page |
| // if it was created within the past day. |
| if (network_state == NetworkState::PROHIBITIVELY_SLOW_NETWORK && |
| base::Time::Now() - offline_page->creation_time > |
| previews::params::OfflinePreviewFreshnessDuration()) { |
| return RequestResult::PAGE_NOT_FRESH; |
| } |
| |
| // Since offline page will be loaded, it should be marked as accessed. |
| OfflinePageModel* offline_page_model = |
| OfflinePageModelFactory::GetForBrowserContext( |
| web_contents->GetBrowserContext()); |
| // |offline_page_model| cannot be null because OfflinePageRequestInterceptor |
| // will not be created under incognito mode. |
| 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, offline_header, true /*is_trusted*/, |
| network_state == NetworkState::PROHIBITIVELY_SLOW_NETWORK); |
| |
| *offline_file_path = offline_page->file_path; |
| return RequestResult::OFFLINE_PAGE_SERVED; |
| } |
| |
| // Handles the result of finding an offline page. |
| void SucceededToFindOfflinePage( |
| const GURL& url, |
| const OfflinePageHeader& offline_header, |
| NetworkState network_state, |
| base::WeakPtr<OfflinePageRequestJob> job, |
| content::ResourceRequestInfo::WebContentsGetter web_contents_getter, |
| const OfflinePageItem* offline_page) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // If the match is for original URL, trigger the redirect. |
| // Note: If the offline page has same orginal URL and final URL, don't trigger |
| // the redirect. Some websites might route the redirect finally back to itself |
| // after intermediate redirects for authentication. Previously this case was |
| // not handled and some pages might be saved with same URLs. Though we fixed |
| // the problem, we still need to support those pages already saved with this |
| if (offline_page && url == offline_page->original_url && |
| url != offline_page->url) { |
| ReportRequestResult(RequestResult::REDIRECTED, network_state); |
| NotifyOfflineRedirectOnUI(job, offline_page->url); |
| return; |
| } |
| |
| base::FilePath offline_file_path; |
| RequestResult request_result = AccessOfflineFile( |
| offline_header, network_state, job, web_contents_getter, offline_page, |
| &offline_file_path); |
| |
| ReportRequestResult(request_result, network_state); |
| if (request_result == RequestResult::OFFLINE_PAGE_SERVED) |
| ReportOfflinePageSize(network_state, offline_page); |
| |
| // NotifyOfflineFilePathOnUI should always be called regardless the failure |
| // result and empty file path such that OfflinePageRequestJob will be notified |
| // on failure. |
| NotifyOfflineFilePathOnUI( |
| job, offline_page ? offline_page->client_id.name_space : std::string(), |
| offline_file_path); |
| } |
| |
| void FailedToFindOfflinePage(base::WeakPtr<OfflinePageRequestJob> job) { |
| 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(job, std::string(), empty_file_path); |
| } |
| |
| // Tries to find the offline page to serve for |url|. |
| void SelectPageForURL( |
| const GURL& url, |
| const OfflinePageHeader& offline_header, |
| NetworkState network_state, |
| content::ResourceRequestInfo::WebContentsGetter web_contents_getter, |
| OfflinePageRequestJob::Delegate::TabIdGetter tab_id_getter, |
| base::WeakPtr<OfflinePageRequestJob> job) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| content::WebContents* web_contents = web_contents_getter.Run(); |
| if (!web_contents){ |
| ReportRequestResult(RequestResult::NO_WEB_CONTENTS, network_state); |
| FailedToFindOfflinePage(job); |
| return; |
| } |
| int tab_id; |
| if (!tab_id_getter.Run(web_contents, &tab_id)) { |
| ReportRequestResult(RequestResult::NO_TAB_ID, network_state); |
| FailedToFindOfflinePage(job); |
| return; |
| } |
| |
| OfflinePageUtils::SelectPageForURL( |
| web_contents->GetBrowserContext(), url, URLSearchMode::SEARCH_BY_ALL_URLS, |
| tab_id, |
| base::Bind(&SucceededToFindOfflinePage, url, offline_header, |
| network_state, job, web_contents_getter)); |
| } |
| |
| void FindPageWithOfflineIDDone( |
| const GURL& url, |
| const OfflinePageHeader& offline_header, |
| NetworkState network_state, |
| content::ResourceRequestInfo::WebContentsGetter web_contents_getter, |
| OfflinePageRequestJob::Delegate::TabIdGetter tab_id_getter, |
| base::WeakPtr<OfflinePageRequestJob> job, |
| const OfflinePageItem* offline_page) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // If the found offline page does not has same URL as the request URL, fall |
| // back to find the offline page based on the URL. |
| if (!offline_page || offline_page->url != url) { |
| SelectPageForURL( |
| url, offline_header, network_state, web_contents_getter, |
| tab_id_getter, job); |
| return; |
| } |
| |
| SucceededToFindOfflinePage( |
| url, offline_header, network_state, job, web_contents_getter, |
| offline_page); |
| } |
| |
| // Tries to find an offline page associated with |offline_id|. |
| void FindPageWithOfflineID( |
| const GURL& url, |
| const OfflinePageHeader& offline_header, |
| int64_t offline_id, |
| NetworkState network_state, |
| content::ResourceRequestInfo::WebContentsGetter web_contents_getter, |
| OfflinePageRequestJob::Delegate::TabIdGetter tab_id_getter, |
| base::WeakPtr<OfflinePageRequestJob> job) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| OfflinePageModel* offline_page_model = |
| GetOfflinePageModel(web_contents_getter); |
| if (!offline_page_model) { |
| FailedToFindOfflinePage(job); |
| return; |
| } |
| |
| offline_page_model->GetPageByOfflineId( |
| offline_id, |
| base::Bind(&FindPageWithOfflineIDDone, |
| url, |
| offline_header, |
| network_state, |
| web_contents_getter, |
| tab_id_getter, |
| job)); |
| } |
| |
| // Tries to find the offline page to serve for |url|. |
| void SelectPage( |
| const GURL& url, |
| const OfflinePageHeader& offline_header, |
| NetworkState network_state, |
| content::ResourceRequestInfo::WebContentsGetter web_contents_getter, |
| OfflinePageRequestJob::Delegate::TabIdGetter tab_id_getter, |
| base::WeakPtr<OfflinePageRequestJob> job) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // If an offline ID is present in the offline header, try to load that |
| // particular version. |
| if (!offline_header.id.empty()) { |
| // if the id string cannot be converted to int64 id, fall through to |
| // select page via URL. |
| int64_t offline_id; |
| if (base::StringToInt64(offline_header.id, &offline_id)) { |
| FindPageWithOfflineID(url, offline_header, offline_id, |
| network_state, web_contents_getter, tab_id_getter, |
| job); |
| return; |
| } |
| } |
| |
| SelectPageForURL(url, offline_header, network_state, web_contents_getter, |
| tab_id_getter, job); |
| } |
| |
| void ReportAccessEntryPoint( |
| const std::string& name_space, |
| OfflinePageRequestJob::AccessEntryPoint entry_point) { |
| base::UmaHistogramEnumeration("OfflinePages.AccessEntryPoint." + name_space, |
| entry_point, |
| OfflinePageRequestJob::AccessEntryPoint::COUNT); |
| } |
| |
| } // namespace |
| |
| // static |
| void OfflinePageRequestJob::ReportAggregatedRequestResult( |
| AggregatedRequestResult result) { |
| UMA_HISTOGRAM_ENUMERATION("OfflinePages.AggregatedRequestResult2", |
| static_cast<int>(result), |
| static_cast<int>(AggregatedRequestResult::AGGREGATED_REQUEST_RESULT_MAX)); |
| } |
| |
| // static |
| OfflinePageRequestJob* OfflinePageRequestJob::Create( |
| net::URLRequest* request, |
| net::NetworkDelegate* network_delegate, |
| previews::PreviewsDecider* previews_decider) { |
| const content::ResourceRequestInfo* resource_request_info = |
| content::ResourceRequestInfo::ForRequest(request); |
| if (!resource_request_info) |
| return nullptr; |
| |
| // Ignore the requests not for the main resource. |
| if (resource_request_info->GetResourceType() != |
| content::RESOURCE_TYPE_MAIN_FRAME) { |
| return nullptr; |
| } |
| |
| // Ignore non-http/https requests. |
| if (!request->url().SchemeIsHTTPOrHTTPS()) |
| return nullptr; |
| |
| // Ignore requests other than GET. |
| if (request->method() != "GET") |
| return nullptr; |
| |
| OfflinePageRequestInfo* info = |
| OfflinePageRequestInfo::GetFromRequest(request); |
| if (info) { |
| // Fall back to default which is set when an offline page cannot be served, |
| // either page not found or online version desired. |
| if (info->use_default()) |
| return nullptr; |
| } else { |
| request->SetUserData(&kUserDataKey, |
| base::MakeUnique<OfflinePageRequestInfo>()); |
| } |
| |
| return new OfflinePageRequestJob(request, network_delegate, previews_decider); |
| } |
| |
| OfflinePageRequestJob::OfflinePageRequestJob( |
| net::URLRequest* request, |
| net::NetworkDelegate* network_delegate, |
| previews::PreviewsDecider* previews_decider) |
| : net::URLRequestFileJob( |
| request, |
| network_delegate, |
| base::FilePath(), |
| base::CreateTaskRunnerWithTraits( |
| {base::MayBlock(), base::TaskPriority::USER_VISIBLE, |
| base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})), |
| delegate_(new DefaultDelegate()), |
| previews_decider_(previews_decider), |
| weak_ptr_factory_(this) {} |
| |
| OfflinePageRequestJob::~OfflinePageRequestJob() { |
| } |
| |
| void OfflinePageRequestJob::Start() { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::Bind(&OfflinePageRequestJob::StartAsync, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void OfflinePageRequestJob::StartAsync() { |
| std::string offline_header_value; |
| request()->extra_request_headers().GetHeader( |
| kOfflinePageHeader, &offline_header_value); |
| // Note that |offline_header| will be empty if parsing from the header value |
| // fails. |
| OfflinePageHeader offline_header(offline_header_value); |
| |
| NetworkState network_state = |
| GetNetworkState(request(), offline_header, previews_decider_); |
| if (network_state == NetworkState::CONNECTED_NETWORK) { |
| FallbackToDefault(); |
| return; |
| } |
| |
| content::BrowserThread::PostTask( |
| content::BrowserThread::UI, |
| FROM_HERE, |
| base::Bind(&SelectPage, |
| request()->url(), |
| offline_header, |
| network_state, |
| delegate_->GetWebContentsGetter(request()), |
| delegate_->GetTabIdGetter(), |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void OfflinePageRequestJob::Kill() { |
| net::URLRequestJob::Kill(); |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| } |
| |
| bool OfflinePageRequestJob::IsRedirectResponse(GURL* location, |
| int* http_status_code) { |
| // OfflinePageRequestJob derives from URLRequestFileJob which derives from |
| // URLRequestJob. We need to invoke the implementation of IsRedirectResponse |
| // in URLRequestJob directly since URLRequestFileJob overrode it with the |
| // logic we don't want. |
| return URLRequestJob::IsRedirectResponse(location, http_status_code); |
| } |
| |
| void OfflinePageRequestJob::GetResponseInfo(net::HttpResponseInfo* info) { |
| if (!fake_headers_for_redirect_) { |
| URLRequestFileJob::GetResponseInfo(info); |
| return; |
| } |
| |
| info->headers = fake_headers_for_redirect_; |
| info->request_time = redirect_response_time_; |
| info->response_time = redirect_response_time_; |
| } |
| |
| void OfflinePageRequestJob::GetLoadTimingInfo( |
| net::LoadTimingInfo* load_timing_info) const { |
| // Set send_start and send_end to receive_redirect_headers_end_ to be |
| // consistent with network cache behavior. |
| load_timing_info->send_start = receive_redirect_headers_end_; |
| load_timing_info->send_end = receive_redirect_headers_end_; |
| load_timing_info->receive_headers_end = receive_redirect_headers_end_; |
| } |
| |
| bool OfflinePageRequestJob::CopyFragmentOnRedirect(const GURL& location) const { |
| return false; |
| } |
| |
| void OfflinePageRequestJob::OnOpenComplete(int result) { |
| base::UmaHistogramSparse("OfflinePages.RequestJob.OpenFileErrorCode", |
| -result); |
| } |
| |
| void OfflinePageRequestJob::OnSeekComplete(int64_t result) { |
| if (result < 0) { |
| base::UmaHistogramSparse("OfflinePages.RequestJob.SeekFileErrorCode", |
| static_cast<int>(-result)); |
| } |
| } |
| |
| void OfflinePageRequestJob::OnReadComplete(net::IOBuffer* buf, int result) { |
| if (result < 0) { |
| base::UmaHistogramSparse("OfflinePages.RequestJob.ReadFileErrorCode", |
| -result); |
| } |
| } |
| |
| void OfflinePageRequestJob::FallbackToDefault() { |
| OfflinePageRequestInfo* info = |
| OfflinePageRequestInfo::GetFromRequest(request()); |
| DCHECK(info); |
| info->set_use_default(true); |
| |
| URLRequestJob::NotifyRestartRequired(); |
| } |
| |
| void OfflinePageRequestJob::OnOfflineFilePathAvailable( |
| const std::string& name_space, |
| const base::FilePath& offline_file_path) { |
| // If offline file path is empty, it means that offline page cannot be found |
| // and we want to restart the job to fall back to the default handling. |
| if (offline_file_path.empty()) { |
| FallbackToDefault(); |
| return; |
| } |
| |
| ReportAccessEntryPoint(name_space, GetAccessEntryPoint()); |
| |
| const content::ResourceRequestInfo* info = |
| content::ResourceRequestInfo::ForRequest(request()); |
| ChromeNavigationUIData* navigation_data = |
| static_cast<ChromeNavigationUIData*>(info->GetNavigationUIData()); |
| if (navigation_data) { |
| std::unique_ptr<OfflinePageNavigationUIData> offline_page_data = |
| std::make_unique<OfflinePageNavigationUIData>(true); |
| navigation_data->SetOfflinePageNavigationUIData( |
| std::move(offline_page_data)); |
| } |
| |
| // Sets the file path and lets URLRequestFileJob start to read from the file. |
| file_path_ = offline_file_path; |
| URLRequestFileJob::Start(); |
| } |
| |
| void OfflinePageRequestJob::OnOfflineRedirectAvailabe( |
| const GURL& redirected_url) { |
| receive_redirect_headers_end_ = base::TimeTicks::Now(); |
| redirect_response_time_ = base::Time::Now(); |
| |
| std::string header_string = |
| base::StringPrintf("HTTP/1.1 %i Internal Redirect\n" |
| // Clear referrer to avoid leak when going online. |
| "Referrer-Policy: no-referrer\n" |
| "Location: %s\n" |
| "Non-Authoritative-Reason: offline redirects", |
| // 302 is used to remove response bodies in order to |
| // avoid leak when going online. |
| net::URLRequestRedirectJob::REDIRECT_302_FOUND, |
| redirected_url.spec().c_str()); |
| |
| fake_headers_for_redirect_ = new net::HttpResponseHeaders( |
| net::HttpUtil::AssembleRawHeaders(header_string.c_str(), |
| header_string.length())); |
| DCHECK(fake_headers_for_redirect_->IsRedirect(nullptr)); |
| |
| URLRequestJob::NotifyHeadersComplete(); |
| } |
| |
| // Returns true to disable the file path checking for file: scheme in |
| // URLRequestFileJob, that's not relevant for this class. |
| bool OfflinePageRequestJob::CanAccessFile(const base::FilePath& original_path, |
| const base::FilePath& absolute_path) { |
| return true; |
| } |
| |
| void OfflinePageRequestJob::SetDelegateForTesting( |
| std::unique_ptr<Delegate> delegate) { |
| delegate_ = std::move(delegate); |
| } |
| |
| OfflinePageRequestJob::AccessEntryPoint |
| OfflinePageRequestJob::GetAccessEntryPoint() const { |
| const content::ResourceRequestInfo* info = |
| content::ResourceRequestInfo::ForRequest(request()); |
| if (!info) |
| return AccessEntryPoint::UNKNOWN; |
| |
| std::string offline_header_value; |
| request()->extra_request_headers().GetHeader(kOfflinePageHeader, |
| &offline_header_value); |
| OfflinePageHeader offline_header(offline_header_value); |
| if (offline_header.reason == OfflinePageHeader::Reason::DOWNLOAD) |
| return AccessEntryPoint::DOWNLOADS; |
| |
| ui::PageTransition transition = info->GetPageTransition(); |
| if (ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_LINK)) { |
| return PageTransitionGetQualifier(transition) == |
| static_cast<int>(ui::PAGE_TRANSITION_FROM_API) |
| ? AccessEntryPoint::CCT |
| : AccessEntryPoint::LINK; |
| } else if (ui::PageTransitionCoreTypeIs(transition, |
| ui::PAGE_TRANSITION_TYPED) || |
| ui::PageTransitionCoreTypeIs(transition, |
| ui::PAGE_TRANSITION_GENERATED)) { |
| return AccessEntryPoint::OMNIBOX; |
| } else if (ui::PageTransitionCoreTypeIs(transition, |
| ui::PAGE_TRANSITION_AUTO_BOOKMARK)) { |
| // Note that this also includes launching from bookmark which tends to be |
| // less likely. For now we don't separate these two. |
| return AccessEntryPoint::NTP_SUGGESTIONS_OR_BOOKMARKS; |
| } else if (ui::PageTransitionCoreTypeIs(transition, |
| ui::PAGE_TRANSITION_RELOAD)) { |
| return AccessEntryPoint::RELOAD; |
| } |
| |
| return AccessEntryPoint::UNKNOWN; |
| } |
| |
| } // namespace offline_pages |