diff --git a/webenginepart/src/webenginepage.cpp b/webenginepart/src/webenginepage.cpp index 864857e85..2f4a360b4 100644 --- a/webenginepart/src/webenginepage.cpp +++ b/webenginepart/src/webenginepage.cpp @@ -1,928 +1,947 @@ /* * 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_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) { + if (m_urlLoadedByPart != url) { + m_urlLoadedByPart = QUrl(); + } // 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(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 +bool WebEnginePage::certificateError(const QWebEngineCertificateError& ce) +{ + if (m_urlLoadedByPart == ce.url()) { + m_urlLoadedByPart = QUrl(); + return true; + } else if (ce.isOverridable()) { + QString translatedDesc = i18n(ce.errorDescription().toUtf8()); + QString text = i18n("

The server failed the authenticity check (%1). The error is:

%2

Do you want to ignore this error?", + ce.url().host(), translatedDesc); + KMessageBox::ButtonCode ans = KMessageBox::questionYesNo(view(), text, i18n("Authentication error")); + return ans == KMessageBox::Yes; + } else { + return false; + } +} + 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 QUrl &url) const { 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 98741f6a2..f97d6a29c 100644 --- a/webenginepart/src/webenginepage.h +++ b/webenginepart/src/webenginepage.h @@ -1,168 +1,207 @@ /* * 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;} + + /** + * @brief Tells the page that the part has requested to load the given URL + * + * @note Calling this function doesn't cause the page to be loaded: you still need to call load() to do so. + * @see m_urlLoadedByPart + * @param url the requested URL + */ + void setLoadUrlCalledByPart(const QUrl &url){m_urlLoadedByPart = url;} 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; + + /** + * @brief Override of `QWebEnginePage::certificateError` + * + * If the error is overridable, asks the user whether to ignore the error or not and returns `true` or `false` accordingly. + * If the error is not overridable, it always returns `false`. + * + * @internal + * A problem arises if the certificate error happens while loading a page requested by WebEnginePart::load() (rather than from the + * user's interaction with the WebEnginePage itself). The problem is that when WebEnginePart::load() is called, any certificate error + * will have already been caught by the `KParts` mechanism and the user will already have been asked about it. so it doesn't make sense + * to ask him again. To avoid doing so, this function checks m_urlLoadedByPart: if it is the same url as the one the certificate + * refers to, `true` is returned (and m_urlLoadedByPart is reset). + * @endinternal + * + * @param ce the certificate error + * @return `true` if the error can be ignored and `false` otherwise + */ + bool certificateError(const QWebEngineCertificateError &ce) 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 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; + + /** + * @brief The last URL that the part requested to be loaded + * + * Before calling `load()`, the part needs to call setLoadUrlCalledByPart() passing the URL which will be loaded. This variable + * will be reset either the first time acceptNavigationRequest() is called with a different URL or when certificateError() is called. + * + * This variable is needed to implement certificateError(). + * + */ + QUrl m_urlLoadedByPart; }; /** * 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 0655a2dd0..711ee3976 100644 --- a/webenginepart/src/webenginepart.cpp +++ b/webenginepart/src/webenginepart.cpp @@ -1,958 +1,959 @@ /* * This file is part of the KDE project. * * Copyright (C) 2007 Trolltech ASA * Copyright (C) 2008 - 2010 Urs Wolfer * Copyright (C) 2008 Laurent Montel * Copyright (C) 2009 Dawit Alemayehu * Copyright (C) 2013 Allan Sandfeld Jensen * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by the * Free Software Foundation; either version 2.1 of the License, or (at your * option) any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ #include "webenginepart.h" #include "webenginepartkiohandler.h" //#include #include #include #include #include "webenginepart_ext.h" #include "webengineview.h" #include "webenginepage.h" #include "websslinfo.h" #include "webhistoryinterface.h" #include "webenginewallet.h" #include "webengineparterrorschemehandler.h" #include "webenginepartcookiejar.h" #include "ui/searchbar.h" #include "ui/passwordbar.h" #include "ui/featurepermissionbar.h" #include "settings/webenginesettings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "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_wallet(Q_NULLPTR) { QWebEngineProfile *prof = QWebEngineProfile::defaultProfile(); if (!prof->urlSchemeHandler("error")) { prof->installUrlSchemeHandler("error", new WebEnginePartErrorSchemeHandler(prof)); prof->installUrlSchemeHandler("help", new WebEnginePartKIOHandler(prof)); } static WebEnginePartCookieJar s_cookieJar(prof, prof); KAboutData about = KAboutData(QStringLiteral("webenginepart"), i18nc("Program Name", "WebEnginePart"), /*version*/ QStringLiteral("1.3.0"), i18nc("Short Description", "QtWebEngine Browser Engine Component"), KAboutLicense::LGPL, i18n("(C) 2009-2010 Dawit Alemayehu\n" "(C) 2008-2010 Urs Wolfer\n" "(C) 2007 Trolltech ASA")); about.addAuthor(i18n("Sune Vuorela"), i18n("Maintainer, Developer"), QStringLiteral("sune@kde.org")); about.addAuthor(i18n("Dawit Alemayehu"), i18n("Developer"), QStringLiteral("adawit@kde.org")); about.addAuthor(i18n("Urs Wolfer"), i18n("Maintainer, Developer"), QStringLiteral("uwolfer@kde.org")); about.addAuthor(i18n("Michael Howell"), i18n("Developer"), QStringLiteral("mhowell123@gmail.com")); about.addAuthor(i18n("Laurent Montel"), i18n("Developer"), QStringLiteral("montel@kde.org")); about.addAuthor(i18n("Dirk Mueller"), i18n("Developer"), QStringLiteral("mueller@kde.org")); about.setProductName("webenginepart/general"); // KComponentData componentData(&about); setComponentData(about, false /*don't load plugins yet*/); #if 0 // NOTE: If the application does not set its version number, we automatically // set it to KDE's version number so that the default user-agent string contains // proper application version number information. See QWebEnginePage::userAgentForUrl... if (QCoreApplication::applicationVersion().isEmpty()) QCoreApplication::setApplicationVersion(QString("%1.%2.%3") .arg(KDE::versionMajor()) .arg(KDE::versionMinor()) .arg(KDE::versionRelease())); #endif setXMLFile(QL1S("webenginepart.rc")); // Create this KPart's widget QWidget *mainWidget = new QWidget (parentWidget); mainWidget->setObjectName(QStringLiteral("webenginepart")); // Create the WebEngineView... m_webView = new WebEngineView (this, parentWidget); // Create the browser extension. m_browserExtension = new WebEngineBrowserExtension(this, cachedHistory); // Add status bar extension... m_statusBarExtension = new KParts::StatusBarExtension(this); // Add a web history interface for storing visited links. // if (!QWebEngineHistoryInterface::defaultInterface()) // QWebHistoryInterface::setDefaultInterface(new WebHistoryInterface(this)); // Add text and html extensions... new WebEngineTextExtension(this); new WebEngineHtmlExtension(this); new WebEngineScriptableExtension(this); // Layout the GUI... QVBoxLayout* l = new QVBoxLayout(mainWidget); l->setContentsMargins(0, 0, 0, 0); l->setSpacing(0); l->addWidget(m_webView); // Set the part's widget setWidget(mainWidget); // Set the web view as the focus object mainWidget->setFocusProxy(m_webView); // Connect the signals from the webview connect(m_webView, &QWebEngineView::titleChanged, this, &Part::setWindowCaption); connect(m_webView, &QWebEngineView::urlChanged, this, &WebEnginePart::slotUrlChanged); // connect(m_webView, SIGNAL(linkMiddleOrCtrlClicked(QUrl)), // this, SLOT(slotLinkMiddleOrCtrlClicked(QUrl))); // connect(m_webView, SIGNAL(selectionClipboardUrlPasted(QUrl,QString)), // this, SLOT(slotSelectionClipboardUrlPasted(QUrl,QString))); connect(m_webView, &QWebEngineView::loadFinished, this, &WebEnginePart::slotLoadFinished); // Connect the signals from the page... connectWebEnginePageSignals(page()); // Init the QAction we are going to use... initActions(); // Load plugins once we are fully ready loadPlugins(); setWallet(page()->wallet()); } WebEnginePart::~WebEnginePart() { } WebEnginePage* WebEnginePart::page() { if (m_webView) return qobject_cast(m_webView->page()); return 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); } }); } void WebEnginePart::setWallet(WebEngineWallet* wallet) { if(m_wallet){ disconnect(m_wallet, &WebEngineWallet::saveFormDataRequested, this, &WebEnginePart::slotSaveFormDataRequested); disconnect(m_wallet, &WebEngineWallet::fillFormRequestCompleted, this, &WebEnginePart::slotFillFormRequestCompleted); disconnect(m_wallet, &WebEngineWallet::walletClosed, this, &WebEnginePart::slotWalletClosed); } m_wallet = wallet; if (m_wallet) { connect(m_wallet, &WebEngineWallet::saveFormDataRequested, this, &WebEnginePart::slotSaveFormDataRequested); connect(m_wallet, &WebEngineWallet::fillFormRequestCompleted, this, &WebEnginePart::slotFillFormRequestCompleted); connect(m_wallet, &WebEngineWallet::walletClosed, this, &WebEnginePart::slotWalletClosed); } } void WebEnginePart::attemptInstallKIOSchemeHandler(const QUrl& url) { if (KProtocolManager::defaultMimetype(url) == "text/html") { // man:, info:, etc. QWebEngineProfile *prof = QWebEngineProfile::defaultProfile(); QByteArray scheme = url.scheme().toUtf8(); if (!prof->urlSchemeHandler(scheme)) { prof->installUrlSchemeHandler(scheme, new WebEnginePartKIOHandler(prof)); } } } bool WebEnginePart::openUrl(const QUrl &_u) { QUrl u (_u); 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); } } attemptInstallKIOSchemeHandler(u); // Set URL in KParts before emitting started; konq plugins rely on that. setUrl(u); m_doLoadFinishedActions = true; + page()->setLoadUrlCalledByPart(u); m_webView->loadUrl(u, args, bargs); return true; } bool WebEnginePart::closeUrl() { m_webView->triggerPageAction(QWebEnginePage::Stop); m_webView->stop(); return true; } QWebEngineView* WebEnginePart::view() { 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 { // Attempt to fill the web form... 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()) return; page()->wallet()->removeFormData(page()); m_hasCachedFormData = false; } void WebEnginePart::slotSetTextEncoding(QTextCodec * codec) { // FIXME: The code below that sets the text encoding has been reported not to work. if (!page()) return; QWebEngineSettings *localSettings = page()->settings(); if (!localSettings) return; 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())); QBoxLayout* lay = qobject_cast<QBoxLayout*>(widget()->layout()); if (lay) lay->insertWidget(0, m_featurePermissionBar); } m_featurePermissionBar->setFeature(feature); // m_featurePermissionBar->setText(i18n("<html>Do you want to grant the site <b>%1</b> " // "access to information about your current physical location?", // url.host())); m_featurePermissionBar->setText(i18n("<html>Do you want to grant the site " "access to information about your current physical location?")); m_featurePermissionBar->animatedShow(); } void WebEnginePart::slotFeaturePermissionGranted(QWebEnginePage::Feature feature) { Q_ASSERT(m_featurePermissionBar && m_featurePermissionBar->feature() == feature); page()->setFeaturePermission(page()->url(), feature, QWebEnginePage::PermissionGrantedByUser); } void WebEnginePart::slotFeaturePermissionDenied(QWebEnginePage::Feature feature) { Q_ASSERT(m_featurePermissionBar && m_featurePermissionBar->feature() == feature); page()->setFeaturePermission(page()->url(), feature, QWebEnginePage::PermissionDeniedByUser); } void WebEnginePart::slotSaveFormDataRequested (const QString& key, const QUrl& url) { if (WebEngineSettings::self()->isNonPasswordStorableSite(url.host())) return; if (!WebEngineSettings::self()->askToSaveSitePassword()) return; if (m_passwordBar && m_passwordBar->isVisible()) return; if (!m_passwordBar) { m_passwordBar = new PasswordBar(widget()); if (!m_wallet) { qDebug() << "No m_wallet instance found! This should never happen!"; return; } connect(m_passwordBar, SIGNAL(saveFormDataAccepted(QString)), m_wallet, SLOT(acceptSaveFormDataRequest(QString))); connect(m_passwordBar, SIGNAL(saveFormDataRejected(QString)), m_wallet, SLOT(rejectSaveFormDataRequest(QString))); connect(m_passwordBar, SIGNAL(done()), this, SLOT(slotSaveFormDataDone())); } Q_ASSERT(m_passwordBar); m_passwordBar->setUrl(url); m_passwordBar->setRequestKey(key); m_passwordBar->setText(i18n("<html>Do you want %1 to remember the login " "information for <b>%2</b>?</html>", QCoreApplication::applicationName(), url.host())); QBoxLayout* lay = qobject_cast<QBoxLayout*>(widget()->layout()); if (lay) lay->insertWidget(0, m_passwordBar); m_passwordBar->animatedShow(); } void WebEnginePart::slotSaveFormDataDone() { if (!m_passwordBar) return; QBoxLayout* lay = qobject_cast<QBoxLayout*>(widget()->layout()); if (lay) lay->removeWidget(m_passwordBar); } void WebEnginePart::addWalletStatusBarIcon () { if (m_statusBarWalletLabel) { m_statusBarExtension->removeStatusBarItem(m_statusBarWalletLabel); } else { m_statusBarWalletLabel = new KUrlLabel(m_statusBarExtension->statusBar()); m_statusBarWalletLabel->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum)); m_statusBarWalletLabel->setUseCursor(false); m_statusBarWalletLabel->setPixmap(QIcon::fromTheme(QStringLiteral("wallet-open")).pixmap(QSize(16,16))); connect(m_statusBarWalletLabel, SIGNAL(leftClickedUrl()), SLOT(slotLaunchWalletManager())); connect(m_statusBarWalletLabel, SIGNAL(rightClickedUrl()), SLOT(slotShowWalletMenu())); } m_statusBarExtension->addStatusBarItem(m_statusBarWalletLabel, 0, false); } void WebEnginePart::slotFillFormRequestCompleted (bool ok) { if ((m_hasCachedFormData = ok)) addWalletStatusBarIcon(); }