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