1 /*
2    SPDX-FileCopyrightText: 2015-2021 Laurent Montel <montel@kde.org>
3 
4    SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "articleviewerwebengine.h"
8 #include "actionmanager.h"
9 #include "actions/actions.h"
10 #include "akregator_debug.h"
11 #include "akregatorconfig.h"
12 #include "articleviewerwebenginepage.h"
13 #include "urlhandler/webengine/urlhandlerwebengine.h"
14 #include "webengine/urlhandlerwebenginemanager.h"
15 #include <KPIMTextEdit/TextToSpeech>
16 #include <WebEngineViewer/InterceptorManager>
17 #include <WebEngineViewer/WebEngineAccessKey>
18 #include <WebEngineViewer/WebEngineManageScript>
19 #include <WebEngineViewer/ZoomActionMenu>
20 
21 #include <GrantleeTheme/GrantleeThemeManager>
22 #include <KAboutData>
23 #include <KActionCollection>
24 #include <KGuiItem>
25 #include <KIO/FileCopyJob>
26 #include <KJobUiDelegate>
27 #include <KJobWidgets>
28 #include <KLocalizedString>
29 #include <KMessageBox>
30 #include <QApplication>
31 #include <QClipboard>
32 #include <QFileDialog>
33 #include <QMenu>
34 #include <QMouseEvent>
35 #include <QPrinter>
36 #include <QWebEngineProfile>
37 #include <QWebEngineUrlRequestInterceptor>
38 #include <viewerplugintoolmanager.h>
39 
40 #include <WebEngineViewer/BlockExternalResourcesUrlInterceptor>
41 #include <WebEngineViewer/BlockTrackingUrlInterceptor>
42 #include <WebEngineViewer/LoadExternalReferencesUrlInterceptor>
43 #include <WebEngineViewer/WebEngineScript>
44 #include <WebEngineViewer/WebHitTest>
45 #include <WebEngineViewer/WebHitTestResult>
46 
47 #include <WebEngineViewer/LocalDataBaseManager>
48 
49 #include <KIO/KUriFilterSearchProviderActions>
50 
51 using namespace Akregator;
52 
53 class AkregatorRequestInterceptor : public QWebEngineUrlRequestInterceptor
54 {
55     Q_OBJECT
56 
57 public:
AkregatorRequestInterceptor(QObject * parent=nullptr)58     explicit AkregatorRequestInterceptor(QObject *parent = nullptr)
59         : QWebEngineUrlRequestInterceptor(parent)
60     {
61     }
62 
interceptRequest(QWebEngineUrlRequestInfo & info)63     void interceptRequest(QWebEngineUrlRequestInfo &info) override
64     {
65         Q_UNUSED(info)
66     }
67 };
68 #define HAVE_BLOCK_SUPPORT 1
ArticleViewerWebEngine(KActionCollection * ac,QWidget * parent)69 ArticleViewerWebEngine::ArticleViewerWebEngine(KActionCollection *ac, QWidget *parent)
70     : WebEngineViewer::WebEngineView(parent)
71     , mActionCollection(ac)
72 {
73 #ifndef HAVE_BLOCK_SUPPORT
74     mNetworkAccessManager = new WebEngineViewer::InterceptorManager(this, ac, this);
75 #endif
76     mPageEngine = new ArticleViewerWebEnginePage(this);
77     QWebEngineProfile *profile = mPageEngine->profile();
78     profile->setPersistentCookiesPolicy(QWebEngineProfile::ForcePersistentCookies);
79 #ifndef HAVE_BLOCK_SUPPORT
80     // Needed to workaround crash in webengine, see https://bugreports.qt.io/browse/QTBUG-72260
81     auto webEngineUrlInterceptor = new AkregatorRequestInterceptor();
82     connect(profile, &QObject::destroyed, webEngineUrlInterceptor, &AkregatorRequestInterceptor::deleteLater);
83     profile->setUrlRequestInterceptor(webEngineUrlInterceptor);
84 #endif
85     setPage(mPageEngine);
86 
87 #ifdef HAVE_BLOCK_SUPPORT
88     mNetworkAccessManager = new WebEngineViewer::InterceptorManager(this, ac, this);
89     mExternalReference = new WebEngineViewer::LoadExternalReferencesUrlInterceptor(this);
90     mExternalReference->setAllowExternalContent(Settings::self()->loadExternalReferences());
91     // connect(externalReference, &MessageViewer::LoadExternalReferencesUrlInterceptor::urlBlocked, this, &MailWebEngineView::urlBlocked);
92     mNetworkAccessManager->addInterceptor(mExternalReference);
93     auto blockTracking = new WebEngineViewer::BlockTrackingUrlInterceptor(this);
94     connect(blockTracking, &WebEngineViewer::BlockTrackingUrlInterceptor::trackingFound, this, &ArticleViewerWebEngine::trackingFound);
95     mNetworkAccessManager->addInterceptor(blockTracking);
96     mBlockExternalReference = new WebEngineViewer::BlockExternalResourcesUrlInterceptor(this);
97     // TODO connect(mBlockExternalReference, &WebEngineViewer::BlockExternalResourcesUrlInterceptor::formSubmittedForbidden, this,
98     // &MailWebEngineView::urlBlocked);
99 #endif
100     connect(this, &ArticleViewerWebEngine::showContextMenu, this, &ArticleViewerWebEngine::slotShowContextMenu);
101 
102     setFocusPolicy(Qt::WheelFocus);
103     connect(mPageEngine, &ArticleViewerWebEnginePage::urlClicked, this, &ArticleViewerWebEngine::slotLinkClicked);
104 
105     mWebEngineViewAccessKey = new WebEngineViewer::WebEngineAccessKey(this, this);
106     mWebEngineViewAccessKey->setActionCollection(mActionCollection);
107     connect(mWebEngineViewAccessKey, &WebEngineViewer::WebEngineAccessKey::openUrl, this, &ArticleViewerWebEngine::slotLinkClicked);
108 
109     connect(this, &ArticleViewerWebEngine::loadStarted, this, &ArticleViewerWebEngine::slotLoadStarted);
110     connect(this, &ArticleViewerWebEngine::loadFinished, this, &ArticleViewerWebEngine::slotLoadFinished);
111     connect(page(), &QWebEnginePage::linkHovered, this, &ArticleViewerWebEngine::slotLinkHovered);
112 
113     setContextMenuPolicy(Qt::DefaultContextMenu);
114     mWebShortcutMenuManager = new KIO::KUriFilterSearchProviderActions(this);
115     mShareServiceManager = new PimCommon::ShareServiceUrlManager(this);
116     connect(mShareServiceManager, &PimCommon::ShareServiceUrlManager::serviceUrlSelected, this, &ArticleViewerWebEngine::slotServiceUrlSelected);
117     connect(page(), &QWebEnginePage::audioMutedChanged, this, &ArticleViewerWebEngine::slotWebPageMutedOrAudibleChanged);
118     connect(page(), &QWebEnginePage::recentlyAudibleChanged, this, &ArticleViewerWebEngine::slotWebPageMutedOrAudibleChanged);
119 
120     connect(phishingDatabase(), &WebEngineViewer::LocalDataBaseManager::checkUrlFinished, this, &ArticleViewerWebEngine::slotCheckedUrlFinished);
121 }
122 
~ArticleViewerWebEngine()123 ArticleViewerWebEngine::~ArticleViewerWebEngine()
124 {
125 }
126 
execPrintPreviewPage(QPrinter * printer,int timeout)127 void ArticleViewerWebEngine::execPrintPreviewPage(QPrinter *printer, int timeout)
128 {
129     if (!mPageEngine->execPrintPreviewPage(printer, timeout)) {
130         qCWarning(AKREGATOR_LOG) << "Impossible to print page";
131     }
132 }
133 
updateSecurity()134 void ArticleViewerWebEngine::updateSecurity()
135 {
136     mExternalReference->setAllowExternalContent(Settings::self()->loadExternalReferences());
137 }
138 
slotWebPageMutedOrAudibleChanged()139 void ArticleViewerWebEngine::slotWebPageMutedOrAudibleChanged()
140 {
141     Q_EMIT webPageMutedOrAudibleChanged(page()->isAudioMuted(), page()->recentlyAudible());
142 }
143 
introductionData() const144 QVariantHash ArticleViewerWebEngine::introductionData() const
145 {
146     QVariantHash data;
147     data[QStringLiteral("icon")] = QStringLiteral("akregator");
148     data[QStringLiteral("name")] = i18n("Akregator");
149     data[QStringLiteral("subtitle")] = i18n("Akregator is a KDE news feed reader.");
150     data[QStringLiteral("version")] = KAboutData::applicationData().version();
151     return data;
152 }
153 
showAboutPage()154 void ArticleViewerWebEngine::showAboutPage()
155 {
156     paintAboutScreen(QStringLiteral(":/about/introduction_akregator.html"), introductionData());
157 }
158 
paintAboutScreen(const QString & templateName,const QVariantHash & data)159 void ArticleViewerWebEngine::paintAboutScreen(const QString &templateName, const QVariantHash &data)
160 {
161     GrantleeTheme::ThemeManager manager(QStringLiteral("splashPage"), QStringLiteral("splash.theme"), nullptr, QStringLiteral("messageviewer/about/"));
162     GrantleeTheme::Theme theme = manager.theme(QStringLiteral("default"));
163     if (theme.isValid()) {
164         setHtml(theme.render(templateName, data, QByteArrayLiteral("akregator")), QUrl::fromLocalFile(theme.absolutePath() + QLatin1Char('/')));
165     } else {
166         qCDebug(AKREGATOR_LOG) << "Theme error: failed to find splash theme";
167     }
168 }
169 
slotServiceUrlSelected(PimCommon::ShareServiceUrlManager::ServiceType type)170 void ArticleViewerWebEngine::slotServiceUrlSelected(PimCommon::ShareServiceUrlManager::ServiceType type)
171 {
172     if (mCurrentUrl.isEmpty()) {
173         return;
174     }
175     const QUrl url = mShareServiceManager->generateServiceUrl(mCurrentUrl.url(), QString(), type);
176     mShareServiceManager->openUrl(url);
177 }
178 
slotSaveLinkAs()179 void ArticleViewerWebEngine::slotSaveLinkAs()
180 {
181     QUrl url(mCurrentUrl);
182     if (url.fileName().isEmpty()) {
183         url = url.adjusted(QUrl::StripTrailingSlash);
184         url.setPath(url.path() + QLatin1String("/index.html"));
185     }
186 
187     auto dlg = new QFileDialog(this);
188     dlg->setAttribute(Qt::WA_DeleteOnClose);
189     dlg->setAcceptMode(QFileDialog::AcceptSave);
190     dlg->setWindowTitle(i18nc("@title:window", "Save As"));
191     dlg->setOption(QFileDialog::DontConfirmOverwrite, false);
192     dlg->selectFile(url.fileName());
193     dlg->show();
194     connect(dlg, &QFileDialog::urlSelected, this, [this, url](const QUrl &destURL) {
195         if (destURL.isValid()) {
196             KIO::FileCopyJob *job = KIO::file_copy(url, destURL, -1, KIO::Overwrite);
197             job->addMetaData(QStringLiteral("MaxCacheSize"), QStringLiteral("0")); // Don't store in http cache.
198             job->addMetaData(QStringLiteral("cache"), QStringLiteral("cache")); // Use entry from cache if available.
199             KJobWidgets::setWindow(job, this);
200             job->uiDelegate()->setAutoErrorHandlingEnabled(true);
201         }
202     });
203 }
204 
slotSaveImageOnDiskInFrame()205 void ArticleViewerWebEngine::slotSaveImageOnDiskInFrame()
206 {
207     slotSaveLinkAs();
208 }
209 
slotCopyImageLocationInFrame()210 void ArticleViewerWebEngine::slotCopyImageLocationInFrame()
211 {
212     slotCopyLinkAddress();
213 }
214 
slotMute(bool mute)215 void ArticleViewerWebEngine::slotMute(bool mute)
216 {
217     page()->setAudioMuted(mute);
218 }
219 
slotCopyLinkAddress()220 void ArticleViewerWebEngine::slotCopyLinkAddress()
221 {
222     if (mCurrentUrl.isEmpty()) {
223         return;
224     }
225     QClipboard *cb = QApplication::clipboard();
226     cb->setText(mCurrentUrl.toString(), QClipboard::Clipboard);
227     // don't set url to selection as it's a no-no according to a fd.o spec
228     // which spec? Nobody seems to care (tested Firefox (3.5.10) Konqueror,and KMail (4.2.3)), so I re-enable the following line unless someone gives
229     // a good reason to remove it again (bug 183022) --Frank
230     cb->setText(mCurrentUrl.toString(), QClipboard::Selection);
231 }
232 
contextMenuEvent(QContextMenuEvent * e)233 void ArticleViewerWebEngine::contextMenuEvent(QContextMenuEvent *e)
234 {
235     displayContextMenu(e->pos());
236 }
237 
slotShowContextMenu(const QPoint & pos)238 void ArticleViewerWebEngine::slotShowContextMenu(const QPoint &pos)
239 {
240     displayContextMenu(pos);
241 }
242 
slotCopy()243 void ArticleViewerWebEngine::slotCopy()
244 {
245     triggerPageAction(QWebEnginePage::Copy);
246 }
247 
slotLoadFinished()248 void ArticleViewerWebEngine::slotLoadFinished()
249 {
250     restoreCurrentPosition();
251     unsetCursor();
252 }
253 
slotLoadStarted()254 void ArticleViewerWebEngine::slotLoadStarted()
255 {
256     mWebEngineViewAccessKey->hideAccessKeys();
257     setCursor(Qt::WaitCursor);
258 }
259 
slotWebHitFinished(const WebEngineViewer::WebHitTestResult & result)260 void ArticleViewerWebEngine::slotWebHitFinished(const WebEngineViewer::WebHitTestResult &result)
261 {
262     mCurrentUrl = result.linkUrl();
263     if (URLHandlerWebEngineManager::instance()->handleContextMenuRequest(mCurrentUrl, mapToGlobal(result.pos()), this)) {
264         return;
265     }
266 
267     QMenu popup(this);
268     const bool noContentSelected = selectedText().isEmpty();
269     if (noContentSelected) {
270         if (!mCurrentUrl.isEmpty()) {
271             {
272                 QAction *act = createOpenLinkInNewTabAction(mCurrentUrl, &popup);
273                 connect(act, &QAction::triggered, this, &ArticleViewerWebEngine::slotOpenLinkInBackgroundTab);
274                 popup.addAction(act);
275             }
276             {
277                 QAction *act = createOpenLinkInExternalBrowserAction(mCurrentUrl, &popup);
278                 connect(act, &QAction::triggered, this, &ArticleViewerWebEngine::slotOpenLinkInBrowser);
279                 popup.addAction(act);
280             }
281             popup.addSeparator();
282             popup.addAction(mActionCollection->action(QStringLiteral("savelinkas")));
283             popup.addAction(mActionCollection->action(QStringLiteral("copylinkaddress")));
284         }
285         if (!result.imageUrl().isEmpty()) {
286             popup.addSeparator();
287             popup.addAction(mActionCollection->action(QStringLiteral("copy_image_location")));
288             popup.addAction(mActionCollection->action(QStringLiteral("saveas_imageurl")));
289         }
290         popup.addSeparator();
291         popup.addActions(viewerPluginActionList(MessageViewer::ViewerPluginInterface::NeedUrl));
292         popup.addSeparator();
293         popup.addAction(mShareServiceManager->menu());
294     } else {
295         popup.addAction(ActionManager::getInstance()->action(QStringLiteral("viewer_copy")));
296         popup.addSeparator();
297         mWebShortcutMenuManager->setSelectedText(page()->selectedText());
298         mWebShortcutMenuManager->addWebShortcutsToMenu(&popup);
299         popup.addSeparator();
300         popup.addActions(viewerPluginActionList(MessageViewer::ViewerPluginInterface::NeedSelection));
301     }
302     popup.addSeparator();
303     popup.addAction(ActionManager::getInstance()->action(QStringLiteral("viewer_print")));
304     popup.addAction(ActionManager::getInstance()->action(QStringLiteral("viewer_printpreview")));
305     popup.addSeparator();
306     popup.addAction(ActionManager::getInstance()->action(QStringLiteral("tab_mute")));
307     popup.addAction(ActionManager::getInstance()->action(QStringLiteral("tab_unmute")));
308     popup.addSeparator();
309     popup.addAction(ActionManager::getInstance()->action(QStringLiteral("find_in_messages")));
310     if (KPIMTextEdit::TextToSpeech::self()->isReady()) {
311         popup.addSeparator();
312         popup.addAction(ActionManager::getInstance()->action(QStringLiteral("speak_text")));
313     }
314     popup.exec(mapToGlobal(result.pos()));
315 }
316 
displayContextMenu(const QPoint & pos)317 void ArticleViewerWebEngine::displayContextMenu(const QPoint &pos)
318 {
319     WebEngineViewer::WebHitTest *webHit = mPageEngine->hitTestContent(pos);
320     connect(webHit, &WebEngineViewer::WebHitTest::finished, this, &ArticleViewerWebEngine::slotWebHitFinished);
321 }
322 
slotLinkHovered(const QString & link)323 void ArticleViewerWebEngine::slotLinkHovered(const QString &link)
324 {
325     QString msg = URLHandlerWebEngineManager::instance()->statusBarMessage(QUrl(link), this);
326     if (msg.isEmpty()) {
327         msg = link;
328     }
329 
330     Q_EMIT showStatusBarMessage(msg);
331 }
332 
forwardKeyReleaseEvent(QKeyEvent * e)333 void ArticleViewerWebEngine::forwardKeyReleaseEvent(QKeyEvent *e)
334 {
335     if (Settings::self()->accessKeyEnabled()) {
336         mWebEngineViewAccessKey->keyReleaseEvent(e);
337     }
338 }
339 
forwardKeyPressEvent(QKeyEvent * e)340 void ArticleViewerWebEngine::forwardKeyPressEvent(QKeyEvent *e)
341 {
342     if (e && hasFocus()) {
343         if (Settings::self()->accessKeyEnabled()) {
344             mWebEngineViewAccessKey->keyPressEvent(e);
345         }
346     }
347 }
348 
forwardWheelEvent(QWheelEvent * e)349 void ArticleViewerWebEngine::forwardWheelEvent(QWheelEvent *e)
350 {
351     if (Settings::self()->accessKeyEnabled()) {
352         mWebEngineViewAccessKey->wheelEvent(e);
353     }
354     if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
355         const int numDegrees = e->angleDelta().y() / 8;
356         const int numSteps = numDegrees / 15;
357         const qreal factor = ActionManager::getInstance()->zoomActionMenu()->zoomFactor() + numSteps * 10;
358         if (factor >= 10 && factor <= 300) {
359             ActionManager::getInstance()->zoomActionMenu()->setZoomFactor(factor);
360             ActionManager::getInstance()->zoomActionMenu()->setWebViewerZoomFactor(factor / 100.0);
361         }
362         e->accept();
363     }
364 }
365 
resizeEvent(QResizeEvent * e)366 void ArticleViewerWebEngine::resizeEvent(QResizeEvent *e)
367 {
368     if (Settings::self()->accessKeyEnabled()) {
369         mWebEngineViewAccessKey->resizeEvent(e);
370     }
371     QWebEngineView::resizeEvent(e);
372 }
373 
disableIntroduction()374 void ArticleViewerWebEngine::disableIntroduction()
375 {
376     KGuiItem yesButton(KStandardGuiItem::yes());
377     yesButton.setText(i18n("Disable"));
378     KGuiItem noButton(KStandardGuiItem::no());
379     noButton.setText(i18n("Keep Enabled"));
380     if (KMessageBox::questionYesNo(this,
381                                    i18n("Are you sure you want to disable this introduction page?"),
382                                    i18n("Disable Introduction Page"),
383                                    yesButton,
384                                    noButton)
385         == KMessageBox::Yes) {
386         Settings::self()->setDisableIntroduction(true);
387         Settings::self()->save();
388     }
389 }
390 
setArticleAction(ArticleViewerWebEngine::ArticleAction type,const QString & articleId,const QString & feed)391 void ArticleViewerWebEngine::setArticleAction(ArticleViewerWebEngine::ArticleAction type, const QString &articleId, const QString &feed)
392 {
393     Q_EMIT articleAction(type, articleId, feed);
394 }
395 
restoreCurrentPosition()396 void ArticleViewerWebEngine::restoreCurrentPosition()
397 {
398     mPageEngine->runJavaScript(WebEngineViewer::WebEngineScript::scrollToRelativePosition(0), WebEngineViewer::WebEngineManageScript::scriptWordId());
399 }
400 
forwardMouseReleaseEvent(QMouseEvent * event)401 void ArticleViewerWebEngine::forwardMouseReleaseEvent(QMouseEvent *event)
402 {
403     if (event->button() & Qt::RightButton) {
404         Q_EMIT showContextMenu(event->pos());
405         mLastButtonClicked = RightButton;
406     } else if (event->button() & Qt::MiddleButton) {
407         mLastButtonClicked = MiddleButton;
408     } else if (event->button() & Qt::LeftButton) {
409         mLastButtonClicked = LeftButton;
410     }
411 }
412 
urlIsAMalwareButContinue()413 bool ArticleViewerWebEngine::urlIsAMalwareButContinue()
414 {
415     if (KMessageBox::No
416         == KMessageBox::warningYesNo(this,
417                                      i18n("This web site is a malware, do you want to continue to show it?"),
418                                      i18n("Malware"),
419                                      KStandardGuiItem::cont(),
420                                      KStandardGuiItem::cancel())) {
421         return false;
422     }
423     return true;
424 }
425 
slotCheckedUrlFinished(const QUrl & url,WebEngineViewer::CheckPhishingUrlUtil::UrlStatus status)426 void ArticleViewerWebEngine::slotCheckedUrlFinished(const QUrl &url, WebEngineViewer::CheckPhishingUrlUtil::UrlStatus status)
427 {
428     switch (status) {
429     case WebEngineViewer::CheckPhishingUrlUtil::BrokenNetwork:
430         KMessageBox::error(this, i18n("The network is broken."), i18n("Check Phishing Url"));
431         break;
432     case WebEngineViewer::CheckPhishingUrlUtil::InvalidUrl:
433         KMessageBox::error(this, i18n("The url %1 is not valid.", url.toString()), i18n("Check Phishing Url"));
434         break;
435     case WebEngineViewer::CheckPhishingUrlUtil::Ok:
436         break;
437     case WebEngineViewer::CheckPhishingUrlUtil::MalWare:
438         if (!urlIsAMalwareButContinue()) {
439             return;
440         }
441         break;
442     case WebEngineViewer::CheckPhishingUrlUtil::Unknown:
443         qCWarning(AKREGATOR_LOG) << "ArticleViewerWebEngine::slotCheckedUrlFinished unknown error ";
444         break;
445     }
446     openSafeUrl(url);
447 }
448 
slotLinkClicked(const QUrl & url)449 void ArticleViewerWebEngine::slotLinkClicked(const QUrl &url)
450 {
451     if (URLHandlerWebEngineManager::instance()->handleClick(url, this)) {
452         return;
453     }
454     if (Settings::checkPhishingUrl()) {
455         phishingDatabase()->checkUrl(url);
456     } else {
457         openSafeUrl(url);
458     }
459 }
460 
openSafeUrl(const QUrl & url)461 void ArticleViewerWebEngine::openSafeUrl(const QUrl &url)
462 {
463     mCurrentUrl = url;
464     OpenUrlRequest req(mCurrentUrl);
465     if (mLastButtonClicked == LeftButton) {
466         switch (Settings::lMBBehaviour()) {
467         case Settings::EnumLMBBehaviour::OpenInExternalBrowser:
468             req.setOptions(OpenUrlRequest::ExternalBrowser);
469             break;
470         case Settings::EnumLMBBehaviour::OpenInBackground:
471             req.setOpenInBackground(true);
472             req.setOptions(OpenUrlRequest::NewTab);
473             break;
474         default:
475             break;
476         }
477     } else if (mLastButtonClicked == MiddleButton) {
478         switch (Settings::mMBBehaviour()) {
479         case Settings::EnumMMBBehaviour::OpenInExternalBrowser:
480             req.setOptions(OpenUrlRequest::ExternalBrowser);
481             break;
482         case Settings::EnumMMBBehaviour::OpenInBackground:
483             req.setOpenInBackground(true);
484             req.setOptions(OpenUrlRequest::NewTab);
485             break;
486         default:
487             break;
488         }
489     }
490     Q_EMIT signalOpenUrlRequest(req);
491 }
492 
slotOpenLinkInForegroundTab()493 void ArticleViewerWebEngine::slotOpenLinkInForegroundTab()
494 {
495     OpenUrlRequest req(mCurrentUrl);
496     req.setOptions(OpenUrlRequest::NewTab);
497     Q_EMIT signalOpenUrlRequest(req);
498 }
499 
slotOpenLinkInBackgroundTab()500 void ArticleViewerWebEngine::slotOpenLinkInBackgroundTab()
501 {
502     OpenUrlRequest req(mCurrentUrl);
503     req.setOptions(OpenUrlRequest::NewTab);
504     req.setOpenInBackground(true);
505     Q_EMIT signalOpenUrlRequest(req);
506 }
507 
slotOpenLinkInBrowser()508 void ArticleViewerWebEngine::slotOpenLinkInBrowser()
509 {
510     OpenUrlRequest req(mCurrentUrl);
511     req.setOptions(OpenUrlRequest::ExternalBrowser);
512     Q_EMIT signalOpenUrlRequest(req);
513 }
514 
createViewerPluginToolManager(KActionCollection * ac,QWidget * parent)515 void ArticleViewerWebEngine::createViewerPluginToolManager(KActionCollection *ac, QWidget *parent)
516 {
517     mViewerPluginToolManager = new MessageViewer::ViewerPluginToolManager(parent, this);
518     mViewerPluginToolManager->setActionCollection(ac);
519     mViewerPluginToolManager->setPluginName(QStringLiteral("akregator"));
520     mViewerPluginToolManager->setPluginDirectory(QStringLiteral("akregator/viewerplugin"));
521     if (!mViewerPluginToolManager->initializePluginList()) {
522         qCWarning(AKREGATOR_LOG) << " Impossible to initialize plugins";
523     }
524     mViewerPluginToolManager->createView();
525     connect(mViewerPluginToolManager, &MessageViewer::ViewerPluginToolManager::activatePlugin, this, &ArticleViewerWebEngine::slotActivatePlugin);
526 }
527 
viewerPluginActionList(MessageViewer::ViewerPluginInterface::SpecificFeatureTypes features)528 QList<QAction *> ArticleViewerWebEngine::viewerPluginActionList(MessageViewer::ViewerPluginInterface::SpecificFeatureTypes features)
529 {
530     if (mViewerPluginToolManager) {
531         return mViewerPluginToolManager->viewerPluginActionList(features);
532     }
533     return QList<QAction *>();
534 }
535 
slotActivatePlugin(MessageViewer::ViewerPluginInterface * interface)536 void ArticleViewerWebEngine::slotActivatePlugin(MessageViewer::ViewerPluginInterface *interface)
537 {
538     const QString text = selectedText();
539     if (!text.isEmpty()) {
540         interface->setText(text);
541     }
542     interface->setUrl(mCurrentUrl);
543     interface->execute();
544 }
545 
546 #include "articleviewerwebengine.moc"
547