1 // 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.
5 #include "content/browser/appcache/appcache_internals_ui.h"
8 #include "base/logging.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "base/strings/string_piece.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/threading/platform_thread.h"
15 #include "base/values.h"
16 #include "content/browser/appcache/appcache.h"
17 #include "content/browser/appcache/appcache_response.h"
18 #include "content/browser/storage_partition_impl.h"
19 #include "content/grit/content_resources.h"
20 #include "content/public/browser/browser_context.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "content/public/browser/web_ui.h"
23 #include "content/public/browser/web_ui_data_source.h"
24 #include "content/public/common/url_constants.h"
25 #include "net/base/escape.h"
26 #include "net/http/http_response_headers.h"
27 #include "net/url_request/view_cache_helper.h"
32 const char kRequestGetAllAppCacheInfo
[] = "getAllAppCache";
33 const char kRequestDeleteAppCache
[] = "deleteAppCache";
34 const char kRequestGetAppCacheDetails
[] = "getAppCacheDetails";
35 const char kRequestGetFileDetails
[] = "getFileDetails";
37 const char kFunctionOnAllAppCacheInfoReady
[] =
38 "appcache.onAllAppCacheInfoReady";
39 const char kFunctionOnAppCacheInfoDeleted
[] = "appcache.onAppCacheInfoDeleted";
40 const char kFunctionOnAppCacheDetailsReady
[] =
41 "appcache.onAppCacheDetailsReady";
42 const char kFunctionOnFileDetailsReady
[] = "appcache.onFileDetailsReady";
43 const char kFunctionOnFileDetailsFailed
[] = "appcache.onFileDetailsFailed";
45 int64
ToInt64(const std::string
& str
) {
47 base::StringToInt64(str
.c_str(), &i
);
51 bool SortByResourceUrl(const AppCacheResourceInfo
& lhs
,
52 const AppCacheResourceInfo
& rhs
) {
53 return lhs
.url
.spec() < rhs
.url
.spec();
56 scoped_ptr
<base::DictionaryValue
> GetDictionaryValueForResponseEnquiry(
57 const content::AppCacheInternalsUI::Proxy::ResponseEnquiry
&
59 scoped_ptr
<base::DictionaryValue
> dict_value(new base::DictionaryValue());
60 dict_value
->SetString("manifestURL", response_enquiry
.manifest_url
);
61 dict_value
->SetString("groupId",
62 base::Int64ToString(response_enquiry
.group_id
));
63 dict_value
->SetString("responseId",
64 base::Int64ToString(response_enquiry
.response_id
));
68 scoped_ptr
<base::DictionaryValue
> GetDictionaryValueForAppCacheInfo(
69 const content::AppCacheInfo
& appcache_info
) {
70 scoped_ptr
<base::DictionaryValue
> dict_value(new base::DictionaryValue());
71 dict_value
->SetString("manifestURL", appcache_info
.manifest_url
.spec());
72 dict_value
->SetDouble("creationTime", appcache_info
.creation_time
.ToJsTime());
73 dict_value
->SetDouble("lastUpdateTime",
74 appcache_info
.last_update_time
.ToJsTime());
75 dict_value
->SetDouble("lastAccessTime",
76 appcache_info
.last_access_time
.ToJsTime());
77 dict_value
->SetString(
79 base::UTF16ToUTF8(base::FormatBytesUnlocalized(appcache_info
.size
)));
80 dict_value
->SetString("groupId", base::Int64ToString(appcache_info
.group_id
));
85 scoped_ptr
<base::ListValue
> GetListValueForAppCacheInfoVector(
86 const AppCacheInfoVector
& appcache_info_vector
) {
87 scoped_ptr
<base::ListValue
> list(new base::ListValue());
88 for (const AppCacheInfo
& info
: appcache_info_vector
)
89 list
->Append(GetDictionaryValueForAppCacheInfo(info
));
93 scoped_ptr
<base::ListValue
> GetListValueFromAppCacheInfoCollection(
94 AppCacheInfoCollection
* appcache_collection
) {
95 scoped_ptr
<base::ListValue
> list(new base::ListValue());
96 for (const auto& key_value
: appcache_collection
->infos_by_origin
) {
97 base::DictionaryValue
* dict
= new base::DictionaryValue
;
98 dict
->SetString("originURL", key_value
.first
.spec());
99 dict
->Set("manifests", GetListValueForAppCacheInfoVector(key_value
.second
));
100 list
->Append(scoped_ptr
<base::Value
>(dict
));
105 scoped_ptr
<base::DictionaryValue
> GetDictionaryValueForAppCacheResourceInfo(
106 const AppCacheResourceInfo
& resource_info
) {
107 scoped_ptr
<base::DictionaryValue
> dict(new base::DictionaryValue
);
108 dict
->SetString("url", resource_info
.url
.spec());
111 base::UTF16ToUTF8(base::FormatBytesUnlocalized(resource_info
.size
)));
112 dict
->SetString("responseId", base::Int64ToString(resource_info
.response_id
));
113 dict
->SetBoolean("isExplicit", resource_info
.is_explicit
);
114 dict
->SetBoolean("isManifest", resource_info
.is_manifest
);
115 dict
->SetBoolean("isMaster", resource_info
.is_master
);
116 dict
->SetBoolean("isFallback", resource_info
.is_fallback
);
117 dict
->SetBoolean("isIntercept", resource_info
.is_intercept
);
118 dict
->SetBoolean("isForeign", resource_info
.is_foreign
);
123 scoped_ptr
<base::ListValue
> GetListValueForAppCacheResourceInfoVector(
124 AppCacheResourceInfoVector
* resource_info_vector
) {
125 scoped_ptr
<base::ListValue
> list(new base::ListValue
);
126 for (const AppCacheResourceInfo
& res_info
: *resource_info_vector
)
127 list
->Append(GetDictionaryValueForAppCacheResourceInfo(res_info
));
133 AppCacheInternalsUI::Proxy::Proxy(
134 base::WeakPtr
<AppCacheInternalsUI
> appcache_internals_ui
,
135 const base::FilePath
& partition_path
)
136 : appcache_internals_ui_(appcache_internals_ui
),
137 partition_path_(partition_path
) {}
139 void AppCacheInternalsUI::Proxy::Initialize(
140 const scoped_refptr
<ChromeAppCacheService
>& chrome_appcache_service
) {
141 if (!BrowserThread::CurrentlyOn(BrowserThread::IO
)) {
142 BrowserThread::PostTask(
143 BrowserThread::IO
, FROM_HERE
,
144 base::Bind(&Proxy::Initialize
, this, chrome_appcache_service
));
147 appcache_service_
= chrome_appcache_service
->AsWeakPtr();
148 shutdown_called_
= false;
149 preparing_response_
= false;
152 AppCacheInternalsUI::Proxy::~Proxy() {
153 DCHECK(shutdown_called_
);
156 void AppCacheInternalsUI::Proxy::Shutdown() {
157 if (!BrowserThread::CurrentlyOn(BrowserThread::IO
)) {
158 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
159 base::Bind(&Proxy::Shutdown
, this));
162 shutdown_called_
= true;
163 if (appcache_service_
) {
164 appcache_service_
->storage()->CancelDelegateCallbacks(this);
165 appcache_service_
.reset();
166 response_enquiries_
.clear();
170 void AppCacheInternalsUI::Proxy::RequestAllAppCacheInfo() {
171 if (!BrowserThread::CurrentlyOn(BrowserThread::IO
)) {
172 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
173 base::Bind(&Proxy::RequestAllAppCacheInfo
, this));
176 if (appcache_service_
) {
177 scoped_refptr
<AppCacheInfoCollection
> collection(
178 new AppCacheInfoCollection());
179 appcache_service_
->GetAllAppCacheInfo(
181 base::Bind(&Proxy::OnAllAppCacheInfoReady
, this, collection
));
185 void AppCacheInternalsUI::Proxy::OnAllAppCacheInfoReady(
186 scoped_refptr
<AppCacheInfoCollection
> collection
,
187 int net_result_code
) {
188 BrowserThread::PostTask(
189 BrowserThread::UI
, FROM_HERE
,
190 base::Bind(&AppCacheInternalsUI::OnAllAppCacheInfoReady
,
191 appcache_internals_ui_
, collection
, partition_path_
));
194 void AppCacheInternalsUI::Proxy::DeleteAppCache(
195 const std::string
& manifest_url
) {
196 if (!BrowserThread::CurrentlyOn(BrowserThread::IO
)) {
197 BrowserThread::PostTask(
198 BrowserThread::IO
, FROM_HERE
,
199 base::Bind(&Proxy::DeleteAppCache
, this, manifest_url
));
202 if (appcache_service_
) {
203 appcache_service_
->DeleteAppCacheGroup(
205 base::Bind(&Proxy::OnAppCacheInfoDeleted
, this, manifest_url
));
209 void AppCacheInternalsUI::Proxy::OnAppCacheInfoDeleted(
210 const std::string
& manifest_url
,
211 int net_result_code
) {
212 BrowserThread::PostTask(
213 BrowserThread::UI
, FROM_HERE
,
214 base::Bind(&AppCacheInternalsUI::OnAppCacheInfoDeleted
,
215 appcache_internals_ui_
, partition_path_
, manifest_url
,
216 net_result_code
== net::OK
));
219 void AppCacheInternalsUI::Proxy::RequestAppCacheDetails(
220 const std::string
& manifest_url
) {
221 if (!BrowserThread::CurrentlyOn(BrowserThread::IO
)) {
222 BrowserThread::PostTask(
223 BrowserThread::IO
, FROM_HERE
,
224 base::Bind(&Proxy::RequestAppCacheDetails
, this, manifest_url
));
228 if (appcache_service_
)
229 appcache_service_
->storage()->LoadOrCreateGroup(GURL(manifest_url
), this);
232 void AppCacheInternalsUI::Proxy::OnGroupLoaded(AppCacheGroup
* appcache_group
,
233 const GURL
& manifest_gurl
) {
234 scoped_ptr
<AppCacheResourceInfoVector
> resource_info_vector
;
235 if (appcache_group
&& appcache_group
->newest_complete_cache()) {
236 resource_info_vector
.reset(new AppCacheResourceInfoVector
);
237 appcache_group
->newest_complete_cache()->ToResourceInfoVector(
238 resource_info_vector
.get());
239 std::sort(resource_info_vector
->begin(), resource_info_vector
->end(),
242 BrowserThread::PostTask(
243 BrowserThread::UI
, FROM_HERE
,
244 base::Bind(&AppCacheInternalsUI::OnAppCacheDetailsReady
,
245 appcache_internals_ui_
, partition_path_
, manifest_gurl
.spec(),
246 base::Passed(&resource_info_vector
)));
249 void AppCacheInternalsUI::Proxy::RequestFileDetails(
250 const ResponseEnquiry
& response_enquiry
) {
251 if (!BrowserThread::CurrentlyOn(BrowserThread::IO
)) {
252 BrowserThread::PostTask(
253 BrowserThread::IO
, FROM_HERE
,
254 base::Bind(&Proxy::RequestFileDetails
, this, response_enquiry
));
257 DCHECK(!shutdown_called_
);
258 response_enquiries_
.push_back(response_enquiry
);
259 HandleFileDetailsRequest();
262 void AppCacheInternalsUI::Proxy::HandleFileDetailsRequest() {
263 if (preparing_response_
|| !response_enquiries_
.size() || !appcache_service_
)
265 preparing_response_
= true;
266 appcache_service_
->storage()->LoadResponseInfo(
267 GURL(response_enquiries_
.front().manifest_url
),
268 response_enquiries_
.front().group_id
,
269 response_enquiries_
.front().response_id
, this);
272 void AppCacheInternalsUI::Proxy::OnResponseInfoLoaded(
273 AppCacheResponseInfo
* response
,
275 if (shutdown_called_
)
277 if (!appcache_service_
)
279 ResponseEnquiry response_enquiry
= response_enquiries_
.front();
280 response_enquiries_
.pop_front();
282 scoped_refptr
<AppCacheResponseInfo
> response_info
= response
;
283 const int64 kLimit
= 100 * 1000;
284 int64 amount_to_read
=
285 std::min(kLimit
, response_info
->response_data_size());
286 scoped_refptr
<net::IOBuffer
> response_data(new net::IOBuffer(
287 base::CheckedNumeric
<size_t>(amount_to_read
).ValueOrDie()));
288 scoped_ptr
<AppCacheResponseReader
> reader(
289 appcache_service_
->storage()->CreateResponseReader(
290 GURL(response_enquiry
.manifest_url
), response_enquiry
.group_id
,
291 response_enquiry
.response_id
));
294 response_data
.get(), amount_to_read
,
295 base::Bind(&Proxy::OnResponseDataReadComplete
, this, response_enquiry
,
296 response_info
, base::Passed(&reader
), response_data
));
298 OnResponseDataReadComplete(response_enquiry
, nullptr, nullptr, nullptr, -1);
302 void AppCacheInternalsUI::Proxy::OnResponseDataReadComplete(
303 const ResponseEnquiry
& response_enquiry
,
304 scoped_refptr
<AppCacheResponseInfo
> response_info
,
305 scoped_ptr
<AppCacheResponseReader
> reader
,
306 scoped_refptr
<net::IOBuffer
> response_data
,
307 int net_result_code
) {
308 if (shutdown_called_
)
310 if (!response_info
|| net_result_code
< 0) {
311 BrowserThread::PostTask(
312 BrowserThread::UI
, FROM_HERE
,
313 base::Bind(&AppCacheInternalsUI::OnFileDetailsFailed
,
314 appcache_internals_ui_
, response_enquiry
, net_result_code
));
316 BrowserThread::PostTask(
317 BrowserThread::UI
, FROM_HERE
,
318 base::Bind(&AppCacheInternalsUI::OnFileDetailsReady
,
319 appcache_internals_ui_
, response_enquiry
, response_info
,
320 response_data
, net_result_code
));
322 preparing_response_
= false;
323 HandleFileDetailsRequest();
326 AppCacheInternalsUI::AppCacheInternalsUI(WebUI
* web_ui
)
327 : WebUIController(web_ui
), weak_ptr_factory_(this) {
328 web_ui
->RegisterMessageCallback(
329 kRequestGetAllAppCacheInfo
,
330 base::Bind(&AppCacheInternalsUI::GetAllAppCache
, AsWeakPtr()));
332 web_ui
->RegisterMessageCallback(
333 kRequestDeleteAppCache
,
334 base::Bind(&AppCacheInternalsUI::DeleteAppCache
, AsWeakPtr()));
336 web_ui
->RegisterMessageCallback(
337 kRequestGetAppCacheDetails
,
338 base::Bind(&AppCacheInternalsUI::GetAppCacheDetails
, AsWeakPtr()));
340 web_ui
->RegisterMessageCallback(
341 kRequestGetFileDetails
,
342 base::Bind(&AppCacheInternalsUI::GetFileDetails
, AsWeakPtr()));
344 WebUIDataSource
* source
=
345 WebUIDataSource::Create(kChromeUIAppCacheInternalsHost
);
347 source
->SetJsonPath("strings.js");
348 source
->AddResourcePath("appcache_internals.js", IDR_APPCACHE_INTERNALS_JS
);
349 source
->AddResourcePath("appcache_internals.css", IDR_APPCACHE_INTERNALS_CSS
);
350 source
->SetDefaultResource(IDR_APPCACHE_INTERNALS_HTML
);
352 WebUIDataSource::Add(browser_context(), source
);
354 BrowserContext::StoragePartitionCallback callback
=
355 base::Bind(&AppCacheInternalsUI::CreateProxyForPartition
, AsWeakPtr());
356 BrowserContext::ForEachStoragePartition(browser_context(), callback
);
359 AppCacheInternalsUI::~AppCacheInternalsUI() {
360 for (auto& proxy
: appcache_proxies_
)
364 void AppCacheInternalsUI::CreateProxyForPartition(
365 StoragePartition
* storage_partition
) {
366 scoped_refptr
<Proxy
> proxy
=
367 new Proxy(weak_ptr_factory_
.GetWeakPtr(), storage_partition
->GetPath());
368 proxy
->Initialize(static_cast<StoragePartitionImpl
*>(storage_partition
)
369 ->GetAppCacheService());
370 appcache_proxies_
.push_back(proxy
);
373 void AppCacheInternalsUI::GetAllAppCache(const base::ListValue
* args
) {
374 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
375 for (scoped_refptr
<Proxy
>& proxy
: appcache_proxies_
)
376 proxy
->RequestAllAppCacheInfo();
379 void AppCacheInternalsUI::DeleteAppCache(const base::ListValue
* args
) {
380 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
381 std::string manifest_url
, partition_path
;
382 args
->GetString(0, &partition_path
);
383 args
->GetString(1, &manifest_url
);
385 GetProxyForPartitionPath(base::FilePath::FromUTF8Unsafe(partition_path
));
387 proxy
->DeleteAppCache(manifest_url
);
390 void AppCacheInternalsUI::GetAppCacheDetails(const base::ListValue
* args
) {
391 std::string manifest_url
, partition_path
;
392 args
->GetString(0, &partition_path
);
393 args
->GetString(1, &manifest_url
);
395 GetProxyForPartitionPath(base::FilePath::FromUTF8Unsafe(partition_path
));
397 proxy
->RequestAppCacheDetails(manifest_url
);
400 void AppCacheInternalsUI::GetFileDetails(const base::ListValue
* args
) {
401 std::string manifest_url
, partition_path
, group_id_str
, response_id_str
;
402 args
->GetString(0, &partition_path
);
403 args
->GetString(1, &manifest_url
);
404 args
->GetString(2, &group_id_str
);
405 args
->GetString(3, &response_id_str
);
407 GetProxyForPartitionPath(base::FilePath::FromUTF8Unsafe(partition_path
));
409 proxy
->RequestFileDetails(
410 {manifest_url
, ToInt64(group_id_str
), ToInt64(response_id_str
)});
413 void AppCacheInternalsUI::OnAllAppCacheInfoReady(
414 scoped_refptr
<AppCacheInfoCollection
> collection
,
415 const base::FilePath
& partition_path
) {
416 std::string incognito_path_prefix
;
417 if (browser_context()->IsOffTheRecord())
418 incognito_path_prefix
= "Incognito ";
419 web_ui()->CallJavascriptFunction(
420 kFunctionOnAllAppCacheInfoReady
,
421 base::StringValue(incognito_path_prefix
+ partition_path
.AsUTF8Unsafe()),
422 *GetListValueFromAppCacheInfoCollection(collection
.get()));
425 void AppCacheInternalsUI::OnAppCacheInfoDeleted(
426 const base::FilePath
& partition_path
,
427 const std::string
& manifest_url
,
429 web_ui()->CallJavascriptFunction(
430 kFunctionOnAppCacheInfoDeleted
,
431 base::StringValue(partition_path
.AsUTF8Unsafe()),
432 base::StringValue(manifest_url
), base::FundamentalValue(deleted
));
435 void AppCacheInternalsUI::OnAppCacheDetailsReady(
436 const base::FilePath
& partition_path
,
437 const std::string
& manifest_url
,
438 scoped_ptr
<AppCacheResourceInfoVector
> resource_info_vector
) {
439 if (resource_info_vector
) {
440 web_ui()->CallJavascriptFunction(
441 kFunctionOnAppCacheDetailsReady
, base::StringValue(manifest_url
),
442 base::StringValue(partition_path
.AsUTF8Unsafe()),
443 *GetListValueForAppCacheResourceInfoVector(resource_info_vector
.get()));
445 web_ui()->CallJavascriptFunction(
446 kFunctionOnAppCacheDetailsReady
, base::StringValue(manifest_url
),
447 base::StringValue(partition_path
.AsUTF8Unsafe()));
451 void AppCacheInternalsUI::OnFileDetailsReady(
452 const Proxy::ResponseEnquiry
& response_enquiry
,
453 scoped_refptr
<AppCacheResponseInfo
> response_info
,
454 scoped_refptr
<net::IOBuffer
> response_data
,
457 if (response_info
->http_response_info()) {
458 headers
.append("<hr><pre>");
459 headers
.append(net::EscapeForHTML(
460 response_info
->http_response_info()->headers
->GetStatusLine()));
461 headers
.push_back('\n');
463 void* iter
= nullptr;
464 std::string name
, value
;
465 while (response_info
->http_response_info()->headers
->EnumerateHeaderLines(
466 &iter
, &name
, &value
)) {
467 headers
.append(net::EscapeForHTML(name
));
468 headers
.append(": ");
469 headers
.append(net::EscapeForHTML(value
));
470 headers
.push_back('\n');
472 headers
.append("</pre>");
474 headers
.append("Failed to read response headers. <br>");
476 std::string hex_dump
= base::StringPrintf(
477 "<hr><pre> Showing %d of %d bytes\n\n", static_cast<int>(data_length
),
478 static_cast<int>(response_info
->response_data_size()));
479 net::ViewCacheHelper::HexDump(response_data
->data(), data_length
, &hex_dump
);
480 if (data_length
< response_info
->response_data_size())
481 hex_dump
.append("\nNote: data is truncated...");
482 hex_dump
.append("</pre>");
483 web_ui()->CallJavascriptFunction(
484 kFunctionOnFileDetailsReady
,
485 *GetDictionaryValueForResponseEnquiry(response_enquiry
),
486 base::StringValue(headers
), base::StringValue(hex_dump
));
489 void AppCacheInternalsUI::OnFileDetailsFailed(
490 const Proxy::ResponseEnquiry
& response_enquiry
,
491 int net_result_code
) {
492 web_ui()->CallJavascriptFunction(
493 kFunctionOnFileDetailsFailed
,
494 *GetDictionaryValueForResponseEnquiry(response_enquiry
),
495 base::FundamentalValue(net_result_code
));
498 AppCacheInternalsUI::Proxy
* AppCacheInternalsUI::GetProxyForPartitionPath(
499 const base::FilePath
& partition_path
) {
500 for (const scoped_refptr
<Proxy
>& proxy
: appcache_proxies_
) {
501 if (proxy
->partition_path_
== partition_path
)
508 } // namespace content