1/*
2 * Copyright (C) 2008-2017 Apple Inc. All Rights Reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "ApplicationCacheGroup.h"
28
29#include "ApplicationCache.h"
30#include "ApplicationCacheHost.h"
31#include "ApplicationCacheResource.h"
32#include "ApplicationCacheResourceLoader.h"
33#include "ApplicationCacheStorage.h"
34#include "Chrome.h"
35#include "ChromeClient.h"
36#include "DOMApplicationCache.h"
37#include "DocumentLoader.h"
38#include "EventNames.h"
39#include "Frame.h"
40#include "FrameLoader.h"
41#include "FrameLoaderClient.h"
42#include "HTTPHeaderNames.h"
43#include "InspectorInstrumentation.h"
44#include "ManifestParser.h"
45#include "NavigationScheduler.h"
46#include "NetworkLoadMetrics.h"
47#include "Page.h"
48#include "ProgressTracker.h"
49#include "SecurityOrigin.h"
50#include "Settings.h"
51#include <wtf/CompletionHandler.h>
52#include <wtf/HashMap.h>
53#include <wtf/MainThread.h>
54
55namespace WebCore {
56
57ApplicationCacheGroup::ApplicationCacheGroup(Ref<ApplicationCacheStorage>&& storage, const URL& manifestURL)
58 : m_storage(WTFMove(storage))
59 , m_manifestURL(manifestURL)
60 , m_origin(SecurityOrigin::create(manifestURL))
61 , m_availableSpaceInQuota(ApplicationCacheStorage::unknownQuota())
62{
63}
64
65ApplicationCacheGroup::~ApplicationCacheGroup()
66{
67 ASSERT(!m_newestCache);
68 ASSERT(m_caches.isEmpty());
69
70 stopLoading();
71
72 m_storage->cacheGroupDestroyed(*this);
73}
74
75ApplicationCache* ApplicationCacheGroup::cacheForMainRequest(const ResourceRequest& request, DocumentLoader* documentLoader)
76{
77 if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
78 return nullptr;
79
80 URL url(request.url());
81 url.removeFragmentIdentifier();
82
83 auto* page = documentLoader->frame() ? documentLoader->frame()->page() : nullptr;
84 if (!page || page->usesEphemeralSession())
85 return nullptr;
86
87 auto* group = page->applicationCacheStorage().cacheGroupForURL(url);
88 if (!group)
89 return nullptr;
90
91 ASSERT(group->newestCache());
92 ASSERT(!group->isObsolete());
93
94 return group->newestCache();
95}
96
97ApplicationCache* ApplicationCacheGroup::fallbackCacheForMainRequest(const ResourceRequest& request, DocumentLoader* documentLoader)
98{
99 if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
100 return nullptr;
101
102 auto* frame = documentLoader->frame();
103 if (!frame)
104 return nullptr;
105
106 auto* page = frame->page();
107 if (!page)
108 return nullptr;
109
110 URL url(request.url());
111 url.removeFragmentIdentifier();
112
113 auto* group = page->applicationCacheStorage().fallbackCacheGroupForURL(url);
114 if (!group)
115 return nullptr;
116
117 ASSERT(group->newestCache());
118 ASSERT(!group->isObsolete());
119
120 return group->newestCache();
121}
122
123void ApplicationCacheGroup::selectCache(Frame& frame, const URL& passedManifestURL)
124{
125 ASSERT(frame.document());
126 ASSERT(frame.page());
127 ASSERT(frame.loader().documentLoader());
128
129 if (!frame.settings().offlineWebApplicationCacheEnabled())
130 return;
131
132 auto& documentLoader = *frame.loader().documentLoader();
133 ASSERT(!documentLoader.applicationCacheHost().applicationCache());
134
135 if (passedManifestURL.isNull()) {
136 selectCacheWithoutManifestURL(frame);
137 return;
138 }
139
140 // Don't access anything on disk if private browsing is enabled.
141 if (frame.page()->usesEphemeralSession() || !frame.document()->securityOrigin().canAccessApplicationCache(frame.tree().top().document()->securityOrigin())) {
142 postListenerTask(eventNames().checkingEvent, documentLoader);
143 postListenerTask(eventNames().errorEvent, documentLoader);
144 return;
145 }
146
147 URL manifestURL(passedManifestURL);
148 manifestURL.removeFragmentIdentifier();
149
150 auto* mainResourceCache = documentLoader.applicationCacheHost().mainResourceApplicationCache();
151
152 if (mainResourceCache) {
153 ASSERT(mainResourceCache->group());
154 if (manifestURL == mainResourceCache->group()->m_manifestURL) {
155 // The cache may have gotten obsoleted after we've loaded from it, but before we parsed the document and saw cache manifest.
156 if (mainResourceCache->group()->isObsolete())
157 return;
158 mainResourceCache->group()->associateDocumentLoaderWithCache(&documentLoader, mainResourceCache);
159 mainResourceCache->group()->update(frame, ApplicationCacheUpdateWithBrowsingContext);
160 } else {
161 // The main resource was loaded from cache, so the cache must have an entry for it. Mark it as foreign.
162 URL resourceURL { documentLoader.responseURL() };
163 resourceURL.removeFragmentIdentifier();
164
165 ASSERT(mainResourceCache->resourceForURL(resourceURL));
166 auto& resource = *mainResourceCache->resourceForURL(resourceURL);
167
168 bool inStorage = resource.storageID();
169 resource.addType(ApplicationCacheResource::Foreign);
170 if (inStorage)
171 frame.page()->applicationCacheStorage().storeUpdatedType(&resource, mainResourceCache);
172
173 // Restart the current navigation from the top of the navigation algorithm, undoing any changes that were made
174 // as part of the initial load.
175 // The navigation will not result in the same resource being loaded, because "foreign" entries are never picked during navigation.
176 frame.navigationScheduler().scheduleLocationChange(*frame.document(), frame.document()->securityOrigin(), documentLoader.url(), frame.loader().referrer());
177 }
178 return;
179 }
180
181 // The resource was loaded from the network, check if it is a HTTP/HTTPS GET.
182 auto& request = frame.loader().activeDocumentLoader()->request();
183
184 if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
185 return;
186
187 // Check that the resource URL has the same scheme/host/port as the manifest URL.
188 if (!protocolHostAndPortAreEqual(manifestURL, request.url()))
189 return;
190
191 auto& group = *frame.page()->applicationCacheStorage().findOrCreateCacheGroup(manifestURL);
192
193 documentLoader.applicationCacheHost().setCandidateApplicationCacheGroup(&group);
194 group.m_pendingMasterResourceLoaders.add(&documentLoader);
195 group.m_downloadingPendingMasterResourceLoadersCount++;
196
197 ASSERT(!group.m_cacheBeingUpdated || group.m_updateStatus != Idle);
198 group.update(frame, ApplicationCacheUpdateWithBrowsingContext);
199}
200
201void ApplicationCacheGroup::selectCacheWithoutManifestURL(Frame& frame)
202{
203 if (!frame.settings().offlineWebApplicationCacheEnabled())
204 return;
205
206 ASSERT(frame.document());
207 ASSERT(frame.page());
208 ASSERT(frame.loader().documentLoader());
209 auto& documentLoader = *frame.loader().documentLoader();
210 ASSERT(!documentLoader.applicationCacheHost().applicationCache());
211
212 // Don't access anything on disk if private browsing is enabled.
213 if (frame.page()->usesEphemeralSession() || !frame.document()->securityOrigin().canAccessApplicationCache(frame.tree().top().document()->securityOrigin())) {
214 postListenerTask(eventNames().checkingEvent, documentLoader);
215 postListenerTask(eventNames().errorEvent, documentLoader);
216 return;
217 }
218
219 if (auto* mainResourceCache = documentLoader.applicationCacheHost().mainResourceApplicationCache()) {
220 ASSERT(mainResourceCache->group());
221 auto& group = *mainResourceCache->group();
222 group.associateDocumentLoaderWithCache(&documentLoader, mainResourceCache);
223 group.update(frame, ApplicationCacheUpdateWithBrowsingContext);
224 }
225}
226
227void ApplicationCacheGroup::finishedLoadingMainResource(DocumentLoader& loader)
228{
229 ASSERT(m_pendingMasterResourceLoaders.contains(&loader));
230 ASSERT(m_completionType == None || m_pendingEntries.isEmpty());
231 URL url = loader.url();
232 url.removeFragmentIdentifier();
233
234 switch (m_completionType) {
235 case None:
236 // The main resource finished loading before the manifest was ready. It will be handled via dispatchMainResources() later.
237 return;
238 case NoUpdate:
239 ASSERT(!m_cacheBeingUpdated);
240 associateDocumentLoaderWithCache(&loader, m_newestCache.get());
241 if (auto* resource = m_newestCache->resourceForURL(url)) {
242 if (!(resource->type() & ApplicationCacheResource::Master)) {
243 resource->addType(ApplicationCacheResource::Master);
244 ASSERT(!resource->storageID());
245 }
246 } else
247 m_newestCache->addResource(ApplicationCacheResource::create(url, loader.response(), ApplicationCacheResource::Master, loader.mainResourceData()));
248 break;
249 case Failure:
250 // Cache update has been a failure, so there is no reason to keep the document associated with the incomplete cache
251 // (its main resource was not cached yet, so it is likely that the application changed significantly server-side).
252 ASSERT(!m_cacheBeingUpdated); // Already cleared out by stopLoading().
253 loader.applicationCacheHost().setApplicationCache(nullptr); // Will unset candidate, too.
254 m_associatedDocumentLoaders.remove(&loader);
255 postListenerTask(eventNames().errorEvent, loader);
256 break;
257 case Completed:
258 ASSERT(m_associatedDocumentLoaders.contains(&loader));
259 if (auto* resource = m_cacheBeingUpdated->resourceForURL(url)) {
260 if (!(resource->type() & ApplicationCacheResource::Master)) {
261 resource->addType(ApplicationCacheResource::Master);
262 ASSERT(!resource->storageID());
263 }
264 } else
265 m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, loader.response(), ApplicationCacheResource::Master, loader.mainResourceData()));
266 // The "cached" event will be posted to all associated documents once update is complete.
267 break;
268 }
269
270 ASSERT(m_downloadingPendingMasterResourceLoadersCount > 0);
271 m_downloadingPendingMasterResourceLoadersCount--;
272 checkIfLoadIsComplete();
273}
274
275void ApplicationCacheGroup::failedLoadingMainResource(DocumentLoader& loader)
276{
277 ASSERT(m_pendingMasterResourceLoaders.contains(&loader));
278 ASSERT(m_completionType == None || m_pendingEntries.isEmpty());
279
280 switch (m_completionType) {
281 case None:
282 // The main resource finished loading before the manifest was ready. It will be handled via dispatchMainResources() later.
283 return;
284 case NoUpdate:
285 ASSERT(!m_cacheBeingUpdated);
286 // The manifest didn't change, and we have a relevant cache - but the main resource download failed mid-way, so it cannot be stored to the cache,
287 // and the loader does not get associated to it. If there are other main resources being downloaded for this cache group, they may still succeed.
288 postListenerTask(eventNames().errorEvent, loader);
289 break;
290 case Failure:
291 // Cache update failed, too.
292 ASSERT(!m_cacheBeingUpdated); // Already cleared out by stopLoading().
293 ASSERT(!loader.applicationCacheHost().applicationCache() || loader.applicationCacheHost().applicationCache()->group() == this);
294 loader.applicationCacheHost().setApplicationCache(nullptr); // Will unset candidate, too.
295 m_associatedDocumentLoaders.remove(&loader);
296 postListenerTask(eventNames().errorEvent, loader);
297 break;
298 case Completed:
299 // The cache manifest didn't list this main resource, and all cache entries were already updated successfully - but the main resource failed to load,
300 // so it cannot be stored to the cache. If there are other main resources being downloaded for this cache group, they may still succeed.
301 ASSERT(m_associatedDocumentLoaders.contains(&loader));
302 ASSERT(loader.applicationCacheHost().applicationCache() == m_cacheBeingUpdated);
303 ASSERT(!loader.applicationCacheHost().candidateApplicationCacheGroup());
304 m_associatedDocumentLoaders.remove(&loader);
305 loader.applicationCacheHost().setApplicationCache(nullptr);
306 postListenerTask(eventNames().errorEvent, loader);
307 break;
308 }
309
310 ASSERT(m_downloadingPendingMasterResourceLoadersCount > 0);
311 m_downloadingPendingMasterResourceLoadersCount--;
312 checkIfLoadIsComplete();
313}
314
315void ApplicationCacheGroup::stopLoading()
316{
317 if (m_manifestLoader) {
318 m_manifestLoader->cancel();
319 m_manifestLoader = nullptr;
320 }
321
322 if (m_entryLoader) {
323 m_entryLoader->cancel();
324 m_entryLoader = nullptr;
325 }
326
327 // FIXME: Resetting just a tiny part of the state in this function is confusing. Callers have to take care of a lot more.
328 m_cacheBeingUpdated = nullptr;
329 m_pendingEntries.clear();
330}
331
332void ApplicationCacheGroup::disassociateDocumentLoader(DocumentLoader& loader)
333{
334 m_associatedDocumentLoaders.remove(&loader);
335 m_pendingMasterResourceLoaders.remove(&loader);
336
337 if (auto* host = loader.applicationCacheHostUnlessBeingDestroyed())
338 host->setApplicationCache(nullptr); // Will set candidate group to null, too.
339
340 if (!m_associatedDocumentLoaders.isEmpty() || !m_pendingMasterResourceLoaders.isEmpty())
341 return;
342
343 if (m_caches.isEmpty()) {
344 // There is an initial cache attempt in progress.
345 ASSERT(!m_newestCache);
346 // Delete ourselves, causing the cache attempt to be stopped.
347 delete this;
348 return;
349 }
350
351 ASSERT(m_caches.contains(m_newestCache.get()));
352
353 // Release our reference to the newest cache. This could cause us to be deleted.
354 // Any ongoing updates will be stopped from destructor.
355 m_newestCache = nullptr;
356}
357
358void ApplicationCacheGroup::cacheDestroyed(ApplicationCache& cache)
359{
360 if (m_caches.remove(&cache) && m_caches.isEmpty()) {
361 ASSERT(m_associatedDocumentLoaders.isEmpty());
362 ASSERT(m_pendingMasterResourceLoaders.isEmpty());
363 delete this;
364 }
365}
366
367void ApplicationCacheGroup::stopLoadingInFrame(Frame& frame)
368{
369 if (&frame != m_frame)
370 return;
371
372 cacheUpdateFailed();
373}
374
375void ApplicationCacheGroup::setNewestCache(Ref<ApplicationCache>&& newestCache)
376{
377 m_newestCache = WTFMove(newestCache);
378
379 m_caches.add(m_newestCache.get());
380 m_newestCache->setGroup(this);
381}
382
383void ApplicationCacheGroup::makeObsolete()
384{
385 if (isObsolete())
386 return;
387
388 m_isObsolete = true;
389 m_storage->cacheGroupMadeObsolete(*this);
390 ASSERT(!m_storageID);
391}
392
393void ApplicationCacheGroup::update(Frame& frame, ApplicationCacheUpdateOption updateOption)
394{
395 ASSERT(frame.loader().documentLoader());
396 auto& documentLoader = *frame.loader().documentLoader();
397
398 if (m_updateStatus == Checking || m_updateStatus == Downloading) {
399 if (updateOption == ApplicationCacheUpdateWithBrowsingContext) {
400 postListenerTask(eventNames().checkingEvent, documentLoader);
401 if (m_updateStatus == Downloading)
402 postListenerTask(eventNames().downloadingEvent, documentLoader);
403 }
404 return;
405 }
406
407 // Don't access anything on disk if private browsing is enabled.
408 if (frame.page()->usesEphemeralSession() || !frame.document()->securityOrigin().canAccessApplicationCache(frame.tree().top().document()->securityOrigin())) {
409 ASSERT(m_pendingMasterResourceLoaders.isEmpty());
410 ASSERT(m_pendingEntries.isEmpty());
411 ASSERT(!m_cacheBeingUpdated);
412 postListenerTask(eventNames().checkingEvent, documentLoader);
413 postListenerTask(eventNames().errorEvent, documentLoader);
414 return;
415 }
416
417 ASSERT(!m_frame);
418 m_frame = &frame;
419
420 setUpdateStatus(Checking);
421
422 postListenerTask(eventNames().checkingEvent, m_associatedDocumentLoaders);
423 if (!m_newestCache) {
424 ASSERT(updateOption == ApplicationCacheUpdateWithBrowsingContext);
425 postListenerTask(eventNames().checkingEvent, documentLoader);
426 }
427
428 ASSERT(!m_manifestLoader);
429 ASSERT(!m_entryLoader);
430 ASSERT(!m_manifestResource);
431 ASSERT(!m_currentResource);
432 ASSERT(m_completionType == None);
433
434 // FIXME: Handle defer loading
435
436 auto request = createRequest(URL { m_manifestURL }, m_newestCache ? m_newestCache->manifestResource() : nullptr);
437
438 m_currentResourceIdentifier = m_frame->page()->progress().createUniqueIdentifier();
439 InspectorInstrumentation::willSendRequest(m_frame, m_currentResourceIdentifier, m_frame->loader().documentLoader(), request, ResourceResponse { });
440
441 m_manifestLoader = ApplicationCacheResourceLoader::create(ApplicationCacheResource::Type::Manifest, documentLoader.cachedResourceLoader(), WTFMove(request), [this] (auto&& resourceOrError) {
442 // 'this' is only valid if returned value is not Error::Abort.
443 if (!resourceOrError.has_value()) {
444 auto error = resourceOrError.error();
445 if (error == ApplicationCacheResourceLoader::Error::Abort)
446 return;
447 if (error == ApplicationCacheResourceLoader::Error::CannotCreateResource) {
448 // FIXME: We should get back the error from ApplicationCacheResourceLoader level.
449 InspectorInstrumentation::didFailLoading(m_frame, m_frame->loader().documentLoader(), m_currentResourceIdentifier, ResourceError { ResourceError::Type::AccessControl });
450 this->cacheUpdateFailed();
451 return;
452 }
453 this->didFailLoadingManifest(error);
454 return;
455 }
456
457 m_manifestResource = WTFMove(resourceOrError.value());
458 this->didFinishLoadingManifest();
459 });
460}
461
462ResourceRequest ApplicationCacheGroup::createRequest(URL&& url, ApplicationCacheResource* resource)
463{
464 ResourceRequest request { WTFMove(url) };
465 m_frame->loader().applyUserAgentIfNeeded(request);
466 request.setHTTPHeaderField(HTTPHeaderName::CacheControl, "max-age=0");
467
468 if (resource) {
469 const String& lastModified = resource->response().httpHeaderField(HTTPHeaderName::LastModified);
470 if (!lastModified.isEmpty())
471 request.setHTTPHeaderField(HTTPHeaderName::IfModifiedSince, lastModified);
472
473 const String& eTag = resource->response().httpHeaderField(HTTPHeaderName::ETag);
474 if (!eTag.isEmpty())
475 request.setHTTPHeaderField(HTTPHeaderName::IfNoneMatch, eTag);
476 }
477 return request;
478}
479
480void ApplicationCacheGroup::abort(Frame& frame)
481{
482 if (m_updateStatus == Idle)
483 return;
484 ASSERT(m_updateStatus == Checking || (m_updateStatus == Downloading && m_cacheBeingUpdated));
485
486 if (m_completionType != None)
487 return;
488
489 frame.document()->addConsoleMessage(MessageSource::AppCache, MessageLevel::Debug, "Application Cache download process was aborted."_s);
490 cacheUpdateFailed();
491}
492
493void ApplicationCacheGroup::didFinishLoadingEntry(const URL& entryURL)
494{
495 // FIXME: We should have NetworkLoadMetrics for ApplicationCache loads.
496 NetworkLoadMetrics emptyMetrics;
497 InspectorInstrumentation::didFinishLoading(m_frame, m_frame->loader().documentLoader(), m_currentResourceIdentifier, emptyMetrics, nullptr);
498
499 ASSERT(m_pendingEntries.contains(entryURL));
500
501 auto type = m_pendingEntries.take(entryURL);
502
503 ASSERT(m_cacheBeingUpdated);
504
505 // Did we received a 304?
506 if (!m_currentResource) {
507 if (m_newestCache) {
508 ApplicationCacheResource* newestCachedResource = m_newestCache->resourceForURL(entryURL);
509 if (newestCachedResource) {
510 m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(entryURL, newestCachedResource->response(), type, &newestCachedResource->data(), newestCachedResource->path()));
511 m_entryLoader = nullptr;
512 startLoadingEntry();
513 return;
514 }
515 }
516 // The server could return 304 for an unconditional request - in this case, we handle the response as a normal error.
517 m_entryLoader = nullptr;
518 startLoadingEntry();
519 return;
520 }
521
522 m_cacheBeingUpdated->addResource(m_currentResource.releaseNonNull());
523 m_entryLoader = nullptr;
524
525 // While downloading check to see if we have exceeded the available quota.
526 // We can stop immediately if we have already previously failed
527 // due to an earlier quota restriction. The client was already notified
528 // of the quota being reached and decided not to increase it then.
529 // FIXME: Should we break earlier and prevent redownloading on later page loads?
530 if (m_originQuotaExceededPreviously && m_availableSpaceInQuota < m_cacheBeingUpdated->estimatedSizeInStorage()) {
531 m_currentResource = nullptr;
532 m_frame->document()->addConsoleMessage(MessageSource::AppCache, MessageLevel::Error, "Application Cache update failed, because size quota was exceeded."_s);
533 cacheUpdateFailed();
534 return;
535 }
536
537 // Load the next resource, if any.
538 startLoadingEntry();
539}
540
541void ApplicationCacheGroup::didFailLoadingEntry(ApplicationCacheResourceLoader::Error error, const URL& entryURL, unsigned type)
542{
543 // FIXME: We should get back the error from ApplicationCacheResourceLoader level.
544 ResourceError resourceError { error == ApplicationCacheResourceLoader::Error::CannotCreateResource ? ResourceError::Type::AccessControl : ResourceError::Type::General };
545
546 InspectorInstrumentation::didFailLoading(m_frame, m_frame->loader().documentLoader(), m_currentResourceIdentifier, resourceError);
547
548 URL url(entryURL);
549 url.removeFragmentIdentifier();
550
551 ASSERT(!m_currentResource || !m_pendingEntries.contains(url));
552 m_currentResource = nullptr;
553 m_pendingEntries.remove(url);
554
555 if ((type & ApplicationCacheResource::Explicit) || (type & ApplicationCacheResource::Fallback)) {
556 m_frame->document()->addConsoleMessage(MessageSource::AppCache, MessageLevel::Error, makeString("Application Cache update failed, because ", url.stringCenterEllipsizedToLength(), (m_entryLoader && m_entryLoader->hasRedirection() ? " was redirected." : " could not be fetched.")));
557 // Note that cacheUpdateFailed() can cause the cache group to be deleted.
558 cacheUpdateFailed();
559 return;
560 }
561
562 if (error == ApplicationCacheResourceLoader::Error::NotFound) {
563 // Skip this resource. It is dropped from the cache.
564 m_pendingEntries.remove(url);
565 startLoadingEntry();
566 return;
567 }
568
569 // Copy the resource and its metadata from the newest application cache in cache group whose completeness flag is complete, and act
570 // as if that was the fetched resource, ignoring the resource obtained from the network.
571 ASSERT(m_newestCache);
572 ApplicationCacheResource* newestCachedResource = m_newestCache->resourceForURL(url);
573 ASSERT(newestCachedResource);
574 m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, newestCachedResource->response(), type, &newestCachedResource->data(), newestCachedResource->path()));
575 // Load the next resource, if any.
576 startLoadingEntry();
577}
578
579void ApplicationCacheGroup::didFinishLoadingManifest()
580{
581 bool isUpgradeAttempt = m_newestCache;
582
583 if (!isUpgradeAttempt && !m_manifestResource) {
584 // The server returned 304 Not Modified even though we didn't send a conditional request.
585 m_frame->document()->addConsoleMessage(MessageSource::AppCache, MessageLevel::Error, "Application Cache manifest could not be fetched because of an unexpected 304 Not Modified server response."_s);
586 cacheUpdateFailed();
587 return;
588 }
589
590 m_manifestLoader = nullptr;
591
592 // Check if the manifest was not modified.
593 if (isUpgradeAttempt) {
594 ApplicationCacheResource* newestManifest = m_newestCache->manifestResource();
595 ASSERT(newestManifest);
596
597 if (!m_manifestResource || // The resource will be null if HTTP response was 304 Not Modified.
598 (newestManifest->data().size() == m_manifestResource->data().size() && !memcmp(newestManifest->data().data(), m_manifestResource->data().data(), newestManifest->data().size()))) {
599
600 m_completionType = NoUpdate;
601 m_manifestResource = nullptr;
602 deliverDelayedMainResources();
603
604 return;
605 }
606 }
607
608 Manifest manifest;
609 if (!parseManifest(m_manifestURL, m_manifestResource->response().mimeType(), m_manifestResource->data().data(), m_manifestResource->data().size(), manifest)) {
610 // At the time of this writing, lack of "CACHE MANIFEST" signature is the only reason for parseManifest to fail.
611 m_frame->document()->addConsoleMessage(MessageSource::AppCache, MessageLevel::Error, "Application Cache manifest could not be parsed. Does it start with CACHE MANIFEST?"_s);
612 cacheUpdateFailed();
613 return;
614 }
615
616 ASSERT(!m_cacheBeingUpdated);
617 m_cacheBeingUpdated = ApplicationCache::create();
618 m_cacheBeingUpdated->setGroup(this);
619
620 for (auto& loader : m_pendingMasterResourceLoaders)
621 associateDocumentLoaderWithCache(loader, m_cacheBeingUpdated.get());
622
623 // We have the manifest, now download the resources.
624 setUpdateStatus(Downloading);
625
626 postListenerTask(eventNames().downloadingEvent, m_associatedDocumentLoaders);
627
628 ASSERT(m_pendingEntries.isEmpty());
629
630 if (isUpgradeAttempt) {
631 for (const auto& urlAndResource : m_newestCache->resources()) {
632 unsigned type = urlAndResource.value->type();
633 if (type & ApplicationCacheResource::Master)
634 addEntry(urlAndResource.key, type);
635 }
636 }
637
638 for (const auto& explicitURL : manifest.explicitURLs)
639 addEntry(explicitURL, ApplicationCacheResource::Explicit);
640
641 for (auto& fallbackURL : manifest.fallbackURLs)
642 addEntry(fallbackURL.second, ApplicationCacheResource::Fallback);
643
644 m_cacheBeingUpdated->setOnlineWhitelist(manifest.onlineWhitelistedURLs);
645 m_cacheBeingUpdated->setFallbackURLs(manifest.fallbackURLs);
646 m_cacheBeingUpdated->setAllowsAllNetworkRequests(manifest.allowAllNetworkRequests);
647
648 m_progressTotal = m_pendingEntries.size();
649 m_progressDone = 0;
650
651 recalculateAvailableSpaceInQuota();
652
653 startLoadingEntry();
654}
655
656void ApplicationCacheGroup::didFailLoadingManifest(ApplicationCacheResourceLoader::Error error)
657{
658 ASSERT(error != ApplicationCacheResourceLoader::Error::Abort && error != ApplicationCacheResourceLoader::Error::CannotCreateResource);
659
660 InspectorInstrumentation::didReceiveResourceResponse(*m_frame, m_currentResourceIdentifier, m_frame->loader().documentLoader(), m_manifestLoader->resource()->response(), nullptr);
661 switch (error) {
662 case ApplicationCacheResourceLoader::Error::NetworkError:
663 cacheUpdateFailed();
664 break;
665 case ApplicationCacheResourceLoader::Error::NotFound:
666 InspectorInstrumentation::didFailLoading(m_frame, m_frame->loader().documentLoader(), m_currentResourceIdentifier, m_frame->loader().cancelledError(m_manifestLoader->resource()->resourceRequest()));
667 m_frame->document()->addConsoleMessage(MessageSource::AppCache, MessageLevel::Error, makeString("Application Cache manifest could not be fetched, because the manifest had a ", m_manifestLoader->resource()->response().httpStatusCode(), " response."));
668 manifestNotFound();
669 break;
670 case ApplicationCacheResourceLoader::Error::NotOK:
671 InspectorInstrumentation::didFailLoading(m_frame, m_frame->loader().documentLoader(), m_currentResourceIdentifier, m_frame->loader().cancelledError(m_manifestLoader->resource()->resourceRequest()));
672 m_frame->document()->addConsoleMessage(MessageSource::AppCache, MessageLevel::Error, makeString("Application Cache manifest could not be fetched, because the manifest had a ", m_manifestLoader->resource()->response().httpStatusCode(), " response."));
673 cacheUpdateFailed();
674 break;
675 case ApplicationCacheResourceLoader::Error::RedirectForbidden:
676 InspectorInstrumentation::didFailLoading(m_frame, m_frame->loader().documentLoader(), m_currentResourceIdentifier, m_frame->loader().cancelledError(m_manifestLoader->resource()->resourceRequest()));
677 m_frame->document()->addConsoleMessage(MessageSource::AppCache, MessageLevel::Error, "Application Cache manifest could not be fetched, because a redirection was attempted."_s);
678 cacheUpdateFailed();
679 break;
680 case ApplicationCacheResourceLoader::Error::CannotCreateResource:
681 case ApplicationCacheResourceLoader::Error::Abort:
682 break;
683 }
684}
685
686void ApplicationCacheGroup::didReachMaxAppCacheSize()
687{
688 ASSERT(m_frame);
689 ASSERT(m_cacheBeingUpdated);
690 m_frame->page()->chrome().client().reachedMaxAppCacheSize(m_frame->page()->applicationCacheStorage().spaceNeeded(m_cacheBeingUpdated->estimatedSizeInStorage()));
691 m_calledReachedMaxAppCacheSize = true;
692 checkIfLoadIsComplete();
693}
694
695void ApplicationCacheGroup::didReachOriginQuota(int64_t totalSpaceNeeded)
696{
697 // Inform the client the origin quota has been reached, they may decide to increase the quota.
698 // We expect quota to be increased synchronously while waiting for the call to return.
699 m_frame->page()->chrome().client().reachedApplicationCacheOriginQuota(m_origin.get(), totalSpaceNeeded);
700}
701
702void ApplicationCacheGroup::cacheUpdateFailed()
703{
704 stopLoading();
705 m_manifestResource = nullptr;
706
707 // Wait for master resource loads to finish.
708 m_completionType = Failure;
709 deliverDelayedMainResources();
710}
711
712void ApplicationCacheGroup::recalculateAvailableSpaceInQuota()
713{
714 if (!m_frame->page()->applicationCacheStorage().calculateRemainingSizeForOriginExcludingCache(m_origin, m_newestCache.get(), m_availableSpaceInQuota)) {
715 // Failed to determine what is left in the quota. Fallback to allowing anything.
716 m_availableSpaceInQuota = ApplicationCacheStorage::noQuota();
717 }
718}
719
720void ApplicationCacheGroup::manifestNotFound()
721{
722 makeObsolete();
723
724 postListenerTask(eventNames().obsoleteEvent, m_associatedDocumentLoaders);
725 postListenerTask(eventNames().errorEvent, m_pendingMasterResourceLoaders);
726
727 stopLoading();
728
729 ASSERT(m_pendingEntries.isEmpty());
730 m_manifestResource = nullptr;
731
732 while (!m_pendingMasterResourceLoaders.isEmpty()) {
733 HashSet<DocumentLoader*>::iterator it = m_pendingMasterResourceLoaders.begin();
734
735 ASSERT((*it)->applicationCacheHost().candidateApplicationCacheGroup() == this);
736 ASSERT(!(*it)->applicationCacheHost().applicationCache());
737 (*it)->applicationCacheHost().setCandidateApplicationCacheGroup(nullptr);
738 m_pendingMasterResourceLoaders.remove(it);
739 }
740
741 m_downloadingPendingMasterResourceLoadersCount = 0;
742 setUpdateStatus(Idle);
743 m_frame = nullptr;
744
745 if (m_caches.isEmpty()) {
746 ASSERT(m_associatedDocumentLoaders.isEmpty());
747 ASSERT(!m_cacheBeingUpdated);
748 delete this;
749 }
750}
751
752void ApplicationCacheGroup::checkIfLoadIsComplete()
753{
754 if (m_manifestLoader || m_entryLoader || !m_pendingEntries.isEmpty() || m_downloadingPendingMasterResourceLoadersCount)
755 return;
756
757 // We're done, all resources have finished downloading (successfully or not).
758
759 bool isUpgradeAttempt = m_newestCache;
760
761 switch (m_completionType) {
762 case None:
763 ASSERT_NOT_REACHED();
764 return;
765 case NoUpdate:
766 ASSERT(isUpgradeAttempt);
767 ASSERT(!m_cacheBeingUpdated);
768
769 // The storage could have been manually emptied by the user.
770 if (!m_storageID)
771 m_storage->storeNewestCache(*this);
772
773 postListenerTask(eventNames().noupdateEvent, m_associatedDocumentLoaders);
774 break;
775 case Failure:
776 ASSERT(!m_cacheBeingUpdated);
777 postListenerTask(eventNames().errorEvent, m_associatedDocumentLoaders);
778 if (m_caches.isEmpty()) {
779 ASSERT(m_associatedDocumentLoaders.isEmpty());
780 delete this;
781 return;
782 }
783 break;
784 case Completed: {
785 // FIXME: Fetch the resource from manifest URL again, and check whether it is identical to the one used for update (in case the application was upgraded server-side in the meanwhile). (<rdar://problem/6467625>)
786
787 ASSERT(m_cacheBeingUpdated);
788 if (m_manifestResource)
789 m_cacheBeingUpdated->setManifestResource(m_manifestResource.releaseNonNull());
790 else {
791 // We can get here as a result of retrying the Complete step, following
792 // a failure of the cache storage to save the newest cache due to hitting
793 // the maximum size. In such a case, m_manifestResource may be 0, as
794 // the manifest was already set on the newest cache object.
795 ASSERT(m_cacheBeingUpdated->manifestResource());
796 ASSERT(m_storage->isMaximumSizeReached());
797 ASSERT(m_calledReachedMaxAppCacheSize);
798 }
799
800 RefPtr<ApplicationCache> oldNewestCache = (m_newestCache == m_cacheBeingUpdated) ? RefPtr<ApplicationCache>() : m_newestCache;
801
802 // If we exceeded the origin quota while downloading we can request a quota
803 // increase now, before we attempt to store the cache.
804 int64_t totalSpaceNeeded;
805 if (!m_storage->checkOriginQuota(this, oldNewestCache.get(), m_cacheBeingUpdated.get(), totalSpaceNeeded))
806 didReachOriginQuota(totalSpaceNeeded);
807
808 ApplicationCacheStorage::FailureReason failureReason;
809 setNewestCache(m_cacheBeingUpdated.releaseNonNull());
810 if (m_storage->storeNewestCache(*this, oldNewestCache.get(), failureReason)) {
811 // New cache stored, now remove the old cache.
812 if (oldNewestCache)
813 m_storage->remove(oldNewestCache.get());
814
815 // Fire the final progress event.
816 ASSERT(m_progressDone == m_progressTotal);
817 postListenerTask(eventNames().progressEvent, m_progressTotal, m_progressDone, m_associatedDocumentLoaders);
818
819 // Fire the success event.
820 postListenerTask(isUpgradeAttempt ? eventNames().updatereadyEvent : eventNames().cachedEvent, m_associatedDocumentLoaders);
821 // It is clear that the origin quota was not reached, so clear the flag if it was set.
822 m_originQuotaExceededPreviously = false;
823 } else {
824 if (failureReason == ApplicationCacheStorage::OriginQuotaReached) {
825 // We ran out of space for this origin. Fall down to the normal error handling
826 // after recording this state.
827 m_originQuotaExceededPreviously = true;
828 m_frame->document()->addConsoleMessage(MessageSource::AppCache, MessageLevel::Error, "Application Cache update failed, because size quota was exceeded."_s);
829 }
830
831 if (failureReason == ApplicationCacheStorage::TotalQuotaReached && !m_calledReachedMaxAppCacheSize) {
832 // FIXME: Should this be handled more like Origin Quotas? Does this fail properly?
833
834 // We ran out of space. All the changes in the cache storage have
835 // been rolled back. We roll back to the previous state in here,
836 // as well, call the chrome client asynchronously and retry to
837 // save the new cache.
838
839 // Save a reference to the new cache.
840 m_cacheBeingUpdated = WTFMove(m_newestCache);
841 if (oldNewestCache)
842 setNewestCache(oldNewestCache.releaseNonNull());
843 scheduleReachedMaxAppCacheSizeCallback();
844 return;
845 }
846
847 // Run the "cache failure steps"
848 // Fire the error events to all pending master entries, as well any other cache hosts
849 // currently associated with a cache in this group.
850 postListenerTask(eventNames().errorEvent, m_associatedDocumentLoaders);
851 // Disassociate the pending master entries from the failed new cache. Note that
852 // all other loaders in the m_associatedDocumentLoaders are still associated with
853 // some other cache in this group. They are not associated with the failed new cache.
854
855 // Need to copy loaders, because the cache group may be destroyed at the end of iteration.
856 for (auto& loader : copyToVector(m_pendingMasterResourceLoaders))
857 disassociateDocumentLoader(*loader); // This can delete this group.
858
859 // Reinstate the oldNewestCache, if there was one.
860 if (oldNewestCache) {
861 // This will discard the failed new cache.
862 setNewestCache(oldNewestCache.releaseNonNull());
863 } else {
864 // We must have been deleted by the last call to disassociateDocumentLoader().
865 return;
866 }
867 }
868 break;
869 }
870 }
871
872 // Empty cache group's list of pending master entries.
873 m_pendingMasterResourceLoaders.clear();
874 m_completionType = None;
875 setUpdateStatus(Idle);
876 m_frame = nullptr;
877 m_availableSpaceInQuota = ApplicationCacheStorage::unknownQuota();
878 m_calledReachedMaxAppCacheSize = false;
879}
880
881void ApplicationCacheGroup::startLoadingEntry()
882{
883 ASSERT(m_cacheBeingUpdated);
884
885 if (m_pendingEntries.isEmpty()) {
886 m_completionType = Completed;
887 deliverDelayedMainResources();
888 return;
889 }
890
891 auto firstPendingEntryURL = m_pendingEntries.begin()->key;
892
893 postListenerTask(eventNames().progressEvent, m_progressTotal, m_progressDone, m_associatedDocumentLoaders);
894 m_progressDone++;
895
896 ASSERT(!m_manifestLoader);
897 ASSERT(!m_entryLoader);
898
899 auto request = createRequest(URL { { }, firstPendingEntryURL }, m_newestCache ? m_newestCache->resourceForURL(firstPendingEntryURL) : nullptr);
900
901 m_currentResourceIdentifier = m_frame->page()->progress().createUniqueIdentifier();
902 InspectorInstrumentation::willSendRequest(m_frame, m_currentResourceIdentifier, m_frame->loader().documentLoader(), request, ResourceResponse { });
903
904 auto& documentLoader = *m_frame->loader().documentLoader();
905 auto requestURL = request.url();
906 unsigned type = m_pendingEntries.begin()->value;
907 m_entryLoader = ApplicationCacheResourceLoader::create(type, documentLoader.cachedResourceLoader(), WTFMove(request), [this, requestURL = WTFMove(requestURL), type] (auto&& resourceOrError) {
908 if (!resourceOrError.has_value()) {
909 auto error = resourceOrError.error();
910 if (error == ApplicationCacheResourceLoader::Error::Abort)
911 return;
912 this->didFailLoadingEntry(error, requestURL, type);
913 return;
914 }
915
916 m_currentResource = WTFMove(resourceOrError.value());
917 this->didFinishLoadingEntry(requestURL);
918 });
919}
920
921void ApplicationCacheGroup::deliverDelayedMainResources()
922{
923 // Need to copy loaders, because the cache group may be destroyed at the end of iteration.
924 auto loaders = copyToVector(m_pendingMasterResourceLoaders);
925 for (auto* loader : loaders) {
926 if (loader->isLoadingMainResource())
927 continue;
928 if (loader->mainDocumentError().isNull())
929 finishedLoadingMainResource(*loader);
930 else
931 failedLoadingMainResource(*loader);
932 }
933 if (loaders.isEmpty())
934 checkIfLoadIsComplete();
935}
936
937void ApplicationCacheGroup::addEntry(const String& url, unsigned type)
938{
939 ASSERT(m_cacheBeingUpdated);
940 ASSERT(!URL({ }, url).hasFragmentIdentifier());
941
942 // Don't add the URL if we already have an master resource in the cache
943 // (i.e., the main resource finished loading before the manifest).
944 if (auto* resource = m_cacheBeingUpdated->resourceForURL(url)) {
945 ASSERT(resource->type() & ApplicationCacheResource::Master);
946 ASSERT(!m_frame->loader().documentLoader()->isLoadingMainResource());
947 resource->addType(type);
948 return;
949 }
950
951 // Don't add the URL if it's the same as the manifest URL.
952 ASSERT(m_manifestResource);
953 if (m_manifestResource->url() == url) {
954 m_manifestResource->addType(type);
955 return;
956 }
957
958 m_pendingEntries.add(url, type).iterator->value |= type;
959}
960
961void ApplicationCacheGroup::associateDocumentLoaderWithCache(DocumentLoader* loader, ApplicationCache* cache)
962{
963 // If teardown started already, revive the group.
964 if (!m_newestCache && !m_cacheBeingUpdated)
965 m_newestCache = cache;
966
967 ASSERT(!m_isObsolete);
968
969 loader->applicationCacheHost().setApplicationCache(cache);
970
971 ASSERT(!m_associatedDocumentLoaders.contains(loader));
972 m_associatedDocumentLoaders.add(loader);
973}
974
975class ChromeClientCallbackTimer final : public TimerBase {
976public:
977 ChromeClientCallbackTimer(ApplicationCacheGroup& group)
978 : m_group(group)
979 {
980 }
981
982private:
983 void fired() final
984 {
985 m_group.didReachMaxAppCacheSize();
986 delete this;
987 }
988
989 // Note that there is no need to use a Ref here. The ApplicationCacheGroup instance is guaranteed
990 // to be alive when the timer fires since invoking the callback is part of its normal
991 // update machinery and nothing can yet cause it to get deleted.
992 ApplicationCacheGroup& m_group;
993};
994
995void ApplicationCacheGroup::scheduleReachedMaxAppCacheSizeCallback()
996{
997 ASSERT(isMainThread());
998 auto* timer = new ChromeClientCallbackTimer(*this);
999 timer->startOneShot(0_s);
1000 // The timer will delete itself once it fires.
1001}
1002
1003void ApplicationCacheGroup::postListenerTask(const AtomicString& eventType, int progressTotal, int progressDone, const HashSet<DocumentLoader*>& loaderSet)
1004{
1005 for (auto& loader : loaderSet)
1006 postListenerTask(eventType, progressTotal, progressDone, *loader);
1007}
1008
1009void ApplicationCacheGroup::postListenerTask(const AtomicString& eventType, int progressTotal, int progressDone, DocumentLoader& loader)
1010{
1011 auto* frame = loader.frame();
1012 if (!frame)
1013 return;
1014
1015 ASSERT(frame->loader().documentLoader() == &loader);
1016
1017 RefPtr<DocumentLoader> protectedLoader(&loader);
1018 frame->document()->postTask([protectedLoader, &eventType, progressTotal, progressDone] (ScriptExecutionContext& context) {
1019 ASSERT_UNUSED(context, context.isDocument());
1020 auto* frame = protectedLoader->frame();
1021 if (!frame)
1022 return;
1023
1024 ASSERT(frame->loader().documentLoader() == protectedLoader);
1025 protectedLoader->applicationCacheHost().notifyDOMApplicationCache(eventType, progressTotal, progressDone);
1026 });
1027}
1028
1029void ApplicationCacheGroup::setUpdateStatus(UpdateStatus status)
1030{
1031 m_updateStatus = status;
1032}
1033
1034void ApplicationCacheGroup::clearStorageID()
1035{
1036 m_storageID = 0;
1037 for (auto& cache : m_caches)
1038 cache->clearStorageID();
1039}
1040
1041}
1042