diff --git a/webenginepart/src/webenginepage.h b/webenginepart/src/webenginepage.h --- a/webenginepart/src/webenginepage.h +++ b/webenginepart/src/webenginepage.h @@ -62,8 +62,30 @@ */ void setSslInfo (const WebSslInfo &other); - void download(const QUrl &url, bool newWindow = false); - + void download(QWebEngineDownloadItem *it, bool newWindow = false); + + /** + * Store the information that loadUrl has been called + * + * @see LoadUrlCalledStatus + */ + void recordLoadUrlCalled(){m_loadUrlCalledStatus = Called;} + + /** + * Removes the information that loadUrl has been called + * + * @see LoadUrlCalledStatus + */ + void clearLoadUrlCalled(){m_loadUrlCalledStatus = NotCalled;} + + /** + * Whether or not the part corresponding to this page must be used to open the given URL, + * regardless of everything else + * + * @see LoadUrlCalledStatus + */ + bool forceDownloadWithThis() const {return m_loadUrlCalledStatus != NotCalled;} + Q_SIGNALS: /** * This signal is emitted whenever a user cancels/aborts a load resource @@ -74,6 +96,20 @@ void navigationRequested(WebEnginePage* page, const QUrl& url); protected: + + /** + * Sometimes Konqueror may be configured to handle extensions which + * QWebEngine can't actually handle. In this case, there's the risk + * of an endless loop: + * QWebEngineProfile::downloadRequested -> download -> emit openUrlRequest -> WebEnginePart::openUrl -> acceptNavigationRequest -> QWebEngineProfile::downloadRequested ... + * To avoid this situation, this enum is used. NotCalled means that + * it's the first time that acceptNavigationRequest is called for an + * URL; Called means that an acceptNavigationRequest call for an URL; + * CalledAndSeen means that a second acceptNavigationRequest call for + * an URL is already being processed (endless loop). + */ + enum LoadUrlCalledStatus {NotCalled, Called, CalledAndSeen}; + /** * Returns the webengine part in use by this object. * @internal @@ -121,6 +157,8 @@ QPointer m_part; QScopedPointer m_passwdServerClient; + + LoadUrlCalledStatus m_loadUrlCalledStatus; }; diff --git a/webenginepart/src/webenginepage.cpp b/webenginepart/src/webenginepage.cpp --- a/webenginepart/src/webenginepage.cpp +++ b/webenginepart/src/webenginepage.cpp @@ -132,8 +132,10 @@ cmd = exeName; } -void WebEnginePage::download(const QUrl& url, bool newWindow) +void WebEnginePage::download(QWebEngineDownloadItem* it, bool newWindow) { + it->cancel(); + QUrl url = it->url(); // Integration with a download manager... if (!url.isLocalFile()) { QString managerExe; @@ -144,9 +146,11 @@ return; } } + KParts::OpenUrlArguments args; + args.setMimeType(it->mimeType()); KParts::BrowserArguments bArgs; bArgs.setForcesNewWindow(newWindow); - emit part()->browserExtension()->openUrlRequest(url, KParts::OpenUrlArguments(), bArgs); + emit part()->browserExtension()->openUrlRequest(url, args, bArgs); } QWebEnginePage *WebEnginePage::createWindow(WebWindowType type) @@ -258,6 +262,16 @@ // Honor the enabling/disabling of plugins per host. settings()->setAttribute(QWebEngineSettings::PluginsEnabled, WebEngineSettings::self()->isPluginsEnabled(reqUrl.host())); + + switch(m_loadUrlCalledStatus){ + case Called: + m_loadUrlCalledStatus = CalledAndSeen; + break; + case CalledAndSeen: + m_loadUrlCalledStatus = NotCalled; + default: + break; + } emit navigationRequested(this, url); return QWebEnginePage::acceptNavigationRequest(url, type, isMainFrame); } diff --git a/webenginepart/src/webenginepartdownloadmanager.h b/webenginepart/src/webenginepartdownloadmanager.h --- a/webenginepart/src/webenginepartdownloadmanager.h +++ b/webenginepart/src/webenginepartdownloadmanager.h @@ -24,6 +24,10 @@ #include #include #include +#include +#include + +#include class WebEnginePage; class QWebEngineDownloadItem; @@ -44,14 +48,26 @@ private: WebEnginePartDownloadManager(); WebEnginePage* pageForDownload(QWebEngineDownloadItem *it); + bool saveOrOpen(QWebEngineDownloadItem* it, WebEnginePage* page); + bool downloadAndSave(QWebEngineDownloadItem* it, WebEnginePage *page); + bool downloadAndOpen(QWebEngineDownloadItem* it, WebEnginePage *page, KService::Ptr offer); + bool performDownloadWithoutKIO(QWebEngineDownloadItem* it); + +#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) + void savePage(QWebEngineDownloadItem* it); +#endif private Q_SLOTS: void performDownload(QWebEngineDownloadItem *it); void recordNavigationRequest(WebEnginePage* page, const QUrl& url); + void addCookie(const QNetworkCookie& cookie); + void clearLastRequestPage(); + void downloadFinished(); private: QVector m_pages; QHash m_requests; + WebEnginePage* m_lastRequestPage; }; #endif // WEBENGINEPARTDOWNLOADMANAGER_H diff --git a/webenginepart/src/webenginepartdownloadmanager.cpp b/webenginepart/src/webenginepartdownloadmanager.cpp --- a/webenginepart/src/webenginepartdownloadmanager.cpp +++ b/webenginepart/src/webenginepartdownloadmanager.cpp @@ -26,11 +26,26 @@ #include #include #include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include WebEnginePartDownloadManager::WebEnginePartDownloadManager() - : QObject() + : QObject(), m_lastRequestPage(Q_NULLPTR) { connect(QWebEngineProfile::defaultProfile(), &QWebEngineProfile::downloadRequested, this, &WebEnginePartDownloadManager::performDownload); + connect(QWebEngineProfile::defaultProfile()->cookieStore(), &QWebEngineCookieStore::cookieAdded, this, &WebEnginePartDownloadManager::addCookie); } WebEnginePartDownloadManager::~WebEnginePartDownloadManager() @@ -62,23 +77,62 @@ void WebEnginePartDownloadManager::performDownload(QWebEngineDownloadItem* it) { + bool forceDownload = false; + bool savingPage = false; +#if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0) + savingPage = it->type() == QWebEngineDownloadItem::SavePage; + forceDownload = it->type() != QWebEngineDownloadItem::UserRequested; +#elif QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) + savingPage = it->savePageFormat() != QWebEngineDownloadItem::UnknownSaveFormat; +#endif + if(savingPage){ + savePage(it); + return; + } WebEnginePage *page = m_requests.take(it->url()); - bool forceNew = false; - if (!page && !m_pages.isEmpty()) { - qDebug() << "downloading" << it->url() << "in new window or tab"; + bool newTabOrWindow = false; + if(!page && !m_pages.isEmpty()){ page = m_pages.first(); - forceNew = true; - } else if (!page) { - qDebug() << "Couldn't find a part wanting to download" << it->url(); - return; + newTabOrWindow = true; } - page->download(it->url(), forceNew); + if(page){ + if(forceDownload || page->forceDownloadWithThis()) saveOrOpen(it, page); + else page->download(it, newTabOrWindow); + } + else performDownloadWithoutKIO(it); +} + +bool WebEnginePartDownloadManager::saveOrOpen(QWebEngineDownloadItem* it, WebEnginePage* page) +{ + QString mimeType = it->mimeType(); + // Convert executable text files to plain text... + if (KParts::BrowserRun::isTextExecutable(mimeType)) { + mimeType = QLatin1String("text/plain"); + } + KParts::BrowserOpenOrSaveQuestion dlg(page->view(), it->url(), mimeType); + dlg.setFeatures(KParts::BrowserOpenOrSaveQuestion::ServiceSelection); + KParts::BrowserOpenOrSaveQuestion::Result res = dlg.askOpenOrSave(); + KService::Ptr offer = dlg.selectedService(); + switch(res){ + case KParts::BrowserOpenOrSaveQuestion::Save: + downloadAndSave(it, page); + break; + case KParts::BrowserOpenOrSaveQuestion::Open: + downloadAndOpen(it, page, offer); + break; + default: + return false; + } + it->cancel(); + return true; } void WebEnginePartDownloadManager::recordNavigationRequest(WebEnginePage *page, const QUrl& url) { - qDebug() << url; m_requests.insert(url, page); + m_lastRequestPage = page; + connect(m_lastRequestPage, &QWebEnginePage::loadFinished,this, &WebEnginePartDownloadManager::clearLastRequestPage); + connect(m_lastRequestPage, &QWebEnginePage::destroyed, this, &WebEnginePartDownloadManager::clearLastRequestPage); } WebEnginePage* WebEnginePartDownloadManager::pageForDownload(QWebEngineDownloadItem* it) @@ -89,3 +143,83 @@ } return page; } + +bool WebEnginePartDownloadManager::downloadAndSave(QWebEngineDownloadItem* it, WebEnginePage* page) +{ + QString fileName = QDir(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)).filePath(it->path()); + QUrl destUrl = QUrl::fromLocalFile(QFileDialog::getSaveFileName(page->view(), "Choose download destination", fileName)); + if (!destUrl.isValid()) return true; + QMap metaData; + metaData[QLatin1String("MaxCacheSize")] = QLatin1String("0"); + metaData[QLatin1String("cache")] = QLatin1String("cache"); + KParts::BrowserRun::saveUrlUsingKIO(it->url(), destUrl, page->view(), metaData); + return true; +} + +bool WebEnginePartDownloadManager::downloadAndOpen(QWebEngineDownloadItem* it, WebEnginePage* page, KService::Ptr offer) +{ + if (KParts::BrowserRun::allowExecution(it->mimeType(), it->url())) { + QList list {it->url()}; + bool success = false; + if (offer) { + success = KRun::runService(*offer, list, page->view(), false, it->path()); + } else { + success = KRun::displayOpenWithDialog(list, page->view(), false, it->path()); + if (!success) return false; + } + // For non KIO apps and cancelled Open With dialog, remove slave on hold. + if (!success || (offer && !offer->categories().contains(QLatin1String("KDE")))) { + KIO::SimpleJob::removeOnHold(); // Remove any slave-on-hold... + } + return true; + } + else return false; +} + +//TODO: write a more fatureful function (have the user choose the path and format, for instance) +void WebEnginePartDownloadManager::savePage(QWebEngineDownloadItem* it) +{ + it->accept(); +} + +//TODO: display message when download finished +bool WebEnginePartDownloadManager::performDownloadWithoutKIO(QWebEngineDownloadItem* it) +{ + qWarning() << "Downloading" << it->url() << "without using KIO"; + QString path = QFileDialog::getSaveFileName(Q_NULLPTR, i18n("Download file"), it->path()); + if(!path.isEmpty()) it->setPath(path); + connect(it, &QWebEngineDownloadItem::finished, this, &WebEnginePartDownloadManager::downloadFinished); + it->accept(); + return true; +} + +void WebEnginePartDownloadManager::addCookie(const QNetworkCookie& cookie) +{ + if(!m_lastRequestPage) return; + QDBusInterface interface("org.kde.kcookiejar5", "/modules/kcookiejar", "org.kde.KCookieServer"); + if(!interface.isValid()){ + qDebug() << "Invalid DBUS interface"; + return; + } + QByteArray header("Set-Cookie: "); + header += cookie.toRawForm(); + QString url = m_lastRequestPage->url().toString(); + qlonglong winId = m_lastRequestPage->view()->window()->winId(); + interface.call(QDBus::NoBlock, "addCookies", url, header, winId); + if(interface.lastError().isValid()) qDebug() << interface.lastError(); +} + +void WebEnginePartDownloadManager::clearLastRequestPage() +{ + if(m_lastRequestPage){ + disconnect(m_lastRequestPage, &QWebEnginePage::loadFinished, this, &WebEnginePartDownloadManager::clearLastRequestPage); + disconnect(m_lastRequestPage, &QWebEnginePage::destroyed, this, &WebEnginePartDownloadManager::clearLastRequestPage); + m_lastRequestPage = Q_NULLPTR; + } +} + +void WebEnginePartDownloadManager::downloadFinished() +{ + QMessageBox::information(Q_NULLPTR, "", i18n("Download finshed")); +} + diff --git a/webenginepart/src/webengineview.cpp b/webenginepart/src/webengineview.cpp --- a/webenginepart/src/webengineview.cpp +++ b/webenginepart/src/webengineview.cpp @@ -85,6 +85,8 @@ void WebEngineView::loadUrl(const QUrl& url, const KParts::OpenUrlArguments& args, const KParts::BrowserArguments& bargs) { page()->setProperty("NavigationTypeUrlEntered", true); + + dynamic_cast(page())->recordLoadUrlCalled(); if (args.reload() && url == this->url()) { reload(); @@ -101,6 +103,7 @@ } else { // QWebEngineView::load(url, QNetworkAccessManager::PostOperation, bargs.postData); } + } QWebEngineContextMenuData WebEngineView::contextMenuResult() const