1 // Copyright (c) 2011 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
5 #include "content/browser/appcache/appcache_host.h"
6
7 #include <utility>
8 #include <vector>
9
10 #include "base/logging.h"
11 #include "base/memory/ptr_util.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/stringprintf.h"
14 #include "content/browser/appcache/appcache.h"
15 #include "content/browser/appcache/appcache_backend_impl.h"
16 #include "content/browser/appcache/appcache_policy.h"
17 #include "content/browser/appcache/appcache_request.h"
18 #include "content/browser/appcache/appcache_request_handler.h"
19 #include "content/browser/appcache/appcache_subresource_url_factory.h"
20 #include "content/browser/child_process_security_policy_impl.h"
21 #include "content/browser/web_contents/web_contents_impl.h"
22 #include "content/common/appcache_interfaces.h"
23 #include "content/public/common/content_client.h"
24 #include "content/public/common/url_constants.h"
25 #include "net/url_request/url_request.h"
26 #include "services/network/public/mojom/url_loader_factory.mojom.h"
27 #include "storage/browser/quota/quota_manager_proxy.h"
28 #include "third_party/blink/public/mojom/appcache/appcache.mojom.h"
29 #include "third_party/blink/public/mojom/appcache/appcache_info.mojom.h"
30
31 namespace content {
32
33 namespace {
34
CreateCacheInfo(const AppCache * cache,const GURL & manifest_url,blink::mojom::AppCacheStatus status)35 blink::mojom::AppCacheInfoPtr CreateCacheInfo(
36 const AppCache* cache,
37 const GURL& manifest_url,
38 blink::mojom::AppCacheStatus status) {
39 auto info = blink::mojom::AppCacheInfo::New();
40 info->manifest_url = manifest_url;
41 info->status = status;
42
43 if (!cache)
44 return info;
45
46 info->cache_id = cache->cache_id();
47
48 if (!cache->is_complete())
49 return info;
50
51 DCHECK(cache->owning_group());
52 info->is_complete = true;
53 info->group_id = cache->owning_group()->group_id();
54 info->last_update_time = cache->update_time();
55 info->creation_time = cache->owning_group()->creation_time();
56 info->response_sizes = cache->cache_size();
57 info->padding_sizes = cache->padding_size();
58 return info;
59 }
60
CanAccessDocumentURL(ChildProcessSecurityPolicyImpl::Handle * handle,const GURL & document_url)61 bool CanAccessDocumentURL(ChildProcessSecurityPolicyImpl::Handle* handle,
62 const GURL& document_url) {
63 DCHECK(handle->is_valid());
64 return document_url.is_empty() || // window.open("javascript:''") case.
65 document_url.IsAboutSrcdoc() || // <iframe srcdoc= ...> case.
66 document_url.IsAboutBlank() || // <iframe src="javascript:''"> case.
67 document_url == GURL("data:,") || // CSP blocked_urls.
68 (document_url.SchemeIsBlob() && // <iframe src="blob:null/xx"> case.
69 url::Origin::Create(document_url).opaque()) ||
70 handle->CanAccessDataForOrigin(document_url);
71 }
72
GetDocumentUrlCrashKey()73 base::debug::CrashKeyString* GetDocumentUrlCrashKey() {
74 static auto* appcache_document_url_key = base::debug::AllocateCrashKeyString(
75 "appcache_document_url", base::debug::CrashKeySize::Size256);
76 return appcache_document_url_key;
77 }
78
79 } // namespace
80
AppCacheHost(const base::UnguessableToken & host_id,int process_id,int render_frame_id,mojo::PendingRemote<blink::mojom::AppCacheFrontend> frontend_remote,AppCacheServiceImpl * service)81 AppCacheHost::AppCacheHost(
82 const base::UnguessableToken& host_id,
83 int process_id,
84 int render_frame_id,
85 mojo::PendingRemote<blink::mojom::AppCacheFrontend> frontend_remote,
86 AppCacheServiceImpl* service)
87 : host_id_(host_id),
88 process_id_(process_id),
89 pending_main_resource_cache_id_(blink::mojom::kAppCacheNoCacheId),
90 pending_selected_cache_id_(blink::mojom::kAppCacheNoCacheId),
91 was_select_cache_called_(false),
92 is_cache_selection_enabled_(true),
93 frontend_remote_(std::move(frontend_remote)),
94 frontend_(frontend_remote_.is_bound() ? frontend_remote_.get() : nullptr),
95 render_frame_id_(render_frame_id),
96 service_(service),
97 storage_(service->storage()),
98 main_resource_was_namespace_entry_(false),
99 main_resource_blocked_(false),
100 associated_cache_info_pending_(false) {
101 service_->AddObserver(this);
102 if (process_id_ != ChildProcessHost::kInvalidUniqueID) {
103 security_policy_handle_ =
104 ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
105 process_id_);
106 }
107 }
108
~AppCacheHost()109 AppCacheHost::~AppCacheHost() {
110 service_->RemoveObserver(this);
111 for (auto& observer : observers_)
112 observer.OnDestructionImminent(this);
113 if (associated_cache_.get())
114 associated_cache_->UnassociateHost(this);
115 if (group_being_updated_.get())
116 group_being_updated_->RemoveUpdateObserver(this);
117 storage()->CancelDelegateCallbacks(this);
118 if (service()->quota_manager_proxy() && !origin_in_use_.opaque())
119 service()->quota_manager_proxy()->NotifyOriginNoLongerInUse(origin_in_use_);
120
121 // Run pending callbacks in case we get destroyed with pending callbacks while
122 // the mojo connection is still open.
123 if (pending_get_status_callback_) {
124 std::move(pending_get_status_callback_)
125 .Run(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED);
126 }
127 if (pending_swap_cache_callback_)
128 std::move(pending_swap_cache_callback_).Run(false);
129 if (pending_start_update_callback_)
130 std::move(pending_start_update_callback_).Run(false);
131 }
132
BindReceiver(mojo::PendingReceiver<blink::mojom::AppCacheHost> receiver)133 void AppCacheHost::BindReceiver(
134 mojo::PendingReceiver<blink::mojom::AppCacheHost> receiver) {
135 receiver_.Bind(std::move(receiver));
136 // Unretained is safe because |this| will outlive |receiver_|.
137 receiver_.set_disconnect_handler(
138 base::BindOnce(&AppCacheHost::Unregister, base::Unretained(this)));
139 }
140
AddObserver(Observer * observer)141 void AppCacheHost::AddObserver(Observer* observer) {
142 observers_.AddObserver(observer);
143 }
144
RemoveObserver(Observer * observer)145 void AppCacheHost::RemoveObserver(Observer* observer) {
146 observers_.RemoveObserver(observer);
147 }
148
Unregister()149 void AppCacheHost::Unregister() {
150 service_->EraseHost(host_id_);
151 }
152
SelectCache(const GURL & document_url,const int64_t cache_document_was_loaded_from,const GURL & manifest_url)153 void AppCacheHost::SelectCache(const GURL& document_url,
154 const int64_t cache_document_was_loaded_from,
155 const GURL& manifest_url) {
156 if (was_select_cache_called_) {
157 mojo::ReportBadMessage("ACH_SELECT_CACHE");
158 return;
159 }
160
161 DCHECK(security_policy_handle_.is_valid());
162 if (!CanAccessDocumentURL(security_policy_handle(), document_url)) {
163 base::debug::SetCrashKeyString(GetDocumentUrlCrashKey(),
164 document_url.spec());
165 mojo::ReportBadMessage("ACH_SELECT_CACHE_DOCUMENT_URL_ACCESS_NOT_ALLOWED");
166 return;
167 }
168
169 if (!manifest_url.is_empty() &&
170 !security_policy_handle()->CanAccessDataForOrigin(manifest_url)) {
171 mojo::ReportBadMessage("ACH_SELECT_CACHE_MANIFEST_URL_ACCESS_NOT_ALLOWED");
172 return;
173 }
174
175 DCHECK(pending_start_update_callback_.is_null() &&
176 pending_swap_cache_callback_.is_null() &&
177 pending_get_status_callback_.is_null() && !is_selection_pending());
178
179 was_select_cache_called_ = true;
180 if (!is_cache_selection_enabled_) {
181 FinishCacheSelection(nullptr, nullptr, mojo::ReportBadMessageCallback());
182 return;
183 }
184
185 origin_in_use_ = url::Origin::Create(document_url);
186 if (service()->quota_manager_proxy() && !origin_in_use_.opaque())
187 service()->quota_manager_proxy()->NotifyOriginInUse(origin_in_use_);
188
189 if (main_resource_blocked_)
190 OnContentBlocked(blocked_manifest_url_);
191
192 // 7.9.5 The application cache selection algorithm.
193 // The algorithm is started here and continues in FinishCacheSelection,
194 // after cache or group loading is complete.
195 // Note: Foreign entries are detected on the client side and
196 // MarkAsForeignEntry is called in that case, so that detection
197 // step is skipped here.
198
199 if (cache_document_was_loaded_from != blink::mojom::kAppCacheNoCacheId) {
200 LoadSelectedCache(cache_document_was_loaded_from);
201 return;
202 }
203
204 if (!manifest_url.is_empty() &&
205 (manifest_url.GetOrigin() == document_url.GetOrigin())) {
206 // TODO(mek): Technically we should be checking to make sure
207 // first_party_url_ was initialized, however in practice it appears often
208 // this isn't the case (even though that means the renderer is trying to
209 // select an AppCache for a document that wasn't fetched through this host
210 // in the first place, which shouldn't happen). Since the worst that can
211 // happen if it isn't is that AppCache isn't used when third party cookie
212 // blocking is enabled, we want to get rid of AppCache anyway, and this has
213 // been the behavior for a long time anyway, don't bother checking and just
214 // continue whether it was set or not.
215
216 AppCachePolicy* policy = service()->appcache_policy();
217 if (policy && !policy->CanCreateAppCache(
218 manifest_url, site_for_cookies_.RepresentativeUrl())) {
219 FinishCacheSelection(nullptr, nullptr, mojo::ReportBadMessageCallback());
220 frontend()->EventRaised(
221 blink::mojom::AppCacheEventID::APPCACHE_CHECKING_EVENT);
222 frontend()->ErrorEventRaised(blink::mojom::AppCacheErrorDetails::New(
223 "Cache creation was blocked by the content policy",
224 blink::mojom::AppCacheErrorReason::APPCACHE_POLICY_ERROR, GURL(), 0,
225 false /*is_cross_origin*/));
226 OnContentBlocked(manifest_url);
227 return;
228 }
229 // Note: The client detects if the document was not loaded using HTTP GET
230 // and invokes SelectCache without a manifest url, so that detection step
231 // is also skipped here.
232 set_preferred_manifest_url(manifest_url);
233 new_master_entry_url_ = document_url;
234 LoadOrCreateGroup(manifest_url);
235 return;
236 }
237 // TODO(michaeln): If there was a manifest URL, the user agent may report
238 // to the user that it was ignored, to aid in application development.
239 FinishCacheSelection(nullptr, nullptr, mojo::ReportBadMessageCallback());
240 }
241
SelectCacheForWorker(int64_t appcache_id)242 void AppCacheHost::SelectCacheForWorker(int64_t appcache_id) {
243 if (was_select_cache_called_) {
244 mojo::ReportBadMessage("ACH_SELECT_CACHE_FOR_WORKER");
245 return;
246 }
247
248 DCHECK(pending_start_update_callback_.is_null() &&
249 pending_swap_cache_callback_.is_null() &&
250 pending_get_status_callback_.is_null() && !is_selection_pending());
251
252 was_select_cache_called_ = true;
253 if (appcache_id != blink::mojom::kAppCacheNoCacheId) {
254 LoadSelectedCache(appcache_id);
255 return;
256 }
257 FinishCacheSelection(nullptr, nullptr, mojo::ReportBadMessageCallback());
258 }
259
260 // TODO(michaeln): change method name to MarkEntryAsForeign for consistency
MarkAsForeignEntry(const GURL & document_url,int64_t cache_document_was_loaded_from)261 void AppCacheHost::MarkAsForeignEntry(const GURL& document_url,
262 int64_t cache_document_was_loaded_from) {
263 if (was_select_cache_called_) {
264 mojo::ReportBadMessage("ACH_MARK_AS_FOREIGN_ENTRY");
265 return;
266 }
267
268 if (!CanAccessDocumentURL(security_policy_handle(), document_url)) {
269 base::debug::SetCrashKeyString(GetDocumentUrlCrashKey(),
270 document_url.spec());
271 mojo::ReportBadMessage(
272 "ACH_MARK_AS_FOREIGN_ENTRY_DOCUMENT_URL_ACCESS_NOT_ALLOWED");
273 return;
274 }
275
276 // The document url is not the resource url in the fallback case.
277 storage()->MarkEntryAsForeign(
278 main_resource_was_namespace_entry_ ? namespace_entry_url_ : document_url,
279 cache_document_was_loaded_from);
280 SelectCache(document_url, blink::mojom::kAppCacheNoCacheId, GURL());
281 }
282
GetStatus(GetStatusCallback callback)283 void AppCacheHost::GetStatus(GetStatusCallback callback) {
284 if (!pending_start_update_callback_.is_null() ||
285 !pending_swap_cache_callback_.is_null() ||
286 !pending_get_status_callback_.is_null()) {
287 mojo::ReportBadMessage("ACH_GET_STATUS");
288 std::move(callback).Run(
289 blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED);
290 return;
291 }
292
293 pending_get_status_callback_ = std::move(callback);
294 if (is_selection_pending())
295 return;
296
297 DoPendingGetStatus();
298 }
299
DoPendingGetStatus()300 void AppCacheHost::DoPendingGetStatus() {
301 DCHECK_EQ(false, pending_get_status_callback_.is_null());
302
303 std::move(pending_get_status_callback_).Run(GetStatusSync());
304 }
305
StartUpdate(StartUpdateCallback callback)306 void AppCacheHost::StartUpdate(StartUpdateCallback callback) {
307 if (!pending_start_update_callback_.is_null() ||
308 !pending_swap_cache_callback_.is_null() ||
309 !pending_get_status_callback_.is_null()) {
310 mojo::ReportBadMessage("ACH_START_UPDATE");
311 std::move(callback).Run(false);
312 return;
313 }
314
315 pending_start_update_callback_ = std::move(callback);
316 if (is_selection_pending())
317 return;
318
319 DoPendingStartUpdate();
320 }
321
DoPendingStartUpdate()322 void AppCacheHost::DoPendingStartUpdate() {
323 DCHECK_EQ(false, pending_start_update_callback_.is_null());
324
325 // 6.9.8 Application cache API
326 bool success = false;
327 if (associated_cache_.get() && associated_cache_->owning_group()) {
328 AppCacheGroup* group = associated_cache_->owning_group();
329 if (!group->is_obsolete() && !group->is_being_deleted()) {
330 success = true;
331 group->StartUpdate();
332 }
333 }
334
335 std::move(pending_start_update_callback_).Run(success);
336 }
337
SwapCache(SwapCacheCallback callback)338 void AppCacheHost::SwapCache(SwapCacheCallback callback) {
339 if (!pending_start_update_callback_.is_null() ||
340 !pending_swap_cache_callback_.is_null() ||
341 !pending_get_status_callback_.is_null()) {
342 mojo::ReportBadMessage("ACH_SWAP_CACHE");
343 std::move(callback).Run(false);
344 return;
345 }
346
347 pending_swap_cache_callback_ = std::move(callback);
348
349 if (is_selection_pending())
350 return;
351
352 DoPendingSwapCache();
353 }
354
DoPendingSwapCache()355 void AppCacheHost::DoPendingSwapCache() {
356 DCHECK_EQ(false, pending_swap_cache_callback_.is_null());
357
358 // 6.9.8 Application cache API
359 bool success = false;
360 if (associated_cache_.get() && associated_cache_->owning_group()) {
361 if (associated_cache_->owning_group()->is_obsolete()) {
362 success = true;
363 AssociateNoCache(GURL());
364 } else if (swappable_cache_.get()) {
365 DCHECK(swappable_cache_.get() ==
366 swappable_cache_->owning_group()->newest_complete_cache());
367 success = true;
368 AssociateCompleteCache(swappable_cache_.get());
369 }
370 }
371
372 std::move(pending_swap_cache_callback_).Run(success);
373 }
374
SetSpawningHostId(const base::UnguessableToken & spawning_host_id)375 void AppCacheHost::SetSpawningHostId(
376 const base::UnguessableToken& spawning_host_id) {
377 spawning_host_id_ = spawning_host_id;
378 }
379
GetSpawningHost() const380 const AppCacheHost* AppCacheHost::GetSpawningHost() const {
381 return service_->GetHost(spawning_host_id_);
382 }
383
GetResourceList(GetResourceListCallback callback)384 void AppCacheHost::GetResourceList(GetResourceListCallback callback) {
385 std::vector<blink::mojom::AppCacheResourceInfo> params;
386 std::vector<blink::mojom::AppCacheResourceInfoPtr> out;
387
388 GetResourceListSync(¶ms);
389
390 // Box up params for output.
391 out.reserve(params.size());
392 for (auto& p : params) {
393 out.emplace_back(base::in_place, std::move(p));
394 }
395 std::move(callback).Run(std::move(out));
396 }
397
CreateRequestHandler(std::unique_ptr<AppCacheRequest> request,blink::mojom::ResourceType resource_type,bool should_reset_appcache)398 std::unique_ptr<AppCacheRequestHandler> AppCacheHost::CreateRequestHandler(
399 std::unique_ptr<AppCacheRequest> request,
400 blink::mojom::ResourceType resource_type,
401 bool should_reset_appcache) {
402 if (AppCacheRequestHandler::IsMainResourceType(resource_type)) {
403 // Store the first party origin so that it can be used later in SelectCache
404 // for checking whether the creation of the appcache is allowed.
405 site_for_cookies_ = request->GetSiteForCookies();
406 site_for_cookies_initialized_ = true;
407 return base::WrapUnique(new AppCacheRequestHandler(
408 this, resource_type, should_reset_appcache, std::move(request)));
409 }
410
411 if ((associated_cache() && associated_cache()->is_complete()) ||
412 is_selection_pending()) {
413 return base::WrapUnique(new AppCacheRequestHandler(
414 this, resource_type, should_reset_appcache, std::move(request)));
415 }
416 return nullptr;
417 }
418
GetResourceListSync(std::vector<blink::mojom::AppCacheResourceInfo> * resource_infos)419 void AppCacheHost::GetResourceListSync(
420 std::vector<blink::mojom::AppCacheResourceInfo>* resource_infos) {
421 if (associated_cache_.get() && associated_cache_->is_complete())
422 associated_cache_->ToResourceInfoVector(resource_infos);
423 }
424
GetStatusSync()425 blink::mojom::AppCacheStatus AppCacheHost::GetStatusSync() {
426 // 6.9.8 Application cache API
427 AppCache* cache = associated_cache();
428 if (!cache)
429 return blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED;
430
431 // A cache without an owning group represents the cache being constructed
432 // during the application cache update process.
433 if (!cache->owning_group())
434 return blink::mojom::AppCacheStatus::APPCACHE_STATUS_DOWNLOADING;
435
436 if (cache->owning_group()->is_obsolete())
437 return blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE;
438 if (cache->owning_group()->update_status() == AppCacheGroup::CHECKING)
439 return blink::mojom::AppCacheStatus::APPCACHE_STATUS_CHECKING;
440 if (cache->owning_group()->update_status() == AppCacheGroup::DOWNLOADING)
441 return blink::mojom::AppCacheStatus::APPCACHE_STATUS_DOWNLOADING;
442 if (swappable_cache_.get())
443 return blink::mojom::AppCacheStatus::APPCACHE_STATUS_UPDATE_READY;
444 return blink::mojom::AppCacheStatus::APPCACHE_STATUS_IDLE;
445 }
446
LoadOrCreateGroup(const GURL & manifest_url)447 void AppCacheHost::LoadOrCreateGroup(const GURL& manifest_url) {
448 DCHECK(manifest_url.is_valid());
449 pending_selected_manifest_url_ = manifest_url;
450 storage()->LoadOrCreateGroup(manifest_url, this);
451 }
452
OnGroupLoaded(AppCacheGroup * group,const GURL & manifest_url)453 void AppCacheHost::OnGroupLoaded(AppCacheGroup* group,
454 const GURL& manifest_url) {
455 DCHECK(manifest_url == pending_selected_manifest_url_);
456 pending_selected_manifest_url_ = GURL();
457 FinishCacheSelection(nullptr, group, mojo::ReportBadMessageCallback());
458 }
459
LoadSelectedCache(int64_t cache_id)460 void AppCacheHost::LoadSelectedCache(int64_t cache_id) {
461 DCHECK(cache_id != blink::mojom::kAppCacheNoCacheId);
462 pending_selected_cache_id_ = cache_id;
463 pending_selected_cache_bad_message_callback_ = mojo::GetBadMessageCallback();
464 storage()->LoadCache(cache_id, this);
465 }
466
OnCacheLoaded(AppCache * cache,int64_t cache_id)467 void AppCacheHost::OnCacheLoaded(AppCache* cache, int64_t cache_id) {
468 if (cache_id == pending_main_resource_cache_id_) {
469 pending_main_resource_cache_id_ = blink::mojom::kAppCacheNoCacheId;
470 main_resource_cache_ = cache;
471 } else if (cache_id == pending_selected_cache_id_) {
472 pending_selected_cache_id_ = blink::mojom::kAppCacheNoCacheId;
473 FinishCacheSelection(
474 cache, nullptr,
475 std::move(pending_selected_cache_bad_message_callback_));
476 }
477 }
478
FinishCacheSelection(AppCache * cache,AppCacheGroup * group,mojo::ReportBadMessageCallback bad_message_callback)479 void AppCacheHost::FinishCacheSelection(
480 AppCache* cache,
481 AppCacheGroup* group,
482 mojo::ReportBadMessageCallback bad_message_callback) {
483 DCHECK(!associated_cache());
484
485 // 7.9.5 The application cache selection algorithm
486 if (cache) {
487 // If document was loaded from an application cache, Associate document
488 // with the application cache from which it was loaded. Invoke the
489 // application cache update process for that cache and with the browsing
490 // context being navigated.
491 DCHECK(new_master_entry_url_.is_empty());
492 DCHECK(bad_message_callback);
493 if (!cache->owning_group()) {
494 std::move(bad_message_callback).Run("ACH_SELECT_CACHE_ID_NOT_OWNED");
495 return;
496 }
497 if (cache->owning_group()->manifest_url() != preferred_manifest_url_) {
498 std::move(bad_message_callback).Run("ACH_SELECT_CACHE_BAD_MANIFEST_URL");
499 return;
500 }
501 AppCacheGroup* owning_group = cache->owning_group();
502 const char* kFormatString =
503 "Document was loaded from Application Cache with manifest %s";
504 frontend()->LogMessage(
505 blink::mojom::ConsoleMessageLevel::kInfo,
506 base::StringPrintf(kFormatString,
507 owning_group->manifest_url().spec().c_str()));
508 AssociateCompleteCache(cache);
509 if (!owning_group->is_obsolete() && !owning_group->is_being_deleted()) {
510 owning_group->StartUpdateWithHost(this);
511 ObserveGroupBeingUpdated(owning_group);
512 }
513 } else if (group && !group->is_being_deleted()) {
514 // If document was loaded using HTTP GET or equivalent, and, there is a
515 // manifest URL, and manifest URL has the same origin as document.
516 // Invoke the application cache update process for manifest URL, with
517 // the browsing context being navigated, and with document and the
518 // resource from which document was loaded as the new master resourse.
519 DCHECK(!group->is_obsolete());
520 DCHECK(new_master_entry_url_.is_valid());
521 DCHECK_EQ(group->manifest_url(), preferred_manifest_url_);
522 const char* kFormatString =
523 group->HasCache()
524 ? "Adding master entry to Application Cache with manifest %s"
525 : "Creating Application Cache with manifest %s";
526 frontend()->LogMessage(
527 blink::mojom::ConsoleMessageLevel::kInfo,
528 base::StringPrintf(kFormatString,
529 group->manifest_url().spec().c_str()));
530 // The UpdateJob may produce one for us later.
531 AssociateNoCache(preferred_manifest_url_);
532 group->StartUpdateWithNewMasterEntry(this, new_master_entry_url_);
533 ObserveGroupBeingUpdated(group);
534 } else {
535 // Otherwise, the Document is not associated with any application cache.
536 new_master_entry_url_ = GURL();
537 AssociateNoCache(GURL());
538 }
539
540 // Respond to pending callbacks now that we have a selection.
541 if (!pending_get_status_callback_.is_null())
542 DoPendingGetStatus();
543 else if (!pending_start_update_callback_.is_null())
544 DoPendingStartUpdate();
545 else if (!pending_swap_cache_callback_.is_null())
546 DoPendingSwapCache();
547
548 for (auto& observer : observers_)
549 observer.OnCacheSelectionComplete(this);
550 }
551
OnServiceReinitialized(AppCacheStorageReference * old_storage_ref)552 void AppCacheHost::OnServiceReinitialized(
553 AppCacheStorageReference* old_storage_ref) {
554 // We continue to use the disabled instance, but arrange for its
555 // deletion when its no longer needed.
556 if (old_storage_ref->storage() == storage())
557 disabled_storage_reference_ = old_storage_ref;
558 }
559
ObserveGroupBeingUpdated(AppCacheGroup * group)560 void AppCacheHost::ObserveGroupBeingUpdated(AppCacheGroup* group) {
561 DCHECK(!group_being_updated_.get());
562 group_being_updated_ = group;
563 newest_cache_of_group_being_updated_ = group->newest_complete_cache();
564 group->AddUpdateObserver(this);
565 }
566
OnUpdateComplete(AppCacheGroup * group)567 void AppCacheHost::OnUpdateComplete(AppCacheGroup* group) {
568 DCHECK_EQ(group, group_being_updated_.get());
569 group->RemoveUpdateObserver(this);
570
571 // Add a reference to the newest complete cache.
572 SetSwappableCache(group);
573
574 group_being_updated_ = nullptr;
575 newest_cache_of_group_being_updated_ = nullptr;
576
577 if (associated_cache_info_pending_ && associated_cache_.get() &&
578 associated_cache_->is_complete()) {
579 blink::mojom::AppCacheInfoPtr info = CreateCacheInfo(
580 associated_cache_.get(), preferred_manifest_url_, GetStatusSync());
581 associated_cache_info_pending_ = false;
582 // In the network service world, we need to pass the URLLoaderFactory
583 // instance to the renderer which it can use to request subresources.
584 // This ensures that they can be served out of the AppCache.
585 MaybePassSubresourceFactory();
586 OnAppCacheAccessed(info->manifest_url, false);
587 frontend()->CacheSelected(std::move(info));
588 }
589 }
590
SetSwappableCache(AppCacheGroup * group)591 void AppCacheHost::SetSwappableCache(AppCacheGroup* group) {
592 if (!group) {
593 swappable_cache_ = nullptr;
594 } else {
595 AppCache* new_cache = group->newest_complete_cache();
596 if (new_cache != associated_cache_.get())
597 swappable_cache_ = new_cache;
598 else
599 swappable_cache_ = nullptr;
600 }
601 }
602
LoadMainResourceCache(int64_t cache_id)603 void AppCacheHost::LoadMainResourceCache(int64_t cache_id) {
604 DCHECK(cache_id != blink::mojom::kAppCacheNoCacheId);
605 if (pending_main_resource_cache_id_ == cache_id ||
606 (main_resource_cache_.get() &&
607 main_resource_cache_->cache_id() == cache_id)) {
608 return;
609 }
610 pending_main_resource_cache_id_ = cache_id;
611 storage()->LoadCache(cache_id, this);
612 }
613
NotifyMainResourceIsNamespaceEntry(const GURL & namespace_entry_url)614 void AppCacheHost::NotifyMainResourceIsNamespaceEntry(
615 const GURL& namespace_entry_url) {
616 main_resource_was_namespace_entry_ = true;
617 namespace_entry_url_ = namespace_entry_url;
618 }
619
NotifyMainResourceBlocked(const GURL & manifest_url)620 void AppCacheHost::NotifyMainResourceBlocked(const GURL& manifest_url) {
621 main_resource_blocked_ = true;
622 blocked_manifest_url_ = manifest_url;
623 }
624
SetProcessId(int process_id)625 void AppCacheHost::SetProcessId(int process_id) {
626 DCHECK_EQ(process_id_, ChildProcessHost::kInvalidUniqueID);
627 DCHECK(!security_policy_handle_.is_valid());
628 DCHECK_NE(process_id, ChildProcessHost::kInvalidUniqueID);
629 process_id_ = process_id;
630 security_policy_handle_ =
631 ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(process_id_);
632 }
633
GetWeakPtr()634 base::WeakPtr<AppCacheHost> AppCacheHost::GetWeakPtr() {
635 return weak_factory_.GetWeakPtr();
636 }
637
MaybePassSubresourceFactory()638 void AppCacheHost::MaybePassSubresourceFactory() {
639 // We already have a valid factory. This happens when the document was loaded
640 // from the AppCache during navigation.
641 if (subresource_url_factory_.get())
642 return;
643
644 mojo::PendingRemote<network::mojom::URLLoaderFactory> factory_remote;
645 auto factory_receiver = factory_remote.InitWithNewPipeAndPassReceiver();
646 auto* rfh = RenderFrameHost::FromID(process_id_, render_frame_id_);
647 if (rfh) {
648 GetContentClient()->browser()->WillCreateURLLoaderFactory(
649 rfh->GetProcess()->GetBrowserContext(), rfh, process_id_,
650 ContentBrowserClient::URLLoaderFactoryType::kDocumentSubResource,
651 origin_for_url_loader_factory_, base::nullopt /* navigation_id */,
652 &factory_receiver, nullptr /* header_client */,
653 nullptr /* bypass_redirect_checks */, nullptr /* disable_secure_dns */,
654 nullptr /* factory_override */);
655 }
656
657 // We may not have bound |factory_remote| if the storage partition has shut
658 // down.
659 if (AppCacheSubresourceURLFactory::CreateURLLoaderFactory(
660 GetWeakPtr(), std::move(factory_receiver))) {
661 frontend()->SetSubresourceFactory(std::move(factory_remote));
662 }
663 }
664
SetAppCacheSubresourceFactory(AppCacheSubresourceURLFactory * subresource_factory)665 void AppCacheHost::SetAppCacheSubresourceFactory(
666 AppCacheSubresourceURLFactory* subresource_factory) {
667 subresource_url_factory_ = subresource_factory->GetWeakPtr();
668 }
669
AssociateNoCache(const GURL & manifest_url)670 void AppCacheHost::AssociateNoCache(const GURL& manifest_url) {
671 // manifest url can be empty.
672 AssociateCacheHelper(nullptr, manifest_url);
673 }
674
AssociateIncompleteCache(AppCache * cache,const GURL & manifest_url)675 void AppCacheHost::AssociateIncompleteCache(AppCache* cache,
676 const GURL& manifest_url) {
677 DCHECK(cache && !cache->is_complete());
678 DCHECK(!manifest_url.is_empty());
679 AssociateCacheHelper(cache, manifest_url);
680 }
681
AssociateCompleteCache(AppCache * cache)682 void AppCacheHost::AssociateCompleteCache(AppCache* cache) {
683 DCHECK(cache && cache->is_complete());
684 AssociateCacheHelper(cache, cache->owning_group()->manifest_url());
685 }
686
AssociateCacheHelper(AppCache * cache,const GURL & manifest_url)687 void AppCacheHost::AssociateCacheHelper(AppCache* cache,
688 const GURL& manifest_url) {
689 if (associated_cache_.get()) {
690 associated_cache_->UnassociateHost(this);
691 }
692
693 associated_cache_ = cache;
694 SetSwappableCache(cache ? cache->owning_group() : nullptr);
695 associated_cache_info_pending_ = cache && !cache->is_complete();
696 if (cache)
697 cache->AssociateHost(this);
698
699 blink::mojom::AppCacheInfoPtr info =
700 CreateCacheInfo(cache, manifest_url, GetStatusSync());
701 // In the network service world, we need to pass the URLLoaderFactory
702 // instance to the renderer which it can use to request subresources.
703 // This ensures that they can be served out of the AppCache.
704 if (cache && cache->is_complete())
705 MaybePassSubresourceFactory();
706
707 OnAppCacheAccessed(info->manifest_url, false);
708 frontend()->CacheSelected(std::move(info));
709 }
710
OnContentBlocked(const GURL & manifest_url)711 void AppCacheHost::OnContentBlocked(const GURL& manifest_url) {
712 OnAppCacheAccessed(manifest_url, /*blocked=*/true);
713 }
714
OnAppCacheAccessed(const GURL & manifest_url,bool blocked)715 void AppCacheHost::OnAppCacheAccessed(const GURL& manifest_url, bool blocked) {
716 if (!blocked && manifest_url.is_empty())
717 return;
718
719 // Unit tests might not have a UI thread, if that's the case just don't bother
720 // informing WebContents about this access.
721 if (render_frame_id_ != MSG_ROUTING_NONE &&
722 BrowserThread::IsThreadInitialized(BrowserThread::UI)) {
723 base::PostTask(
724 FROM_HERE, {BrowserThread::UI},
725 base::BindOnce(
726 [](int process_id, int render_frame_id, const GURL& manifest_url,
727 bool blocked) {
728 WebContents* web_contents =
729 WebContentsImpl::FromRenderFrameHostID(process_id,
730 render_frame_id);
731 if (web_contents) {
732 static_cast<WebContentsImpl*>(web_contents)
733 ->OnAppCacheAccessed(manifest_url, blocked);
734 }
735 },
736 process_id_, render_frame_id_, manifest_url, blocked));
737 }
738 }
739
740 } // namespace content
741