So, to try to boil this down to the simple bits. I have a href entries like this:
<a href="#section_8">
linktext
</a>
These In-Page links work if I load the page via Qts setContent, however, as my content often exceeds 2MB, I have to use the urlhandler methods. So, quick overview, looks something like this:
scheme.SchemeHandler.register(lambda: webpage, "reportstext")
self.loader = QWebEngineView()
self.loader.setUrl(QUrl("conapp://reportstext"))
SchemeHandler keeps a dictionary of application internal content and it keeps it all behind conapp://<contentname>
Now, this works. I can serve web content to the WebView. That is, until I try to use in-page links as mentioned at the start. What I get then is simply a blank page, no debug messages, no crash, no anything. Just blank. There is also no request going out to the schemehandler.
How do I get these to work?
If I export the content to a .html file and open it in a browser it works as expected, so there's something on the Qt end of things not working as I expect it to.
Complete Example, needs PyQt5:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QSize, QUrl
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtCore import QBuffer, QIODevice
from PyQt5.QtWebEngineCore import (QWebEngineUrlSchemeHandler,
QWebEngineUrlRequestJob)
from PyQt5.QtWebEngineWidgets import QWebEngineProfile
import sys
app = QApplication(sys.argv)
class SchemeHandler(QWebEngineUrlSchemeHandler):
def __init__(self):
super(SchemeHandler, self).__init__()
QWebEngineProfile.defaultProfile().installUrlSchemeHandler(b'conapp', self)
self._handlers = {}
def requestStarted(self, job):
url = job.requestUrl()
print("Got request for {}".format(url.toDisplayString()))
request = url.toString().split("//")[1]
buf = QBuffer(parent=self)
buf.open(QIODevice.WriteOnly)
buf.write(self._handlers[request]().encode("utf-8"))
buf.seek(0)
buf.close()
job.reply("text/html".encode("ascii"), buf)
def register(self, contentgenerator, contentname):
self._handlers[contentname] = contentgenerator
return contentname
SchemeHandler = SchemeHandler()
class ReportMenu(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setMinimumSize(QSize(800, 600))
self.late_init()
def late_init(self):
def webpage():
return """<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>
ConParser Output
</title>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
</head>
<body>
<div class="navbar">
<a href="#section_0">
MVZ Arztfallzähler
</a>
</div>
</body>
</html>"""
SchemeHandler.register(webpage, "reportstext")
self.loader = QWebEngineView()
self.loader.setUrl(QUrl("conapp://reportstext"))
self.report_loader = QWebEngineView()
self.gridLayout = QGridLayout(self)
self.gridLayout.addWidget(self.loader, 0, 0)
centralWidget = QWidget(self)
self.setCentralWidget(centralWidget)
centralWidget.setLayout(self.gridLayout)
self.showMaximized()
self.loader.loadFinished.connect(self.load_completed)
def load_completed(self, *args):
if not args[0]:
print("Load failed:", args)
menu = ReportMenu()
ret_code = app.exec_()
sys.exit(ret_code)
If you now take this and switch out
self.loader.setUrl(QUrl("conapp://reportstext"))
for
self.loader.setHtml(webpage())
This will give the intended effect of in-page urls working, however, this has 2 MB content limit, that I frequently exceed.
This seems to be caused by a problem with how the base-url is handled by the underlying Chrome engine. The bookmark anchor in your example is relative, but if you make it absolute, like this:
<a href="conapp://reportstext#section_0">
MVZ Arztfallzähler
</a>
<p style="margin-top: 1500px">
<a name="section_0">HELLO WORLD</a>
</p>
your example will work as expected. In light of this, I was hoping it would be possible to get relative bookmarks to work automatically by adding a <base>
tag like this:
<head>
<base href="conapp://reportstext">
</head>
But sadly it seems Chrome's implementation of the <base>
tag is broken. It can be partially worked around by adding a trailing slash to the href:
<base href="conapp://reportstext/">
Then your custom scheme-handler will get requests for all relative urls on the page. But an unwanted side-effect of this is that Chrome then interprets the hrefs of relative bookmark anchors like this: conapp://reportstext/#section_0
. So instead of navigating to the bookmark, your scheme-handler will get a request for an invalid url.
AFAICS, there doesn't seem to be any other way of intercepting navigation requests for bookmark anchors. I tried the QWebEngineUrlRequestInterceptor class and reimplementing QWebEnginePage.acceptNavigationRequest(), but as with custom scheme-handlers, they just don't get called for bookmark anchors. It seems all the handling for that happens within Chrome.
A somewhat hackish way to way to work around the above issues would be to run some javascript on page load which simply rewrites all the bookmark anchors so they have absolute hrefs.
Here is a basic implementation that works with your example:
class ReportMenu(QMainWindow):
...
def load_completed(self, ok):
if ok:
page = self.loader.page()
page.runJavaScript("""
url = window.location.href
links = document.querySelectorAll('a')
for (index = 0; index < links.length; ++index) {
link = links[index]
href = link.getAttribute('href')
if (href && href[0] == '#') {
link.href = url + href
}
}
""")