I am currently trying to implement a custom url scheme using an in-app browser made with PyQtWebEngine. The problem I am facing is that the whole app crashes right after loading the custom scheme url. The strange part is that it only crashes when provided custom urls, meaning any normal http/https urls work completely fine.
My minimal code example is the following:
import sys
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile
from PyQt5.QtWebEngineCore import (
QWebEngineUrlRequestJob,
QWebEngineUrlSchemeHandler,
QWebEngineUrlScheme,
)
from PyQt5.QtCore import QUrl, QByteArray, QBuffer
class SchemeHandler(QWebEngineUrlSchemeHandler):
def requestStarted(self, request: QWebEngineUrlRequestJob | None) -> None:
if not request:
return
print(f"Got request for {request.requestUrl()}")
request.reply(
QByteArray(b"text/html"), QBuffer(QByteArray(b"<h1>Custom Scheme</h1>"))
)
class Browser(QMainWindow):
def __init__(self) -> None:
super().__init__()
browser = QWebEngineView()
self.setCentralWidget(browser)
browser.showMaximized()
self.handler = SchemeHandler()
profile = QWebEngineProfile.defaultProfile()
if profile:
profile.installUrlSchemeHandler(b"test", self.handler)
print("Installed SchemeHandler")
browser.load(QUrl("test://someurl"))
def register_scheme():
scheme = QWebEngineUrlScheme(b"test")
scheme.setFlags(
QWebEngineUrlScheme.SecureScheme | QWebEngineUrlScheme.LocalAccessAllowed
)
scheme.setSyntax(QWebEngineUrlScheme.Syntax.Path)
scheme.setDefaultPort(80)
QWebEngineUrlScheme.registerScheme(scheme)
if __name__ == "__main__":
register_scheme()
app = QApplication(sys.argv)
b = Browser()
b.show()
app.exec_()
and it outputs this:
Installed SchemeHandler
Got request for PyQt5.QtCore.QUrl('test://someurl')
I am running Windows 11 Build 22631.4460, Python 3.12.3, PyQt5 5.15.11 and PyQtWebEngine 5.15.7.
I already tried updating all libraries, running the example in a venv and adding debugging output like os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--enable-logging --v=1"
.
I would appreciate any help!
The problem here is that the buffer will be immediately garbage-collected once the requestStarted
method returns. The reply is processed asynchronously, so when the request-job tries to access the buffer later, it will find nothing there, causing a fatal error. Strictly speaking, the same applies to the data passed to the buffer as well - this has to remain fully available for the entire read operation, otherwise it could also cause a fatal error. (NB: the Qt docs will usually include warnings about these kinds of issues by stating that it's the caller's responsibility to maintain ownership of the relevant objects).
Given all the above, your example can be fixed by keeping references to the buffer and data, like this:
class SchemeHandler(QWebEngineUrlSchemeHandler):
def requestStarted(self, request: QWebEngineUrlRequestJob | None) -> None:
if not request:
return
print(f"Got request for {request.requestUrl()}")
mimetype = QByteArray(b"text/html")
# keep a reference to the buffered data
self._data = QByteArray(b"<h1>Custom Scheme</h1>")
# make the request-job the parent of the buffer
buffer = QBuffer(request)
buffer.setData(self._data)
buffer.open(QBuffer.ReadOnly)
request.reply(mimetype, buffer)