Hi

I have run into a mysterious issue with QNetworkAccessManager and/or QtWebkit. Using qt 4.6.2 with vs2008.

My app uses webkit to provide a user interface so the user can navigate through and display app-specific data structure held within the app's memory.

To do this the app has a very similar structure to the 'fancybrowser' sample included with Qt, but to which I added a custom QNetworkAccessManager which detects url's that use a private scheme (I used "myp:") and passes them to my own QNetworkReply derivation which knows how to supply the app specific html. All other urls are passed to the QNetworkAccessManager base class for default handling. This is exactly the same code structure as described in a recent Qt Quarterly article describing these techniques.

This appears to work however there is a problem. I have found that whenever the webview is displaying html obtained from an app-private url e.g myp:/fred, any image within the returned html whose source url is not http based just does not get displayed. Why is this?

Here's a more specific description of the problem.

Lets say I create a trivial bit of html which just displays three different images using <img> tags. The first image is stored locally and referenced with a file: based url. The second image is built into the app's binary as a Qt resource and is referenced with a qrc: url. The final image is taken from the net and so has a http: url. I store this html snippet in a local file C:\test.html:

<html><body>
FROM file:///c:/Dalmatian.jpg:<br><img src='file:///c:/Dalmatian.jpg'><br>
FROM qrc:/5453039.jpg:<br><img src='qrc:/5453039.jpg'><br>
FROM http://www.google.co.uk/intl/en_uk/images/logo.gif:<br>
<img src='http://www.google.co.uk/intl/en_uk/images/logo.gif'>
</body></html>
Using my modded fancybrowser I can display this by entering "file:///c:/test.html" in the url box and all is fine, with the 3 images displaying as expected. Here's a screenshot (like my classy images!):



BUT if I arrange for the precise same html snippet to be returned by my own QNetworkReply class, in response to entering an app-private url starting "myp:", what I find is that the only image that displays is the one with the http: url; the images with file: and qrc: urls just dont appear. Here's the screenshot after I put 'myp:' in the url bar. Note images 1+2 missing.



Why is this? What is going on? Is there a workaround/fix? Why is it that the provider of the html seems to affect how QtWebkit displays it?

Thanks.

Here is the relevant code. Note I can reproduce this problem by taking a copy of the qt fancybrowser sample, adding in the code below. Oh and I bind the jpg image referred to (using the qrc: url) into the app by adding a Q_INIT_RESOURCE line to main(). But that is all. No other changes.

First the QNetworkAccessManager derivation which is virtually identical to the example code from a recent Qt Quarterly.

Qt Code:
  1. // header
  2. #include <QNetworkAccessManager>
  3. class MyNetworkAccessManager : public QNetworkAccessManager
  4. {
  5. Q_OBJECT
  6.  
  7. public:
  8. MyNetworkAccessManager(QNetworkAccessManager *oldManager, QObject *parent = 0);
  9.  
  10. protected:
  11. QNetworkReply* createRequest(QNetworkAccessManager::Operation op,
  12. const QNetworkRequest &req, QIODevice *device);
  13. };
  14.  
  15. // code
  16. MyNetworkAccessManager::MyNetworkAccessManager(
  17. QNetworkAccessManager *oldManager, QObject *parent /*= 0*/)
  18. : QNetworkAccessManager(parent)
  19. {
  20. setCache(oldManager->cache());
  21. setCookieJar(oldManager->cookieJar());
  22. setProxy(oldManager->proxy());
  23. setProxyFactory(oldManager->proxyFactory());
  24. }
  25.  
  26. QNetworkReply* MyNetworkAccessManager::createRequest(
  27. QNetworkAccessManager::Operation op, const QNetworkRequest &req,
  28. QIODevice *device)
  29. {
  30. if ((op == QNetworkAccessManager::GetOperation ||
  31. op == QNetworkAccessManager::HeadOperation) && req.url().scheme() == "myp")
  32. return new MyNetworkReply(this, req, op);
  33. // defer to default:
  34. return QNetworkAccessManager::createRequest(op, req, device);
  35. }
To copy to clipboard, switch view to plain text mode 

The code to install this network manager is placed in the main window class immediately after creation of the QWebView:
Qt Code:
  1. .
  2. .
  3. view = new QWebView(this);
  4. // MYCODE BEGINS
  5. QNetworkAccessManager *oldManager = view->page()->networkAccessManager();
  6. MyNetworkAccessManager *newManager = new MyNetworkAccessManager(oldManager, this);
  7. view->page()->setNetworkAccessManager(newManager);
  8. / MYCODE ENDS
  9. .
  10. .
To copy to clipboard, switch view to plain text mode 

The QNetworkReply class. Here I have cut it down to its barest minimum, all this does is setup a reply containing a fixed snippet of html above. It just sticks the html in a QByteArray and set up readData/bytesAvailable etc to access this.

Qt Code:
  1. class MyNetworkReply : public QNetworkReply
  2. {
  3. Q_OBJECT
  4.  
  5. public:
  6. MyNetworkReply(QObject *parent, const QNetworkRequest &req,
  7. const QNetworkAccessManager::Operation op)
  8. : QNetworkReply(parent)
  9. {
  10. setRequest(req);
  11. setUrl(req.url());
  12. setOperation(op);
  13. QNetworkReply::open(QIODevice::ReadOnly | QIODevice::Unbuffered);
  14.  
  15. QString string = "<html><body>FROM MyNetworkReply:<br>\
  16. FROM file:///c:/Dalmatian.jpg:<br><img src='file:///c:/Dalmatian.jpg'><br>\
  17. FROM qrc:/5453039.jpg:<br><img src='qrc:/5453039.jpg'><br>\
  18. FROM http://www.google.co.uk/intl/en_uk/images/logo.gif:<br>\
  19. <img src='http://www.google.co.uk/intl/en_uk/images/logo.gif'>\
  20. </body></html>\n";
  21.  
  22. m_content = string.toUtf8();
  23. m_lOffset = 0;
  24.  
  25. setHeader(QNetworkRequest::ContentTypeHeader,
  26. QVariant("text/html; charset=UTF-8"));
  27. setHeader(QNetworkRequest::ContentLengthHeader,
  28. QVariant(m_content.size()));
  29.  
  30. QMetaObject::invokeMethod(this, "metaDataChanged",
  31. Qt::QueuedConnection);
  32. QMetaObject::invokeMethod(this, "downloadProgress",
  33. Qt::QueuedConnection, Q_ARG(qint64, m_content.size()), Q_ARG(qint64, m_content.size()));
  34. QMetaObject::invokeMethod(this, "readyRead",
  35. Qt::QueuedConnection);
  36. QMetaObject::invokeMethod(this, "finished",
  37. Qt::QueuedConnection);
  38. }
  39. void abort() { QNetworkReply::close(); }
  40. qint64 bytesAvailable() const { return m_content.size() - m_lOffset; }
  41. bool isSequential() const { return true; }
  42.  
  43. protected:
  44. qint64 readData(char* pData, qint64 lMaxSize)
  45. {
  46. if (m_lOffset >= m_content.size())
  47. return -1;
  48.  
  49. qint64 lCount = qMin(lMaxSize, m_content.size() - m_lOffset);
  50. memcpy(pData, m_content.constData() + m_lOffset, lCount);
  51. m_lOffset += lCount;
  52. return lCount;
  53. }
  54.  
  55. private slots:
  56.  
  57. private:
  58. QByteArray m_content;
  59. qint64 m_lOffset;
  60. };
To copy to clipboard, switch view to plain text mode