QtWebView 中的 In-Page href 无法显示任何内容

Posted

技术标签:

【中文标题】QtWebView 中的 In-Page href 无法显示任何内容【英文标题】:In-Page href in QtWebView fails to display anything 【发布时间】:2018-03-05 18:18:36 【问题描述】:

因此,尝试将其归结为简单的部分。 我有一个这样的 href 条目:

<a href="#section_8">
linktext
</a>

如果我通过 Qts setContent 加载页面,这些页面内链接可以工作,但是,由于我的内容经常超过 2MB,我必须使用 urlhandler 方法。因此,快速概览一下,如下所示:

    scheme.SchemeHandler.register(lambda: webpage, "reportstext")
    self.loader = QWebEngineView()
    self.loader.setUrl(QUrl("conapp://reportstext"))

SchemeHandler 保存应用程序内部内容的字典,并将其全部保存在conapp://&lt;contentname&gt;

现在,这行得通。我可以将 Web 内容提供给 WebView。也就是说,直到我尝试使用开头提到的页内链接。我得到的只是一个空白页面,没有调试消息,没有崩溃,什么都没有。只是空白。也没有向方案处理程序发出请求。

如何让这些工作?

如果我将内容导出到 .html 文件并在浏览器中打开它,它会按预期工作,所以 Qt 端的某些东西无法按我预期的那样工作。

完整示例,需要 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)

如果你现在把它换掉

self.loader.setUrl(QUrl("conapp://reportstext"))

self.loader.setHtml(webpage())

这将产生页内网址的预期效果,但是,这有 2 MB 的内容限制,我经常超过。

【问题讨论】:

复制粘贴一个完整的例子。这将在 web 视图中显示一个链接,单击它将使页面变为空白。公平地说,在这个例子中没有有效的目标,但这并没有改变任何东西。 【参考方案1】:

这似乎是由底层 Chrome 引擎处理 base-url 的问题引起的。您示例中的书签锚点是 relative,但如果您将其设为 absolute,如下所示:

<a href="conapp://reportstext#section_0">
  MVZ Arztfallzähler
</a>
<p style="margin-top: 1500px">
  <a name="section_0">HELLO WORLD</a>
</p>

您的示例将按预期工作。有鉴于此,我希望可以通过添加这样的&lt;base&gt; 标签来让相关书签自动工作:

<head>
  <base href="conapp://reportstext">
</head>

但遗憾的是,Chrome 对&lt;base&gt; 标签的实现似乎被破坏了。它可以部分通过在 href 中添加斜杠来解决:

<base href="conapp://reportstext/">

然后您的自定义方案处理程序将获取页面上 所有 相对 url 的请求。但这样做的副作用是 Chrome 会像这样解释相关书签锚的 href:conapp://reportstext/#section_0。因此,您的方案处理程序不会导航到书签,而是会收到对无效 url 的请求。

AFAICS,似乎没有其他方法可以拦截书签锚点的导航请求。我尝试了QWebEngineUrlRequestInterceptor 类并重新实现QWebEnginePage.acceptNavigationRequest(),但与自定义方案处理程序一样,它们只是不会被调用书签锚点。似乎所有的处理都发生在 Chrome 中。


解决上述问题的一种有点骇人听闻的方法是在页面加载时运行一些 javascript,它只是重写所有书签锚点,因此它们具有绝对的 href。

这是一个适用于您的示例的基本实现:

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
                
            
            """)

【讨论】:

要在其上添加问题,可以将此网页内容导出/保存到磁盘。显然,如果您随后使用 conapp:// 链接库打开它,常规浏览器就会开始变得相当混乱。我可以在保存之前进行转换,但是这个问题很烦人,然后似乎是 Qt 中的一个错误? @Berserker。如果书签锚完全由 chrome 处理,我看不出 qt 能做些什么。如果这里有错误,可能是在 chrome 对 base-url 的处理中。再说一次,也许 qt 需要提供一种方法来通知 chrome 正确的 base-url 以用于自定义方案处理程序。 嗯。在这里只是在黑暗中拍摄,但是使用嵌入到页面中的 javascript,我可以覆盖 # 链接的标准处理并以某种方式实现自己的滚动,以作为 .html 和 Qt 的方式工作吗?这只是一个想法,我需要首先研究如何去做...... @Berserker。好吧,如果您不介意一点 javacsript 黑客,您可以简单地重置所有书签锚的 href 以使其绝对。我在我的答案中添加了一些示例代码。 谢谢,现在可以了。

以上是关于QtWebView 中的 In-Page href 无法显示任何内容的主要内容,如果未能解决你的问题,请参考以下文章

QTWebView 真的能通过 Apple App Store Checking 吗?

QtWebView - 如何启用页面滚动和页面中元素的滚动(例如谷歌地图)

如何使用 QML QtWebView 调用 C++?

qt 5.6 怎么编译 qtWebView 或者 qtWebEngine

Qt5.4 不支持 QtWebEngine 吗?

使用 qt 和 django 创建桌面应用