• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 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 COMPUTER, 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 COMPUTER, 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 "PageCache.h"
28 
29 #include "ApplicationCacheHost.h"
30 #include "BackForwardController.h"
31 #include "MemoryCache.h"
32 #include "CachedPage.h"
33 #include "DOMWindow.h"
34 #include "DeviceMotionController.h"
35 #include "DeviceOrientationController.h"
36 #include "Document.h"
37 #include "DocumentLoader.h"
38 #include "Frame.h"
39 #include "FrameLoader.h"
40 #include "FrameLoaderClient.h"
41 #include "FrameLoaderStateMachine.h"
42 #include "HistoryItem.h"
43 #include "Logging.h"
44 #include "Page.h"
45 #include "Settings.h"
46 #include "SharedWorkerRepository.h"
47 #include "SystemTime.h"
48 #include <wtf/CurrentTime.h>
49 #include <wtf/text/CString.h>
50 #include <wtf/text/StringConcatenate.h>
51 
52 using namespace std;
53 
54 namespace WebCore {
55 
56 static const double autoreleaseInterval = 3;
57 
58 #ifndef NDEBUG
59 
pageCacheLogPrefix(int indentLevel)60 static String& pageCacheLogPrefix(int indentLevel)
61 {
62     static int previousIndent = -1;
63     DEFINE_STATIC_LOCAL(String, prefix, ());
64 
65     if (indentLevel != previousIndent) {
66         previousIndent = indentLevel;
67         prefix.truncate(0);
68         for (int i = 0; i < previousIndent; ++i)
69             prefix += "    ";
70     }
71 
72     return prefix;
73 }
74 
pageCacheLog(const String & prefix,const String & message)75 static void pageCacheLog(const String& prefix, const String& message)
76 {
77     LOG(PageCache, "%s%s", prefix.utf8().data(), message.utf8().data());
78 }
79 
80 #define PCLOG(...) pageCacheLog(pageCacheLogPrefix(indentLevel), makeString(__VA_ARGS__))
81 
logCanCacheFrameDecision(Frame * frame,int indentLevel)82 static bool logCanCacheFrameDecision(Frame* frame, int indentLevel)
83 {
84     // Only bother logging for frames that have actually loaded and have content.
85     if (frame->loader()->stateMachine()->creatingInitialEmptyDocument())
86         return false;
87     KURL currentURL = frame->loader()->documentLoader() ? frame->loader()->documentLoader()->url() : KURL();
88     if (currentURL.isEmpty())
89         return false;
90 
91     PCLOG("+---");
92     KURL newURL = frame->loader()->provisionalDocumentLoader() ? frame->loader()->provisionalDocumentLoader()->url() : KURL();
93     if (!newURL.isEmpty())
94         PCLOG(" Determining if frame can be cached navigating from (", currentURL.string(), ") to (", newURL.string(), "):");
95     else
96         PCLOG(" Determining if subframe with URL (", currentURL.string(), ") can be cached:");
97 
98     bool cannotCache = false;
99 
100     do {
101         if (!frame->loader()->documentLoader()) {
102             PCLOG("   -There is no DocumentLoader object");
103             cannotCache = true;
104             break;
105         }
106         if (!frame->loader()->documentLoader()->mainDocumentError().isNull()) {
107             PCLOG("   -Main document has an error");
108             cannotCache = true;
109         }
110         if (frame->loader()->subframeLoader()->containsPlugins()) {
111             PCLOG("   -Frame contains plugins");
112             cannotCache = true;
113         }
114         if (frame->document()->url().protocolIs("https")) {
115             PCLOG("   -Frame is HTTPS");
116             cannotCache = true;
117         }
118         if (frame->domWindow() && frame->domWindow()->hasEventListeners(eventNames().unloadEvent)) {
119             PCLOG("   -Frame has an unload event listener");
120             cannotCache = true;
121         }
122 #if ENABLE(DATABASE)
123         if (frame->document()->hasOpenDatabases()) {
124             PCLOG("   -Frame has open database handles");
125             cannotCache = true;
126         }
127 #endif
128 #if ENABLE(SHARED_WORKERS)
129         if (SharedWorkerRepository::hasSharedWorkers(frame->document())) {
130             PCLOG("   -Frame has associated SharedWorkers");
131             cannotCache = true;
132         }
133 #endif
134         if (frame->document()->usingGeolocation()) {
135             PCLOG("   -Frame uses Geolocation");
136             cannotCache = true;
137         }
138         if (!frame->loader()->history()->currentItem()) {
139             PCLOG("   -No current history item");
140             cannotCache = true;
141         }
142         if (frame->loader()->quickRedirectComing()) {
143             PCLOG("   -Quick redirect is coming");
144             cannotCache = true;
145         }
146         if (frame->loader()->documentLoader()->isLoadingInAPISense()) {
147             PCLOG("   -DocumentLoader is still loading in API sense");
148             cannotCache = true;
149         }
150         if (frame->loader()->documentLoader()->isStopping()) {
151             PCLOG("   -DocumentLoader is in the middle of stopping");
152             cannotCache = true;
153         }
154         if (!frame->document()->canSuspendActiveDOMObjects()) {
155             PCLOG("   -The document cannot suspect its active DOM Objects");
156             cannotCache = true;
157         }
158 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
159         if (!frame->loader()->documentLoader()->applicationCacheHost()->canCacheInPageCache()) {
160             PCLOG("   -The DocumentLoader uses an application cache");
161             cannotCache = true;
162         }
163 #endif
164         if (!frame->loader()->client()->canCachePage()) {
165             PCLOG("   -The client says this frame cannot be cached");
166             cannotCache = true;
167         }
168     } while (false);
169 
170     for (Frame* child = frame->tree()->firstChild(); child; child = child->tree()->nextSibling())
171         if (!logCanCacheFrameDecision(child, indentLevel + 1))
172             cannotCache = true;
173 
174     PCLOG(cannotCache ? " Frame CANNOT be cached" : " Frame CAN be cached");
175     PCLOG("+---");
176 
177     return !cannotCache;
178 }
179 
logCanCachePageDecision(Page * page)180 static void logCanCachePageDecision(Page* page)
181 {
182     // Only bother logging for main frames that have actually loaded and have content.
183     if (page->mainFrame()->loader()->stateMachine()->creatingInitialEmptyDocument())
184         return;
185     KURL currentURL = page->mainFrame()->loader()->documentLoader() ? page->mainFrame()->loader()->documentLoader()->url() : KURL();
186     if (currentURL.isEmpty())
187         return;
188 
189     int indentLevel = 0;
190     PCLOG("--------\n Determining if page can be cached:");
191 
192     bool cannotCache = !logCanCacheFrameDecision(page->mainFrame(), 1);
193 
194     FrameLoadType loadType = page->mainFrame()->loader()->loadType();
195     if (!page->backForward()->isActive()) {
196         PCLOG("   -The back/forward list is disabled or has 0 capacity");
197         cannotCache = true;
198     }
199     if (!page->settings()->usesPageCache()) {
200         PCLOG("   -Page settings says b/f cache disabled");
201         cannotCache = true;
202     }
203 #if ENABLE(DEVICE_ORIENTATION)
204     if (page->deviceMotionController() && page->deviceMotionController()->isActive()) {
205         PCLOG("   -Page is using DeviceMotion");
206         cannotCache = true;
207     }
208     if (page->deviceOrientationController() && page->deviceOrientationController()->isActive()) {
209         PCLOG("   -Page is using DeviceOrientation");
210         cannotCache = true;
211     }
212 #endif
213     if (loadType == FrameLoadTypeReload) {
214         PCLOG("   -Load type is: Reload");
215         cannotCache = true;
216     }
217     if (loadType == FrameLoadTypeReloadFromOrigin) {
218         PCLOG("   -Load type is: Reload from origin");
219         cannotCache = true;
220     }
221     if (loadType == FrameLoadTypeSame) {
222         PCLOG("   -Load type is: Same");
223         cannotCache = true;
224     }
225 
226     PCLOG(cannotCache ? " Page CANNOT be cached\n--------" : " Page CAN be cached\n--------");
227 }
228 
229 #endif
230 
pageCache()231 PageCache* pageCache()
232 {
233     static PageCache* staticPageCache = new PageCache;
234     return staticPageCache;
235 }
236 
PageCache()237 PageCache::PageCache()
238     : m_capacity(0)
239     , m_size(0)
240     , m_head(0)
241     , m_tail(0)
242     , m_autoreleaseTimer(this, &PageCache::releaseAutoreleasedPagesNowOrReschedule)
243 {
244 }
245 
canCachePageContainingThisFrame(Frame * frame)246 bool PageCache::canCachePageContainingThisFrame(Frame* frame)
247 {
248     for (Frame* child = frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) {
249         if (!canCachePageContainingThisFrame(child))
250             return false;
251     }
252 
253     return frame->loader()->documentLoader()
254         && frame->loader()->documentLoader()->mainDocumentError().isNull()
255         // Do not cache error pages (these can be recognized as pages with substitute data or unreachable URLs).
256         && !(frame->loader()->documentLoader()->substituteData().isValid() && !frame->loader()->documentLoader()->substituteData().failingURL().isEmpty())
257         // FIXME: If we ever change this so that frames with plug-ins will be cached,
258         // we need to make sure that we don't cache frames that have outstanding NPObjects
259         // (objects created by the plug-in). Since there is no way to pause/resume a Netscape plug-in,
260         // they would need to be destroyed and then recreated, and there is no way that we can recreate
261         // the right NPObjects. See <rdar://problem/5197041> for more information.
262         && !frame->loader()->subframeLoader()->containsPlugins()
263         && !frame->document()->url().protocolIs("https")
264         && (!frame->domWindow() || !frame->domWindow()->hasEventListeners(eventNames().unloadEvent))
265 #if ENABLE(DATABASE)
266         && !frame->document()->hasOpenDatabases()
267 #endif
268 #if ENABLE(SHARED_WORKERS)
269         && !SharedWorkerRepository::hasSharedWorkers(frame->document())
270 #endif
271         && !frame->document()->usingGeolocation()
272         && frame->loader()->history()->currentItem()
273         && !frame->loader()->quickRedirectComing()
274         && !frame->loader()->documentLoader()->isLoadingInAPISense()
275         && !frame->loader()->documentLoader()->isStopping()
276         && frame->document()->canSuspendActiveDOMObjects()
277 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
278         // FIXME: We should investigating caching frames that have an associated
279         // application cache. <rdar://problem/5917899> tracks that work.
280         && frame->loader()->documentLoader()->applicationCacheHost()->canCacheInPageCache()
281 #endif
282 #if ENABLE(WML)
283         && !frame->document()->containsWMLContent()
284         && !frame->document()->isWMLDocument()
285 #endif
286         && frame->loader()->client()->canCachePage();
287 }
288 
canCache(Page * page)289 bool PageCache::canCache(Page* page)
290 {
291     if (!page)
292         return false;
293 
294 #ifndef NDEBUG
295     logCanCachePageDecision(page);
296 #endif
297 
298     // Cache the page, if possible.
299     // Don't write to the cache if in the middle of a redirect, since we will want to
300     // store the final page we end up on.
301     // No point writing to the cache on a reload or loadSame, since we will just write
302     // over it again when we leave that page.
303     // FIXME: <rdar://problem/4886592> - We should work out the complexities of caching pages with frames as they
304     // are the most interesting pages on the web, and often those that would benefit the most from caching!
305     FrameLoadType loadType = page->mainFrame()->loader()->loadType();
306 
307     return canCachePageContainingThisFrame(page->mainFrame())
308         && page->backForward()->isActive()
309         && page->settings()->usesPageCache()
310 #if ENABLE(DEVICE_ORIENTATION)
311         && !(page->deviceMotionController() && page->deviceMotionController()->isActive())
312         && !(page->deviceOrientationController() && page->deviceOrientationController()->isActive())
313 #endif
314         && loadType != FrameLoadTypeReload
315         && loadType != FrameLoadTypeReloadFromOrigin
316         && loadType != FrameLoadTypeSame;
317 }
318 
setCapacity(int capacity)319 void PageCache::setCapacity(int capacity)
320 {
321     ASSERT(capacity >= 0);
322     m_capacity = max(capacity, 0);
323 
324     prune();
325 }
326 
frameCount() const327 int PageCache::frameCount() const
328 {
329     int frameCount = 0;
330     for (HistoryItem* current = m_head; current; current = current->m_next) {
331         ++frameCount;
332         ASSERT(current->m_cachedPage);
333         frameCount += current->m_cachedPage ? current->m_cachedPage->cachedMainFrame()->descendantFrameCount() : 0;
334     }
335 
336     return frameCount;
337 }
338 
autoreleasedPageCount() const339 int PageCache::autoreleasedPageCount() const
340 {
341     return m_autoreleaseSet.size();
342 }
343 
markPagesForVistedLinkStyleRecalc()344 void PageCache::markPagesForVistedLinkStyleRecalc()
345 {
346     for (HistoryItem* current = m_head; current; current = current->m_next)
347         current->m_cachedPage->markForVistedLinkStyleRecalc();
348 }
349 
add(PassRefPtr<HistoryItem> prpItem,Page * page)350 void PageCache::add(PassRefPtr<HistoryItem> prpItem, Page* page)
351 {
352     ASSERT(prpItem);
353     ASSERT(page);
354     ASSERT(canCache(page));
355 
356     HistoryItem* item = prpItem.releaseRef(); // Balanced in remove().
357 
358     // Remove stale cache entry if necessary.
359     if (item->m_cachedPage)
360         remove(item);
361 
362     item->m_cachedPage = CachedPage::create(page);
363     addToLRUList(item);
364     ++m_size;
365 
366     prune();
367 }
368 
get(HistoryItem * item)369 CachedPage* PageCache::get(HistoryItem* item)
370 {
371     if (!item)
372         return 0;
373 
374     if (CachedPage* cachedPage = item->m_cachedPage.get()) {
375         // FIXME: 1800 should not be hardcoded, it should come from
376         // WebKitBackForwardCacheExpirationIntervalKey in WebKit.
377         // Or we should remove WebKitBackForwardCacheExpirationIntervalKey.
378         if (currentTime() - cachedPage->timeStamp() <= 1800)
379             return cachedPage;
380 
381         LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item->url().string().ascii().data());
382         pageCache()->remove(item);
383     }
384     return 0;
385 }
386 
remove(HistoryItem * item)387 void PageCache::remove(HistoryItem* item)
388 {
389     // Safely ignore attempts to remove items not in the cache.
390     if (!item || !item->m_cachedPage)
391         return;
392 
393     autorelease(item->m_cachedPage.release());
394     removeFromLRUList(item);
395     --m_size;
396 
397     item->deref(); // Balanced in add().
398 }
399 
prune()400 void PageCache::prune()
401 {
402     while (m_size > m_capacity) {
403         ASSERT(m_tail && m_tail->m_cachedPage);
404         remove(m_tail);
405     }
406 }
407 
addToLRUList(HistoryItem * item)408 void PageCache::addToLRUList(HistoryItem* item)
409 {
410     item->m_next = m_head;
411     item->m_prev = 0;
412 
413     if (m_head) {
414         ASSERT(m_tail);
415         m_head->m_prev = item;
416     } else {
417         ASSERT(!m_tail);
418         m_tail = item;
419     }
420 
421     m_head = item;
422 }
423 
removeFromLRUList(HistoryItem * item)424 void PageCache::removeFromLRUList(HistoryItem* item)
425 {
426     if (!item->m_next) {
427         ASSERT(item == m_tail);
428         m_tail = item->m_prev;
429     } else {
430         ASSERT(item != m_tail);
431         item->m_next->m_prev = item->m_prev;
432     }
433 
434     if (!item->m_prev) {
435         ASSERT(item == m_head);
436         m_head = item->m_next;
437     } else {
438         ASSERT(item != m_head);
439         item->m_prev->m_next = item->m_next;
440     }
441 }
442 
releaseAutoreleasedPagesNowOrReschedule(Timer<PageCache> * timer)443 void PageCache::releaseAutoreleasedPagesNowOrReschedule(Timer<PageCache>* timer)
444 {
445     double loadDelta = currentTime() - FrameLoader::timeOfLastCompletedLoad();
446     float userDelta = userIdleTime();
447 
448     // FIXME: <rdar://problem/5211190> This limit of 42 risks growing the page cache far beyond its nominal capacity.
449     if ((userDelta < 0.5 || loadDelta < 1.25) && m_autoreleaseSet.size() < 42) {
450         LOG(PageCache, "WebCorePageCache: Postponing releaseAutoreleasedPagesNowOrReschedule() - %f since last load, %f since last input, %i objects pending release", loadDelta, userDelta, m_autoreleaseSet.size());
451         timer->startOneShot(autoreleaseInterval);
452         return;
453     }
454 
455     LOG(PageCache, "WebCorePageCache: Releasing page caches - %f seconds since last load, %f since last input, %i objects pending release", loadDelta, userDelta, m_autoreleaseSet.size());
456     releaseAutoreleasedPagesNow();
457 }
458 
releaseAutoreleasedPagesNow()459 void PageCache::releaseAutoreleasedPagesNow()
460 {
461     m_autoreleaseTimer.stop();
462 
463     // Postpone dead pruning until all our resources have gone dead.
464     memoryCache()->setPruneEnabled(false);
465 
466     CachedPageSet tmp;
467     tmp.swap(m_autoreleaseSet);
468 
469     CachedPageSet::iterator end = tmp.end();
470     for (CachedPageSet::iterator it = tmp.begin(); it != end; ++it)
471         (*it)->destroy();
472 
473     // Now do the prune.
474     memoryCache()->setPruneEnabled(true);
475     memoryCache()->prune();
476 }
477 
autorelease(PassRefPtr<CachedPage> page)478 void PageCache::autorelease(PassRefPtr<CachedPage> page)
479 {
480     ASSERT(page);
481     ASSERT(!m_autoreleaseSet.contains(page.get()));
482     m_autoreleaseSet.add(page);
483     if (!m_autoreleaseTimer.isActive())
484         m_autoreleaseTimer.startOneShot(autoreleaseInterval);
485 }
486 
487 } // namespace WebCore
488