[education/rkward] rkward: Implement more functions in rkqwebview
Thomas Friedrichsmeier
null at kde.org
Thu Apr 9 20:44:24 BST 2026
Git commit 0e42417f5704dcb88a41e7c450db15cff88f891f by Thomas Friedrichsmeier.
Committed on 09/04/2026 at 19:44.
Pushed by tfry into branch 'master'.
Implement more functions in rkqwebview
M +1 -1 rkward/main.cpp
M +1 -1 rkward/windows/rkhtmlwindow.cpp
M +61 -22 rkward/windows/rkqwebview.cpp
M +10 -2 rkward/windows/rkqwebview.h
M +17 -9 rkward/windows/rkqwebview.qml
M +7 -1 rkward/windows/rkworkplace.cpp
M +3 -0 rkward/windows/rkworkplace.h
https://invent.kde.org/education/rkward/-/commit/0e42417f5704dcb88a41e7c450db15cff88f891f
diff --git a/rkward/main.cpp b/rkward/main.cpp
index c69dbb753..61b494637 100644
--- a/rkward/main.cpp
+++ b/rkward/main.cpp
@@ -154,8 +154,8 @@ int main(int argc, char *argv[]) {
scheme.setFlags(QWebEngineUrlScheme::LocalAccessAllowed);
QWebEngineUrlScheme::registerScheme(scheme);
BreezeIcons::initIcons(); // install as fallback theme. Too many issues with missing icons, otherwise
- QtWebView::initialize();
QApplication app(argc, argv);
+ QtWebView::initialize();
KDSingleApplication app_singleton;
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
// Follow the example of kate, and use breeze theme on Windows and Mac, which appears to work best
diff --git a/rkward/windows/rkhtmlwindow.cpp b/rkward/windows/rkhtmlwindow.cpp
index a6d5d3984..06f369228 100644
--- a/rkward/windows/rkhtmlwindow.cpp
+++ b/rkward/windows/rkhtmlwindow.cpp
@@ -89,7 +89,7 @@ RKHTMLWindow::RKHTMLWindow(QWidget *parent, WindowMode mode) : RKMDIWindow(paren
.arg(scheme->foreground().color().name(), scheme->background().color().name(),
scheme->foreground(KColorScheme::VisitedText).color().name(), scheme->foreground(KColorScheme::LinkText).color().name(),
scheme->shade(KColorScheme::MidShade).name());
- page->installPersistentJS(u"fix_color_scheme"_s, color_scheme_js);
+ page->installPersistentJS(color_scheme_js, u"fix_color_scheme"_s);
page->installHelpProtocolHandler();
layout->addWidget(findbar);
diff --git a/rkward/windows/rkqwebview.cpp b/rkward/windows/rkqwebview.cpp
index 1d40cd158..e6d7eeaa1 100644
--- a/rkward/windows/rkqwebview.cpp
+++ b/rkward/windows/rkqwebview.cpp
@@ -11,7 +11,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
#include <QQuickItem>
#include <QQuickWidget>
+#include "../misc/rkfindbar.h"
#include "rkhtmlwindow.h"
+#include "rkworkplace.h"
#include "../debug.h"
@@ -19,38 +21,54 @@ RKQWebView::RKQWebView(RKHTMLWindow *parent) : RKHTMLViewer(parent), view(nullpt
RK_TRACE(APP);
}
-void RKQWebView::onPageLoad(const QUrl &_url, const QString &error, int status) {
- RK_DEBUG(APP, DL_WARNING, "loading %s %d", qPrintable(_url.toString()), status);
- /* TODO: we may need to inject a script similar for handling target=new
- and page internal navigation?
- RKHTMLViewer::runJS(u"document.addEventListener('click', e => {"
- " const origin = e.target.closest('a');"
- " if (origin) alert(origin.href);"
- "})\n"_s); */
- if (status == 0) {
- bool new_window = false;
- Q_EMIT navigationRequest(webView()->property("acceptedUrl").toUrl(), _url, new_window);
- // we may be redirected via the above signal
- if (_url != url()) {
- RK_DEBUG(APP, DL_DEBUG, "redirecting %s to %s", qPrintable(_url.toString()), qPrintable(url().toString()));
- }
- } else if (status == 2) {
- Q_EMIT loadFinished();
+QUrl RKQWebView::currentAcceptedUrl() const {
+ return webView()->property("acceptedUrl").toUrl();
+}
+
+void RKQWebView::onUrlChanged(const QUrl &_url, const QString &error, int status) {
+ RK_DEBUG(APP, DL_DEBUG, "webview url changed to %s status %d, msg %s", qPrintable(_url.toString()), status, qPrintable(error));
+ bool new_window = false;
+ Q_EMIT navigationRequest(currentAcceptedUrl(), _url, new_window);
+ // we may be redirected via the above signal
+ if (_url != url()) {
+ RK_DEBUG(APP, DL_DEBUG, "redirecting %s to %s", qPrintable(_url.toString()), qPrintable(url().toString()));
+ }
+ if (status == 0 && _url.scheme() == u"rkward") {
+ // Some QWebView plugins will also trigger openRKWardURl() via QDesktopServices::setUrlHandler()
+ // Others won't. In either case, we've handled it from here.
+ RKWorkplace::mainWorkplace()->suppressRKWardUrlHandling(_url);
+ }
+}
+
+void RKQWebView::onLoadFinished(const QUrl &url) {
+ RK_TRACE(APP);
+ RK_ASSERT(currentAcceptedUrl() == url);
+ for (const auto &script : std::as_const(persistentScripts)) {
+ RKHTMLViewer::runJS(script);
}
+ Q_EMIT loadFinished();
}
QWidget *RKQWebView::createWidget() {
RK_TRACE(APP);
view = new QQuickWidget();
- view->setSource(QUrl(QStringLiteral("qrc:/qml/rkqwebview.qml")));
view->setResizeMode(QQuickWidget::SizeRootObjectToView);
- connect(webView(), SIGNAL(loaded(const QUrl &, const QString &, int)), this, SLOT(onPageLoad(const QUrl &, const QString &, int)));
+ view->setSource(QUrl(QStringLiteral("qrc:/qml/rkqwebview.qml")));
+ connect(webView(), SIGNAL(pageUrlChanged(const QUrl &, const QString &, int)), this, SLOT(onUrlChanged(const QUrl &, const QString &, int)));
+ connect(webView(), SIGNAL(loadFinished(const QUrl &)), this, SLOT(onLoadFinished(const QUrl &)));
+ connect(webView(), SIGNAL(runJSResult(const QVariant &)), this, SLOT(onRunJSResult(const QVariant &)));
+ /* TODO: we may need to inject a script similar for handling target=new
+ and page internal navigation?
+ RKHTMLViewer::runJS(u"document.addEventListener('click', e => {"
+ " const origin = e.target.closest('a');"
+ " if (origin) alert(origin.href);"
+ "})\n"_s); */
return view;
}
QObject *RKQWebView::webView() const {
- // TODO: we have some premature calls to this function. Find and fix them
if (!view) {
+ // could happen (happened in the past) due to premature calls to this function.
RK_ASSERT(view);
return nullptr;
}
@@ -77,20 +95,31 @@ void RKQWebView::load(const QUrl &url) {
void RKQWebView::print() {
RK_TRACE(APP);
+ // TODO: Doesn't work. Why?
RKHTMLViewer::runJS(u"window.print()"_s);
}
void RKQWebView::installPersistentJS(const QString &script, const QString &id) {
RK_TRACE(APP);
+ persistentScripts[id] = script;
}
void RKQWebView::runJS(const QString &script, std::function<void(const QVariant &)> callback) {
RK_TRACE(APP);
- QMetaObject::invokeMethod(webView(), "runJavaScript", Q_ARG(QString, script), Q_ARG(QJSValue, QJSValue()));
+ RK_ASSERT(runjs_callback == nullptr); // no nested calls, please!
+ runjs_callback = callback;
+ QMetaObject::invokeMethod(webView(), "runJSWrapper", Q_ARG(QString, script));
+}
+
+void RKQWebView::onRunJSResult(const QVariant &result) {
+ RK_TRACE(APP);
+ if (runjs_callback != nullptr) runjs_callback(result);
+ runjs_callback = std::function<void(const QVariant &)>();
}
-void RKQWebView::setHTML(const QString &html, const QUrl &url) {
+void RKQWebView::setHTML(const QString &html, const QUrl &_url) {
RK_TRACE(APP);
+ QMetaObject::invokeMethod(webView(), "loadHtml", Q_ARG(QString, html), Q_ARG(QUrl, _url));
}
bool RKQWebView::installHelpProtocolHandler() {
@@ -100,6 +129,16 @@ bool RKQWebView::installHelpProtocolHandler() {
void RKQWebView::findRequest(const QString &text, bool backwards, RKFindBar *findbar, bool *found) {
RK_TRACE(APP);
+
+ *found = true; // real result is not available, synchronously
+ auto _txt = text;
+ // NOTE: window.find() is marked as not standardized at the time of this writing!
+ QString script = u"window.find('%1', %2, %3, true)"_s.arg(_txt.replace(u"'"_s, u"\\'"_s),
+ (findbar->isOptionSet(RKFindBar::MatchCase) ? u"true"_s : u"false"_s),
+ (backwards ? u"true"_s : u"false"_s));
+ runJS(script, [findbar](const QVariant &res) {
+ if (!res.toBool()) findbar->indicateSearchFail();
+ });
}
QMenu *RKQWebView::createContextMenu(const QPoint &clickpos) {
diff --git a/rkward/windows/rkqwebview.h b/rkward/windows/rkqwebview.h
index 48511ed09..f681105c5 100644
--- a/rkward/windows/rkqwebview.h
+++ b/rkward/windows/rkqwebview.h
@@ -8,6 +8,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
#ifndef RKQWEBVIEW_H
#define RKQWEBVIEW_H
+#include <QMap>
#include <QPointer>
#include "rkhtmlviewer.h"
@@ -15,6 +16,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
class QQuickWidget;
class QQuickItem;
+/** Interface abstraction for QWebView. Note that QWebView is essentially QML only until Qt 6.10. Once
+ * we can assume Qt 6.11,some simplifications will be possible. */
class RKQWebView : public RKHTMLViewer {
Q_OBJECT
public:
@@ -23,7 +26,7 @@ class RKQWebView : public RKHTMLViewer {
void load(const QUrl &url) override;
void print() override;
void installPersistentJS(const QString &script, const QString &id) override;
- void runJS(const QString &script, std::function<void(const QVariant &)> callback) override;
+ void runJS(const QString &script, std::function<void(const QVariant &)> callback = std::function<void(const QVariant &)>()) override;
void setHTML(const QString &html, const QUrl &url) override;
bool installHelpProtocolHandler() override;
void findRequest(const QString &text, bool backwards, RKFindBar *findbar, bool *found) override;
@@ -40,8 +43,13 @@ class RKQWebView : public RKHTMLViewer {
friend class RKHTMLViewer;
RKQWebView(RKHTMLWindow *parent);
QPointer<QQuickWidget> view;
+ QUrl currentAcceptedUrl() const;
+ QMap<QString, QString> persistentScripts;
+ std::function<void(const QVariant &)> runjs_callback;
private Q_SLOTS:
- void onPageLoad(const QUrl &url, const QString &error, int status);
+ void onUrlChanged(const QUrl &url, const QString &error, int status);
+ void onLoadFinished(const QUrl &url);
+ void onRunJSResult(const QVariant &result);
QObject *webView() const;
};
diff --git a/rkward/windows/rkqwebview.qml b/rkward/windows/rkqwebview.qml
index 1059a24ab..1efc367a2 100644
--- a/rkward/windows/rkqwebview.qml
+++ b/rkward/windows/rkqwebview.qml
@@ -13,7 +13,13 @@ Item {
visible: true
WebView {
- signal loaded(url murl, string error, int status)
+ signal pageUrlChanged(url murl, string error, int status)
+ signal loadFinished(url murl)
+ signal runJSResult(variant result)
+
+ function runJSWrapper(script: string) {
+ runJavaScript(script, runJSResult)
+ }
id: webView
objectName: "webView"
visible: true
@@ -22,14 +28,16 @@ Item {
property string acceptedUrl: ""
onLoadingChanged: request => {
- // give the frontend a chance to intervene, e.g. rewriting rkward:// url, opening in new window,
- // denying external urls, etc.
- loaded(request.url, request.error, request.status);
- if (request.status == WebView.LoadStartedStatus) {
- if (request.url != acceptedUrl) {
- //console.log("Navigation request to " + request.url + "denied");
- url = acceptedUrl;
- }
+ // notify frontend of any url changes, giving it a chance to intervene (e.g. rewriting
+ // rkward:// urls etc.)
+ // Note that the semantics of just when loadingChanged is emitted differ between the QWebView
+ // plugins. E.g. for webengine, we get a LoadStartedStatus, then LoadFailedStatus for rkward://-urls,
+ // while for webview2, we only get LoadFailedStatus.
+ if (request.url != acceptedUrl) {
+ pageUrlChanged(request.url, request.error, request.status);
+ }
+ if (request.status == WebView.LoadSucceededStatus) {
+ loadFinished(request.url);
}
}
}
diff --git a/rkward/windows/rkworkplace.cpp b/rkward/windows/rkworkplace.cpp
index 369cd9f0b..ba7ed1de6 100644
--- a/rkward/windows/rkworkplace.cpp
+++ b/rkward/windows/rkworkplace.cpp
@@ -403,7 +403,13 @@ void RKWorkplace::namedWindowOwnerDestroyed(QObject *owner) {
void RKWorkplace::openRKWardUrl(const QUrl &url) {
RK_TRACE(APP);
RK_ASSERT(url.scheme() == QLatin1String("rkward"));
- RKHTMLWindow::handleRKWardURL(url);
+ if (url != suppressed_rkward_url) RKHTMLWindow::handleRKWardURL(url);
+ suppressed_rkward_url.clear();
+}
+
+void RKWorkplace::suppressRKWardUrlHandling(const QUrl &url) {
+ RK_TRACE(APP);
+ suppressed_rkward_url = url;
}
bool RKWorkplace::openAnyUrl(const QUrl &url, const QString &known_mimetype, bool force_external) {
diff --git a/rkward/windows/rkworkplace.h b/rkward/windows/rkworkplace.h
index 8cffaa8dd..6884fc3d5 100644
--- a/rkward/windows/rkworkplace.h
+++ b/rkward/windows/rkworkplace.h
@@ -229,6 +229,8 @@ class RKWorkplace : public QWidget {
/** Inform the workplace that this window is handled outside the regular attached/detached mechanisms (such as preview windows). Internally, this just sets the window to detached, without giving it a DetachedWindowContainer.
This seems good enough for now, but may be something to revisit in case of unexpected problems. */
void setWindowNotManaged(RKMDIWindow *window);
+ /** Used from RKQWebView */
+ void suppressRKWardUrlHandling(const QUrl &url);
Q_SIGNALS:
/** emitted when the workspace Url has changed */
void workspaceUrlChanged(const QUrl &url);
@@ -284,6 +286,7 @@ class RKWorkplace : public QWidget {
RKMDIWindow::State window_placement_override;
QString window_name_override;
QString window_style_override;
+ QUrl suppressed_rkward_url;
};
#endif
More information about the rkward-tracker
mailing list