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