|
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) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. |
|
6 |
|
7 This library is free software; you can redistribute it and/or |
|
8 modify it under the terms of the GNU Library General Public |
|
9 License as published by the Free Software Foundation; either |
|
10 version 2 of the License, or (at your option) any later version. |
|
11 |
|
12 This library is distributed in the hope that it will be useful, |
|
13 but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
15 Library General Public License for more details. |
|
16 |
|
17 You should have received a copy of the GNU Library General Public License |
|
18 along with this library; see the file COPYING.LIB. If not, write to |
|
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
|
20 Boston, MA 02110-1301, USA. |
|
21 */ |
|
22 |
|
23 #include "config.h" |
|
24 #include "Cache.h" |
|
25 |
|
26 #include "CachedCSSStyleSheet.h" |
|
27 #include "CachedFont.h" |
|
28 #include "CachedImage.h" |
|
29 #include "CachedScript.h" |
|
30 #include "CachedXSLStyleSheet.h" |
|
31 #include "DocLoader.h" |
|
32 #include "Document.h" |
|
33 #include "FrameLoader.h" |
|
34 #include "FrameLoaderTypes.h" |
|
35 #include "FrameView.h" |
|
36 #include "Image.h" |
|
37 #include "ResourceHandle.h" |
|
38 #include "SecurityOrigin.h" |
|
39 #include <stdio.h> |
|
40 #include <wtf/CurrentTime.h> |
|
41 |
|
42 using namespace std; |
|
43 |
|
44 namespace WebCore { |
|
45 |
|
46 static const int cDefaultCacheCapacity = 8192 * 1024; |
|
47 static const double cMinDelayBeforeLiveDecodedPrune = 1; // Seconds. |
|
48 static const float cTargetPrunePercentage = .95f; // Percentage of capacity toward which we prune, to avoid immediately pruning again. |
|
49 static const double cDefaultDecodedDataDeletionInterval = 0; |
|
50 |
|
51 Cache* cache() |
|
52 { |
|
53 static Cache* staticCache = new Cache; |
|
54 return staticCache; |
|
55 } |
|
56 |
|
57 Cache::Cache() |
|
58 : m_disabled(false) |
|
59 , m_pruneEnabled(true) |
|
60 , m_inPruneDeadResources(false) |
|
61 , m_capacity(cDefaultCacheCapacity) |
|
62 , m_minDeadCapacity(0) |
|
63 , m_maxDeadCapacity(cDefaultCacheCapacity) |
|
64 , m_deadDecodedDataDeletionInterval(cDefaultDecodedDataDeletionInterval) |
|
65 , m_liveSize(0) |
|
66 , m_deadSize(0) |
|
67 { |
|
68 } |
|
69 |
|
70 static CachedResource* createResource(CachedResource::Type type, const KURL& url, const String& charset) |
|
71 { |
|
72 switch (type) { |
|
73 case CachedResource::ImageResource: |
|
74 return new CachedImage(url.string()); |
|
75 case CachedResource::CSSStyleSheet: |
|
76 return new CachedCSSStyleSheet(url.string(), charset); |
|
77 case CachedResource::Script: |
|
78 return new CachedScript(url.string(), charset); |
|
79 case CachedResource::FontResource: |
|
80 return new CachedFont(url.string()); |
|
81 #if ENABLE(XSLT) |
|
82 case CachedResource::XSLStyleSheet: |
|
83 return new CachedXSLStyleSheet(url.string()); |
|
84 #endif |
|
85 #if ENABLE(XBL) |
|
86 case CachedResource::XBLStyleSheet: |
|
87 return new CachedXBLDocument(url.string()); |
|
88 #endif |
|
89 #if ENABLE(LINK_PREFETCH) |
|
90 case CachedResource::LinkPrefetch: |
|
91 return new CachedResource(url.string(), CachedResource::LinkPrefetch); |
|
92 #endif |
|
93 default: |
|
94 break; |
|
95 } |
|
96 |
|
97 return 0; |
|
98 } |
|
99 |
|
100 CachedResource* Cache::requestResource(DocLoader* docLoader, CachedResource::Type type, const KURL& url, const String& charset, bool requestIsPreload) |
|
101 { |
|
102 // FIXME: Do we really need to special-case an empty URL? |
|
103 // Would it be better to just go on with the cache code and let it fail later? |
|
104 if (url.isEmpty()) |
|
105 return 0; |
|
106 |
|
107 // Look up the resource in our map. |
|
108 CachedResource* resource = resourceForURL(url.string()); |
|
109 |
|
110 if (resource && requestIsPreload && !resource->isPreloaded()) |
|
111 return 0; |
|
112 |
|
113 if (SecurityOrigin::restrictAccessToLocal() && !SecurityOrigin::canLoad(url, String(), docLoader->doc())) { |
|
114 Document* doc = docLoader->doc(); |
|
115 if (doc && !requestIsPreload) |
|
116 FrameLoader::reportLocalLoadFailed(doc->frame(), url.string()); |
|
117 return 0; |
|
118 } |
|
119 |
|
120 if (!resource) { |
|
121 // The resource does not exist. Create it. |
|
122 resource = createResource(type, url, charset); |
|
123 ASSERT(resource); |
|
124 |
|
125 // Pretend the resource is in the cache, to prevent it from being deleted during the load() call. |
|
126 // FIXME: CachedResource should just use normal refcounting instead. |
|
127 resource->setInCache(true); |
|
128 |
|
129 resource->load(docLoader); |
|
130 |
|
131 if (resource->errorOccurred()) { |
|
132 // We don't support immediate loads, but we do support immediate failure. |
|
133 // In that case we should to delete the resource now and return 0 because otherwise |
|
134 // it would leak if no ref/deref was ever done on it. |
|
135 resource->setInCache(false); |
|
136 delete resource; |
|
137 return 0; |
|
138 } |
|
139 |
|
140 if (!disabled()) |
|
141 m_resources.set(url.string(), resource); // The size will be added in later once the resource is loaded and calls back to us with the new size. |
|
142 else { |
|
143 // Kick the resource out of the cache, because the cache is disabled. |
|
144 resource->setInCache(false); |
|
145 resource->setDocLoader(docLoader); |
|
146 } |
|
147 } |
|
148 |
|
149 if (resource->type() != type) |
|
150 return 0; |
|
151 |
|
152 if (!disabled()) { |
|
153 // This will move the resource to the front of its LRU list and increase its access count. |
|
154 resourceAccessed(resource); |
|
155 } |
|
156 |
|
157 return resource; |
|
158 } |
|
159 |
|
160 CachedCSSStyleSheet* Cache::requestUserCSSStyleSheet(DocLoader* docLoader, const String& url, const String& charset) |
|
161 { |
|
162 CachedCSSStyleSheet* userSheet; |
|
163 if (CachedResource* existing = resourceForURL(url)) { |
|
164 if (existing->type() != CachedResource::CSSStyleSheet) |
|
165 return 0; |
|
166 userSheet = static_cast<CachedCSSStyleSheet*>(existing); |
|
167 } else { |
|
168 userSheet = new CachedCSSStyleSheet(url, charset); |
|
169 |
|
170 // Pretend the resource is in the cache, to prevent it from being deleted during the load() call. |
|
171 // FIXME: CachedResource should just use normal refcounting instead. |
|
172 userSheet->setInCache(true); |
|
173 // Don't load incrementally, skip load checks, don't send resource load callbacks. |
|
174 userSheet->load(docLoader, false, SkipSecurityCheck, false); |
|
175 if (!disabled()) |
|
176 m_resources.set(url, userSheet); |
|
177 else |
|
178 userSheet->setInCache(false); |
|
179 } |
|
180 |
|
181 if (!disabled()) { |
|
182 // This will move the resource to the front of its LRU list and increase its access count. |
|
183 resourceAccessed(userSheet); |
|
184 } |
|
185 |
|
186 return userSheet; |
|
187 } |
|
188 |
|
189 void Cache::revalidateResource(CachedResource* resource, DocLoader* docLoader) |
|
190 { |
|
191 ASSERT(resource); |
|
192 ASSERT(resource->inCache()); |
|
193 ASSERT(resource == m_resources.get(resource->url())); |
|
194 ASSERT(!disabled()); |
|
195 if (resource->resourceToRevalidate()) |
|
196 return; |
|
197 if (!resource->canUseCacheValidator()) { |
|
198 evict(resource); |
|
199 return; |
|
200 } |
|
201 const String& url = resource->url(); |
|
202 CachedResource* newResource = createResource(resource->type(), KURL(ParsedURLString, url), resource->encoding()); |
|
203 newResource->setResourceToRevalidate(resource); |
|
204 evict(resource); |
|
205 m_resources.set(url, newResource); |
|
206 newResource->setInCache(true); |
|
207 resourceAccessed(newResource); |
|
208 newResource->load(docLoader); |
|
209 } |
|
210 |
|
211 void Cache::revalidationSucceeded(CachedResource* revalidatingResource, const ResourceResponse& response) |
|
212 { |
|
213 CachedResource* resource = revalidatingResource->resourceToRevalidate(); |
|
214 ASSERT(resource); |
|
215 ASSERT(!resource->inCache()); |
|
216 ASSERT(resource->isLoaded()); |
|
217 ASSERT(revalidatingResource->inCache()); |
|
218 |
|
219 evict(revalidatingResource); |
|
220 |
|
221 ASSERT(!m_resources.get(resource->url())); |
|
222 m_resources.set(resource->url(), resource); |
|
223 resource->setInCache(true); |
|
224 resource->updateResponseAfterRevalidation(response); |
|
225 insertInLRUList(resource); |
|
226 int delta = resource->size(); |
|
227 if (resource->decodedSize() && resource->hasClients()) |
|
228 insertInLiveDecodedResourcesList(resource); |
|
229 if (delta) |
|
230 adjustSize(resource->hasClients(), delta); |
|
231 |
|
232 revalidatingResource->switchClientsToRevalidatedResource(); |
|
233 // this deletes the revalidating resource |
|
234 revalidatingResource->clearResourceToRevalidate(); |
|
235 } |
|
236 |
|
237 void Cache::revalidationFailed(CachedResource* revalidatingResource) |
|
238 { |
|
239 ASSERT(revalidatingResource->resourceToRevalidate()); |
|
240 revalidatingResource->clearResourceToRevalidate(); |
|
241 } |
|
242 |
|
243 CachedResource* Cache::resourceForURL(const String& url) |
|
244 { |
|
245 CachedResource* resource = m_resources.get(url); |
|
246 if (resource && !resource->makePurgeable(false)) { |
|
247 ASSERT(!resource->hasClients()); |
|
248 evict(resource); |
|
249 return 0; |
|
250 } |
|
251 return resource; |
|
252 } |
|
253 |
|
254 unsigned Cache::deadCapacity() const |
|
255 { |
|
256 // Dead resource capacity is whatever space is not occupied by live resources, bounded by an independent minimum and maximum. |
|
257 unsigned capacity = m_capacity - min(m_liveSize, m_capacity); // Start with available capacity. |
|
258 capacity = max(capacity, m_minDeadCapacity); // Make sure it's above the minimum. |
|
259 capacity = min(capacity, m_maxDeadCapacity); // Make sure it's below the maximum. |
|
260 return capacity; |
|
261 } |
|
262 |
|
263 unsigned Cache::liveCapacity() const |
|
264 { |
|
265 // Live resource capacity is whatever is left over after calculating dead resource capacity. |
|
266 return m_capacity - deadCapacity(); |
|
267 } |
|
268 |
|
269 void Cache::pruneLiveResources() |
|
270 { |
|
271 if (!m_pruneEnabled) |
|
272 return; |
|
273 |
|
274 unsigned capacity = liveCapacity(); |
|
275 if (capacity && m_liveSize <= capacity) |
|
276 return; |
|
277 |
|
278 unsigned targetSize = static_cast<unsigned>(capacity * cTargetPrunePercentage); // Cut by a percentage to avoid immediately pruning again. |
|
279 double currentTime = FrameView::currentPaintTimeStamp(); |
|
280 if (!currentTime) // In case prune is called directly, outside of a Frame paint. |
|
281 currentTime = WTF::currentTime(); |
|
282 |
|
283 // Destroy any decoded data in live objects that we can. |
|
284 // Start from the tail, since this is the least recently accessed of the objects. |
|
285 |
|
286 // The list might not be sorted by the m_lastDecodedAccessTime. The impact |
|
287 // of this weaker invariant is minor as the below if statement to check the |
|
288 // elapsedTime will evaluate to false as the currentTime will be a lot |
|
289 // greater than the current->m_lastDecodedAccessTime. |
|
290 // For more details see: https://bugs.webkit.org/show_bug.cgi?id=30209 |
|
291 CachedResource* current = m_liveDecodedResources.m_tail; |
|
292 while (current) { |
|
293 CachedResource* prev = current->m_prevInLiveResourcesList; |
|
294 ASSERT(current->hasClients()); |
|
295 if (current->isLoaded() && current->decodedSize()) { |
|
296 // Check to see if the remaining resources are too new to prune. |
|
297 double elapsedTime = currentTime - current->m_lastDecodedAccessTime; |
|
298 if (elapsedTime < cMinDelayBeforeLiveDecodedPrune) |
|
299 return; |
|
300 |
|
301 // Destroy our decoded data. This will remove us from |
|
302 // m_liveDecodedResources, and possibly move us to a different LRU |
|
303 // list in m_allResources. |
|
304 current->destroyDecodedData(); |
|
305 |
|
306 if (targetSize && m_liveSize <= targetSize) |
|
307 return; |
|
308 } |
|
309 current = prev; |
|
310 } |
|
311 } |
|
312 |
|
313 void Cache::pruneDeadResources() |
|
314 { |
|
315 if (!m_pruneEnabled) |
|
316 return; |
|
317 |
|
318 unsigned capacity = deadCapacity(); |
|
319 if (capacity && m_deadSize <= capacity) |
|
320 return; |
|
321 |
|
322 unsigned targetSize = static_cast<unsigned>(capacity * cTargetPrunePercentage); // Cut by a percentage to avoid immediately pruning again. |
|
323 int size = m_allResources.size(); |
|
324 |
|
325 if (!m_inPruneDeadResources) { |
|
326 // See if we have any purged resources we can evict. |
|
327 for (int i = 0; i < size; i++) { |
|
328 CachedResource* current = m_allResources[i].m_tail; |
|
329 while (current) { |
|
330 CachedResource* prev = current->m_prevInAllResourcesList; |
|
331 if (current->wasPurged()) { |
|
332 ASSERT(!current->hasClients()); |
|
333 ASSERT(!current->isPreloaded()); |
|
334 evict(current); |
|
335 } |
|
336 current = prev; |
|
337 } |
|
338 } |
|
339 if (targetSize && m_deadSize <= targetSize) |
|
340 return; |
|
341 } |
|
342 |
|
343 bool canShrinkLRULists = true; |
|
344 m_inPruneDeadResources = true; |
|
345 for (int i = size - 1; i >= 0; i--) { |
|
346 // Remove from the tail, since this is the least frequently accessed of the objects. |
|
347 CachedResource* current = m_allResources[i].m_tail; |
|
348 |
|
349 // First flush all the decoded data in this queue. |
|
350 while (current) { |
|
351 CachedResource* prev = current->m_prevInAllResourcesList; |
|
352 if (!current->hasClients() && !current->isPreloaded() && current->isLoaded()) { |
|
353 // Destroy our decoded data. This will remove us from |
|
354 // m_liveDecodedResources, and possibly move us to a different |
|
355 // LRU list in m_allResources. |
|
356 current->destroyDecodedData(); |
|
357 |
|
358 if (targetSize && m_deadSize <= targetSize) { |
|
359 m_inPruneDeadResources = false; |
|
360 return; |
|
361 } |
|
362 } |
|
363 current = prev; |
|
364 } |
|
365 |
|
366 // Now evict objects from this queue. |
|
367 current = m_allResources[i].m_tail; |
|
368 while (current) { |
|
369 CachedResource* prev = current->m_prevInAllResourcesList; |
|
370 if (!current->hasClients() && !current->isPreloaded() && !current->isCacheValidator()) { |
|
371 evict(current); |
|
372 // If evict() caused pruneDeadResources() to be re-entered, bail out. This can happen when removing an |
|
373 // SVG CachedImage that has subresources. |
|
374 if (!m_inPruneDeadResources) |
|
375 return; |
|
376 |
|
377 if (targetSize && m_deadSize <= targetSize) { |
|
378 m_inPruneDeadResources = false; |
|
379 return; |
|
380 } |
|
381 } |
|
382 current = prev; |
|
383 } |
|
384 |
|
385 // Shrink the vector back down so we don't waste time inspecting |
|
386 // empty LRU lists on future prunes. |
|
387 if (m_allResources[i].m_head) |
|
388 canShrinkLRULists = false; |
|
389 else if (canShrinkLRULists) |
|
390 m_allResources.resize(i); |
|
391 } |
|
392 m_inPruneDeadResources = false; |
|
393 } |
|
394 |
|
395 void Cache::setCapacities(unsigned minDeadBytes, unsigned maxDeadBytes, unsigned totalBytes) |
|
396 { |
|
397 ASSERT(minDeadBytes <= maxDeadBytes); |
|
398 ASSERT(maxDeadBytes <= totalBytes); |
|
399 m_minDeadCapacity = minDeadBytes; |
|
400 m_maxDeadCapacity = maxDeadBytes; |
|
401 m_capacity = totalBytes; |
|
402 prune(); |
|
403 } |
|
404 |
|
405 void Cache::evict(CachedResource* resource) |
|
406 { |
|
407 // The resource may have already been removed by someone other than our caller, |
|
408 // who needed a fresh copy for a reload. See <http://bugs.webkit.org/show_bug.cgi?id=12479#c6>. |
|
409 if (resource->inCache()) { |
|
410 // Remove from the resource map. |
|
411 m_resources.remove(resource->url()); |
|
412 resource->setInCache(false); |
|
413 |
|
414 // Remove from the appropriate LRU list. |
|
415 removeFromLRUList(resource); |
|
416 removeFromLiveDecodedResourcesList(resource); |
|
417 |
|
418 // Subtract from our size totals. |
|
419 int delta = -static_cast<int>(resource->size()); |
|
420 if (delta) |
|
421 adjustSize(resource->hasClients(), delta); |
|
422 } else |
|
423 ASSERT(m_resources.get(resource->url()) != resource); |
|
424 |
|
425 if (resource->canDelete()) |
|
426 delete resource; |
|
427 } |
|
428 |
|
429 void Cache::addDocLoader(DocLoader* docLoader) |
|
430 { |
|
431 m_docLoaders.add(docLoader); |
|
432 } |
|
433 |
|
434 void Cache::removeDocLoader(DocLoader* docLoader) |
|
435 { |
|
436 m_docLoaders.remove(docLoader); |
|
437 } |
|
438 |
|
439 static inline unsigned fastLog2(unsigned i) |
|
440 { |
|
441 unsigned log2 = 0; |
|
442 if (i & (i - 1)) |
|
443 log2 += 1; |
|
444 if (i >> 16) |
|
445 log2 += 16, i >>= 16; |
|
446 if (i >> 8) |
|
447 log2 += 8, i >>= 8; |
|
448 if (i >> 4) |
|
449 log2 += 4, i >>= 4; |
|
450 if (i >> 2) |
|
451 log2 += 2, i >>= 2; |
|
452 if (i >> 1) |
|
453 log2 += 1; |
|
454 return log2; |
|
455 } |
|
456 |
|
457 Cache::LRUList* Cache::lruListFor(CachedResource* resource) |
|
458 { |
|
459 unsigned accessCount = max(resource->accessCount(), 1U); |
|
460 unsigned queueIndex = fastLog2(resource->size() / accessCount); |
|
461 #ifndef NDEBUG |
|
462 resource->m_lruIndex = queueIndex; |
|
463 #endif |
|
464 if (m_allResources.size() <= queueIndex) |
|
465 m_allResources.grow(queueIndex + 1); |
|
466 return &m_allResources[queueIndex]; |
|
467 } |
|
468 |
|
469 void Cache::removeFromLRUList(CachedResource* resource) |
|
470 { |
|
471 // If we've never been accessed, then we're brand new and not in any list. |
|
472 if (resource->accessCount() == 0) |
|
473 return; |
|
474 |
|
475 #if !ASSERT_DISABLED |
|
476 unsigned oldListIndex = resource->m_lruIndex; |
|
477 #endif |
|
478 |
|
479 LRUList* list = lruListFor(resource); |
|
480 |
|
481 #if !ASSERT_DISABLED |
|
482 // Verify that the list we got is the list we want. |
|
483 ASSERT(resource->m_lruIndex == oldListIndex); |
|
484 |
|
485 // Verify that we are in fact in this list. |
|
486 bool found = false; |
|
487 for (CachedResource* current = list->m_head; current; current = current->m_nextInAllResourcesList) { |
|
488 if (current == resource) { |
|
489 found = true; |
|
490 break; |
|
491 } |
|
492 } |
|
493 ASSERT(found); |
|
494 #endif |
|
495 |
|
496 CachedResource* next = resource->m_nextInAllResourcesList; |
|
497 CachedResource* prev = resource->m_prevInAllResourcesList; |
|
498 |
|
499 if (next == 0 && prev == 0 && list->m_head != resource) |
|
500 return; |
|
501 |
|
502 resource->m_nextInAllResourcesList = 0; |
|
503 resource->m_prevInAllResourcesList = 0; |
|
504 |
|
505 if (next) |
|
506 next->m_prevInAllResourcesList = prev; |
|
507 else if (list->m_tail == resource) |
|
508 list->m_tail = prev; |
|
509 |
|
510 if (prev) |
|
511 prev->m_nextInAllResourcesList = next; |
|
512 else if (list->m_head == resource) |
|
513 list->m_head = next; |
|
514 } |
|
515 |
|
516 void Cache::insertInLRUList(CachedResource* resource) |
|
517 { |
|
518 // Make sure we aren't in some list already. |
|
519 ASSERT(!resource->m_nextInAllResourcesList && !resource->m_prevInAllResourcesList); |
|
520 ASSERT(resource->inCache()); |
|
521 ASSERT(resource->accessCount() > 0); |
|
522 |
|
523 LRUList* list = lruListFor(resource); |
|
524 |
|
525 resource->m_nextInAllResourcesList = list->m_head; |
|
526 if (list->m_head) |
|
527 list->m_head->m_prevInAllResourcesList = resource; |
|
528 list->m_head = resource; |
|
529 |
|
530 if (!resource->m_nextInAllResourcesList) |
|
531 list->m_tail = resource; |
|
532 |
|
533 #ifndef NDEBUG |
|
534 // Verify that we are in now in the list like we should be. |
|
535 list = lruListFor(resource); |
|
536 bool found = false; |
|
537 for (CachedResource* current = list->m_head; current; current = current->m_nextInAllResourcesList) { |
|
538 if (current == resource) { |
|
539 found = true; |
|
540 break; |
|
541 } |
|
542 } |
|
543 ASSERT(found); |
|
544 #endif |
|
545 |
|
546 } |
|
547 |
|
548 void Cache::resourceAccessed(CachedResource* resource) |
|
549 { |
|
550 ASSERT(resource->inCache()); |
|
551 |
|
552 // Need to make sure to remove before we increase the access count, since |
|
553 // the queue will possibly change. |
|
554 removeFromLRUList(resource); |
|
555 |
|
556 // If this is the first time the resource has been accessed, adjust the size of the cache to account for its initial size. |
|
557 if (!resource->accessCount()) |
|
558 adjustSize(resource->hasClients(), resource->size()); |
|
559 |
|
560 // Add to our access count. |
|
561 resource->increaseAccessCount(); |
|
562 |
|
563 // Now insert into the new queue. |
|
564 insertInLRUList(resource); |
|
565 } |
|
566 |
|
567 void Cache::removeFromLiveDecodedResourcesList(CachedResource* resource) |
|
568 { |
|
569 // If we've never been accessed, then we're brand new and not in any list. |
|
570 if (!resource->m_inLiveDecodedResourcesList) |
|
571 return; |
|
572 resource->m_inLiveDecodedResourcesList = false; |
|
573 |
|
574 #ifndef NDEBUG |
|
575 // Verify that we are in fact in this list. |
|
576 bool found = false; |
|
577 for (CachedResource* current = m_liveDecodedResources.m_head; current; current = current->m_nextInLiveResourcesList) { |
|
578 if (current == resource) { |
|
579 found = true; |
|
580 break; |
|
581 } |
|
582 } |
|
583 ASSERT(found); |
|
584 #endif |
|
585 |
|
586 CachedResource* next = resource->m_nextInLiveResourcesList; |
|
587 CachedResource* prev = resource->m_prevInLiveResourcesList; |
|
588 |
|
589 if (next == 0 && prev == 0 && m_liveDecodedResources.m_head != resource) |
|
590 return; |
|
591 |
|
592 resource->m_nextInLiveResourcesList = 0; |
|
593 resource->m_prevInLiveResourcesList = 0; |
|
594 |
|
595 if (next) |
|
596 next->m_prevInLiveResourcesList = prev; |
|
597 else if (m_liveDecodedResources.m_tail == resource) |
|
598 m_liveDecodedResources.m_tail = prev; |
|
599 |
|
600 if (prev) |
|
601 prev->m_nextInLiveResourcesList = next; |
|
602 else if (m_liveDecodedResources.m_head == resource) |
|
603 m_liveDecodedResources.m_head = next; |
|
604 } |
|
605 |
|
606 void Cache::insertInLiveDecodedResourcesList(CachedResource* resource) |
|
607 { |
|
608 // Make sure we aren't in the list already. |
|
609 ASSERT(!resource->m_nextInLiveResourcesList && !resource->m_prevInLiveResourcesList && !resource->m_inLiveDecodedResourcesList); |
|
610 resource->m_inLiveDecodedResourcesList = true; |
|
611 |
|
612 resource->m_nextInLiveResourcesList = m_liveDecodedResources.m_head; |
|
613 if (m_liveDecodedResources.m_head) |
|
614 m_liveDecodedResources.m_head->m_prevInLiveResourcesList = resource; |
|
615 m_liveDecodedResources.m_head = resource; |
|
616 |
|
617 if (!resource->m_nextInLiveResourcesList) |
|
618 m_liveDecodedResources.m_tail = resource; |
|
619 |
|
620 #ifndef NDEBUG |
|
621 // Verify that we are in now in the list like we should be. |
|
622 bool found = false; |
|
623 for (CachedResource* current = m_liveDecodedResources.m_head; current; current = current->m_nextInLiveResourcesList) { |
|
624 if (current == resource) { |
|
625 found = true; |
|
626 break; |
|
627 } |
|
628 } |
|
629 ASSERT(found); |
|
630 #endif |
|
631 |
|
632 } |
|
633 |
|
634 void Cache::addToLiveResourcesSize(CachedResource* resource) |
|
635 { |
|
636 m_liveSize += resource->size(); |
|
637 m_deadSize -= resource->size(); |
|
638 } |
|
639 |
|
640 void Cache::removeFromLiveResourcesSize(CachedResource* resource) |
|
641 { |
|
642 m_liveSize -= resource->size(); |
|
643 m_deadSize += resource->size(); |
|
644 } |
|
645 |
|
646 void Cache::adjustSize(bool live, int delta) |
|
647 { |
|
648 if (live) { |
|
649 ASSERT(delta >= 0 || ((int)m_liveSize + delta >= 0)); |
|
650 m_liveSize += delta; |
|
651 } else { |
|
652 ASSERT(delta >= 0 || ((int)m_deadSize + delta >= 0)); |
|
653 m_deadSize += delta; |
|
654 } |
|
655 } |
|
656 |
|
657 void Cache::TypeStatistic::addResource(CachedResource* o) |
|
658 { |
|
659 bool purged = o->wasPurged(); |
|
660 bool purgeable = o->isPurgeable() && !purged; |
|
661 int pageSize = (o->encodedSize() + o->overheadSize() + 4095) & ~4095; |
|
662 count++; |
|
663 size += purged ? 0 : o->size(); |
|
664 liveSize += o->hasClients() ? o->size() : 0; |
|
665 decodedSize += o->decodedSize(); |
|
666 purgeableSize += purgeable ? pageSize : 0; |
|
667 purgedSize += purged ? pageSize : 0; |
|
668 } |
|
669 |
|
670 Cache::Statistics Cache::getStatistics() |
|
671 { |
|
672 Statistics stats; |
|
673 CachedResourceMap::iterator e = m_resources.end(); |
|
674 for (CachedResourceMap::iterator i = m_resources.begin(); i != e; ++i) { |
|
675 CachedResource* resource = i->second; |
|
676 switch (resource->type()) { |
|
677 case CachedResource::ImageResource: |
|
678 stats.images.addResource(resource); |
|
679 break; |
|
680 case CachedResource::CSSStyleSheet: |
|
681 stats.cssStyleSheets.addResource(resource); |
|
682 break; |
|
683 case CachedResource::Script: |
|
684 stats.scripts.addResource(resource); |
|
685 break; |
|
686 #if ENABLE(XSLT) |
|
687 case CachedResource::XSLStyleSheet: |
|
688 stats.xslStyleSheets.addResource(resource); |
|
689 break; |
|
690 #endif |
|
691 case CachedResource::FontResource: |
|
692 stats.fonts.addResource(resource); |
|
693 break; |
|
694 #if ENABLE(XBL) |
|
695 case CachedResource::XBL: |
|
696 stats.xblDocs.addResource(resource) |
|
697 break; |
|
698 #endif |
|
699 default: |
|
700 break; |
|
701 } |
|
702 } |
|
703 return stats; |
|
704 } |
|
705 |
|
706 void Cache::setDisabled(bool disabled) |
|
707 { |
|
708 m_disabled = disabled; |
|
709 if (!m_disabled) |
|
710 return; |
|
711 |
|
712 for (;;) { |
|
713 CachedResourceMap::iterator i = m_resources.begin(); |
|
714 if (i == m_resources.end()) |
|
715 break; |
|
716 evict(i->second); |
|
717 } |
|
718 } |
|
719 |
|
720 #ifndef NDEBUG |
|
721 void Cache::dumpStats() |
|
722 { |
|
723 Statistics s = getStatistics(); |
|
724 printf("%-11s %-11s %-11s %-11s %-11s %-11s %-11s\n", "", "Count", "Size", "LiveSize", "DecodedSize", "PurgeableSize", "PurgedSize"); |
|
725 printf("%-11s %-11s %-11s %-11s %-11s %-11s %-11s\n", "-----------", "-----------", "-----------", "-----------", "-----------", "-----------", "-----------"); |
|
726 printf("%-11s %11d %11d %11d %11d %11d %11d\n", "Images", s.images.count, s.images.size, s.images.liveSize, s.images.decodedSize, s.images.purgeableSize, s.images.purgedSize); |
|
727 printf("%-11s %11d %11d %11d %11d %11d %11d\n", "CSS", s.cssStyleSheets.count, s.cssStyleSheets.size, s.cssStyleSheets.liveSize, s.cssStyleSheets.decodedSize, s.cssStyleSheets.purgeableSize, s.cssStyleSheets.purgedSize); |
|
728 #if ENABLE(XSLT) |
|
729 printf("%-11s %11d %11d %11d %11d %11d %11d\n", "XSL", s.xslStyleSheets.count, s.xslStyleSheets.size, s.xslStyleSheets.liveSize, s.xslStyleSheets.decodedSize, s.xslStyleSheets.purgeableSize, s.xslStyleSheets.purgedSize); |
|
730 #endif |
|
731 printf("%-11s %11d %11d %11d %11d %11d %11d\n", "JavaScript", s.scripts.count, s.scripts.size, s.scripts.liveSize, s.scripts.decodedSize, s.scripts.purgeableSize, s.scripts.purgedSize); |
|
732 printf("%-11s %11d %11d %11d %11d %11d %11d\n", "Fonts", s.fonts.count, s.fonts.size, s.fonts.liveSize, s.fonts.decodedSize, s.fonts.purgeableSize, s.fonts.purgedSize); |
|
733 printf("%-11s %-11s %-11s %-11s %-11s %-11s %-11s\n\n", "-----------", "-----------", "-----------", "-----------", "-----------", "-----------", "-----------"); |
|
734 } |
|
735 |
|
736 void Cache::dumpLRULists(bool includeLive) const |
|
737 { |
|
738 printf("LRU-SP lists in eviction order (Kilobytes decoded, Kilobytes encoded, Access count, Referenced):\n"); |
|
739 |
|
740 int size = m_allResources.size(); |
|
741 for (int i = size - 1; i >= 0; i--) { |
|
742 printf("\n\nList %d: ", i); |
|
743 CachedResource* current = m_allResources[i].m_tail; |
|
744 while (current) { |
|
745 CachedResource* prev = current->m_prevInAllResourcesList; |
|
746 if (includeLive || !current->hasClients()) |
|
747 printf("(%.1fK, %.1fK, %uA, %dR); ", current->decodedSize() / 1024.0f, (current->encodedSize() + current->overheadSize()) / 1024.0f, current->accessCount(), current->hasClients()); |
|
748 current = prev; |
|
749 } |
|
750 } |
|
751 } |
|
752 #endif |
|
753 |
|
754 } // namespace WebCore |