blob: c86158d333bb1aef5ebd2124f61444a8a127cbb8 [file] [log] [blame]
fgorski1d4c9c92015-12-17 20:39:321// Copyright 2015 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Dmitry Titov909e8d62017-07-19 01:39:565#include "chrome/browser/offline_pages/offline_page_utils.h"
fgorski1d4c9c92015-12-17 20:39:326
dewittjfd1614e52016-06-06 18:36:017#include "base/bind.h"
8#include "base/location.h"
jianli81fd5882017-04-12 01:42:049#include "base/metrics/histogram_macros.h"
tripta.g3e097a92017-06-22 07:56:2610#include "base/stl_util.h"
bburns16d15332016-03-04 00:34:0911#include "base/strings/string_number_conversions.h"
fgorski1d4c9c92015-12-17 20:39:3212#include "base/strings/string_piece.h"
13#include "base/strings/string_util.h"
dewittjfd1614e52016-06-06 18:36:0114#include "base/threading/thread_task_runner_handle.h"
fgorskie8aae2c2016-11-23 23:45:3915#include "base/time/time.h"
Dmitry Titov909e8d62017-07-19 01:39:5616#include "build/build_config.h"
17#include "chrome/browser/offline_pages/offline_page_tab_helper.h"
jianlib0fe12b2017-01-12 01:14:0818#include "chrome/browser/net/net_error_tab_helper.h"
Dmitry Titov909e8d62017-07-19 01:39:5619#include "chrome/browser/offline_pages/offline_page_mhtml_archiver.h"
Jian Lie632ebc82017-06-30 23:40:3220#include "chrome/browser/offline_pages/offline_page_model_factory.h"
Dmitry Titov909e8d62017-07-19 01:39:5621#include "chrome/browser/offline_pages/request_coordinator_factory.h"
chiliecd23802016-12-07 10:27:1122#include "components/offline_pages/core/background/request_coordinator.h"
23#include "components/offline_pages/core/background/save_page_request.h"
24#include "components/offline_pages/core/client_namespace_constants.h"
25#include "components/offline_pages/core/client_policy_controller.h"
26#include "components/offline_pages/core/offline_page_feature.h"
27#include "components/offline_pages/core/offline_page_item.h"
28#include "components/offline_pages/core/offline_page_model.h"
29#include "components/offline_pages/core/request_header/offline_page_header.h"
fgorski1d4c9c92015-12-17 20:39:3230#include "content/public/browser/browser_context.h"
jianli918265d2017-02-28 01:50:1731#include "content/public/browser/navigation_entry.h"
dewittj44d11cb2016-06-17 23:30:2832#include "content/public/browser/web_contents.h"
jianli030745882017-04-28 08:35:1833#include "net/base/mime_util.h"
fgorski1d4c9c92015-12-17 20:39:3234
35namespace offline_pages {
36namespace {
37
jianli610b1fd2016-11-17 23:17:5238void OnGetPagesByURLDone(
39 const GURL& url,
jianlib76ea422016-08-11 02:30:5740 int tab_id,
dewittj7be8aa02016-10-07 18:19:1441 const std::vector<std::string>& namespaces_to_show_in_original_tab,
jianlib76ea422016-08-11 02:30:5742 const base::Callback<void(const OfflinePageItem*)>& callback,
43 const MultipleOfflinePageItemResult& pages) {
jianli610b1fd2016-11-17 23:17:5244 const OfflinePageItem* selected_page_for_final_url = nullptr;
45 const OfflinePageItem* selected_page_for_original_url = nullptr;
jianlib76ea422016-08-11 02:30:5746 std::string tab_id_str = base::IntToString(tab_id);
dewittj7be8aa02016-10-07 18:19:1447
jianli610b1fd2016-11-17 23:17:5248 for (const auto& page : pages) {
tripta.g3e097a92017-06-22 07:56:2649 if (base::ContainsValue(namespaces_to_show_in_original_tab,
50 page.client_id.name_space) &&
jianli610b1fd2016-11-17 23:17:5251 page.client_id.id != tab_id_str) {
dewittj7be8aa02016-10-07 18:19:1452 continue;
53 }
54
jianli610b1fd2016-11-17 23:17:5255 if (OfflinePageUtils::EqualsIgnoringFragment(url, page.url)) {
56 if (!selected_page_for_final_url ||
57 page.creation_time > selected_page_for_final_url->creation_time) {
58 selected_page_for_final_url = &page;
59 }
60 } else {
61 // This is consistent with exact match against original url done in
62 // OfflinePageModelImpl.
63 DCHECK(url == page.original_url);
64 if (!selected_page_for_original_url ||
65 page.creation_time > selected_page_for_original_url->creation_time) {
66 selected_page_for_original_url = &page;
67 }
jianlib76ea422016-08-11 02:30:5768 }
69 }
jianli610b1fd2016-11-17 23:17:5270
71 // Match for final URL should take high priority than matching for original
72 // URL.
73 callback.Run(selected_page_for_final_url ? selected_page_for_final_url
74 : selected_page_for_original_url);
jianlib76ea422016-08-11 02:30:5775}
76
jianli81fd5882017-04-12 01:42:0477bool IsSupportedByDownload(content::BrowserContext* browser_context,
78 const std::string& name_space) {
79 OfflinePageModel* offline_page_model =
80 OfflinePageModelFactory::GetForBrowserContext(browser_context);
81 DCHECK(offline_page_model);
82 ClientPolicyController* policy_controller =
83 offline_page_model->GetPolicyController();
84 DCHECK(policy_controller);
85 return policy_controller->IsSupportedByDownload(name_space);
86}
87
88void CheckDuplicateOngoingDownloads(
89 content::BrowserContext* browser_context,
90 const GURL& url,
91 const OfflinePageUtils::DuplicateCheckCallback& callback) {
92 RequestCoordinator* request_coordinator =
93 RequestCoordinatorFactory::GetForBrowserContext(browser_context);
94 if (!request_coordinator)
95 return;
96
97 auto request_coordinator_continuation =
98 [](content::BrowserContext* browser_context, const GURL& url,
99 const OfflinePageUtils::DuplicateCheckCallback& callback,
100 std::vector<std::unique_ptr<SavePageRequest>> requests) {
101 base::Time latest_request_time;
102 for (auto& request : requests) {
103 if (IsSupportedByDownload(browser_context,
104 request->client_id().name_space) &&
105 request->url() == url &&
106 latest_request_time < request->creation_time()) {
107 latest_request_time = request->creation_time();
108 }
109 }
110
111 if (latest_request_time.is_null()) {
112 callback.Run(OfflinePageUtils::DuplicateCheckResult::NOT_FOUND);
113 } else {
114 // Using CUSTOM_COUNTS instead of time-oriented histogram to record
115 // samples in seconds rather than milliseconds.
116 UMA_HISTOGRAM_CUSTOM_COUNTS(
117 "OfflinePages.DownloadRequestTimeSinceDuplicateRequested",
118 (base::Time::Now() - latest_request_time).InSeconds(),
119 base::TimeDelta::FromSeconds(1).InSeconds(),
120 base::TimeDelta::FromDays(7).InSeconds(), 50);
121
122 callback.Run(
123 OfflinePageUtils::DuplicateCheckResult::DUPLICATE_REQUEST_FOUND);
124 }
125 };
126
127 request_coordinator->GetAllRequests(base::Bind(
128 request_coordinator_continuation, browser_context, url, callback));
129}
130
romax444db4b2017-05-16 23:52:38131void DoCalculateSizeBetween(
132 const offline_pages::SizeInBytesCallback& callback,
133 const base::Time& begin_time,
134 const base::Time& end_time,
135 const offline_pages::MultipleOfflinePageItemResult& result) {
136 int64_t total_size = 0;
137 for (auto& page : result) {
138 if (begin_time <= page.creation_time && page.creation_time < end_time)
139 total_size += page.file_size;
140 }
141 callback.Run(total_size);
142}
143
fgorski1d4c9c92015-12-17 20:39:32144} // namespace
145
146// static
jianli610b1fd2016-11-17 23:17:52147void OfflinePageUtils::SelectPageForURL(
jianlib76ea422016-08-11 02:30:57148 content::BrowserContext* browser_context,
jianli610b1fd2016-11-17 23:17:52149 const GURL& url,
150 OfflinePageModel::URLSearchMode url_search_mode,
jianlib76ea422016-08-11 02:30:57151 int tab_id,
152 const base::Callback<void(const OfflinePageItem*)>& callback) {
153 OfflinePageModel* offline_page_model =
154 OfflinePageModelFactory::GetForBrowserContext(browser_context);
155 if (!offline_page_model) {
156 base::ThreadTaskRunnerHandle::Get()->PostTask(
157 FROM_HERE, base::Bind(callback, nullptr));
158 return;
159 }
160
jianli610b1fd2016-11-17 23:17:52161 offline_page_model->GetPagesByURL(
162 url,
163 url_search_mode,
164 base::Bind(&OnGetPagesByURLDone, url, tab_id,
165 offline_page_model->GetPolicyController()
166 ->GetNamespacesRestrictedToOriginalTab(),
167 callback));
jianlib76ea422016-08-11 02:30:57168}
169
dewittj44d11cb2016-06-17 23:30:28170const OfflinePageItem* OfflinePageUtils::GetOfflinePageFromWebContents(
171 content::WebContents* web_contents) {
172 OfflinePageTabHelper* tab_helper =
173 OfflinePageTabHelper::FromWebContents(web_contents);
jianlif68c52f2016-09-20 22:10:00174 if (!tab_helper)
175 return nullptr;
176 const OfflinePageItem* offline_page = tab_helper->offline_page();
177 if (!offline_page)
178 return nullptr;
179
180 // Returns the cached offline page only if the offline URL matches with
181 // current tab URL (skipping fragment identifier part). This is to prevent
182 // from returning the wrong offline page if DidStartNavigation is never called
183 // to clear it up.
184 GURL::Replacements remove_params;
185 remove_params.ClearRef();
186 GURL offline_url = offline_page->url.ReplaceComponents(remove_params);
187 GURL web_contents_url =
188 web_contents->GetVisibleURL().ReplaceComponents(remove_params);
189 return offline_url == web_contents_url ? offline_page : nullptr;
190}
191
192// static
193const OfflinePageHeader* OfflinePageUtils::GetOfflineHeaderFromWebContents(
194 content::WebContents* web_contents) {
195 OfflinePageTabHelper* tab_helper =
196 OfflinePageTabHelper::FromWebContents(web_contents);
197 return tab_helper ? &(tab_helper->offline_header()) : nullptr;
dewittj44d11cb2016-06-17 23:30:28198}
199
fgorski7a82d312016-06-28 22:09:34200// static
ryansturmad9e85d32016-10-28 17:28:06201bool OfflinePageUtils::IsShowingOfflinePreview(
202 content::WebContents* web_contents) {
203 OfflinePageTabHelper* tab_helper =
204 OfflinePageTabHelper::FromWebContents(web_contents);
205 return tab_helper && tab_helper->IsShowingOfflinePreview();
206}
207
208// static
jianlib0fe12b2017-01-12 01:14:08209bool OfflinePageUtils::IsShowingDownloadButtonInErrorPage(
210 content::WebContents* web_contents) {
211 chrome_browser_net::NetErrorTabHelper* tab_helper =
212 chrome_browser_net::NetErrorTabHelper::FromWebContents(web_contents);
213 return tab_helper && tab_helper->is_showing_download_button_in_error_page();
214}
215
216// static
fgorski2684ff82016-10-07 17:49:40217bool OfflinePageUtils::EqualsIgnoringFragment(const GURL& lhs,
218 const GURL& rhs) {
219 GURL::Replacements remove_params;
220 remove_params.ClearRef();
221
222 GURL lhs_stripped = lhs.ReplaceComponents(remove_params);
fgorski1acc2c42016-11-29 01:15:01223 GURL rhs_stripped = rhs.ReplaceComponents(remove_params);
fgorski2684ff82016-10-07 17:49:40224
225 return lhs_stripped == rhs_stripped;
226}
227
dimich4f6b2802016-12-20 00:18:33228// static
jianli918265d2017-02-28 01:50:17229GURL OfflinePageUtils::GetOriginalURLFromWebContents(
230 content::WebContents* web_contents) {
231 content::NavigationEntry* entry =
232 web_contents->GetController().GetLastCommittedEntry();
233 if (!entry || entry->GetRedirectChain().size() <= 1)
234 return GURL();
235 return entry->GetRedirectChain().front();
236}
237
jianli81fd5882017-04-12 01:42:04238// static
239void OfflinePageUtils::CheckDuplicateDownloads(
240 content::BrowserContext* browser_context,
241 const GURL& url,
242 const DuplicateCheckCallback& callback) {
243 // First check for finished downloads, that is, saved pages.
244 OfflinePageModel* offline_page_model =
245 OfflinePageModelFactory::GetForBrowserContext(browser_context);
246 if (!offline_page_model)
247 return;
248
249 auto continuation = [](content::BrowserContext* browser_context,
250 const GURL& url,
251 const DuplicateCheckCallback& callback,
252 const std::vector<OfflinePageItem>& pages) {
253 base::Time latest_saved_time;
254 for (const auto& offline_page_item : pages) {
255 if (IsSupportedByDownload(browser_context,
256 offline_page_item.client_id.name_space) &&
257 latest_saved_time < offline_page_item.creation_time) {
258 latest_saved_time = offline_page_item.creation_time;
259 }
260 }
261 if (latest_saved_time.is_null()) {
262 // Then check for ongoing downloads, that is, requests.
263 CheckDuplicateOngoingDownloads(browser_context, url, callback);
264 } else {
265 // Using CUSTOM_COUNTS instead of time-oriented histogram to record
266 // samples in seconds rather than milliseconds.
267 UMA_HISTOGRAM_CUSTOM_COUNTS(
268 "OfflinePages.DownloadRequestTimeSinceDuplicateSaved",
269 (base::Time::Now() - latest_saved_time).InSeconds(),
270 base::TimeDelta::FromSeconds(1).InSeconds(),
271 base::TimeDelta::FromDays(7).InSeconds(), 50);
272
273 callback.Run(DuplicateCheckResult::DUPLICATE_PAGE_FOUND);
274 }
275 };
276
277 offline_page_model->GetPagesByURL(
278 url, OfflinePageModel::URLSearchMode::SEARCH_BY_ALL_URLS,
279 base::Bind(continuation, browser_context, url, callback));
280}
281
282// static
283void OfflinePageUtils::ScheduleDownload(content::WebContents* web_contents,
284 const std::string& name_space,
285 const GURL& url,
Cathy Li7187388f2017-08-09 15:34:51286 DownloadUIActionFlags ui_action,
287 const std::string& request_origin) {
jianli81fd5882017-04-12 01:42:04288 DCHECK(web_contents);
289
290 OfflinePageTabHelper* tab_helper =
291 OfflinePageTabHelper::FromWebContents(web_contents);
292 if (!tab_helper)
293 return;
Cathy Li7187388f2017-08-09 15:34:51294 tab_helper->ScheduleDownloadHelper(web_contents, name_space, url, ui_action,
295 request_origin);
296}
297
298// static
299void OfflinePageUtils::ScheduleDownload(content::WebContents* web_contents,
300 const std::string& name_space,
301 const GURL& url,
302 DownloadUIActionFlags ui_action) {
303 ScheduleDownload(web_contents, name_space, url, ui_action, "");
jianli81fd5882017-04-12 01:42:04304}
305
jianli030745882017-04-28 08:35:18306// static
307bool OfflinePageUtils::CanDownloadAsOfflinePage(
308 const GURL& url,
309 const std::string& contents_mime_type) {
310 return url.SchemeIsHTTPOrHTTPS() &&
311 (net::MatchesMimeType(contents_mime_type, "text/html") ||
312 net::MatchesMimeType(contents_mime_type, "application/xhtml+xml"));
313}
314
romax444db4b2017-05-16 23:52:38315// static
316bool OfflinePageUtils::GetCachedOfflinePageSizeBetween(
317 content::BrowserContext* browser_context,
318 const SizeInBytesCallback& callback,
319 const base::Time& begin_time,
320 const base::Time& end_time) {
321 OfflinePageModel* offline_page_model =
322 OfflinePageModelFactory::GetForBrowserContext(browser_context);
323 if (!offline_page_model || begin_time > end_time)
324 return false;
325 OfflinePageModelQueryBuilder builder;
326 builder.RequireRemovedOnCacheReset(
327 OfflinePageModelQuery::Requirement::INCLUDE_MATCHING);
328 offline_page_model->GetPagesMatchingQuery(
329 builder.Build(offline_page_model->GetPolicyController()),
330 base::Bind(&DoCalculateSizeBetween, callback, begin_time, end_time));
331 return true;
332}
333
fgorski1d4c9c92015-12-17 20:39:32334} // namespace offline_pages