1 /*
2 This file is part of the KDE libraries
3
4 Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de)
5 (C) 2001-2003 Dirk Mueller (mueller@kde.org)
6 (C) 2002 Waldo Bastian (bastian@kde.org)
7 (C) 2003 Apple Computer, Inc.
8 (C) 2006-2010 Germain Garand (germain@ebooksfrance.org)
9
10 This library is free software; you can redistribute it and/or
11 modify it under the terms of the GNU Library General Public
12 License as published by the Free Software Foundation; either
13 version 2 of the License, or (at your option) any later version.
14
15 This library is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 Library General Public License for more details.
19
20 You should have received a copy of the GNU Library General Public License
21 along with this library; see the file COPYING.LIB. If not, write to
22 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 Boston, MA 02110-1301, USA.
24
25 This class provides all functionality needed for loading images, style sheets and html
26 pages from the web. It has a memory cache for these objects.
27
28 // regarding the LRU:
29 // http://www.is.kyusan-u.ac.jp/~chengk/pub/papers/compsac00_A07-07.pdf
30 */
31
32 #undef CACHE_DEBUG
33 //#define CACHE_DEBUG
34
35 #ifdef CACHE_DEBUG
36 #define CDEBUG qCDebug(KHTML_LOG)
37 #else
38 #define CDEBUG qCDebug(KHTML_LOG)
39 #endif
40
41 #undef LOADER_DEBUG
42 //#define LOADER_DEBUG
43
44 //#define PRELOAD_DEBUG
45
46 #include "loader.h"
47 #include "seed.h"
48 #include "woff.h"
49 #include <imload/imagepainter.h>
50 #include <imload/imagemanager.h>
51 #include <KCompressionDevice>
52
53 #include <assert.h>
54
55 // default cache size
56 #define DEFCACHESIZE 2096*1024
57
58 //#include <qasyncio.h>
59 //#include <qasyncimageio.h>
60 #include <QApplication>
61 #include <QDesktopWidget>
62 #include <QPainter>
63 #include <QBitmap>
64 #include <QMovie>
65 #include <QWidget>
66 #include <QDebug>
67 #include <kurlauthorized.h>
68 #include <kio/job.h>
69 #include <kio/jobuidelegate.h>
70 #include <kio/jobclasses.h>
71 #include <kio/scheduler.h>
72 #include <kcharsets.h>
73 #include <kiconloader.h>
74 #include "khtml_debug.h"
75 #include <kjobwidgets.h>
76
77 #include <khtml_global.h>
78 #include <khtml_part.h>
79
80 #ifdef IMAGE_TITLES
81 #include <qfile.h>
82 #include <kfilemetainfo.h>
83 #include <qtemporaryfile.h>
84 #endif
85
86 #include "html/html_documentimpl.h"
87 #include "css/css_stylesheetimpl.h"
88 #include "xml/dom_docimpl.h"
89
90 #include "blocked_icon.cpp"
91
92 #include <QPaintEngine>
93
94 using namespace khtml;
95 using namespace DOM;
96 using namespace khtmlImLoad;
97
98 #define MAX_LRU_LISTS 20
99 struct LRUList {
100 CachedObject *m_head;
101 CachedObject *m_tail;
102
LRUListLRUList103 LRUList() : m_head(nullptr), m_tail(nullptr) {}
104 };
105
106 static LRUList m_LRULists[MAX_LRU_LISTS];
107 static LRUList *getLRUListFor(CachedObject *o);
108
~CachedObjectClient()109 CachedObjectClient::~CachedObjectClient()
110 {
111 }
112
~CachedObject()113 CachedObject::~CachedObject()
114 {
115 Cache::removeFromLRUList(this);
116 }
117
finish()118 void CachedObject::finish()
119 {
120 m_status = Cached;
121 }
122
isExpired() const123 bool CachedObject::isExpired() const
124 {
125 if (!m_expireDate.isValid()) {
126 return false;
127 }
128 QDateTime now = QDateTime::currentDateTime();
129 return (now >= m_expireDate);
130 }
131
setRequest(Request * _request)132 void CachedObject::setRequest(Request *_request)
133 {
134 if (_request && !m_request) {
135 m_status = Pending;
136 }
137
138 if (allowInLRUList()) {
139 Cache::removeFromLRUList(this);
140 }
141
142 m_request = _request;
143
144 if (allowInLRUList()) {
145 Cache::insertInLRUList(this);
146 }
147 }
148
ref(CachedObjectClient * c)149 void CachedObject::ref(CachedObjectClient *c)
150 {
151 if (m_preloadResult == PreloadNotReferenced) {
152 if (isLoaded()) {
153 m_preloadResult = PreloadReferencedWhileComplete;
154 } else if (m_prospectiveRequest) {
155 m_preloadResult = PreloadReferencedWhileLoading;
156 } else {
157 m_preloadResult = PreloadReferenced;
158 }
159 }
160 // unfortunately we can be ref'ed multiple times from the
161 // same object, because it uses e.g. the same foreground
162 // and the same background picture. so deal with it.
163 // Hence the use of a QHash rather than of a QSet.
164 m_clients.insertMulti(c, c);
165 Cache::removeFromLRUList(this);
166 m_accessCount++;
167 }
168
deref(CachedObjectClient * c)169 void CachedObject::deref(CachedObjectClient *c)
170 {
171 assert(c);
172 assert(m_clients.count());
173 assert(!canDelete());
174 assert(m_clients.contains(c));
175
176 Cache::flush();
177
178 m_clients.take(c);
179
180 if (allowInLRUList()) {
181 Cache::insertInLRUList(this);
182 }
183 }
184
setSize(int size)185 void CachedObject::setSize(int size)
186 {
187 bool sizeChanged;
188
189 if (!m_next && !m_prev && getLRUListFor(this)->m_head != this) {
190 sizeChanged = false;
191 } else {
192 sizeChanged = (size - m_size) != 0;
193 }
194
195 // The object must now be moved to a different queue,
196 // since its size has been changed.
197 if (sizeChanged && allowInLRUList()) {
198 Cache::removeFromLRUList(this);
199 }
200
201 m_size = size;
202
203 if (sizeChanged && allowInLRUList()) {
204 Cache::insertInLRUList(this);
205 }
206 }
207
codecForBuffer(const QString & charset,const QByteArray & buffer) const208 QTextCodec *CachedObject::codecForBuffer(const QString &charset, const QByteArray &buffer) const
209 {
210 // we don't use heuristicContentMatch here since it is a) far too slow and
211 // b) having too much functionality for our case.
212
213 uchar *d = (uchar *) buffer.data();
214 int s = buffer.size();
215
216 // BOM
217 if (s >= 3 &&
218 d[0] == 0xef && d[1] == 0xbb && d[2] == 0xbf) {
219 return QTextCodec::codecForMib(106); // UTF-8
220 }
221
222 if (s >= 2 && ((d[0] == 0xff && d[1] == 0xfe) ||
223 (d[0] == 0xfe && d[1] == 0xff))) {
224 return QTextCodec::codecForMib(1000); // UCS-2
225 }
226
227 // Link or @charset
228 if (!charset.isEmpty()) {
229 QTextCodec *c = KCharsets::charsets()->codecForName(charset);
230 if (c->mibEnum() == 11) {
231 // iso8859-8 (visually ordered)
232 c = QTextCodec::codecForName("iso8859-8-i");
233 }
234 return c;
235 }
236
237 // Default
238 return QTextCodec::codecForMib(4); // latin 1
239 }
240
241 // -------------------------------------------------------------------------------------------
242
CachedCSSStyleSheet(DocLoader * dl,const DOMString & url,KIO::CacheControl _cachePolicy,const char * accept)243 CachedCSSStyleSheet::CachedCSSStyleSheet(DocLoader *dl, const DOMString &url, KIO::CacheControl _cachePolicy,
244 const char *accept)
245 : CachedObject(url, CSSStyleSheet, _cachePolicy, 0)
246 {
247 // Set the type we want (probably css or xml)
248 QString ah = QLatin1String(accept);
249 if (!ah.isEmpty()) {
250 ah += ',';
251 }
252 ah += "*/*;q=0.1";
253 setAccept(ah);
254 m_hadError = false;
255 m_wasBlocked = false;
256 m_err = 0;
257 // load the file.
258 // Style sheets block rendering, they are therefore the higher priority item.
259 // Do |not| touch the priority value unless you conducted thorough tests and
260 // can back your choice with meaningful data, testing page load time and
261 // time to first paint.
262 Cache::loader()->load(dl, this, false, -8);
263 m_loading = true;
264 }
265
CachedCSSStyleSheet(const DOMString & url,const QString & stylesheet_data)266 CachedCSSStyleSheet::CachedCSSStyleSheet(const DOMString &url, const QString &stylesheet_data)
267 : CachedObject(url, CSSStyleSheet, KIO::CC_Verify, stylesheet_data.length())
268 {
269 m_loading = false;
270 m_status = Persistent;
271 m_sheet = DOMString(stylesheet_data);
272 }
273
isAcceptableCSSMimetype(const DOM::DOMString & mimetype)274 bool khtml::isAcceptableCSSMimetype(const DOM::DOMString &mimetype)
275 {
276 // matches Mozilla's check (cf. nsCSSLoader.cpp)
277 return mimetype.isEmpty() || mimetype == "text/css" || mimetype == "application/x-unknown-content-type";
278 }
279
ref(CachedObjectClient * c)280 void CachedCSSStyleSheet::ref(CachedObjectClient *c)
281 {
282 CachedObject::ref(c);
283
284 if (!m_loading) {
285 if (m_hadError) {
286 c->error(m_err, m_errText);
287 } else {
288 c->setStyleSheet(m_url, m_sheet, m_charset, m_mimetype);
289 }
290 }
291 }
292
data(QBuffer & buffer,bool eof)293 void CachedCSSStyleSheet::data(QBuffer &buffer, bool eof)
294 {
295 if (!eof) {
296 return;
297 }
298 buffer.close();
299 setSize(buffer.buffer().size());
300
301 m_charset = checkCharset(buffer.buffer());
302 QTextCodec *c = nullptr;
303 if (!m_charset.isEmpty()) {
304 c = KCharsets::charsets()->codecForName(m_charset);
305 if (c->mibEnum() == 11) {
306 c = QTextCodec::codecForName("iso8859-8-i");
307 }
308 } else {
309 c = codecForBuffer(m_charsetHint, buffer.buffer());
310 m_charset = c->name();
311 }
312 QString data = c->toUnicode(buffer.buffer().data(), m_size);
313 // workaround Qt bugs
314 m_sheet = static_cast<QChar>(data[0]) == QChar::ByteOrderMark ? DOMString(data.mid(1)) : DOMString(data);
315 m_loading = false;
316
317 checkNotify();
318 }
319
checkNotify()320 void CachedCSSStyleSheet::checkNotify()
321 {
322 if (m_loading || m_hadError) {
323 return;
324 }
325
326 CDEBUG << "finishedLoading" << m_url.string();
327
328 for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
329 it.next().value()->setStyleSheet(m_url, m_sheet, m_charset, m_mimetype);
330 }
331 }
332
error(int err,const char * text)333 void CachedCSSStyleSheet::error(int err, const char *text)
334 {
335 m_hadError = true;
336 m_err = err;
337 m_errText = text;
338 m_loading = false;
339
340 for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
341 it.next().value()->error(m_err, m_errText);
342 }
343 }
344
checkCharset(const QByteArray & buffer) const345 QString CachedCSSStyleSheet::checkCharset(const QByteArray &buffer) const
346 {
347 int s = buffer.size();
348 if (s <= 12) {
349 return m_charset;
350 }
351
352 // @charset has to be first or directly after BOM.
353 // CSS 2.1 says @charset should win over BOM, but since more browsers support BOM
354 // than @charset, we default to that.
355 const char *d = buffer.data();
356 if (strncmp(d, "@charset \"", 10) == 0) {
357 // the string until "; is the charset name
358 const char *p = strchr(d + 10, '"');
359 if (p == nullptr) {
360 return m_charset;
361 }
362 QString charset = QString::fromLatin1(d + 10, p - (d + 10));
363 return charset;
364 }
365 return m_charset;
366 }
367
368 // -------------------------------------------------------------------------------------------
369
CachedScript(DocLoader * dl,const DOMString & url,KIO::CacheControl _cachePolicy,const char *)370 CachedScript::CachedScript(DocLoader *dl, const DOMString &url, KIO::CacheControl _cachePolicy, const char *)
371 : CachedObject(url, Script, _cachePolicy, 0)
372 {
373 // It's javascript we want.
374 // But some websites think their scripts are <some wrong mimetype here>
375 // and refuse to serve them if we only accept application/x-javascript.
376 setAccept(QLatin1String("*/*"));
377 // load the file.
378 // Scripts block document parsing. They are therefore second in our list of most
379 // desired resources.
380 Cache::loader()->load(dl, this, false/*incremental*/, -6);
381 m_loading = true;
382 m_hadError = false;
383 }
384
CachedScript(const DOMString & url,const QString & script_data)385 CachedScript::CachedScript(const DOMString &url, const QString &script_data)
386 : CachedObject(url, Script, KIO::CC_Verify, script_data.length())
387 {
388 m_hadError = false;
389 m_loading = false;
390 m_status = Persistent;
391 m_script = DOMString(script_data);
392 }
393
ref(CachedObjectClient * c)394 void CachedScript::ref(CachedObjectClient *c)
395 {
396 CachedObject::ref(c);
397
398 if (!m_loading) {
399 c->notifyFinished(this);
400 }
401 }
402
data(QBuffer & buffer,bool eof)403 void CachedScript::data(QBuffer &buffer, bool eof)
404 {
405 if (!eof) {
406 return;
407 }
408 buffer.close();
409 setSize(buffer.buffer().size());
410
411 QTextCodec *c = codecForBuffer(m_charset, buffer.buffer());
412 QString data = c->toUnicode(buffer.buffer().data(), m_size);
413 m_script = static_cast<QChar>(data[0]) == QChar::ByteOrderMark ? DOMString(data.mid(1)) : DOMString(data);
414 m_loading = false;
415 checkNotify();
416 }
417
checkNotify()418 void CachedScript::checkNotify()
419 {
420 if (m_loading) {
421 return;
422 }
423
424 for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
425 it.next().value()->notifyFinished(this);
426 }
427 }
428
error(int,const char *)429 void CachedScript::error(int /*err*/, const char * /*text*/)
430 {
431 m_hadError = true;
432 m_loading = false;
433 checkNotify();
434 }
435
436 // ------------------------------------------------------------------------------------------
437
buildAcceptHeader()438 static QString buildAcceptHeader()
439 {
440 return "image/png, image/jpeg, video/x-mng, image/jp2, image/gif;q=0.5,*/*;q=0.1";
441 }
442
443 // -------------------------------------------------------------------------------------
444
CachedImage(DocLoader * dl,const DOMString & url,KIO::CacheControl _cachePolicy,const char *)445 CachedImage::CachedImage(DocLoader *dl, const DOMString &url, KIO::CacheControl _cachePolicy, const char *)
446 : QObject(), CachedObject(url, Image, _cachePolicy, 0)
447 {
448 i = new khtmlImLoad::Image(this);
449 //p = 0;
450 //pixPart = 0;
451 bg = nullptr;
452 scaled = nullptr;
453 bgColor = qRgba(0, 0, 0, 0);
454 m_status = Unknown;
455 setAccept(buildAcceptHeader());
456 i->setShowAnimations(dl->showAnimations());
457 m_loading = true;
458
459 if (KHTMLGlobal::defaultHTMLSettings()->isAdFiltered(url.string())) {
460 m_wasBlocked = true;
461 CachedObject::finish();
462 }
463 }
464
~CachedImage()465 CachedImage::~CachedImage()
466 {
467 clear();
468 delete i;
469 }
470
ref(CachedObjectClient * c)471 void CachedImage::ref(CachedObjectClient *c)
472 {
473 CachedObject::ref(c);
474
475 #ifdef LOADER_DEBUG
476 qCDebug(KHTML_LOG) << "image" << this << "ref'd by client" << c;
477 #endif
478
479 // for mouseovers, dynamic changes
480 //### having both makes no sense
481 if (m_status >= Persistent && !pixmap_size().isNull()) {
482 #ifdef LOADER_DEBUG
483 qCDebug(KHTML_LOG) << "Notifying finished size:" << i->size().width() << "," << i->size().height();
484 #endif
485 c->updatePixmap(QRect(QPoint(0, 0), pixmap_size()), this);
486 c->notifyFinished(this);
487 }
488 }
489
deref(CachedObjectClient * c)490 void CachedImage::deref(CachedObjectClient *c)
491 {
492 CachedObject::deref(c);
493 /* if(m && m_clients.isEmpty() && m->running())
494 m->pause();*/
495 }
496
497 #define BGMINWIDTH 32
498 #define BGMINHEIGHT 32
499
tiled_pixmap(const QColor & newc,int xWidth,int xHeight)500 QPixmap CachedImage::tiled_pixmap(const QColor &newc, int xWidth, int xHeight)
501 {
502
503 // no error indication for background images
504 if (m_hadError || m_wasBlocked) {
505 return *Cache::nullPixmap;
506 }
507
508 // If we don't have size yet, nothing to draw yet
509 if (i->size().width() == 0 || i->size().height() == 0) {
510 return *Cache::nullPixmap;
511 }
512
513 #ifdef __GNUC__
514 #warning "Needs some additional performance work"
515 #endif
516
517 static QRgb bgTransparent = qRgba(0, 0, 0, 0);
518
519 QSize s(pixmap_size());
520 int w = xWidth;
521 int h = xHeight;
522
523 if (w == -1) {
524 xWidth = w = s.width();
525 }
526 if (h == -1) {
527 xHeight = h = s.height();
528 }
529
530 if (((bgColor != bgTransparent) && (bgColor != newc.rgba())) ||
531 (bgSize != QSize(xWidth, xHeight))) {
532 delete bg; bg = nullptr;
533 }
534
535 if (bg) {
536 return *bg;
537 }
538
539 const QPixmap *src; //source for pretiling, if any
540
541 const QPixmap &r = pixmap(); //this is expensive
542 if (r.isNull()) {
543 return r;
544 }
545
546 //See whether we should scale
547 if (xWidth != s.width() || xHeight != s.height()) {
548 src = scaled_pixmap(xWidth, xHeight);
549 } else {
550 src = &r;
551 }
552
553 bgSize = QSize(xWidth, xHeight);
554
555 //See whether we can - and should - pre-blend
556 // ### this needs serious investigations. Not likely to help with transparent bgColor,
557 // won't work with CSS3 multiple backgrounds. Does it help at all in Qt4? (ref: #114938)
558 if (newc.isValid() && (r.hasAlpha() || r.hasAlphaChannel())) {
559 bg = new QPixmap(xWidth, xHeight);
560 bg->fill(newc);
561 QPainter p(bg);
562 p.drawPixmap(0, 0, *src);
563 bgColor = newc.rgba();
564 src = bg;
565 } else {
566 bgColor = bgTransparent;
567 }
568
569 //See whether to pre-tile.
570 if (w * h < 8192) {
571 if (r.width() < BGMINWIDTH) {
572 w = ((BGMINWIDTH - 1) / xWidth + 1) * xWidth;
573 }
574 if (r.height() < BGMINHEIGHT) {
575 h = ((BGMINHEIGHT - 1) / xHeight + 1) * xHeight;
576 }
577 }
578
579 if (w != xWidth || h != xHeight) {
580 // qCDebug(KHTML_LOG) << "pre-tiling " << s.width() << "," << s.height() << " to " << w << "," << h;
581 QPixmap *oldbg = bg;
582 bg = new QPixmap(w, h);
583 if (src->hasAlpha() || src->hasAlphaChannel()) {
584 if (newc.isValid() && (bgColor != bgTransparent)) {
585 bg->fill(bgColor);
586 } else {
587 bg->fill(Qt::transparent);
588 }
589 }
590
591 QPainter p(bg);
592 p.drawTiledPixmap(0, 0, w, h, *src);
593 p.end();
594
595 if (src == oldbg) {
596 delete oldbg;
597 }
598 } else if (src && !bg) {
599 // we were asked for the entire pixmap. Cache it.
600 // ### goes against imload stuff, but it's far too expensive
601 // to recreate the full pixmap each time it's requested as
602 // we don't know what portion of it will be used eventually
603 // (by e.g. paintBackgroundExtended). It could be a few pixels of
604 // a huge image. See #140248/#1 for an obvious example.
605 // Imload probably needs to handle all painting in paintBackgroundExtended.
606 bg = new QPixmap(*src);
607 }
608
609 if (bg) {
610 return *bg;
611 }
612
613 return *src;
614 }
615
scaled_pixmap(int xWidth,int xHeight)616 QPixmap *CachedImage::scaled_pixmap(int xWidth, int xHeight)
617 {
618 // no error indication for background images
619 if (m_hadError || m_wasBlocked) {
620 return Cache::nullPixmap;
621 }
622
623 // If we don't have size yet, nothing to draw yet
624 if (i->size().width() == 0 || i->size().height() == 0) {
625 return Cache::nullPixmap;
626 }
627
628 if (scaled) {
629 if (scaled->width() == xWidth && scaled->height() == xHeight) {
630 return scaled;
631 }
632 delete scaled;
633 }
634
635 //### this is quite awful performance-wise. It should avoid
636 // alpha if not needed, and go to pixmap, etc.
637 QImage im(xWidth, xHeight, QImage::Format_ARGB32_Premultiplied);
638
639 QPainter paint(&im);
640 paint.setCompositionMode(QPainter::CompositionMode_Source);
641 ImagePainter pi(i, QSize(xWidth, xHeight));
642 pi.paint(0, 0, &paint);
643 paint.end();
644
645 scaled = new QPixmap(QPixmap::fromImage(im));
646
647 return scaled;
648 }
649
pixmap() const650 QPixmap CachedImage::pixmap() const
651 {
652 if (m_hadError) {
653 return *Cache::brokenPixmap;
654 }
655
656 if (m_wasBlocked) {
657 return *Cache::blockedPixmap;
658 }
659
660 int w = i->size().width();
661 int h = i->size().height();
662
663 if (i->hasAlpha() && QApplication::desktop()->paintEngine() &&
664 !QApplication::desktop()->paintEngine()->hasFeature(QPaintEngine::PorterDuff)) {
665 QImage im(w, h, QImage::Format_ARGB32_Premultiplied);
666 QPainter paint(&im);
667 paint.setCompositionMode(QPainter::CompositionMode_Source);
668 ImagePainter pi(i);
669 pi.paint(0, 0, &paint);
670 paint.end();
671 return QPixmap::fromImage(im, Qt::NoOpaqueDetection);
672 } else {
673 QPixmap pm(w, h);
674 if (i->hasAlpha()) {
675 pm.fill(Qt::transparent);
676 }
677 QPainter paint(&pm);
678 paint.setCompositionMode(QPainter::CompositionMode_Source);
679 ImagePainter pi(i);
680 pi.paint(0, 0, &paint);
681 paint.end();
682 return pm;
683 }
684 }
685
pixmap_size() const686 QSize CachedImage::pixmap_size() const
687 {
688 if (m_wasBlocked) {
689 return Cache::blockedPixmap->size();
690 }
691 if (m_hadError) {
692 return Cache::brokenPixmap->size();
693 }
694 if (i) {
695 return i->size();
696 }
697 return QSize();
698 }
699
imageHasGeometry(khtmlImLoad::Image *,int width,int height)700 void CachedImage::imageHasGeometry(khtmlImLoad::Image * /*img*/, int width, int height)
701 {
702 #ifdef LOADER_DEBUG
703 qCDebug(KHTML_LOG) << this << "got geometry" << width << "x" << height;
704 #endif
705
706 do_notify(QRect(0, 0, width, height));
707 }
708
imageChange(khtmlImLoad::Image *,QRect region)709 void CachedImage::imageChange(khtmlImLoad::Image * /*img*/, QRect region)
710 {
711 #ifdef LOADER_DEBUG
712 qCDebug(KHTML_LOG) << "Image" << this << "change" <<
713 region.x() << "," << region.y() << ":" << region.width() << "x" << region.height();
714 #endif
715
716 //### this is overly conservative -- I guess we need to also specify reason,
717 //e.g. repaint vs. changed !!!
718 delete bg;
719 bg = nullptr;
720
721 do_notify(region);
722 }
723
doNotifyFinished()724 void CachedImage::doNotifyFinished()
725 {
726 for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
727 it.next().value()->notifyFinished(this);
728 }
729 }
730
imageError(khtmlImLoad::Image *)731 void CachedImage::imageError(khtmlImLoad::Image * /*img*/)
732 {
733 error(0, nullptr);
734 }
735
imageDone(khtmlImLoad::Image *)736 void CachedImage::imageDone(khtmlImLoad::Image * /*img*/)
737 {
738 #ifdef LOADER_DEBUG
739 qCDebug(KHTML_LOG) << "Image is done:" << this;
740 #endif
741 m_status = Persistent;
742 m_loading = false;
743 doNotifyFinished();
744 }
745
746 // QRect CachedImage::valid_rect() const
747 // {
748 // if (m_wasBlocked) return Cache::blockedPixmap->rect();
749 // return (m_hadError ? Cache::brokenPixmap->rect() : m ? m->frameRect() : ( p ? p->rect() : QRect()) );
750 // }
751
do_notify(const QRect & r)752 void CachedImage::do_notify(const QRect &r)
753 {
754 for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
755 #ifdef LOADER_DEBUG
756 qCDebug(KHTML_LOG) << "image" << this << "notify of geom client" << it.peekNext().value();
757 #endif
758 it.next().value()->updatePixmap(r, this);
759 }
760 }
761
setShowAnimations(KHTMLSettings::KAnimationAdvice showAnimations)762 void CachedImage::setShowAnimations(KHTMLSettings::KAnimationAdvice showAnimations)
763 {
764 if (i) {
765 i->setShowAnimations(showAnimations);
766 }
767 }
768
clear()769 void CachedImage::clear()
770 {
771 delete i; i = new khtmlImLoad::Image(this);
772 delete scaled; scaled = nullptr;
773 bgColor = qRgba(0, 0, 0, 0xff);
774 delete bg; bg = nullptr;
775 bgSize = QSize(-1, -1);
776
777 setSize(0);
778 }
779
data(QBuffer & _buffer,bool eof)780 void CachedImage::data(QBuffer &_buffer, bool eof)
781 {
782 #ifdef LOADER_DEBUG
783 qCDebug(KHTML_LOG) << this << "buffersize =" << _buffer.buffer().size() << ", eof =" << eof << ", pos :" << _buffer.pos();
784 #endif
785 i->processData((uchar *)_buffer.data().data(), _buffer.pos());
786
787 _buffer.close();
788
789 if (eof) {
790 i->processEOF();
791 }
792 }
793
finish()794 void CachedImage::finish()
795 {
796 CachedObject::finish();
797 m_loading = false;
798 QSize s = pixmap_size();
799 setSize(s.width() * s.height() * 2);
800 }
801
error(int,const char *)802 void CachedImage::error(int /*err*/, const char * /*text*/)
803 {
804 clear();
805 m_hadError = true;
806 m_loading = false;
807 do_notify(QRect(0, 0, 16, 16));
808 for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
809 it.next().value()->notifyFinished(this);
810 }
811 }
812
813 // -------------------------------------------------------------------------------------------
814
CachedSound(DocLoader * dl,const DOMString & url,KIO::CacheControl _cachePolicy,const char *)815 CachedSound::CachedSound(DocLoader *dl, const DOMString &url, KIO::CacheControl _cachePolicy, const char *)
816 : CachedObject(url, Sound, _cachePolicy, 0)
817 {
818 setAccept(QLatin1String("*/*")); // should be whatever phonon would accept...
819 Cache::loader()->load(dl, this, false/*incremental*/, 2);
820 m_loading = true;
821 }
822
ref(CachedObjectClient * c)823 void CachedSound::ref(CachedObjectClient *c)
824 {
825 CachedObject::ref(c);
826
827 if (!m_loading) {
828 c->notifyFinished(this);
829 }
830 }
831
data(QBuffer & buffer,bool eof)832 void CachedSound::data(QBuffer &buffer, bool eof)
833 {
834 if (!eof) {
835 return;
836 }
837 buffer.close();
838 setSize(buffer.buffer().size());
839
840 m_sound = buffer.buffer();
841 m_loading = false;
842 checkNotify();
843 }
844
checkNotify()845 void CachedSound::checkNotify()
846 {
847 if (m_loading) {
848 return;
849 }
850
851 for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
852 it.next().value()->notifyFinished(this);
853 }
854 }
855
error(int,const char *)856 void CachedSound::error(int /*err*/, const char * /*text*/)
857 {
858 m_loading = false;
859 checkNotify();
860 }
861
862 // -------------------------------------------------------------------------------------------
863
CachedFont(DocLoader * dl,const DOMString & url,KIO::CacheControl _cachePolicy,const char *)864 CachedFont::CachedFont(DocLoader *dl, const DOMString &url, KIO::CacheControl _cachePolicy, const char *)
865 : CachedObject(url, Font, _cachePolicy, 0)
866 {
867 setAccept(QLatin1String("*/*"));
868 // Fonts are desired early because their absence will lead to a page being rendered
869 // with a default replacement, then the text being re-rendered with the new font when it arrives.
870 // This can be fairly disturbing for the reader - more than missing images for instance.
871 Cache::loader()->load(dl, this, false /*incremental*/, -4);
872 m_loading = true;
873 }
874
ref(CachedObjectClient * c)875 void CachedFont::ref(CachedObjectClient *c)
876 {
877 CachedObject::ref(c);
878
879 if (!m_loading) {
880 c->notifyFinished(this);
881 }
882 }
883
data(QBuffer & buffer,bool eof)884 void CachedFont::data(QBuffer &buffer, bool eof)
885 {
886 if (!eof) {
887 return;
888 }
889 buffer.close();
890 m_font = buffer.buffer();
891
892 // some fonts are compressed.
893 {
894 KCompressionDevice::CompressionType compressionType = KCompressionDevice::compressionTypeForMimeType(mimetype());
895 QScopedPointer<KCompressionDevice> dev(new KCompressionDevice(&buffer, false /*autoDeleteInDevice*/, compressionType));
896 if (dev && dev->open(QIODevice::ReadOnly)) {
897 m_font = dev->readAll();
898 }
899 }
900
901 // handle decoding of WOFF fonts
902 int woffStatus = eWOFF_ok;
903 if (int need = WOFF::getDecodedSize(m_font.constData(), m_font.size(), &woffStatus)) {
904 // qCDebug(KHTML_LOG) << "***************************** Got WOFF FoNT";
905 m_hadError = true;
906 do {
907 if (WOFF_FAILURE(woffStatus)) {
908 break;
909 }
910 QByteArray wbuffer;
911 wbuffer.resize(need);
912 int len;
913 woffStatus = eWOFF_ok;
914 WOFF::decodeToBuffer(m_font.constData(), m_font.size(), wbuffer.data(), wbuffer.size(), &len, &woffStatus);
915 if (WOFF_FAILURE(woffStatus)) {
916 break;
917 }
918 wbuffer.resize(len);
919 m_font = wbuffer;
920 m_hadError = false;
921 } while (false);
922 } else if (m_font.isEmpty()) {
923 m_hadError = true;
924 } else {
925 // qCDebug(KHTML_LOG) << "******** #################### ********************* NON WOFF font";
926 }
927 setSize(m_font.size());
928
929 m_loading = false;
930 checkNotify();
931 }
932
checkNotify()933 void CachedFont::checkNotify()
934 {
935 if (m_loading) {
936 return;
937 }
938
939 for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
940 it.next().value()->notifyFinished(this);
941 }
942 }
943
error(int,const char *)944 void CachedFont::error(int /*err*/, const char * /*text*/)
945 {
946 m_loading = false;
947 m_hadError = true;
948 checkNotify();
949 }
950
951 // ------------------------------------------------------------------------------------------
952
Request(DocLoader * dl,CachedObject * _object,bool _incremental,int _priority)953 Request::Request(DocLoader *dl, CachedObject *_object, bool _incremental, int _priority)
954 {
955 object = _object;
956 object->setRequest(this);
957 incremental = _incremental;
958 priority = _priority;
959 m_docLoader = dl;
960 }
961
~Request()962 Request::~Request()
963 {
964 object->setRequest(nullptr);
965 }
966
967 // ------------------------------------------------------------------------------------------
968
DocLoader(KHTMLPart * part,DocumentImpl * doc)969 DocLoader::DocLoader(KHTMLPart *part, DocumentImpl *doc)
970 {
971 m_cachePolicy = KIO::CC_Verify;
972 m_creationDate = QDateTime::currentDateTime();
973 m_bautoloadImages = true;
974 m_showAnimations = KHTMLSettings::KAnimationEnabled;
975 m_part = part;
976 m_doc = doc;
977
978 Cache::docloader->append(this);
979 }
980
~DocLoader()981 DocLoader::~DocLoader()
982 {
983 clearPreloads();
984 Cache::loader()->cancelRequests(this);
985 Cache::docloader->removeAll(this);
986 }
987
setCacheCreationDate(const QDateTime & _creationDate)988 void DocLoader::setCacheCreationDate(const QDateTime &_creationDate)
989 {
990 if (_creationDate.isValid()) {
991 m_creationDate = _creationDate;
992 } else {
993 m_creationDate = QDateTime::currentDateTime();
994 }
995 }
996
setExpireDate(const QDateTime & _expireDate)997 void DocLoader::setExpireDate(const QDateTime &_expireDate)
998 {
999 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
setRelativeExpireDate(qint64 seconds)1006 void DocLoader::setRelativeExpireDate(qint64 seconds)
1007 {
1008 m_expireDate = m_creationDate.addSecs(seconds);
1009 }
1010
insertCachedObject(CachedObject * o) const1011 void DocLoader::insertCachedObject(CachedObject *o) const
1012 {
1013 m_docObjects.insert(o);
1014 }
1015
needReload(CachedObject * existing,const QString & fullURL)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
registerPreload(CachedObject * resource)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
clearPreloads()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
printPreloadStats()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
securityCheckUrl(const QUrl & fullURL,KHTMLPart * part,DOM::DocumentImpl * doc,bool doRedirectCheck,bool isImg)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
willLoadMediaElement(const DOM::DOMString & url)1146 bool DocLoader::willLoadMediaElement(const DOM::DOMString &url)
1147 {
1148 DOCLOADER_SECCHECK_BOOL(true);
1149
1150 return true;
1151 }
1152
requestImage(const DOM::DOMString & url)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
requestStyleSheet(const DOM::DOMString & url,const QString & charset,const char * accept,bool userSheet)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
requestScript(const DOM::DOMString & url,const QString & charset)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
requestSound(const DOM::DOMString & url)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
requestFont(const DOM::DOMString & url)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
setAutoloadImages(bool enable)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
setShowAnimations(KHTMLSettings::KAnimationAdvice showAnimations)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
Loader()1255 Loader::Loader() : QObject()
1256 {
1257 m_supportedImageTypes = khtmlImLoad::ImageManager::loaderDatabase()->supportedMimeTypes();
1258 }
1259
~Loader()1260 Loader::~Loader()
1261 {
1262 qDeleteAll(m_requestsLoading);
1263 }
1264
load(DocLoader * dl,CachedObject * object,bool incremental,int priority)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
scheduleRequest(Request * req)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
slotMimetype(KIO::Job * j,const QString & s)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
slotFinished(KJob * job)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
slotData(KIO::Job * job,const QByteArray & data)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
numRequests(DocLoader * dl) const1416 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
cancelRequests(DocLoader * dl)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
jobForRequest(const DOM::DOMString & url) const1443 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
init()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
clear()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
preloadStyleSheet(const QString & url,const QString & stylesheet_data)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
preloadScript(const QString & url,const QString & script_data)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
flush(bool force)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
setSize(int bytes)1638 void Cache::setSize(int bytes)
1639 {
1640 maxSize = bytes;
1641 flush(true /* force */);
1642 }
1643
statistics()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
removeCacheEntry(CachedObject * object)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
FastLog2(unsigned int j)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
getLRUListFor(CachedObject * o)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
removeFromLRUList(CachedObject * object)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
insertInLRUList(CachedObject * object)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
updatePixmap(const QRect &,CachedImage *)1819 void CachedObjectClient::updatePixmap(const QRect &, CachedImage *) {}
setStyleSheet(const DOM::DOMString &,const DOM::DOMString &,const DOM::DOMString &,const DOM::DOMString &)1820 void CachedObjectClient::setStyleSheet(const DOM::DOMString &/*url*/, const DOM::DOMString &/*sheet*/, const DOM::DOMString &/*charset*/, const DOM::DOMString &/*mimetype*/) {}
notifyFinished(CachedObject *)1821 void CachedObjectClient::notifyFinished(CachedObject * /*finishedObj*/) {}
error(int,const QString &)1822 void CachedObjectClient::error(int /*err*/, const QString &/*text*/) {}
1823
1824 #undef CDEBUG
1825
1826