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(&params);
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