File indexing completed on 2024-05-05 16:10:30

0001 /*
0002     This file is part of the KDE libraries
0003 
0004     Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de)
0005               (C) 2001-2003 Dirk Mueller (mueller@kde.org)
0006               (C) 2002 Waldo Bastian (bastian@kde.org)
0007               (C) 2003 Apple Computer, Inc.
0008               (C) 2006-2010 Germain Garand (germain@ebooksfrance.org)
0009 
0010     This library is free software; you can redistribute it and/or
0011     modify it under the terms of the GNU Library General Public
0012     License as published by the Free Software Foundation; either
0013     version 2 of the License, or (at your option) any later version.
0014 
0015     This library is distributed in the hope that it will be useful,
0016     but WITHOUT ANY WARRANTY; without even the implied warranty of
0017     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0018     Library General Public License for more details.
0019 
0020     You should have received a copy of the GNU Library General Public License
0021     along with this library; see the file COPYING.LIB.  If not, write to
0022     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0023     Boston, MA 02110-1301, USA.
0024 
0025     This class provides all functionality needed for loading images, style sheets and html
0026     pages from the web. It has a memory cache for these objects.
0027 
0028     // regarding the LRU:
0029     // http://www.is.kyusan-u.ac.jp/~chengk/pub/papers/compsac00_A07-07.pdf
0030 */
0031 
0032 #undef CACHE_DEBUG
0033 //#define CACHE_DEBUG
0034 
0035 #ifdef CACHE_DEBUG
0036 #define CDEBUG qCDebug(KHTML_LOG)
0037 #else
0038 #define CDEBUG qCDebug(KHTML_LOG)
0039 #endif
0040 
0041 #undef LOADER_DEBUG
0042 //#define LOADER_DEBUG
0043 
0044 //#define PRELOAD_DEBUG
0045 
0046 #include "loader.h"
0047 #include "seed.h"
0048 #include "woff.h"
0049 #include <imload/imagepainter.h>
0050 #include <imload/imagemanager.h>
0051 #include <KCompressionDevice>
0052 
0053 #include <assert.h>
0054 
0055 // default cache size
0056 #define DEFCACHESIZE 2096*1024
0057 
0058 //#include <qasyncio.h>
0059 //#include <qasyncimageio.h>
0060 #include <QApplication>
0061 #include <QDesktopWidget>
0062 #include <QPainter>
0063 #include <QBitmap>
0064 #include <QMovie>
0065 #include <QWidget>
0066 #include <QDebug>
0067 #include <kurlauthorized.h>
0068 #include <kio/job.h>
0069 #include <kio/jobuidelegate.h>
0070 #include <kio/jobclasses.h>
0071 #include <kio/scheduler.h>
0072 #include <kcharsets.h>
0073 #include <kiconloader.h>
0074 #include "khtml_debug.h"
0075 #include <kjobwidgets.h>
0076 
0077 #include <khtml_global.h>
0078 #include <khtml_part.h>
0079 
0080 #ifdef IMAGE_TITLES
0081 #include <qfile.h>
0082 #include <kfilemetainfo.h>
0083 #include <qtemporaryfile.h>
0084 #endif
0085 
0086 #include "html/html_documentimpl.h"
0087 #include "css/css_stylesheetimpl.h"
0088 #include "xml/dom_docimpl.h"
0089 
0090 #include "blocked_icon.cpp"
0091 
0092 #include <QPaintEngine>
0093 
0094 using namespace khtml;
0095 using namespace DOM;
0096 using namespace khtmlImLoad;
0097 
0098 #define MAX_LRU_LISTS 20
0099 struct LRUList {
0100     CachedObject *m_head;
0101     CachedObject *m_tail;
0102 
0103     LRUList() : m_head(nullptr), m_tail(nullptr) {}
0104 };
0105 
0106 static LRUList m_LRULists[MAX_LRU_LISTS];
0107 static LRUList *getLRUListFor(CachedObject *o);
0108 
0109 CachedObjectClient::~CachedObjectClient()
0110 {
0111 }
0112 
0113 CachedObject::~CachedObject()
0114 {
0115     Cache::removeFromLRUList(this);
0116 }
0117 
0118 void CachedObject::finish()
0119 {
0120     m_status = Cached;
0121 }
0122 
0123 bool CachedObject::isExpired() const
0124 {
0125     if (!m_expireDate.isValid()) {
0126         return false;
0127     }
0128     QDateTime now = QDateTime::currentDateTime();
0129     return (now >= m_expireDate);
0130 }
0131 
0132 void CachedObject::setRequest(Request *_request)
0133 {
0134     if (_request && !m_request) {
0135         m_status = Pending;
0136     }
0137 
0138     if (allowInLRUList()) {
0139         Cache::removeFromLRUList(this);
0140     }
0141 
0142     m_request = _request;
0143 
0144     if (allowInLRUList()) {
0145         Cache::insertInLRUList(this);
0146     }
0147 }
0148 
0149 void CachedObject::ref(CachedObjectClient *c)
0150 {
0151     if (m_preloadResult == PreloadNotReferenced) {
0152         if (isLoaded()) {
0153             m_preloadResult = PreloadReferencedWhileComplete;
0154         } else if (m_prospectiveRequest) {
0155             m_preloadResult = PreloadReferencedWhileLoading;
0156         } else {
0157             m_preloadResult = PreloadReferenced;
0158         }
0159     }
0160     // unfortunately we can be ref'ed multiple times from the
0161     // same object,  because it uses e.g. the same foreground
0162     // and the same background picture. so deal with it.
0163     // Hence the use of a QHash rather than of a QSet.
0164     m_clients.insertMulti(c, c);
0165     Cache::removeFromLRUList(this);
0166     m_accessCount++;
0167 }
0168 
0169 void CachedObject::deref(CachedObjectClient *c)
0170 {
0171     assert(c);
0172     assert(m_clients.count());
0173     assert(!canDelete());
0174     assert(m_clients.contains(c));
0175 
0176     Cache::flush();
0177 
0178     m_clients.take(c);
0179 
0180     if (allowInLRUList()) {
0181         Cache::insertInLRUList(this);
0182     }
0183 }
0184 
0185 void CachedObject::setSize(int size)
0186 {
0187     bool sizeChanged;
0188 
0189     if (!m_next && !m_prev && getLRUListFor(this)->m_head != this) {
0190         sizeChanged = false;
0191     } else {
0192         sizeChanged = (size - m_size) != 0;
0193     }
0194 
0195     // The object must now be moved to a different queue,
0196     // since its size has been changed.
0197     if (sizeChanged  && allowInLRUList()) {
0198         Cache::removeFromLRUList(this);
0199     }
0200 
0201     m_size = size;
0202 
0203     if (sizeChanged && allowInLRUList()) {
0204         Cache::insertInLRUList(this);
0205     }
0206 }
0207 
0208 QTextCodec *CachedObject::codecForBuffer(const QString &charset, const QByteArray &buffer) const
0209 {
0210     // we don't use heuristicContentMatch here since it is a) far too slow and
0211     // b) having too much functionality for our case.
0212 
0213     uchar *d = (uchar *) buffer.data();
0214     int s = buffer.size();
0215 
0216     // BOM
0217     if (s >= 3 &&
0218             d[0] == 0xef && d[1] == 0xbb && d[2] == 0xbf) {
0219         return QTextCodec::codecForMib(106);    // UTF-8
0220     }
0221 
0222     if (s >= 2 && ((d[0] == 0xff && d[1] == 0xfe) ||
0223                    (d[0] == 0xfe && d[1] == 0xff))) {
0224         return QTextCodec::codecForMib(1000);    // UCS-2
0225     }
0226 
0227     // Link or @charset
0228     if (!charset.isEmpty()) {
0229         QTextCodec *c = KCharsets::charsets()->codecForName(charset);
0230         if (c->mibEnum() == 11)  {
0231             // iso8859-8 (visually ordered)
0232             c = QTextCodec::codecForName("iso8859-8-i");
0233         }
0234         return c;
0235     }
0236 
0237     // Default
0238     return QTextCodec::codecForMib(4);   // latin 1
0239 }
0240 
0241 // -------------------------------------------------------------------------------------------
0242 
0243 CachedCSSStyleSheet::CachedCSSStyleSheet(DocLoader *dl, const DOMString &url, KIO::CacheControl _cachePolicy,
0244         const char *accept)
0245     : CachedObject(url, CSSStyleSheet, _cachePolicy, 0)
0246 {
0247     // Set the type we want (probably css or xml)
0248     QString ah = QLatin1String(accept);
0249     if (!ah.isEmpty()) {
0250         ah += ',';
0251     }
0252     ah += "*/*;q=0.1";
0253     setAccept(ah);
0254     m_hadError = false;
0255     m_wasBlocked = false;
0256     m_err = 0;
0257     // load the file.
0258     // Style sheets block rendering, they are therefore the higher priority item.
0259     // Do |not| touch the priority value unless you conducted thorough tests and
0260     // can back your choice with meaningful data, testing page load time and
0261     // time to first paint.
0262     Cache::loader()->load(dl, this, false, -8);
0263     m_loading = true;
0264 }
0265 
0266 CachedCSSStyleSheet::CachedCSSStyleSheet(const DOMString &url, const QString &stylesheet_data)
0267     : CachedObject(url, CSSStyleSheet, KIO::CC_Verify, stylesheet_data.length())
0268 {
0269     m_loading = false;
0270     m_status = Persistent;
0271     m_sheet = DOMString(stylesheet_data);
0272 }
0273 
0274 bool khtml::isAcceptableCSSMimetype(const DOM::DOMString &mimetype)
0275 {
0276     // matches Mozilla's check (cf. nsCSSLoader.cpp)
0277     return mimetype.isEmpty() || mimetype == "text/css" || mimetype == "application/x-unknown-content-type";
0278 }
0279 
0280 void CachedCSSStyleSheet::ref(CachedObjectClient *c)
0281 {
0282     CachedObject::ref(c);
0283 
0284     if (!m_loading) {
0285         if (m_hadError) {
0286             c->error(m_err, m_errText);
0287         } else {
0288             c->setStyleSheet(m_url, m_sheet, m_charset, m_mimetype);
0289         }
0290     }
0291 }
0292 
0293 void CachedCSSStyleSheet::data(QBuffer &buffer, bool eof)
0294 {
0295     if (!eof) {
0296         return;
0297     }
0298     buffer.close();
0299     setSize(buffer.buffer().size());
0300 
0301     m_charset = checkCharset(buffer.buffer());
0302     QTextCodec *c = nullptr;
0303     if (!m_charset.isEmpty()) {
0304         c = KCharsets::charsets()->codecForName(m_charset);
0305         if (c->mibEnum() == 11) {
0306             c = QTextCodec::codecForName("iso8859-8-i");
0307         }
0308     } else {
0309         c = codecForBuffer(m_charsetHint, buffer.buffer());
0310         m_charset = c->name();
0311     }
0312     QString data = c->toUnicode(buffer.buffer().data(), m_size);
0313     // workaround Qt bugs
0314     m_sheet = static_cast<QChar>(data[0]) == QChar::ByteOrderMark ? DOMString(data.mid(1)) : DOMString(data);
0315     m_loading = false;
0316 
0317     checkNotify();
0318 }
0319 
0320 void CachedCSSStyleSheet::checkNotify()
0321 {
0322     if (m_loading || m_hadError) {
0323         return;
0324     }
0325 
0326     CDEBUG << "finishedLoading" << m_url.string();
0327 
0328     for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
0329         it.next().value()->setStyleSheet(m_url, m_sheet, m_charset, m_mimetype);
0330     }
0331 }
0332 
0333 void CachedCSSStyleSheet::error(int err, const char *text)
0334 {
0335     m_hadError = true;
0336     m_err = err;
0337     m_errText = text;
0338     m_loading = false;
0339 
0340     for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
0341         it.next().value()->error(m_err, m_errText);
0342     }
0343 }
0344 
0345 QString CachedCSSStyleSheet::checkCharset(const QByteArray &buffer) const
0346 {
0347     int s = buffer.size();
0348     if (s <= 12) {
0349         return m_charset;
0350     }
0351 
0352     // @charset has to be first or directly after BOM.
0353     // CSS 2.1 says @charset should win over BOM, but since more browsers support BOM
0354     // than @charset, we default to that.
0355     const char *d = buffer.data();
0356     if (strncmp(d, "@charset \"", 10) == 0) {
0357         // the string until "; is the charset name
0358         const char *p = strchr(d + 10, '"');
0359         if (p == nullptr) {
0360             return m_charset;
0361         }
0362         QString charset = QString::fromLatin1(d + 10, p - (d + 10));
0363         return charset;
0364     }
0365     return m_charset;
0366 }
0367 
0368 // -------------------------------------------------------------------------------------------
0369 
0370 CachedScript::CachedScript(DocLoader *dl, const DOMString &url, KIO::CacheControl _cachePolicy, const char *)
0371     : CachedObject(url, Script, _cachePolicy, 0)
0372 {
0373     // It's javascript we want.
0374     // But some websites think their scripts are <some wrong mimetype here>
0375     // and refuse to serve them if we only accept application/x-javascript.
0376     setAccept(QLatin1String("*/*"));
0377     // load the file.
0378     // Scripts block document parsing. They are therefore second in our list of most
0379     // desired resources.
0380     Cache::loader()->load(dl, this, false/*incremental*/, -6);
0381     m_loading = true;
0382     m_hadError = false;
0383 }
0384 
0385 CachedScript::CachedScript(const DOMString &url, const QString &script_data)
0386     : CachedObject(url, Script, KIO::CC_Verify, script_data.length())
0387 {
0388     m_hadError = false;
0389     m_loading = false;
0390     m_status = Persistent;
0391     m_script = DOMString(script_data);
0392 }
0393 
0394 void CachedScript::ref(CachedObjectClient *c)
0395 {
0396     CachedObject::ref(c);
0397 
0398     if (!m_loading) {
0399         c->notifyFinished(this);
0400     }
0401 }
0402 
0403 void CachedScript::data(QBuffer &buffer, bool eof)
0404 {
0405     if (!eof) {
0406         return;
0407     }
0408     buffer.close();
0409     setSize(buffer.buffer().size());
0410 
0411     QTextCodec *c = codecForBuffer(m_charset, buffer.buffer());
0412     QString data = c->toUnicode(buffer.buffer().data(), m_size);
0413     m_script = static_cast<QChar>(data[0]) == QChar::ByteOrderMark ? DOMString(data.mid(1)) : DOMString(data);
0414     m_loading = false;
0415     checkNotify();
0416 }
0417 
0418 void CachedScript::checkNotify()
0419 {
0420     if (m_loading) {
0421         return;
0422     }
0423 
0424     for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
0425         it.next().value()->notifyFinished(this);
0426     }
0427 }
0428 
0429 void CachedScript::error(int /*err*/, const char * /*text*/)
0430 {
0431     m_hadError = true;
0432     m_loading  = false;
0433     checkNotify();
0434 }
0435 
0436 // ------------------------------------------------------------------------------------------
0437 
0438 static QString buildAcceptHeader()
0439 {
0440     return "image/png, image/jpeg, video/x-mng, image/jp2, image/gif;q=0.5,*/*;q=0.1";
0441 }
0442 
0443 // -------------------------------------------------------------------------------------
0444 
0445 CachedImage::CachedImage(DocLoader *dl, const DOMString &url, KIO::CacheControl _cachePolicy, const char *)
0446     : QObject(), CachedObject(url, Image, _cachePolicy, 0)
0447 {
0448     i = new khtmlImLoad::Image(this);
0449     //p = 0;
0450     //pixPart = 0;
0451     bg = nullptr;
0452     scaled = nullptr;
0453     bgColor = qRgba(0, 0, 0, 0);
0454     m_status = Unknown;
0455     setAccept(buildAcceptHeader());
0456     i->setShowAnimations(dl->showAnimations());
0457     m_loading = true;
0458 
0459     if (KHTMLGlobal::defaultHTMLSettings()->isAdFiltered(url.string())) {
0460         m_wasBlocked = true;
0461         CachedObject::finish();
0462     }
0463 }
0464 
0465 CachedImage::~CachedImage()
0466 {
0467     clear();
0468     delete i;
0469 }
0470 
0471 void CachedImage::ref(CachedObjectClient *c)
0472 {
0473     CachedObject::ref(c);
0474 
0475 #ifdef LOADER_DEBUG
0476     qCDebug(KHTML_LOG) << "image" << this << "ref'd by client" << c;
0477 #endif
0478 
0479     // for mouseovers, dynamic changes
0480     //### having both makes no sense
0481     if (m_status >= Persistent && !pixmap_size().isNull()) {
0482 #ifdef LOADER_DEBUG
0483         qCDebug(KHTML_LOG) << "Notifying finished size:" << i->size().width() << "," << i->size().height();
0484 #endif
0485         c->updatePixmap(QRect(QPoint(0, 0), pixmap_size()), this);
0486         c->notifyFinished(this);
0487     }
0488 }
0489 
0490 void CachedImage::deref(CachedObjectClient *c)
0491 {
0492     CachedObject::deref(c);
0493     /*    if(m && m_clients.isEmpty() && m->running())
0494             m->pause();*/
0495 }
0496 
0497 #define BGMINWIDTH      32
0498 #define BGMINHEIGHT     32
0499 
0500 QPixmap CachedImage::tiled_pixmap(const QColor &newc, int xWidth, int xHeight)
0501 {
0502 
0503     // no error indication for background images
0504     if (m_hadError || m_wasBlocked) {
0505         return *Cache::nullPixmap;
0506     }
0507 
0508     // If we don't have size yet, nothing to draw yet
0509     if (i->size().width() == 0 || i->size().height() == 0) {
0510         return *Cache::nullPixmap;
0511     }
0512 
0513 #ifdef __GNUC__
0514 #warning "Needs some additional performance work"
0515 #endif
0516 
0517     static QRgb bgTransparent = qRgba(0, 0, 0, 0);
0518 
0519     QSize s(pixmap_size());
0520     int w = xWidth;
0521     int h = xHeight;
0522 
0523     if (w == -1) {
0524         xWidth = w = s.width();
0525     }
0526     if (h == -1) {
0527         xHeight = h = s.height();
0528     }
0529 
0530     if (((bgColor != bgTransparent) && (bgColor != newc.rgba())) ||
0531             (bgSize != QSize(xWidth, xHeight))) {
0532         delete bg; bg = nullptr;
0533     }
0534 
0535     if (bg) {
0536         return *bg;
0537     }
0538 
0539     const QPixmap *src; //source for pretiling, if any
0540 
0541     const QPixmap &r = pixmap(); //this is expensive
0542     if (r.isNull()) {
0543         return r;
0544     }
0545 
0546     //See whether we should scale
0547     if (xWidth != s.width() || xHeight != s.height()) {
0548         src = scaled_pixmap(xWidth, xHeight);
0549     } else {
0550         src = &r;
0551     }
0552 
0553     bgSize = QSize(xWidth, xHeight);
0554 
0555     //See whether we can - and should - pre-blend
0556     // ### this needs serious investigations. Not likely to help with transparent bgColor,
0557     // won't work with CSS3 multiple backgrounds. Does it help at all in Qt4? (ref: #114938)
0558     if (newc.isValid() && (r.hasAlpha() || r.hasAlphaChannel())) {
0559         bg = new QPixmap(xWidth, xHeight);
0560         bg->fill(newc);
0561         QPainter p(bg);
0562         p.drawPixmap(0, 0, *src);
0563         bgColor = newc.rgba();
0564         src     = bg;
0565     } else {
0566         bgColor = bgTransparent;
0567     }
0568 
0569     //See whether to pre-tile.
0570     if (w * h < 8192) {
0571         if (r.width() < BGMINWIDTH) {
0572             w = ((BGMINWIDTH - 1) / xWidth + 1) * xWidth;
0573         }
0574         if (r.height() < BGMINHEIGHT) {
0575             h = ((BGMINHEIGHT - 1) / xHeight + 1) * xHeight;
0576         }
0577     }
0578 
0579     if (w != xWidth  || h != xHeight) {
0580         // qCDebug(KHTML_LOG) << "pre-tiling " << s.width() << "," << s.height() << " to " << w << "," << h;
0581         QPixmap *oldbg = bg;
0582         bg = new QPixmap(w, h);
0583         if (src->hasAlpha() || src->hasAlphaChannel()) {
0584             if (newc.isValid() && (bgColor != bgTransparent)) {
0585                 bg->fill(bgColor);
0586             } else {
0587                 bg->fill(Qt::transparent);
0588             }
0589         }
0590 
0591         QPainter p(bg);
0592         p.drawTiledPixmap(0, 0, w, h, *src);
0593         p.end();
0594 
0595         if (src == oldbg) {
0596             delete oldbg;
0597         }
0598     } else if (src && !bg) {
0599         // we were asked for the entire pixmap. Cache it.
0600         // ### goes against imload stuff, but it's far too expensive
0601         //     to recreate the full pixmap each time it's requested as
0602         //     we don't know what portion of it will be used eventually
0603         //     (by e.g. paintBackgroundExtended). It could be a few pixels of
0604         //     a huge image. See #140248/#1 for an obvious example.
0605         //     Imload probably needs to handle all painting in paintBackgroundExtended.
0606         bg = new QPixmap(*src);
0607     }
0608 
0609     if (bg) {
0610         return *bg;
0611     }
0612 
0613     return *src;
0614 }
0615 
0616 QPixmap *CachedImage::scaled_pixmap(int xWidth, int xHeight)
0617 {
0618     // no error indication for background images
0619     if (m_hadError || m_wasBlocked) {
0620         return Cache::nullPixmap;
0621     }
0622 
0623     // If we don't have size yet, nothing to draw yet
0624     if (i->size().width() == 0 || i->size().height() == 0) {
0625         return Cache::nullPixmap;
0626     }
0627 
0628     if (scaled) {
0629         if (scaled->width() == xWidth && scaled->height() == xHeight) {
0630             return scaled;
0631         }
0632         delete scaled;
0633     }
0634 
0635     //### this is quite awful performance-wise. It should avoid
0636     // alpha if not needed, and go to pixmap, etc.
0637     QImage im(xWidth, xHeight, QImage::Format_ARGB32_Premultiplied);
0638 
0639     QPainter paint(&im);
0640     paint.setCompositionMode(QPainter::CompositionMode_Source);
0641     ImagePainter pi(i, QSize(xWidth, xHeight));
0642     pi.paint(0, 0, &paint);
0643     paint.end();
0644 
0645     scaled = new QPixmap(QPixmap::fromImage(im));
0646 
0647     return scaled;
0648 }
0649 
0650 QPixmap CachedImage::pixmap() const
0651 {
0652     if (m_hadError) {
0653         return *Cache::brokenPixmap;
0654     }
0655 
0656     if (m_wasBlocked) {
0657         return *Cache::blockedPixmap;
0658     }
0659 
0660     int w = i->size().width();
0661     int h = i->size().height();
0662 
0663     if (i->hasAlpha() && QApplication::desktop()->paintEngine() &&
0664             !QApplication::desktop()->paintEngine()->hasFeature(QPaintEngine::PorterDuff)) {
0665         QImage im(w, h, QImage::Format_ARGB32_Premultiplied);
0666         QPainter paint(&im);
0667         paint.setCompositionMode(QPainter::CompositionMode_Source);
0668         ImagePainter pi(i);
0669         pi.paint(0, 0, &paint);
0670         paint.end();
0671         return QPixmap::fromImage(im, Qt::NoOpaqueDetection);
0672     } else {
0673         QPixmap pm(w, h);
0674         if (i->hasAlpha()) {
0675             pm.fill(Qt::transparent);
0676         }
0677         QPainter paint(&pm);
0678         paint.setCompositionMode(QPainter::CompositionMode_Source);
0679         ImagePainter pi(i);
0680         pi.paint(0, 0, &paint);
0681         paint.end();
0682         return pm;
0683     }
0684 }
0685 
0686 QSize CachedImage::pixmap_size() const
0687 {
0688     if (m_wasBlocked) {
0689         return Cache::blockedPixmap->size();
0690     }
0691     if (m_hadError) {
0692         return Cache::brokenPixmap->size();
0693     }
0694     if (i) {
0695         return i->size();
0696     }
0697     return QSize();
0698 }
0699 
0700 void CachedImage::imageHasGeometry(khtmlImLoad::Image * /*img*/, int width, int height)
0701 {
0702 #ifdef LOADER_DEBUG
0703     qCDebug(KHTML_LOG) << this << "got geometry" << width << "x" << height;
0704 #endif
0705 
0706     do_notify(QRect(0, 0, width, height));
0707 }
0708 
0709 void CachedImage::imageChange(khtmlImLoad::Image * /*img*/, QRect region)
0710 {
0711 #ifdef LOADER_DEBUG
0712     qCDebug(KHTML_LOG) << "Image" << this << "change" <<
0713              region.x() << "," << region.y() << ":" << region.width() << "x" << region.height();
0714 #endif
0715 
0716     //### this is overly conservative -- I guess we need to also specify reason,
0717     //e.g. repaint vs. changed !!!
0718     delete bg;
0719     bg = nullptr;
0720 
0721     do_notify(region);
0722 }
0723 
0724 void CachedImage::doNotifyFinished()
0725 {
0726     for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
0727         it.next().value()->notifyFinished(this);
0728     }
0729 }
0730 
0731 void CachedImage::imageError(khtmlImLoad::Image * /*img*/)
0732 {
0733     error(0, nullptr);
0734 }
0735 
0736 void CachedImage::imageDone(khtmlImLoad::Image * /*img*/)
0737 {
0738 #ifdef LOADER_DEBUG
0739     qCDebug(KHTML_LOG) << "Image is done:" << this;
0740 #endif
0741     m_status = Persistent;
0742     m_loading = false;
0743     doNotifyFinished();
0744 }
0745 
0746 // QRect CachedImage::valid_rect() const
0747 // {
0748 //     if (m_wasBlocked) return Cache::blockedPixmap->rect();
0749 //     return (m_hadError ? Cache::brokenPixmap->rect() : m ? m->frameRect() : ( p ? p->rect() : QRect()) );
0750 // }
0751 
0752 void CachedImage::do_notify(const QRect &r)
0753 {
0754     for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
0755 #ifdef LOADER_DEBUG
0756         qCDebug(KHTML_LOG) << "image" << this << "notify of geom client" << it.peekNext().value();
0757 #endif
0758         it.next().value()->updatePixmap(r, this);
0759     }
0760 }
0761 
0762 void CachedImage::setShowAnimations(KHTMLSettings::KAnimationAdvice showAnimations)
0763 {
0764     if (i) {
0765         i->setShowAnimations(showAnimations);
0766     }
0767 }
0768 
0769 void CachedImage::clear()
0770 {
0771     delete i;   i = new khtmlImLoad::Image(this);
0772     delete scaled;  scaled = nullptr;
0773     bgColor = qRgba(0, 0, 0, 0xff);
0774     delete bg;  bg = nullptr;
0775     bgSize = QSize(-1, -1);
0776 
0777     setSize(0);
0778 }
0779 
0780 void CachedImage::data(QBuffer &_buffer, bool eof)
0781 {
0782 #ifdef LOADER_DEBUG
0783     qCDebug(KHTML_LOG) << this << "buffersize =" << _buffer.buffer().size() << ", eof =" << eof << ", pos :" << _buffer.pos();
0784 #endif
0785     i->processData((uchar *)_buffer.data().data(), _buffer.pos());
0786 
0787     _buffer.close();
0788 
0789     if (eof) {
0790         i->processEOF();
0791     }
0792 }
0793 
0794 void CachedImage::finish()
0795 {
0796     CachedObject::finish();
0797     m_loading = false;
0798     QSize s = pixmap_size();
0799     setSize(s.width() * s.height() * 2);
0800 }
0801 
0802 void CachedImage::error(int /*err*/, const char * /*text*/)
0803 {
0804     clear();
0805     m_hadError = true;
0806     m_loading = false;
0807     do_notify(QRect(0, 0, 16, 16));
0808     for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
0809         it.next().value()->notifyFinished(this);
0810     }
0811 }
0812 
0813 // -------------------------------------------------------------------------------------------
0814 
0815 CachedSound::CachedSound(DocLoader *dl, const DOMString &url, KIO::CacheControl _cachePolicy, const char *)
0816     : CachedObject(url, Sound, _cachePolicy, 0)
0817 {
0818     setAccept(QLatin1String("*/*"));   // should be whatever phonon would accept...
0819     Cache::loader()->load(dl, this, false/*incremental*/, 2);
0820     m_loading = true;
0821 }
0822 
0823 void CachedSound::ref(CachedObjectClient *c)
0824 {
0825     CachedObject::ref(c);
0826 
0827     if (!m_loading) {
0828         c->notifyFinished(this);
0829     }
0830 }
0831 
0832 void CachedSound::data(QBuffer &buffer, bool eof)
0833 {
0834     if (!eof) {
0835         return;
0836     }
0837     buffer.close();
0838     setSize(buffer.buffer().size());
0839 
0840     m_sound = buffer.buffer();
0841     m_loading = false;
0842     checkNotify();
0843 }
0844 
0845 void CachedSound::checkNotify()
0846 {
0847     if (m_loading) {
0848         return;
0849     }
0850 
0851     for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
0852         it.next().value()->notifyFinished(this);
0853     }
0854 }
0855 
0856 void CachedSound::error(int /*err*/, const char * /*text*/)
0857 {
0858     m_loading = false;
0859     checkNotify();
0860 }
0861 
0862 // -------------------------------------------------------------------------------------------
0863 
0864 CachedFont::CachedFont(DocLoader *dl, const DOMString &url, KIO::CacheControl _cachePolicy, const char *)
0865     : CachedObject(url, Font, _cachePolicy, 0)
0866 {
0867     setAccept(QLatin1String("*/*"));
0868     // Fonts are desired early because their absence will lead to a page being rendered
0869     // with a default replacement, then the text being re-rendered with the new font when it arrives.
0870     // This can be fairly disturbing for the reader - more than missing images for instance.
0871     Cache::loader()->load(dl, this, false /*incremental*/, -4);
0872     m_loading = true;
0873 }
0874 
0875 void CachedFont::ref(CachedObjectClient *c)
0876 {
0877     CachedObject::ref(c);
0878 
0879     if (!m_loading) {
0880         c->notifyFinished(this);
0881     }
0882 }
0883 
0884 void CachedFont::data(QBuffer &buffer, bool eof)
0885 {
0886     if (!eof) {
0887         return;
0888     }
0889     buffer.close();
0890     m_font = buffer.buffer();
0891 
0892     // some fonts are compressed.
0893     {
0894         KCompressionDevice::CompressionType compressionType = KCompressionDevice::compressionTypeForMimeType(mimetype());
0895         QScopedPointer<KCompressionDevice> dev(new KCompressionDevice(&buffer, false /*autoDeleteInDevice*/, compressionType));
0896         if (dev && dev->open(QIODevice::ReadOnly)) {
0897             m_font = dev->readAll();
0898         }
0899     }
0900 
0901     // handle decoding of WOFF fonts
0902     int woffStatus = eWOFF_ok;
0903     if (int need = WOFF::getDecodedSize(m_font.constData(), m_font.size(), &woffStatus)) {
0904         // qCDebug(KHTML_LOG) << "***************************** Got WOFF FoNT";
0905         m_hadError = true;
0906         do {
0907             if (WOFF_FAILURE(woffStatus)) {
0908                 break;
0909             }
0910             QByteArray wbuffer;
0911             wbuffer.resize(need);
0912             int len;
0913             woffStatus = eWOFF_ok;
0914             WOFF::decodeToBuffer(m_font.constData(), m_font.size(), wbuffer.data(), wbuffer.size(), &len, &woffStatus);
0915             if (WOFF_FAILURE(woffStatus)) {
0916                 break;
0917             }
0918             wbuffer.resize(len);
0919             m_font = wbuffer;
0920             m_hadError = false;
0921         } while (false);
0922     } else if (m_font.isEmpty()) {
0923         m_hadError = true;
0924     } else {
0925         // qCDebug(KHTML_LOG) << "******** #################### ********************* NON WOFF font";
0926     }
0927     setSize(m_font.size());
0928 
0929     m_loading = false;
0930     checkNotify();
0931 }
0932 
0933 void CachedFont::checkNotify()
0934 {
0935     if (m_loading) {
0936         return;
0937     }
0938 
0939     for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
0940         it.next().value()->notifyFinished(this);
0941     }
0942 }
0943 
0944 void CachedFont::error(int /*err*/, const char * /*text*/)
0945 {
0946     m_loading = false;
0947     m_hadError = true;
0948     checkNotify();
0949 }
0950 
0951 // ------------------------------------------------------------------------------------------
0952 
0953 Request::Request(DocLoader *dl, CachedObject *_object, bool _incremental, int _priority)
0954 {
0955     object = _object;
0956     object->setRequest(this);
0957     incremental = _incremental;
0958     priority = _priority;
0959     m_docLoader = dl;
0960 }
0961 
0962 Request::~Request()
0963 {
0964     object->setRequest(nullptr);
0965 }
0966 
0967 // ------------------------------------------------------------------------------------------
0968 
0969 DocLoader::DocLoader(KHTMLPart *part, DocumentImpl *doc)
0970 {
0971     m_cachePolicy = KIO::CC_Verify;
0972     m_creationDate = QDateTime::currentDateTime();
0973     m_bautoloadImages = true;
0974     m_showAnimations = KHTMLSettings::KAnimationEnabled;
0975     m_part = part;
0976     m_doc = doc;
0977 
0978     Cache::docloader->append(this);
0979 }
0980 
0981 DocLoader::~DocLoader()
0982 {
0983     clearPreloads();
0984     Cache::loader()->cancelRequests(this);
0985     Cache::docloader->removeAll(this);
0986 }
0987 
0988 void DocLoader::setCacheCreationDate(const QDateTime &_creationDate)
0989 {
0990     if (_creationDate.isValid()) {
0991         m_creationDate = _creationDate;
0992     } else {
0993         m_creationDate = QDateTime::currentDateTime();
0994     }
0995 }
0996 
0997 void DocLoader::setExpireDate(const QDateTime &_expireDate)
0998 {
0999     m_expireDate = _expireDate;
1000 
1001 #ifdef CACHE_DEBUG
1002     qCDebug(KHTML_LOG) << QDateTime::currentDateTime().secsTo(m_expireDate) << "seconds left until reload required.";
1003 #endif
1004 }
1005 
1006 void DocLoader::setRelativeExpireDate(qint64 seconds)
1007 {
1008     m_expireDate = m_creationDate.addSecs(seconds);
1009 }
1010 
1011 void DocLoader::insertCachedObject(CachedObject *o) const
1012 {
1013     m_docObjects.insert(o);
1014 }
1015 
1016 bool DocLoader::needReload(CachedObject *existing, const QString &fullURL)
1017 {
1018     bool reload = false;
1019     if (m_cachePolicy == KIO::CC_Verify) {
1020         if (!m_reloadedURLs.contains(fullURL)) {
1021             if (existing && existing->isExpired() && !existing->isPreloaded()) {
1022                 Cache::removeCacheEntry(existing);
1023                 m_reloadedURLs.append(fullURL);
1024                 reload = true;
1025             }
1026         }
1027     } else if ((m_cachePolicy == KIO::CC_Reload) || (m_cachePolicy == KIO::CC_Refresh)) {
1028         if (!m_reloadedURLs.contains(fullURL)) {
1029             if (existing && !existing->isPreloaded()) {
1030                 Cache::removeCacheEntry(existing);
1031             }
1032             if (!existing || !existing->isPreloaded()) {
1033                 m_reloadedURLs.append(fullURL);
1034                 reload = true;
1035             }
1036         }
1037     }
1038     return reload;
1039 }
1040 
1041 void DocLoader::registerPreload(CachedObject *resource)
1042 {
1043     if (!resource || resource->isLoaded() || m_preloads.contains(resource)) {
1044         return;
1045     }
1046     resource->increasePreloadCount();
1047     m_preloads.insert(resource);
1048     resource->setProspectiveRequest();
1049 #ifdef PRELOAD_DEBUG
1050     fprintf(stderr, "PRELOADING %s\n", resource->url().string().toLatin1().data());
1051 #endif
1052 }
1053 
1054 void DocLoader::clearPreloads()
1055 {
1056     printPreloadStats();
1057     QSet<CachedObject *>::iterator end = m_preloads.end();
1058     for (QSet<CachedObject *>::iterator it = m_preloads.begin(); it != end; ++it) {
1059         CachedObject *res = *it;
1060         res->decreasePreloadCount();
1061         if (res->preloadResult() == CachedObject::PreloadNotReferenced || res->hadError()) {
1062             Cache::removeCacheEntry(res);
1063         }
1064     }
1065     m_preloads.clear();
1066 }
1067 
1068 void DocLoader::printPreloadStats()
1069 {
1070 #ifdef PRELOAD_DEBUG
1071     unsigned scripts = 0;
1072     unsigned scriptMisses = 0;
1073     unsigned stylesheets = 0;
1074     unsigned stylesheetMisses = 0;
1075     unsigned images = 0;
1076     unsigned imageMisses = 0;
1077     QSet<CachedObject *>::iterator end = m_preloads.end();
1078     for (QSet<CachedObject *>::iterator it = m_preloads.begin(); it != end; ++it) {
1079         CachedObject *res = *it;
1080         if (res->preloadResult() == CachedObject::PreloadNotReferenced) {
1081             fprintf(stderr, "!! UNREFERENCED PRELOAD %s\n", res->url().string().toLatin1().data());
1082         } else if (res->preloadResult() == CachedObject::PreloadReferencedWhileComplete) {
1083             fprintf(stderr, "HIT COMPLETE PRELOAD %s\n", res->url().string().toLatin1().data());
1084         } else if (res->preloadResult() == CachedObject::PreloadReferencedWhileLoading) {
1085             fprintf(stderr, "HIT LOADING PRELOAD %s\n", res->url().string().toLatin1().data());
1086         }
1087 
1088         if (res->type() == CachedObject::Script) {
1089             scripts++;
1090             if (res->preloadResult() < CachedObject::PreloadReferencedWhileLoading) {
1091                 scriptMisses++;
1092             }
1093         } else if (res->type() == CachedObject::CSSStyleSheet) {
1094             stylesheets++;
1095             if (res->preloadResult() < CachedObject::PreloadReferencedWhileLoading) {
1096                 stylesheetMisses++;
1097             }
1098         } else {
1099             images++;
1100             if (res->preloadResult() < CachedObject::PreloadReferencedWhileLoading) {
1101                 imageMisses++;
1102             }
1103         }
1104     }
1105     if (scripts) {
1106         fprintf(stderr, "SCRIPTS: %d (%d hits, hit rate %d%%)\n", scripts, scripts - scriptMisses, (scripts - scriptMisses) * 100 / scripts);
1107     }
1108     if (stylesheets) {
1109         fprintf(stderr, "STYLESHEETS: %d (%d hits, hit rate %d%%)\n", stylesheets, stylesheets - stylesheetMisses, (stylesheets - stylesheetMisses) * 100 / stylesheets);
1110     }
1111     if (images) {
1112         fprintf(stderr, "IMAGES:  %d (%d hits, hit rate %d%%)\n", images, images - imageMisses, (images - imageMisses) * 100 / images);
1113     }
1114 #endif
1115 }
1116 
1117 static inline bool securityCheckUrl(const QUrl &fullURL, KHTMLPart *part, DOM::DocumentImpl *doc,
1118                                     bool doRedirectCheck, bool isImg)
1119 {
1120     if (!fullURL.isValid()) {
1121         return false;
1122     }
1123     if (part && part->onlyLocalReferences() && fullURL.scheme() != "file" && fullURL.scheme() != "data") {
1124         return false;
1125     }
1126     if (doRedirectCheck && doc) {
1127         if (isImg && part && part->forcePermitLocalImages() && fullURL.scheme() == "file") {
1128             return true;
1129         } else {
1130             return KUrlAuthorized::authorizeUrlAction("redirect", doc->URL(), fullURL);
1131         }
1132     }
1133 
1134     return true;
1135 }
1136 
1137 #define DOCLOADER_SECCHECK_IMP(doRedirectCheck,isImg,failValue) \
1138     QUrl fullURL(m_doc->completeURL(url.string())); \
1139     if (!securityCheckUrl(fullURL, m_part, m_doc, doRedirectCheck, isImg)) \
1140         return failValue;
1141 
1142 #define DOCLOADER_SECCHECK(doRedirectCheck)      DOCLOADER_SECCHECK_IMP(doRedirectCheck, false, nullptr)
1143 #define DOCLOADER_SECCHECK_BOOL(doRedirectCheck) DOCLOADER_SECCHECK_IMP(doRedirectCheck, false, false)
1144 #define DOCLOADER_SECCHECK_IMG(doRedirectCheck)  DOCLOADER_SECCHECK_IMP(doRedirectCheck, true,  nullptr)
1145 
1146 bool DocLoader::willLoadMediaElement(const DOM::DOMString &url)
1147 {
1148     DOCLOADER_SECCHECK_BOOL(true);
1149 
1150     return true;
1151 }
1152 
1153 CachedImage *DocLoader::requestImage(const DOM::DOMString &url)
1154 {
1155     DOCLOADER_SECCHECK_IMG(true);
1156 
1157     CachedImage *i = Cache::requestObject<CachedImage, CachedObject::Image>(this, fullURL, nullptr);
1158 
1159     if (i && i->status() == CachedObject::Unknown && autoloadImages()) {
1160         Cache::loader()->load(this, i, true /*incremental*/);
1161     }
1162 
1163     return i;
1164 }
1165 
1166 CachedCSSStyleSheet *DocLoader::requestStyleSheet(const DOM::DOMString &url, const QString &charset,
1167         const char *accept, bool userSheet)
1168 {
1169     DOCLOADER_SECCHECK(!userSheet);
1170 
1171     CachedCSSStyleSheet *s = Cache::requestObject<CachedCSSStyleSheet, CachedObject::CSSStyleSheet>(this, fullURL, accept);
1172     if (s && !charset.isEmpty()) {
1173         s->setCharsetHint(charset);
1174     }
1175     return s;
1176 }
1177 
1178 CachedScript *DocLoader::requestScript(const DOM::DOMString &url, const QString &charset)
1179 {
1180     DOCLOADER_SECCHECK(true);
1181     if (! KHTMLGlobal::defaultHTMLSettings()->isJavaScriptEnabled(fullURL.host()) ||
1182             KHTMLGlobal::defaultHTMLSettings()->isAdFiltered(fullURL.url())) {
1183         return nullptr;
1184     }
1185 
1186     CachedScript *s = Cache::requestObject<CachedScript, CachedObject::Script>(this, fullURL, nullptr);
1187     if (s && !charset.isEmpty()) {
1188         s->setCharset(charset);
1189     }
1190     return s;
1191 }
1192 
1193 CachedSound *DocLoader::requestSound(const DOM::DOMString &url)
1194 {
1195     DOCLOADER_SECCHECK(true);
1196     CachedSound *s = Cache::requestObject<CachedSound, CachedObject::Sound>(this, fullURL, nullptr);
1197     return s;
1198 }
1199 
1200 CachedFont *DocLoader::requestFont(const DOM::DOMString &url)
1201 {
1202     DOCLOADER_SECCHECK(true);
1203     CachedFont *s = Cache::requestObject<CachedFont, CachedObject::Font>(this, fullURL, nullptr);
1204     return s;
1205 }
1206 
1207 #undef DOCLOADER_SECCHECK
1208 
1209 void DocLoader::setAutoloadImages(bool enable)
1210 {
1211     if (enable == m_bautoloadImages) {
1212         return;
1213     }
1214 
1215     m_bautoloadImages = enable;
1216 
1217     if (!m_bautoloadImages) {
1218         return;
1219     }
1220 
1221     for (QSetIterator<CachedObject *> it(m_docObjects); it.hasNext();) {
1222         CachedObject *cur = it.next();
1223         if (cur->type() == CachedObject::Image) {
1224             CachedImage *img = const_cast<CachedImage *>(static_cast<const CachedImage *>(cur));
1225 
1226             CachedObject::Status status = img->status();
1227             if (status != CachedObject::Unknown) {
1228                 continue;
1229             }
1230 
1231             Cache::loader()->load(this, img, true /*incremental*/);
1232         }
1233     }
1234 }
1235 
1236 void DocLoader::setShowAnimations(KHTMLSettings::KAnimationAdvice showAnimations)
1237 {
1238     if (showAnimations == m_showAnimations) {
1239         return;
1240     }
1241     m_showAnimations = showAnimations;
1242 
1243     for (QSetIterator<CachedObject *> it(m_docObjects); it.hasNext();) {
1244         CachedObject *cur = it.next();
1245         if (cur->type() == CachedObject::Image) {
1246             CachedImage *img = const_cast<CachedImage *>(static_cast<const CachedImage *>(cur));
1247 
1248             img->setShowAnimations(m_showAnimations);
1249         }
1250     }
1251 }
1252 
1253 // ------------------------------------------------------------------------------------------
1254 
1255 Loader::Loader() : QObject()
1256 {
1257     m_supportedImageTypes = khtmlImLoad::ImageManager::loaderDatabase()->supportedMimeTypes();
1258 }
1259 
1260 Loader::~Loader()
1261 {
1262     qDeleteAll(m_requestsLoading);
1263 }
1264 
1265 void Loader::load(DocLoader *dl, CachedObject *object, bool incremental, int priority)
1266 {
1267     Request *req = new Request(dl, object, incremental, priority);
1268     scheduleRequest(req);
1269     emit requestStarted(req->m_docLoader, req->object);
1270 }
1271 
1272 void Loader::scheduleRequest(Request *req)
1273 {
1274 #ifdef LOADER_DEBUG
1275     qCDebug(KHTML_LOG) << "starting Loader url =" << req->object->url().string();
1276 #endif
1277 
1278     QUrl u(req->object->url().string());
1279     KIO::TransferJob *job = KIO::get(u, KIO::NoReload, KIO::HideProgressInfo /*no GUI*/);
1280 
1281     job->addMetaData("cache", KIO::getCacheControlString(req->object->cachePolicy()));
1282     if (!req->object->accept().isEmpty()) {
1283         job->addMetaData("accept", req->object->accept());
1284     }
1285     if (req->m_docLoader) {
1286         job->addMetaData("referrer",  req->m_docLoader->doc()->URL().url());
1287         KHTMLPart *part = req->m_docLoader->part();
1288         if (part) {
1289             job->addMetaData("cross-domain", part->toplevelURL().url());
1290             if (part->widget()) {
1291                 KJobWidgets::setWindow(job, part->widget()->topLevelWidget());
1292             }
1293         }
1294     }
1295 
1296     connect(job, SIGNAL(result(KJob*)), this, SLOT(slotFinished(KJob*)));
1297     connect(job, SIGNAL(mimetype(KIO::Job*,QString)), this, SLOT(slotMimetype(KIO::Job*,QString)));
1298     connect(job, SIGNAL(data(KIO::Job*,QByteArray)),
1299             SLOT(slotData(KIO::Job*,QByteArray)));
1300 
1301     KIO::Scheduler::setJobPriority(job, req->priority);
1302 
1303     m_requestsLoading.insertMulti(job, req);
1304 }
1305 
1306 void Loader::slotMimetype(KIO::Job *j, const QString &s)
1307 {
1308     Request *r = m_requestsLoading.value(j);
1309     if (!r) {
1310         return;
1311     }
1312     CachedObject *o = r->object;
1313 
1314     // Mozilla plain ignores any  mimetype that doesn't have / in it, and handles it as "",
1315     // including when being picky about mimetypes. Match that for better compatibility with broken servers.
1316     if (s.contains('/')) {
1317         o->m_mimetype = s;
1318     } else {
1319         o->m_mimetype = "";
1320     }
1321 }
1322 
1323 void Loader::slotFinished(KJob *job)
1324 {
1325     KIO::TransferJob *j = static_cast<KIO::TransferJob *>(job);
1326     Request *r = m_requestsLoading.take(j);
1327 
1328     if (!r) {
1329         return;
1330     }
1331 
1332     bool reqFailed = false;
1333     if (j->error()) {
1334         reqFailed = true;
1335     } else if (j->isErrorPage()) {
1336         if (r->object->type() == CachedObject::Image && m_supportedImageTypes.contains(r->object->m_mimetype)) {
1337             // Do not set the request as a failed, we asked for an image and got it
1338             // as the content of the error response (e.g. 404)
1339         } else {
1340             reqFailed = true;
1341         }
1342     }
1343 
1344     if (reqFailed) {
1345 #ifdef LOADER_DEBUG
1346         qCDebug(KHTML_LOG) << "ERROR: job->error() =" << j->error() << ", job->isErrorPage() =" << j->isErrorPage();
1347 #endif
1348         r->object->error(job->error(), job->errorText().toLatin1().constData());
1349         emit requestFailed(r->m_docLoader, r->object);
1350     } else {
1351         QString cs = j->queryMetaData("charset");
1352         if (!cs.isEmpty()) {
1353             r->object->setCharset(cs);
1354         }
1355         r->object->data(r->m_buffer, true);
1356         emit requestDone(r->m_docLoader, r->object);
1357         QDateTime expireDate = QDateTime::fromTime_t(j->queryMetaData("expire-date").toLong());
1358 #ifdef LOADER_DEBUG
1359         qCDebug(KHTML_LOG) << "url =" << j->url().url();
1360 #endif
1361         r->object->setExpireDate(expireDate);
1362 
1363         if (r->object->type() == CachedObject::Image) {
1364             QString fn = j->queryMetaData("content-disposition-filename");
1365             static_cast<CachedImage *>(r->object)->setSuggestedFilename(fn);
1366 #ifdef IMAGE_TITLES
1367             static_cast<CachedImage *>(r->object)->setSuggestedTitle(fn);
1368             QTemporaryFile tf;
1369             tf.open();
1370             tf.write((const char *)r->m_buffer.buffer().data(), r->m_buffer.size());
1371             tf.flush();
1372             KFileMetaInfo kfmi(tf.fileName());
1373             if (!kfmi.isEmpty()) {
1374                 KFileMetaInfoItem i = kfmi.item("Name");
1375                 if (i.isValid()) {
1376                     static_cast<CachedImage *>(r->object)->setSuggestedTitle(i.string());
1377                 } else {
1378                     i = kfmi.item("Title");
1379                     if (i.isValid()) {
1380                         static_cast<CachedImage *>(r->object)->setSuggestedTitle(i.string());
1381                     }
1382                 }
1383             }
1384 #endif
1385         }
1386     }
1387 
1388     r->object->finish();
1389 
1390 #ifdef LOADER_DEBUG
1391     qCDebug(KHTML_LOG) << "JOB FINISHED" << r->object << ":" << r->object->url().string();
1392 #endif
1393 
1394     delete r;
1395 }
1396 
1397 void Loader::slotData(KIO::Job *job, const QByteArray &data)
1398 {
1399     Request *r = m_requestsLoading.value(job);
1400     if (!r) {
1401         qCDebug(KHTML_LOG) << "got data for unknown request!";
1402         return;
1403     }
1404 
1405     if (!r->m_buffer.isOpen()) {
1406         r->m_buffer.open(QIODevice::WriteOnly);
1407     }
1408 
1409     r->m_buffer.write(data.data(), data.size());
1410 
1411     if (r->incremental) {
1412         r->object->data(r->m_buffer, false);
1413     }
1414 }
1415 
1416 int Loader::numRequests(DocLoader *dl) const
1417 {
1418     int res = 0;
1419     foreach (Request *req, m_requestsLoading)
1420         if (req->m_docLoader == dl) {
1421             res++;
1422         }
1423 
1424     return res;
1425 }
1426 
1427 void Loader::cancelRequests(DocLoader *dl)
1428 {
1429     QMutableHashIterator<KIO::Job *, Request *> lIt(m_requestsLoading);
1430     while (lIt.hasNext()) {
1431         lIt.next();
1432         if (lIt.value()->m_docLoader == dl) {
1433             //qCDebug(KHTML_LOG) << "canceling loading request for" << lIt.current()->object->url().string();
1434             KIO::Job *job = static_cast<KIO::Job *>(lIt.key());
1435             Cache::removeCacheEntry(lIt.value()->object);
1436             delete lIt.value();
1437             lIt.remove();
1438             job->kill();
1439         }
1440     }
1441 }
1442 
1443 KIO::Job *Loader::jobForRequest(const DOM::DOMString &url) const
1444 {
1445     QHashIterator<KIO::Job *, Request *> it(m_requestsLoading);
1446     while (it.hasNext()) {
1447         it.next();
1448         if (it.value()->object && it.value()->object->url() == url) {
1449             return static_cast<KIO::Job *>(it.key());
1450         }
1451     }
1452 
1453     return nullptr;
1454 }
1455 
1456 // ----------------------------------------------------------------------------
1457 
1458 QHash<QString, CachedObject *> *Cache::cache;
1459 QLinkedList<DocLoader *>    *Cache::docloader;
1460 QLinkedList<CachedObject *> *Cache::freeList;
1461 Loader *Cache::m_loader;
1462 
1463 int Cache::maxSize = DEFCACHESIZE;
1464 int Cache::totalSizeOfLRU;
1465 
1466 QPixmap *Cache::nullPixmap;
1467 QPixmap *Cache::brokenPixmap;
1468 QPixmap *Cache::blockedPixmap;
1469 
1470 void Cache::init()
1471 {
1472     if (!cache) {
1473         cache = new QHash<QString, CachedObject *>();
1474     }
1475 
1476     if (!docloader) {
1477         docloader = new QLinkedList<DocLoader *>;
1478     }
1479 
1480     if (!nullPixmap) {
1481         nullPixmap = new QPixmap;
1482     }
1483 
1484     if (!brokenPixmap) {
1485         brokenPixmap = new QPixmap(KHTMLGlobal::iconLoader()->loadIcon("image-missing", KIconLoader::Desktop, 16, KIconLoader::DisabledState));
1486     }
1487 
1488     if (!blockedPixmap) {
1489         blockedPixmap = new QPixmap();
1490         blockedPixmap->loadFromData(blocked_icon_data, blocked_icon_len);
1491     }
1492 
1493     if (!m_loader) {
1494         m_loader = new Loader();
1495     }
1496 
1497     if (!freeList) {
1498         freeList = new QLinkedList<CachedObject *>;
1499     }
1500 }
1501 
1502 void Cache::clear()
1503 {
1504     if (!cache) {
1505         return;
1506     }
1507 #ifdef CACHE_DEBUG
1508     qCDebug(KHTML_LOG) << "CLEAR!";
1509     statistics();
1510 #endif
1511 
1512 #ifndef NDEBUG
1513     bool crash = false;
1514     foreach (CachedObject *co, *cache) {
1515         if (!co->canDelete()) {
1516             qCDebug(KHTML_LOG) << " Object in cache still linked to";
1517             qCDebug(KHTML_LOG) << " -> URL :" << co->url();
1518             qCDebug(KHTML_LOG) << " -> #clients :" << co->count();
1519             crash = true;
1520 //         assert(co->canDelete());
1521         }
1522     }
1523     foreach (CachedObject *co, *freeList) {
1524         if (!co->canDelete()) {
1525             qCDebug(KHTML_LOG) << " Object in freelist still linked to";
1526             qCDebug(KHTML_LOG) << " -> URL :" << co->url();
1527             qCDebug(KHTML_LOG) << " -> #clients :" << co->count();
1528             crash = true;
1529             /*
1530             foreach (CachedObjectClient* cur, (*co->m_clients)))
1531             {
1532                 if (dynamic_cast<RenderObject*>(cur)) {
1533                     qCDebug(KHTML_LOG) << " --> RenderObject";
1534                 } else
1535                     qCDebug(KHTML_LOG) << " --> Something else";
1536             }*/
1537         }
1538 //         assert(freeList->current()->canDelete());
1539     }
1540     assert(!crash);
1541 #endif
1542     qDeleteAll(*cache);
1543     delete cache; cache = nullptr;
1544     delete nullPixmap; nullPixmap = nullptr;
1545     delete brokenPixmap; brokenPixmap = nullptr;
1546     delete blockedPixmap; blockedPixmap = nullptr;
1547     delete m_loader;  m_loader = nullptr;
1548     delete docloader; docloader = nullptr;
1549     qDeleteAll(*freeList);
1550     delete freeList; freeList = nullptr;
1551 }
1552 
1553 template<typename CachedObjectType, enum CachedObject::Type CachedType>
1554 CachedObjectType *Cache::requestObject(DocLoader *dl, const QUrl &kurl, const char *accept)
1555 {
1556     KIO::CacheControl cachePolicy = dl->cachePolicy();
1557 
1558     QString url = kurl.url();
1559     CachedObject *o = cache->value(url);
1560 
1561     if (o && o->type() != CachedType) {
1562         removeCacheEntry(o);
1563         o = nullptr;
1564     }
1565 
1566     if (o && dl->needReload(o, url)) {
1567         o = nullptr;
1568         assert(!cache->contains(url));
1569     }
1570 
1571     if (!o) {
1572 #ifdef CACHE_DEBUG
1573         qCDebug(KHTML_LOG) << "new:" << kurl.url();
1574 #endif
1575         CachedObjectType *cot = new CachedObjectType(dl, url, cachePolicy, accept);
1576         cache->insert(url, cot);
1577         if (cot->allowInLRUList()) {
1578             insertInLRUList(cot);
1579         }
1580         o = cot;
1581     }
1582 #ifdef CACHE_DEBUG
1583     else {
1584         qCDebug(KHTML_LOG) << "using pending/cached:" << kurl.url();
1585     }
1586 #endif
1587 
1588     dl->insertCachedObject(o);
1589 
1590     return static_cast<CachedObjectType *>(o);
1591 }
1592 
1593 void Cache::preloadStyleSheet(const QString &url, const QString &stylesheet_data)
1594 {
1595     if (cache->contains(url)) {
1596         removeCacheEntry(cache->value(url));
1597     }
1598 
1599     CachedCSSStyleSheet *stylesheet = new CachedCSSStyleSheet(url, stylesheet_data);
1600     cache->insert(url, stylesheet);
1601 }
1602 
1603 void Cache::preloadScript(const QString &url, const QString &script_data)
1604 {
1605     if (cache->contains(url)) {
1606         removeCacheEntry(cache->value(url));
1607     }
1608 
1609     CachedScript *script = new CachedScript(url, script_data);
1610     cache->insert(url, script);
1611 }
1612 
1613 void Cache::flush(bool force)
1614 {
1615     init();
1616 
1617     if (force || totalSizeOfLRU > maxSize + maxSize / 4) {
1618         for (int i = MAX_LRU_LISTS - 1; i >= 0 && totalSizeOfLRU > maxSize; --i)
1619             while (totalSizeOfLRU > maxSize && m_LRULists[i].m_tail) {
1620                 removeCacheEntry(m_LRULists[i].m_tail);
1621             }
1622 
1623 #ifdef CACHE_DEBUG
1624         statistics();
1625 #endif
1626     }
1627 
1628     QMutableLinkedListIterator<CachedObject *> it(*freeList);
1629     while (it.hasNext()) {
1630         CachedObject *p = it.next();
1631         if (p->canDelete()) {
1632             it.remove();
1633             delete p;
1634         }
1635     }
1636 }
1637 
1638 void Cache::setSize(int bytes)
1639 {
1640     maxSize = bytes;
1641     flush(true /* force */);
1642 }
1643 
1644 void Cache::statistics()
1645 {
1646     // this function is for debugging purposes only
1647     init();
1648 
1649     int size = 0;
1650     int msize = 0;
1651     int movie = 0;
1652     int images = 0;
1653     int scripts = 0;
1654     int stylesheets = 0;
1655     int sound = 0;
1656     int fonts = 0;
1657     foreach (CachedObject *o, *cache) {
1658         switch (o->type()) {
1659         case CachedObject::Image: {
1660             //CachedImage *im = static_cast<CachedImage *>(o);
1661             images++;
1662             /*if(im->m != 0)
1663             {
1664                 movie++;
1665                 msize += im->size();
1666             }*/
1667             break;
1668         }
1669         case CachedObject::CSSStyleSheet:
1670             stylesheets++;
1671             break;
1672         case CachedObject::Script:
1673             scripts++;
1674             break;
1675         case CachedObject::Sound:
1676             sound++;
1677             break;
1678         case CachedObject::Font:
1679             fonts++;
1680             break;
1681         }
1682         size += o->size();
1683     }
1684     size /= 1024;
1685 
1686     qCDebug(KHTML_LOG) << "------------------------- image cache statistics -------------------";
1687     qCDebug(KHTML_LOG) << "Number of items in cache:" << cache->count();
1688     qCDebug(KHTML_LOG) << "Number of cached images:" << images;
1689     qCDebug(KHTML_LOG) << "Number of cached movies:" << movie;
1690     qCDebug(KHTML_LOG) << "Number of cached scripts:" << scripts;
1691     qCDebug(KHTML_LOG) << "Number of cached stylesheets:" << stylesheets;
1692     qCDebug(KHTML_LOG) << "Number of cached sounds:" << sound;
1693     qCDebug(KHTML_LOG) << "Number of cached fonts:" << fonts;
1694     qCDebug(KHTML_LOG) << "pixmaps:   allocated space approx." << size << "kB";
1695     qCDebug(KHTML_LOG) << "movies :   allocated space approx." << msize / 1024 << "kB";
1696     qCDebug(KHTML_LOG) << "--------------------------------------------------------------------";
1697 }
1698 
1699 void Cache::removeCacheEntry(CachedObject *object)
1700 {
1701     QString key = object->url().string();
1702 
1703     cache->remove(key);
1704     removeFromLRUList(object);
1705 
1706     foreach (DocLoader *dl, *docloader) {
1707         dl->removeCachedObject(object);
1708     }
1709 
1710     if (!object->free()) {
1711         Cache::freeList->append(object);
1712         object->m_free = true;
1713     }
1714 }
1715 
1716 static inline int FastLog2(unsigned int j)
1717 {
1718     unsigned int log2;
1719     log2 = 0;
1720     if (j & (j - 1)) {
1721         log2 += 1;
1722     }
1723     if (j >> 16) {
1724         log2 += 16, j >>= 16;
1725     }
1726     if (j >> 8) {
1727         log2 += 8, j >>= 8;
1728     }
1729     if (j >> 4) {
1730         log2 += 4, j >>= 4;
1731     }
1732     if (j >> 2) {
1733         log2 += 2, j >>= 2;
1734     }
1735     if (j >> 1) {
1736         log2 += 1;
1737     }
1738 
1739     return log2;
1740 }
1741 
1742 static LRUList *getLRUListFor(CachedObject *o)
1743 {
1744     int accessCount = o->accessCount();
1745     int queueIndex;
1746     if (accessCount == 0) {
1747         queueIndex = 0;
1748     } else {
1749         int sizeLog = FastLog2(o->size());
1750         queueIndex = sizeLog / o->accessCount() - 1;
1751         if (queueIndex < 0) {
1752             queueIndex = 0;
1753         }
1754         if (queueIndex >= MAX_LRU_LISTS) {
1755             queueIndex = MAX_LRU_LISTS - 1;
1756         }
1757     }
1758     return &m_LRULists[queueIndex];
1759 }
1760 
1761 void Cache::removeFromLRUList(CachedObject *object)
1762 {
1763     CachedObject *next = object->m_next;
1764     CachedObject *prev = object->m_prev;
1765 
1766     LRUList *list = getLRUListFor(object);
1767     CachedObject *&head = getLRUListFor(object)->m_head;
1768 
1769     if (next == nullptr && prev == nullptr && head != object) {
1770         return;
1771     }
1772 
1773     object->m_next = nullptr;
1774     object->m_prev = nullptr;
1775 
1776     if (next) {
1777         next->m_prev = prev;
1778     } else if (list->m_tail == object) {
1779         list->m_tail = prev;
1780     }
1781 
1782     if (prev) {
1783         prev->m_next = next;
1784     } else if (head == object) {
1785         head = next;
1786     }
1787 
1788     totalSizeOfLRU -= object->size();
1789 }
1790 
1791 void Cache::insertInLRUList(CachedObject *object)
1792 {
1793     removeFromLRUList(object);
1794 
1795     assert(object);
1796     assert(!object->free());
1797     assert(object->canDelete());
1798     assert(object->allowInLRUList());
1799 
1800     LRUList *list = getLRUListFor(object);
1801 
1802     CachedObject *&head = list->m_head;
1803 
1804     object->m_next = head;
1805     if (head) {
1806         head->m_prev = object;
1807     }
1808     head = object;
1809 
1810     if (object->m_next == nullptr) {
1811         list->m_tail = object;
1812     }
1813 
1814     totalSizeOfLRU += object->size();
1815 }
1816 
1817 // --------------------------------------
1818 
1819 void CachedObjectClient::updatePixmap(const QRect &, CachedImage *) {}
1820 void CachedObjectClient::setStyleSheet(const DOM::DOMString &/*url*/, const DOM::DOMString &/*sheet*/, const DOM::DOMString &/*charset*/, const DOM::DOMString &/*mimetype*/) {}
1821 void CachedObjectClient::notifyFinished(CachedObject * /*finishedObj*/) {}
1822 void CachedObjectClient::error(int /*err*/, const QString &/*text*/) {}
1823 
1824 #undef CDEBUG
1825 
1826 #include "moc_loader.cpp"