1c5f0b72443921ac25a1d00be1ae0d6697022973
[WebKit-https.git] / WebCore / loader / DocLoader.cpp
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, 2008 Apple Inc. All rights reserved.
6     Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/
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     This class provides all functionality needed for loading images, style sheets and html
24     pages from the web. It has a memory cache for these objects.
25 */
26
27 #include "config.h"
28 #include "DocLoader.h"
29
30 #include "Cache.h"
31 #include "CachedCSSStyleSheet.h"
32 #include "CachedFont.h"
33 #include "CachedImage.h"
34 #include "CachedScript.h"
35 #include "CachedXSLStyleSheet.h"
36 #include "Console.h"
37 #include "CString.h"
38 #include "Document.h"
39 #include "DOMWindow.h"
40 #include "Frame.h"
41 #include "FrameLoader.h"
42 #include "loader.h"
43 #include "SecurityOrigin.h"
44 #include "Settings.h"
45
46 #define PRELOAD_DEBUG 0
47
48 namespace WebCore {
49
50 DocLoader::DocLoader(Document* doc)
51     : m_cache(cache())
52     , m_doc(doc)
53     , m_requestCount(0)
54     , m_autoLoadImages(true)
55     , m_loadInProgress(false)
56     , m_allowStaleResources(false)
57 {
58     m_cache->addDocLoader(this);
59 }
60
61 DocLoader::~DocLoader()
62 {
63     clearPreloads();
64     DocumentResourceMap::iterator end = m_documentResources.end();
65     for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it)
66         it->second->setDocLoader(0);
67     m_cache->removeDocLoader(this);
68 }
69
70 Frame* DocLoader::frame() const
71 {
72     return m_doc->frame();
73 }
74
75 void DocLoader::checkForReload(const KURL& fullURL)
76 {
77     if (m_allowStaleResources)
78         return; // Don't reload resources while pasting
79
80     if (fullURL.isEmpty())
81         return;
82     
83     if (m_reloadedURLs.contains(fullURL.string()))
84         return;
85     
86     CachedResource* existing = cache()->resourceForURL(fullURL.string());
87     if (!existing || existing->isPreloaded())
88         return;
89
90     switch (cachePolicy()) {
91     case CachePolicyVerify:
92         if (!existing->mustRevalidate(CachePolicyVerify))
93             return;
94         cache()->revalidateResource(existing, this);
95         break;
96     case CachePolicyCache:
97         if (!existing->mustRevalidate(CachePolicyCache))
98             return;
99         cache()->revalidateResource(existing, this);
100         break;
101     case CachePolicyReload:
102         cache()->remove(existing);        
103         break;
104     case CachePolicyRevalidate:
105         cache()->revalidateResource(existing, this);
106         break;
107     default:
108         ASSERT_NOT_REACHED();
109     }
110
111     m_reloadedURLs.add(fullURL.string());
112 }
113
114 CachedImage* DocLoader::requestImage(const String& url)
115 {
116     CachedImage* resource = static_cast<CachedImage*>(requestResource(CachedResource::ImageResource, url, String()));
117     if (autoLoadImages() && resource && resource->stillNeedsLoad()) {
118         resource->setLoading(true);
119         cache()->loader()->load(this, resource, true);
120     }
121     return resource;
122 }
123
124 CachedFont* DocLoader::requestFont(const String& url)
125 {
126     return static_cast<CachedFont*>(requestResource(CachedResource::FontResource, url, String()));
127 }
128
129 CachedCSSStyleSheet* DocLoader::requestCSSStyleSheet(const String& url, const String& charset)
130 {
131     return static_cast<CachedCSSStyleSheet*>(requestResource(CachedResource::CSSStyleSheet, url, charset));
132 }
133
134 CachedCSSStyleSheet* DocLoader::requestUserCSSStyleSheet(const String& url, const String& charset)
135 {
136     return cache()->requestUserCSSStyleSheet(this, url, charset);
137 }
138
139 CachedScript* DocLoader::requestScript(const String& url, const String& charset)
140 {
141     return static_cast<CachedScript*>(requestResource(CachedResource::Script, url, charset));
142 }
143
144 #if ENABLE(XSLT)
145 CachedXSLStyleSheet* DocLoader::requestXSLStyleSheet(const String& url)
146 {
147     return static_cast<CachedXSLStyleSheet*>(requestResource(CachedResource::XSLStyleSheet, url, String()));
148 }
149 #endif
150
151 #if ENABLE(XBL)
152 CachedXBLDocument* DocLoader::requestXBLDocument(const String& url)
153 {
154     return static_cast<CachedXSLStyleSheet*>(requestResource(CachedResource::XBL, url, String()));
155 }
156 #endif
157
158 bool DocLoader::canRequest(CachedResource::Type type, const KURL& url)
159 {
160     // Some types of resources can be loaded only from the same origin.  Other
161     // types of resources, like Images, Scripts, and CSS, can be loaded from
162     // any URL.
163     switch (type) {
164     case CachedResource::ImageResource:
165     case CachedResource::CSSStyleSheet:
166     case CachedResource::Script:
167     case CachedResource::FontResource:
168         // These types of resources can be loaded from any origin.
169         // FIXME: Are we sure about CachedResource::FontResource?
170         break;
171 #if ENABLE(XSLT)
172     case CachedResource::XSLStyleSheet:
173 #endif
174 #if ENABLE(XBL)
175     case CachedResource::XBL:
176 #endif
177 #if ENABLE(XSLT) || ENABLE(XBL)
178         if (!m_doc->securityOrigin()->canRequest(url)) {
179             printAccessDeniedMessage(url);
180             return false;
181         }
182         break;
183 #endif
184     default:
185         ASSERT_NOT_REACHED();
186         break;
187     }
188     return true;
189 }
190
191 CachedResource* DocLoader::requestResource(CachedResource::Type type, const String& url, const String& charset, bool isPreload)
192 {
193     KURL fullURL = m_doc->completeURL(url);
194
195     if (!fullURL.isValid() || !canRequest(type, fullURL))
196         return 0;
197
198     if (cache()->disabled()) {
199         DocumentResourceMap::iterator it = m_documentResources.find(fullURL.string());
200         
201         if (it != m_documentResources.end()) {
202             it->second->setDocLoader(0);
203             m_documentResources.remove(it);
204         }
205     }
206
207     checkForReload(fullURL);
208
209     CachedResource* resource = cache()->requestResource(this, type, fullURL, charset, isPreload);
210     if (resource) {
211         // Check final URL of resource to catch redirects.
212         // See <https://bugs.webkit.org/show_bug.cgi?id=21963>.
213         if (!canRequest(type, KURL(resource->url())))
214             return 0;
215
216         m_documentResources.set(resource->url(), resource);
217         checkCacheObjectStatus(resource);
218     }
219     return resource;
220 }
221
222 void DocLoader::printAccessDeniedMessage(const KURL& url) const
223 {
224     if (url.isNull())
225         return;
226
227     if (!frame())
228         return;
229
230     Settings* settings = frame()->settings();
231     if (!settings || settings->privateBrowsingEnabled())
232         return;
233
234     String message = m_doc->url().isNull() ?
235         String::format("Unsafe attempt to load URL %s.",
236                        url.string().utf8().data()) :
237         String::format("Unsafe attempt to load URL %s from frame with URL %s. "
238                        "Domains, protocols and ports must match.\n",
239                        url.string().utf8().data(),
240                        m_doc->url().string().utf8().data());
241
242     // FIXME: provide a real line number and source URL.
243     frame()->domWindow()->console()->addMessage(OtherMessageSource, ErrorMessageLevel, message, 1, String());
244 }
245
246 void DocLoader::setAutoLoadImages(bool enable)
247 {
248     if (enable == m_autoLoadImages)
249         return;
250
251     m_autoLoadImages = enable;
252
253     if (!m_autoLoadImages)
254         return;
255
256     DocumentResourceMap::iterator end = m_documentResources.end();
257     for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) {
258         CachedResource* resource = it->second.get();
259         if (resource->type() == CachedResource::ImageResource) {
260             CachedImage* image = const_cast<CachedImage*>(static_cast<const CachedImage*>(resource));
261
262             if (image->stillNeedsLoad())
263                 cache()->loader()->load(this, image, true);
264         }
265     }
266 }
267
268 CachePolicy DocLoader::cachePolicy() const
269 {
270     return frame() ? frame()->loader()->cachePolicy() : CachePolicyVerify;
271 }
272
273 void DocLoader::removeCachedResource(CachedResource* resource) const
274 {
275     m_documentResources.remove(resource->url());
276 }
277
278 void DocLoader::setLoadInProgress(bool load)
279 {
280     m_loadInProgress = load;
281     if (!load && frame())
282         frame()->loader()->loadDone();
283 }
284
285 void DocLoader::checkCacheObjectStatus(CachedResource* resource)
286 {
287     // Return from the function for objects that we didn't load from the cache or if we don't have a frame.
288     if (!resource || !frame())
289         return;
290
291     switch (resource->status()) {
292         case CachedResource::Cached:
293             break;
294         case CachedResource::NotCached:
295         case CachedResource::Unknown:
296         case CachedResource::New:
297         case CachedResource::Pending:
298             return;
299     }
300
301     // FIXME: If the WebKit client changes or cancels the request, WebCore does not respect this and continues the load.
302     frame()->loader()->loadedResourceFromMemoryCache(resource);
303 }
304
305 void DocLoader::incrementRequestCount()
306 {
307     ++m_requestCount;
308 }
309
310 void DocLoader::decrementRequestCount()
311 {
312     --m_requestCount;
313     ASSERT(m_requestCount > -1);
314 }
315
316 int DocLoader::requestCount()
317 {
318     if (loadInProgress())
319          return m_requestCount + 1;
320     return m_requestCount;
321 }
322     
323 void DocLoader::preload(CachedResource::Type type, const String& url, const String& charset, bool referencedFromBody)
324 {
325     bool hasRendering = m_doc->body() && m_doc->body()->renderer();
326     if (!hasRendering && (referencedFromBody || type == CachedResource::ImageResource)) {
327         // Don't preload images or body resources before we have something to draw. This prevents
328         // preloads from body delaying first display when bandwidth is limited.
329         PendingPreload pendingPreload = { type, url, charset };
330         m_pendingPreloads.append(pendingPreload);
331         return;
332     }
333     requestPreload(type, url, charset);
334 }
335
336 void DocLoader::checkForPendingPreloads() 
337 {
338     unsigned count = m_pendingPreloads.size();
339     if (!count || !m_doc->body() || !m_doc->body()->renderer())
340         return;
341     for (unsigned i = 0; i < count; ++i) {
342         PendingPreload& preload = m_pendingPreloads[i];
343         requestPreload(preload.m_type, preload.m_url, preload.m_charset);
344     }
345     m_pendingPreloads.clear();
346 }
347
348 void DocLoader::requestPreload(CachedResource::Type type, const String& url, const String& charset)
349 {
350     String encoding;
351     if (type == CachedResource::Script || type == CachedResource::CSSStyleSheet)
352         encoding = charset.isEmpty() ? m_doc->frame()->loader()->encoding() : charset;
353
354     CachedResource* resource = requestResource(type, url, encoding, true);
355     if (!resource || m_preloads.contains(resource))
356         return;
357     resource->increasePreloadCount();
358     m_preloads.add(resource);
359 #if PRELOAD_DEBUG
360     printf("PRELOADING %s\n",  resource->url().latin1().data());
361 #endif
362 }
363
364 void DocLoader::clearPreloads()
365 {
366 #if PRELOAD_DEBUG
367     printPreloadStats();
368 #endif
369     ListHashSet<CachedResource*>::iterator end = m_preloads.end();
370     for (ListHashSet<CachedResource*>::iterator it = m_preloads.begin(); it != end; ++it) {
371         CachedResource* res = *it;
372         res->decreasePreloadCount();
373         if (res->canDelete() && !res->inCache())
374             delete res;
375         else if (res->preloadResult() == CachedResource::PreloadNotReferenced)
376             cache()->remove(res);
377     }
378     m_preloads.clear();
379 }
380
381 void DocLoader::clearPendingPreloads()
382 {
383     m_pendingPreloads.clear();
384 }
385
386 #if PRELOAD_DEBUG
387 void DocLoader::printPreloadStats()
388 {
389     unsigned scripts = 0;
390     unsigned scriptMisses = 0;
391     unsigned stylesheets = 0;
392     unsigned stylesheetMisses = 0;
393     unsigned images = 0;
394     unsigned imageMisses = 0;
395     ListHashSet<CachedResource*>::iterator end = m_preloads.end();
396     for (ListHashSet<CachedResource*>::iterator it = m_preloads.begin(); it != end; ++it) {
397         CachedResource* res = *it;
398         if (res->preloadResult() == CachedResource::PreloadNotReferenced)
399             printf("!! UNREFERENCED PRELOAD %s\n", res->url().latin1().data());
400         else if (res->preloadResult() == CachedResource::PreloadReferencedWhileComplete)
401             printf("HIT COMPLETE PRELOAD %s\n", res->url().latin1().data());
402         else if (res->preloadResult() == CachedResource::PreloadReferencedWhileLoading)
403             printf("HIT LOADING PRELOAD %s\n", res->url().latin1().data());
404         
405         if (res->type() == CachedResource::Script) {
406             scripts++;
407             if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading)
408                 scriptMisses++;
409         } else if (res->type() == CachedResource::CSSStyleSheet) {
410             stylesheets++;
411             if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading)
412                 stylesheetMisses++;
413         } else {
414             images++;
415             if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading)
416                 imageMisses++;
417         }
418         
419         if (res->errorOccurred())
420             cache()->remove(res);
421         
422         res->decreasePreloadCount();
423     }
424     m_preloads.clear();
425     
426     if (scripts)
427         printf("SCRIPTS: %d (%d hits, hit rate %d%%)\n", scripts, scripts - scriptMisses, (scripts - scriptMisses) * 100 / scripts);
428     if (stylesheets)
429         printf("STYLESHEETS: %d (%d hits, hit rate %d%%)\n", stylesheets, stylesheets - stylesheetMisses, (stylesheets - stylesheetMisses) * 100 / stylesheets);
430     if (images)
431         printf("IMAGES:  %d (%d hits, hit rate %d%%)\n", images, images - imageMisses, (images - imageMisses) * 100 / images);
432 }
433 #endif
434     
435 }