0

I am using a QWebview to display html generated by the same program. Now the html can have references to other resources, e.g. <FRAME src=...> in a fram set. When the browser would start downloading that resource, I must intercept that request and suply the content myself, since there's no webserver involved. Where are the hooks that I may use to catch up the requested url and supply the generated content?

to create the browser widget:

self.browser = QWebView()
self.layout.addWidget(self.browser)

to load the frame set:

self.browser.setHtml(ret.text)

Now what I would expect to find is some signal and then

self.browser.requestURI.connect(myhandler)

But I don't see anything alike it. What is the better approach here?


EDIT:

The major problem seems to be using setHtml. Thus, all loading mechanisms appear to be bypassed. With load() in combination with a QNetworkAccessManager I had better results (see below). Now the response-object is offered to my content manager, however, I sofar failed to write anything to the response object (or to instantiate a fresh one). It can be opened passing an access mode parameter. Then the READ-ONLY error disappears, but still write returns -1.

I rephrase the title of this question accordingly.

from PyQt5.Qt import *  # @UnusedWildImport

class ContentManager(object):
    def handleRequest(self, request, response):
#         response.writeData(bytearray("hello new year", "utf-8")) #THIS WORKS NOT
        return response


class NAM(QNetworkAccessManager):
    def __init__(self, contentManager):
        super().__init__()
        self.contentManager = contentManager

    def createRequest(self, operation, request, device):
        response = super().createRequest(operation, request, device)
        return self.contentManager.handleRequest(request, response)

class Browser(QWidget):
    def __init__(self):
        super().__init__()

    def open(self, url):
        self.browser.load(QUrl(url))

    def build(self, contentManager):
        layout = QVBoxLayout(self)

        view = QWebView()
        page = view.page(); view.setPage(page)
        page.setNetworkAccessManager(NAM(contentManager))
        layout.addWidget(view)

        self.browser = view        

if __name__ == '__main__':
    import sys

    app = QApplication(sys.argv)
    w   = Browser()
    w.build(ContentManager())
    w.open("index.html")
    w.show()

    app.exec_() 
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
user508402
  • 496
  • 1
  • 4
  • 19
  • No duplicate, although it may provide the first lead indeed. The answer provided there is more generic than the question, viz. it deals with any navigation request and not only with clicked links, so that's good. However, it only flags go/nogo, whereas I need to feed content into the response. – user508402 Jan 01 '18 at 10:39
  • It returns a response, because that's what it should do, see http://doc.qt.io/qt-5/qnetworkaccessmanager.html#createRequest. Your dup only implements an acceptor: there's no way it could generate a response. – user508402 Jan 01 '18 at 14:58
  • "why he has 2 returns: " the second is obviously obsolete. I'll delete it. – user508402 Jan 01 '18 at 15:10
  • Kindly concentrate on the problem at hand, i.e. how to change to contents of the response object. Or, if applicable, what would be a better approach to intercept the request-response chain and feed a custom response into it. – user508402 Jan 01 '18 at 15:12

1 Answers1

3

You need to implement a custom QNetworkReply, in general it is possible to serve whatever you want to 'inject' into webview (html, text, images).

I am not sure about local files and frames, but when you are using a 'fake domain' that has to be resolved by QNetworkAccessManager it will work.

The following very simple example works using Python 3.6.3 and PyQt 5.10.0:

from PyQt5.Qt import *  # @UnusedWildImport
import signal, os, sys
signal.signal(signal.SIGINT, signal.SIG_DFL)


class ContentHandler(object):
    def __init__(self, url):
        self.url = url

    def __call__(self):
        print ('ContentHandler >>', self.url)
        path, basename = os.path.split(self.url)

        if basename == 'index.html':
            return b"hello new year", 'text/html'


class DownloadReply(QNetworkReply):
    def __init__(self, parent, operation, request):
        super(DownloadReply, self).__init__(parent)
        self.setRequest(request)
        self.setOperation(operation)
        self.setUrl(request.url())
        self.bytes_read = 0
        self.content = b''

        # give webkit time to connect to the finished and readyRead signals
        QTimer.singleShot(200, self.load_content)

    def load_content(self):
        self.content, self.data = ContentHandler(str(self.url().toString()))()
        self.offset = 0

        self.open(QIODevice.ReadOnly | QIODevice.Unbuffered)
        self.setHeader(QNetworkRequest.ContentTypeHeader, QVariant(self.data))
        self.setHeader(QNetworkRequest.ContentLengthHeader, QVariant(len(self.content)))

        self.readyRead.emit()
        self.finished.emit()

    def abort(self):
        pass

    def isSequential(self):
        return True

    def bytesAvailable(self):
        ba = len(self.content) - self.bytes_read + super(DownloadReply, self).bytesAvailable()
        return ba

    def readData(self, size):
        if self.bytes_read >= len(self.content):
            return None
        data = self.content[self.bytes_read:self.bytes_read + size]
        self.bytes_read += len(data)
        return data

    def manager(self):
        return self.parent()


class NetworkAccessManager(QNetworkAccessManager):
    def __init__(self, parent=None):
        super(NetworkAccessManager, self).__init__(parent=parent)

    def createRequest(self, operation, request, device):
        if str(request.url().host()).lower() == "myfakedom.ain":
            print ('request:', request.url().host())
            return DownloadReply(self, self.GetOperation, request)
        return super(NetworkAccessManager, self).createRequest(operation, request, device)


if __name__ == '__main__':
    app = QApplication(sys.argv)

    webView = QWebView()
    webView.settings().setAttribute(QWebSettings.PluginsEnabled, True)
    webView.settings().setAttribute(QWebSettings.DeveloperExtrasEnabled, True)
    webView.settings().setAttribute(QWebSettings.AutoLoadImages, True)
    webView.settings().setAttribute(QWebSettings.JavascriptEnabled, True)

    webInspector = QWebInspector()

    nam = NetworkAccessManager()
    webView.page().setNetworkAccessManager(nam)
    webView.load(QUrl('http://myFakeDom.ain/index.html'))
    webInspector.setPage(webView.page())

    window = QMainWindow()
    window.setCentralWidget(webView)
    window.setFixedSize(1200, 840)
    window.setWindowTitle('Test')
    window.show()

    sys.exit(app.exec_())
Maurice Meyer
  • 17,279
  • 4
  • 30
  • 47
  • Wow, this really works! I had come close with my own custom reply (not shown here), but I see you're using a number of trick I'd never had dreamt of (such as the delay). I notice that when the main code is placed within a method, nam must be made a member of the object or it will go out of scope and disappear with the c-lib still relying on it being present. – user508402 Jan 01 '18 at 19:19