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