diff --git a/webenginepart/src/CMakeLists.txt b/webenginepart/src/CMakeLists.txt index 091845db3..4b565e56d 100644 --- a/webenginepart/src/CMakeLists.txt +++ b/webenginepart/src/CMakeLists.txt @@ -1,78 +1,83 @@ find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Wallet) add_definitions(-DTRANSLATION_DOMAIN=\"webenginepart\") if(BUILD_TESTING) add_definitions(-DBUILD_TESTING) endif(BUILD_TESTING) include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_BINARY_DIR}) set(kwebenginepartlib_LIB_SRCS webenginepart.cpp webenginepart_ext.cpp webengineview.cpp webenginepage.cpp websslinfo.cpp webhistoryinterface.cpp webenginepartdownloadmanager.cpp webenginewallet.cpp webengineparterrorschemehandler.cpp - webengineparthtmlembedder.cpp webenginepartkiohandler.cpp webenginepartcookiejar.cpp settings/webenginesettings.cpp settings/webengine_filter.cpp ui/searchbar.cpp ui/passwordbar.cpp ui/featurepermissionbar.cpp ) +if(Qt5WebEngineWidgets_VERSION VERSION_GREATER_EQUAL "5.12.0") + add_definitions(-DUSE_QWEBENGINE_URL_SCHEME) +else() + list(APPEND kwebenginepartlib_LIB_SRCS, webengineparthtmlembedder.cpp) +endif() + qt5_wrap_ui(kwebenginepartlib_LIB_SRCS ui/searchbar.ui ) ecm_qt_declare_logging_category(kwebenginepartlib_LIB_SRCS HEADER webenginepart_debug.h IDENTIFIER WEBENGINEPART_LOG CATEGORY_NAME org.kde.webenginepart ) add_library(kwebenginepartlib ${kwebenginepartlib_LIB_SRCS}) generate_export_header(kwebenginepartlib) target_link_libraries(kwebenginepartlib PUBLIC Qt5::Core Qt5::DBus Qt5::Gui Qt5::Widgets Qt5::WebEngineWidgets KF5::Parts KF5::Wallet PRIVATE Qt5::PrintSupport KF5::SonnetCore KF5::IconThemes #for KIconLoader used by WebEnginePartErrorSchemeHandler KF5::WindowSystem # for KUserTimestamp ) target_include_directories(kwebenginepartlib PUBLIC "$" ) set_target_properties(kwebenginepartlib PROPERTIES OUTPUT_NAME kwebenginepart) install(TARGETS kwebenginepartlib ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) add_library(webenginepart MODULE webenginepartfactory.cpp) kcoreaddons_desktop_to_json(webenginepart webenginepart.desktop) target_link_libraries(webenginepart kwebenginepartlib) install(TARGETS webenginepart DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/parts) install(FILES webenginepart.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) install(FILES webenginepart.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/webenginepart) install(FILES error.html DESTINATION ${KDE_INSTALL_DATADIR}/webenginepart) diff --git a/webenginepart/src/webenginepart.cpp b/webenginepart/src/webenginepart.cpp index bfd16ff4a..80d7504f9 100644 --- a/webenginepart/src/webenginepart.cpp +++ b/webenginepart/src/webenginepart.cpp @@ -1,959 +1,997 @@ /* * This file is part of the KDE project. * * Copyright (C) 2007 Trolltech ASA * Copyright (C) 2008 - 2010 Urs Wolfer * Copyright (C) 2008 Laurent Montel * Copyright (C) 2009 Dawit Alemayehu * Copyright (C) 2013 Allan Sandfeld Jensen * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by the * Free Software Foundation; either version 2.1 of the License, or (at your * option) any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ #include "webenginepart.h" #include "webenginepartkiohandler.h" #include //#include #include #include #include #include "webenginepart_ext.h" #include "webengineview.h" #include "webenginepage.h" #include "websslinfo.h" #include "webhistoryinterface.h" #include "webenginewallet.h" #include "webengineparterrorschemehandler.h" #include "webenginepartcookiejar.h" #include "ui/searchbar.h" #include "ui/passwordbar.h" #include "ui/featurepermissionbar.h" #include "settings/webenginesettings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include +#ifdef USE_QWEBENGINE_URL_SCHEME +#include +#endif #include "utils.h" +#include + +void WebEnginePart::initWebEngineUrlSchemes() +{ +#ifdef USE_QWEBENGINE_URL_SCHEME + static bool needToInitUrlSchemes = true; + if (needToInitUrlSchemes) { + needToInitUrlSchemes = false; + QVector localSchemes = {"error"}; + const QStringList protocols = KProtocolInfo::protocols(); + for(const QString &prot : protocols){ +#if KIO_VERSION >= QT_VERSION_CHECK(5,60,0) + if (KProtocolInfo::defaultMimetype(prot) == "text/html") { + localSchemes.append(QByteArray(prot.toLatin1())); + } +#else + QUrl fakeUrl; + fakeUrl.setScheme(prot); + fakeUrl.setPath("fake"); + if (KProtocolManager::defaultMimetype(fakeUrl) == "text/html"){ + localSchemes.append(QByteArray(prot.toLatin1())); + } +#endif + } + for (const QByteArray &name : qAsConst(localSchemes)){ + QWebEngineUrlScheme scheme(name); + scheme.setFlags(QWebEngineUrlScheme::LocalScheme|QWebEngineUrlScheme::LocalAccessAllowed); + scheme.setSyntax(QWebEngineUrlScheme::Syntax::Path); + QWebEngineUrlScheme::registerScheme(scheme); + } + } +#endif +} WebEnginePart::WebEnginePart(QWidget *parentWidget, QObject *parent, const QByteArray& cachedHistory, const QStringList& /*args*/) :KParts::ReadOnlyPart(parent), m_emitOpenUrlNotify(true), m_hasCachedFormData(false), m_doLoadFinishedActions(false), m_statusBarWalletLabel(nullptr), m_searchBar(nullptr), m_passwordBar(nullptr), m_featurePermissionBar(nullptr), m_wallet(nullptr) { + initWebEngineUrlSchemes(); QWebEngineProfile *prof = QWebEngineProfile::defaultProfile(); if (!prof->urlSchemeHandler("error")) { prof->installUrlSchemeHandler("error", new WebEnginePartErrorSchemeHandler(prof)); prof->installUrlSchemeHandler("help", new WebEnginePartKIOHandler(prof)); } static WebEnginePartCookieJar s_cookieJar(prof, prof); KAboutData about = KAboutData(QStringLiteral("webenginepart"), i18nc("Program Name", "WebEnginePart"), /*version*/ QStringLiteral("1.3.0"), i18nc("Short Description", "QtWebEngine Browser Engine Component"), KAboutLicense::LGPL, i18n("(C) 2009-2010 Dawit Alemayehu\n" "(C) 2008-2010 Urs Wolfer\n" "(C) 2007 Trolltech ASA")); about.addAuthor(i18n("Sune Vuorela"), i18n("Maintainer, Developer"), QStringLiteral("sune@kde.org")); about.addAuthor(i18n("Dawit Alemayehu"), i18n("Developer"), QStringLiteral("adawit@kde.org")); about.addAuthor(i18n("Urs Wolfer"), i18n("Maintainer, Developer"), QStringLiteral("uwolfer@kde.org")); about.addAuthor(i18n("Michael Howell"), i18n("Developer"), QStringLiteral("mhowell123@gmail.com")); about.addAuthor(i18n("Laurent Montel"), i18n("Developer"), QStringLiteral("montel@kde.org")); about.addAuthor(i18n("Dirk Mueller"), i18n("Developer"), QStringLiteral("mueller@kde.org")); about.setProductName("webenginepart/general"); // KComponentData componentData(&about); setComponentData(about, false /*don't load plugins yet*/); #if 0 // NOTE: If the application does not set its version number, we automatically // set it to KDE's version number so that the default user-agent string contains // proper application version number information. See QWebEnginePage::userAgentForUrl... if (QCoreApplication::applicationVersion().isEmpty()) QCoreApplication::setApplicationVersion(QString("%1.%2.%3") .arg(KDE::versionMajor()) .arg(KDE::versionMinor()) .arg(KDE::versionRelease())); #endif setXMLFile(QL1S("webenginepart.rc")); // Create this KPart's widget QWidget *mainWidget = new QWidget (parentWidget); mainWidget->setObjectName(QStringLiteral("webenginepart")); // Create the WebEngineView... m_webView = new WebEngineView (this, parentWidget); // Create the browser extension. m_browserExtension = new WebEngineBrowserExtension(this, cachedHistory); // Add status bar extension... m_statusBarExtension = new KParts::StatusBarExtension(this); // Add a web history interface for storing visited links. // if (!QWebEngineHistoryInterface::defaultInterface()) // QWebHistoryInterface::setDefaultInterface(new WebHistoryInterface(this)); // Add text and html extensions... new WebEngineTextExtension(this); new WebEngineHtmlExtension(this); new WebEngineScriptableExtension(this); // Layout the GUI... QVBoxLayout* l = new QVBoxLayout(mainWidget); l->setContentsMargins(0, 0, 0, 0); l->setSpacing(0); l->addWidget(m_webView); // Set the part's widget setWidget(mainWidget); // Set the web view as the focus object mainWidget->setFocusProxy(m_webView); // Connect the signals from the webview connect(m_webView, &QWebEngineView::titleChanged, this, &Part::setWindowCaption); connect(m_webView, &QWebEngineView::urlChanged, this, &WebEnginePart::slotUrlChanged); // connect(m_webView, SIGNAL(linkMiddleOrCtrlClicked(QUrl)), // this, SLOT(slotLinkMiddleOrCtrlClicked(QUrl))); // connect(m_webView, SIGNAL(selectionClipboardUrlPasted(QUrl,QString)), // this, SLOT(slotSelectionClipboardUrlPasted(QUrl,QString))); connect(m_webView, &QWebEngineView::loadFinished, this, &WebEnginePart::slotLoadFinished); // Connect the signals from the page... connectWebEnginePageSignals(page()); // Init the QAction we are going to use... initActions(); // Load plugins once we are fully ready loadPlugins(); setWallet(page()->wallet()); } WebEnginePart::~WebEnginePart() { } WebEnginePage* WebEnginePart::page() { if (m_webView) return qobject_cast(m_webView->page()); return nullptr; } const WebEnginePage* WebEnginePart::page() const { if (m_webView) return qobject_cast(m_webView->page()); return nullptr; } void WebEnginePart::initActions() { actionCollection()->addAction(KStandardAction::SaveAs, QStringLiteral("saveDocument"), m_browserExtension, SLOT(slotSaveDocument())); QAction* action = new QAction(i18n("Save &Frame As..."), this); actionCollection()->addAction(QStringLiteral("saveFrame"), action); connect(action, &QAction::triggered, m_browserExtension, &WebEngineBrowserExtension::slotSaveFrame); action = new QAction(QIcon::fromTheme(QStringLiteral("document-print-preview")), i18n("Print Preview"), this); actionCollection()->addAction(QStringLiteral("printPreview"), action); connect(action, &QAction::triggered, m_browserExtension, &WebEngineBrowserExtension::slotPrintPreview); action = new QAction(QIcon::fromTheme(QStringLiteral("zoom-in")), i18nc("zoom in action", "Zoom In"), this); actionCollection()->addAction(QStringLiteral("zoomIn"), action); actionCollection()->setDefaultShortcuts(action, QList () << QKeySequence(QStringLiteral("CTRL++")) << QKeySequence(QStringLiteral("CTRL+="))); connect(action, &QAction::triggered, m_browserExtension, &WebEngineBrowserExtension::zoomIn); action = new QAction(QIcon::fromTheme(QStringLiteral("zoom-out")), i18nc("zoom out action", "Zoom Out"), this); actionCollection()->addAction(QStringLiteral("zoomOut"), action); actionCollection()->setDefaultShortcuts(action, QList () << QKeySequence(QStringLiteral("CTRL+-")) << QKeySequence(QStringLiteral("CTRL+_"))); connect(action, &QAction::triggered, m_browserExtension, &WebEngineBrowserExtension::zoomOut); action = new QAction(QIcon::fromTheme(QStringLiteral("zoom-original")), i18nc("reset zoom action", "Actual Size"), this); actionCollection()->addAction(QStringLiteral("zoomNormal"), action); actionCollection()->setDefaultShortcut(action, QKeySequence(QStringLiteral("CTRL+0"))); connect(action, &QAction::triggered, m_browserExtension, &WebEngineBrowserExtension::zoomNormal); action = new QAction(i18n("Zoom Text Only"), this); action->setCheckable(true); KConfigGroup cgHtml(KSharedConfig::openConfig(), "HTML Settings"); bool zoomTextOnly = cgHtml.readEntry("ZoomTextOnly", false); action->setChecked(zoomTextOnly); actionCollection()->addAction(QStringLiteral("zoomTextOnly"), action); connect(action, &QAction::triggered, m_browserExtension, &WebEngineBrowserExtension::toogleZoomTextOnly); action = new QAction(i18n("Zoom To DPI"), this); action->setCheckable(true); bool zoomToDPI = cgHtml.readEntry("ZoomToDPI", false); action->setChecked(zoomToDPI); actionCollection()->addAction(QStringLiteral("zoomToDPI"), action); connect(action, &QAction::triggered, m_browserExtension, &WebEngineBrowserExtension::toogleZoomToDPI); action = actionCollection()->addAction(KStandardAction::SelectAll, QStringLiteral("selectAll"), m_browserExtension, SLOT(slotSelectAll())); action->setShortcutContext(Qt::WidgetShortcut); m_webView->addAction(action); KCodecAction *codecAction = new KCodecAction( QIcon::fromTheme(QStringLiteral("character-set")), i18n( "Set &Encoding" ), this, true ); actionCollection()->addAction( QStringLiteral("setEncoding"), codecAction ); connect(codecAction, SIGNAL(triggered(QTextCodec*)), SLOT(slotSetTextEncoding(QTextCodec*))); action = new QAction(i18n("View Do&cument Source"), this); actionCollection()->addAction(QStringLiteral("viewDocumentSource"), action); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_U)); connect(action, &QAction::triggered, m_browserExtension, &WebEngineBrowserExtension::slotViewDocumentSource); action = new QAction(i18nc("Secure Sockets Layer", "SSL"), this); actionCollection()->addAction(QStringLiteral("security"), action); connect(action, &QAction::triggered, this, &WebEnginePart::slotShowSecurity); action = actionCollection()->addAction(KStandardAction::Find, QStringLiteral("find"), this, SLOT(slotShowSearchBar())); action->setWhatsThis(i18nc("find action \"whats this\" text", "

Find text

" "Shows a dialog that allows you to find text on the displayed page.")); } void WebEnginePart::updateActions() { m_browserExtension->updateActions(); QAction* action = actionCollection()->action(QL1S("saveDocument")); if (action) { const QString protocol (url().scheme()); action->setEnabled(protocol != QL1S("about") && protocol != QL1S("error")); } action = actionCollection()->action(QL1S("printPreview")); if (action) { action->setEnabled(m_browserExtension->isActionEnabled("print")); } } void WebEnginePart::connectWebEnginePageSignals(WebEnginePage* page) { if (!page) return; connect(page, SIGNAL(loadStarted()), this, SLOT(slotLoadStarted())); connect(page, SIGNAL(loadAborted(QUrl)), this, SLOT(slotLoadAborted(QUrl))); connect(page, &QWebEnginePage::linkHovered, this, &WebEnginePart::slotLinkHovered); // connect(page, SIGNAL(saveFrameStateRequested(QWebFrame*,QWebHistoryItem*)), // this, SLOT(slotSaveFrameState(QWebFrame*,QWebHistoryItem*))); // connect(page, SIGNAL(restoreFrameStateRequested(QWebFrame*)), // this, SLOT(slotRestoreFrameState(QWebFrame*))); // connect(page, SIGNAL(statusBarMessage(QString)), // this, SLOT(slotSetStatusBarText(QString))); connect(page, SIGNAL(windowCloseRequested()), this, SLOT(slotWindowCloseRequested())); // connect(page, SIGNAL(printRequested(QWebFrame*)), // m_browserExtension, SLOT(slotPrintRequested(QWebFrame*))); // connect(page, SIGNAL(frameCreated(QWebFrame*)), // this, SLOT(slotFrameCreated(QWebFrame*))); // connect(m_webView, SIGNAL(linkShiftClicked(QUrl)), // page, SLOT(downloadUrl(QUrl))); connect(page, SIGNAL(loadProgress(int)), m_browserExtension, SIGNAL(loadingProgress(int))); connect(page, SIGNAL(selectionChanged()), m_browserExtension, SLOT(updateEditActions())); // connect(m_browserExtension, SIGNAL(saveUrl(QUrl)), // page, SLOT(downloadUrl(QUrl))); connect(page, &QWebEnginePage::iconUrlChanged, [page, this](const QUrl& url) { if (WebEngineSettings::self()->favIconsEnabled() && !page->profile()->isOffTheRecord()){ emit m_browserExtension->setIconUrl(url); } }); } void WebEnginePart::setWallet(WebEngineWallet* wallet) { if(m_wallet){ disconnect(m_wallet, &WebEngineWallet::saveFormDataRequested, this, &WebEnginePart::slotSaveFormDataRequested); disconnect(m_wallet, &WebEngineWallet::fillFormRequestCompleted, this, &WebEnginePart::slotFillFormRequestCompleted); disconnect(m_wallet, &WebEngineWallet::walletClosed, this, &WebEnginePart::slotWalletClosed); } m_wallet = wallet; if (m_wallet) { connect(m_wallet, &WebEngineWallet::saveFormDataRequested, this, &WebEnginePart::slotSaveFormDataRequested); connect(m_wallet, &WebEngineWallet::fillFormRequestCompleted, this, &WebEnginePart::slotFillFormRequestCompleted); connect(m_wallet, &WebEngineWallet::walletClosed, this, &WebEnginePart::slotWalletClosed); } } void WebEnginePart::attemptInstallKIOSchemeHandler(const QUrl& url) { if (KProtocolManager::defaultMimetype(url) == "text/html") { // man:, info:, etc. QWebEngineProfile *prof = QWebEngineProfile::defaultProfile(); QByteArray scheme = url.scheme().toUtf8(); if (!prof->urlSchemeHandler(scheme)) { prof->installUrlSchemeHandler(scheme, new WebEnginePartKIOHandler(prof)); } } } bool WebEnginePart::openUrl(const QUrl &_u) { QUrl u (_u); // Ignore empty requests... if (u.isEmpty()) return false; // If the URL given is a supported local protocol, e.g. "bookmark" but lacks // a path component, we set the path to "/" here so that the security context // will properly allow access to local resources. if (u.host().isEmpty() && u.path().isEmpty() && KProtocolInfo::protocolClass(u.scheme()) == QL1S(":local")) { u.setPath(QL1S("/")); } // Do not emit update history when url is typed in since the host // should handle that automatically itself. m_emitOpenUrlNotify = false; // Pointer to the page object... WebEnginePage* p = page(); Q_ASSERT(p); KParts::BrowserArguments bargs (m_browserExtension->browserArguments()); KParts::OpenUrlArguments args (arguments()); if (!Utils::isBlankUrl(u)) { // Get the SSL information sent, if any... if (args.metaData().contains(QL1S("ssl_in_use"))) { WebSslInfo sslInfo; sslInfo.restoreFrom(KIO::MetaData(args.metaData()).toVariant()); sslInfo.setUrl(u); p->setSslInfo(sslInfo); } } attemptInstallKIOSchemeHandler(u); // Set URL in KParts before emitting started; konq plugins rely on that. setUrl(u); m_doLoadFinishedActions = true; page()->setLoadUrlCalledByPart(u); m_webView->loadUrl(u, args, bargs); return true; } bool WebEnginePart::closeUrl() { m_webView->triggerPageAction(QWebEnginePage::Stop); m_webView->stop(); return true; } QWebEngineView* WebEnginePart::view() const { return m_webView; } bool WebEnginePart::isModified() const { //return m_webView->isModified(); return false; } void WebEnginePart::guiActivateEvent(KParts::GUIActivateEvent *event) { if (event && event->activated() && m_webView) { emit setWindowCaption(m_webView->title()); } } bool WebEnginePart::openFile() { // never reached return false; } /// slots... void WebEnginePart::slotLoadStarted() { if(!Utils::isBlankUrl(url())) { emit started(nullptr); } updateActions(); // If "NoEmitOpenUrlNotification" property is set to true, do not // emit the open url notification. Property is set by this part's // extension to prevent openUrl notification being sent when // handling history navigation requests (back/forward). const bool doNotEmitOpenUrl = property("NoEmitOpenUrlNotification").toBool(); if (doNotEmitOpenUrl) { setProperty("NoEmitOpenUrlNotification", QVariant()); } else { if (m_emitOpenUrlNotify) { emit m_browserExtension->openUrlNotify(); } } // Unless we go via openUrl again, the next time we are here we emit (e.g. after clicking on a link) m_emitOpenUrlNotify = true; } void WebEnginePart::slotLoadFinished (bool ok) { if (!ok || !m_doLoadFinishedActions) return; slotWalletClosed(); m_doLoadFinishedActions = false; // If the document contains no tag, then set it to the current url. if (m_webView->title().trimmed().isEmpty()) { // If the document title is empty, then set it to the current url const QUrl url (m_webView->url()); const QString caption (url.toString((QUrl::RemoveQuery|QUrl::RemoveFragment))); emit setWindowCaption(caption); // The urlChanged signal is emitted if and only if the main frame // receives the title of the page so we manually invoke the slot as a // work around here for pages that do not contain it, such as text // documents... slotUrlChanged(url); } if (!Utils::isBlankUrl(url())) { m_hasCachedFormData = false; if (WebEngineSettings::self()->isNonPasswordStorableSite(url().host())) { addWalletStatusBarIcon(); } else { // Attempt to fill the web form... WebEngineWallet *wallet = page() ? page()->wallet() : nullptr; if (wallet){ wallet->fillFormData(page()); } } } bool pending = false; // QWebFrame* frame = (page() ? page()->currentFrame() : 0); // if (ok && // frame == page()->mainFrame() && // !frame->findFirstElement(QL1S("head>meta[http-equiv=refresh]")).isNull()) { // if (WebEngineSettings::self()->autoPageRefresh()) { // pending = true; // } else { // frame->page()->triggerAction(QWebEnginePage::Stop); // } // } emit completed ((ok && pending)); updateActions(); } void WebEnginePart::slotLoadAborted(const QUrl & url) { closeUrl(); m_doLoadFinishedActions = false; if (url.isValid()) emit m_browserExtension->openUrlRequest(url); else setUrl(m_webView->url()); } void WebEnginePart::slotUrlChanged(const QUrl& url) { // Ignore if empty if (url.isEmpty()) return; // Ignore if error url if (url.scheme() == QL1S("error")) return; const QUrl u (url); // Ignore if url has not changed! if (this->url() == u) return; m_doLoadFinishedActions = true; setUrl(u); // Do not update the location bar with about:blank if (!Utils::isBlankUrl(url)) { //kDebug() << "Setting location bar to" << u.prettyUrl() << "current URL:" << this->url(); emit m_browserExtension->setLocationBarUrl(u.toDisplayString()); } } void WebEnginePart::slotShowSecurity() { if (!page()) return; const WebSslInfo& sslInfo = page()->sslInfo(); if (!sslInfo.isValid()) { KMessageBox::information(nullptr, i18n("The SSL information for this site " "appears to be corrupt."), i18nc("Secure Sockets Layer", "SSL")); return; } KSslInfoDialog *dlg = new KSslInfoDialog (widget()); dlg->setSslInfo(sslInfo.certificateChain(), sslInfo.peerAddress().toString(), url().host(), sslInfo.protocol(), sslInfo.ciphers(), sslInfo.usedChiperBits(), sslInfo.supportedChiperBits(), KSslInfoDialog::errorsFromString(sslInfo.certificateErrors())); dlg->open(); } #if 0 void WebEnginePart::slotSaveFrameState(QWebFrame *frame, QWebHistoryItem *item) { if (!frame || !item) { return; } // Handle actions that apply only to the mainframe... if (frame == view()->page()->mainFrame()) { } // For some reason, QtWebEngine PORTING_TODO does not restore scroll position when // QWebHistory is restored from persistent storage. Therefore, we // preserve that information and restore it as needed. See // slotRestoreFrameState. const QPoint scrollPos (frame->scrollPosition()); if (!scrollPos.isNull()) { // kDebug() << "Saving scroll position:" << scrollPos; item->setUserData(scrollPos); } } #endif #if 0 void WebEnginePart::slotRestoreFrameState(QWebFrame *frame) { QWebEnginePage* page = (frame ? frame->page() : 0); QWebHistory* history = (page ? page->history() : 0); // No history item... if (!history || history->count() < 1) return; QWebHistoryItem currentHistoryItem (history->currentItem()); // Update the scroll position if needed. See comment in slotSaveFrameState above. if (frame->baseUrl().resolved(frame->url()) == currentHistoryItem.url()) { const QPoint currentPos (frame->scrollPosition()); const QPoint desiredPos (currentHistoryItem.userData().toPoint()); if (currentPos.isNull() && !desiredPos.isNull()) { frame->setScrollPosition(desiredPos); } } } #endif void WebEnginePart::slotLinkHovered(const QString& _link) { QString message; if (_link.isEmpty()) { message = QL1S(""); emit m_browserExtension->mouseOverInfo(KFileItem()); } else { QUrl linkUrl (_link); const QString scheme = linkUrl.scheme(); // Protect the user against URL spoofing! linkUrl.setUserName(QString()); const QString link (linkUrl.toString()); if (QString::compare(scheme, QL1S("mailto"), Qt::CaseInsensitive) == 0) { message += i18nc("status bar text when hovering email links; looks like \"Email: xy@kde.org - CC: z@kde.org -BCC: x@kde.org - Subject: Hi translator\"", "Email: "); // Workaround: for QUrl's parsing deficiencies of "mailto:foo@bar.com". if (!linkUrl.hasQuery()) linkUrl = QUrl(scheme + '?' + linkUrl.path()); QMap<QString, QStringList> fields; QUrlQuery query(linkUrl); QList<QPair<QString, QString> > queryItems = query.queryItems(); const int count = queryItems.count(); for(int i = 0; i < count; ++i) { const QPair<QString, QString> queryItem (queryItems.at(i)); //kDebug() << "query: " << queryItem.first << queryItem.second; if (queryItem.first.contains(QL1C('@')) && queryItem.second.isEmpty()) fields[QStringLiteral("to")] << queryItem.first; if (QString::compare(queryItem.first, QL1S("to"), Qt::CaseInsensitive) == 0) fields[QStringLiteral("to")] << queryItem.second; if (QString::compare(queryItem.first, QL1S("cc"), Qt::CaseInsensitive) == 0) fields[QStringLiteral("cc")] << queryItem.second; if (QString::compare(queryItem.first, QL1S("bcc"), Qt::CaseInsensitive) == 0) fields[QStringLiteral("bcc")] << queryItem.second; if (QString::compare(queryItem.first, QL1S("subject"), Qt::CaseInsensitive) == 0) fields[QStringLiteral("subject")] << queryItem.second; } if (fields.contains(QL1S("to"))) message += fields.value(QL1S("to")).join(QL1S(", ")); if (fields.contains(QL1S("cc"))) message += i18nc("status bar text when hovering email links; looks like \"Email: xy@kde.org - CC: z@kde.org -BCC: x@kde.org - Subject: Hi translator\"", " - CC: ") + fields.value(QL1S("cc")).join(QL1S(", ")); if (fields.contains(QL1S("bcc"))) message += i18nc("status bar text when hovering email links; looks like \"Email: xy@kde.org - CC: z@kde.org -BCC: x@kde.org - Subject: Hi translator\"", " - BCC: ") + fields.value(QL1S("bcc")).join(QL1S(", ")); if (fields.contains(QL1S("subject"))) message += i18nc("status bar text when hovering email links; looks like \"Email: xy@kde.org - CC: z@kde.org -BCC: x@kde.org - Subject: Hi translator\"", " - Subject: ") + fields.value(QL1S("subject")).join(QL1S(" ")); } else if (scheme == QL1S("javascript")) { message = KStringHandler::rsqueeze(link, 150); if (link.startsWith(QL1S("javascript:window.open"))) message += i18n(" (In new window)"); } else { message = link; #if 0 QWebFrame* frame = page() ? page()->currentFrame() : 0; if (frame) { QWebHitTestResult result = frame->hitTestContent(page()->view()->mapFromGlobal(QCursor::pos())); QWebFrame* target = result.linkTargetFrame(); if (frame->parentFrame() && target == frame->parentFrame()) { message += i18n(" (In parent frame)"); } else if (!target || target != frame) { message += i18n(" (In new window)"); } } #endif KFileItem item (linkUrl, QString(), KFileItem::Unknown); emit m_browserExtension->mouseOverInfo(item); } } emit setStatusBarText(message); } void WebEnginePart::slotSearchForText(const QString &text, bool backward) { QWebEnginePage::FindFlags flags; // = QWebEnginePage::FindWrapsAroundDocument; if (backward) flags |= QWebEnginePage::FindBackward; if (m_searchBar->caseSensitive()) flags |= QWebEnginePage::FindCaseSensitively; //kDebug() << "search for text:" << text << ", backward ?" << backward; page()->findText(text, flags, [this](bool found) { m_searchBar->setFoundMatch(found); }); } void WebEnginePart::slotShowSearchBar() { if (!m_searchBar) { // Create the search bar... m_searchBar = new SearchBar(widget()); connect(m_searchBar, SIGNAL(searchTextChanged(QString,bool)), this, SLOT(slotSearchForText(QString,bool))); actionCollection()->addAction(KStandardAction::FindNext, QStringLiteral("findnext"), m_searchBar, SLOT(findNext())); actionCollection()->addAction(KStandardAction::FindPrev, QStringLiteral("findprev"), m_searchBar, SLOT(findPrevious())); QBoxLayout* lay = qobject_cast<QBoxLayout*>(widget()->layout()); if (lay) { lay->addWidget(m_searchBar); } } const QString text = m_webView->selectedText(); m_searchBar->setSearchText(text.left(150)); } void WebEnginePart::slotLinkMiddleOrCtrlClicked(const QUrl& linkUrl) { emit m_browserExtension->createNewWindow(linkUrl); } void WebEnginePart::slotSelectionClipboardUrlPasted(const QUrl& selectedUrl, const QString& searchText) { if (!WebEngineSettings::self()->isOpenMiddleClickEnabled()) return; if (!searchText.isEmpty() && KMessageBox::questionYesNo(m_webView, i18n("<qt>Do you want to search for <b>%1</b>?</qt>", searchText), i18n("Internet Search"), KGuiItem(i18n("&Search"), QStringLiteral("edit-find")), KStandardGuiItem::cancel(), QStringLiteral("MiddleClickSearch")) != KMessageBox::Yes) return; emit m_browserExtension->openUrlRequest(selectedUrl); } void WebEnginePart::slotWalletClosed() { if (!m_statusBarWalletLabel) return; m_statusBarExtension->removeStatusBarItem(m_statusBarWalletLabel); delete m_statusBarWalletLabel; m_statusBarWalletLabel = nullptr; m_hasCachedFormData = false; } void WebEnginePart::slotShowWalletMenu() { QMenu *menu = new QMenu(nullptr); if (m_webView && WebEngineSettings::self()->isNonPasswordStorableSite(m_webView->url().host())) menu->addAction(i18n("&Allow password caching for this site"), this, SLOT(slotDeleteNonPasswordStorableSite())); if (m_hasCachedFormData) menu->addAction(i18n("Remove all cached passwords for this site"), this, SLOT(slotRemoveCachedPasswords())); menu->addSeparator(); menu->addAction(i18n("&Close Wallet"), this, SLOT(slotWalletClosed())); KAcceleratorManager::manage(menu); menu->popup(QCursor::pos()); } void WebEnginePart::slotLaunchWalletManager() { QDBusInterface r(QStringLiteral("org.kde.kwalletmanager"), QStringLiteral("/kwalletmanager/MainWindow_1")); if (r.isValid()) r.call(QDBus::NoBlock, QStringLiteral("show")); else KToolInvocation::startServiceByDesktopName(QStringLiteral("kwalletmanager_show")); } void WebEnginePart::slotDeleteNonPasswordStorableSite() { if (m_webView) WebEngineSettings::self()->removeNonPasswordStorableSite(m_webView->url().host()); } void WebEnginePart::slotRemoveCachedPasswords() { if (!page() || !page()->wallet()) return; page()->wallet()->removeFormData(page()); m_hasCachedFormData = false; } void WebEnginePart::slotSetTextEncoding(QTextCodec * codec) { // FIXME: The code below that sets the text encoding has been reported not to work. if (!page()) return; QWebEngineSettings *localSettings = page()->settings(); if (!localSettings) return; qCDebug(WEBENGINEPART_LOG) << "Encoding: new=>" << localSettings->defaultTextEncoding() << ", old=>" << codec->name(); localSettings->setDefaultTextEncoding(codec->name()); page()->triggerAction(QWebEnginePage::Reload); } void WebEnginePart::slotSetStatusBarText(const QString& text) { const QString host (page() ? page()->url().host() : QString()); if (WebEngineSettings::self()->windowStatusPolicy(host) == KParts::HtmlSettingsInterface::JSWindowStatusAllow) emit setStatusBarText(text); } void WebEnginePart::slotWindowCloseRequested() { emit m_browserExtension->requestFocus(this); #if 0 if (KMessageBox::questionYesNo(m_webView, i18n("Close window?"), i18n("Confirmation Required"), KStandardGuiItem::close(), KStandardGuiItem::cancel()) != KMessageBox::Yes) return; #endif this->deleteLater(); } void WebEnginePart::slotShowFeaturePermissionBar(QWebEnginePage::Feature feature) { // FIXME: Allow multiple concurrent feature permission requests. if (m_featurePermissionBar && m_featurePermissionBar->isVisible()) return; if (!m_featurePermissionBar) { m_featurePermissionBar = new FeaturePermissionBar(widget()); connect(m_featurePermissionBar, SIGNAL(permissionGranted(QWebEnginePage::Feature)), this, SLOT(slotFeaturePermissionGranted(QWebEnginePage::Feature))); connect(m_featurePermissionBar, SIGNAL(permissionDenied(QWebEnginePage::Feature)), this, SLOT(slotFeaturePermissionDenied(QWebEnginePage::Feature))); connect(m_passwordBar, SIGNAL(done()), this, SLOT(slotSaveFormDataDone())); QBoxLayout* lay = qobject_cast<QBoxLayout*>(widget()->layout()); if (lay) lay->insertWidget(0, m_featurePermissionBar); } m_featurePermissionBar->setFeature(feature); // m_featurePermissionBar->setText(i18n("<html>Do you want to grant the site <b>%1</b> " // "access to information about your current physical location?", // url.host())); m_featurePermissionBar->setText(i18n("<html>Do you want to grant the site " "access to information about your current physical location?")); m_featurePermissionBar->animatedShow(); } void WebEnginePart::slotFeaturePermissionGranted(QWebEnginePage::Feature feature) { Q_ASSERT(m_featurePermissionBar && m_featurePermissionBar->feature() == feature); page()->setFeaturePermission(page()->url(), feature, QWebEnginePage::PermissionGrantedByUser); } void WebEnginePart::slotFeaturePermissionDenied(QWebEnginePage::Feature feature) { Q_ASSERT(m_featurePermissionBar && m_featurePermissionBar->feature() == feature); page()->setFeaturePermission(page()->url(), feature, QWebEnginePage::PermissionDeniedByUser); } void WebEnginePart::slotSaveFormDataRequested (const QString& key, const QUrl& url) { if (WebEngineSettings::self()->isNonPasswordStorableSite(url.host())) return; if (!WebEngineSettings::self()->askToSaveSitePassword()) return; if (m_passwordBar && m_passwordBar->isVisible()) return; if (!m_passwordBar) { m_passwordBar = new PasswordBar(widget()); if (!m_wallet) { qCWarning(WEBENGINEPART_LOG) << "No m_wallet instance found! This should never happen!"; return; } connect(m_passwordBar, SIGNAL(saveFormDataAccepted(QString)), m_wallet, SLOT(acceptSaveFormDataRequest(QString))); connect(m_passwordBar, SIGNAL(saveFormDataRejected(QString)), m_wallet, SLOT(rejectSaveFormDataRequest(QString))); connect(m_passwordBar, SIGNAL(done()), this, SLOT(slotSaveFormDataDone())); } Q_ASSERT(m_passwordBar); m_passwordBar->setUrl(url); m_passwordBar->setRequestKey(key); m_passwordBar->setText(i18n("<html>Do you want %1 to remember the login " "information for <b>%2</b>?</html>", QCoreApplication::applicationName(), url.host())); QBoxLayout* lay = qobject_cast<QBoxLayout*>(widget()->layout()); if (lay) lay->insertWidget(0, m_passwordBar); m_passwordBar->animatedShow(); } void WebEnginePart::slotSaveFormDataDone() { if (!m_passwordBar) return; QBoxLayout* lay = qobject_cast<QBoxLayout*>(widget()->layout()); if (lay) lay->removeWidget(m_passwordBar); } void WebEnginePart::addWalletStatusBarIcon () { if (m_statusBarWalletLabel) { m_statusBarExtension->removeStatusBarItem(m_statusBarWalletLabel); } else { m_statusBarWalletLabel = new KUrlLabel(m_statusBarExtension->statusBar()); m_statusBarWalletLabel->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum)); m_statusBarWalletLabel->setUseCursor(false); m_statusBarWalletLabel->setPixmap(QIcon::fromTheme(QStringLiteral("wallet-open")).pixmap(QSize(16,16))); connect(m_statusBarWalletLabel, SIGNAL(leftClickedUrl()), SLOT(slotLaunchWalletManager())); connect(m_statusBarWalletLabel, SIGNAL(rightClickedUrl()), SLOT(slotShowWalletMenu())); } m_statusBarExtension->addStatusBarItem(m_statusBarWalletLabel, 0, false); } void WebEnginePart::slotFillFormRequestCompleted (bool ok) { if ((m_hasCachedFormData = ok)) addWalletStatusBarIcon(); } diff --git a/webenginepart/src/webenginepart.h b/webenginepart/src/webenginepart.h index 25177eaad..f0ae8138e 100644 --- a/webenginepart/src/webenginepart.h +++ b/webenginepart/src/webenginepart.h @@ -1,174 +1,175 @@ /* * This file is part of the KDE project. * * Copyright (C) 2007 Trolltech ASA * Copyright (C) 2008 Urs Wolfer <uwolfer @ kde.org> * Copyright (C) 2009 Dawit Alemayehu <adawit@kde.org> * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by the * Free Software Foundation; either version 2.1 of the License, or (at your * option) any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. * */ #ifndef WEBENGINEPART_H #define WEBENGINEPART_H #include "kwebenginepartlib_export.h" #include <QWebEnginePage> #include <KParts/ReadOnlyPart> #include <QUrl> namespace KParts { class BrowserExtension; class StatusBarExtension; } class QWebEngineView; class WebEngineView; class WebEnginePage; class SearchBar; class PasswordBar; class FeaturePermissionBar; class KUrlLabel; class WebEngineBrowserExtension; class WebEngineWallet; class WebEngineErrorSchemeHandler; /** * A KPart wrapper for the QtWebEngine's browser rendering engine. * * This class attempts to provide the same type of integration into KPart * plugin applications, such as Konqueror, in much the same way as KHTML. * * Unlink the KHTML part however, access into the internals of the rendering * engine are provided through existing QtWebEngine class ; @see QWebEngineView. * */ class KWEBENGINEPARTLIB_EXPORT WebEnginePart : public KParts::ReadOnlyPart { Q_OBJECT Q_PROPERTY( bool modified READ isModified ) public: explicit WebEnginePart(QWidget* parentWidget = nullptr, QObject* parent = nullptr, const QByteArray& cachedHistory = QByteArray(), const QStringList& = QStringList()); ~WebEnginePart() override; /** * Re-implemented for internal reasons. API remains unaffected. * * @see KParts::ReadOnlyPart::openUrl */ bool openUrl(const QUrl &) override; /** * Re-implemented for internal reasons. API remains unaffected. * * @see KParts::ReadOnlyPart::closeUrl */ bool closeUrl() override; /** * Returns a pointer to the render widget used to display a web page. * * @see QWebEngineView. */ QWebEngineView *view() const; /** * Checks whether the page contains unsubmitted form changes. * * @return @p true if form changes exist. */ bool isModified() const; /** * Connects the appropriate signals from the given page to the slots * in this class. */ void connectWebEnginePageSignals(WebEnginePage* page); void slotShowFeaturePermissionBar(QWebEnginePage::Feature); void setWallet(WebEngineWallet* wallet); protected: /** * Re-implemented for internal reasons. API remains unaffected. * * @see KParts::ReadOnlyPart::guiActivateEvent */ void guiActivateEvent(KParts::GUIActivateEvent *) override; /** * Re-implemented for internal reasons. API remains unaffected. * * @see KParts::ReadOnlyPart::openFile */ bool openFile() override; private Q_SLOTS: void slotShowSecurity(); void slotShowSearchBar(); void slotLoadStarted(); void slotLoadAborted(const QUrl &); void slotLoadFinished(bool); void slotSearchForText(const QString &text, bool backward); void slotLinkHovered(const QString &); //void slotSaveFrameState(QWebFrame *frame, QWebHistoryItem *item); //void slotRestoreFrameState(QWebFrame *frame); void slotLinkMiddleOrCtrlClicked(const QUrl&); void slotSelectionClipboardUrlPasted(const QUrl&, const QString&); void slotUrlChanged(const QUrl &); void slotWalletClosed(); void slotShowWalletMenu(); void slotLaunchWalletManager(); void slotDeleteNonPasswordStorableSite(); void slotRemoveCachedPasswords(); void slotSetTextEncoding(QTextCodec*); void slotSetStatusBarText(const QString& text); void slotWindowCloseRequested(); void slotSaveFormDataRequested(const QString &, const QUrl &); void slotSaveFormDataDone(); void slotFillFormRequestCompleted(bool); void slotFeaturePermissionGranted(QWebEnginePage::Feature); void slotFeaturePermissionDenied(QWebEnginePage::Feature); private: WebEnginePage* page(); const WebEnginePage* page() const; + static void initWebEngineUrlSchemes(); void attemptInstallKIOSchemeHandler(const QUrl &url); void initActions(); void updateActions(); void addWalletStatusBarIcon(); bool m_emitOpenUrlNotify; bool m_hasCachedFormData; bool m_doLoadFinishedActions; KUrlLabel* m_statusBarWalletLabel; SearchBar* m_searchBar; PasswordBar* m_passwordBar; FeaturePermissionBar* m_featurePermissionBar; WebEngineBrowserExtension* m_browserExtension; KParts::StatusBarExtension* m_statusBarExtension; WebEngineView* m_webView; WebEngineWallet* m_wallet; }; #endif // WEBENGINEPART_H diff --git a/webenginepart/src/webenginepartkiohandler.cpp b/webenginepart/src/webenginepartkiohandler.cpp index 6463a3417..8ea049db9 100644 --- a/webenginepart/src/webenginepartkiohandler.cpp +++ b/webenginepart/src/webenginepartkiohandler.cpp @@ -1,108 +1,121 @@ /* * This file is part of the KDE project. * * Copyright (C) 2018 Stefano Crocco <stefano.crocco@alice.it> * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by the * Free Software Foundation; either version 2.1 of the License, or (at your * option) any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. * */ #include "webenginepartkiohandler.h" + +#ifndef USE_QWEBENGINE_URL_SCHEME #include "webengineparthtmlembedder.h" +#endif #include <QMimeDatabase> #include <QWebEngineUrlRequestJob> #include <QBuffer> +#include <QtWebEngine/QtWebEngineVersion> #include <KIO/StoredTransferJob> void WebEnginePartKIOHandler::requestStarted(QWebEngineUrlRequestJob *req) { m_queuedRequests << RequestJobPointer(req); processNextRequest(); } WebEnginePartKIOHandler::WebEnginePartKIOHandler(QObject* parent): - QWebEngineUrlSchemeHandler(parent), m_embedder(nullptr) + QWebEngineUrlSchemeHandler(parent) +#ifndef USE_QWEBENGINE_URL_SCHEME + , m_embedder(nullptr) +#endif { connect(this, &WebEnginePartKIOHandler::ready, this, &WebEnginePartKIOHandler::sendReply); } void WebEnginePartKIOHandler::sendReply() { if (m_currentRequest) { if (isSuccessful()) { QBuffer *buf = new QBuffer; buf->open(QBuffer::ReadWrite); buf->write(m_data); buf->seek(0); connect(buf, &QIODevice::aboutToClose, buf, &QObject::deleteLater); m_currentRequest->reply(m_mimeType.name().toUtf8(), buf); } else { m_currentRequest->fail(QWebEngineUrlRequestJob::UrlInvalid); } m_currentRequest.clear(); } processNextRequest(); } void WebEnginePartKIOHandler::processNextRequest() { if (m_currentRequest) { return; } while (!m_currentRequest && !m_queuedRequests.isEmpty()) { m_currentRequest = m_queuedRequests.takeFirst(); } if (!m_currentRequest) { return; } KIO::StoredTransferJob *job = KIO::storedGet(m_currentRequest ->requestUrl(), KIO::NoReload, KIO::HideProgressInfo); connect(job, &KIO::StoredTransferJob::result, this, [this, job](){kioJobFinished(job);}); } void WebEnginePartKIOHandler::embedderFinished(const QString& html) { m_data = html.toUtf8(); emit ready(); } void WebEnginePartKIOHandler::processSlaveOutput() { +#ifdef USE_QWEBENGINE_URL_SCHEME + emit ready(); +#else if (m_mimeType.inherits("text/html") || m_mimeType.inherits("application/xhtml+xml")) { htmlEmbedder()->startEmbedding(m_data, m_mimeType.name()); } else { emit ready(); } +#endif } void WebEnginePartKIOHandler::kioJobFinished(KIO::StoredTransferJob* job) { m_error = job->error() == 0 ? QWebEngineUrlRequestJob::NoError : QWebEngineUrlRequestJob::RequestFailed; m_errorMessage = isSuccessful() ? job->errorString() : QString(); m_data = job->data(); m_mimeType = QMimeDatabase().mimeTypeForData(m_data); processSlaveOutput(); } +#ifndef USE_QWEBENGINE_URL_SCHEME WebEnginePartHtmlEmbedder * WebEnginePartKIOHandler::htmlEmbedder() { if (!m_embedder) { m_embedder = new WebEnginePartHtmlEmbedder(this); connect(htmlEmbedder(), &WebEnginePartHtmlEmbedder::finished, this, &WebEnginePartKIOHandler::embedderFinished); } return m_embedder; } +#endif diff --git a/webenginepart/src/webenginepartkiohandler.h b/webenginepart/src/webenginepartkiohandler.h index 9b6319d99..11e51d61b 100644 --- a/webenginepart/src/webenginepartkiohandler.h +++ b/webenginepart/src/webenginepartkiohandler.h @@ -1,313 +1,316 @@ /* * This file is part of the KDE project. * * Copyright (C) 2018 Stefano Crocco <stefano.crocco@alice.it> * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by the * Free Software Foundation; either version 2.1 of the License, or (at your * option) any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. * */ #ifndef WEBENGINEPARTKIOHANDLER_H #define WEBENGINEPARTKIOHANDLER_H #include <QWebEngineUrlSchemeHandler> #include <QWebEngineUrlRequestJob> #include <QPointer> #include <QMimeType> namespace KIO { class StoredTransferJob; }; class WebEnginePartHtmlEmbedder; /** * @brief Class which allows QWebEngine to access URLs provided by KIO * * The class assumes that the data can be obtained from KIO using `KIO::storedGet` and * passes it to `QWebEngineUrlRequestJob::reply`. The mime type is determined using - * `QMimeDatabase::mimeTypeForData`.If the data is HTML or XHTML, + * `QMimeDatabase::mimeTypeForData`. If the data is HTML or XHTML, on Qt versions before 5.12 * WebEnginePartHtmlEmbedder is used to embed the contents of local URLs inside the code, * so that it can be displayed by QWebEngine without breaking cross-origin rules. * * This class provides a virtual function, processSlaveOutput() which allows to further process KIO output before * using it as reply. Derived classes can use it to change the data, its mimetype and to set * an error code and error message. * * @internal * * Since requestStarted() can be called multiple times, requests are stored in a list and * processed one by one. This is hidden to derived classes, which only work with the current request. */ class WebEnginePartKIOHandler : public QWebEngineUrlSchemeHandler { Q_OBJECT public: /** * Constructor * * @param parent the parent object */ WebEnginePartKIOHandler(QObject* parent); /** * @brief Override of `QWebEngineUrlSchemeHandler::requestStarted` * * It adds the new request to the list and start processing it if there aren't * other queued requests * * @param req: the request job */ void requestStarted(QWebEngineUrlRequestJob* req) override; protected slots: /** * @brief Slot called in response to the WebEnginePartHtmlEmbedder::finished() signal * * The base class implementation calls setData() with the resulting HTML code, then * emits the ready() signal. You can override this function to do something else with * the HTML code. In this case, most likely you won't want to call the base class version * but just call setData() and emit the ready() when you're done processing the HTML code * * @param html: the HTML code produced by the embedder */ virtual void embedderFinished(const QString &html); protected: /** * @brief Creates and returns the html embedder object * * This is the object used to replace local URLs with their content as `data` URLs. * * The embedder is only created on the first use of this function. When this happens, its * WebEnginePartHtmlEmbedder::finished() signal is connected to the embedderFinished(). If you * don't want this, either disconnect the signal or reimplement embedderFinished() so that it * does nothing, then connect WebEnginePartHtmlEmbedder::finished() with the appropriate slot * yourself * * @return the html embedder */ +#ifndef USE_QWEBENGINE_URL_SCHEME WebEnginePartHtmlEmbedder* htmlEmbedder(); - +#endif /** * @brief The request object * * @return The request object */ inline QPointer<QWebEngineUrlRequestJob> request() const {return m_currentRequest;} /** * @brief The error code to pass to `QWebEngineUrlRequestJob::fail` * * In the base class implementation, this can be either `QWebEngineUrlRequestJob::NoError` or * `QWebEngineUrlRequestJob::RequestFailed`. * * @return the error code to pass to `QWebEngineUrlRequestJob::fail` */ inline QWebEngineUrlRequestJob::Error error() const {return m_error;} /** * @brief Sets the error code passed to `QWebEngineUrlRequestJob::fail` * * Changes the error code. This function can be called by derived class in their * implementation of processSlaveOutput(). It should always be set to `QWebEngineUrlRequestJob::NoError` * if `QWebEngineUrlRequestJob::reply` should be called. * * @see isSuccessful() * * @param error: the new error code */ inline void setError(QWebEngineUrlRequestJob::Error error) {m_error = error;} /** * @brief Whether an error has occurred or not * * @return `true` if error() is `QWebEngineUrlRequestJob::NoError` and false otherwise */ inline bool isSuccessful() const {return m_error == QWebEngineUrlRequestJob::NoError;} /** * @brief The error message * * Currently, this is only used for debugging purpose * * @return The error message given by `KJob::errorString` (or set by derived classes using setErrorMessage()) * or an empty string if KIO produced no error. */ inline QString errorMessage() const {return m_errorMessage;} /** * @brief Changes the error message * * This function can be called by derived classes from their implementation of * processSlaveOutput(). * * @param message the new error message. If error() is not `QWebEngineUrlRequestJob::NoError`, this * should not be empty */ inline void setErrorMessage(const QString &message) {m_errorMessage = message;} /** * @brief The data to pass to `QWebEngineUrlRequestJob::reply` * * This is the data returned by the ioslave, but can be changed in processSlaveOutput() using setData() * * @return The data to pass to QWebEngineUrlRequestJob::reply */ inline QByteArray data() const {return m_data;} /** * @brief Changes the data to pass to `QWebEngineUrlRequestJob::reply` * * This function can be used by derived classes in their implementation of processSlaveOutput(). The base * class implementation calls it for (X)HTML code to embed the content of local files in the code itself. * * @param data: the new data */ inline void setData(const QByteArray& data) {m_data = data;} /** * @brief The mime type of the data * * This value (actually, it's `name`) will be passed to QWebEngineUrlRequestJob::reply * @return The mime type of the data */ inline QMimeType mimeType() const {return m_mimeType;} /** * @brief Changes the mime type of the data * * This function can be used by derived classes in their implementation of processSlaveOutput() * if the mime type according to `QMimeDatabase::mimeTypeForData` is not correct or if they change * the data so that its mimetype changes from what the ioslave returned. */ inline void setMimeType(const QMimeType &mime) {m_mimeType = mime;} /** * @brief Processes the output from the ioslave and replies to the request * * In the base class implementation, if the data is (X)HTML code (according to mimetype()), the * htmlEmbedder() is used to embed the content of local files in the code. In response to * WebEnginePartHtmlEmbedder::finished(), the ready() signal is emitted. For other mimetypes, the * ready() signal is emitted directly from here. * * Derived classes can reimplement this function to do their own processing, of the data produced * by the ioslave. At the beginning of this function, data(), mimetype(), and errorMessage() are * those produced by the ioslave, while error is `QWebEngineUrlRequestJob::NoError` if the ioslave * was successful andd `QWebEngineUrlRequestJob::RequestFailed` if there were errors. Derived classes can * change these values using setData(), setMimeType(), setError() and setErrorMessage(). * * It is important that, after having changed the values as desired, the ready() signal is emitted. */ virtual void processSlaveOutput(); signals: /** * @brief Signal emitted when processing of data produced by the ioslave has finished * * In response to this signal, sendReply() is called, sending the response to the original request */ void ready(); private slots: /** * Slot called when the ioslave finishes * * It reads the data, mimetype and error state from the job and stores it. * * @param job: the finished ioslave job */ void kioJobFinished(KIO::StoredTransferJob *job); /** * @brief Sends a reply to the `QWebEngineUrlRequestJob` * * The reply is sent using the values returned by data(), mimeType(), error() and errorMessage(). * * If error() is something else from `QWebEngineUrlRequestJob::NoError`, this function calls * `QWebEngineUrlRequestJob::fail` with the corresponding error code, otherwise it calls * `QWebEngineUrlRequestJob::reply()` passing data() and mimetype() as arguments. * * This slot is called in response to the ready() signal */ void sendReply(); private: /** * @brief Starts processing the next `QWebEngineUrlRequestJob` in queue * * @internal * This function removes the first (valid) request from #m_queuedRequests, * stores it in #m_currentRequest and starts a `KIO::storedGet` for its URL */ void processNextRequest(); using RequestJobPointer = QPointer<QWebEngineUrlRequestJob>; /** * @brief A list of requests to be processed */ QList<RequestJobPointer> m_queuedRequests; /** * @brief The request currently being processed */ RequestJobPointer m_currentRequest; /** * @brief The error code to use for `QWebEngineUrlRequestJob::fail` for the current request * * This is valid only after the call to kioJobFinished() */ QWebEngineUrlRequestJob::Error m_error; /** * @brief The error message produced by KIO for the current request * * Currently, this is only used for debugging purposes * * This is valid only after the call to kioJobFinished() */ QString m_errorMessage; /** * @brief The data produced by KIO for the current request * * This is valid only after the call to kioJobFinished() */ QByteArray m_data; /** * @brief The mime type of the data produced by KIO for the current request * * This is valid only after the call to kioJobFinished() */ QMimeType m_mimeType; +#ifndef USE_QWEBENGINE_URL_SCHEME /** * @brief The object to use for embedding `file` URLs in (X)HTML code * * This empty until the first time htmlEmbedder() is called */ WebEnginePartHtmlEmbedder *m_embedder; +#endif }; #endif // WEBENGINEPARTKIOHANDLER_H