KHtml

loader.cpp
1 /*
2  This file is part of the KDE libraries
3 
4  Copyright (C) 1998 Lars Knoll ([email protected])
5  (C) 2001-2003 Dirk Mueller ([email protected])
6  (C) 2002 Waldo Bastian ([email protected])
7  (C) 2003 Apple Computer, Inc.
8  (C) 2006-2010 Germain Garand ([email protected])
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 <kfilterdev.h>
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 
103  LRUList() : m_head(nullptr), m_tail(nullptr) {}
104 };
105 
106 static LRUList m_LRULists[MAX_LRU_LISTS];
107 static LRUList *getLRUListFor(CachedObject *o);
108 
109 CachedObjectClient::~CachedObjectClient()
110 {
111 }
112 
113 CachedObject::~CachedObject()
114 {
115  Cache::removeFromLRUList(this);
116 }
117 
118 void CachedObject::finish()
119 {
120  m_status = Cached;
121 }
122 
123 bool CachedObject::isExpired() const
124 {
125  if (!m_expireDate.isValid()) {
126  return false;
127  }
129  return (now >= m_expireDate);
130 }
131 
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 
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 
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 
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 
208 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()) {
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 
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 
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 
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 
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 
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 
320 void CachedCSSStyleSheet::checkNotify()
321 {
322  if (m_loading || m_hadError) {
323  return;
324  }
325 
326  CDEBUG << "finishedLoading" << m_url.string();
327 
329  it.next().value()->setStyleSheet(m_url, m_sheet, m_charset, m_mimetype);
330  }
331 }
332 
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 
341  it.next().value()->error(m_err, m_errText);
342  }
343 }
344 
345 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 
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 
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 
394 void CachedScript::ref(CachedObjectClient *c)
395 {
396  CachedObject::ref(c);
397 
398  if (!m_loading) {
399  c->notifyFinished(this);
400  }
401 }
402 
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 
418 void CachedScript::checkNotify()
419 {
420  if (m_loading) {
421  return;
422  }
423 
425  it.next().value()->notifyFinished(this);
426  }
427 }
428 
429 void CachedScript::error(int /*err*/, const char * /*text*/)
430 {
431  m_hadError = true;
432  m_loading = false;
433  checkNotify();
434 }
435 
436 // ------------------------------------------------------------------------------------------
437 
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 
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 
465 CachedImage::~CachedImage()
466 {
467  clear();
468  delete i;
469 }
470 
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 
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 
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 
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 
650 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() &&
666  QPainter paint(&im);
667  paint.setCompositionMode(QPainter::CompositionMode_Source);
668  ImagePainter pi(i);
669  pi.paint(0, 0, &paint);
670  paint.end();
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 
686 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 
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 
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 
724 void CachedImage::doNotifyFinished()
725 {
727  it.next().value()->notifyFinished(this);
728  }
729 }
730 
731 void CachedImage::imageError(khtmlImLoad::Image * /*img*/)
732 {
733  error(0, nullptr);
734 }
735 
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 
752 void CachedImage::do_notify(const QRect &r)
753 {
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 
762 void CachedImage::setShowAnimations(KHTMLSettings::KAnimationAdvice showAnimations)
763 {
764  if (i) {
765  i->setShowAnimations(showAnimations);
766  }
767 }
768 
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 
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 
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 
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));
809  it.next().value()->notifyFinished(this);
810  }
811 }
812 
813 // -------------------------------------------------------------------------------------------
814 
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 
823 void CachedSound::ref(CachedObjectClient *c)
824 {
825  CachedObject::ref(c);
826 
827  if (!m_loading) {
828  c->notifyFinished(this);
829  }
830 }
831 
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 
845 void CachedSound::checkNotify()
846 {
847  if (m_loading) {
848  return;
849  }
850 
852  it.next().value()->notifyFinished(this);
853  }
854 }
855 
856 void CachedSound::error(int /*err*/, const char * /*text*/)
857 {
858  m_loading = false;
859  checkNotify();
860 }
861 
862 // -------------------------------------------------------------------------------------------
863 
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 
875 void CachedFont::ref(CachedObjectClient *c)
876 {
877  CachedObject::ref(c);
878 
879  if (!m_loading) {
880  c->notifyFinished(this);
881  }
882 }
883 
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 = KFilterDev::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 
933 void CachedFont::checkNotify()
934 {
935  if (m_loading) {
936  return;
937  }
938 
940  it.next().value()->notifyFinished(this);
941  }
942 }
943 
944 void CachedFont::error(int /*err*/, const char * /*text*/)
945 {
946  m_loading = false;
947  m_hadError = true;
948  checkNotify();
949 }
950 
951 // ------------------------------------------------------------------------------------------
952 
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 
962 Request::~Request()
963 {
964  object->setRequest(nullptr);
965 }
966 
967 // ------------------------------------------------------------------------------------------
968 
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 
981 DocLoader::~DocLoader()
982 {
983  clearPreloads();
984  Cache::loader()->cancelRequests(this);
985  Cache::docloader->removeAll(this);
986 }
987 
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 
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 
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 
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 
QString url(QUrl::FormattingOptions options) const const
KJOBWIDGETS_EXPORT void setWindow(KJob *job, QWidget *widget)
QTextCodec * codecForName(const QString &name) const
QSize size() const const
An image represents a static picture or an animation, that may be incrementally loaded.
Definition: image.h:51
virtual qint64 size() const const override
int width() const const
int width() const const
Format_ARGB32_Premultiplied
const QUrl & url() const
a cached sound
Definition: loader.h:465
static void setJobPriority(SimpleJob *job, int priority)
bool flush()
bool onlyLocalReferences() const
Returns whether only file:/ or data:/ references are allowed to be loaded ( default false )...
bool hasNext() const const
bool hasNext() const const
This file is part of the HTML rendering engine for KDE.
This class is khtml&#39;s main class.
Definition: khtml_part.h:208
QPixmap fromImage(const QImage &image, Qt::ImageConversionFlags flags)
virtual QWidget * widget()
int height() const const
int x() const const
int y() const const
QString & remove(int position, int n)
An image painter let&#39;s one paint an image at the given size.
Definition: imagepainter.h:40
QString queryMetaData(const QString &key)
bool hasAlphaChannel() const const
QByteArray & buffer()
a cached image
Definition: loader.h:359
a cached style sheet.
Definition: loader.h:282
bool isErrorPage() const
QDateTime fromTime_t(uint seconds)
const QList< QKeySequence > & reload()
int width() const const
void resize(int size)
static CompressionType compressionTypeForMimeType(const QString &mimetype)
The CSSStyleSheet interface is a concrete interface used to represent a CSS style sheet i...
NoOpaqueDetection
a cached font
Definition: loader.h:493
QWidget * topLevelWidget() const const
bool forcePermitLocalImages() const
If true, local image files will be loaded even when forbidden by the Kiosk/KAuthorized policies ( def...
bool hasNext() const const
bool isEmpty() const const
const char * constData() const const
QUrl toplevelURL()
Returns the toplevel (origin) URL of this document, even if this part is a frame or an iframe...
void error(QWidget *parent, const QString &text, const QString &caption=QString(), Options options=Notify)
CacheControl
This class implements the basic string we use in the DOM.
Definition: dom_string.h:44
virtual bool open(QIODevice::OpenMode flags) override
bool isOpen() const const
int result() const const
QString scheme() const const
virtual qint64 pos() const const override
virtual void accept()
const QByteArray & data() const const
KIOCORE_EXPORT TransferJob * get(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
CompositionMode_Source
bool isNull() const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
int height() const const
static KCharsets * charsets()
virtual int mibEnum() const const =0
QCA_EXPORT void init()
virtual QString fileName() const const override
bool isValid() const const
long toLong(bool *ok, int base) const const
This library provides a full-featured HTML parser and widget.
const QList< QKeySequence > & end()
virtual void close() override
bool isValid() const const
QDateTime currentDateTime()
QByteArray toLatin1() const const
int width() const const
QPixmap pixmap(QWizard::WizardPixmap which) const const
QString mid(int position, int n) const const
bool hasAlpha() const const
QDesktopWidget * desktop()
qint64 secsTo(const QDateTime &other) const const
QTextCodec * codecForName(const QByteArray &name)
QTextCodec * codecForMib(int mib)
int height() const const
char * data()
qint64 write(const char *data, qint64 maxSize)
QString fromLatin1(const char *str, int size)
virtual QPaintEngine * paintEngine() const const override
bool hasFeature(QPaintEngine::PaintEngineFeatures feature) const const
transparent
int size() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void next()
a cached script
Definition: loader.h:322
int height() const const
void append(const T &value)
QString errorText() const
QRgb rgba() const const
QString accept() const
List of acceptable mimetypes separated by ",".
Definition: loader.h:226
bool isValid() const const
bool authorizeUrlAction(const QString &action, const QUrl &baseUrl, const QUrl &destUrl)
int error() const
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Fri Aug 14 2020 22:45:51 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.