Oh boy, I’d been dancing around Qt WebKit 5.1 for a couple of days already, when I finally found a way to implement sort of a trivial thing. The original task was about saving assets from the WebKit view. Suppose you’ve got a web browser in Qt and you want it to automatically save all, let’s say, pictures, on the hard drive. It seems like a trivial thing though: traversing through all the QWebElement
objects by img
selector and point its content with QWebElement::render()
on QImage, which gets saved eventually. Unfortunately, it lets you capture static images only (gifs are not gonna work) and it obviously won’t work for any other assets (styles, scripts). So we obviously should go the other way around. Turns out, it’s not that easy at all. I’ll tell you about the path I went through to complete the objective and get the whole thing working.
Problem
Since my particular usecase was specific enough (I was interested in .png images only), I’ve implemented an algorithm from the previous paragraph and tried it out with Qt 5.1 on Mac. All in all, it didn’t work at all. All the saved pictures ended up being either black rectangles or just random rubbish. At this point I recalled a shipped Qt 5.1 example, implementing identical functionality. It surprisingly didn’t work neither on Mac nor Linux. No worries, let’s move on.
So what’s next? No way, it just doesn’t work and that’s it. I didn’t have any time to play Hide-and-Seek with webkit, so I’ve started looking for alternatives. Here is the funniest one: I’ve been drawing an image on HTML5 canvas, encoding what’s been drawn in base64 and eventually decoding it on the back-end side. You guess what? It didn’t work as well: images were either black or broken pixmaps. Deffo crazy, but I ain’t got any time to work it around—deadline is on fire!
Solution
What if we could intercept all the assets loaded? You can’t do that out of the box, QNAM
(which is used for networking by QWebView
) just won’t let you do that. Well, it would, but the long way around. I created the InterceptorManager
class, directly inheriting the network access manager:
class InterceptorManager : public QNetworkAccessManager
{
Q_OBJECT
public:
explicit InterceptorManager(QObject *parent = 0);
protected:
QNetworkReply *createRequest(Operation op, const QNetworkRequest &r, QIODevice *d)
{
QNetworkReply *real = QNetworkAccessManager::createRequest(op, r, d);
if (r.url().toString().endsWith(".png")) {
// a tricky one
NetworkReplyProxy *proxy = new NetworkReplyProxy(this, real);
return proxy;
}
return real;
}
};
Let’s override protected createRequest()
and return a custom QNetworkReply
proxy for all the assets of our interest. Why would we do that? QNetworkReply
is not a sequential device, thus you obviously can’t read its data twice and QWebView
must read the image anyway, so it’s able to render one. As you see, we won’t have much to read. That said, there are no implementations of sequential network replies in Qt. We gotta write one then!
I wouldn’t say that writing a proxy for QNetworkReply
is trivial: original implementation is quite big and covers a lot, so implementing it might take some time. Hopefully, in your case it won’t, since I’ve uploaded it to GitHub: tucnak/qtwebkit-ri. I’m not sure if it’s entirely correct, but it works. All in all, that’s what really matters, doesn’t it?
Have fun!
(powered by Disqus)