don't discard iframe children.
[kdelibs.git] / khtml / misc / loader.cpp
blobf6f9e5aab88a67f479b13e4ea799db75e7e37880
1 /*
2 This file is part of the KDE libraries
4 Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de)
5 Copyright (C) 2001-2003 Dirk Mueller (mueller@kde.org)
6 Copyright (C) 2002 Waldo Bastian (bastian@kde.org)
7 Copyright (C) 2003 Apple Computer, Inc.
9 This library is free software; you can redistribute it and/or
10 modify it under the terms of the GNU Library General Public
11 License as published by the Free Software Foundation; either
12 version 2 of the License, or (at your option) any later version.
14 This library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Library General Public License for more details.
19 You should have received a copy of the GNU Library General Public License
20 along with this library; see the file COPYING.LIB. If not, write to
21 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 Boston, MA 02110-1301, USA.
24 This class provides all functionality needed for loading images, style sheets and html
25 pages from the web. It has a memory cache for these objects.
27 // regarding the LRU:
28 // http://www.is.kyusan-u.ac.jp/~chengk/pub/papers/compsac00_A07-07.pdf
31 #undef CACHE_DEBUG
32 //#define CACHE_DEBUG
34 #ifdef CACHE_DEBUG
35 #define CDEBUG kDebug(6060)
36 #else
37 #define CDEBUG kDebugDevNull()
38 #endif
40 #undef LOADER_DEBUG
41 //#define LOADER_DEBUG
43 //#define PRELOAD_DEBUG
45 #include "loader.h"
46 #include "seed.h"
47 #include <imload/image.h>
48 #include <imload/imagepainter.h>
50 #include <assert.h>
52 // default cache size
53 #define DEFCACHESIZE 2096*1024
54 #define MAX_JOB_COUNT 32
56 //#include <qasyncio.h>
57 //#include <qasyncimageio.h>
58 #include <QtGui/QApplication>
59 #include <QtGui/QDesktopWidget>
60 #include <QtGui/QPainter>
61 #include <QtGui/QBitmap>
62 #include <QtGui/QMovie>
63 #include <QtGui/QWidget>
64 #include <QtCore/QDebug>
65 #include <kauthorized.h>
66 #include <kio/job.h>
67 #include <kio/jobuidelegate.h>
68 #include <kio/jobclasses.h>
69 #include <kglobal.h>
70 #include <kcharsets.h>
71 #include <kiconloader.h>
72 #include <scheduler.h>
73 #include <kdebug.h>
75 #include <khtml_global.h>
76 #include <khtml_part.h>
78 #ifdef IMAGE_TITLES
79 #include <qfile.h>
80 #include <kfilemetainfo.h>
81 #include <ktemporaryfile.h>
82 #endif
84 #include "html/html_documentimpl.h"
85 #include "css/css_stylesheetimpl.h"
86 #include "xml/dom_docimpl.h"
88 #include "blocked_icon.cpp"
90 #include <QPaintEngine>
92 using namespace khtml;
93 using namespace DOM;
94 using namespace khtmlImLoad;
96 #define MAX_LRU_LISTS 20
97 struct LRUList {
98 CachedObject* m_head;
99 CachedObject* m_tail;
101 LRUList() : m_head(0), m_tail(0) {}
104 static LRUList m_LRULists[MAX_LRU_LISTS];
105 static LRUList* getLRUListFor(CachedObject* o);
107 CachedObjectClient::~CachedObjectClient()
111 CachedObject::~CachedObject()
113 Cache::removeFromLRUList(this);
116 void CachedObject::finish()
118 m_status = Cached;
121 bool CachedObject::isExpired() const
123 if (!m_expireDate) return false;
124 time_t now = time(0);
125 return (difftime(now, m_expireDate) >= 0);
128 void CachedObject::setRequest(Request *_request)
130 if ( _request && !m_request )
131 m_status = Pending;
133 if ( allowInLRUList() )
134 Cache::removeFromLRUList( this );
136 m_request = _request;
138 if ( allowInLRUList() )
139 Cache::insertInLRUList( this );
142 void CachedObject::ref(CachedObjectClient *c)
144 if (m_preloadResult == PreloadNotReferenced) {
145 if (isLoaded())
146 m_preloadResult = PreloadReferencedWhileComplete;
147 else if (m_prospectiveRequest)
148 m_preloadResult = PreloadReferencedWhileLoading;
149 else
150 m_preloadResult = PreloadReferenced;
152 // unfortunately we can be ref'ed multiple times from the
153 // same object, because it uses e.g. the same foreground
154 // and the same background picture. so deal with it.
155 // Hence the use of a QHash rather than of a QSet.
156 m_clients.insertMulti(c,c);
157 Cache::removeFromLRUList(this);
158 m_accessCount++;
161 void CachedObject::deref(CachedObjectClient *c)
163 assert( c );
164 assert( m_clients.count() );
165 assert( !canDelete() );
166 assert( m_clients.contains( c ) );
168 Cache::flush();
170 m_clients.take(c);
172 if (allowInLRUList())
173 Cache::insertInLRUList(this);
176 void CachedObject::setSize(int size)
178 bool sizeChanged;
180 if ( !m_next && !m_prev && getLRUListFor(this)->m_head != this )
181 sizeChanged = false;
182 else
183 sizeChanged = ( size - m_size ) != 0;
185 // The object must now be moved to a different queue,
186 // since its size has been changed.
187 if ( sizeChanged && allowInLRUList())
188 Cache::removeFromLRUList(this);
190 m_size = size;
192 if ( sizeChanged && allowInLRUList())
193 Cache::insertInLRUList(this);
196 QTextCodec* CachedObject::codecForBuffer( const QString& charset, const QByteArray& buffer ) const
198 // we don't use heuristicContentMatch here since it is a) far too slow and
199 // b) having too much functionality for our case.
201 uchar* d = ( uchar* ) buffer.data();
202 int s = buffer.size();
204 // BOM
205 if ( s >= 3 &&
206 d[0] == 0xef && d[1] == 0xbb && d[2] == 0xbf)
207 return QTextCodec::codecForMib( 106 ); // UTF-8
209 if ( s >= 2 && ((d[0] == 0xff && d[1] == 0xfe) ||
210 (d[0] == 0xfe && d[1] == 0xff)))
211 return QTextCodec::codecForMib( 1000 ); // UCS-2
213 // Link or @charset
214 if(!charset.isEmpty())
216 QTextCodec* c = KGlobal::charsets()->codecForName(charset);
217 if(c->mibEnum() == 11) {
218 // iso8859-8 (visually ordered)
219 c = QTextCodec::codecForName("iso8859-8-i");
221 return c;
224 // Default
225 return QTextCodec::codecForMib( 4 ); // latin 1
228 // -------------------------------------------------------------------------------------------
230 CachedCSSStyleSheet::CachedCSSStyleSheet(DocLoader* dl, const DOMString &url, KIO::CacheControl _cachePolicy,
231 const char *accept)
232 : CachedObject(url, CSSStyleSheet, _cachePolicy, 0)
234 // Set the type we want (probably css or xml)
235 QString ah = QLatin1String( accept );
236 if ( !ah.isEmpty() )
237 ah += ',';
238 ah += "*/*;q=0.1";
239 setAccept( ah );
240 m_hadError = false;
241 m_wasBlocked = false;
242 m_err = 0;
243 // load the file
244 Cache::loader()->load(dl, this, false, true /*highPriority*/);
245 m_loading = true;
248 CachedCSSStyleSheet::CachedCSSStyleSheet(const DOMString &url, const QString &stylesheet_data)
249 : CachedObject(url, CSSStyleSheet, KIO::CC_Verify, stylesheet_data.length())
251 m_loading = false;
252 m_status = Persistent;
253 m_sheet = DOMString(stylesheet_data);
256 bool khtml::isAcceptableCSSMimetype( const DOM::DOMString& mimetype )
258 // matches Mozilla's check (cf. nsCSSLoader.cpp)
259 return mimetype.isEmpty() || mimetype == "text/css" || mimetype == "application/x-unknown-content-type";
262 void CachedCSSStyleSheet::ref(CachedObjectClient *c)
264 CachedObject::ref(c);
266 if (!m_loading) {
267 if (m_hadError)
268 c->error( m_err, m_errText );
269 else
270 c->setStyleSheet( m_url, m_sheet, m_charset, m_mimetype );
274 void CachedCSSStyleSheet::data( QBuffer &buffer, bool eof )
276 if(!eof) return;
277 buffer.close();
278 setSize(buffer.buffer().size());
280 // QString charset = checkCharset( buffer.buffer() );
281 QTextCodec* c = 0;
282 if (!m_charset.isEmpty()) {
283 c = KGlobal::charsets()->codecForName(m_charset);
284 if(c->mibEnum() == 11) c = QTextCodec::codecForName("iso8859-8-i");
286 else {
287 c = codecForBuffer( m_charsetHint, buffer.buffer() );
288 m_charset = c->name();
290 QString data = c->toUnicode( buffer.buffer().data(), m_size );
291 // workaround Qt bugs
292 m_sheet = static_cast<QChar>(data[0]) == QChar::ByteOrderMark ? DOMString(data.mid( 1 ) ) : DOMString(data);
293 m_loading = false;
295 checkNotify();
298 void CachedCSSStyleSheet::checkNotify()
300 if(m_loading || m_hadError) return;
302 CDEBUG << "CachedCSSStyleSheet:: finishedLoading " << m_url.string() << endl;
304 for (QHashIterator<CachedObjectClient*,CachedObjectClient*> it( m_clients ); it.hasNext();)
305 it.next().value()->setStyleSheet( m_url, m_sheet, m_charset, m_mimetype );
309 void CachedCSSStyleSheet::error( int err, const char* text )
311 m_hadError = true;
312 m_err = err;
313 m_errText = text;
314 m_loading = false;
316 for (QHashIterator<CachedObjectClient*,CachedObjectClient*> it( m_clients ); it.hasNext();)
317 it.next().value()->error( m_err, m_errText );
320 #if 0
321 QString CachedCSSStyleSheet::checkCharset(const QByteArray& buffer ) const
323 int s = buffer.size();
324 if (s <= 12) return m_charset;
326 // @charset has to be first or directly after BOM.
327 // CSS 2.1 says @charset should win over BOM, but since more browsers support BOM
328 // than @charset, we default to that.
329 const char* d = (const char*) buffer.data();
330 if (strncmp(d, "@charset \"",10) == 0)
332 // the string until "; is the charset name
333 char *p = strchr(d+10, '"');
334 if (p == 0) return m_charset;
335 QString charset = QString::fromAscii(d+10, p-(d+10));
336 return charset;
338 return m_charset;
340 #endif
342 // -------------------------------------------------------------------------------------------
344 CachedScript::CachedScript(DocLoader* dl, const DOMString &url, KIO::CacheControl _cachePolicy, const char*)
345 : CachedObject(url, Script, _cachePolicy, 0)
347 // It's javascript we want.
348 // But some websites think their scripts are <some wrong mimetype here>
349 // and refuse to serve them if we only accept application/x-javascript.
350 setAccept( QLatin1String("*/*") );
351 // load the file
352 Cache::loader()->load(dl, this, false);
353 m_loading = true;
354 m_hadError = false;
357 CachedScript::CachedScript(const DOMString &url, const QString &script_data)
358 : CachedObject(url, Script, KIO::CC_Verify, script_data.length())
360 m_hadError = false;
361 m_loading = false;
362 m_status = Persistent;
363 m_script = DOMString(script_data);
366 void CachedScript::ref(CachedObjectClient *c)
368 CachedObject::ref(c);
370 if(!m_loading) c->notifyFinished(this);
373 void CachedScript::data( QBuffer &buffer, bool eof )
375 if(!eof) return;
376 buffer.close();
377 setSize(buffer.buffer().size());
379 QTextCodec* c = codecForBuffer( m_charset, buffer.buffer() );
380 QString data = c->toUnicode( buffer.buffer().data(), m_size );
381 m_script = static_cast<QChar>(data[0]) == QChar::ByteOrderMark ? DOMString(data.mid( 1 ) ) : DOMString(data);
382 m_loading = false;
383 checkNotify();
386 void CachedScript::checkNotify()
388 if(m_loading) return;
390 for (QHashIterator<CachedObjectClient*,CachedObjectClient*> it( m_clients ); it.hasNext();)
391 it.next().value()->notifyFinished(this);
394 void CachedScript::error( int /*err*/, const char* /*text*/ )
396 m_hadError = true;
397 m_loading = false;
398 checkNotify();
401 // ------------------------------------------------------------------------------------------
403 static QString buildAcceptHeader()
405 return "image/png, image/jpeg, video/x-mng, image/jp2, image/gif;q=0.5,*/*;q=0.1";
408 // -------------------------------------------------------------------------------------
410 CachedImage::CachedImage(DocLoader* dl, const DOMString &url, KIO::CacheControl _cachePolicy, const char*)
411 : QObject(), CachedObject(url, Image, _cachePolicy, 0)
413 static const QString &acceptHeader = KGlobal::staticQString( buildAcceptHeader() );
415 i = new khtmlImLoad::Image(this);
416 //p = 0;
417 //pixPart = 0;
418 bg = 0;
419 scaled = 0;
420 bgColor = qRgba( 0, 0, 0, 0 );
421 typeChecked = false;
422 isFullyTransparent = false;
423 monochrome = false;
424 formatType = 0;
425 m_status = Unknown;
426 imgSource = 0;
427 setAccept( acceptHeader );
428 i->setShowAnimations(dl->showAnimations());
429 m_loading = true;
431 if ( KHTMLGlobal::defaultHTMLSettings()->isAdFiltered( url.string() ) ) {
432 m_wasBlocked = true;
433 CachedObject::finish();
437 CachedImage::~CachedImage()
439 clear();
440 delete i;
443 void CachedImage::ref( CachedObjectClient *c )
445 CachedObject::ref(c);
447 #ifdef LOADER_DEBUG
448 kDebug(6060) << " image "<<this<<" ref'd by client " << c << "\n";
449 #endif
451 // for mouseovers, dynamic changes
452 //### having both makes no sense
453 if ( m_status >= Persistent && !pixmap_size().isNull() ) {
454 #ifdef LOADER_DEBUG
455 kDebug(6060) << "Notifying finished size:" <<
456 i->size().width() << ", " << i->size().height() << endl;
457 #endif
458 c->updatePixmap( QRect(QPoint(0, 0), pixmap_size()),
459 this );
460 c->notifyFinished( this );
464 void CachedImage::deref( CachedObjectClient *c )
466 CachedObject::deref(c);
467 /* if(m && m_clients.isEmpty() && m->running())
468 m->pause();*/
471 #define BGMINWIDTH 32
472 #define BGMINHEIGHT 32
474 QPixmap CachedImage::tiled_pixmap(const QColor& newc, int xWidth, int xHeight)
477 // no error indication for background images
478 if(m_hadError||m_wasBlocked) return *Cache::nullPixmap;
480 // If we don't have size yet, nothing to draw yet
481 if (i->size().width() == 0 || i->size().height() == 0)
482 return *Cache::nullPixmap;
484 #ifdef __GNUC__
485 #warning "Needs some additional performance work"
486 #endif
488 static QRgb bgTransparent = qRgba( 0, 0, 0, 0 );
490 QSize s(pixmap_size());
491 int w = xWidth;
492 int h = xHeight;
494 if (w == -1) xWidth = w = s.width();
495 if (h == -1) xHeight = h = s.height();
497 if ( ( (bgColor != bgTransparent) && (bgColor != newc.rgba()) ) ||
498 ( bgSize != QSize(xWidth, xHeight)) )
500 delete bg; bg = 0;
503 if (bg)
504 return *bg;
506 const QPixmap* src; //source for pretiling, if any
508 const QPixmap &r = pixmap(); //this is expensive
509 if (r.isNull()) return r;
511 //See whether we should scale
512 if (xWidth != s.width() || xHeight != s.height()) {
513 src = scaled_pixmap(xWidth, xHeight);
514 } else {
515 src = &r;
518 bgSize = QSize(xWidth, xHeight);
520 //See whether we can - and should - pre-blend
521 // ### this needs serious investigations. Not likely to help with transparent bgColor,
522 // won't work with CSS3 multiple backgrounds. Does it help at all in Qt4? (ref: #114938)
523 if (newc.isValid() && (r.hasAlpha() || r.hasAlphaChannel())) {
524 bg = new QPixmap(xWidth, xHeight);
525 bg->fill(newc);
526 QPainter p(bg);
527 p.drawPixmap(0, 0, *src);
528 bgColor = newc.rgba();
529 src = bg;
530 } else {
531 bgColor = bgTransparent;
534 //See whether to pre-tile.
535 if ( w*h < 8192 )
537 if ( r.width() < BGMINWIDTH )
538 w = ((BGMINWIDTH-1) / xWidth + 1) * xWidth;
539 if ( r.height() < BGMINHEIGHT )
540 h = ((BGMINHEIGHT-1) / xHeight + 1) * xHeight;
543 if ( w != xWidth || h != xHeight )
545 // kDebug() << "pre-tiling " << s.width() << "," << s.height() << " to " << w << "," << h;
546 QPixmap* oldbg = bg;
547 bg = new QPixmap(w, h);
548 if (src->hasAlpha() || src->hasAlphaChannel()) {
549 if (newc.isValid() && (bgColor != bgTransparent))
550 bg->fill( bgColor );
551 else
552 bg->fill( Qt::transparent );
555 QPainter p(bg);
556 p.drawTiledPixmap(0, 0, w, h, *src);
557 p.end();
559 if ( src == oldbg )
560 delete oldbg;
561 } else if (src && !bg) {
562 // we were asked for the entire pixmap. Cache it.
563 // ### goes against imload stuff, but it's far too expensive
564 // to recreate the full pixmap each time it's requested as
565 // we don't know what portion of it will be used eventually
566 // (by e.g. paintBackgroundExtended). It could be a few pixels of
567 // a huge image. See #140248/#1 for an obvious example.
568 // Imload probably needs to handle all painting in paintBackgroundExtended.
569 bg = new QPixmap(*src);
572 if (bg)
573 return *bg;
575 return *src;
579 QPixmap* CachedImage::scaled_pixmap( int xWidth, int xHeight )
581 // no error indication for background images
582 if(m_hadError||m_wasBlocked) return Cache::nullPixmap;
584 // If we don't have size yet, nothing to draw yet
585 if (i->size().width() == 0 || i->size().height() == 0)
586 return Cache::nullPixmap;
588 if (scaled) {
589 if (scaled->width() == xWidth && scaled->height() == xHeight)
590 return scaled;
591 delete scaled;
594 //### this is quite awful performance-wise. It should avoid
595 // alpha if not needed, and go to pixmap, etc.
596 QImage im(xWidth, xHeight, QImage::Format_ARGB32_Premultiplied);
598 QPainter paint(&im);
599 paint.setCompositionMode(QPainter::CompositionMode_Source);
600 ImagePainter pi(i, QSize(xWidth, xHeight));
601 pi.paint(0, 0, &paint);
602 paint.end();
604 scaled = new QPixmap(QPixmap::fromImage(im));
606 return scaled;
609 QPixmap CachedImage::pixmap( ) const
611 if (m_hadError)
612 return *Cache::brokenPixmap;
614 if(m_wasBlocked)
615 return *Cache::blockedPixmap;
617 int w = i->size().width();
618 int h = i->size().height();
620 if (i->hasAlpha() && !QApplication::desktop()->paintEngine()->hasFeature(QPaintEngine::PorterDuff)) {
621 QImage im(w, h, QImage::Format_ARGB32_Premultiplied);
622 QPainter paint(&im);
623 paint.setCompositionMode(QPainter::CompositionMode_Source);
624 ImagePainter pi(i);
625 pi.paint(0, 0, &paint);
626 paint.end();
627 return QPixmap::fromImage( im, Qt::NoOpaqueDetection );
628 } else {
629 QPixmap pm(w, h);
630 if (i->hasAlpha())
631 pm.fill(Qt::transparent);
632 QPainter paint(&pm);
633 paint.setCompositionMode(QPainter::CompositionMode_Source);
634 ImagePainter pi(i);
635 pi.paint(0, 0, &paint);
636 paint.end();
637 return pm;
642 QSize CachedImage::pixmap_size() const
644 if (m_wasBlocked) return Cache::blockedPixmap->size();
645 if (m_hadError) return Cache::brokenPixmap->size();
646 if (i) return i->size();
647 return QSize();
651 void CachedImage::imageHasGeometry(khtmlImLoad::Image* /*img*/, int width, int height)
653 #ifdef LOADER_DEBUG
654 kDebug(6060) << this << " got geometry "<< width << "x" << height;
655 #endif
656 do_notify(QRect(0, 0, width, height));
659 void CachedImage::imageChange (khtmlImLoad::Image* /*img*/, QRect region)
661 #ifdef LOADER_DEBUG
662 kDebug(6060) << "Image " << this << " change " <<
663 region.x() << "," << region.y() << ":" << region.width() << "x" << region.height() << endl;
664 #endif
665 //### this is overly conservative -- I guess we need to also specify reason,
666 //e.g. repaint vs. changed !!!
667 delete bg;
668 bg = 0;
670 do_notify(region);
673 void CachedImage::doNotifyFinished()
675 for (QHashIterator<CachedObjectClient*,CachedObjectClient*> it( m_clients ); it.hasNext();)
677 it.next().value()->notifyFinished(this);
681 void CachedImage::imageError(khtmlImLoad::Image* /*img*/)
683 error(0, 0);
687 void CachedImage::imageDone(khtmlImLoad::Image* /*img*/)
689 #ifdef LOADER_DEBUG
690 kDebug(6060)<<"Image is done:" << this;
691 #endif
692 m_status = Persistent;
693 m_loading = false;
694 doNotifyFinished();
697 // QRect CachedImage::valid_rect() const
698 // {
699 // if (m_wasBlocked) return Cache::blockedPixmap->rect();
700 // return (m_hadError ? Cache::brokenPixmap->rect() : m ? m->frameRect() : ( p ? p->rect() : QRect()) );
701 // }
704 void CachedImage::do_notify(const QRect& r)
706 for (QHashIterator<CachedObjectClient*,CachedObjectClient*> it( m_clients ); it.hasNext();)
708 #ifdef LOADER_DEBUG
709 kDebug(6060) << " image "<<this<<" notify of geom client " << it.peekNext() << "\n";
710 #endif
711 it.next().value()->updatePixmap( r, this);
716 // void CachedImage::movieUpdated( const QRect& r )
717 // {
718 // #ifdef LOADER_DEBUG
719 // qDebug("movie updated %d/%d/%d/%d, pixmap size %d/%d", r.x(), r.y(), r.right(), r.bottom(),
720 // m->framePixmap().size().width(), m->framePixmap().size().height());
721 // #endif
723 // do_notify(m->framePixmap(), r);
724 // }
725 #if 0
727 void CachedImage::movieStatus(int status)
729 #warning QMovie emits different signals now and requires different ways to call
730 #ifdef LOADER_DEBUG
731 qDebug("movieStatus(%d)", status);
732 #endif
734 // ### the html image objects are supposed to send the load event after every frame (according to
735 // netscape). We have a problem though where an image is present, and js code creates a new Image object,
736 // which uses the same CachedImage, the one in the document is not supposed to be notified
738 // just another Qt 2.2.0 bug. we cannot call
739 // QMovie::frameImage if we're after QMovie::EndOfMovie
740 if(status == QMovie::EndOfFrame)
742 const QImage& im = m->frameImage();
743 monochrome = ( ( im.depth() <= 8 ) && ( im.numColors() - int( im.hasAlphaBuffer() ) <= 2 ) );
744 for (int i = 0; monochrome && i < im.numColors(); ++i)
745 if (im.colorTable()[i] != qRgb(0xff, 0xff, 0xff) &&
746 im.colorTable()[i] != qRgb(0x00, 0x00, 0x00))
747 monochrome = false;
748 if( (im.width() < 5 || im.height() < 5) && im.hasAlphaBuffer()) // only evaluate for small images
750 QImage am = im.createAlphaMask();
751 if(am.depth() == 1)
753 bool solid = false;
754 for(int y = 0; y < am.height(); y++)
755 for(int x = 0; x < am.width(); x++)
756 if(am.pixelIndex(x, y)) {
757 solid = true;
758 break;
760 isFullyTransparent = (!solid);
764 // we have to delete our tiled bg variant here
765 // because the frame has changed (in order to keep it in sync)
766 delete bg;
767 bg = 0;
770 if((status == QMovie::EndOfMovie && (!m || m->frameNumber() <= 1)) ||
771 ((status == QMovie::EndOfLoop) && (m_showAnimations == KHTMLSettings::KAnimationLoopOnce)) ||
772 ((status == QMovie::EndOfFrame) && (m_showAnimations == KHTMLSettings::KAnimationDisabled))
775 if(imgSource)
777 setShowAnimations( KHTMLSettings::KAnimationDisabled );
779 // monochrome alphamasked images are usually about 10000 times
780 // faster to draw, so this is worth the hack
781 if (p && monochrome && p->depth() > 1)
783 QPixmap* pix = new QPixmap;
784 pix->convertFromImage( p->convertToImage().convertDepth( 1 ), Qt::MonoOnly|Qt::AvoidDither );
785 pix->setMask( p->mask() );
786 delete p;
787 p = pix;
788 monochrome = false;
791 for (QHashIterator<CachedObjectClient*,CachedObjectClient*> it( m_clients ); it.hasNext();)
792 it.next().value()->notifyFinished( this );
793 m_status = Cached; //all done
796 if((status == QMovie::EndOfFrame) || (status == QMovie::EndOfMovie))
798 #ifdef LOADER_DEBUG
799 QRect r(valid_rect());
800 qDebug("movie Status frame update %d/%d/%d/%d, pixmap size %d/%d", r.x(), r.y(), r.right(), r.bottom(),
801 pixmap().size().width(), pixmap().size().height());
802 #endif
803 do_notify(pixmap(), valid_rect());
806 #endif
808 // void CachedImage::movieResize(const QSize& /*s*/)
809 // {
810 // do_notify(m->framePixmap(), QRect());
811 // }
813 void CachedImage::setShowAnimations( KHTMLSettings::KAnimationAdvice showAnimations )
815 if (i)
816 i->setShowAnimations(showAnimations);
819 // void CachedImage::deleteMovie()
820 // {
821 // delete m; m = 0;
822 // }
824 void CachedImage::pauseAnimations()
826 // if ( m ) m->setPaused( true );
829 void CachedImage::resumeAnimations()
831 // if ( m ) m->setPaused( false );
834 void CachedImage::clear()
836 delete i; i = new khtmlImLoad::Image(this);
837 delete scaled; scaled = 0;
838 bgColor = qRgba( 0, 0, 0, 0xff );
839 delete bg; bg = 0;
840 bgSize = QSize(-1,-1);
842 formatType = 0;
843 typeChecked = false;
844 setSize(0);
846 // No need to delete imageSource - QMovie does it for us
847 imgSource = 0;
850 void CachedImage::data ( QBuffer &_buffer, bool eof )
852 #ifdef LOADER_DEBUG
853 kDebug( 6060 ) << this << "in CachedImage::data(buffersize " << _buffer.buffer().size() <<", eof=" << eof << " pos:" << _buffer.pos();
854 #endif
855 i->processData((uchar*)_buffer.data().data(), _buffer.pos());
857 _buffer.close();
859 if (eof)
860 i->processEOF();
862 //if ( !typeChecked )
864 // don't attempt incremental loading if we have all the data already
865 // assert(!eof);
867 #ifdef __GNUC__
868 #warning QImage* requires heavy porting
869 #endif
870 #if 0
871 formatType = QImageDecoder::formatName( (const uchar*)_buffer.buffer().data(), _buffer.size());
872 if ( formatType && strcmp( formatType, "PNG" ) == 0 )
873 formatType = 0; // Some png files contain multiple images, we want to show only the first one
875 typeChecked = true;
877 if ( formatType ) // movie format exists
879 // imgSource = new ImageSource( _buffer.buffer());
880 // m = new QMovie( imgSource, 8192 );
881 m = new QMovie( _buffer);
882 m->connectUpdate( this, SLOT( movieUpdated( const QRect &) ));
883 m->connectStatus( this, SLOT( movieStatus(int)));
884 m->connectResize( this, SLOT( movieResize( const QSize& ) ) );
888 if ( imgSource )
890 imgSource->setEOF(eof);
891 imgSource->maybeReady();
894 if(eof)
896 // QMovie currently doesn't support all kinds of image formats
897 // so we need to use a QPixmap here when we finished loading the complete
898 // picture and display it then all at once.
899 if(typeChecked && !formatType)
901 #ifdef CACHE_DEBUG
902 kDebug(6060) << "CachedImage::data(): reloading as pixmap:";
903 #endif
904 p = new QPixmap;
906 QBuffer buffer(_buffer.buffer());
907 buffer.open(IO_ReadOnly);
908 QImageIO io( &buffer, 0 );
909 io.setGamma(2.2); // hardcoded "reasonable value"
910 bool result = io.read();
911 if (result) p->convertFromImage(io.image(), 0);
914 // set size of image.
915 #ifdef CACHE_DEBUG
916 kDebug(6060) << "CachedImage::data(): image is null: " << p->isNull();
917 #endif
918 if(p->isNull())
920 m_hadError = true;
921 do_notify(pixmap(), QRect(0, 0, 16, 16)); // load "broken image" icon
923 else
924 do_notify(*p, p->rect());
926 for (QHashIterator<CachedObjectClient*,CachedObjectClient*> it( m_clients ); it.hasNext();)
927 it.next().value()->notifyFinished( this );
928 m_status = Cached; //all done
930 #endif
934 void CachedImage::finish()
936 //Status oldStatus = m_status;
937 CachedObject::finish();
938 /* if ( oldStatus != m_status ) {
939 const QPixmap &pm = pixmap();
940 do_notify( pm, pm.rect() );
942 m_loading = false;
943 QSize s = pixmap_size();
944 setSize( s.width() * s.height() * 2);
948 void CachedImage::error( int /*err*/, const char* /*text*/ )
950 clear();
951 typeChecked = true;
952 m_hadError = true;
953 m_loading = false;
954 do_notify(QRect(0, 0, 16, 16));
955 for (QHashIterator<CachedObjectClient*,CachedObjectClient*> it( m_clients ); it.hasNext();)
956 it.next().value()->notifyFinished(this);
959 // -------------------------------------------------------------------------------------------
961 CachedSound::CachedSound(DocLoader* dl, const DOMString &url, KIO::CacheControl _cachePolicy, const char*)
962 : CachedObject(url, Sound, _cachePolicy, 0)
964 setAccept( QLatin1String("*/*") ); // should be whatever phonon would accept...
965 Cache::loader()->load(dl, this, false);
966 m_loading = true;
969 void CachedSound::ref(CachedObjectClient *c)
971 CachedObject::ref(c);
973 if(!m_loading) c->notifyFinished(this);
976 void CachedSound::data( QBuffer &buffer, bool eof )
978 if(!eof) return;
979 buffer.close();
980 setSize(buffer.buffer().size());
982 m_sound = buffer.buffer();
983 m_loading = false;
984 checkNotify();
987 void CachedSound::checkNotify()
989 if(m_loading) return;
991 for (QHashIterator<CachedObjectClient*,CachedObjectClient*> it( m_clients ); it.hasNext();)
992 it.next().value()->notifyFinished(this);
995 void CachedSound::error( int /*err*/, const char* /*text*/ )
997 m_loading = false;
998 checkNotify();
1001 // ------------------------------------------------------------------------------------------
1003 Request::Request(DocLoader* dl, CachedObject *_object, bool _incremental)
1005 object = _object;
1006 object->setRequest(this);
1007 incremental = _incremental;
1008 m_docLoader = dl;
1011 Request::~Request()
1013 object->setRequest(0);
1016 // ------------------------------------------------------------------------------------------
1018 DocLoader::DocLoader(KHTMLPart* part, DocumentImpl* doc)
1020 m_cachePolicy = KIO::CC_Verify;
1021 m_expireDate = 0;
1022 m_creationDate = time(0);
1023 m_bautoloadImages = true;
1024 m_showAnimations = KHTMLSettings::KAnimationEnabled;
1025 m_part = part;
1026 m_doc = doc;
1028 Cache::docloader->append( this );
1031 DocLoader::~DocLoader()
1033 clearPreloads();
1034 Cache::loader()->cancelRequests( this );
1035 Cache::docloader->removeAll( this );
1038 void DocLoader::setCacheCreationDate(time_t _creationDate)
1040 if (_creationDate)
1041 m_creationDate = _creationDate;
1042 else
1043 m_creationDate = time(0); // Now
1046 void DocLoader::setExpireDate(time_t _expireDate, bool relative)
1048 if (relative)
1049 m_expireDate = _expireDate + m_creationDate; // Relative date
1050 else
1051 m_expireDate = _expireDate; // Absolute date
1052 #ifdef CACHE_DEBUG
1053 kDebug(6061) << "docLoader: " << m_expireDate - time(0) << " seconds left until reload required.\n";
1054 #endif
1057 void DocLoader::insertCachedObject( CachedObject* o ) const
1059 m_docObjects.insert( o );
1062 bool DocLoader::needReload(CachedObject *existing, const QString& fullURL)
1064 bool reload = false;
1065 if (m_cachePolicy == KIO::CC_Verify ||
1066 // During a softReload, we favour using cached images
1067 // over forcibly re-downloading them.
1068 (existing->type() == CachedObject::Image && m_part->browserExtension()->browserArguments().softReload))
1070 if (!m_reloadedURLs.contains(fullURL))
1072 if (existing && existing->isExpired() && !existing->isPreloaded())
1074 Cache::removeCacheEntry(existing);
1075 m_reloadedURLs.append(fullURL);
1076 reload = true;
1080 else if ((m_cachePolicy == KIO::CC_Reload) || (m_cachePolicy == KIO::CC_Refresh))
1082 if (!m_reloadedURLs.contains(fullURL))
1084 if (existing && !existing->isPreloaded())
1086 Cache::removeCacheEntry(existing);
1088 if (!existing || !existing->isPreloaded()) {
1089 m_reloadedURLs.append(fullURL);
1090 reload = true;
1094 return reload;
1097 void DocLoader::registerPreload(CachedObject* resource)
1099 if (!resource || resource->isLoaded() || m_preloads.contains(resource))
1100 return;
1101 resource->increasePreloadCount();
1102 m_preloads.insert(resource);
1103 resource->setProspectiveRequest();
1104 #ifdef PRELOAD_DEBUG
1105 fprintf(stderr, "PRELOADING %s\n", resource->url().string().toLatin1().data());
1106 #endif
1109 void DocLoader::clearPreloads()
1111 printPreloadStats();
1112 QSet<CachedObject*>::iterator end = m_preloads.end();
1113 for (QSet<CachedObject*>::iterator it = m_preloads.begin(); it != end; ++it) {
1114 CachedObject* res = *it;
1115 res->decreasePreloadCount();
1116 if (res->preloadResult() == CachedObject::PreloadNotReferenced || res->hadError())
1117 Cache::removeCacheEntry(res);
1119 m_preloads.clear();
1122 void DocLoader::printPreloadStats()
1124 #ifdef PRELOAD_DEBUG
1125 unsigned scripts = 0;
1126 unsigned scriptMisses = 0;
1127 unsigned stylesheets = 0;
1128 unsigned stylesheetMisses = 0;
1129 unsigned images = 0;
1130 unsigned imageMisses = 0;
1131 QSet<CachedObject*>::iterator end = m_preloads.end();
1132 for (QSet<CachedObject*>::iterator it = m_preloads.begin(); it != end; ++it) {
1133 CachedObject* res = *it;
1134 if (res->preloadResult() == CachedObject::PreloadNotReferenced)
1135 fprintf(stderr,"!! UNREFERENCED PRELOAD %s\n", res->url().string().toLatin1().data());
1136 else if (res->preloadResult() == CachedObject::PreloadReferencedWhileComplete)
1137 fprintf(stderr,"HIT COMPLETE PRELOAD %s\n", res->url().string().toLatin1().data());
1138 else if (res->preloadResult() == CachedObject::PreloadReferencedWhileLoading)
1139 fprintf(stderr,"HIT LOADING PRELOAD %s\n", res->url().string().toLatin1().data());
1141 if (res->type() == CachedObject::Script) {
1142 scripts++;
1143 if (res->preloadResult() < CachedObject::PreloadReferencedWhileLoading)
1144 scriptMisses++;
1145 } else if (res->type() == CachedObject::CSSStyleSheet) {
1146 stylesheets++;
1147 if (res->preloadResult() < CachedObject::PreloadReferencedWhileLoading)
1148 stylesheetMisses++;
1149 } else {
1150 images++;
1151 if (res->preloadResult() < CachedObject::PreloadReferencedWhileLoading)
1152 imageMisses++;
1155 if (scripts)
1156 fprintf(stderr, "SCRIPTS: %d (%d hits, hit rate %d%%)\n", scripts, scripts - scriptMisses, (scripts - scriptMisses) * 100 / scripts);
1157 if (stylesheets)
1158 fprintf(stderr, "STYLESHEETS: %d (%d hits, hit rate %d%%)\n", stylesheets, stylesheets - stylesheetMisses, (stylesheets - stylesheetMisses) * 100 / stylesheets);
1159 if (images)
1160 fprintf(stderr, "IMAGES: %d (%d hits, hit rate %d%%)\n", images, images - imageMisses, (images - imageMisses) * 100 / images);
1161 #endif
1164 #define DOCLOADER_SECCHECK(doRedirectCheck) \
1165 KUrl fullURL (m_doc->completeURL( url.string() )); \
1166 if ( !fullURL.isValid() || \
1167 ( m_part && m_part->onlyLocalReferences() && fullURL.protocol() != "file" && fullURL.protocol() != "data") || \
1168 doRedirectCheck && (m_doc && !KAuthorized::authorizeUrlAction("redirect", m_doc->URL(), fullURL))) \
1169 return 0L;
1171 CachedImage *DocLoader::requestImage( const DOM::DOMString &url)
1173 DOCLOADER_SECCHECK(true);
1175 CachedImage* i = Cache::requestObject<CachedImage, CachedObject::Image>( this, fullURL, 0);
1177 if (i && i->status() == CachedObject::Unknown && autoloadImages())
1178 Cache::loader()->load(this, i, true);
1180 return i;
1183 CachedCSSStyleSheet *DocLoader::requestStyleSheet( const DOM::DOMString &url, const QString& charset,
1184 const char *accept, bool userSheet )
1186 DOCLOADER_SECCHECK(!userSheet);
1188 CachedCSSStyleSheet* s = Cache::requestObject<CachedCSSStyleSheet, CachedObject::CSSStyleSheet>( this, fullURL, accept );
1189 if ( s && !charset.isEmpty() ) {
1190 s->setCharsetHint( charset );
1192 return s;
1195 CachedScript *DocLoader::requestScript( const DOM::DOMString &url, const QString& charset)
1197 DOCLOADER_SECCHECK(true);
1198 if ( ! KHTMLGlobal::defaultHTMLSettings()->isJavaScriptEnabled(fullURL.host()) ||
1199 KHTMLGlobal::defaultHTMLSettings()->isAdFiltered(fullURL.url()))
1200 return 0L;
1202 CachedScript* s = Cache::requestObject<CachedScript, CachedObject::Script>( this, fullURL, 0 );
1203 if ( s && !charset.isEmpty() )
1204 s->setCharset( charset );
1205 return s;
1208 CachedSound *DocLoader::requestSound( const DOM::DOMString &url )
1210 DOCLOADER_SECCHECK(true);
1211 CachedSound* s = Cache::requestObject<CachedSound, CachedObject::Sound>( this, fullURL, 0 );
1212 return s;
1215 #undef DOCLOADER_SECCHECK
1217 void DocLoader::setAutoloadImages( bool enable )
1219 if ( enable == m_bautoloadImages )
1220 return;
1222 m_bautoloadImages = enable;
1224 if ( !m_bautoloadImages ) return;
1226 for ( QSetIterator<CachedObject*> it( m_docObjects ); it.hasNext(); )
1228 CachedObject* cur = it.next();
1229 if ( cur->type() == CachedObject::Image )
1231 CachedImage *img = const_cast<CachedImage*>( static_cast<const CachedImage *>(cur) );
1233 CachedObject::Status status = img->status();
1234 if ( status != CachedObject::Unknown )
1235 continue;
1237 Cache::loader()->load(this, img, true);
1242 void DocLoader::setShowAnimations( KHTMLSettings::KAnimationAdvice showAnimations )
1244 if ( showAnimations == m_showAnimations ) return;
1245 m_showAnimations = showAnimations;
1247 for ( QSetIterator<CachedObject*> it( m_docObjects ); it.hasNext(); )
1249 CachedObject* cur = it.next();
1250 if ( cur->type() == CachedObject::Image )
1252 CachedImage *img = const_cast<CachedImage*>( static_cast<const CachedImage *>( cur ) );
1254 img->setShowAnimations( m_showAnimations );
1259 void DocLoader::pauseAnimations()
1261 for ( QSetIterator<CachedObject*> it( m_docObjects ); it.hasNext(); )
1263 CachedObject* cur = it.next();
1264 if ( cur->type() == CachedObject::Image )
1266 CachedImage *img = const_cast<CachedImage*>( static_cast<const CachedImage *>( cur ) );
1268 img->pauseAnimations();
1273 void DocLoader::resumeAnimations()
1275 for ( QSetIterator<CachedObject*> it( m_docObjects ); it.hasNext(); )
1277 CachedObject* cur = it.next();
1278 if ( cur->type() == CachedObject::Image )
1280 CachedImage *img = const_cast<CachedImage*>( static_cast<const CachedImage *>( cur ) );
1282 img->resumeAnimations();
1287 // ------------------------------------------------------------------------------------------
1289 Loader::Loader() : QObject()
1291 connect(&m_timer, SIGNAL(timeout()), this, SLOT( servePendingRequests() ) );
1292 m_highPriorityRequestPending = 0;
1295 Loader::~Loader()
1297 delete m_highPriorityRequestPending;
1298 qDeleteAll(m_requestsPending);
1299 qDeleteAll(m_requestsLoading);
1302 void Loader::load(DocLoader* dl, CachedObject *object, bool incremental, bool highPriority)
1304 Request *req = new Request(dl, object, incremental);
1305 if (highPriority && !m_highPriorityRequestPending) {
1306 m_highPriorityRequestPending = req;
1307 } else {
1308 if (highPriority) {
1309 m_requestsPending.prepend(req);
1310 } else {
1311 m_requestsPending.append(req);
1313 highPriority = false;
1316 emit requestStarted( req->m_docLoader, req->object );
1318 if (highPriority) {
1319 servePendingRequests();
1320 } else {
1321 m_timer.setSingleShot(true);
1322 m_timer.start(0);
1326 void Loader::servePendingRequests()
1328 while ( (m_highPriorityRequestPending != 0 || m_requestsPending.count() != 0) && (m_requestsLoading.count() < MAX_JOB_COUNT) )
1330 // get the first pending request
1331 Request *req = m_highPriorityRequestPending ? m_highPriorityRequestPending : m_requestsPending.takeFirst();
1333 #ifdef LOADER_DEBUG
1334 kDebug( 6060 ) << "starting Loader url=" << req->object->url().string();
1335 #endif
1337 KUrl u(req->object->url().string());
1338 KIO::TransferJob* job = KIO::get( u, KIO::NoReload, KIO::HideProgressInfo /*no GUI*/);
1340 job->addMetaData("cache", KIO::getCacheControlString(req->object->cachePolicy()));
1341 if (!req->object->accept().isEmpty())
1342 job->addMetaData("accept", req->object->accept());
1343 if ( req->m_docLoader )
1345 job->addMetaData( "referrer", req->m_docLoader->doc()->URL().url() );
1347 KHTMLPart *part = req->m_docLoader->part();
1348 if (part )
1350 job->addMetaData( "cross-domain", part->toplevelURL().url() );
1351 if (part->widget())
1352 job->ui()->setWindow (part->widget()->topLevelWidget());
1356 connect( job, SIGNAL( result( KJob * ) ), this, SLOT( slotFinished( KJob * ) ) );
1357 connect( job, SIGNAL( mimetype( KIO::Job *, const QString& ) ), this, SLOT( slotMimetype( KIO::Job *, const QString& ) ) );
1358 connect( job, SIGNAL( data( KIO::Job*, const QByteArray &)),
1359 SLOT( slotData( KIO::Job*, const QByteArray &)));
1361 if ( req->object->schedule() )
1362 KIO::Scheduler::scheduleJob( job );
1364 m_requestsLoading.insertMulti(job, req);
1365 if (m_highPriorityRequestPending) {
1366 m_highPriorityRequestPending = 0;
1367 break;
1372 void Loader::slotMimetype( KIO::Job *j, const QString& s )
1374 KIO::TransferJob* job = static_cast<KIO::TransferJob*>(j);
1375 Request *r = m_requestsLoading.value( j );
1376 if (!r)
1377 return;
1378 CachedObject *o = r->object;
1380 // Mozilla plain ignores any mimetype that doesn't have / in it, and handles it as "",
1381 // including when being picky about mimetypes. Match that for better compatibility with broken servers.
1382 if (s.contains('/'))
1383 o->m_mimetype = s;
1384 else
1385 o->m_mimetype = "";
1388 void Loader::slotFinished( KJob* job )
1390 KIO::TransferJob* j = static_cast<KIO::TransferJob*>(job);
1391 Request *r = m_requestsLoading.take( j );
1393 if ( !r )
1394 return;
1396 if (j->error() || j->isErrorPage())
1398 #ifdef LOADER_DEBUG
1399 kDebug(6060) << "Loader::slotFinished, with error. job->error()= " << j->error() << " job->isErrorPage()=" << j->isErrorPage();
1400 #endif
1401 r->object->error( job->error(), job->errorText().toAscii().constData() );
1402 emit requestFailed( r->m_docLoader, r->object );
1404 else
1406 QString cs = j->queryMetaData("charset");
1407 if (!cs.isEmpty()) r->object->setCharset(cs);
1408 r->object->data(r->m_buffer, true);
1409 emit requestDone( r->m_docLoader, r->object );
1410 time_t expireDate = j->queryMetaData("expire-date").toLong();
1411 #ifdef LOADER_DEBUG
1412 kDebug(6060) << "Loader::slotFinished, url = " << j->url().url();
1413 #endif
1414 r->object->setExpireDate( expireDate );
1416 if ( r->object->type() == CachedObject::Image ) {
1417 QString fn = j->queryMetaData("content-disposition");
1418 static_cast<CachedImage*>( r->object )->setSuggestedFilename(fn);
1419 #ifdef IMAGE_TITLES
1420 static_cast<CachedImage*>( r->object )->setSuggestedTitle(fn);
1421 KTemporaryFile tf;
1422 tf.open();
1423 tf.write((const char*)r->m_buffer.buffer().data(), r->m_buffer.size());
1424 tf.flush();
1425 KFileMetaInfo kfmi(tf.fileName());
1426 if (!kfmi.isEmpty()) {
1427 KFileMetaInfoItem i = kfmi.item("Name");
1428 if (i.isValid()) {
1429 static_cast<CachedImage*>(r->object)->setSuggestedTitle(i.string());
1430 } else {
1431 i = kfmi.item("Title");
1432 if (i.isValid()) {
1433 static_cast<CachedImage*>(r->object)->setSuggestedTitle(i.string());
1437 #endif
1441 r->object->finish();
1443 #ifdef LOADER_DEBUG
1444 kDebug( 6060 ) << "Loader:: JOB FINISHED " << r->object << ": " << r->object->url().string();
1445 #endif
1447 delete r;
1449 if ( (m_highPriorityRequestPending != 0 || m_requestsPending.count() != 0) && (m_requestsLoading.count() < MAX_JOB_COUNT / 2) ) {
1450 m_timer.setSingleShot(true);
1451 m_timer.start(0);
1455 void Loader::slotData( KIO::Job*job, const QByteArray &data )
1457 Request *r = m_requestsLoading[job];
1458 if(!r) {
1459 kDebug( 6060 ) << "got data for unknown request!";
1460 return;
1463 if ( !r->m_buffer.isOpen() )
1464 r->m_buffer.open( QIODevice::WriteOnly );
1466 r->m_buffer.write( data.data(), data.size() );
1468 if(r->incremental)
1469 r->object->data( r->m_buffer, false );
1472 int Loader::numRequests( DocLoader* dl ) const
1474 int res = 0;
1475 if (m_highPriorityRequestPending && m_highPriorityRequestPending->m_docLoader == dl)
1476 res++;
1478 foreach( Request* req, m_requestsPending )
1479 if ( req->m_docLoader == dl )
1480 res++;
1482 foreach( Request* req, m_requestsLoading)
1483 if ( req->m_docLoader == dl )
1484 res++;
1486 return res;
1489 void Loader::cancelRequests( DocLoader* dl )
1491 if (m_highPriorityRequestPending && m_highPriorityRequestPending->m_docLoader == dl) {
1492 CDEBUG << "canceling high priority pending request for " << m_highPriorityRequestPending->object->url().string() << endl;
1493 Cache::removeCacheEntry( m_highPriorityRequestPending->object );
1494 delete m_highPriorityRequestPending;
1495 m_highPriorityRequestPending = 0;
1498 QMutableLinkedListIterator<Request*> pIt( m_requestsPending );
1499 while ( pIt.hasNext() ) {
1500 Request* cur = pIt.next();
1501 if ( cur->m_docLoader == dl )
1503 CDEBUG << "canceling pending request for " << cur->object->url().string() << endl;
1504 Cache::removeCacheEntry( cur->object );
1505 pIt.remove();
1506 delete cur;
1510 //kDebug( 6060 ) << "got " << m_requestsLoading.count() << "loading requests";
1512 QMutableHashIterator<KIO::Job*,Request*> lIt( m_requestsLoading );
1513 while ( lIt.hasNext() )
1515 lIt.next();
1516 if ( lIt.value()->m_docLoader == dl )
1518 //kDebug( 6060 ) << "canceling loading request for " << lIt.current()->object->url().string();
1519 KIO::Job *job = static_cast<KIO::Job *>( lIt.key() );
1520 Cache::removeCacheEntry( lIt.value()->object );
1521 delete lIt.value();
1522 lIt.remove();
1523 job->kill();
1528 KIO::Job *Loader::jobForRequest( const DOM::DOMString &url ) const
1530 QHashIterator<KIO::Job*,Request*> it( m_requestsLoading );
1531 while (it.hasNext())
1533 it.next();
1534 if ( it.value()->object && it.value()->object->url() == url )
1535 return static_cast<KIO::Job *>( it.key() );
1538 return 0;
1541 // ----------------------------------------------------------------------------
1544 QHash<QString,CachedObject*> *Cache::cache;
1545 QLinkedList<DocLoader*> *Cache::docloader;
1546 QLinkedList<CachedObject*> *Cache::freeList;
1547 Loader *Cache::m_loader;
1549 int Cache::maxSize = DEFCACHESIZE;
1550 int Cache::totalSizeOfLRU;
1552 QPixmap *Cache::nullPixmap;
1553 QPixmap *Cache::brokenPixmap;
1554 QPixmap *Cache::blockedPixmap;
1556 void Cache::init()
1558 if ( !cache )
1559 cache = new QHash<QString,CachedObject*>();
1561 if ( !docloader )
1562 docloader = new QLinkedList<DocLoader*>;
1564 if ( !nullPixmap )
1565 nullPixmap = new QPixmap;
1567 if ( !brokenPixmap )
1568 brokenPixmap = new QPixmap(KHTMLGlobal::iconLoader()->loadIcon("image-missing", KIconLoader::Desktop, 16, KIconLoader::DisabledState));
1570 if ( !blockedPixmap ) {
1571 blockedPixmap = new QPixmap();
1572 blockedPixmap->loadFromData(blocked_icon_data, blocked_icon_len);
1575 if ( !m_loader )
1576 m_loader = new Loader();
1578 if ( !freeList )
1579 freeList = new QLinkedList<CachedObject*>;
1582 void Cache::clear()
1584 if ( !cache ) return;
1585 #ifdef CACHE_DEBUG
1586 kDebug( 6060 ) << "Cache: CLEAR!";
1587 statistics();
1588 #endif
1590 #ifndef NDEBUG
1591 bool crash = false;
1592 foreach (CachedObject* co, *cache) {
1593 if (!co->canDelete()) {
1594 kDebug( 6060 ) << " Object in cache still linked to";
1595 kDebug( 6060 ) << " -> URL: " << co->url();
1596 kDebug( 6060 ) << " -> #clients: " << co->count();
1597 crash = true;
1598 // assert(co->canDelete());
1601 foreach (CachedObject* co, *freeList) {
1602 if (!co->canDelete()) {
1603 kDebug( 6060 ) << " Object in freelist still linked to";
1604 kDebug( 6060 ) << " -> URL: " << co->url();
1605 kDebug( 6060 ) << " -> #clients: " << co->count();
1606 crash = true;
1608 foreach (CachedObjectClient* cur, (*co->m_clients)))
1610 if (dynamic_cast<RenderObject*>(cur)) {
1611 kDebug( 6060 ) << " --> RenderObject";
1612 } else
1613 kDebug( 6060 ) << " --> Something else";
1616 // assert(freeList->current()->canDelete());
1618 assert(!crash);
1619 #endif
1620 qDeleteAll(*cache);
1621 delete cache; cache = 0;
1622 delete nullPixmap; nullPixmap = 0;
1623 delete brokenPixmap; brokenPixmap = 0;
1624 delete blockedPixmap; blockedPixmap = 0;
1625 delete m_loader; m_loader = 0;
1626 delete docloader; docloader = 0;
1627 qDeleteAll(*freeList);
1628 delete freeList; freeList = 0;
1631 template<typename CachedObjectType, enum CachedObject::Type CachedType>
1632 CachedObjectType* Cache::requestObject( DocLoader* dl, const KUrl& kurl, const char* accept )
1634 KIO::CacheControl cachePolicy = dl->cachePolicy();
1636 QString url = kurl.url();
1637 CachedObject* o = cache->value(url);
1639 if ( o && o->type() != CachedType ) {
1640 removeCacheEntry( o );
1641 o = 0;
1644 if ( o && dl->needReload( o, url ) ) {
1645 o = 0;
1646 assert( !cache->contains( url ) );
1649 if(!o)
1651 #ifdef CACHE_DEBUG
1652 kDebug( 6060 ) << "Cache: new: " << kurl.url();
1653 #endif
1654 CachedObjectType* cot = new CachedObjectType(dl, url, cachePolicy, accept);
1655 cache->insert( url, cot );
1656 if ( cot->allowInLRUList() )
1657 insertInLRUList( cot );
1658 o = cot;
1660 #ifdef CACHE_DEBUG
1661 else {
1662 kDebug( 6060 ) << "Cache: using pending/cached: " << kurl.url();
1664 #endif
1667 dl->insertCachedObject( o );
1669 return static_cast<CachedObjectType *>(o);
1672 void Cache::preloadStyleSheet( const QString &url, const QString &stylesheet_data)
1674 if (cache->contains(url))
1675 removeCacheEntry(cache->value(url));
1677 CachedCSSStyleSheet *stylesheet = new CachedCSSStyleSheet(url, stylesheet_data);
1678 cache->insert( url, stylesheet );
1681 void Cache::preloadScript( const QString &url, const QString &script_data)
1683 if (cache->contains(url))
1684 removeCacheEntry(cache->value(url));
1686 CachedScript *script = new CachedScript(url, script_data);
1687 cache->insert( url, script );
1690 void Cache::flush(bool force)
1692 init();
1694 if ( force || totalSizeOfLRU > maxSize + maxSize/4) {
1695 for ( int i = MAX_LRU_LISTS-1; i >= 0 && totalSizeOfLRU > maxSize; --i )
1696 while ( totalSizeOfLRU > maxSize && m_LRULists[i].m_tail )
1697 removeCacheEntry( m_LRULists[i].m_tail );
1699 #ifdef CACHE_DEBUG
1700 statistics();
1701 #endif
1704 QMutableLinkedListIterator<CachedObject*> it(*freeList);
1705 while ( it.hasNext() ) {
1706 CachedObject* p = it.next();
1707 if ( p->canDelete() ) {
1708 it.remove();
1709 delete p;
1714 void Cache::setSize( int bytes )
1716 maxSize = bytes;
1717 flush(true /* force */);
1720 void Cache::statistics()
1722 // this function is for debugging purposes only
1723 init();
1725 int size = 0;
1726 int msize = 0;
1727 int movie = 0;
1728 int images = 0;
1729 int scripts = 0;
1730 int stylesheets = 0;
1731 int sound = 0;
1732 foreach (CachedObject* o, *cache)
1734 switch(o->type()) {
1735 case CachedObject::Image:
1737 //CachedImage *im = static_cast<CachedImage *>(o);
1738 images++;
1739 /*if(im->m != 0)
1741 movie++;
1742 msize += im->size();
1744 break;
1746 case CachedObject::CSSStyleSheet:
1747 stylesheets++;
1748 break;
1749 case CachedObject::Script:
1750 scripts++;
1751 break;
1752 case CachedObject::Sound:
1753 sound++;
1754 break;
1756 size += o->size();
1758 size /= 1024;
1760 kDebug( 6060 ) << "------------------------- image cache statistics -------------------";
1761 kDebug( 6060 ) << "Number of items in cache: " << cache->count();
1762 kDebug( 6060 ) << "Number of cached images: " << images;
1763 kDebug( 6060 ) << "Number of cached movies: " << movie;
1764 kDebug( 6060 ) << "Number of cached scripts: " << scripts;
1765 kDebug( 6060 ) << "Number of cached stylesheets: " << stylesheets;
1766 kDebug( 6060 ) << "Number of cached sounds: " << sound;
1767 kDebug( 6060 ) << "pixmaps: allocated space approx. " << size << " kB";
1768 kDebug( 6060 ) << "movies : allocated space approx. " << msize/1024 << " kB";
1769 kDebug( 6060 ) << "--------------------------------------------------------------------";
1772 void Cache::removeCacheEntry( CachedObject *object )
1774 QString key = object->url().string();
1776 cache->remove( key );
1777 removeFromLRUList( object );
1779 foreach( DocLoader* dl, *docloader )
1780 dl->removeCachedObject( object );
1782 if ( !object->free() ) {
1783 Cache::freeList->append( object );
1784 object->m_free = true;
1788 static inline int FastLog2(unsigned int j)
1790 unsigned int log2;
1791 log2 = 0;
1792 if (j & (j-1))
1793 log2 += 1;
1794 if (j >> 16)
1795 log2 += 16, j >>= 16;
1796 if (j >> 8)
1797 log2 += 8, j >>= 8;
1798 if (j >> 4)
1799 log2 += 4, j >>= 4;
1800 if (j >> 2)
1801 log2 += 2, j >>= 2;
1802 if (j >> 1)
1803 log2 += 1;
1805 return log2;
1808 static LRUList* getLRUListFor(CachedObject* o)
1810 int accessCount = o->accessCount();
1811 int queueIndex;
1812 if (accessCount == 0) {
1813 queueIndex = 0;
1814 } else {
1815 int sizeLog = FastLog2(o->size());
1816 queueIndex = sizeLog/o->accessCount() - 1;
1817 if (queueIndex < 0)
1818 queueIndex = 0;
1819 if (queueIndex >= MAX_LRU_LISTS)
1820 queueIndex = MAX_LRU_LISTS-1;
1822 return &m_LRULists[queueIndex];
1825 void Cache::removeFromLRUList(CachedObject *object)
1827 CachedObject *next = object->m_next;
1828 CachedObject *prev = object->m_prev;
1830 LRUList* list = getLRUListFor(object);
1831 CachedObject *&head = getLRUListFor(object)->m_head;
1833 if (next == 0 && prev == 0 && head != object) {
1834 return;
1837 object->m_next = 0;
1838 object->m_prev = 0;
1840 if (next)
1841 next->m_prev = prev;
1842 else if (list->m_tail == object)
1843 list->m_tail = prev;
1845 if (prev)
1846 prev->m_next = next;
1847 else if (head == object)
1848 head = next;
1850 totalSizeOfLRU -= object->size();
1853 void Cache::insertInLRUList(CachedObject *object)
1855 removeFromLRUList(object);
1857 assert( object );
1858 assert( !object->free() );
1859 assert( object->canDelete() );
1860 assert( object->allowInLRUList() );
1862 LRUList* list = getLRUListFor(object);
1864 CachedObject *&head = list->m_head;
1866 object->m_next = head;
1867 if (head)
1868 head->m_prev = object;
1869 head = object;
1871 if (object->m_next == 0)
1872 list->m_tail = object;
1874 totalSizeOfLRU += object->size();
1877 // --------------------------------------
1879 void CachedObjectClient::updatePixmap(const QRect&, CachedImage *) {}
1880 void CachedObjectClient::setStyleSheet(const DOM::DOMString &/*url*/, const DOM::DOMString &/*sheet*/, const DOM::DOMString &/*charset*/, const DOM::DOMString &/*mimetype*/) {}
1881 void CachedObjectClient::notifyFinished(CachedObject * /*finishedObj*/) {}
1882 void CachedObjectClient::error(int /*err*/, const QString &/*text*/) {}
1884 #undef CDEBUG
1886 #include "loader.moc"