fix: migrate QtWebKit API calls to QtWebEngine for QGIS 4 / Qt6 #416
fix: migrate QtWebKit API calls to QtWebEngine for QGIS 4 / Qt6 #416ghtmtt merged 10 commits intoghtmtt:qt6from
Conversation
|
many many thanks @bruno-portfolio! I'll test this PR ASAP! |
|
@bruno-portfolio print layout is not exploding anymore, and that's a super good thing! However, the plot is not rendered, is that expected?
|
|
Hi @ghtmtt, thanks for testing ! The blank plot is not expected, it's a timing bug in QtWebEngine. Unlike QtWebKit, loadFinished fires when the DOM is parsed, not when Plotly has finished rendering. So grab() was capturing an empty frame. I reproduced it in a docker container with qgis/qgis:4.0-trixie + python3-pyqt6.qtwebengine and confirmed
I just pushed a fix: loading_html_finished now polls for Plotly's DOM elements before calling grab(), with a short compositor delay. Could you test again ? Cheers ! |
|
@bruno-portfolio I've tested it again but still the plot is empty. Can I help you debugging in some way? |
|
what OS/Distro are you using ? does the DataPlotly panel (canvas view, not print layout) render plots correctly? could you check the QGIS log (View → Log Messages → DataPlotly tab) for any JS errors? in the Python console, what does |
|
I'm on a Debian Sid machine. Plots are created without any issues from the main interface, while I get this error in the log when using from the print layout: |
…creates an opaque Chromium origin that silently blocks file:// script loading (Plotly.js).
|
I really think we are almost there. The previous error is gone, but I get this other one: |
|
@ghtmtt That error is pre-existing. But is the plot actually rendering now in the layout? |
|
Try now and let me know |
|
Let's try this then |
|
@ghtmtt, sorry for the back and forth ! Could you run this in the QGIS Python console after adding a plot item to the print layout ? item = [i for i in iface.openLayoutDesigners()[0].layout().items() if hasattr(i, 'web_page')][0]; item.web_page.runJavaScript("JSON.stringify({plotly: typeof Plotly, scatter: !!document.querySelector('.scatterlayer'), points: document.querySelectorAll('.point').length, flag: window._plotlyRenderComplete, data: document.querySelector('.js-plotly-plot') ? document.querySelector('.js-plotly-plot').data.length : -1})", print) |
|
thanks that you are taking the time to fix this! That's what I get from the console: |
|
Replaced web_view.grab() with Plotly.toImage(), the Chromium compositor doesn't reliably update the backing store for offscreen widgets, so we now let Plotly export the image directly as PNG base64 and decode it into a QPixmap on the Python side. Let me know what happens |
|
Let's see now |
|
can u run this |
|
here you go: |
|
alright, let's test this one |
|
That's great !!! |
|
if you want to include also the resizing in this PR it would be super great! |
|
done |
|
awesome! Super many thanks for this contribution. I'm going to merge the pull request in the qt6 branch, but I've still to figure out a decent way to ship the plugin for both QGIS 3 with Webkit and QGIS 4 with Webengine. That's a super great contribution, thank you so much! |
|
Thanks @ghtmtt ! For the dual QGIS 3/4 shipping, one approach would be a runtime check, try importing QtWebEngineWidgets, fall back to QtWebKitWidgets, with the layout item using the appropriate rendering path (the old mainFrame().render() for WebKit, Plotly.toImage() for WebEngine). happy to help with that if you want ! |
|
yeah, that is also what I was thinking. Also the js function for the plot interaction is different and I've added a custom bridge class that is needed only for webengine. If you want to help me out we can split the work! |
|
Sure, let's do it ! I can take the layout item dual rendering path (WebKit mainFrame().render() vs WebEngine Plotly.toImage()) with runtime detection. You handle the interaction bridge + JS callbacks side. We can track it under #377 since that's already the migration issue. |
|
good to me! We can work on separated branches starting from this last one and when we are done we can merge to master directly. Do you have both QGIS releases installed? The strategy could be to check the QGIS version and if < 4 than using WebKit (as it is right now) is >= 4 then using WebEngine, without considering to have the chance to have a QGIS 4 with WebKit and not WebEngine. What do you think? |
|
I only have QGIS 4.0 on windows + docker for testing. I don't have QGIS 3 installed, so I can't test the webkit path directly, I'll need your help validating that side. I went with import-based detection instead of version check, try: import QtWebEngine / except: import qtwebkit. In practice the result is identical to checking the QGIS version, but it also handles edge cases like missing bindings gracefully instead of crashing. If you prefer a version check, it's a trivial change in one file. What do you think? I've already started working on the detection module + layout item dual rendering on a branch. I'll open a PR shortly so you can see the approach and test the webkit path on your end if that is ok with you. |
|
I can test on my linux with QGIS 3 and 4. I've prepared a PR feel free to push to that branch or to a clean one and then we will merge into the main qt6 and finally into the master one (resolving all the conflicts first :( ) |







Hello !
This PR fixes the remaining QtWebKit API calls in the
qt6branch that prevent the plugin from working on QGIS 4.0 / Qt6.PlotLayoutItemcrashes silently on creation due toQWebEngineViewrejecting a non-QWidget parentsave_plot_as_image()crashes onmainFrame()which doesn't exist in QtWebEngineRelated: #377, #400
See also: qgis/QGIS#65398
Architecture change: separated
QWebEnginePage(page logic, console logging) fromQWebEngineView(offscreen rendering viagrab()). The view usesWA_DontShowOnScreen+show()which is the standard Qt6 pattern for offscreen widget rendering.Full end-to-end testing is blocked by qgis/QGIS#65398 — QGIS 4.0 on Windows does not ship
PyQt6-WebEnginebindings. The Qt6 WebEngine DLLs are present but the Python bindings are missing, soQWebEngineViewloads but fails to render (loadFinishedreturnsFalse).Cheers !