File indexing completed on 2024-12-01 12:33:55
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"