1 /*
2 Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de)
3 Copyright (C) 2001 Dirk Mueller (mueller@kde.org)
4 Copyright (C) 2002 Waldo Bastian (bastian@kde.org)
5 Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com)
6 Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
7
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Library General Public
10 License as published by the Free Software Foundation; either
11 version 2 of the License, or (at your option) any later version.
12
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Library General Public License for more details.
17
18 You should have received a copy of the GNU Library General Public License
19 along with this library; see the file COPYING.LIB. If not, write to
20 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 Boston, MA 02110-1301, USA.
22 */
23
24 #include "config.h"
25 #include "CachedResource.h"
26
27 #include "Cache.h"
28 #include "CachedResourceHandle.h"
29 #include "DocLoader.h"
30 #include "Frame.h"
31 #include "FrameLoader.h"
32 #include "KURL.h"
33 #include "PurgeableBuffer.h"
34 #include "Request.h"
35 #include <wtf/CurrentTime.h>
36 #include <wtf/MathExtras.h>
37 #include <wtf/RefCountedLeakCounter.h>
38 #include <wtf/StdLibExtras.h>
39 #include <wtf/Vector.h>
40
41 using namespace WTF;
42
43 namespace WebCore {
44
45 #ifndef NDEBUG
46 static RefCountedLeakCounter cachedResourceLeakCounter("CachedResource");
47 #endif
48
CachedResource(const String & url,Type type)49 CachedResource::CachedResource(const String& url, Type type)
50 : m_url(url)
51 , m_responseTimestamp(currentTime())
52 , m_lastDecodedAccessTime(0)
53 , m_sendResourceLoadCallbacks(true)
54 , m_preloadCount(0)
55 , m_preloadResult(PreloadNotReferenced)
56 , m_requestedFromNetworkingLayer(false)
57 , m_inCache(false)
58 , m_loading(false)
59 , m_docLoader(0)
60 , m_handleCount(0)
61 , m_resourceToRevalidate(0)
62 , m_isBeingRevalidated(false)
63 {
64 #ifndef NDEBUG
65 cachedResourceLeakCounter.increment();
66 #endif
67
68 m_type = type;
69 m_status = Pending;
70 m_encodedSize = 0;
71 m_decodedSize = 0;
72 m_request = 0;
73
74 m_accessCount = 0;
75 m_inLiveDecodedResourcesList = false;
76
77 m_nextInAllResourcesList = 0;
78 m_prevInAllResourcesList = 0;
79
80 m_nextInLiveResourcesList = 0;
81 m_prevInLiveResourcesList = 0;
82
83 #ifndef NDEBUG
84 m_deleted = false;
85 m_lruIndex = 0;
86 #endif
87 m_errorOccurred = false;
88 }
89
~CachedResource()90 CachedResource::~CachedResource()
91 {
92 ASSERT(!inCache());
93 ASSERT(!m_deleted);
94 ASSERT(url().isNull() || cache()->resourceForURL(url()) != this);
95 #ifndef NDEBUG
96 m_deleted = true;
97 cachedResourceLeakCounter.decrement();
98 #endif
99
100 if (m_resourceToRevalidate)
101 m_resourceToRevalidate->m_isBeingRevalidated = false;
102
103 if (m_docLoader)
104 m_docLoader->removeCachedResource(this);
105 }
106
load(DocLoader * docLoader,bool incremental,bool skipCanLoadCheck,bool sendResourceLoadCallbacks)107 void CachedResource::load(DocLoader* docLoader, bool incremental, bool skipCanLoadCheck, bool sendResourceLoadCallbacks)
108 {
109 m_sendResourceLoadCallbacks = sendResourceLoadCallbacks;
110 cache()->loader()->load(docLoader, this, incremental, skipCanLoadCheck, sendResourceLoadCallbacks);
111 m_loading = true;
112 }
113
finish()114 void CachedResource::finish()
115 {
116 m_status = Cached;
117 }
118
isExpired() const119 bool CachedResource::isExpired() const
120 {
121 if (m_response.isNull())
122 return false;
123
124 return currentAge() > freshnessLifetime();
125 }
126
currentAge() const127 double CachedResource::currentAge() const
128 {
129 // RFC2616 13.2.3
130 // No compensation for latency as that is not terribly important in practice
131 double dateValue = m_response.date();
132 double apparentAge = isfinite(dateValue) ? max(0., m_responseTimestamp - dateValue) : 0;
133 double ageValue = m_response.age();
134 double correctedReceivedAge = isfinite(ageValue) ? max(apparentAge, ageValue) : apparentAge;
135 double residentTime = currentTime() - m_responseTimestamp;
136 return correctedReceivedAge + residentTime;
137 }
138
freshnessLifetime() const139 double CachedResource::freshnessLifetime() const
140 {
141 // Cache non-http resources liberally
142 if (!m_response.url().protocolInHTTPFamily())
143 return std::numeric_limits<double>::max();
144
145 // RFC2616 13.2.4
146 double maxAgeValue = m_response.cacheControlMaxAge();
147 if (isfinite(maxAgeValue))
148 return maxAgeValue;
149 double expiresValue = m_response.expires();
150 double dateValue = m_response.date();
151 double creationTime = isfinite(dateValue) ? dateValue : m_responseTimestamp;
152 if (isfinite(expiresValue))
153 return expiresValue - creationTime;
154 double lastModifiedValue = m_response.lastModified();
155 if (isfinite(lastModifiedValue))
156 return (creationTime - lastModifiedValue) * 0.1;
157 // If no cache headers are present, the specification leaves the decision to the UA. Other browsers seem to opt for 0.
158 return 0;
159 }
160
setResponse(const ResourceResponse & response)161 void CachedResource::setResponse(const ResourceResponse& response)
162 {
163 m_response = response;
164 m_responseTimestamp = currentTime();
165 }
166
setRequest(Request * request)167 void CachedResource::setRequest(Request* request)
168 {
169 if (request && !m_request)
170 m_status = Pending;
171 m_request = request;
172 if (canDelete() && !inCache())
173 delete this;
174 }
175
addClient(CachedResourceClient * client)176 void CachedResource::addClient(CachedResourceClient* client)
177 {
178 addClientToSet(client);
179 didAddClient(client);
180 }
181
addClientToSet(CachedResourceClient * client)182 void CachedResource::addClientToSet(CachedResourceClient* client)
183 {
184 ASSERT(!isPurgeable());
185
186 if (m_preloadResult == PreloadNotReferenced) {
187 if (isLoaded())
188 m_preloadResult = PreloadReferencedWhileComplete;
189 else if (m_requestedFromNetworkingLayer)
190 m_preloadResult = PreloadReferencedWhileLoading;
191 else
192 m_preloadResult = PreloadReferenced;
193 }
194 if (!hasClients() && inCache())
195 cache()->addToLiveResourcesSize(this);
196 m_clients.add(client);
197 }
198
removeClient(CachedResourceClient * client)199 void CachedResource::removeClient(CachedResourceClient* client)
200 {
201 ASSERT(m_clients.contains(client));
202 m_clients.remove(client);
203
204 if (canDelete() && !inCache())
205 delete this;
206 else if (!hasClients() && inCache()) {
207 cache()->removeFromLiveResourcesSize(this);
208 cache()->removeFromLiveDecodedResourcesList(this);
209 allClientsRemoved();
210 if (response().cacheControlContainsNoStore()) {
211 // RFC2616 14.9.2:
212 // "no-store: ...MUST make a best-effort attempt to remove the information from volatile storage as promptly as possible"
213 cache()->remove(this);
214 } else
215 cache()->prune();
216 }
217 // This object may be dead here.
218 }
219
deleteIfPossible()220 void CachedResource::deleteIfPossible()
221 {
222 if (canDelete() && !inCache())
223 delete this;
224 }
225
setDecodedSize(unsigned size)226 void CachedResource::setDecodedSize(unsigned size)
227 {
228 if (size == m_decodedSize)
229 return;
230
231 int delta = size - m_decodedSize;
232
233 // The object must now be moved to a different queue, since its size has been changed.
234 // We have to remove explicitly before updating m_decodedSize, so that we find the correct previous
235 // queue.
236 if (inCache())
237 cache()->removeFromLRUList(this);
238
239 m_decodedSize = size;
240
241 if (inCache()) {
242 // Now insert into the new LRU list.
243 cache()->insertInLRUList(this);
244
245 // Insert into or remove from the live decoded list if necessary.
246 if (m_decodedSize && !m_inLiveDecodedResourcesList && hasClients())
247 cache()->insertInLiveDecodedResourcesList(this);
248 else if (!m_decodedSize && m_inLiveDecodedResourcesList)
249 cache()->removeFromLiveDecodedResourcesList(this);
250
251 // Update the cache's size totals.
252 cache()->adjustSize(hasClients(), delta);
253 }
254 }
255
setEncodedSize(unsigned size)256 void CachedResource::setEncodedSize(unsigned size)
257 {
258 if (size == m_encodedSize)
259 return;
260
261 // The size cannot ever shrink (unless it is being nulled out because of an error). If it ever does, assert.
262 ASSERT(size == 0 || size >= m_encodedSize);
263
264 int delta = size - m_encodedSize;
265
266 // The object must now be moved to a different queue, since its size has been changed.
267 // We have to remove explicitly before updating m_encodedSize, so that we find the correct previous
268 // queue.
269 if (inCache())
270 cache()->removeFromLRUList(this);
271
272 m_encodedSize = size;
273
274 if (inCache()) {
275 // Now insert into the new LRU list.
276 cache()->insertInLRUList(this);
277
278 // Update the cache's size totals.
279 cache()->adjustSize(hasClients(), delta);
280 }
281 }
282
didAccessDecodedData(double timeStamp)283 void CachedResource::didAccessDecodedData(double timeStamp)
284 {
285 m_lastDecodedAccessTime = timeStamp;
286
287 if (inCache()) {
288 if (m_inLiveDecodedResourcesList) {
289 cache()->removeFromLiveDecodedResourcesList(this);
290 cache()->insertInLiveDecodedResourcesList(this);
291 }
292 cache()->prune();
293 }
294 }
295
setResourceToRevalidate(CachedResource * resource)296 void CachedResource::setResourceToRevalidate(CachedResource* resource)
297 {
298 ASSERT(resource);
299 ASSERT(!m_resourceToRevalidate);
300 ASSERT(resource != this);
301 ASSERT(!resource->m_isBeingRevalidated);
302 ASSERT(m_handlesToRevalidate.isEmpty());
303 ASSERT(resource->type() == type());
304 resource->m_isBeingRevalidated = true;
305 m_resourceToRevalidate = resource;
306 }
307
clearResourceToRevalidate()308 void CachedResource::clearResourceToRevalidate()
309 {
310 ASSERT(m_resourceToRevalidate);
311 m_resourceToRevalidate->m_isBeingRevalidated = false;
312 m_resourceToRevalidate->deleteIfPossible();
313 m_handlesToRevalidate.clear();
314 m_resourceToRevalidate = 0;
315 deleteIfPossible();
316 }
317
switchClientsToRevalidatedResource()318 void CachedResource::switchClientsToRevalidatedResource()
319 {
320 ASSERT(m_resourceToRevalidate);
321 ASSERT(m_resourceToRevalidate->inCache());
322 ASSERT(!inCache());
323
324 HashSet<CachedResourceHandleBase*>::iterator end = m_handlesToRevalidate.end();
325 for (HashSet<CachedResourceHandleBase*>::iterator it = m_handlesToRevalidate.begin(); it != end; ++it) {
326 CachedResourceHandleBase* handle = *it;
327 handle->m_resource = m_resourceToRevalidate;
328 m_resourceToRevalidate->registerHandle(handle);
329 --m_handleCount;
330 }
331 ASSERT(!m_handleCount);
332 m_handlesToRevalidate.clear();
333
334 Vector<CachedResourceClient*> clientsToMove;
335 HashCountedSet<CachedResourceClient*>::iterator end2 = m_clients.end();
336 for (HashCountedSet<CachedResourceClient*>::iterator it = m_clients.begin(); it != end2; ++it) {
337 CachedResourceClient* client = it->first;
338 unsigned count = it->second;
339 while (count) {
340 clientsToMove.append(client);
341 --count;
342 }
343 }
344 // Equivalent of calling removeClient() for all clients
345 m_clients.clear();
346
347 unsigned moveCount = clientsToMove.size();
348 for (unsigned n = 0; n < moveCount; ++n)
349 m_resourceToRevalidate->addClientToSet(clientsToMove[n]);
350 for (unsigned n = 0; n < moveCount; ++n) {
351 // Calling didAddClient for a client may end up removing another client. In that case it won't be in the set anymore.
352 if (m_resourceToRevalidate->m_clients.contains(clientsToMove[n]))
353 m_resourceToRevalidate->didAddClient(clientsToMove[n]);
354 }
355 }
356
updateResponseAfterRevalidation(const ResourceResponse & validatingResponse)357 void CachedResource::updateResponseAfterRevalidation(const ResourceResponse& validatingResponse)
358 {
359 m_responseTimestamp = currentTime();
360
361 DEFINE_STATIC_LOCAL(const AtomicString, contentHeaderPrefix, ("content-"));
362 // RFC2616 10.3.5
363 // Update cached headers from the 304 response
364 const HTTPHeaderMap& newHeaders = validatingResponse.httpHeaderFields();
365 HTTPHeaderMap::const_iterator end = newHeaders.end();
366 for (HTTPHeaderMap::const_iterator it = newHeaders.begin(); it != end; ++it) {
367 // Don't allow 304 response to update content headers, these can't change but some servers send wrong values.
368 if (it->first.startsWith(contentHeaderPrefix, false))
369 continue;
370 m_response.setHTTPHeaderField(it->first, it->second);
371 }
372 }
373
canUseCacheValidator() const374 bool CachedResource::canUseCacheValidator() const
375 {
376 if (m_loading || m_errorOccurred)
377 return false;
378
379 if (m_response.cacheControlContainsNoStore())
380 return false;
381
382 DEFINE_STATIC_LOCAL(const AtomicString, lastModifiedHeader, ("last-modified"));
383 DEFINE_STATIC_LOCAL(const AtomicString, eTagHeader, ("etag"));
384 return !m_response.httpHeaderField(lastModifiedHeader).isEmpty() || !m_response.httpHeaderField(eTagHeader).isEmpty();
385 }
386
mustRevalidate(CachePolicy cachePolicy) const387 bool CachedResource::mustRevalidate(CachePolicy cachePolicy) const
388 {
389 if (m_errorOccurred)
390 return true;
391
392 if (m_loading)
393 return false;
394
395 if (m_response.cacheControlContainsNoCache() || m_response.cacheControlContainsNoStore())
396 return true;
397
398 if (cachePolicy == CachePolicyCache)
399 return m_response.cacheControlContainsMustRevalidate() && isExpired();
400
401 return isExpired();
402 }
403
isSafeToMakePurgeable() const404 bool CachedResource::isSafeToMakePurgeable() const
405 {
406 return !hasClients() && !m_isBeingRevalidated && !m_resourceToRevalidate;
407 }
408
makePurgeable(bool purgeable)409 bool CachedResource::makePurgeable(bool purgeable)
410 {
411 if (purgeable) {
412 ASSERT(isSafeToMakePurgeable());
413
414 if (m_purgeableData) {
415 ASSERT(!m_data);
416 return true;
417 }
418 if (!m_data)
419 return false;
420
421 // Should not make buffer purgeable if it has refs othen than this since we don't want two copies.
422 if (!m_data->hasOneRef())
423 return false;
424
425 // Purgeable buffers are allocated in multiples of the page size (4KB in common CPUs) so it does not make sense for very small buffers.
426 const size_t purgeableThreshold = 4 * 4096;
427 if (m_data->size() < purgeableThreshold)
428 return false;
429
430 if (m_data->hasPurgeableBuffer()) {
431 m_purgeableData.set(m_data->releasePurgeableBuffer());
432 } else {
433 m_purgeableData.set(PurgeableBuffer::create(m_data->data(), m_data->size()));
434 if (!m_purgeableData)
435 return false;
436 }
437
438 m_purgeableData->makePurgeable(true);
439 m_data.clear();
440 return true;
441 }
442
443 if (!m_purgeableData)
444 return true;
445 ASSERT(!m_data);
446 ASSERT(!hasClients());
447
448 if (!m_purgeableData->makePurgeable(false))
449 return false;
450
451 m_data = SharedBuffer::adoptPurgeableBuffer(m_purgeableData.release());
452 return true;
453 }
454
isPurgeable() const455 bool CachedResource::isPurgeable() const
456 {
457 return m_purgeableData && m_purgeableData->isPurgeable();
458 }
459
wasPurged() const460 bool CachedResource::wasPurged() const
461 {
462 return m_purgeableData && m_purgeableData->wasPurged();
463 }
464
overheadSize() const465 unsigned CachedResource::overheadSize() const
466 {
467 return sizeof(CachedResource) + m_response.memoryUsage() + 576;
468 /*
469 576 = 192 + // average size of m_url
470 384; // average size of m_clients hash map
471 */
472 }
473
474 }
475