File indexing completed on 2024-05-19 05:01:22
0001 /* 0002 This file is part of the KDE project. 0003 0004 SPDX-FileCopyrightText: 2009 Dawit Alemayehu <adawit@kde.org> 0005 0006 The code for the function slotPrintPreview was adapted from the PrintMe Qt example 0007 and is distributed under the BSD license: 0008 SPDX-License-Identifier: LGPL-2.1-or-later AND BSD-3-Clause 0009 */ 0010 0011 #include "webenginepart_ext.h" 0012 0013 #include "webenginepart.h" 0014 #include "webengineview.h" 0015 #include "webenginepage.h" 0016 #include "settings/webenginesettings.h" 0017 #include <webenginepart_debug.h> 0018 #include "webenginepage.h" 0019 #include "webenginepartcontrols.h" 0020 #include "webenginepartdownloadmanager.h" 0021 0022 #include "libkonq_utils.h" 0023 0024 #include <QWebEngineSettings> 0025 0026 #include <KDesktopFile> 0027 #include <KConfigGroup> 0028 #include <KSharedConfig> 0029 #include <KProtocolInfo> 0030 #include <QInputDialog> 0031 #include <KLocalizedString> 0032 #include <QTemporaryFile> 0033 #include <KUriFilter> 0034 #include <Sonnet/Dialog> 0035 #include <sonnet/backgroundchecker.h> 0036 #include <KIO/JobUiDelegate> 0037 #include <KIO/OpenUrlJob> 0038 #include <KEMailClientLauncherJob> 0039 #include <KIO/JobUiDelegateFactory> 0040 0041 #include <QBuffer> 0042 #include <QVariant> 0043 #include <QClipboard> 0044 #include <QApplication> 0045 #include <QAction> 0046 #include <QPrinter> 0047 #include <QPrintDialog> 0048 #include <QPrintPreviewDialog> 0049 #include <QWebEngineHistory> 0050 #include <QMimeData> 0051 #include <QPrinterInfo> 0052 #include <QJsonDocument> 0053 #include <QJsonArray> 0054 #include <QStringView> 0055 0056 #define QL1S(x) QLatin1String(x) 0057 #define QL1C(x) QLatin1Char(x) 0058 0059 using Element = AsyncSelectorInterface::Element; 0060 0061 using namespace KonqInterfaces; 0062 0063 // A functor that calls a member function 0064 template<typename Arg, typename R, typename C> 0065 struct InvokeWrapper { 0066 R *receiver; 0067 void (C::*memberFun)(Arg); 0068 void operator()(Arg result) 0069 { 0070 (receiver->*memberFun)(result); 0071 } 0072 }; 0073 0074 template<typename Arg, typename R, typename C> 0075 InvokeWrapper<Arg, R, C> invoke(R *receiver, void (C::*memberFun)(Arg)) 0076 { 0077 return InvokeWrapper<Arg, R, C>{receiver, memberFun}; 0078 } 0079 0080 WebEngineNavigationExtension::WebEngineNavigationExtension(WebEnginePart *parent, const QByteArray& cachedHistoryData) 0081 : BrowserExtension(parent), 0082 m_part(parent), 0083 mCurrentPrinter(nullptr) 0084 { 0085 emit enableAction("cut", false); 0086 emit enableAction("copy", false); 0087 emit enableAction("paste", false); 0088 emit enableAction("print", true); 0089 0090 #if QT_VERSION_MAJOR >= 6 0091 connect(view(), &QWebEngineView::printFinished, this, &WebEngineNavigationExtension::slotHandlePagePrinted); 0092 #endif 0093 0094 if (cachedHistoryData.isEmpty()) { 0095 return; 0096 } 0097 0098 QBuffer buffer; 0099 buffer.setData(cachedHistoryData); 0100 if (!buffer.open(QIODevice::ReadOnly)) { 0101 return; 0102 } 0103 0104 // NOTE: When restoring history, webengine automatically navigates to 0105 // the previous "currentItem". Since we do not want that to happen, 0106 // we set a property on the WebEnginePage object that is used to allow or 0107 // disallow history navigation in WebEnginePage::acceptNavigationRequest. 0108 view()->page()->setProperty("HistoryNavigationLocked", true); 0109 QDataStream s (&buffer); 0110 s >> *(view()->history()); 0111 } 0112 0113 WebEngineNavigationExtension::~WebEngineNavigationExtension() 0114 { 0115 } 0116 0117 WebEngineView* WebEngineNavigationExtension::view() 0118 { 0119 if (!m_view && m_part) { 0120 m_view = qobject_cast<WebEngineView*>(m_part->view()); 0121 } 0122 0123 return m_view; 0124 } 0125 0126 WebEnginePage* WebEngineNavigationExtension::page() 0127 { 0128 WebEngineView *v = view(); 0129 if (v) { 0130 return qobject_cast<WebEnginePage*>(v->page()); 0131 } else { 0132 return nullptr; 0133 } 0134 } 0135 0136 int WebEngineNavigationExtension::xOffset() 0137 { 0138 if (view()) { 0139 return view()->page()->scrollPosition().x(); 0140 } 0141 0142 return KParts::NavigationExtension::xOffset(); 0143 } 0144 0145 int WebEngineNavigationExtension::yOffset() 0146 { 0147 if (view()) { 0148 return view()->page()->scrollPosition().y(); 0149 } 0150 0151 return KParts::NavigationExtension::yOffset(); 0152 } 0153 0154 void WebEngineNavigationExtension::saveState(QDataStream &stream) 0155 { 0156 // TODO: Save information such as form data from the current page. 0157 QWebEngineHistory* history = (view() ? view()->history() : nullptr); 0158 const int historyIndex = (history ? history->currentItemIndex() : -1); 0159 const QUrl historyUrl = (history && historyIndex > -1) ? QUrl(history->currentItem().url()) : m_part->url(); 0160 0161 stream << historyUrl 0162 << static_cast<qint32>(xOffset()) 0163 << static_cast<qint32>(yOffset()) 0164 << historyIndex 0165 << m_historyData; 0166 } 0167 0168 void WebEngineNavigationExtension::restoreState(QDataStream &stream) 0169 { 0170 QUrl u; 0171 QByteArray historyData; 0172 qint32 xOfs = -1, yOfs = -1, historyItemIndex = -1; 0173 //TODO KF6: it seems that for some reason historyData is empty, at least when restoring a saved session 0174 stream >> u >> xOfs >> yOfs >> historyItemIndex >> historyData; 0175 0176 QWebEngineHistory* history = (view() ? view()->page()->history() : nullptr); 0177 if (history) { 0178 bool success = false; 0179 if (history->count() == 0) { // Handle restoration: crash recovery, tab close undo, session restore 0180 if (!historyData.isEmpty()) { 0181 historyData = qUncompress(historyData); // uncompress the history data... 0182 QBuffer buffer (&historyData); 0183 if (buffer.open(QIODevice::ReadOnly)) { 0184 QDataStream stream (&buffer); 0185 view()->page()->setProperty("HistoryNavigationLocked", true); 0186 stream >> *history; 0187 QWebEngineHistoryItem currentItem(history->currentItem()); 0188 if (currentItem.isValid()) { 0189 if (currentItem.isValid() && (xOfs != -1 || yOfs != -1)) { 0190 const QPoint scrollPos (xOfs, yOfs); 0191 // currentItem.setUserData(scrollPos); 0192 } 0193 // NOTE 1: The following Konqueror specific workaround is necessary 0194 // because Konqueror only preserves information for the last visited 0195 // page. However, we save the entire history content in saveState and 0196 // and hence need to eliminate all but the current item here. 0197 // NOTE 2: This condition only applies when Konqueror is restored from 0198 // abnormal termination ; a crash and/or a session restoration. 0199 if (QCoreApplication::applicationName() == QLatin1String("konqueror")) { 0200 history->clear(); 0201 } 0202 //qCDebug(WEBENGINEPART_LOG) << "Restoring URL:" << currentItem.url(); 0203 m_part->setProperty("NoEmitOpenUrlNotification", true); 0204 history->goToItem(currentItem); 0205 } 0206 } 0207 } 0208 success = (history->count() > 0); 0209 } else { // Handle navigation: back and forward button navigation. 0210 //qCDebug(WEBENGINEPART_LOG) << "history count:" << history->count() << "request index:" << historyItemIndex; 0211 if (history->count() > historyItemIndex && historyItemIndex > -1) { 0212 QWebEngineHistoryItem item (history->itemAt(historyItemIndex)); 0213 //qCDebug(WEBENGINEPART_LOG) << "URL:" << u << "Item URL:" << item.url(); 0214 if (u == item.url()) { 0215 if (item.isValid() && (xOfs != -1 || yOfs != -1)) { 0216 const QPoint scrollPos (xOfs, yOfs); 0217 // item.setUserData(scrollPos); 0218 } 0219 m_part->setProperty("NoEmitOpenUrlNotification", true); 0220 history->goToItem(item); 0221 success = true; 0222 } 0223 } 0224 } 0225 0226 if (success) { 0227 return; 0228 } 0229 } 0230 0231 // As a last resort, in case the history restoration logic above fails, 0232 // attempt to open the requested URL directly. 0233 qCDebug(WEBENGINEPART_LOG) << "Normal history navigation logic failed! Falling back to opening url directly."; 0234 m_part->openUrl(u); 0235 } 0236 0237 0238 void WebEngineNavigationExtension::cut() 0239 { 0240 if (view()) 0241 view()->triggerPageAction(QWebEnginePage::Cut); 0242 } 0243 0244 void WebEngineNavigationExtension::copy() 0245 { 0246 if (view()) 0247 view()->triggerPageAction(QWebEnginePage::Copy); 0248 } 0249 0250 void WebEngineNavigationExtension::paste() 0251 { 0252 if (view()) 0253 view()->triggerPageAction(QWebEnginePage::Paste); 0254 } 0255 0256 void WebEngineNavigationExtension::slotSaveDocument() 0257 { 0258 WebEnginePage *pg = page(); 0259 if (pg) { 0260 WebEnginePartControls::self()->downloadManager()->specifyDownloadObjective(pg->url(), pg, 0261 WebEnginePartDownloadManager::DownloadObjective::SaveAs); 0262 pg->download(pg->url()); 0263 } 0264 } 0265 0266 void WebEngineNavigationExtension::slotSaveFullHTMLPage() 0267 { 0268 WebEnginePage *p = page(); 0269 if (p) { 0270 p->triggerAction(QWebEnginePage::SavePage); 0271 } 0272 } 0273 0274 void WebEngineNavigationExtension::print() 0275 { 0276 if (view()) { 0277 mCurrentPrinter = new QPrinter(); 0278 QPointer<QPrintDialog> dialog = new QPrintDialog(mCurrentPrinter, nullptr); 0279 dialog->setWindowTitle(i18n("Print Document")); 0280 if (dialog->exec() != QDialog::Accepted) { 0281 slotHandlePagePrinted(false); 0282 delete dialog; 0283 return; 0284 } 0285 delete dialog; 0286 #if QT_VERSION_MAJOR < 6 0287 view()->page()->print(mCurrentPrinter, invoke(this, &WebEngineNavigationExtension::slotHandlePagePrinted)); 0288 #else 0289 view()->print(mCurrentPrinter); 0290 #endif 0291 } 0292 } 0293 0294 void WebEngineNavigationExtension::slotHandlePagePrinted(bool result) 0295 { 0296 Q_UNUSED(result); 0297 delete mCurrentPrinter; 0298 mCurrentPrinter = nullptr; 0299 } 0300 0301 void WebEngineNavigationExtension::updateEditActions() 0302 { 0303 if (!view()) 0304 return; 0305 0306 emit enableAction("cut", view()->pageAction(QWebEnginePage::Cut)->isEnabled()); 0307 emit enableAction("copy", view()->pageAction(QWebEnginePage::Copy)->isEnabled()); 0308 emit enableAction("paste", view()->pageAction(QWebEnginePage::Paste)->isEnabled()); 0309 } 0310 0311 void WebEngineNavigationExtension::updateActions() 0312 { 0313 const QString protocol (m_part->url().scheme()); 0314 const bool isValidDocument = (protocol != QL1S("about") && protocol != QL1S("error") && protocol != QL1S("konq")); 0315 emit enableAction("print", isValidDocument); 0316 } 0317 0318 void WebEngineNavigationExtension::searchProvider() 0319 { 0320 if (!view()) 0321 return; 0322 0323 QAction *action = qobject_cast<QAction*>(sender()); 0324 if (!action) 0325 return; 0326 0327 QUrl url = action->data().toUrl(); 0328 0329 if (url.host().isEmpty()) { 0330 KUriFilterData data; 0331 data.setData(action->data().toString()); 0332 if (KUriFilter::self()->filterSearchUri(data, KUriFilter::WebShortcutFilter)) 0333 url = data.uri(); 0334 } 0335 0336 if (!url.isValid()) 0337 return; 0338 0339 BrowserArguments bargs; 0340 bargs.frameName = QL1S("_blank"); 0341 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0342 emit openUrlRequest(url, KParts::OpenUrlArguments(), bargs); 0343 #else 0344 emit browserOpenUrlRequest(url, KParts::OpenUrlArguments(), bargs); 0345 #endif 0346 } 0347 0348 void WebEngineNavigationExtension::reparseConfiguration() 0349 { 0350 // Force the configuration stuff to reparse... 0351 WebEngineSettings::self()->init(); 0352 } 0353 0354 void WebEngineNavigationExtension::disableScrolling() 0355 { 0356 QWebEngineView* currentView = view(); 0357 QWebEnginePage* page = currentView ? currentView->page() : nullptr; 0358 0359 if (!page) 0360 return; 0361 0362 page->runJavaScript(QStringLiteral("document.documentElement.style.overflow = 'hidden';")); 0363 } 0364 0365 void WebEngineNavigationExtension::zoomIn() 0366 { 0367 if (view()) 0368 view()->setZoomFactor(view()->zoomFactor() + 0.1); 0369 } 0370 0371 void WebEngineNavigationExtension::zoomOut() 0372 { 0373 if (view()) 0374 view()->setZoomFactor(view()->zoomFactor() - 0.1); 0375 } 0376 0377 void WebEngineNavigationExtension::zoomNormal() 0378 { 0379 if (view()) { 0380 if (WebEngineSettings::self()->zoomToDPI()) 0381 view()->setZoomFactor(view()->logicalDpiY() / 96.0f); 0382 else 0383 view()->setZoomFactor(1); 0384 } 0385 } 0386 0387 void WebEngineNavigationExtension::toogleZoomTextOnly() 0388 { 0389 if (!view()) 0390 return; 0391 0392 KConfigGroup cgHtml(KSharedConfig::openConfig(), "HTML Settings"); 0393 bool zoomTextOnly = cgHtml.readEntry( "ZoomTextOnly", false ); 0394 cgHtml.writeEntry("ZoomTextOnly", !zoomTextOnly); 0395 cgHtml.sync(); 0396 0397 // view()->settings()->setAttribute(QWebEngineSettings::ZoomTextOnly, !zoomTextOnly); 0398 } 0399 0400 void WebEngineNavigationExtension::toogleZoomToDPI() 0401 { 0402 if (!view()) 0403 return; 0404 0405 bool zoomToDPI = !WebEngineSettings::self()->zoomToDPI(); 0406 WebEngineSettings::self()->setZoomToDPI(zoomToDPI); 0407 0408 if (zoomToDPI) 0409 view()->setZoomFactor(view()->zoomFactor() * view()->logicalDpiY() / 96.0f); 0410 else 0411 view()->setZoomFactor(view()->zoomFactor() * 96.0f / view()->logicalDpiY()); 0412 0413 // Recompute default font-sizes since they are only DPI dependent when zoomToDPI is false. 0414 WebEngineSettings::self()->computeFontSizes(view()->logicalDpiY()); 0415 } 0416 0417 void WebEngineNavigationExtension::slotSelectAll() 0418 { 0419 if (view()) 0420 view()->triggerPageAction(QWebEnginePage::SelectAll); 0421 } 0422 0423 void WebEngineNavigationExtension::slotSaveImageAs() 0424 { 0425 if (view()) 0426 view()->triggerPageAction(QWebEnginePage::DownloadImageToDisk); 0427 } 0428 0429 void WebEngineNavigationExtension::slotSendImage() 0430 { 0431 if (!view()) { 0432 return; 0433 } 0434 0435 QList<QUrl> urls = {view()->contextMenuResult()->mediaUrl()}; 0436 const QString subject = view()->contextMenuResult()->mediaUrl().path(); 0437 0438 auto *job = new KEMailClientLauncherJob; 0439 job->setSubject(subject); 0440 job->setAttachments(urls); 0441 job->start(); 0442 } 0443 0444 void WebEngineNavigationExtension::slotCopyImageURL() 0445 { 0446 if (!view()) { 0447 return; 0448 } 0449 0450 QUrl safeURL = view()->contextMenuResult()->mediaUrl(); 0451 safeURL.setPassword(QString()); 0452 // Set it in both the mouse selection and in the clipboard 0453 QMimeData* mimeData = new QMimeData; 0454 //TODO: Porting: test 0455 QList<QUrl> safeURLList; 0456 safeURLList.append(safeURL); 0457 mimeData->setUrls(safeURLList); 0458 QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard); 0459 0460 mimeData = new QMimeData; 0461 mimeData->setUrls(safeURLList); 0462 QApplication::clipboard()->setMimeData(mimeData, QClipboard::Selection); 0463 } 0464 0465 0466 void WebEngineNavigationExtension::slotCopyImage() 0467 { 0468 if (!view()) { 0469 return; 0470 } 0471 0472 QUrl safeURL; //(view()->contextMenuResult()->imageUrl()); 0473 safeURL.setPassword(QString()); 0474 0475 // Set it in both the mouse selection and in the clipboard 0476 QMimeData* mimeData = new QMimeData; 0477 // mimeData->setImageData(view()->contextMenuResult()->pixmap()); 0478 //TODO: Porting: test 0479 QList<QUrl> safeURLList; 0480 safeURLList.append(safeURL); 0481 mimeData->setUrls(safeURLList); 0482 QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard); 0483 0484 mimeData = new QMimeData; 0485 // mimeData->setImageData(view()->contextMenuResult()->pixmap()); 0486 mimeData->setUrls(safeURLList); 0487 QApplication::clipboard()->setMimeData(mimeData, QClipboard::Selection); 0488 } 0489 0490 void WebEngineNavigationExtension::slotViewImage() 0491 { 0492 if (view()) { 0493 emit createNewWindow(view()->contextMenuResult()->mediaUrl()); 0494 } 0495 } 0496 0497 void WebEngineNavigationExtension::slotBlockImage() 0498 { 0499 if (!view()) { 0500 return; 0501 } 0502 0503 bool ok = false; 0504 const QString url = QInputDialog::getText(view(), i18n("Add URL to Filter"), 0505 i18n("Enter the URL:"), QLineEdit::Normal, 0506 view()->contextMenuResult()->mediaUrl().toString(), 0507 &ok); 0508 if (ok) { 0509 WebEngineSettings::self()->addAdFilter(url); 0510 reparseConfiguration(); 0511 } 0512 } 0513 0514 void WebEngineNavigationExtension::slotBlockHost() 0515 { 0516 if (!view()) 0517 return; 0518 0519 QUrl url; // (view()->contextMenuResult()->imageUrl()); 0520 url.setPath(QL1S("/*")); 0521 WebEngineSettings::self()->addAdFilter(url.toString(QUrl::RemoveUserInfo | QUrl::RemovePort)); 0522 reparseConfiguration(); 0523 } 0524 0525 void WebEngineNavigationExtension::slotCopyLinkURL() 0526 { 0527 if (view()) 0528 view()->triggerPageAction(QWebEnginePage::CopyLinkToClipboard); 0529 } 0530 0531 void WebEngineNavigationExtension::slotCopyLinkText() 0532 { 0533 if (view()) { 0534 QMimeData* data = new QMimeData; 0535 data->setText(view()->contextMenuResult()->linkText()); 0536 QApplication::clipboard()->setMimeData(data, QClipboard::Clipboard); 0537 } 0538 } 0539 0540 void WebEngineNavigationExtension::slotCopyEmailAddress() 0541 { 0542 if (view()) { 0543 QMimeData* data = new QMimeData; 0544 const QUrl url(view()->contextMenuResult()->linkUrl()); 0545 data->setText(url.path()); 0546 QApplication::clipboard()->setMimeData(data, QClipboard::Clipboard); 0547 } 0548 } 0549 0550 void WebEngineNavigationExtension::slotSaveLinkAs(const QUrl &url) 0551 { 0552 if (!view()) { 0553 return; 0554 } 0555 if (!url.isEmpty()) { 0556 WebEnginePage *pg = qobject_cast<WebEnginePage*>(view()->page()); 0557 if (pg) { 0558 WebEnginePartControls::self()->downloadManager()->specifyDownloadObjective(url, 0559 pg, WebEnginePartDownloadManager::DownloadObjective::SaveOnly); 0560 } 0561 } 0562 view()->triggerPageAction(QWebEnginePage::DownloadLinkToDisk); 0563 } 0564 0565 void WebEngineNavigationExtension::slotViewDocumentSource() 0566 { 0567 if (!view()) 0568 return; 0569 0570 const QUrl pageUrl (view()->url()); 0571 if (pageUrl.isLocalFile()) { 0572 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(pageUrl, QL1S("text/plain")); 0573 job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, view())); 0574 job->start(); 0575 } else { 0576 view()->page()->toHtml([this](const QString& html) { 0577 QTemporaryFile tempFile; 0578 tempFile.setFileTemplate(tempFile.fileTemplate() + QL1S(".html")); 0579 tempFile.setAutoRemove(false); 0580 if (tempFile.open()) { 0581 tempFile.write(html.toUtf8()); 0582 tempFile.close(); 0583 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl::fromLocalFile(tempFile.fileName()), QL1S("text/plain")); 0584 job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, view())); 0585 job->setDeleteTemporaryFile(true); 0586 job->start(); 0587 } 0588 }); 0589 } 0590 } 0591 0592 static bool isMultimediaElement(QWebEngineContextMenuRequest::MediaType mediaType) 0593 { 0594 switch(mediaType) 0595 { 0596 case QWebEngineContextMenuRequest::MediaTypeVideo: 0597 case QWebEngineContextMenuRequest::MediaTypeAudio: 0598 return true; 0599 default: 0600 return false; 0601 } 0602 } 0603 0604 void WebEngineNavigationExtension::slotLoopMedia() 0605 { 0606 if (!view()) { 0607 return; 0608 } 0609 0610 const QWebEngineContextMenuRequest *data = view()->contextMenuResult(); 0611 if (!isMultimediaElement( data->mediaType())) 0612 return; 0613 view()->page()->triggerAction(QWebEnginePage::ToggleMediaLoop); 0614 } 0615 0616 void WebEngineNavigationExtension::slotMuteMedia() 0617 { 0618 if (!view()) { 0619 return; 0620 } 0621 0622 const QWebEngineContextMenuRequest *data = view()->contextMenuResult(); 0623 if (!isMultimediaElement( data->mediaType())) 0624 return; 0625 view()->page()->triggerAction(QWebEnginePage::ToggleMediaMute); 0626 } 0627 0628 void WebEngineNavigationExtension::slotPlayMedia() 0629 { 0630 if (!view()) { 0631 return; 0632 } 0633 0634 const QWebEngineContextMenuRequest *data = view()->contextMenuResult(); 0635 if (!isMultimediaElement( data->mediaType())) 0636 return; 0637 view()->page()->triggerAction(QWebEnginePage::ToggleMediaPlayPause); 0638 } 0639 0640 void WebEngineNavigationExtension::slotShowMediaControls() 0641 { 0642 if (!view()) { 0643 return; 0644 } 0645 0646 const QWebEngineContextMenuRequest *data = view()->contextMenuResult(); 0647 if (!isMultimediaElement( data->mediaType())) 0648 return; 0649 view()->page()->triggerAction(QWebEnginePage::ToggleMediaControls); 0650 } 0651 0652 #if 0 0653 static QUrl mediaUrlFrom(QWebElement& element) 0654 { 0655 QWebFrame* frame = element.webFrame(); 0656 QString src = frame ? element.attribute(QL1S("src")) : QString(); 0657 if (src.isEmpty()) 0658 src = frame ? element.evaluateJavaScript(QL1S("this.src")).toString() : QString(); 0659 0660 if (src.isEmpty()) 0661 return QUrl(); 0662 0663 return QUrl(frame->baseUrl().resolved(QUrl::fromEncoded(QUrl::toPercentEncoding(src), QUrl::StrictMode))); 0664 } 0665 #endif 0666 0667 void WebEngineNavigationExtension::slotSaveMedia() 0668 { 0669 WebEnginePage *pg = page(); 0670 const QWebEngineContextMenuRequest *data = view()->contextMenuResult(); 0671 if (!isMultimediaElement( data->mediaType())) { 0672 return; 0673 } 0674 if (pg) { 0675 if (data->mediaUrl().isValid()) { 0676 WebEnginePartControls::self()->downloadManager()->specifyDownloadObjective(data->mediaUrl(), pg, 0677 WebEnginePartDownloadManager::DownloadObjective::SaveOnly); 0678 } 0679 pg->triggerAction(QWebEnginePage::DownloadMediaToDisk); 0680 } 0681 } 0682 0683 void WebEngineNavigationExtension::slotCopyMedia() 0684 { 0685 if (!view()) { 0686 return; 0687 } 0688 const QWebEngineContextMenuRequest *data = view()->contextMenuResult(); 0689 if (!isMultimediaElement( data->mediaType())) 0690 return; 0691 0692 QUrl safeURL(data->mediaUrl()); 0693 if (!safeURL.isValid()) 0694 return; 0695 0696 safeURL.setPassword(QString()); 0697 // Set it in both the mouse selection and in the clipboard 0698 QMimeData* mimeData = new QMimeData; 0699 //TODO: Porting: test 0700 QList<QUrl> safeURLList; 0701 safeURLList.append(safeURL); 0702 mimeData->setUrls(safeURLList); 0703 QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard); 0704 0705 mimeData = new QMimeData; 0706 mimeData->setUrls(safeURLList); 0707 QApplication::clipboard()->setMimeData(mimeData, QClipboard::Selection); 0708 } 0709 0710 void WebEngineNavigationExtension::slotTextDirectionChanged() 0711 { 0712 QAction* action = qobject_cast<QAction*>(sender()); 0713 if (action) { 0714 bool ok = false; 0715 const int value = action->data().toInt(&ok); 0716 if (ok) { 0717 view()->triggerPageAction(static_cast<QWebEnginePage::WebAction>(value)); 0718 } 0719 } 0720 } 0721 0722 void WebEngineNavigationExtension::slotCheckSpelling() 0723 { 0724 view()->page()->runJavaScript(QL1S("this.value"), [this](const QVariant &value) { 0725 if (!value.isValid()) { 0726 return; 0727 } 0728 const QString text = value.toString(); 0729 if (!text.isEmpty()) { 0730 m_spellTextSelectionStart = 0; 0731 m_spellTextSelectionEnd = 0; 0732 0733 Sonnet::BackgroundChecker *backgroundSpellCheck = new Sonnet::BackgroundChecker; 0734 Sonnet::Dialog* spellDialog = new Sonnet::Dialog(backgroundSpellCheck, view()); 0735 backgroundSpellCheck->setParent(spellDialog); 0736 spellDialog->setAttribute(Qt::WA_DeleteOnClose, true); 0737 spellDialog->showSpellCheckCompletionMessage(true); 0738 connect(spellDialog, &Sonnet::Dialog::replace, this, &WebEngineNavigationExtension::spellCheckerCorrected); 0739 connect(spellDialog, &Sonnet::Dialog::misspelling, this, &WebEngineNavigationExtension::spellCheckerMisspelling); 0740 spellDialog->setBuffer(text); 0741 spellDialog->show(); 0742 } 0743 }); 0744 } 0745 0746 void WebEngineNavigationExtension::slotSpellCheckSelection() 0747 { 0748 view()->page()->runJavaScript(QL1S("this.value"), [this](const QVariant &value) { 0749 if (!value.isValid()) { 0750 return; 0751 } 0752 const QString text = value.toString(); 0753 if (!text.isEmpty()) { 0754 view()->page()->runJavaScript(QL1S("this.selectionStart + ' ' + this.selectionEnd"), [this, text](const QVariant &value) { 0755 if (!value.isValid()) { 0756 return; 0757 } 0758 const QString values = value.toString(); 0759 const int pos = values.indexOf(' '); 0760 m_spellTextSelectionStart = qMax(0, QStringView{values}.left(pos).toInt()); 0761 m_spellTextSelectionEnd = qMax(0, QStringView{values}.mid(pos + 1).toInt()); 0762 // qCDebug(WEBENGINEPART_LOG) << "selection start:" << m_spellTextSelectionStart << "end:" << m_spellTextSelectionEnd; 0763 0764 Sonnet::BackgroundChecker *backgroundSpellCheck = new Sonnet::BackgroundChecker; 0765 Sonnet::Dialog* spellDialog = new Sonnet::Dialog(backgroundSpellCheck, view()); 0766 backgroundSpellCheck->setParent(spellDialog); 0767 spellDialog->setAttribute(Qt::WA_DeleteOnClose, true); 0768 spellDialog->showSpellCheckCompletionMessage(true); 0769 connect(spellDialog, &Sonnet::Dialog::replace, this, &WebEngineNavigationExtension::spellCheckerCorrected); 0770 connect(spellDialog, &Sonnet::Dialog::misspelling, this, &WebEngineNavigationExtension::spellCheckerMisspelling); 0771 connect(spellDialog, &Sonnet::Dialog::spellCheckDone, this, &WebEngineNavigationExtension::slotSpellCheckDone); 0772 spellDialog->setBuffer(text.mid(m_spellTextSelectionStart, (m_spellTextSelectionEnd - m_spellTextSelectionStart))); 0773 spellDialog->show(); 0774 }); 0775 } 0776 }); 0777 } 0778 0779 void WebEngineNavigationExtension::spellCheckerCorrected(const QString& original, int pos, const QString& replacement) 0780 { 0781 // Adjust the selection end... 0782 if (m_spellTextSelectionEnd > 0) { 0783 m_spellTextSelectionEnd += qMax (0, (replacement.length() - original.length())); 0784 } 0785 0786 const int index = pos + m_spellTextSelectionStart; 0787 QString script(QL1S("this.value=this.value.substring(0,")); 0788 script += QString::number(index); 0789 script += QL1S(") + \""); 0790 script += replacement; 0791 script += QL1S("\" + this.value.substring("); 0792 script += QString::number(index + original.length()); 0793 script += QL1S(")"); 0794 0795 //qCDebug(WEBENGINEPART_LOG) << "**** script:" << script; 0796 view()->page()->runJavaScript(script); 0797 } 0798 0799 void WebEngineNavigationExtension::spellCheckerMisspelling(const QString& text, int pos) 0800 { 0801 // qCDebug(WEBENGINEPART_LOG) << text << pos; 0802 QString selectionScript (QL1S("this.setSelectionRange(")); 0803 selectionScript += QString::number(pos + m_spellTextSelectionStart); 0804 selectionScript += QL1C(','); 0805 selectionScript += QString::number(pos + text.length() + m_spellTextSelectionStart); 0806 selectionScript += QL1C(')'); 0807 view()->page()->runJavaScript(selectionScript); 0808 } 0809 0810 void WebEngineNavigationExtension::slotSpellCheckDone(const QString&) 0811 { 0812 // Restore the text selection if one was present before we started the 0813 // spell check. 0814 if (m_spellTextSelectionStart > 0 || m_spellTextSelectionEnd > 0) { 0815 QString script (QL1S("; this.setSelectionRange(")); 0816 script += QString::number(m_spellTextSelectionStart); 0817 script += QL1C(','); 0818 script += QString::number(m_spellTextSelectionEnd); 0819 script += QL1C(')'); 0820 view()->page()->runJavaScript(script); 0821 } 0822 } 0823 0824 void WebEngineNavigationExtension::saveHistory() 0825 { 0826 QWebEngineHistory* history = (view() ? view()->history() : nullptr); 0827 0828 if (history && history->count() > 0) { 0829 //qCDebug(WEBENGINEPART_LOG) << "Current history: index=" << history->currentItemIndex() << "url=" << history->currentItem().url(); 0830 QByteArray histData; 0831 QBuffer buff (&histData); 0832 m_historyData.clear(); 0833 if (buff.open(QIODevice::WriteOnly)) { 0834 QDataStream stream (&buff); 0835 stream << *history; 0836 m_historyData = qCompress(histData, 9); 0837 } 0838 QWidget* mainWidget = m_part ? m_part->widget() : nullptr; 0839 QWidget* frameWidget = mainWidget ? mainWidget->parentWidget() : nullptr; 0840 if (frameWidget) { 0841 emit saveHistory(frameWidget, m_historyData); 0842 // qCDebug(WEBENGINEPART_LOG) << "# of items:" << history->count() << "current item:" << history->currentItemIndex() << "url:" << history->currentItem().url(); 0843 } 0844 } else { 0845 Q_ASSERT(false); // should never happen!!! 0846 } 0847 } 0848 0849 void WebEngineNavigationExtension::slotPrintPreview() 0850 { 0851 QPrinter printer; 0852 QPrintPreviewDialog dlg(&printer, view()); 0853 auto printPreview = [this](QPrinter *p){ 0854 QEventLoop loop; 0855 auto preview = [&](bool) {loop.quit();}; 0856 #if QT_VERSION_MAJOR < 6 0857 m_view->page()->print(p, preview); 0858 #else 0859 m_view->print(p); 0860 connect(m_view, &QWebEngineView::printFinished, &loop, preview); 0861 #endif 0862 loop.exec(); 0863 }; 0864 connect(&dlg, &QPrintPreviewDialog::paintRequested, this, printPreview); 0865 dlg.exec(); 0866 } 0867 0868 void WebEngineNavigationExtension::slotOpenSelection() 0869 { 0870 QAction *action = qobject_cast<QAction*>(sender()); 0871 if (action) { 0872 BrowserArguments browserArgs; 0873 browserArgs.frameName = QStringLiteral("_blank"); 0874 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0875 emit openUrlRequest(QUrl(action->data().toUrl()), KParts::OpenUrlArguments(), browserArgs); 0876 #else 0877 emit browserOpenUrlRequest(QUrl(action->data().toUrl()), KParts::OpenUrlArguments(), browserArgs); 0878 #endif 0879 } 0880 } 0881 0882 void WebEngineNavigationExtension::slotLinkInTop() 0883 { 0884 if (!view()) { 0885 return; 0886 } 0887 0888 KParts::OpenUrlArguments uargs; 0889 uargs.setActionRequestedByUser(true); 0890 0891 BrowserArguments bargs; 0892 bargs.frameName = QL1S("_top"); 0893 0894 const QUrl url(view()->contextMenuResult()->linkUrl()); 0895 0896 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0897 emit openUrlRequest(url, uargs, bargs); 0898 #else 0899 emit browserOpenUrlRequest(url, uargs, bargs); 0900 #endif 0901 } 0902 0903 //// 0904 0905 WebEngineTextExtension::WebEngineTextExtension(WebEnginePart* part) 0906 : TextExtension(part) 0907 { 0908 connect(part->view(), &QWebEngineView::selectionChanged, this, &TextExtension::selectionChanged); 0909 } 0910 0911 WebEnginePart* WebEngineTextExtension::part() const 0912 { 0913 return static_cast<WebEnginePart*>(parent()); 0914 } 0915 0916 bool WebEngineTextExtension::hasSelection() const 0917 { 0918 return part()->view()->hasSelection(); 0919 } 0920 0921 QString WebEngineTextExtension::selectedText(Format format) const 0922 { 0923 switch(format) { 0924 case PlainText: 0925 return part()->view()->selectedText(); 0926 case HTML: 0927 // PORTING_TODO selectedText might not be html 0928 return part()->view()->selectedText(); 0929 } 0930 return QString(); 0931 } 0932 0933 QString WebEngineTextExtension::completeText(Format format) const 0934 { 0935 // TODO David will hunt me down with a rusty spork if he sees this 0936 QEventLoop ev; 0937 QString str; 0938 switch(format) { 0939 case PlainText: 0940 part()->view()->page()->toPlainText([&ev,&str](const QString& data) { 0941 str = data; 0942 ev.quit(); 0943 }); 0944 break; 0945 case HTML: 0946 part()->view()->page()->toHtml([&ev,&str](const QString& data) { 0947 str = data; 0948 ev.quit(); 0949 }); 0950 break; 0951 } 0952 ev.exec(); 0953 return str; 0954 } 0955 0956 //// 0957 0958 WebEngineHtmlExtension::WebEngineHtmlExtension(WebEnginePart* part) 0959 : HtmlExtension(part) 0960 { 0961 } 0962 0963 QUrl WebEngineHtmlExtension::baseUrl() const 0964 { 0965 return part()->view()->page()->url(); 0966 } 0967 0968 bool WebEngineHtmlExtension::hasSelection() const 0969 { 0970 return part()->view()->hasSelection(); 0971 } 0972 0973 AsyncSelectorInterface::QueryMethods WebEngineHtmlExtension::supportedAsyncQueryMethods() const 0974 { 0975 return AsyncSelectorInterface::EntireContent; 0976 } 0977 0978 QList<AsyncSelectorInterface::Element> WebEngineHtmlExtension::jsonToElementList(const QVariant& json) 0979 { 0980 QList<AsyncSelectorInterface::Element> res; 0981 QJsonDocument doc = QJsonDocument::fromVariant(json); 0982 if (!doc.isArray()) { 0983 return res; 0984 } 0985 0986 QJsonArray array = doc.array(); 0987 std::transform(array.constBegin(), array.constEnd(), std::back_inserter(res), [](const QJsonValue &val){return WebEngineHtmlExtension::jsonToElement(val.toObject());}); 0988 return res; 0989 } 0990 0991 AsyncSelectorInterface::Element WebEngineHtmlExtension::jsonToElement(const QVariant& json) 0992 { 0993 QJsonDocument doc = QJsonDocument::fromVariant(json); 0994 if (!doc.isObject()) { 0995 return AsyncSelectorInterface::Element(); 0996 } 0997 QJsonObject obj = doc.object(); 0998 return jsonToElement(obj); 0999 } 1000 1001 AsyncSelectorInterface::Element WebEngineHtmlExtension::jsonToElement(const QJsonObject& obj) 1002 { 1003 AsyncSelectorInterface::Element res; 1004 QJsonValue nameVal = obj.value(QLatin1String("tag")); 1005 if (nameVal.isUndefined()) { 1006 return res; 1007 } 1008 res.setTagName(nameVal.toString()); 1009 QVariantHash attributes = obj.value(QLatin1String("attributes")).toObject().toVariantHash(); 1010 for (auto it = attributes.constBegin(); it != attributes.constEnd(); ++it) { 1011 res.setAttribute(it.key(), it.value().toString()); 1012 } 1013 return res; 1014 } 1015 1016 void WebEngineHtmlExtension::querySelectorAllAsync(const QString& query, AsyncSelectorInterface::QueryMethod method, MultipleElementSelectorCallback& callback) 1017 { 1018 QList<Element> result; 1019 if (method == None || !part() || !part()->page() || !(supportedAsyncQueryMethods() & method)) { 1020 callback(result); 1021 return; 1022 } 1023 1024 auto internalCallback = [callback] (const QVariant &res) { 1025 if (!res.isValid()) { 1026 return; 1027 } 1028 callback(WebEngineHtmlExtension::jsonToElementList(res)); 1029 }; 1030 1031 static const QString s_allSelectorTemplate = QStringLiteral("querySelectorAllToList(\"%1\")"); 1032 QString fullQuery = s_allSelectorTemplate.arg(query); 1033 // QString test = "function findElements(sel) {\nvar list = document.querySelectorAll(sel);var result = []; for (const e of list) { var obj = {'tag': e.tagName, 'attributes' : {}}; for (const a of e.attributes) { obj.attributes[a.name] = a.value; } result.push(obj); } return result; } findElements(%1);"; 1034 part()->page()->runJavaScript(fullQuery, QWebEngineScript::ApplicationWorld, internalCallback); 1035 } 1036 1037 void WebEngineHtmlExtension::querySelectorAsync(const QString& query, AsyncSelectorInterface::QueryMethod method, SingleElementSelectorCallback& callback) 1038 { 1039 Element result; 1040 if (method == AsyncSelectorInterface::None || !part() || !part()->page() || !(supportedAsyncQueryMethods() & method)) { 1041 callback(result); 1042 return; 1043 } 1044 1045 auto internalCallback = [callback] (const QVariant &res) { 1046 if (!res.isValid()) { 1047 return; 1048 } 1049 callback(WebEngineHtmlExtension::jsonToElement(res)); 1050 }; 1051 1052 static const QString s_selectorTemplate = QStringLiteral("querySelectorToObject(\"%1\")"); 1053 QString fullQuery = s_selectorTemplate.arg(query); 1054 part()->page()->runJavaScript(fullQuery, QWebEngineScript::ApplicationWorld, internalCallback); 1055 } 1056 1057 QVariant WebEngineHtmlExtension::htmlSettingsProperty(HtmlSettingsInterface::HtmlSettingsType type) const 1058 { 1059 QWebEngineView* view = part() ? part()->view() : nullptr; 1060 QWebEnginePage* page = view ? view->page() : nullptr; 1061 QWebEngineSettings* settings = page ? page->settings() : nullptr; 1062 1063 if (settings) { 1064 switch (type) { 1065 case HtmlSettingsInterface::AutoLoadImages: 1066 return settings->testAttribute(QWebEngineSettings::AutoLoadImages); 1067 case HtmlSettingsInterface::JavaEnabled: 1068 return false; // settings->testAttribute(QWebEngineSettings::JavaEnabled); 1069 case HtmlSettingsInterface::JavascriptEnabled: 1070 return settings->testAttribute(QWebEngineSettings::JavascriptEnabled); 1071 case HtmlSettingsInterface::PluginsEnabled: 1072 return settings->testAttribute(QWebEngineSettings::PluginsEnabled); 1073 case HtmlSettingsInterface::DnsPrefetchEnabled: 1074 return false; //settings->testAttribute(QWebEngineSettings::DnsPrefetchEnabled); 1075 case HtmlSettingsInterface::MetaRefreshEnabled: 1076 return view->pageAction(QWebEnginePage::Stop)->isEnabled(); 1077 case HtmlSettingsInterface::LocalStorageEnabled: 1078 return settings->testAttribute(QWebEngineSettings::LocalStorageEnabled); 1079 case HtmlSettingsInterface::OfflineStorageDatabaseEnabled: 1080 return false; //settings->testAttribute(QWebEngineSettings::OfflineStorageDatabaseEnabled); 1081 case HtmlSettingsInterface::OfflineWebApplicationCacheEnabled: 1082 return false ;//settings->testAttribute(QWebEngineSettings::OfflineWebApplicationCacheEnabled); 1083 case HtmlSettingsInterface::PrivateBrowsingEnabled: 1084 return false; //settings->testAttribute(QWebEngineSettings::PrivateBrowsingEnabled); 1085 case HtmlSettingsInterface::UserDefinedStyleSheetURL: 1086 return false; //settings->userStyleSheetUrl(); 1087 default: 1088 break; 1089 } 1090 } 1091 1092 return QVariant(); 1093 } 1094 1095 bool WebEngineHtmlExtension::setHtmlSettingsProperty(HtmlSettingsInterface::HtmlSettingsType type, const QVariant& value) 1096 { 1097 QWebEngineView* view = part() ? part()->view() : nullptr; 1098 QWebEnginePage* page = view ? view->page() : nullptr; 1099 QWebEngineSettings* settings = page ? page->settings() : nullptr; 1100 1101 if (settings) { 1102 switch (type) { 1103 case HtmlSettingsInterface::AutoLoadImages: 1104 settings->setAttribute(QWebEngineSettings::AutoLoadImages, value.toBool()); 1105 return true; 1106 case HtmlSettingsInterface::JavaEnabled: 1107 //settings->setAttribute(QWebESettings::JavaEnabled, value.toBool()); 1108 return false; 1109 case HtmlSettingsInterface::JavascriptEnabled: 1110 settings->setAttribute(QWebEngineSettings::JavascriptEnabled, value.toBool()); 1111 return true; 1112 case HtmlSettingsInterface::PluginsEnabled: 1113 settings->setAttribute(QWebEngineSettings::PluginsEnabled, value.toBool()); 1114 return true; 1115 case HtmlSettingsInterface::DnsPrefetchEnabled: 1116 // settings->setAttribute(QWebEngineSettings::DnsPrefetchEnabled, value.toBool()); 1117 return false; 1118 case HtmlSettingsInterface::MetaRefreshEnabled: 1119 view->triggerPageAction(QWebEnginePage::Stop); 1120 return true; 1121 case HtmlSettingsInterface::LocalStorageEnabled: 1122 settings->setAttribute(QWebEngineSettings::LocalStorageEnabled, value.toBool()); 1123 return false; 1124 case HtmlSettingsInterface::OfflineStorageDatabaseEnabled: 1125 //settings->setAttribute(QWebEngineSettings::OfflineStorageDatabaseEnabled, value.toBool()); 1126 return false; 1127 case HtmlSettingsInterface::OfflineWebApplicationCacheEnabled: 1128 //settings->setAttribute(QWebEngineSettings::OfflineWebApplicationCacheEnabled, value.toBool()); 1129 return false; 1130 case HtmlSettingsInterface::PrivateBrowsingEnabled: 1131 //settings->setAttribute(QWebEnngineSettings::PrivateBrowsingEnabled, value.toBool()); 1132 return false; 1133 case HtmlSettingsInterface::UserDefinedStyleSheetURL: 1134 //qCDebug(WEBENGINEPART_LOG) << "Setting user style sheet for" << page << "to" << value.toUrl(); 1135 // settings->setUserStyleSheetUrl(value.toUrl()); 1136 return false; 1137 default: 1138 break; 1139 } 1140 } 1141 1142 return false; 1143 } 1144 1145 WebEnginePart* WebEngineHtmlExtension::part() const 1146 { 1147 return static_cast<WebEnginePart*>(parent()); 1148 } 1149 1150 WebEngineDownloaderExtension::WebEngineDownloaderExtension(WebEnginePart* parent) : DownloaderExtension(parent) 1151 { 1152 } 1153 1154 WebEngineDownloaderExtension::~WebEngineDownloaderExtension() 1155 { 1156 } 1157 1158 KParts::ReadOnlyPart* WebEngineDownloaderExtension::part() const 1159 { 1160 return qobject_cast<WebEnginePart*>(parent()); 1161 } 1162 1163 void WebEngineDownloaderExtension::addDownloadRequest(QWebEngineDownloadRequest* req) 1164 { 1165 QUrl url = req->url(); 1166 m_downloadRequests.insert(url, req); 1167 auto removeRequest = [this, url] (QObject *obj) {m_downloadRequests.remove(url, dynamic_cast<QWebEngineDownloadRequest*>(obj));}; 1168 connect(req, &QWebEngineDownloadRequest::destroyed, this, removeRequest); 1169 } 1170 1171 KonqInterfaces::DownloaderJob* WebEngineDownloaderExtension::downloadJob(const QUrl& url, quint32 id, QObject* parent) 1172 { 1173 auto items = m_downloadRequests.values(url); 1174 if (items.isEmpty()) { 1175 return nullptr; 1176 } 1177 auto it = std::find_if(items.constBegin(), items.constEnd(), [id](QWebEngineDownloadRequest* it){return it->id() == id;}); 1178 //If no job with the given ID is found, return the last one, which is the first created 1179 QWebEngineDownloadRequest *item = it != items.constEnd() ? *it : items.last(); 1180 return new WebEngineDownloadJob(item, parent); 1181 }