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