In-Page href в QtWebView ничего не отображает

Итак, попробуем свести это к простым битам. У меня есть такие записи href:

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

Эти ссылки на странице работают, если я загружаю страницу через Qts setContent, однако, поскольку мой контент часто превышает 2 МБ, мне приходится использовать методы urlhandler. Итак, краткий обзор выглядит примерно так:

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

SchemeHandler хранит словарь внутреннего содержимого приложения и хранит все это conapp://<contentname>

Теперь это работает. Я могу передавать веб-контент в 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())

Это даст ожидаемый эффект работы URL-адресов на странице, однако у этого есть ограничение на содержимое 2 МБ, которое я часто превышаю.


person Berserker    schedule 05.03.2018    source источник
comment
Скопировал вместе полный пример. Это покажет вам одну ссылку в веб-просмотре, и нажатие на нее сделает страницу пустой. Справедливости ради, в этом примере нет действительной цели, но это ничего не меняет.   -  person Berserker    schedule 05.03.2018


Ответы (1)


Похоже, это вызвано проблемой обработки базового URL базовым движком Chrome. Якорь закладки в вашем примере относительный, но если вы сделаете его абсолютным, вот так:

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

ваш пример будет работать так, как ожидалось. В свете этого я надеялся, что можно будет заставить относительные закладки работать автоматически, добавив тег <base> следующим образом:

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

Но, к сожалению, похоже, что реализация тега <base> в Chrome не работает. Это можно частично обойти, добавив косую черту в конце href:

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

Затем ваш пользовательский обработчик схемы будет получать запросы на все относительные URL-адреса на странице. Но нежелательным побочным эффектом этого является то, что Chrome затем интерпретирует hrefs относительных привязок закладок следующим образом: conapp://reportstext/#section_0. Таким образом, вместо перехода к закладке ваш обработчик схемы получит запрос на недопустимый URL-адрес.

AFAICS, кажется, нет другого способа перехватить навигационные запросы для привязок закладок. Я попробовал класс QWebEngineUrlRequestInterceptor и повторно реализовал QWebEnginePage.acceptNavigationRequest(), но, как и в случае с пользовательскими обработчиками схем, они просто не вызываются для закладки якоря. Кажется, что вся обработка этого происходит в Chrome.


Несколько хакерский способ обойти вышеуказанные проблемы — запустить некоторый javascript при загрузке страницы, который просто переписывает все привязки закладок, чтобы они имели абсолютные hrefs.

Вот базовая реализация, которая работает с вашим примером:

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
                }
            }
            """)
person ekhumoro    schedule 05.03.2018
comment
Чтобы добавить проблему поверх нее, этот веб-контент можно экспортировать/сохранить на диск. И, очевидно, если вы затем откроете это с помощью базы ссылок conapp://, обычный браузер начнет немного путаться. Я могу конвертировать перед сохранением, но эта проблема довольно раздражает и кажется ошибкой в ​​​​Qt? - person Berserker; 06.03.2018
comment
@Берсеркер. Если привязки закладок полностью обрабатываются хромом, я не вижу, что может с этим поделать qt. Если здесь есть ошибка, она, вероятно, связана с обработкой хромом базового URL-адреса. Опять же, возможно, qt должен предоставить способ сообщить chrome о правильном базовом URL-адресе для использования с обработчиками пользовательских схем. - person ekhumoro; 06.03.2018
comment
Хм. Просто выстрел в темноте здесь, но, используя javascript, встроенный в страницу, могу ли я перезаписать стандартную обработку # ссылок и каким-то образом реализовать прокрутку самостоятельно, чтобы это работало как .html и в Qt? Это просто идея, и мне нужно сначала изучить, как это сделать... - person Berserker; 06.03.2018
comment
@Берсеркер. Что ж, если вы не возражаете против небольшого хакерства с javacsript, вы можете просто сбросить hrefs всех якорей закладок, чтобы сделать их абсолютными. Я добавил в свой ответ пример кода, который делает это. - person ekhumoro; 06.03.2018
comment
Спасибо, сейчас это подойдет. - person Berserker; 06.03.2018