File indexing completed on 2024-05-19 05:01:20
0001 /* 0002 This file is part of the KDE project. 0003 0004 SPDX-FileCopyrightText: 2008 Dirk Mueller <mueller@kde.org> 0005 SPDX-FileCopyrightText: 2008-2010 Urs Wolfer <uwolfer @ kde.org> 0006 SPDX-FileCopyrightText: 2009 Dawit Alemayehu <adawit@kde.org> 0007 0008 SPDX-License-Identifier: LGPL-2.1-or-later 0009 */ 0010 0011 #include "webenginepage.h" 0012 0013 #include "webenginepart.h" 0014 #include "webengineview.h" 0015 #include "settings/webenginesettings.h" 0016 #include "webenginepartdownloadmanager.h" 0017 #include "webenginewallet.h" 0018 #include <webenginepart_debug.h> 0019 #include "webenginepartcontrols.h" 0020 #include "navigationrecorder.h" 0021 #include "profile.h" 0022 #include "webenginepart_ext.h" 0023 #include "qtwebengine6compat.h" 0024 0025 #include "libkonq_utils.h" 0026 #include "interfaces/browser.h" 0027 0028 #include <QWebEngineCertificateError> 0029 #include <QWebEngineSettings> 0030 #include <QWebEngineProfile> 0031 #include <KDialogJobUiDelegate> 0032 #include <QWebEngineView> 0033 0034 #include <KMessageBox> 0035 #include <KLocalizedString> 0036 #include <KShell> 0037 #include <KAuthorized> 0038 #include <KStringHandler> 0039 #include <KUrlAuthorized> 0040 #include <KSharedConfig> 0041 #include <KIO/AuthInfo> 0042 #include <KIO/Job> 0043 #include <KIO/CommandLauncherJob> 0044 #include <KJobTrackerInterface> 0045 #include <KUserTimestamp> 0046 #include <KPasswdServerClient> 0047 #include <KJobWidgets> 0048 #include <KPluginMetaData> 0049 #include <KIO/JobUiDelegateFactory> 0050 0051 #include <QStandardPaths> 0052 #include <QScreen> 0053 #include <QFileDialog> 0054 #include <QDialogButtonBox> 0055 #include <QMimeDatabase> 0056 0057 #include <QFile> 0058 #include <QAuthenticator> 0059 #include <QApplication> 0060 #include <QNetworkReply> 0061 #include <QTimer> 0062 #include <QWebEngineHistory> 0063 #include <QWebEngineHistoryItem> 0064 #include <QUrlQuery> 0065 #include <KConfigGroup> 0066 #include <KToggleFullScreenAction> 0067 //#include <QWebSecurityOrigin> 0068 #include "utils.h" 0069 #include "htmlextension.h" 0070 0071 using namespace KonqInterfaces; 0072 0073 WebEnginePage::WebEnginePage(WebEnginePart *part, QWidget *parent) 0074 : QWebEnginePage(KonqWebEnginePart::Profile::defaultProfile(), parent), 0075 m_kioErrorCode(0), 0076 m_ignoreError(false), 0077 m_part(part), 0078 m_passwdServerClient(new KPasswdServerClient) 0079 #ifndef REMOTE_DND_NOT_HANDLED_BY_WEBENGINE 0080 , m_dropOperationTimer(new QTimer(this)) 0081 #endif 0082 { 0083 if (view()) { 0084 WebEngineSettings::self()->computeFontSizes(view()->logicalDpiY()); 0085 } 0086 0087 //setForwardUnsupportedContent(true); 0088 0089 connect(this, &QWebEnginePage::geometryChangeRequested, 0090 this, &WebEnginePage::slotGeometryChangeRequested); 0091 // connect(this, SIGNAL(unsupportedContent(QNetworkReply*)), 0092 // this, SLOT(slotUnsupportedContent(QNetworkReply*))); 0093 connect(this, &QWebEnginePage::featurePermissionRequested, 0094 this, &WebEnginePage::slotFeaturePermissionRequested); 0095 connect(this, &QWebEnginePage::loadFinished, 0096 this, &WebEnginePage::slotLoadFinished); 0097 connect(this, &QWebEnginePage::authenticationRequired, 0098 this, &WebEnginePage::slotAuthenticationRequired); 0099 connect(this, &QWebEnginePage::fullScreenRequested, this, &WebEnginePage::changeFullScreenMode); 0100 connect(this, &QWebEnginePage::recommendedStateChanged, this, &WebEnginePage::changeLifecycleState); 0101 0102 #ifndef REMOTE_DND_NOT_HANDLED_BY_WEBENGINE 0103 connect(this, &QWebEnginePage::loadStarted, this, [this](){m_dropOperationTimer->stop();}); 0104 m_dropOperationTimer->setSingleShot(true); 0105 #endif 0106 0107 #if QT_VERSION_MAJOR == 6 0108 connect(this, &QWebEnginePage::certificateError, this, &WebEnginePage::handleCertificateError); 0109 #endif 0110 0111 //If this part is displaying the developer tools for another part, inform the other page it's not displaying the developer tools anymore. 0112 //I'm not sure this is needed, but I think it's better to do it, just to be on the safe side 0113 auto unsetInspectedPageIfNeeded = [this](bool ok) { 0114 if (ok && inspectedPage() && url().scheme() != QLatin1String("devtools")) { 0115 setInspectedPage(nullptr); 0116 } 0117 }; 0118 connect(this, &QWebEnginePage::loadFinished, this, unsetInspectedPageIfNeeded); 0119 0120 WebEnginePartControls::self()->navigationRecorder()->registerPage(this); 0121 m_part->downloadManager()->addPage(this); 0122 0123 connect(WebEnginePartControls::self(), &WebEnginePartControls::updateStyleSheet, this, &WebEnginePage::updateUserStyleSheet); 0124 } 0125 0126 WebEnginePage::~WebEnginePage() 0127 { 0128 //qCDebug(WEBENGINEPART_LOG) << this; 0129 } 0130 0131 const WebSslInfo& WebEnginePage::sslInfo() const 0132 { 0133 return m_sslInfo; 0134 } 0135 0136 #if QT_VERSION_MAJOR > 5 0137 QWidget *WebEnginePage::view() const 0138 { 0139 return QWebEngineView::forPage(this); 0140 } 0141 #endif 0142 0143 void WebEnginePage::setSslInfo (const WebSslInfo& info) 0144 { 0145 m_sslInfo = info; 0146 } 0147 0148 static QString checkForDownloadManager(QWidget* widget) 0149 { 0150 KConfigGroup cfg (KSharedConfig::openConfig(QStringLiteral("konquerorrc"), KConfig::NoGlobals), "HTML Settings"); 0151 const QString fileName (cfg.readPathEntry("DownloadManager", QString())); 0152 if (fileName.isEmpty()) { 0153 return QString(); 0154 } 0155 0156 const QString exeName = QStandardPaths::findExecutable(fileName); 0157 if (exeName.isEmpty()) { 0158 KMessageBox::detailedError(widget, 0159 i18n("The download manager (%1) could not be found in your installation.", fileName), 0160 i18n("Try to reinstall it and make sure that it is available in $PATH. \n\nThe integration will be disabled.")); 0161 cfg.writePathEntry("DownloadManager", QString()); 0162 cfg.sync(); 0163 return QString(); 0164 } 0165 return exeName; 0166 } 0167 0168 bool WebEnginePage::downloadWithExternalDonwloadManager(const QUrl &url) 0169 { 0170 if (url.isLocalFile()) { 0171 return false; 0172 } 0173 0174 KConfigGroup cfg (KSharedConfig::openConfig(QStringLiteral("konquerorrc"), KConfig::NoGlobals), "HTML Settings"); 0175 const QString fileName (cfg.readPathEntry("DownloadManager", QString())); 0176 if (fileName.isEmpty()) { 0177 return false; 0178 } 0179 0180 const QString managerExe = QStandardPaths::findExecutable(fileName); 0181 if (managerExe.isEmpty()) { 0182 KMessageBox::detailedError(view(), 0183 i18n("The download manager (%1) could not be found in your installation.", fileName), 0184 i18n("Try to reinstall it and make sure that it is available in $PATH. \n\nThe integration will be disabled.")); 0185 cfg.writePathEntry("DownloadManager", QString()); 0186 cfg.sync(); 0187 return false; 0188 } 0189 0190 //qCDebug(WEBENGINEPART_LOG) << "Calling command" << cmd; 0191 KIO::CommandLauncherJob *job = new KIO::CommandLauncherJob(managerExe, {url.toString()}); 0192 job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, view())); 0193 job->start(); 0194 return true; 0195 } 0196 0197 void WebEnginePage::requestDownload(QWebEngineDownloadRequest *item, bool newWindow, WebEnginePartDownloadManager::DownloadObjective objective) 0198 { 0199 QUrl url = item->url(); 0200 if (downloadWithExternalDonwloadManager(url)) { 0201 item->cancel(); 0202 item->deleteLater(); 0203 return; 0204 } 0205 0206 WebEngineDownloaderExtension *downloader = m_part->downloader(); 0207 Q_ASSERT(downloader); 0208 0209 downloader->addDownloadRequest(item); 0210 0211 BrowserArguments bArgs; 0212 bArgs.setForcesNewWindow(newWindow); 0213 KParts::OpenUrlArguments args; 0214 0215 QMimeDatabase db; 0216 QMimeType mime = db.mimeTypeForName(item->mimeType()); 0217 if (!mime.isValid() || mime.isDefault()) { 0218 mime = db.mimeTypeForFile(item->suggestedFileName(), QMimeDatabase::MatchExtension); 0219 } 0220 args.setMimeType(mime.name()); 0221 0222 args.metaData().insert(QStringLiteral("DontSendToDefaultHTMLPart"), QString()); 0223 args.metaData().insert(QStringLiteral("SuggestedFileName"), item->suggestedFileName()); 0224 0225 if (objective == WebEnginePartDownloadManager::DownloadObjective::SaveOnly) { 0226 args.metaData().insert(QStringLiteral("EmbedOrNothing"), {}); 0227 bArgs.setForcesNewWindow(true); 0228 saveUrlToDiskAndDisplay(item, args, bArgs); 0229 return; 0230 } else if (objective == WebEnginePartDownloadManager::DownloadObjective::SaveAs) { 0231 saveAs(item); 0232 } else { 0233 //TODO KF6: this lamba only exists to reduce code duplication in the two #ifdef 0234 //branches below. When KF5 compatibility will be removed, cookies will always 0235 //be managed internally, so remove the #ifdef and the lambda and move its body 0236 //inside the `if (downloader)` 0237 auto requestDownloadAndOpen = [&]() { 0238 args.metaData().insert(QStringLiteral("TempFile"), QString()); 0239 emit downloader->downloadAndOpenUrl(url, item->id(), args, bArgs, true); 0240 if (item->state() == QWebEngineDownloadRequest::DownloadRequested) { 0241 qCDebug(WEBENGINEPART_LOG()) << "Automatically accepting download for" << item->url() << "This shouldn't happen"; 0242 item->accept(); 0243 } 0244 }; 0245 0246 //TODO KF6: remove #ifdef and the #else block when dropping compatibility with KF5 0247 #ifdef MANAGE_COOKIES_INTERNALLY 0248 requestDownloadAndOpen(); 0249 #else //This can only happen in KF5, as MANAGE_COOKIES_INTERNALLY is always defined in KF6 0250 // blob URLs can't be downloaded by KIO, so use the part to download them even when using KCookieJar 0251 if (item->url().scheme() == QStringLiteral("blob") && downloader) { 0252 requestDownloadAndOpen(); 0253 } else { 0254 emit m_part->browserExtension()->openUrlRequest(url, args, bArgs); 0255 item->cancel(); 0256 item->deleteLater(); 0257 } 0258 #endif 0259 } 0260 } 0261 0262 void WebEnginePage::saveUrlToDiskAndDisplay(QWebEngineDownloadRequest* req, const KParts::OpenUrlArguments& args, const BrowserArguments& bArgs) 0263 { 0264 QWidget *window = view() ? view()->window() : nullptr; 0265 0266 QString suggestedName = !req->suggestedFileName().isEmpty() ? req->suggestedFileName() : req->url().fileName(); 0267 QString downloadPath = Konq::askDownloadLocation(suggestedName, window); 0268 if (downloadPath.isEmpty()) { 0269 req->cancel(); 0270 return; 0271 } 0272 0273 WebEngineDownloaderExtension *downloader = m_part->downloader(); 0274 DownloaderJob *job = downloader->downloadJob(req->url(), req->id(), this); 0275 if (!job) { 0276 return; 0277 } 0278 0279 auto lambda = [this, args, bArgs](DownloaderJob *, const QUrl &url) { 0280 #if QT_VERSION_MAJOR < 6 0281 emit m_part->browserExtension()->openUrlRequest(url, args, bArgs); 0282 #else 0283 emit m_part->browserExtension()->browserOpenUrlRequest(url, args, bArgs); 0284 #endif 0285 }; 0286 job->startDownload(downloadPath, window, this, lambda); 0287 } 0288 0289 void WebEnginePage::saveAs(QWebEngineDownloadRequest* req) 0290 { 0291 QWidget *window = view() ? view()->window() : nullptr; 0292 0293 QString suggestedName = !req->suggestedFileName().isEmpty() ? req->suggestedFileName() : req->url().fileName(); 0294 QString downloadPath = Konq::askDownloadLocation(suggestedName, window); 0295 if (downloadPath.isEmpty()) { 0296 req->cancel(); 0297 return; 0298 } 0299 0300 WebEngineDownloaderExtension *downloader = m_part->downloader(); 0301 DownloaderJob *job = downloader->downloadJob(req->url(), req->id(), this); 0302 if (!job) { 0303 return; 0304 } 0305 0306 auto lambda = [this](DownloaderJob *dj, const QUrl &url) { 0307 if (dj->error() == 0) { 0308 m_part->openUrl(url); 0309 return; 0310 } 0311 BrowserArguments bArgs; 0312 bArgs.setForcesNewWindow(true); 0313 #if QT_VERSION_MAJOR < 6 0314 emit m_part->browserExtension()->openUrlRequest(url, {}, bArgs); 0315 #else 0316 emit m_part->browserExtension()->browserOpenUrlRequest(url, {}, bArgs); 0317 #endif 0318 }; 0319 job->startDownload(downloadPath, window, this, lambda); 0320 } 0321 0322 # ifndef REMOTE_DND_NOT_HANDLED_BY_WEBENGINE 0323 void WebEnginePage::setDropOperationStarted() 0324 { 0325 m_dropOperationTimer->start(100); 0326 } 0327 #endif 0328 0329 0330 QWebEnginePage *WebEnginePage::createWindow(WebWindowType type) 0331 { 0332 #ifndef REMOTE_DND_NOT_HANDLED_BY_WEBENGINE 0333 if (m_dropOperationTimer->isActive()) { 0334 m_dropOperationTimer->stop(); 0335 return this; 0336 } 0337 #endif 0338 0339 //qCDebug(WEBENGINEPART_LOG) << "window type:" << type; 0340 // Crete an instance of NewWindowPage class to capture all the 0341 // information we need to create a new window. See documentation of 0342 // the class for more information... 0343 NewWindowPage* page = new NewWindowPage(type, part()); 0344 return page; 0345 } 0346 0347 // Returns true if the scheme and domain of the two urls match... 0348 static bool domainSchemeMatch(const QUrl& u1, const QUrl& u2) 0349 { 0350 if (u1.scheme() != u2.scheme()) 0351 return false; 0352 0353 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 0354 QStringList u1List = u1.host().split(QL1C('.'), QString::SkipEmptyParts); 0355 #else 0356 QStringList u1List = u1.host().split(QL1C('.'), Qt::SkipEmptyParts); 0357 #endif 0358 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 0359 QStringList u2List = u2.host().split(QL1C('.'), QString::SkipEmptyParts); 0360 #else 0361 QStringList u2List = u2.host().split(QL1C('.'), Qt::SkipEmptyParts); 0362 #endif 0363 0364 if (qMin(u1List.count(), u2List.count()) < 2) 0365 return false; // better safe than sorry... 0366 0367 while (u1List.count() > 2) 0368 u1List.removeFirst(); 0369 0370 while (u2List.count() > 2) 0371 u2List.removeFirst(); 0372 0373 return (u1List == u2List); 0374 } 0375 0376 bool WebEnginePage::askBrowserToOpenUrl(const QUrl& url, const QString& mimetype, const KParts::OpenUrlArguments &_args, const BrowserArguments &bargs) 0377 { 0378 KParts::OpenUrlArguments args(_args); 0379 args.setMimeType(mimetype); 0380 args.metaData().insert("DontSendToDefaultHTMLPart", ""); 0381 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0382 emit m_part->browserExtension()->openUrlRequest(url, args, bargs); 0383 #else 0384 emit m_part->browserExtension()->browserOpenUrlRequest(url, args, bargs); 0385 #endif 0386 return false; 0387 } 0388 0389 bool WebEnginePage::shouldOpenUrl(const QUrl& url) const 0390 { 0391 static const QStringList s_forcedSchemes{QStringLiteral("remote"), QStringLiteral("trash")}; 0392 if (s_forcedSchemes.contains(url.scheme())) { 0393 return false; 0394 } 0395 if (!url.isLocalFile()) { 0396 return true; 0397 } 0398 BrowserInterface *bi = m_part->browserExtension()->browserInterface(); 0399 bool useThisPart = false; 0400 //We don't check whether bi is valid, as invokeMethod will fail if it's nullptr 0401 //If invokeMethod fails, useThisPart will keep its default value (false) which is what we need to return, so there's no 0402 //need to check the return value of invokeMethod 0403 QMetaObject::invokeMethod(bi, "isCorrectPartForLocalFile", Q_RETURN_ARG(bool, useThisPart), Q_ARG(KParts::ReadOnlyPart*, part()), Q_ARG(QString, url.path())); 0404 return useThisPart; 0405 } 0406 0407 bool WebEnginePage::acceptNavigationRequest(const QUrl& url, NavigationType type, bool isMainFrame) 0408 { 0409 //Ask the browser for permission to navigate away. In Konqueror, if a view is locked, it can't navigate to somewhere else 0410 if (isMainFrame) { 0411 KonqInterfaces::Browser *browser = KonqInterfaces::Browser::browser(qApp); 0412 if (browser && !browser->canNavigateTo(part(), url)) { 0413 return false; 0414 } 0415 } 0416 0417 if (isMainFrame) { 0418 if (!shouldOpenUrl(url)) { 0419 return askBrowserToOpenUrl(url); 0420 } 0421 } 0422 0423 QUrl reqUrl(url); 0424 0425 // Handle "mailto:" url here... 0426 if (handleMailToUrl(reqUrl, type)) 0427 return false; 0428 0429 const bool isTypedUrl = property("NavigationTypeUrlEntered").toBool(); 0430 0431 /* 0432 NOTE: We use a dynamic QObject property called "NavigationTypeUrlEntered" 0433 to distinguish between requests generated by user entering a url vs those 0434 that were generated programmatically through javascript (AJAX requests). 0435 */ 0436 if (isMainFrame && isTypedUrl) 0437 setProperty("NavigationTypeUrlEntered", QVariant()); 0438 0439 // inPage requests are those generarted within the current page through 0440 // link clicks, javascript queries, and button clicks (form submission). 0441 bool inPageRequest = true; 0442 switch (type) { 0443 case QWebEnginePage::NavigationTypeFormSubmitted: 0444 if (!checkFormData(url)) 0445 return false; 0446 if (part() && part()->wallet()) { 0447 part()->wallet()->saveFormsInPage(this); 0448 } 0449 0450 break; 0451 #if 0 0452 case QWebEnginePage::NavigationTypeFormResubmitted: 0453 if (!checkFormData(request)) 0454 return false; 0455 if (KMessageBox::warningContinueCancel(view(), 0456 i18n("<qt><p>To display the requested web page again, " 0457 "the browser needs to resend information you have " 0458 "previously submitted.</p>" 0459 "<p>If you were shopping online and made a purchase, " 0460 "click the Cancel button to prevent a duplicate purchase." 0461 "Otherwise, click the Continue button to display the web" 0462 "page again.</p>"), 0463 i18n("Resubmit Information")) == KMessageBox::Cancel) { 0464 return false; 0465 } 0466 break; 0467 #endif 0468 case QWebEnginePage::NavigationTypeBackForward: 0469 // If history navigation is locked, ignore all such requests... 0470 if (property("HistoryNavigationLocked").toBool()) { 0471 setProperty("HistoryNavigationLocked", QVariant()); 0472 qCDebug(WEBENGINEPART_LOG) << "Rejected history navigation because 'HistoryNavigationLocked' property is set!"; 0473 return false; 0474 } 0475 //qCDebug(WEBENGINEPART_LOG) << "Navigating to item (" << history()->currentItemIndex() 0476 // << "of" << history()->count() << "):" << history()->currentItem().url(); 0477 inPageRequest = false; 0478 break; 0479 case QWebEnginePage::NavigationTypeReload: 0480 // setRequestMetaData(QL1S("cache"), QL1S("reload")); 0481 inPageRequest = false; 0482 break; 0483 case QWebEnginePage::NavigationTypeOther: // triggered by javascript 0484 qCDebug(WEBENGINEPART_LOG) << "Triggered by javascript"; 0485 inPageRequest = !isTypedUrl; 0486 break; 0487 default: 0488 break; 0489 } 0490 0491 if (inPageRequest) { 0492 // if (!checkLinkSecurity(request, type)) 0493 // return false; 0494 0495 // if (m_sslInfo.isValid()) 0496 // setRequestMetaData(QL1S("ssl_was_in_use"), QL1S("TRUE")); 0497 } 0498 0499 0500 // Honor the enabling/disabling of plugins per host. 0501 settings()->setAttribute(QWebEngineSettings::PluginsEnabled, WebEngineSettings::self()->isPluginsEnabled(reqUrl.host())); 0502 0503 if (isMainFrame) { 0504 emit mainFrameNavigationRequested(this, url); 0505 } 0506 return QWebEnginePage::acceptNavigationRequest(url, type, isMainFrame); 0507 } 0508 0509 #if 0 0510 static int errorCodeFromReply(QNetworkReply* reply) 0511 { 0512 // First check if there is a KIO error code sent back and use that, 0513 // if not attempt to convert QNetworkReply's NetworkError to KIO::Error. 0514 QVariant attr = reply->attribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::KioError)); 0515 if (attr.isValid() && attr.type() == QVariant::Int) 0516 return attr.toInt(); 0517 0518 switch (reply->error()) { 0519 case QNetworkReply::ConnectionRefusedError: 0520 return KIO::ERR_CANNOT_CONNECT; 0521 case QNetworkReply::HostNotFoundError: 0522 return KIO::ERR_UNKNOWN_HOST; 0523 case QNetworkReply::TimeoutError: 0524 return KIO::ERR_SERVER_TIMEOUT; 0525 case QNetworkReply::OperationCanceledError: 0526 return KIO::ERR_USER_CANCELED; 0527 case QNetworkReply::ProxyNotFoundError: 0528 return KIO::ERR_UNKNOWN_PROXY_HOST; 0529 case QNetworkReply::ContentAccessDenied: 0530 return KIO::ERR_ACCESS_DENIED; 0531 case QNetworkReply::ContentOperationNotPermittedError: 0532 return KIO::ERR_WRITE_ACCESS_DENIED; 0533 case QNetworkReply::ContentNotFoundError: 0534 return KIO::ERR_NO_CONTENT; 0535 case QNetworkReply::AuthenticationRequiredError: 0536 return KIO::ERR_CANNOT_AUTHENTICATE; 0537 case QNetworkReply::ProtocolUnknownError: 0538 return KIO::ERR_UNSUPPORTED_PROTOCOL; 0539 case QNetworkReply::ProtocolInvalidOperationError: 0540 return KIO::ERR_UNSUPPORTED_ACTION; 0541 case QNetworkReply::UnknownNetworkError: 0542 return KIO::ERR_UNKNOWN; 0543 case QNetworkReply::NoError: 0544 default: 0545 break; 0546 } 0547 0548 return 0; 0549 } 0550 #endif 0551 0552 #if QT_VERSION_MAJOR < 6 0553 bool WebEnginePage::certificateError(const QWebEngineCertificateError& ce) 0554 { 0555 return WebEnginePartControls::self()->handleCertificateError(ce, this); 0556 } 0557 #else 0558 void WebEnginePage::handleCertificateError(const QWebEngineCertificateError &ce) { 0559 WebEnginePartControls::self()->handleCertificateError(ce, this); 0560 } 0561 #endif 0562 0563 0564 WebEnginePart* WebEnginePage::part() const 0565 { 0566 return m_part.data(); 0567 } 0568 0569 void WebEnginePage::setPart(WebEnginePart* part) 0570 { 0571 m_part = part; 0572 } 0573 0574 void WebEnginePage::slotLoadFinished(bool ok) 0575 { 0576 QUrl requestUrl = url(); 0577 requestUrl.setUserInfo(QString()); 0578 #if 0 0579 const bool shouldResetSslInfo = (m_sslInfo.isValid() && !domainSchemeMatch(requestUrl, m_sslInfo.url())); 0580 QWebFrame* frame = qobject_cast<QWebFrame *>(reply->request().originatingObject()); 0581 if (!frame) 0582 return; 0583 const bool isMainFrameRequest = (frame == mainFrame()); 0584 #else 0585 // PORTING_TODO 0586 const bool isMainFrameRequest = true; 0587 #endif 0588 0589 #if 0 0590 // Only deal with non-redirect responses... 0591 const QVariant redirectVar = reply->attribute(QNetworkRequest::RedirectionTargetAttribute); 0592 0593 if (isMainFrameRequest && redirectVar.isValid()) { 0594 m_sslInfo.restoreFrom(reply->attribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::MetaData)), 0595 reply->url(), shouldResetSslInfo); 0596 return; 0597 } 0598 0599 const int errCode = errorCodeFromReply(reply); 0600 qCDebug(WEBENGINEPART_LOG) << frame << "is main frame request?" << isMainFrameRequest << requestUrl; 0601 #endif 0602 0603 if (ok) { 0604 if (isMainFrameRequest) { 0605 #if 0 0606 m_sslInfo.restoreFrom(reply->attribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::MetaData)), 0607 reply->url(), shouldResetSslInfo); 0608 #endif 0609 setPageJScriptPolicy(url()); 0610 } 0611 } else { 0612 // Handle any error... 0613 #if 0 0614 switch (errCode) { 0615 case 0: 0616 case KIO::ERR_NO_CONTENT: 0617 break; 0618 case KIO::ERR_ABORTED: 0619 case KIO::ERR_USER_CANCELED: // Do nothing if request is cancelled/aborted 0620 //qCDebug(WEBENGINEPART_LOG) << "User aborted request!"; 0621 m_ignoreError = true; 0622 emit loadAborted(QUrl()); 0623 return; 0624 // Handle the user clicking on a link that refers to a directory 0625 // Since KIO cannot automatically convert a GET request to a LISTDIR one. 0626 case KIO::ERR_IS_DIRECTORY: 0627 m_ignoreError = true; 0628 emit loadAborted(reply->url()); 0629 return; 0630 default: 0631 // Make sure the saveFrameStateRequested signal is emitted so 0632 // the page can restored properly. 0633 if (isMainFrameRequest) 0634 emit saveFrameStateRequested(frame, 0); 0635 0636 m_ignoreError = (reply->attribute(QNetworkRequest::User).toInt() == QNetworkReply::ContentAccessDenied); 0637 m_kioErrorCode = errCode; 0638 break; 0639 #endif 0640 } 0641 0642 if (isMainFrameRequest) { 0643 const WebEnginePageSecurity security = (m_sslInfo.isValid() ? PageEncrypted : PageUnencrypted); 0644 emit m_part->navigationExtension()->setPageSecurity(security); 0645 } 0646 } 0647 0648 void WebEnginePage::slotFeaturePermissionRequested(const QUrl& url, QWebEnginePage::Feature feature) 0649 { 0650 //url.path() is always / (meaning that permissions should be granted site-wide and not per page) 0651 QUrl thisUrl(this->url()); 0652 thisUrl.setPath("/"); 0653 thisUrl.setQuery(QString()); 0654 thisUrl.setFragment(QString()); 0655 if (url == thisUrl) { 0656 part()->slotShowFeaturePermissionBar(url, feature); 0657 return; 0658 } 0659 switch(feature) { 0660 case QWebEnginePage::Notifications: 0661 // FIXME: We should have a setting to tell if this is enabled, but so far it is always enabled. 0662 setFeaturePermission(url, feature, QWebEnginePage::PermissionGrantedByUser); 0663 break; 0664 case QWebEnginePage::Geolocation: 0665 if (KMessageBox::warningContinueCancel(nullptr, i18n("This site is attempting to " 0666 "access information about your " 0667 "physical location.\n" 0668 "Do you want to allow it access?"), 0669 i18n("Network Transmission"), 0670 KGuiItem(i18n("Allow access")), 0671 KStandardGuiItem::cancel(), 0672 QStringLiteral("WarnGeolocation")) == KMessageBox::Cancel) { 0673 setFeaturePermission(url, feature, QWebEnginePage::PermissionDeniedByUser); 0674 } else { 0675 setFeaturePermission(url, feature, QWebEnginePage::PermissionGrantedByUser); 0676 } 0677 break; 0678 default: 0679 setFeaturePermission(url, feature, QWebEnginePage::PermissionUnknown); 0680 break; 0681 } 0682 } 0683 0684 void WebEnginePage::slotGeometryChangeRequested(const QRect & rect) 0685 { 0686 const QString host = url().host(); 0687 0688 // NOTE: If a new window was created from another window which is in 0689 // maximized mode and its width and/or height were not specified at the 0690 // time of its creation, which is always the case in QWebEnginePage::createWindow, 0691 // then any move operation will seem not to work. That is because the new 0692 // window will be in maximized mode where moving it will not be possible... 0693 if (WebEngineSettings::self()->windowMovePolicy(host) == HtmlSettingsInterface::JSWindowMoveAllow && 0694 (view()->x() != rect.x() || view()->y() != rect.y())) 0695 emit m_part->navigationExtension()->moveTopLevelWidget(rect.x(), rect.y()); 0696 0697 const int height = rect.height(); 0698 const int width = rect.width(); 0699 0700 // parts of following code are based on kjs_window.cpp 0701 // Security check: within desktop limits and bigger than 100x100 (per spec) 0702 if (width < 100 || height < 100) { 0703 qCWarning(WEBENGINEPART_LOG) << "Window resize refused, window would be too small (" << width << "," << height << ")"; 0704 return; 0705 } 0706 0707 QRect sg = view()->screen()->virtualGeometry(); 0708 0709 if (width > sg.width() || height > sg.height()) { 0710 qCWarning(WEBENGINEPART_LOG) << "Window resize refused, window would be too big (" << width << "," << height << ")"; 0711 return; 0712 } 0713 0714 if (WebEngineSettings::self()->windowResizePolicy(host) == HtmlSettingsInterface::JSWindowResizeAllow) { 0715 //qCDebug(WEBENGINEPART_LOG) << "resizing to " << width << "x" << height; 0716 emit m_part->navigationExtension()->resizeTopLevelWidget(width, height); 0717 } 0718 0719 // If the window is out of the desktop, move it up/left 0720 // (maybe we should use workarea instead of sg, otherwise the window ends up below kicker) 0721 const int right = view()->x() + view()->frameGeometry().width(); 0722 const int bottom = view()->y() + view()->frameGeometry().height(); 0723 int moveByX = 0, moveByY = 0; 0724 if (right > sg.right()) 0725 moveByX = - right + sg.right(); // always <0 0726 if (bottom > sg.bottom()) 0727 moveByY = - bottom + sg.bottom(); // always <0 0728 0729 if ((moveByX || moveByY) && WebEngineSettings::self()->windowMovePolicy(host) == HtmlSettingsInterface::JSWindowMoveAllow) 0730 emit m_part->navigationExtension()->moveTopLevelWidget(view()->x() + moveByX, view()->y() + moveByY); 0731 } 0732 0733 bool WebEnginePage::checkFormData(const QUrl &url) const 0734 { 0735 const QString scheme (url.scheme()); 0736 0737 if (m_sslInfo.isValid() && 0738 !scheme.compare(QL1S("https")) && !scheme.compare(QL1S("mailto")) && 0739 (KMessageBox::warningContinueCancel(nullptr, 0740 i18n("Warning: This is a secure form " 0741 "but it is attempting to send " 0742 "your data back unencrypted.\n" 0743 "A third party may be able to " 0744 "intercept and view this " 0745 "information.\nAre you sure you " 0746 "want to send the data unencrypted?"), 0747 i18n("Network Transmission"), 0748 KGuiItem(i18n("&Send Unencrypted"))) == KMessageBox::Cancel)) { 0749 0750 return false; 0751 } 0752 0753 0754 if (scheme.compare(QL1S("mailto")) == 0 && 0755 (KMessageBox::warningContinueCancel(nullptr, i18n("This site is attempting to " 0756 "submit form data via email.\n" 0757 "Do you want to continue?"), 0758 i18n("Network Transmission"), 0759 KGuiItem(i18n("&Send Email")), 0760 KStandardGuiItem::cancel(), 0761 QStringLiteral("WarnTriedEmailSubmit")) == KMessageBox::Cancel)) { 0762 return false; 0763 } 0764 0765 return true; 0766 } 0767 0768 // Sanitizes the "mailto:" url, e.g. strips out any "attach" parameters. 0769 static QUrl sanitizeMailToUrl(const QUrl &url, QStringList& files) { 0770 QUrl sanitizedUrl; 0771 0772 // NOTE: This is necessary to ensure we can properly use QUrl's query 0773 // related APIs to process 'mailto:' urls of form 'mailto:foo@bar.com'. 0774 if (url.hasQuery()) 0775 sanitizedUrl = url; 0776 else 0777 sanitizedUrl = QUrl(url.scheme() + QL1S(":?") + url.path()); 0778 0779 QUrlQuery query(sanitizedUrl); 0780 const QList<QPair<QString, QString> > items (query.queryItems()); 0781 0782 QUrlQuery sanitizedQuery; 0783 for(auto queryItem : items) { 0784 if (queryItem.first.contains(QL1C('@')) && queryItem.second.isEmpty()) { 0785 // ### DF: this hack breaks mailto:faure@kde.org, kmail doesn't expect mailto:?to=faure@kde.org 0786 queryItem.second = queryItem.first; 0787 queryItem.first = QStringLiteral("to"); 0788 } else if (QString::compare(queryItem.first, QL1S("attach"), Qt::CaseInsensitive) == 0) { 0789 files << queryItem.second; 0790 continue; 0791 } 0792 sanitizedQuery.addQueryItem(queryItem.first, queryItem.second); 0793 } 0794 0795 sanitizedUrl.setQuery(sanitizedQuery); 0796 return sanitizedUrl; 0797 } 0798 0799 bool WebEnginePage::handleMailToUrl (const QUrl &url, NavigationType type) const 0800 { 0801 if (url.scheme() == QL1S("mailto")) { 0802 QStringList files; 0803 QUrl mailtoUrl (sanitizeMailToUrl(url, files)); 0804 0805 switch (type) { 0806 case QWebEnginePage::NavigationTypeLinkClicked: 0807 if (!files.isEmpty() && KMessageBox::warningContinueCancelList(nullptr, 0808 i18n("<qt>Do you want to allow this site to attach " 0809 "the following files to the email message?</qt>"), 0810 files, i18n("Email Attachment Confirmation"), 0811 KGuiItem(i18n("&Allow attachments")), 0812 KGuiItem(i18n("&Ignore attachments")), QL1S("WarnEmailAttachment")) == KMessageBox::Continue) { 0813 0814 // Re-add the attachments... 0815 QStringListIterator filesIt (files); 0816 QUrlQuery query(mailtoUrl); 0817 while (filesIt.hasNext()) { 0818 query.addQueryItem(QL1S("attach"), filesIt.next()); 0819 } 0820 mailtoUrl.setQuery(query); 0821 } 0822 break; 0823 case QWebEnginePage::NavigationTypeFormSubmitted: 0824 //case QWebEnginePage::NavigationTypeFormResubmitted: 0825 if (!files.isEmpty()) { 0826 KMessageBox::information(nullptr, i18n("This site attempted to attach a file from your " 0827 "computer in the form submission. The attachment " 0828 "was removed for your protection."), 0829 i18n("Attachment Removed"), QStringLiteral("InfoTriedAttach")); 0830 } 0831 break; 0832 default: 0833 break; 0834 } 0835 0836 //qCDebug(WEBENGINEPART_LOG) << "Emitting openUrlRequest with " << mailtoUrl; 0837 emit m_part->navigationExtension()->openUrlRequest(mailtoUrl); 0838 return true; 0839 } 0840 0841 return false; 0842 } 0843 0844 void WebEnginePage::setPageJScriptPolicy(const QUrl &url) 0845 { 0846 const QString hostname (url.host()); 0847 settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, 0848 WebEngineSettings::self()->isJavaScriptEnabled(hostname)); 0849 0850 const HtmlSettingsInterface::JSWindowOpenPolicy policy = WebEngineSettings::self()->windowOpenPolicy(hostname); 0851 settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, 0852 (policy != HtmlSettingsInterface::JSWindowOpenDeny && 0853 policy != HtmlSettingsInterface::JSWindowOpenSmart)); 0854 } 0855 0856 void WebEnginePage::slotAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth) 0857 { 0858 KIO::AuthInfo info; 0859 info.url = requestUrl; 0860 info.username = auth->user(); 0861 info.realmValue = auth->realm(); 0862 // If no realm metadata, then make sure path matching is turned on. 0863 info.verifyPath = info.realmValue.isEmpty(); 0864 0865 const QString errorMsg = QString(); 0866 const int ret = m_passwdServerClient->queryAuthInfo(&info, errorMsg, view()->window()->winId(), KUserTimestamp::userTimestamp()); 0867 if (ret == KJob::NoError) { 0868 auth->setUser(info.username); 0869 auth->setPassword(info.password); 0870 } else { 0871 // Set authenticator null if dialog is cancelled 0872 // or if we couldn't communicate with kpasswdserver 0873 *auth = QAuthenticator(); 0874 } 0875 } 0876 0877 void WebEnginePage::changeFullScreenMode(QWebEngineFullScreenRequest req) 0878 { 0879 BrowserInterface *iface = part()->browserExtension()->browserInterface(); 0880 if (iface) { 0881 req.accept(); 0882 iface->callMethod("toggleCompleteFullScreen", req.toggleOn()); 0883 } else { 0884 req.reject(); 0885 } 0886 } 0887 0888 0889 void WebEnginePage::setStatusBarText(const QString& text) 0890 { 0891 if (m_part) { 0892 emit m_part->setStatusBarText(text); 0893 } 0894 } 0895 0896 void WebEnginePage::changeLifecycleState(QWebEnginePage::LifecycleState recommendedState) 0897 { 0898 if (recommendedState != QWebEnginePage::LifecycleState::Active && !isVisible()) { 0899 setLifecycleState(QWebEnginePage::LifecycleState::Frozen); 0900 } else { 0901 setLifecycleState(QWebEnginePage::LifecycleState::Active); 0902 } 0903 } 0904 0905 void WebEnginePage::updateUserStyleSheet(const QString& script) 0906 { 0907 runJavaScript(script, QWebEngineScript::ApplicationWorld); 0908 } 0909 0910 /************************************* Begin NewWindowPage ******************************************/ 0911 0912 NewWindowPage::NewWindowPage(WebWindowType type, WebEnginePart* part, QWidget* parent) 0913 :WebEnginePage(part, parent) , m_type(type) , m_createNewWindow(true) 0914 { 0915 Q_ASSERT_X (part, "NewWindowPage", "Must specify a valid KPart"); 0916 0917 // FIXME: are these 3 signals actually defined or used? 0918 connect(this, SIGNAL(menuBarVisibilityChangeRequested(bool)), 0919 this, SLOT(slotMenuBarVisibilityChangeRequested(bool))); 0920 connect(this, SIGNAL(toolBarVisibilityChangeRequested(bool)), 0921 this, SLOT(slotToolBarVisibilityChangeRequested(bool))); 0922 connect(this, SIGNAL(statusBarVisibilityChangeRequested(bool)), 0923 this, SLOT(slotStatusBarVisibilityChangeRequested(bool))); 0924 connect(this, &QWebEnginePage::loadFinished, this, &NewWindowPage::slotLoadFinished); 0925 if (m_type == WebBrowserBackgroundTab) { 0926 m_windowArgs.setLowerWindow(true); 0927 } 0928 } 0929 0930 NewWindowPage::~NewWindowPage() 0931 { 0932 } 0933 0934 bool NewWindowPage::decideHandlingOfJavascripWindow(const QUrl url) const 0935 { 0936 const HtmlSettingsInterface::JSWindowOpenPolicy policy = WebEngineSettings::self()->windowOpenPolicy(url.host()); 0937 switch (policy) { 0938 case HtmlSettingsInterface::JSWindowOpenDeny: 0939 // TODO: Implement support for dealing with blocked pop up windows. 0940 return false; 0941 case HtmlSettingsInterface::JSWindowOpenAsk: { 0942 const QString message = (url.isEmpty() ? 0943 i18n("This site is requesting to open a new popup window.\n" 0944 "Do you want to allow this?") : 0945 i18n("<qt>This site is requesting to open a popup window to" 0946 "<p>%1</p><br/>Do you want to allow this?</qt>", 0947 KStringHandler::rsqueeze(url.toDisplayString().toHtmlEscaped(), 100))); 0948 return KMessageBox::questionTwoActions(view(), message, i18n("Javascript Popup Confirmation"), 0949 KGuiItem(i18n("Allow")), KGuiItem(i18n("Do Not Allow"))) == KMessageBox::PrimaryAction; 0950 // TODO: Implement support for dealing with blocked pop up windows. 0951 } 0952 default: 0953 break; 0954 } 0955 return true; 0956 } 0957 0958 bool NewWindowPage::acceptNavigationRequest(const QUrl &url, NavigationType type, bool isMainFrame) 0959 { 0960 //qCDebug(WEBENGINEPART_LOG) << "url:" << url << ", type:" << type << ", isMainFrame:" << isMainFrame << "m_createNewWindow=" << m_createNewWindow; 0961 if (!m_createNewWindow) { 0962 return WebEnginePage::acceptNavigationRequest(url, type, isMainFrame); 0963 } 0964 0965 const QUrl reqUrl (url); 0966 const bool actionRequestedByUser = type != QWebEnginePage::NavigationTypeOther; 0967 const bool actionRequestsNewTab = m_type == QWebEnginePage::WebBrowserBackgroundTab || m_type == QWebEnginePage::WebBrowserTab; 0968 0969 if (actionRequestedByUser && !actionRequestsNewTab) { 0970 if (!part() && !isMainFrame) { 0971 return false; 0972 } 0973 if (!decideHandlingOfJavascripWindow(reqUrl)) { 0974 deleteLater(); 0975 return false; 0976 } 0977 } 0978 0979 // Browser args... 0980 BrowserArguments bargs; 0981 //Don't set forcesNewWindow for if m_type is WebDialog because it include popups, which the user may want to open in a new tab 0982 bargs.setForcesNewWindow(m_type == WebBrowserWindow); 0983 0984 // OpenUrl args... 0985 KParts::OpenUrlArguments uargs; 0986 uargs.setMimeType(QL1S("text/html")); 0987 uargs.setActionRequestedByUser(actionRequestedByUser); 0988 0989 // Window args... 0990 WindowArgs wargs (m_windowArgs); 0991 0992 KParts::ReadOnlyPart* newWindowPart = nullptr; 0993 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0994 emit part()->browserExtension()->createNewWindow(url, uargs, bargs, wargs, &newWindowPart); 0995 #else 0996 emit part()->browserExtension()->browserCreateNewWindow(url, uargs, bargs, wargs, &newWindowPart); 0997 #endif 0998 qCDebug(WEBENGINEPART_LOG) << "Created new window" << newWindowPart; 0999 1000 if (newWindowPart && newWindowPart->widget()->topLevelWidget() != part()->widget()->topLevelWidget()) { 1001 KParts::OpenUrlArguments args; 1002 args.metaData().insert(QL1S("new-window"), QL1S("true")); 1003 newWindowPart->setArguments(args); 1004 } 1005 deleteLater(); 1006 return false; 1007 } 1008 1009 void NewWindowPage::slotGeometryChangeRequested(const QRect & rect) 1010 { 1011 if (!rect.isValid()) 1012 return; 1013 1014 if (!m_createNewWindow) { 1015 WebEnginePage::slotGeometryChangeRequested(rect); 1016 return; 1017 } 1018 1019 m_windowArgs.setX(rect.x()); 1020 m_windowArgs.setY(rect.y()); 1021 m_windowArgs.setWidth(qMax(rect.width(), 100)); 1022 m_windowArgs.setHeight(qMax(rect.height(), 100)); 1023 } 1024 1025 void NewWindowPage::slotMenuBarVisibilityChangeRequested(bool visible) 1026 { 1027 //qCDebug(WEBENGINEPART_LOG) << visible; 1028 m_windowArgs.setMenuBarVisible(visible); 1029 } 1030 1031 void NewWindowPage::slotStatusBarVisibilityChangeRequested(bool visible) 1032 { 1033 //qCDebug(WEBENGINEPART_LOG) << visible; 1034 m_windowArgs.setStatusBarVisible(visible); 1035 } 1036 1037 void NewWindowPage::slotToolBarVisibilityChangeRequested(bool visible) 1038 { 1039 //qCDebug(WEBENGINEPART_LOG) << visible; 1040 m_windowArgs.setToolBarsVisible(visible); 1041 } 1042 1043 // When is this called? (and acceptNavigationRequest is not called?) 1044 // The only case I found is Ctrl+click on link to data URL (like in konqviewmgrtest), that's quite specific... 1045 // Everything else seems to work with this method being commented out... 1046 void NewWindowPage::slotLoadFinished(bool ok) 1047 { 1048 Q_UNUSED(ok) 1049 if (!m_createNewWindow) 1050 return; 1051 1052 const bool actionRequestedByUser = true; // ### we don't have the information here, unlike in acceptNavigationRequest 1053 1054 // Browser args... 1055 BrowserArguments bargs; 1056 //Don't set forcesNewWindow for if m_type is WebDialog because it include popups, which the user may want to open in a new tab 1057 bargs.setForcesNewWindow(m_type == WebBrowserWindow); 1058 1059 // OpenUrl args... 1060 KParts::OpenUrlArguments uargs; 1061 uargs.setMimeType(QL1S("text/html")); 1062 uargs.setActionRequestedByUser(actionRequestedByUser); 1063 1064 // Window args... 1065 WindowArgs wargs (m_windowArgs); 1066 1067 KParts::ReadOnlyPart* newWindowPart =nullptr; 1068 1069 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 1070 emit part()->browserExtension()->createNewWindow(QUrl(), uargs, bargs, wargs, &newWindowPart); 1071 #else 1072 emit part()->browserExtension()->browserCreateNewWindow(QUrl(), uargs, bargs, wargs, &newWindowPart); 1073 #endif 1074 1075 qCDebug(WEBENGINEPART_LOG) << "Created new window or tab" << newWindowPart; 1076 1077 // Get the webview... 1078 WebEnginePart* webenginePart = newWindowPart ? qobject_cast<WebEnginePart*>(newWindowPart) : nullptr; 1079 WebEngineView* webView = webenginePart ? qobject_cast<WebEngineView*>(webenginePart->view()) : nullptr; 1080 1081 if (webView) { 1082 // if a new window is created, set a new window meta-data flag. 1083 if (newWindowPart->widget()->topLevelWidget() != part()->widget()->topLevelWidget()) { 1084 KParts::OpenUrlArguments args; 1085 args.metaData().insert(QL1S("new-window"), QL1S("true")); 1086 newWindowPart->setArguments(args); 1087 } 1088 // Reparent this page to the new webview to prevent memory leaks. 1089 setParent(webView); 1090 // Replace the webpage of the new webview with this one. Nice trick... 1091 webView->setPage(this); 1092 // Set the new part as the one this page will use going forward. 1093 setPart(webenginePart); 1094 // Connect all the signals from this page to the slots in the new part. 1095 webenginePart->connectWebEnginePageSignals(this); 1096 } 1097 1098 //Set the create new window flag to false... 1099 m_createNewWindow = false; 1100 } 1101 1102 /****************************** End NewWindowPage *************************************************/ 1103