File indexing completed on 2025-03-09 04:54:40
0001 /* 0002 SPDX-FileCopyrightText: 2016-2024 Laurent Montel <montel@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 #include "mailwebengineview.h" 0007 #include "../urlhandlermanager.h" 0008 #include "cidreferencesurlinterceptor/cidreferencesurlinterceptor.h" 0009 #include "cidschemehandler/cidschemehandler.h" 0010 #include "loadexternalreferencesurlinterceptor/loadexternalreferencesurlinterceptor.h" 0011 #include "mailwebenginepage.h" 0012 #include "messageviewer/messageviewersettings.h" 0013 #include "webengineviewer/webengineaccesskey.h" 0014 #include "webengineviewer/webenginescript.h" 0015 #include <WebEngineViewer/BlockExternalResourcesUrlInterceptor> 0016 #include <WebEngineViewer/InterceptorManager> 0017 #include <WebEngineViewer/WebEngineManageScript> 0018 0019 #include "scamdetection/scamdetectionwebengine.h" 0020 #include <QContextMenuEvent> 0021 #include <QWebEngineProfile> 0022 #include <WebEngineViewer/WebHitTest> 0023 0024 #include <QPainter> 0025 #include <QPrinter> 0026 #include <QWebEngineUrlScheme> 0027 0028 #include <WebEngineViewer/WebHitTestResult> 0029 0030 using namespace MessageViewer; 0031 template<typename Arg, typename R, typename C> 0032 struct InvokeWrapper { 0033 R *receiver; 0034 void (C::*memberFunction)(Arg); 0035 void operator()(Arg result) 0036 { 0037 (receiver->*memberFunction)(result); 0038 } 0039 }; 0040 0041 template<typename Arg, typename R, typename C> 0042 0043 InvokeWrapper<Arg, R, C> invoke(R *receiver, void (C::*memberFunction)(Arg)) 0044 { 0045 InvokeWrapper<Arg, R, C> wrapper = {receiver, memberFunction}; 0046 return wrapper; 0047 } 0048 0049 class MessageViewer::MailWebEngineViewPrivate 0050 { 0051 public: 0052 MailWebEngineViewPrivate() = default; 0053 0054 QUrl mHoveredUrl; 0055 QPoint mLastClickPosition; 0056 ScamDetectionWebEngine *mScamDetection = nullptr; 0057 WebEngineViewer::WebEngineAccessKey *mWebViewAccessKey = nullptr; 0058 MessageViewer::LoadExternalReferencesUrlInterceptor *mExternalReference = nullptr; 0059 MailWebEnginePage *mPageEngine = nullptr; 0060 WebEngineViewer::InterceptorManager *mNetworkAccessManager = nullptr; 0061 MessageViewer::ViewerPrivate *mViewer = nullptr; 0062 WebEngineViewer::BlockTrackingUrlInterceptor *mBlockMailTrackingUrl = nullptr; 0063 bool mCanStartDrag = false; 0064 }; 0065 0066 MailWebEngineView::MailWebEngineView(KActionCollection *ac, QWidget *parent) 0067 : WebEngineViewer::WebEngineView(parent) 0068 , d(new MessageViewer::MailWebEngineViewPrivate) 0069 { 0070 d->mPageEngine = new MailWebEnginePage(this); 0071 setPage(d->mPageEngine); 0072 d->mWebViewAccessKey = new WebEngineViewer::WebEngineAccessKey(this, this); 0073 d->mWebViewAccessKey->setActionCollection(ac); 0074 d->mScamDetection = new ScamDetectionWebEngine(this); 0075 connect(d->mScamDetection, &ScamDetectionWebEngine::messageMayBeAScam, this, &MailWebEngineView::messageMayBeAScam); 0076 connect(d->mWebViewAccessKey, &WebEngineViewer::WebEngineAccessKey::openUrl, this, &MailWebEngineView::openUrl); 0077 connect(this, &MailWebEngineView::loadFinished, this, &MailWebEngineView::slotLoadFinished); 0078 0079 d->mPageEngine->profile()->installUrlSchemeHandler(QByteArrayLiteral("cid"), new CidSchemeHandler(this)); 0080 0081 d->mNetworkAccessManager = new WebEngineViewer::InterceptorManager(this, ac, this); 0082 d->mExternalReference = new MessageViewer::LoadExternalReferencesUrlInterceptor(this); 0083 connect(d->mExternalReference, &MessageViewer::LoadExternalReferencesUrlInterceptor::urlBlocked, this, &MailWebEngineView::urlBlocked); 0084 d->mNetworkAccessManager->addInterceptor(d->mExternalReference); 0085 auto cidReference = new MessageViewer::CidReferencesUrlInterceptor(this); 0086 d->mNetworkAccessManager->addInterceptor(cidReference); 0087 auto blockExternalUrl = new WebEngineViewer::BlockExternalResourcesUrlInterceptor(this); 0088 connect(blockExternalUrl, &WebEngineViewer::BlockExternalResourcesUrlInterceptor::formSubmittedForbidden, this, &MailWebEngineView::formSubmittedForbidden); 0089 d->mNetworkAccessManager->addInterceptor(blockExternalUrl); 0090 0091 d->mBlockMailTrackingUrl = new WebEngineViewer::BlockTrackingUrlInterceptor(this); 0092 connect(d->mBlockMailTrackingUrl, &WebEngineViewer::BlockTrackingUrlInterceptor::trackingFound, this, &MailWebEngineView::mailTrackingFound); 0093 d->mNetworkAccessManager->addInterceptor(d->mBlockMailTrackingUrl); 0094 0095 setFocusPolicy(Qt::WheelFocus); 0096 connect(d->mPageEngine, &MailWebEnginePage::urlClicked, this, &MailWebEngineView::openUrl); 0097 connect(page(), &QWebEnginePage::scrollPositionChanged, d->mWebViewAccessKey, &WebEngineViewer::WebEngineAccessKey::hideAccessKeys); 0098 } 0099 0100 MailWebEngineView::~MailWebEngineView() = default; 0101 0102 void MailWebEngineView::readConfig() 0103 { 0104 d->mBlockMailTrackingUrl->setEnabledMailTrackingInterceptor(MessageViewer::MessageViewerSettings::self()->mailTrackingUrlEnabled()); 0105 } 0106 0107 void MailWebEngineView::setLinkHovered(const QUrl &url) 0108 { 0109 // TODO we need to detect image url too. 0110 d->mHoveredUrl = url; 0111 } 0112 0113 void MailWebEngineView::runJavaScriptInWordId(const QString &script) 0114 { 0115 page()->runJavaScript(script, WebEngineViewer::WebEngineManageScript::scriptWordId()); 0116 } 0117 0118 void MailWebEngineView::setViewer(MessageViewer::ViewerPrivate *viewer) 0119 { 0120 d->mViewer = viewer; 0121 } 0122 0123 void MailWebEngineView::contextMenuEvent(QContextMenuEvent *e) 0124 { 0125 WebEngineViewer::WebHitTest *webHit = d->mPageEngine->hitTestContent(e->pos()); 0126 connect(webHit, &WebEngineViewer::WebHitTest::finished, this, &MailWebEngineView::slotWebHitFinished); 0127 } 0128 0129 void MailWebEngineView::slotWebHitFinished(const WebEngineViewer::WebHitTestResult &result) 0130 { 0131 Q_EMIT popupMenu(result); 0132 } 0133 0134 void MailWebEngineView::scrollUp(int pixels) 0135 { 0136 runJavaScriptInWordId(WebEngineViewer::WebEngineScript::scrollUp(pixels)); 0137 } 0138 0139 void MailWebEngineView::scrollDown(int pixels) 0140 { 0141 runJavaScriptInWordId(WebEngineViewer::WebEngineScript::scrollDown(pixels)); 0142 } 0143 0144 void MailWebEngineView::selectAll() 0145 { 0146 page()->triggerAction(QWebEnginePage::SelectAll); 0147 } 0148 0149 void MailWebEngineView::slotZoomChanged(qreal zoom) 0150 { 0151 setZoomFactor(zoom); 0152 } 0153 0154 void MailWebEngineView::scamCheck() 0155 { 0156 d->mScamDetection->scanPage(page()); 0157 } 0158 0159 void MailWebEngineView::slotShowDetails() 0160 { 0161 d->mScamDetection->showDetails(); 0162 } 0163 0164 void MailWebEngineView::forwardKeyReleaseEvent(QKeyEvent *e) 0165 { 0166 if (MessageViewer::MessageViewerSettings::self()->accessKeyEnabled()) { 0167 d->mWebViewAccessKey->keyReleaseEvent(e); 0168 } 0169 } 0170 0171 void MailWebEngineView::forwardMousePressEvent(QMouseEvent *event) 0172 { 0173 if (d->mViewer && !d->mHoveredUrl.isEmpty()) { 0174 if (event->button() == Qt::LeftButton && (event->modifiers() & Qt::ShiftModifier)) { 0175 // special processing for shift+click 0176 if (URLHandlerManager::instance()->handleShiftClick(d->mHoveredUrl, d->mViewer)) { 0177 event->accept(); 0178 return; 0179 } 0180 } 0181 if (event->button() == Qt::LeftButton) { 0182 d->mCanStartDrag = URLHandlerManager::instance()->willHandleDrag(d->mHoveredUrl, d->mViewer); 0183 d->mLastClickPosition = event->pos(); 0184 } 0185 } 0186 } 0187 0188 void MailWebEngineView::forwardMouseMoveEvent(QMouseEvent *event) 0189 { 0190 if (d->mViewer && !d->mHoveredUrl.isEmpty()) { 0191 // If we are potentially handling a drag, deal with that. 0192 if (d->mCanStartDrag && (event->buttons() & Qt::LeftButton)) { 0193 if ((d->mLastClickPosition - event->pos()).manhattanLength() > QApplication::startDragDistance()) { 0194 if (URLHandlerManager::instance()->handleDrag(d->mHoveredUrl, d->mViewer)) { 0195 // If the URL handler manager started a drag, don't handle this in the future 0196 d->mCanStartDrag = false; 0197 } 0198 } 0199 event->accept(); 0200 } 0201 } 0202 } 0203 0204 void MailWebEngineView::forwardMouseReleaseEvent(QMouseEvent *event) 0205 { 0206 Q_UNUSED(event) 0207 d->mCanStartDrag = false; 0208 } 0209 0210 void MailWebEngineView::forwardKeyPressEvent(QKeyEvent *e) 0211 { 0212 if (e && hasFocus()) { 0213 if (MessageViewer::MessageViewerSettings::self()->accessKeyEnabled()) { 0214 d->mWebViewAccessKey->keyPressEvent(e); 0215 } 0216 } 0217 } 0218 0219 void MailWebEngineView::forwardWheelEvent(QWheelEvent *e) 0220 { 0221 if (MessageViewer::MessageViewerSettings::self()->accessKeyEnabled()) { 0222 d->mWebViewAccessKey->wheelEvent(e); 0223 } 0224 if (QApplication::keyboardModifiers() & Qt::ControlModifier) { 0225 const int numDegrees = e->angleDelta().y() / 8; 0226 const int numSteps = numDegrees / 15; 0227 Q_EMIT wheelZoomChanged(numSteps); 0228 e->accept(); 0229 } 0230 } 0231 0232 void MailWebEngineView::resizeEvent(QResizeEvent *e) 0233 { 0234 if (MessageViewer::MessageViewerSettings::self()->accessKeyEnabled()) { 0235 d->mWebViewAccessKey->resizeEvent(e); 0236 } 0237 QWebEngineView::resizeEvent(e); 0238 } 0239 0240 void MailWebEngineView::saveMainFrameScreenshotInFile(const QString &filename) 0241 { 0242 // TODO need to verify it 0243 QImage image(size(), QImage::Format_ARGB32_Premultiplied); 0244 image.fill(Qt::transparent); 0245 0246 QPainter painter(&image); 0247 painter.setRenderHint(QPainter::Antialiasing, true); 0248 painter.setRenderHint(QPainter::TextAntialiasing, true); 0249 painter.setRenderHint(QPainter::SmoothPixmapTransform, true); 0250 render(&painter); 0251 painter.end(); 0252 image.save(filename); 0253 } 0254 0255 void MailWebEngineView::showAccessKeys() 0256 { 0257 d->mWebViewAccessKey->showAccessKeys(); 0258 } 0259 0260 void MailWebEngineView::hideAccessKeys() 0261 { 0262 d->mWebViewAccessKey->hideAccessKeys(); 0263 } 0264 0265 void MailWebEngineView::isScrolledToBottom() 0266 { 0267 page()->runJavaScript(WebEngineViewer::WebEngineScript::isScrolledToBottom(), 0268 WebEngineViewer::WebEngineManageScript::scriptWordId(), 0269 invoke(this, &MailWebEngineView::handleIsScrolledToBottom)); 0270 } 0271 0272 void MailWebEngineView::setElementByIdVisible(const QString &id, bool visible) 0273 { 0274 runJavaScriptInWordId(WebEngineViewer::WebEngineScript::setElementByIdVisible(id, visible)); 0275 } 0276 0277 void MailWebEngineView::removeAttachmentMarking(const QString &id) 0278 { 0279 runJavaScriptInWordId(WebEngineViewer::WebEngineScript::removeStyleToElement(id)); 0280 } 0281 0282 void MailWebEngineView::markAttachment(const QString &id, const QString &style) 0283 { 0284 runJavaScriptInWordId(WebEngineViewer::WebEngineScript::setStyleToElement(id, style)); 0285 } 0286 0287 void MailWebEngineView::scrollToAnchor(const QString &anchor) 0288 { 0289 page()->runJavaScript(WebEngineViewer::WebEngineScript::searchElementPosition(anchor), 0290 WebEngineViewer::WebEngineManageScript::scriptWordId(), 0291 invoke(this, &MailWebEngineView::handleScrollToAnchor)); 0292 } 0293 0294 void MailWebEngineView::handleIsScrolledToBottom(const QVariant &result) 0295 { 0296 bool scrolledToBottomResult = false; 0297 if (result.isValid()) { 0298 scrolledToBottomResult = result.toBool(); 0299 } 0300 Q_EMIT pageIsScrolledToBottom(scrolledToBottomResult); 0301 } 0302 0303 void MailWebEngineView::handleScrollToAnchor(const QVariant &result) 0304 { 0305 if (result.isValid()) { 0306 const QList<QVariant> lst = result.toList(); 0307 if (lst.count() == 2) { 0308 const QPoint pos(lst.at(0).toInt(), lst.at(1).toInt()); 0309 runJavaScriptInWordId(WebEngineViewer::WebEngineScript::scrollToPosition(pos)); 0310 } 0311 } 0312 } 0313 0314 void MailWebEngineView::scrollPageDown(int percent) 0315 { 0316 runJavaScriptInWordId(WebEngineViewer::WebEngineScript::scrollPercentage(percent)); 0317 } 0318 0319 void MailWebEngineView::scrollPageUp(int percent) 0320 { 0321 scrollPageDown(-percent); 0322 } 0323 0324 void MailWebEngineView::scrollToRelativePosition(qreal pos) 0325 { 0326 runJavaScriptInWordId(WebEngineViewer::WebEngineScript::scrollToRelativePosition(pos)); 0327 } 0328 0329 void MailWebEngineView::setAllowExternalContent(bool b) 0330 { 0331 if (d->mExternalReference->allowExternalContent() != b) { 0332 d->mExternalReference->setAllowExternalContent(b); 0333 reload(); 0334 } 0335 } 0336 0337 QList<QAction *> MailWebEngineView::interceptorUrlActions(const WebEngineViewer::WebHitTestResult &result) const 0338 { 0339 return d->mNetworkAccessManager->interceptorUrlActions(result); 0340 } 0341 0342 void MailWebEngineView::slotLoadFinished() 0343 { 0344 scamCheck(); 0345 } 0346 0347 void MailWebEngineView::setPrintElementBackground(bool printElementBackground) 0348 { 0349 d->mPageEngine->setPrintElementBackground(printElementBackground); 0350 } 0351 0352 void MailWebEngineView::printPreviewPage(QPrinter *printer) 0353 { 0354 print(printer); 0355 } 0356 0357 void MailWebEngineView::initializeCustomScheme() 0358 { 0359 QWebEngineUrlScheme cidScheme("cid"); 0360 cidScheme.setFlags(QWebEngineUrlScheme::SecureScheme | QWebEngineUrlScheme::ContentSecurityPolicyIgnored | QWebEngineUrlScheme::LocalScheme 0361 | QWebEngineUrlScheme::LocalAccessAllowed); 0362 cidScheme.setSyntax(QWebEngineUrlScheme::Syntax::Path); 0363 QWebEngineUrlScheme::registerScheme(cidScheme); 0364 } 0365 0366 #include "moc_mailwebengineview.cpp"