Commit 60df20c0 authored by Igor Kushnir's avatar Igor Kushnir
Browse files

Embed CSS code into HTML instead of injecting via JavaScript

This improves performance and eliminates flickering when large man pages
are loaded.

Remove mostly obsolete CSS workarounds in
QtHelpDocumentation::setUserStyleSheet(), which where implemented in
245f8e98 10 years ago. Would be a pity
if these practically useless workarounds caused page flickering.
Rewriting this code in a way that prevents flickering just to improve
the looks of documentation pages from older Qt versions does not seem
worthwhile. Especially since, as far as I can tell, overriding CSS has
never worked with Qt WebEngine, until my recent commit. And all these
years no one bothered to fix it.

If overriding CSS in QtHelp pages is ever needed again, it can be
embedded into a page directly as is done in ManPageDocumentation. In
HelpNetworkReply::HelpNetworkReply(), inside the
`if (request.url().fileName().endsWith(QLatin1String(".html")))` block,
add this code:
    constexpr char headEndTag[] = "</head>";
    const auto headEndTagPos = data.indexOf(headEndTag, 0);
    if (headEndTagPos == -1) {
        qCWarning(QTHELP) << "missing" << headEndTag << "on the HTML page.";
    } else {
        auto cssCode = QByteArrayLiteral("html { background: white !important; }\n");
        if (request.url().scheme() == QLatin1String("qthelp")
            && request.url().host().startsWith(QLatin1String("com.trolltech.qt."))) {
            cssCode += ".content .toc + .title + p { clear:left; }\n"
                       "#qtdocheader .qtref { position: absolute !important; top: 5px !important; right: 0 "
                       "!important; }\n";
        }
        data.insert(headEndTagPos, "<style>" + cssCode + "</style>");
    }

I have verified that this proof-of-concept code works in practice by
replacing "background: white" with "background: green". However, a
proper implementation would have to search for headEndTag in QByteArray
data case-insensitively, which could be done with
std::boyer_moore_horspool_searcher and a custom equality BinaryPredicate
based on <cctype>.

Keep no longer used StandardDocumentationView::setOverrideCss*() because
CSS code cannot be embedded into an external website page. For example,
if kdev-php needs to override CSS in the future, it would have to use
this KDevPlatform API.
parent af3e3f95
......@@ -45,6 +45,10 @@ public:
void setDocumentation(const IDocumentation::Ptr& doc);
// NOTE: prefer overriding CSS by embedding a <style> element into HTML code directly instead of calling
// setOverrideCss*(). These functions, in case of Qt WebEngine, inject CSS code via JavaScript, which
// causes reloading and flickering of large pages. See for example kdevmanpage's class StyleSheetFixer.
/**
* Specifies the location of a user stylesheet to load with every web page
*
......
......@@ -9,14 +9,85 @@
#include "manpageplugin.h"
#include "manpagedocumentationwidget.h"
#include "debug.h"
#include <documentation/standarddocumentationview.h>
#include <KIO/TransferJob>
#include <KLocalizedString>
#include <QFile>
#include <QStandardPaths>
namespace {
class StyleSheetFixer
{
public:
static void process(QString& htmlPage)
{
static StyleSheetFixer instance;
instance.fix(htmlPage);
}
private:
/**
* Read the file contents and return it wrapped in a &lt;style&gt; HTML element.
*
* @return The &lt;style&gt; HTML element or an empty string in case of error.
*
* @note Referencing a local file via absolute path or file:// URL inside a &lt;link&gt;
* HTML element does not work because Qt WebEngine forbids such file system access.
*/
static QString readStyleSheet(const QString& fileName)
{
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qCWarning(MANPAGE) << "cannot read CSS file" << fileName << ':' << file.error() << file.errorString();
return QString();
}
const auto cssCode = file.readAll();
return QString::fromUtf8("<style>" + cssCode + "</style>");
}
static QString readCustomStyleSheet()
{
const auto customStyleSheetFile = QStringLiteral("kdevmanpage/manpagedocumentation.css");
const QString cssFilePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, customStyleSheetFile);
if (cssFilePath.isEmpty()) {
qCWarning(MANPAGE) << "couldn't find" << customStyleSheetFile;
return QString();
}
return readStyleSheet(cssFilePath);
}
StyleSheetFixer()
: m_customStyleSheet{readCustomStyleSheet()}
{
}
void fix(QString& htmlPage)
{
if (m_customStyleSheet.isEmpty()) {
return; // nothing to do
}
const QLatin1String headEndTag("</head>");
const auto headEndTagPos = htmlPage.indexOf(headEndTag, 0, Qt::CaseInsensitive);
if (headEndTagPos == -1) {
qCWarning(MANPAGE) << "missing" << headEndTag << "on the HTML page.";
return;
}
// Apply our custom style sheet to normalize look of the page. Embed the <style> element
// into the HTML code directly rather than inject it with JavaScript to avoid reloading
// and flickering of large pages such as cmake-modules man page.
htmlPage.insert(headEndTagPos, m_customStyleSheet);
}
/// The style sheet does not change => read it once and store in a constant.
const QString m_customStyleSheet;
};
} // unnamed namespace
ManPagePlugin* ManPageDocumentation::s_provider=nullptr;
......@@ -33,6 +104,7 @@ void ManPageDocumentation::finished(KJob* j)
auto* job = qobject_cast<KIO::StoredTransferJob*>(j);
if(job && job->error()==0) {
m_description = QString::fromUtf8(job->data());
StyleSheetFixer::process(m_description);
} else {
m_description.clear();
}
......@@ -56,13 +128,6 @@ QWidget* ManPageDocumentation::documentationWidget(KDevelop::DocumentationFindWi
view->setDocumentation(IDocumentation::Ptr(this));
view->setDelegateLinks(true);
QObject::connect(view, &KDevelop::StandardDocumentationView::linkClicked, ManPageDocumentation::s_provider->model(), &ManPageModel::showItemFromUrl);
// apply custom style-sheet to normalize look of the page
const QString cssFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevmanpage/manpagedocumentation.css"));
if (!cssFile.isEmpty()) {
view->setOverrideCssFile(cssFile);
}
return view;
}
......
......@@ -188,16 +188,6 @@ QString QtHelpDocumentation::description() const
return titles.join(QLatin1String(", "));
}
void QtHelpDocumentation::setUserStyleSheet(StandardDocumentationView* view, const QUrl& url)
{
auto cssCode = QByteArrayLiteral("html { background: white !important; }\n");
if (url.scheme() == QLatin1String("qthelp") && url.host().startsWith(QLatin1String("com.trolltech.qt."))) {
cssCode += ".content .toc + .title + p { clear:left; }\n"
"#qtdocheader .qtref { position: absolute !important; top: 5px !important; right: 0 !important; }\n";
}
view->setOverrideCssCode(cssCode);
}
QWidget* QtHelpDocumentation::documentationWidget(DocumentationFindWidget* findWidget, QWidget* parent)
{
if(m_info.isEmpty()) { //QtHelp sometimes has empty info maps. e.g. availableaudioeffects i 4.5.2
......@@ -211,7 +201,6 @@ QWidget* QtHelpDocumentation::documentationWidget(DocumentationFindWidget* findW
QObject::connect(view, &StandardDocumentationView::linkClicked, this, &QtHelpDocumentation::jumpedTo);
connect(view, &StandardDocumentationView::customContextMenuRequested, this, &QtHelpDocumentation::viewContextMenuRequested);
setUserStyleSheet(view, currentUrl());
view->load(currentUrl());
lastView = view;
return view;
......
......@@ -59,7 +59,6 @@ class QtHelpDocumentation : public KDevelop::IDocumentation
void jumpedTo(const QUrl& newUrl);
private:
void setUserStyleSheet(KDevelop::StandardDocumentationView* view, const QUrl& url);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
const QUrl& currentUrl() const { return m_current->url; }
const QString& currentTitle() const { return m_current->title; }
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment