diff --git a/webenginepart/src/CMakeLists.txt b/webenginepart/src/CMakeLists.txt index bfac06715..fffce883b 100644 --- a/webenginepart/src/CMakeLists.txt +++ b/webenginepart/src/CMakeLists.txt @@ -1,46 +1,50 @@ +find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Wallet) + 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 settings/webenginesettings.cpp settings/webengine_filter.cpp ui/searchbar.cpp ui/passwordbar.cpp ui/featurepermissionbar.cpp ) qt5_wrap_ui(kwebenginepartlib_LIB_SRCS ui/searchbar.ui ) add_library(kwebenginepartlib ${kwebenginepartlib_LIB_SRCS}) generate_export_header(kwebenginepartlib) target_link_libraries(kwebenginepartlib Qt5::Core Qt5::DBus Qt5::Gui Qt5::Widgets Qt5::WebEngineWidgets Qt5::PrintSupport KF5::Parts KF5::SonnetCore KF5::WindowSystem # for KUserTimestamp + KF5::Wallet ) target_include_directories(kwebenginepartlib PUBLIC "$" ) install(TARGETS kwebenginepartlib ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) add_library(webenginepart MODULE webenginepartfactory.cpp) 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) diff --git a/webenginepart/src/webenginepage.cpp b/webenginepart/src/webenginepage.cpp index 212e1895c..615e8f773 100644 --- a/webenginepart/src/webenginepage.cpp +++ b/webenginepart/src/webenginepage.cpp @@ -1,924 +1,928 @@ /* * This file is part of the KDE project. * * Copyright (C) 2008 Dirk Mueller * Copyright (C) 2008 - 2010 Urs Wolfer * Copyright (C) 2009 Dawit Alemayehu * * 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 "webenginepage.h" #include "webenginepart.h" #include "websslinfo.h" #include "webengineview.h" #include "settings/webenginesettings.h" #include "webenginepartdownloadmanager.h" +#include "webenginewallet.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if KIO_VERSION >= QT_VERSION_CHECK(5, 30, 0) #include #endif #include #include #include #include #include #include #include // Qt::escape #include #include #include #include #include #include //#include #include "utils.h" WebEnginePage::WebEnginePage(WebEnginePart *part, QWidget *parent) : QWebEnginePage(parent), m_kioErrorCode(0), m_ignoreError(false), m_part(part), - m_passwdServerClient(new KPasswdServerClient) + m_passwdServerClient(new KPasswdServerClient), + m_wallet(Q_NULLPTR) { if (view()) WebEngineSettings::self()->computeFontSizes(view()->logicalDpiY()); //setForwardUnsupportedContent(true); connect(this, &QWebEnginePage::geometryChangeRequested, this, &WebEnginePage::slotGeometryChangeRequested); // connect(this, SIGNAL(unsupportedContent(QNetworkReply*)), // this, SLOT(slotUnsupportedContent(QNetworkReply*))); connect(this, &QWebEnginePage::featurePermissionRequested, this, &WebEnginePage::slotFeaturePermissionRequested); connect(this, &QWebEnginePage::loadFinished, this, &WebEnginePage::slotLoadFinished); connect(this, &QWebEnginePage::authenticationRequired, this, &WebEnginePage::slotAuthenticationRequired); if(!this->profile()->httpUserAgent().contains(QLatin1String("Konqueror"))) { this->profile()->setHttpUserAgent(this->profile()->httpUserAgent() + " Konqueror (WebEnginePart)"); } WebEnginePartDownloadManager::instance()->addPage(this); + m_wallet = new WebEngineWallet(this, parent ? parent->window()->winId() : 0); } WebEnginePage::~WebEnginePage() { //kDebug() << this; } const WebSslInfo& WebEnginePage::sslInfo() const { return m_sslInfo; } void WebEnginePage::setSslInfo (const WebSslInfo& info) { m_sslInfo = info; } static void checkForDownloadManager(QWidget* widget, QString& cmd) { cmd.clear(); KConfigGroup cfg (KSharedConfig::openConfig(QStringLiteral("konquerorrc"), KConfig::NoGlobals), "HTML Settings"); const QString fileName (cfg.readPathEntry("DownloadManager", QString())); if (fileName.isEmpty()) return; const QString exeName = QStandardPaths::findExecutable(fileName); if (exeName.isEmpty()) { KMessageBox::detailedSorry(widget, i18n("The download manager (%1) could not be found in your installation.", fileName), i18n("Try to reinstall it and make sure that it is available in $PATH. \n\nThe integration will be disabled.")); cfg.writePathEntry("DownloadManager", QString()); cfg.sync(); return; } cmd = exeName; } void WebEnginePage::download(const QUrl& url, bool newWindow) { // Integration with a download manager... if (!url.isLocalFile()) { QString managerExe; checkForDownloadManager(view(), managerExe); if (!managerExe.isEmpty()) { //kDebug() << "Calling command" << cmd; KRun::runCommand((managerExe + QLatin1Char(' ') + KShell::quoteArg(url.url())), view()); return; } } KParts::BrowserArguments bArgs; bArgs.setForcesNewWindow(newWindow); emit part()->browserExtension()->openUrlRequest(url, KParts::OpenUrlArguments(), bArgs); } QWebEnginePage *WebEnginePage::createWindow(WebWindowType type) { //qDebug() << "window type:" << type; // Crete an instance of NewWindowPage class to capture all the // information we need to create a new window. See documentation of // the class for more information... NewWindowPage* page = new NewWindowPage(type, part()); return page; } // Returns true if the scheme and domain of the two urls match... static bool domainSchemeMatch(const QUrl& u1, const QUrl& u2) { if (u1.scheme() != u2.scheme()) return false; QStringList u1List = u1.host().split(QL1C('.'), QString::SkipEmptyParts); QStringList u2List = u2.host().split(QL1C('.'), QString::SkipEmptyParts); if (qMin(u1List.count(), u2List.count()) < 2) return false; // better safe than sorry... while (u1List.count() > 2) u1List.removeFirst(); while (u2List.count() > 2) u2List.removeFirst(); return (u1List == u2List); } bool WebEnginePage::acceptNavigationRequest(const QUrl& url, NavigationType type, bool isMainFrame) { qDebug() << url << "type=" << type; QUrl reqUrl(url); // Handle "mailto:" url here... if (handleMailToUrl(reqUrl, type)) return false; const bool isTypedUrl = property("NavigationTypeUrlEntered").toBool(); /* NOTE: We use a dynamic QObject property called "NavigationTypeUrlEntered" to distinguish between requests generated by user entering a url vs those that were generated programatically through javascript (AJAX requests). */ if (isMainFrame && isTypedUrl) setProperty("NavigationTypeUrlEntered", QVariant()); // inPage requests are those generarted within the current page through // link clicks, javascript queries, and button clicks (form submission). bool inPageRequest = true; switch (type) { case QWebEnginePage::NavigationTypeFormSubmitted: - //if (!checkFormData(request)) - // return false; + if (!checkFormData(url)) + return false; + m_wallet->saveFormData(this); break; #if 0 case QWebEnginePage::NavigationTypeFormResubmitted: if (!checkFormData(request)) return false; if (KMessageBox::warningContinueCancel(view(), i18n("

To display the requested web page again, " "the browser needs to resend information you have " "previously submitted.

" "

If you were shopping online and made a purchase, " "click the Cancel button to prevent a duplicate purchase." "Otherwise, click the Continue button to display the web" "page again.

"), i18n("Resubmit Information")) == KMessageBox::Cancel) { return false; } break; #endif case QWebEnginePage::NavigationTypeBackForward: // If history navigation is locked, ignore all such requests... if (property("HistoryNavigationLocked").toBool()) { setProperty("HistoryNavigationLocked", QVariant()); qDebug() << "Rejected history navigation because 'HistoryNavigationLocked' property is set!"; return false; } //kDebug() << "Navigating to item (" << history()->currentItemIndex() // << "of" << history()->count() << "):" << history()->currentItem().url(); inPageRequest = false; break; case QWebEnginePage::NavigationTypeReload: // setRequestMetaData(QL1S("cache"), QL1S("reload")); inPageRequest = false; break; case QWebEnginePage::NavigationTypeOther: // triggered by javascript qDebug() << "Triggered by javascript"; inPageRequest = !isTypedUrl; break; default: break; } if (inPageRequest) { // if (!checkLinkSecurity(request, type)) // return false; // if (m_sslInfo.isValid()) // setRequestMetaData(QL1S("ssl_was_in_use"), QL1S("TRUE")); } // Honor the enabling/disabling of plugins per host. settings()->setAttribute(QWebEngineSettings::PluginsEnabled, WebEngineSettings::self()->isPluginsEnabled(reqUrl.host())); emit navigationRequested(this, url); return QWebEnginePage::acceptNavigationRequest(url, type, isMainFrame); } #if 0 static int errorCodeFromReply(QNetworkReply* reply) { // First check if there is a KIO error code sent back and use that, // if not attempt to convert QNetworkReply's NetworkError to KIO::Error. QVariant attr = reply->attribute(static_cast(KIO::AccessManager::KioError)); if (attr.isValid() && attr.type() == QVariant::Int) return attr.toInt(); switch (reply->error()) { case QNetworkReply::ConnectionRefusedError: return KIO::ERR_COULD_NOT_CONNECT; case QNetworkReply::HostNotFoundError: return KIO::ERR_UNKNOWN_HOST; case QNetworkReply::TimeoutError: return KIO::ERR_SERVER_TIMEOUT; case QNetworkReply::OperationCanceledError: return KIO::ERR_USER_CANCELED; case QNetworkReply::ProxyNotFoundError: return KIO::ERR_UNKNOWN_PROXY_HOST; case QNetworkReply::ContentAccessDenied: return KIO::ERR_ACCESS_DENIED; case QNetworkReply::ContentOperationNotPermittedError: return KIO::ERR_WRITE_ACCESS_DENIED; case QNetworkReply::ContentNotFoundError: return KIO::ERR_NO_CONTENT; case QNetworkReply::AuthenticationRequiredError: return KIO::ERR_COULD_NOT_AUTHENTICATE; case QNetworkReply::ProtocolUnknownError: return KIO::ERR_UNSUPPORTED_PROTOCOL; case QNetworkReply::ProtocolInvalidOperationError: return KIO::ERR_UNSUPPORTED_ACTION; case QNetworkReply::UnknownNetworkError: return KIO::ERR_UNKNOWN; case QNetworkReply::NoError: default: break; } return 0; } #endif WebEnginePart* WebEnginePage::part() const { return m_part.data(); } void WebEnginePage::setPart(WebEnginePart* part) { m_part = part; } void WebEnginePage::slotLoadFinished(bool ok) { QUrl requestUrl = url(); requestUrl.setUserInfo(QString()); const bool shouldResetSslInfo = (m_sslInfo.isValid() && !domainSchemeMatch(requestUrl, m_sslInfo.url())); #if 0 QWebFrame* frame = qobject_cast(reply->request().originatingObject()); if (!frame) return; const bool isMainFrameRequest = (frame == mainFrame()); #else // PORTING_TODO const bool isMainFrameRequest = true; #endif #if 0 // Only deal with non-redirect responses... const QVariant redirectVar = reply->attribute(QNetworkRequest::RedirectionTargetAttribute); if (isMainFrameRequest && redirectVar.isValid()) { m_sslInfo.restoreFrom(reply->attribute(static_cast(KIO::AccessManager::MetaData)), reply->url(), shouldResetSslInfo); return; } const int errCode = errorCodeFromReply(reply); kDebug() << frame << "is main frame request?" << isMainFrameRequest << requestUrl; #endif if (ok) { if (isMainFrameRequest) { #if 0 m_sslInfo.restoreFrom(reply->attribute(static_cast(KIO::AccessManager::MetaData)), reply->url(), shouldResetSslInfo); #endif setPageJScriptPolicy(url()); } } else { // Handle any error... #if 0 switch (errCode) { case 0: case KIO::ERR_NO_CONTENT: break; case KIO::ERR_ABORTED: case KIO::ERR_USER_CANCELED: // Do nothing if request is cancelled/aborted //kDebug() << "User aborted request!"; m_ignoreError = true; emit loadAborted(QUrl()); return; // Handle the user clicking on a link that refers to a directory // Since KIO cannot automatically convert a GET request to a LISTDIR one. case KIO::ERR_IS_DIRECTORY: m_ignoreError = true; emit loadAborted(reply->url()); return; default: // Make sure the saveFrameStateRequested signal is emitted so // the page can restored properly. if (isMainFrameRequest) emit saveFrameStateRequested(frame, 0); m_ignoreError = (reply->attribute(QNetworkRequest::User).toInt() == QNetworkReply::ContentAccessDenied); m_kioErrorCode = errCode; break; #endif } if (isMainFrameRequest) { const WebEnginePageSecurity security = (m_sslInfo.isValid() ? PageEncrypted : PageUnencrypted); emit m_part->browserExtension()->setPageSecurity(security); } } void WebEnginePage::slotUnsupportedContent(QNetworkReply* reply) { #if 0 //kDebug() << reply->url(); QString mimeType; KIO::MetaData metaData; KIO::AccessManager::putReplyOnHold(reply); QString downloadCmd; checkForDownloadManager(view(), downloadCmd); if (!downloadCmd.isEmpty()) { reply->setProperty("DownloadManagerExe", downloadCmd); } if (QWePage::handleReply(reply, &mimeType, &metaData)) { reply->deleteLater(); if (qobject_cast(this) && isBlankUrl(m_part->url())) { m_part->closeUrl(); if (m_part->arguments().metaData().contains(QL1S("new-window"))) { m_part->widget()->topLevelWidget()->close(); } else { delete m_part; } } return; } //kDebug() << "mimetype=" << mimeType << "metadata:" << metaData; if (reply->request().originatingObject() == this->mainFrame()) { KParts::OpenUrlArguments args; args.setMimeType(mimeType); args.metaData() = metaData; emit m_part->browserExtension()->openUrlRequest(reply->url(), args, KParts::BrowserArguments()); return; } #endif reply->deleteLater(); } void WebEnginePage::slotFeaturePermissionRequested(const QUrl& url, QWebEnginePage::Feature feature) { if (url == this->url()) { part()->slotShowFeaturePermissionBar(feature); return; } switch(feature) { case QWebEnginePage::Notifications: // FIXME: We should have a setting to tell if this is enabled, but so far it is always enabled. setFeaturePermission(url, feature, QWebEnginePage::PermissionGrantedByUser); break; case QWebEnginePage::Geolocation: if (KMessageBox::warningContinueCancel(0, i18n("This site is attempting to " "access information about your " "physical location.\n" "Do you want to allow it access?"), i18n("Network Transmission"), KGuiItem(i18n("Allow access")), KStandardGuiItem::cancel(), QStringLiteral("WarnGeolocation")) == KMessageBox::Cancel) { setFeaturePermission(url, feature, QWebEnginePage::PermissionDeniedByUser); } else { setFeaturePermission(url, feature, QWebEnginePage::PermissionGrantedByUser); } break; default: setFeaturePermission(url, feature, QWebEnginePage::PermissionUnknown); break; } } void WebEnginePage::slotGeometryChangeRequested(const QRect & rect) { const QString host = url().host(); // NOTE: If a new window was created from another window which is in // maximized mode and its width and/or height were not specified at the // time of its creation, which is always the case in QWebEnginePage::createWindow, // then any move operation will seem not to work. That is because the new // window will be in maximized mode where moving it will not be possible... if (WebEngineSettings::self()->windowMovePolicy(host) == KParts::HtmlSettingsInterface::JSWindowMoveAllow && (view()->x() != rect.x() || view()->y() != rect.y())) emit m_part->browserExtension()->moveTopLevelWidget(rect.x(), rect.y()); const int height = rect.height(); const int width = rect.width(); // parts of following code are based on kjs_window.cpp // Security check: within desktop limits and bigger than 100x100 (per spec) if (width < 100 || height < 100) { qWarning() << "Window resize refused, window would be too small (" << width << "," << height << ")"; return; } QRect sg = QApplication::desktop()->screenGeometry(view()); if (width > sg.width() || height > sg.height()) { qWarning() << "Window resize refused, window would be too big (" << width << "," << height << ")"; return; } if (WebEngineSettings::self()->windowResizePolicy(host) == KParts::HtmlSettingsInterface::JSWindowResizeAllow) { //kDebug() << "resizing to " << width << "x" << height; emit m_part->browserExtension()->resizeTopLevelWidget(width, height); } // If the window is out of the desktop, move it up/left // (maybe we should use workarea instead of sg, otherwise the window ends up below kicker) const int right = view()->x() + view()->frameGeometry().width(); const int bottom = view()->y() + view()->frameGeometry().height(); int moveByX = 0, moveByY = 0; if (right > sg.right()) moveByX = - right + sg.right(); // always <0 if (bottom > sg.bottom()) moveByY = - bottom + sg.bottom(); // always <0 if ((moveByX || moveByY) && WebEngineSettings::self()->windowMovePolicy(host) == KParts::HtmlSettingsInterface::JSWindowMoveAllow) emit m_part->browserExtension()->moveTopLevelWidget(view()->x() + moveByX, view()->y() + moveByY); } bool WebEnginePage::checkLinkSecurity(const QNetworkRequest &req, NavigationType type) const { // Check whether the request is authorized or not... if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("redirect"), url(), req.url())) { //kDebug() << "*** Failed security check: base-url=" << mainFrame()->url() << ", dest-url=" << req.url(); QString buttonText, title, message; int response = KMessageBox::Cancel; QUrl linkUrl (req.url()); if (type == QWebEnginePage::NavigationTypeLinkClicked) { message = i18n("This untrusted page links to
%1." "
Do you want to follow the link?
", linkUrl.url()); title = i18n("Security Warning"); buttonText = i18nc("follow link despite of security warning", "Follow"); } else { title = i18n("Security Alert"); message = i18n("Access by untrusted page to
%1
denied.
", linkUrl.toDisplayString().toHtmlEscaped()); } if (buttonText.isEmpty()) { KMessageBox::error( 0, message, title); } else { // Dangerous flag makes the Cancel button the default response = KMessageBox::warningContinueCancel(0, message, title, KGuiItem(buttonText), KStandardGuiItem::cancel(), QString(), // no don't ask again info KMessageBox::Notify | KMessageBox::Dangerous); } return (response == KMessageBox::Continue); } return true; } -bool WebEnginePage::checkFormData(const QNetworkRequest &req) const +bool WebEnginePage::checkFormData(const QUrl &url) const { - const QString scheme (req.url().scheme()); + const QString scheme (url.scheme()); if (m_sslInfo.isValid() && !scheme.compare(QL1S("https")) && !scheme.compare(QL1S("mailto")) && (KMessageBox::warningContinueCancel(0, i18n("Warning: This is a secure form " "but it is attempting to send " "your data back unencrypted.\n" "A third party may be able to " "intercept and view this " "information.\nAre you sure you " "want to send the data unencrypted?"), i18n("Network Transmission"), KGuiItem(i18n("&Send Unencrypted"))) == KMessageBox::Cancel)) { return false; } if (scheme.compare(QL1S("mailto")) == 0 && (KMessageBox::warningContinueCancel(0, i18n("This site is attempting to " "submit form data via email.\n" "Do you want to continue?"), i18n("Network Transmission"), KGuiItem(i18n("&Send Email")), KStandardGuiItem::cancel(), QStringLiteral("WarnTriedEmailSubmit")) == KMessageBox::Cancel)) { return false; } return true; } // Sanitizes the "mailto:" url, e.g. strips out any "attach" parameters. static QUrl sanitizeMailToUrl(const QUrl &url, QStringList& files) { QUrl sanitizedUrl; // NOTE: This is necessary to ensure we can properly use QUrl's query // related APIs to process 'mailto:' urls of form 'mailto:foo@bar.com'. if (url.hasQuery()) sanitizedUrl = url; else sanitizedUrl = QUrl(url.scheme() + QL1S(":?") + url.path()); QUrlQuery query(sanitizedUrl); const QList > items (query.queryItems()); QUrlQuery sanitizedQuery; for(auto queryItem : items) { if (queryItem.first.contains(QL1C('@')) && queryItem.second.isEmpty()) { // ### DF: this hack breaks mailto:faure@kde.org, kmail doesn't expect mailto:?to=faure@kde.org queryItem.second = queryItem.first; queryItem.first = QStringLiteral("to"); } else if (QString::compare(queryItem.first, QL1S("attach"), Qt::CaseInsensitive) == 0) { files << queryItem.second; continue; } sanitizedQuery.addQueryItem(queryItem.first, queryItem.second); } sanitizedUrl.setQuery(sanitizedQuery); return sanitizedUrl; } bool WebEnginePage::handleMailToUrl (const QUrl &url, NavigationType type) const { if (url.scheme() == QL1S("mailto")) { QStringList files; QUrl mailtoUrl (sanitizeMailToUrl(url, files)); switch (type) { case QWebEnginePage::NavigationTypeLinkClicked: if (!files.isEmpty() && KMessageBox::warningContinueCancelList(0, i18n("Do you want to allow this site to attach " "the following files to the email message?"), files, i18n("Email Attachment Confirmation"), KGuiItem(i18n("&Allow attachments")), KGuiItem(i18n("&Ignore attachments")), QL1S("WarnEmailAttachment")) == KMessageBox::Continue) { // Re-add the attachments... QStringListIterator filesIt (files); QUrlQuery query(mailtoUrl); while (filesIt.hasNext()) { query.addQueryItem(QL1S("attach"), filesIt.next()); } mailtoUrl.setQuery(query); } break; case QWebEnginePage::NavigationTypeFormSubmitted: //case QWebEnginePage::NavigationTypeFormResubmitted: if (!files.isEmpty()) { KMessageBox::information(0, i18n("This site attempted to attach a file from your " "computer in the form submission. The attachment " "was removed for your protection."), i18n("Attachment Removed"), QStringLiteral("InfoTriedAttach")); } break; default: break; } //kDebug() << "Emitting openUrlRequest with " << mailtoUrl; emit m_part->browserExtension()->openUrlRequest(mailtoUrl); return true; } return false; } void WebEnginePage::setPageJScriptPolicy(const QUrl &url) { const QString hostname (url.host()); settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, WebEngineSettings::self()->isJavaScriptEnabled(hostname)); const KParts::HtmlSettingsInterface::JSWindowOpenPolicy policy = WebEngineSettings::self()->windowOpenPolicy(hostname); settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, (policy != KParts::HtmlSettingsInterface::JSWindowOpenDeny && policy != KParts::HtmlSettingsInterface::JSWindowOpenSmart)); } void WebEnginePage::slotAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth) { KIO::AuthInfo info; info.url = requestUrl; info.username = auth->user(); info.realmValue = auth->realm(); // If no realm metadata, then make sure path matching is turned on. info.verifyPath = info.realmValue.isEmpty(); const QString errorMsg = QString(); #if KIO_VERSION >= QT_VERSION_CHECK(5, 30, 0) const int ret = m_passwdServerClient->queryAuthInfo(&info, errorMsg, view()->window()->winId(), KUserTimestamp::userTimestamp()); #else const int ret = 1; // no KPasswdServerClient until 5.30 #endif if (ret == KJob::NoError) { auth->setUser(info.username); auth->setPassword(info.password); } else { // Set authenticator null if dialog is cancelled // or if we couldn't communicate with kpasswdserver *auth = QAuthenticator(); } } /************************************* Begin NewWindowPage ******************************************/ NewWindowPage::NewWindowPage(WebWindowType type, WebEnginePart* part, QWidget* parent) :WebEnginePage(part, parent) , m_type(type) , m_createNewWindow(true) { Q_ASSERT_X (part, "NewWindowPage", "Must specify a valid KPart"); connect(this, SIGNAL(menuBarVisibilityChangeRequested(bool)), this, SLOT(slotMenuBarVisibilityChangeRequested(bool))); connect(this, SIGNAL(toolBarVisibilityChangeRequested(bool)), this, SLOT(slotToolBarVisibilityChangeRequested(bool))); connect(this, SIGNAL(statusBarVisibilityChangeRequested(bool)), this, SLOT(slotStatusBarVisibilityChangeRequested(bool))); connect(this, SIGNAL(loadFinished(bool)), this, SLOT(slotLoadFinished(bool))); #if QTWEBENGINE_VERSION >= QT_VERSION_CHECK(5, 7, 0) if (m_type == WebBrowserBackgroundTab) { m_windowArgs.setLowerWindow(true); } #endif } NewWindowPage::~NewWindowPage() { } static KParts::BrowserArguments browserArgs(WebEnginePage::WebWindowType type) { KParts::BrowserArguments bargs; switch (type) { case WebEnginePage::WebDialog: case WebEnginePage::WebBrowserWindow: bargs.setForcesNewWindow(true); break; case WebEnginePage::WebBrowserTab: #if QTWEBENGINE_VERSION >= QT_VERSION_CHECK(5, 7, 0) case WebEnginePage::WebBrowserBackgroundTab: #endif // let konq decide, based on user configuration //bargs.setNewTab(true); break; } return bargs; } bool NewWindowPage::acceptNavigationRequest(const QUrl &url, NavigationType type, bool isMainFrame) { //qDebug() << "url:" << url << ", type:" << type << ", isMainFrame:" << isMainFrame << "m_createNewWindow=" << m_createNewWindow; if (m_createNewWindow) { const QUrl reqUrl (url); const bool actionRequestedByUser = type != QWebEnginePage::NavigationTypeOther; if (actionRequestedByUser) { if (!part() && !isMainFrame) { return false; } const KParts::HtmlSettingsInterface::JSWindowOpenPolicy policy = WebEngineSettings::self()->windowOpenPolicy(reqUrl.host()); switch (policy) { case KParts::HtmlSettingsInterface::JSWindowOpenDeny: // TODO: Implement support for dealing with blocked pop up windows. this->deleteLater(); return false; case KParts::HtmlSettingsInterface::JSWindowOpenAsk: { const QString message = (reqUrl.isEmpty() ? i18n("This site is requesting to open a new popup window.\n" "Do you want to allow this?") : i18n("This site is requesting to open a popup window to" "

%1


Do you want to allow this?
", KStringHandler::rsqueeze(reqUrl.toDisplayString().toHtmlEscaped(), 100))); if (KMessageBox::questionYesNo(view(), message, i18n("Javascript Popup Confirmation"), KGuiItem(i18n("Allow")), KGuiItem(i18n("Do Not Allow"))) == KMessageBox::No) { // TODO: Implement support for dealing with blocked pop up windows. this->deleteLater(); return false; } break; } default: break; } } // Browser args... KParts::BrowserArguments bargs = browserArgs(m_type); // OpenUrl args... KParts::OpenUrlArguments uargs; uargs.setMimeType(QL1S("text/html")); uargs.setActionRequestedByUser(actionRequestedByUser); // Window args... KParts::WindowArgs wargs (m_windowArgs); KParts::ReadOnlyPart* newWindowPart =0; part()->browserExtension()->createNewWindow(QUrl(), uargs, bargs, wargs, &newWindowPart); qDebug() << "Created new window" << newWindowPart; if (!newWindowPart) { return false; } else if (newWindowPart->widget()->topLevelWidget() != part()->widget()->topLevelWidget()) { KParts::OpenUrlArguments args; args.metaData().insert(QL1S("new-window"), QL1S("true")); newWindowPart->setArguments(args); } // Get the webview... WebEnginePart* webenginePart = qobject_cast(newWindowPart); WebEngineView* webView = webenginePart ? qobject_cast(webenginePart->view()) : 0; // If the newly created window is NOT a webenginepart... if (!webView) { qDebug() << "Opening URL on" << newWindowPart; newWindowPart->openUrl(reqUrl); this->deleteLater(); return false; } // Reparent this page to the new webview to prevent memory leaks. setParent(webView); // Replace the webpage of the new webview with this one. Nice trick... webView->setPage(this); // Set the new part as the one this page will use going forward. setPart(webenginePart); // Connect all the signals from this page to the slots in the new part. webenginePart->connectWebEnginePageSignals(this); //Set the create new window flag to false... m_createNewWindow = false; } emit navigationRequested(this, url); return WebEnginePage::acceptNavigationRequest(url, type, isMainFrame); } void NewWindowPage::slotGeometryChangeRequested(const QRect & rect) { if (!rect.isValid()) return; if (!m_createNewWindow) { WebEnginePage::slotGeometryChangeRequested(rect); return; } m_windowArgs.setX(rect.x()); m_windowArgs.setY(rect.y()); m_windowArgs.setWidth(qMax(rect.width(), 100)); m_windowArgs.setHeight(qMax(rect.height(), 100)); } void NewWindowPage::slotMenuBarVisibilityChangeRequested(bool visible) { //kDebug() << visible; m_windowArgs.setMenuBarVisible(visible); } void NewWindowPage::slotStatusBarVisibilityChangeRequested(bool visible) { //kDebug() << visible; m_windowArgs.setStatusBarVisible(visible); } void NewWindowPage::slotToolBarVisibilityChangeRequested(bool visible) { //kDebug() << visible; m_windowArgs.setToolBarsVisible(visible); } // When is this called? (and acceptNavigationRequest is not called?) // The only case I found is Ctrl+click on link to data URL (like in konqviewmgrtest), that's quite specific... // Everything else seems to work with this method being commented out... void NewWindowPage::slotLoadFinished(bool ok) { Q_UNUSED(ok) qDebug() << ok; if (!m_createNewWindow) return; const bool actionRequestedByUser = true; // ### we don't have the information here, unlike in acceptNavigationRequest // Browser args... KParts::BrowserArguments bargs = browserArgs(m_type); //bargs.frameName = mainFrame()->frameName(); // OpenUrl args... KParts::OpenUrlArguments uargs; uargs.setMimeType(QL1S("text/html")); uargs.setActionRequestedByUser(actionRequestedByUser); // Window args... KParts::WindowArgs wargs (m_windowArgs); KParts::ReadOnlyPart* newWindowPart =0; part()->browserExtension()->createNewWindow(QUrl(), uargs, bargs, wargs, &newWindowPart); qDebug() << "Created new window or tab" << newWindowPart; // Get the webview... WebEnginePart* webenginePart = newWindowPart ? qobject_cast(newWindowPart) : 0; WebEngineView* webView = webenginePart ? qobject_cast(webenginePart->view()) : 0; if (webView) { // if a new window is created, set a new window meta-data flag. if (newWindowPart->widget()->topLevelWidget() != part()->widget()->topLevelWidget()) { KParts::OpenUrlArguments args; args.metaData().insert(QL1S("new-window"), QL1S("true")); newWindowPart->setArguments(args); } // Reparent this page to the new webview to prevent memory leaks. setParent(webView); // Replace the webpage of the new webview with this one. Nice trick... webView->setPage(this); // Set the new part as the one this page will use going forward. setPart(webenginePart); // Connect all the signals from this page to the slots in the new part. webenginePart->connectWebEnginePageSignals(this); } //Set the create new window flag to false... m_createNewWindow = false; } /****************************** End NewWindowPage *************************************************/ diff --git a/webenginepart/src/webenginepage.h b/webenginepart/src/webenginepage.h index 8fa038665..cb3d5ed71 100644 --- a/webenginepart/src/webenginepage.h +++ b/webenginepart/src/webenginepage.h @@ -1,163 +1,168 @@ /* * This file is part of the KDE project. * * Copyright (C) 2008 Dirk Mueller * Copyright (C) 2008 Urs Wolfer * Copyright (C) 2009 Dawit Alemayehu * * 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 . * */ #ifndef WEBENGINEPAGE_H #define WEBENGINEPAGE_H #include "websslinfo.h" #include #include #include #include #include #include #include class QAuthenticator; class QUrl; class WebSslInfo; class WebEnginePart; class QWebEngineDownloadItem; class KPasswdServerClient; +class WebEngineWallet; class WebEnginePage : public QWebEnginePage { Q_OBJECT public: explicit WebEnginePage(WebEnginePart *wpart, QWidget *parent = Q_NULLPTR); ~WebEnginePage(); /** * Returns the SSL information for the current page. * * @see WebSslInfo. */ const WebSslInfo& sslInfo() const; /** * Sets the page's SSL information to @p other. * * @see WebSslInfo */ void setSslInfo (const WebSslInfo &other); void download(const QUrl &url, bool newWindow = false); + WebEngineWallet* wallet() const {return m_wallet;} + Q_SIGNALS: /** * This signal is emitted whenever a user cancels/aborts a load resource * request. */ void loadAborted(const QUrl &url); void navigationRequested(WebEnginePage* page, const QUrl& url); protected: /** * Returns the webengine part in use by this object. * @internal */ WebEnginePart* part() const; /** * Sets the webengine part to be used by this object. * @internal */ void setPart(WebEnginePart*); /** * Reimplemented for internal reasons, the API is not affected. * @internal */ QWebEnginePage* createWindow(WebWindowType type) Q_DECL_OVERRIDE; /** * Reimplemented for internal reasons, the API is not affected. * @internal */ bool acceptNavigationRequest(const QUrl& request, NavigationType type, bool isMainFrame) Q_DECL_OVERRIDE; protected Q_SLOTS: void slotLoadFinished(bool ok); void slotUnsupportedContent(QNetworkReply* reply); virtual void slotGeometryChangeRequested(const QRect& rect); void slotFeaturePermissionRequested(const QUrl& url, QWebEnginePage::Feature feature); void slotAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth); private: bool checkLinkSecurity(const QNetworkRequest& req, NavigationType type) const; - bool checkFormData(const QNetworkRequest& req) const; + bool checkFormData(const QUrl& url) const; bool handleMailToUrl (const QUrl& , NavigationType type) const; void setPageJScriptPolicy(const QUrl& url); private: enum WebEnginePageSecurity { PageUnencrypted, PageEncrypted, PageMixed }; int m_kioErrorCode; bool m_ignoreError; WebSslInfo m_sslInfo; QPointer m_part; QScopedPointer m_passwdServerClient; + WebEngineWallet *m_wallet; }; /** * This is a fake implementation of WebEnginePage to workaround the ugly API used * to request for the creation of a new window from javascript in QtWebEngine. PORTING_TODO * * The KPart API for creating new windows requires all the information about the * new window up front. Unfortunately QWebEnginePage::createWindow function does not * provide any of these necessary information except for the window type. All * the other necessary information is emitted as signals instead! Hence, the * need for this class to collect all of the necessary information, such as * window name, size and position, before calling KPart's createNewWindow * function. */ class NewWindowPage : public WebEnginePage { Q_OBJECT public: NewWindowPage(WebWindowType windowType, WebEnginePart* part, QWidget* parent = Q_NULLPTR); virtual ~NewWindowPage(); protected: bool acceptNavigationRequest(const QUrl& request, NavigationType type, bool isMainFrame) Q_DECL_OVERRIDE; private Q_SLOTS: void slotGeometryChangeRequested(const QRect& rect) override; void slotMenuBarVisibilityChangeRequested(bool visible); void slotStatusBarVisibilityChangeRequested(bool visible); void slotToolBarVisibilityChangeRequested(bool visible); void slotLoadFinished(bool); private: KParts::WindowArgs m_windowArgs; WebWindowType m_type; bool m_createNewWindow; + WebEngineWallet* m_wallet; }; #endif // WEBENGINEPAGE_H diff --git a/webenginepart/src/webenginepart.cpp b/webenginepart/src/webenginepart.cpp index 10bbffc15..66edce7f0 100644 --- a/webenginepart/src/webenginepart.cpp +++ b/webenginepart/src/webenginepart.cpp @@ -1,926 +1,934 @@ /* * 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 #include #include #include #include "webenginepart_ext.h" #include "webengineview.h" #include "webenginepage.h" #include "websslinfo.h" #include "webhistoryinterface.h" +#include "webenginewallet.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 "utils.h" 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(0), m_searchBar(0), m_passwordBar(0), - m_featurePermissionBar(0) + m_featurePermissionBar(0), + m_wallet(Q_NULLPTR) { 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 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 Q_NULLPTR; } const WebEnginePage* WebEnginePart::page() const { if (m_webView) return qobject_cast(m_webView->page()); return Q_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()){ m_browserExtension->setIconUrl(url); } }); +} -#if 0 - KWebWallet *wallet = page->wallet(); - if (wallet) { - connect(wallet, SIGNAL(saveFormDataRequested(QString,QUrl)), - this, SLOT(slotSaveFormDataRequested(QString,QUrl))); - connect(wallet, SIGNAL(fillFormRequestCompleted(bool)), - this, SLOT(slotFillFormRequestCompleted(bool))); - connect(wallet, SIGNAL(walletClosed()), this, SLOT(slotWalletClosed())); +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); } -#endif } bool WebEnginePart::openUrl(const QUrl &_u) { QUrl u (_u); qDebug() << 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); } } // Set URL in KParts before emitting started; konq plugins rely on that. setUrl(u); m_doLoadFinishedActions = true; m_webView->loadUrl(u, args, bargs); return true; } bool WebEnginePart::closeUrl() { m_webView->triggerPageAction(QWebEnginePage::Stop); m_webView->stop(); return true; } QWebEngineView* WebEnginePart::view() { 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(0); } 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 { + } + else { // Attempt to fill the web form... -// KWebWallet *webWallet = page() ? page()->wallet() : 0; -// if (webWallet) { -// webWallet->fillFormData(frame, false); -// } + WebEngineWallet *wallet = page() ? page()->wallet() : 0; + 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(0, 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 = 0; m_hasCachedFormData = false; } void WebEnginePart::slotShowWalletMenu() { QMenu *menu = new QMenu(0); 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()) + if (!page() || !page()->wallet()) return; -// page()->wallet()->removeFormData(page()->mainFrame(), true); + 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; qDebug() << "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())); + 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 0 - KWebWallet* wallet = page()->wallet(); - if (!wallet) { - kWarning() << "No wallet instance found! This should never happen!"; + if (!m_wallet) { + qDebug() << "No m_wallet instance found! This should never happen!"; return; } connect(m_passwordBar, SIGNAL(saveFormDataAccepted(QString)), - wallet, SLOT(acceptSaveFormDataRequest(QString))); + m_wallet, SLOT(acceptSaveFormDataRequest(QString))); connect(m_passwordBar, SIGNAL(saveFormDataRejected(QString)), - wallet, SLOT(rejectSaveFormDataRequest(QString))); + m_wallet, SLOT(rejectSaveFormDataRequest(QString))); connect(m_passwordBar, SIGNAL(done()), this, SLOT(slotSaveFormDataDone())); -#endif } 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 6889e6d7f..91afa426f 100644 --- a/webenginepart/src/webenginepart.h +++ b/webenginepart/src/webenginepart.h @@ -1,165 +1,170 @@ /* * 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 <QtWebEngineWidgets/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; /** * 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 = 0, QObject* parent = Q_NULLPTR, const QByteArray& cachedHistory = QByteArray(), const QStringList& = QStringList()); ~WebEnginePart(); /** * Re-implemented for internal reasons. API remains unaffected. * * @see KParts::ReadOnlyPart::openUrl */ bool openUrl(const QUrl &) Q_DECL_OVERRIDE; /** * Re-implemented for internal reasons. API remains unaffected. * * @see KParts::ReadOnlyPart::closeUrl */ bool closeUrl() Q_DECL_OVERRIDE; /** * Returns a pointer to the render widget used to display a web page. * * @see QWebEngineView. */ virtual QWebEngineView *view(); /** * 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 *) Q_DECL_OVERRIDE; /** * Re-implemented for internal reasons. API remains unaffected. * * @see KParts::ReadOnlyPart::openFile */ bool openFile() Q_DECL_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; 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/webenginepartfactory.cpp b/webenginepart/src/webenginepartfactory.cpp index 04853bd27..7a877b422 100644 --- a/webenginepart/src/webenginepartfactory.cpp +++ b/webenginepart/src/webenginepartfactory.cpp @@ -1,65 +1,66 @@ /* * This file is part of the KDE project. * * Copyright (C) 2008 Laurent Montel <montel@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/>. * */ #include "webenginepartfactory.h" #include "webenginepart_ext.h" #include "webenginepart.h" #include <QWidget> + WebEngineFactory::~WebEngineFactory() { // kDebug() << this; } QObject *WebEngineFactory::create(const char* iface, QWidget *parentWidget, QObject *parent, const QVariantList &args, const QString& keyword) { Q_UNUSED(iface); Q_UNUSED(keyword); Q_UNUSED(args); qDebug() << parentWidget << parent; connect(parentWidget, SIGNAL(destroyed(QObject*)), this, SLOT(slotDestroyed(QObject*))); // NOTE: The code below is what makes it possible to properly integrate QtWebEngine's PORTING_TODO // history management with any KParts based application. QByteArray histData (m_historyBufContainer.value(parentWidget)); if (!histData.isEmpty()) histData = qUncompress(histData); WebEnginePart* part = new WebEnginePart(parentWidget, parent, histData); WebEngineBrowserExtension* ext = qobject_cast<WebEngineBrowserExtension*>(part->browserExtension()); if (ext) { connect(ext, SIGNAL(saveHistory(QObject*,QByteArray)), this, SLOT(slotSaveHistory(QObject*,QByteArray))); } return part; } void WebEngineFactory::slotSaveHistory(QObject* widget, const QByteArray& buffer) { // kDebug() << "Caching history data from" << widget; m_historyBufContainer.insert(widget, buffer); } void WebEngineFactory::slotDestroyed(QObject* object) { // kDebug() << "Removing cached history data of" << object; m_historyBufContainer.remove(object); } K_EXPORT_PLUGIN(WebEngineFactory) diff --git a/webenginepart/src/webenginewallet.cpp b/webenginepart/src/webenginewallet.cpp new file mode 100644 index 000000000..f501b273b --- /dev/null +++ b/webenginepart/src/webenginewallet.cpp @@ -0,0 +1,599 @@ +/* + * This file is part of the KDE project. + * + * Copyright (C) 2009 Dawit Alemayehu <adawit@kde.org> + * 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 Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include "webenginewallet.h" +#include "webenginepage.h" + +#include <KWallet> +#include <QDebug> + +#include <QtCore/QSet> +#include <QtCore/QHash> +#include <QtCore/QFile> +#include <QtCore/QPointer> +#include <QtCore/QScopedPointer> +#include <QJsonDocument> +#include <qwindowdefs.h> + +#define QL1S(x) QLatin1String(x) +#define QL1C(x) QLatin1Char(x) + +// Javascript used to extract/set data from <form> elements. +static const char s_fillableFormElementExtractorJs[] = "(function(){ " +" function findFormsRecursive(wnd, existingList, path){" +" findFormsInFrame(wnd, existingList, path);" +" frameList = wnd.frames;" +" for(var i = 0; i < frameList.length; ++i) {" +" var newPath = path.concat(i);" +" findFormsRecursive(frameList[i], existingList, newPath);" +" }" +" }" +" function findFormsInFrame(frm, existingList, path){" +" var url = frm.location;" +" var formList;" +" try{ formList = frm.document.forms; } " +" catch(e){" +" return;" +" }" +" if (formList.length > 0) { " +" for (var i = 0; i < formList.length; ++i) { " +" var inputList = formList[i].elements; " +" if (inputList.length < 1) { " +" continue; " +" } " +" var formObject = new Object; " +" formObject.url = url;" +" formObject.name = formList[i].name; " +" if (typeof(formObject.name) != 'string') { " +" formObject.name = String(formList[i].id); " +" } " +" formObject.index = i; " +" formObject.elements = new Array; " +" for (var j = 0; j < inputList.length; ++j) { " +" if (inputList[j].type != 'text' && inputList[j].type != 'email' && inputList[j].type != 'password') { " +" continue; " +" } " +" if (inputList[j].disabled || inputList[j].autocomplete == 'off') { " +" continue; " +" } " +" var element = new Object; " +" element.name = inputList[j].name; " +" if (typeof(element.name) != 'string' ) { " +" element.name = String(inputList[j].id); " +" } " +" element.value = String(inputList[j].value); " +" element.type = String(inputList[j].type); " +" element.readonly = Boolean(inputList[j].readOnly); " +" formObject.elements.push(element); " +" } " +" if (formObject.elements.length > 0) { " +" formObject.framePath = path;" +" console.log(JSON.stringify(formObject));" +" existingList.push(JSON.stringify(formObject)); " +" } " +" } " +" } " +" }" +" var forms = new Array;" +" findFormsRecursive(window, forms, []);" +" return forms;" +"})()"; + +//javascript used to fill a single form element +static const char s_javascriptFillInputFragment[] = "var frm = window;" +" for(var i=0; i < [%1].length; ++i) frm=frm.frames[i];" +" if (frm.document.forms['%2'] && frm.document.forms['%2'].elements['%3']){" +" frm.document.forms['%2'].elements['%3'].value='%4';\n" +" }"; + + +/** + * Creates key used to store and retrieve form data. + * + */ +static QString walletKey(WebEngineWallet::WebForm form) +{ + QString key = form.url.toString(QUrl::RemoveQuery | QUrl::RemoveFragment); + key += QL1C('#'); + key += form.name; + return key; +} + +static QUrl urlForFrame(const QUrl &frameUrl, const QUrl &pageUrl) +{ + return (frameUrl.isEmpty() || frameUrl.isRelative() ? pageUrl.resolved(frameUrl) : frameUrl); +} + +class WebEngineWallet::WebEngineWalletPrivate +{ +public: + struct FormsData { + QPointer<WebEnginePage> page; + WebEngineWallet::WebFormList forms; + }; + + typedef std::function<void(const WebEngineWallet::WebFormList &)> WebWalletCallback; + + WebEngineWalletPrivate(WebEngineWallet *parent); + + void withFormData(WebEnginePage *page, WebWalletCallback callback, bool fillform = true, bool ignorepasswd = false); + WebFormList parseFormData(const QVariant &result, const QUrl &url, bool fillform = true, bool ignorepasswd = false); + void performFormDataParsing(const QVariant &result, const QUrl &url, WebWalletCallback callback, bool fillform, bool ignorepasswd); + void fillDataFromCache(WebEngineWallet::WebFormList &formList); + void saveDataToCache(const QString &key); + void removeDataFromCache(const WebFormList &formList); + void openWallet(); + + // Private slots... + void _k_openWalletDone(bool); + void _k_walletClosed(); + + WId wid; + WebEngineWallet *q; + QScopedPointer<KWallet::Wallet> wallet; + WebEngineWallet::WebFormList pendingRemoveRequests; + QHash<QUrl, FormsData> pendingFillRequests; + QHash<QString, WebEngineWallet::WebFormList> pendingSaveRequests; + QSet<QUrl> confirmSaveRequestOverwrites; +}; + +WebEngineWallet::WebEngineWalletPrivate::WebEngineWalletPrivate(WebEngineWallet *parent) + : wid(0), q(parent) +{ +} + +WebEngineWallet::WebFormList WebEngineWallet::WebEngineWalletPrivate::parseFormData(const QVariant &result, const QUrl &url, bool fillform, bool ignorepasswd) +{ + const QVariantList results(result.toList()); + WebEngineWallet::WebFormList list; + Q_FOREACH (const QVariant &formVariant, results) { + QJsonDocument doc = QJsonDocument::fromJson(formVariant.toString().toUtf8()); + const QVariantMap map = doc.toVariant().toMap(); + WebEngineWallet::WebForm form; + form.url = urlForFrame(QUrl(map[QL1S("url")].toString()), url); + form.name = map[QL1S("name")].toString(); + form.index = map[QL1S("index")].toString(); + form.framePath = map["framePath"].toStringList().join(","); + bool formHasPasswords = false; + const QVariantList elements = map[QL1S("elements")].toList(); + QVector<WebEngineWallet::WebForm::WebField> inputFields; + Q_FOREACH (const QVariant &element, elements) { + QVariantMap elementMap(element.toMap()); + const QString name(elementMap[QL1S("name")].toString()); + const QString value(ignorepasswd ? QString() : elementMap[QL1S("value")].toString()); + if (name.isEmpty()) { + continue; + } + if (fillform && elementMap[QL1S("readonly")].toBool()) { + continue; + } + if (elementMap[QL1S("type")].toString().compare(QL1S("password"), Qt::CaseInsensitive) == 0) { + if (!fillform && value.isEmpty()) { + continue; + } + formHasPasswords = true; + } + inputFields.append(qMakePair(name, value)); + } + // Only add the input fields on form save requests... + if (formHasPasswords || fillform) { + form.fields = inputFields; + } + + // Add the form to the list if we are saving it or it has cached data. + if ((fillform && q->hasCachedFormData(form)) || (!fillform && !form.fields.isEmpty())) { + list << form; + } + } + return list; +} + +void WebEngineWallet::WebEngineWalletPrivate::withFormData(WebEnginePage* page, WebWalletCallback callback, bool fillform, bool ignorepasswd) +{ + Q_ASSERT(page); + QUrl url = page->url(); + auto internalCallback = [this, url, fillform, ignorepasswd, callback](const QVariant &result){ + WebFormList res = parseFormData(result, url, fillform, ignorepasswd); + callback(res); + }; + page->runJavaScript(QL1S(s_fillableFormElementExtractorJs), internalCallback); +} + +void WebEngineWallet::WebEngineWalletPrivate::fillDataFromCache(WebEngineWallet::WebFormList &formList) +{ + if (!wallet) { + qWarning() << "Unable to retrieve form data from wallet"; + return; + } + + QString lastKey; + QMap<QString, QString> cachedValues; + QMutableVectorIterator <WebForm> formIt(formList); + + while (formIt.hasNext()) { + WebEngineWallet::WebForm &form = formIt.next(); + const QString key(walletKey(form)); + if (key != lastKey && wallet->readMap(key, cachedValues) != 0) { + qWarning() << "Unable to read form data for key:" << key; + continue; + } + + for (int i = 0, count = form.fields.count(); i < count; ++i) { + form.fields[i].second = cachedValues.value(form.fields[i].first); + } + lastKey = key; + } +} + +void WebEngineWallet::WebEngineWalletPrivate::saveDataToCache(const QString &key) +{ + // Make sure the specified keys exists before acting on it. See BR# 270209. + if (!pendingSaveRequests.contains(key)) { + return; + } + + bool success = false; + const QUrl url = pendingSaveRequests.value(key).first().url; + + if (wallet) { + int count = 0; + const WebEngineWallet::WebFormList list = pendingSaveRequests.value(key); + QVectorIterator<WebEngineWallet::WebForm> formIt(list); + + while (formIt.hasNext()) { + QMap<QString, QString> values, storedValues; + const WebEngineWallet::WebForm form = formIt.next(); + const QString accessKey = walletKey(form); + if (confirmSaveRequestOverwrites.contains(url)) { + confirmSaveRequestOverwrites.remove(url); + const int status = wallet->readMap(accessKey, storedValues); + if (status == 0 && storedValues.count()) { + QVectorIterator<WebEngineWallet::WebForm::WebField> fieldIt(form.fields); + while (fieldIt.hasNext()) { + const WebEngineWallet::WebForm::WebField field = fieldIt.next(); + if (storedValues.contains(field.first) && + storedValues.value(field.first) != field.second) { + emit q->saveFormDataRequested(key, url); + return; + } + } + // If we got here it means the new credential is exactly + // the same as the one already cached ; so skip the + // re-saving part... + success = true; + continue; + } + } + QVectorIterator<WebEngineWallet::WebForm::WebField> fieldIt(form.fields); + while (fieldIt.hasNext()) { + const WebEngineWallet::WebForm::WebField field = fieldIt.next(); + values.insert(field.first, field.second); + } + + if (wallet->writeMap(accessKey, values) == 0) { + count++; + } else { + qWarning() << "Unable to write form data to wallet"; + } + } + + if (list.isEmpty() || count > 0) { + success = true; + } + + pendingSaveRequests.remove(key); + } else { + qWarning() << "NULL Wallet instance!"; + } + + emit q->saveFormDataCompleted(url, success); +} + +void WebEngineWallet::WebEngineWalletPrivate::openWallet() +{ + if (!wallet.isNull()) { + return; + } + + wallet.reset(KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), + wid, KWallet::Wallet::Asynchronous)); + + if (wallet.isNull()) { + return; + } + + connect(wallet.data(), SIGNAL(walletOpened(bool)), q, SLOT(_k_openWalletDone(bool))); + connect(wallet.data(), SIGNAL(walletClosed()), q, SLOT(_k_walletClosed())); +} + +void WebEngineWallet::WebEngineWalletPrivate::removeDataFromCache(const WebFormList &formList) +{ + if (!wallet) { + qWarning() << "NULL Wallet instance!"; + return; + } + + QVectorIterator<WebForm> formIt(formList); + while (formIt.hasNext()) { + wallet->removeEntry(walletKey(formIt.next())); + } +} + +void WebEngineWallet::WebEngineWalletPrivate::_k_openWalletDone(bool ok) +{ + Q_ASSERT(wallet); + + if (ok && + (wallet->hasFolder(KWallet::Wallet::FormDataFolder()) || + wallet->createFolder(KWallet::Wallet::FormDataFolder())) && + wallet->setFolder(KWallet::Wallet::FormDataFolder())) { + + // Do pending fill requests... + if (!pendingFillRequests.isEmpty()) { + QList<QUrl> urlList; + QMutableHashIterator<QUrl, FormsData> requestIt(pendingFillRequests); + while (requestIt.hasNext()) { + requestIt.next(); + WebEngineWallet::WebFormList list = requestIt.value().forms; + fillDataFromCache(list); + q->fillWebForm(requestIt.key(), list); + } + + pendingFillRequests.clear(); + } + + // Do pending save requests... + if (!pendingSaveRequests.isEmpty()) { + QListIterator<QString> keysIt(pendingSaveRequests.keys()); + while (keysIt.hasNext()) { + saveDataToCache(keysIt.next()); + } + } + + // Do pending remove requests... + if (!pendingRemoveRequests.isEmpty()) { + removeDataFromCache(pendingRemoveRequests); + pendingRemoveRequests.clear(); + } + } else { + // Delete the wallet if opening the wallet failed or we were unable + // to change to the folder we wanted to change to. + delete wallet.take(); + } +} + +void WebEngineWallet::WebEngineWalletPrivate::_k_walletClosed() +{ + if (wallet) { + wallet.take()->deleteLater(); + } + + emit q->walletClosed(); +} + +WebEngineWallet::WebEngineWallet(QObject *parent, WId wid) + : QObject(parent), d(new WebEngineWalletPrivate(this)) +{ + d->wid = wid; +} + +WebEngineWallet::~WebEngineWallet() +{ + delete d; +} + +void WebEngineWallet::fillFormDataCallback(WebEnginePage* page, const WebEngineWallet::WebFormList& formsList) +{ + QList<QUrl> urlList; + if (!formsList.isEmpty()) { + const QUrl url(page->url()); + if (d->pendingFillRequests.contains(url)) { + qWarning() << "Duplicate request rejected!"; + } else { + WebEngineWalletPrivate::FormsData data; + data.page = QPointer<WebEnginePage>(page); + data.forms << formsList; + d->pendingFillRequests.insert(url, data); + urlList << url; + } + } + + if (!urlList.isEmpty()) { + fillFormDataFromCache(urlList); + } + +} + +void WebEngineWallet::fillFormData(WebEnginePage *page) +{ + if (!page) return; + auto callback = [this, page](const WebFormList &forms){ + fillFormDataCallback(page, forms); + }; + d->withFormData(page, callback); +} + +static void createSaveKeyFor(WebEnginePage *page, QString *key) +{ + QUrl pageUrl(page->url()); + pageUrl.setPassword(QString()); + + QString keyStr = pageUrl.toString(); + + *key = QString::number(qHash(keyStr), 16); +} + +void WebEngineWallet::saveFormData(WebEnginePage *page, bool ignorePasswordFields) +{ + if (!page) { + return; + } + + QString key; + createSaveKeyFor(page, &key); + if (d->pendingSaveRequests.contains(key)) { + return; + } + + QUrl url = page->url(); + auto callback = [this, key, url](const WebFormList &list){saveFormDataCallback(key, url, list);}; + d->withFormData(page, callback, false, ignorePasswordFields); +} + +void WebEngineWallet::saveFormDataCallback(const QString &key, const QUrl& url, const WebEngineWallet::WebFormList& formsList) +{ + + if (formsList.isEmpty()) { + return; + } + + WebFormList list(formsList); + + d->pendingSaveRequests.insert(key, list); + + QMutableVectorIterator<WebForm> it(list); + while (it.hasNext()) { + const WebForm form(it.next()); + if (hasCachedFormData(form)) { + it.remove(); + } + } + + if (list.isEmpty()) { + d->confirmSaveRequestOverwrites.insert(url); + saveFormDataToCache(key); + return; + } + + emit saveFormDataRequested(key, url); +} + +void WebEngineWallet::removeFormData(WebEnginePage *page) +{ + if (page) { + auto callback = [this](const WebFormList &list){removeFormDataFromCache(list);}; + d->withFormData(page, callback); + } +} + +void WebEngineWallet::removeFormDataCallback(const WebFormList& list) +{ + removeFormDataFromCache(list); +} + + +void WebEngineWallet::removeFormData(const WebFormList &forms) +{ + d->pendingRemoveRequests << forms; + removeFormDataFromCache(forms); +} + +void WebEngineWallet::acceptSaveFormDataRequest(const QString &key) +{ + saveFormDataToCache(key); +} + +void WebEngineWallet::rejectSaveFormDataRequest(const QString &key) +{ + d->pendingSaveRequests.remove(key); +} + +void WebEngineWallet::fillWebForm(const QUrl &url, const WebEngineWallet::WebFormList &forms) +{ + QPointer<WebEnginePage> page = d->pendingFillRequests.value(url).page; + if (!page) { + return; + } + + QString script; + bool wasFilled = false; + + Q_FOREACH (const WebEngineWallet::WebForm &form, forms) { + Q_FOREACH (const WebEngineWallet::WebForm::WebField &field, form.fields) { + QString value = field.second; + value.replace(QL1C('\\'), QL1S("\\\\")); + script+= QString(s_javascriptFillInputFragment) + .arg(form.framePath) + .arg((form.name.isEmpty() ? form.index : form.name)) + .arg(field.first).arg(value); + } + } + if (!script.isEmpty()) { + wasFilled = true; + auto callback = [wasFilled, this](const QVariant &){emit fillFormRequestCompleted(wasFilled);}; + page.data()->runJavaScript(script, callback); + } +} + +WebEngineWallet::WebFormList WebEngineWallet::formsToFill(const QUrl &url) const +{ + return d->pendingFillRequests.value(url).forms; +} + +WebEngineWallet::WebFormList WebEngineWallet::formsToSave(const QString &key) const +{ + return d->pendingSaveRequests.value(key); +} + +bool WebEngineWallet::hasCachedFormData(const WebForm &form) const +{ + return !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), + KWallet::Wallet::FormDataFolder(), + walletKey(form)); +} + +void WebEngineWallet::fillFormDataFromCache(const QList<QUrl> &urlList) +{ + if (d->wallet) { + QListIterator<QUrl> urlIt(urlList); + while (urlIt.hasNext()) { + const QUrl url = urlIt.next(); + WebFormList list = formsToFill(url); + d->fillDataFromCache(list); + fillWebForm(url, list); + } + d->pendingFillRequests.clear(); + } + d->openWallet(); +} + +void WebEngineWallet::saveFormDataToCache(const QString &key) +{ + if (d->wallet) { + d->saveDataToCache(key); + return; + } + d->openWallet(); +} + +void WebEngineWallet::removeFormDataFromCache(const WebFormList &forms) +{ + if (d->wallet) { + d->removeDataFromCache(forms); + d->pendingRemoveRequests.clear(); + return; + } + d->openWallet(); +} + +#include "moc_webenginewallet.cpp" diff --git a/webenginepart/src/webenginewallet.h b/webenginepart/src/webenginewallet.h new file mode 100644 index 000000000..2c3d65b14 --- /dev/null +++ b/webenginepart/src/webenginewallet.h @@ -0,0 +1,305 @@ +/* + * This file is part of the KDE project. + * + * Copyright (C) 2009 Dawit Alemayehu <adawit@kde.org> + * 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 Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ +#ifndef WEBENGINEWALLET_H +#define WEBENGINEWALLET_H + +#include <QtCore/QObject> +#include <QtCore/QString> +#include <QtCore/QList> +#include <QtCore/QPair> +#include <QUrl> +#include <QWidget> +#include <QtCore/QtGlobal> + +class WebEnginePage; + +class WebEngineWallet : public QObject +{ + Q_OBJECT + +public: + + /** + * Holds data from a HTML <form> element. + */ + struct WebForm { + /** + * A typedef for storing the name and value attributes of HTML <input> + * elements. + */ + typedef QPair<QString, QString> WebField; + + /** The URL the form was found at. */ + QUrl url; + /** The name attribute of the form. */ + QString name; + /** The position of the form on the web page, relative to other forms. */ + QString index; + /** The path of the frame the form belongs to relative to the toplevel window (in the javascript sense). + * + * This is stored as a string containing a javascript array (it is passed as is to javascript code, so no need to store it in C++ format + */ + QString framePath; + /** The name and value attributes of each input element in the form. */ + QVector<WebField> fields; + }; + + /** + * A list of web forms + */ + typedef QVector<WebForm> WebFormList; + + /** + * Constructs a WebEngineWebWallet + * + * @p parent is usually the WebEnginePage this wallet is being used for. + * + * The @p wid parameter is used to tell the KWallet manager which window + * is requesting access to the wallet. + * + * @param parent the owner of this wallet + * @param wid the window ID of the window the web page will be + * embedded in + */ + explicit WebEngineWallet(QObject *parent = nullptr, WId wid = 0); + + /** + * Destructor + */ + virtual ~WebEngineWallet(); + + /** + * Attempts to save the form data from @p page and its children frames. + * + * You must connect to the @ref saveFormDataRequested signal and call either + * @ref rejectSaveFormDataRequest or @ref acceptSaveFormDataRequest signals + * in order to complete the save request. Otherwise, you request will simply + * be ignored. + * + * Note that this function is asynchronous, as it requires running javascript code + * on the page using @ref QWebEnginePage::runJavaScript. This function only requests + * for the form data to be saved when @ref QWebEnginePage::runJavaScript finishes. + * The actual saving is done by @ref saveFormDataCallback + */ + void saveFormData(WebEnginePage *page, bool ignorePasswordFields = false); + + /** + * Attempts to fill forms contained in @p page with cached data. + * + * Note that this function is asynchronous, as it requires running javascript code + * on the page using @ref QWebEnginePage::runJavaScript. This function only requests + * for the form data to be filled when @ref QWebEnginePage::runJavaScript finishes. + * The actual filling is done by @ref fillFormDataCallback + */ + void fillFormData(WebEnginePage *page); + + /** + * Removes the form data specified by @p forms from the persistent storage. + * + * Note that this function will remove all cached data for forms found in @p page. + * + * Note that this function is asynchronous, as it requires running javascript code + * on the page using @ref QWebEnginePage::runJavaScript. This function only requests + * for the form data to be removed when @ref QWebEnginePage::runJavaScript finishes. + * The actual removing is done by @ref removeFormDataCallback + */ + void removeFormData(WebEnginePage *page); + + /** + * Removes the form data specified by @p forms from the persistent storage. + * + * @see formsWithCachedData + */ + void removeFormData(const WebFormList &forms); + +public Q_SLOTS: + /** + * Accepts the save form data request associated with @p key. + * + * The @p key parameter is the one sent through the @ref saveFormDataRequested + * signal. + * + * You must always call this function or @ref rejectSaveFormDataRequest in + * order to complete the save form data request. Otherwise, the request will + * simply be ignored. + * + * @see saveFormDataRequested. + */ + void acceptSaveFormDataRequest(const QString &key); + + /** + * Rejects the save form data request associated with @p key. + * + * The @p key parameter is the one sent through the @ref saveFormDataRequested + * signal. + * + * @see saveFormDataRequested. + */ + void rejectSaveFormDataRequest(const QString &key); + +Q_SIGNALS: + /** + * This signal is emitted whenever a save form data request is received. + * + * Unless you connect to this signal and and call @ref acceptSaveFormDataRequest + * or @ref rejectSaveFormDataRequest slots, the save form data requested through + * @ref saveFormData will simply be ignored. + * + * @p key is a value that uniquely identifies the save request and @p url + * is the address for which the form data is being saved. + * + * @see acceptSaveFormDataRequest + * @see rejectSaveFormDataRequest + */ + void saveFormDataRequested(const QString &key, const QUrl &url); + + /** + * This signal is emitted whenever a save form data request is completed. + * + * @p ok will be set to true if the save form data request for @p url was + * completed successfully. + * + * @see saveFormDataRequested + */ + void saveFormDataCompleted(const QUrl &url, bool ok); + + /** + * This signal is emitted whenever a fill form data request is completed. + * + * @p ok will be set to true if any forms were successfully filled with + * cached data from the persistent storage. + * + * @see fillFormData + * @since 4.5 + */ + void fillFormRequestCompleted(bool ok); + + /** + * This signal is emitted whenever the current wallet is closed. + */ + void walletClosed(); + +protected: + /** + * Returns a list of forms for @p url that are waiting to be filled. + * + * This function returns an empty list if there is no pending requests + * for filling forms associated with @p url. + */ + WebFormList formsToFill(const QUrl &url) const; + + /** + * Returns a list of for @p key that are waiting to be saved. + * + * This function returns an empty list if there are no pending requests + * for saving forms associated with @p key. + */ + WebFormList formsToSave(const QString &key) const; + + /** + * Returns forms to be removed from persistent storage. + */ + WebFormList formsToDelete() const; + + /** + * Returns true when there is data associated with @p form in the + * persistent storage. + */ + bool hasCachedFormData(const WebForm &form) const; + + /** + * Fills the web forms in frame that point to @p url with data from @p forms. + * + * @see fillFormDataFromCache. + */ + void fillWebForm(const QUrl &url, const WebFormList &forms); + + /** + * Fills form data from persistent storage. + * + * If you reimplement this function, call @ref formsToFill to obtain + * the list of forms pending to be filled. Once you fill the list with + * the cached data from the persistent storage, you must call @p fillWebForm + * to fill out the actual web forms. + * + * @see formsToFill + */ + void fillFormDataFromCache(const QList<QUrl> &list); + + /** + * Stores form data associated with @p key to a persistent storage. + * + * If you reimplement this function, call @ref formsToSave to obtain the + * list of form data pending to be saved to persistent storage. + * + *@see formsToSave + */ + void saveFormDataToCache(const QString &key); + + /** + * Removes all cached form data associated with @p forms from persistent storage. + * + * If you reimplement this function, call @ref formsToDelete to obtain the + * list of form data pending to be removed from persistent storage. + * + *@see formsToDelete + */ + void removeFormDataFromCache(const WebFormList &forms); + +private: + + /** + * Callback used by @ref fillFormData to insert form data + * + * This function is called as a callback from @ref fillFormData after + * @ref QWebEnginePage::runJavaScript has finished + */ + void fillFormDataCallback(WebEnginePage *page, const WebFormList &formsList); + + /** + * Callback used by @ref saveFormData to save data + * + * This function is called as a callback from @ref saveFormData after + * @ref QWebEnginePage::runJavaScript has finished + */ + void saveFormDataCallback(const QString &key, const QUrl &url, const WebFormList &formslist); + + /** + * Callback used by @ref removeFormData to remove data + * + * This function is called as a callback from @ref removeFormData after + * @ref QWebEnginePage::runJavaScript has finished + */ + void removeFormDataCallback(const WebFormList &list); + +private: + class WebEngineWalletPrivate; + friend class WebEngineWalletPrivate; + WebEngineWalletPrivate *const d; + + Q_PRIVATE_SLOT(d, void _k_openWalletDone(bool)) + Q_PRIVATE_SLOT(d, void _k_walletClosed()) +}; + + +#endif // WEBENGINEWALLET_H