File indexing completed on 2024-05-12 04:58:31

0001 /* ============================================================
0002 * Falkon - Qt web browser
0003 * Copyright (C) 2010-2018 David Rosca <nowrep@gmail.com>
0004 *
0005 * This program is free software: you can redistribute it and/or modify
0006 * it under the terms of the GNU General Public License as published by
0007 * the Free Software Foundation, either version 3 of the License, or
0008 * (at your option) any later version.
0009 *
0010 * This program is distributed in the hope that it will be useful,
0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013 * GNU General Public License for more details.
0014 *
0015 * You should have received a copy of the GNU General Public License
0016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0017 * ============================================================ */
0018 #include "webview.h"
0019 #include "webpage.h"
0020 #include "mainapplication.h"
0021 #include "qztools.h"
0022 #include "iconprovider.h"
0023 #include "history.h"
0024 #include "pluginproxy.h"
0025 #include "downloadmanager.h"
0026 #include "siteinfo.h"
0027 #include "searchenginesmanager.h"
0028 #include "browsinglibrary.h"
0029 #include "bookmarkstools.h"
0030 #include "settings.h"
0031 #include "qzsettings.h"
0032 #include "enhancedmenu.h"
0033 #include "locationbar.h"
0034 #include "webinspector.h"
0035 #include "scripts.h"
0036 #include "webhittestresult.h"
0037 #include "webscrollbarmanager.h"
0038 
0039 #include <iostream>
0040 
0041 #include <QDir>
0042 #include <QTimer>
0043 #include <QDesktopServices>
0044 #include <QWebEngineHistory>
0045 #include <QClipboard>
0046 #include <QMimeData>
0047 #include <QWebEngineContextMenuRequest>
0048 #include <QStackedLayout>
0049 #include <QScrollBar>
0050 #include <QPrintDialog>
0051 #include <QPrinter>
0052 #include <QQuickWidget>
0053 #include <QtWebEngineWidgetsVersion>
0054 
0055 bool WebView::s_forceContextMenuOnMouseRelease = false;
0056 
0057 WebView::WebView(QWidget* parent)
0058     : QWebEngineView(parent)
0059     , m_progress(100)
0060     , m_backgroundActivity(false)
0061     , m_page(nullptr)
0062     , m_firstLoad(false)
0063 {
0064     connect(this, &QWebEngineView::loadStarted, this, &WebView::slotLoadStarted);
0065     connect(this, &QWebEngineView::loadProgress, this, &WebView::slotLoadProgress);
0066     connect(this, &QWebEngineView::loadFinished, this, &WebView::slotLoadFinished);
0067     connect(this, &QWebEngineView::iconChanged, this, &WebView::slotIconChanged);
0068     connect(this, &QWebEngineView::urlChanged, this, &WebView::slotUrlChanged);
0069     connect(this, &QWebEngineView::titleChanged, this, &WebView::slotTitleChanged);
0070 
0071     m_currentZoomLevel = zoomLevels().indexOf(100);
0072 
0073     setAcceptDrops(true);
0074     installEventFilter(this);
0075     if (parentWidget()) {
0076         parentWidget()->installEventFilter(this);
0077     }
0078 
0079     WebInspector::registerView(this);
0080 }
0081 
0082 WebView::~WebView()
0083 {
0084     mApp->plugins()->emitWebPageDeleted(m_page);
0085 
0086     WebInspector::unregisterView(this);
0087     WebScrollBarManager::instance()->removeWebView(this);
0088 }
0089 
0090 QIcon WebView::icon(bool allowNull) const
0091 {
0092     if (!QWebEngineView::icon().isNull()) {
0093         return QWebEngineView::icon();
0094     }
0095 
0096     if (url().scheme() == QLatin1String("ftp")) {
0097         return IconProvider::standardIcon(QStyle::SP_ComputerIcon);
0098     }
0099 
0100     if (url().scheme() == QLatin1String("file")) {
0101         return IconProvider::standardIcon(QStyle::SP_DriveHDIcon);
0102     }
0103 
0104     return IconProvider::iconForUrl(url(), allowNull);
0105 }
0106 
0107 QString WebView::title(bool allowEmpty) const
0108 {
0109     QString title = QWebEngineView::title();
0110 
0111     if (allowEmpty) {
0112         return title;
0113     }
0114 
0115     const QUrl u = url().isEmpty() ? m_page->requestedUrl() : url();
0116 
0117     if (title.isEmpty()) {
0118         title = u.host();
0119     }
0120 
0121     if (title.isEmpty()) {
0122         title = u.toString(QUrl::RemoveFragment);
0123     }
0124 
0125     if (title.isEmpty() || title == QL1S("about:blank")) {
0126         return tr("Empty Page");
0127     }
0128 
0129     return title;
0130 }
0131 
0132 WebPage* WebView::page() const
0133 {
0134     return m_page;
0135 }
0136 
0137 void WebView::setPage(WebPage *page)
0138 {
0139     if (m_page == page) {
0140         return;
0141     }
0142 
0143     if (m_page) {
0144         if (m_page->isLoading()) {
0145             Q_EMIT m_page->loadProgress(100);
0146             Q_EMIT m_page->loadFinished(true);
0147         }
0148         mApp->plugins()->emitWebPageDeleted(m_page);
0149     }
0150 
0151     page->setParent(this);
0152     QWebEngineView::setPage(page);
0153     delete m_page;
0154     m_page = page;
0155 
0156     if (m_page->isLoading()) {
0157         Q_EMIT loadStarted();
0158         Q_EMIT loadProgress(m_page->m_loadProgress);
0159     }
0160 
0161     connect(m_page, &WebPage::privacyChanged, this, &WebView::privacyChanged);
0162     connect(m_page, &WebPage::printRequested, this, &WebView::printPage);
0163 
0164     // Set default zoom level
0165     zoomReset();
0166 
0167     // Actions needs to be initialized for every QWebEnginePage change
0168     initializeActions();
0169 
0170     // Scrollbars must be added only after QWebEnginePage is set
0171     WebScrollBarManager::instance()->addWebView(this);
0172 
0173     Q_EMIT pageChanged(m_page);
0174     mApp->plugins()->emitWebPageCreated(m_page);
0175 }
0176 
0177 void WebView::load(const QUrl &url)
0178 {
0179     if (m_page && !m_page->acceptNavigationRequest(url, QWebEnginePage::NavigationTypeTyped, true)) {
0180         return;
0181     }
0182 
0183     QWebEngineView::load(url);
0184 
0185     if (!m_firstLoad) {
0186         m_firstLoad = true;
0187         WebInspector::pushView(this);
0188     }
0189 }
0190 
0191 void WebView::load(const LoadRequest &request)
0192 {
0193     const QUrl reqUrl = request.url();
0194 
0195     if (reqUrl.isEmpty())
0196         return;
0197 
0198     if (reqUrl.scheme() == QL1S("javascript")) {
0199         const QString scriptSource = reqUrl.toString().mid(11);
0200         // Is the javascript source percent encoded or not?
0201         // Looking for % character in source should work in most cases
0202         if (scriptSource.contains(QL1C('%')))
0203             page()->runJavaScript(QUrl::fromPercentEncoding(scriptSource.toUtf8()));
0204         else
0205             page()->runJavaScript(scriptSource);
0206         return;
0207     }
0208 
0209     if (isUrlValid(reqUrl)) {
0210         loadRequest(request);
0211     }
0212 }
0213 
0214 bool WebView::isLoading() const
0215 {
0216     return m_progress < 100;
0217 }
0218 
0219 int WebView::loadingProgress() const
0220 {
0221     return m_progress;
0222 }
0223 
0224 bool WebView::backgroundActivity() const
0225 {
0226     return m_backgroundActivity;
0227 }
0228 
0229 int WebView::zoomLevel() const
0230 {
0231     return m_currentZoomLevel;
0232 }
0233 
0234 void WebView::setZoomLevel(int level)
0235 {
0236     m_currentZoomLevel = level;
0237     applyZoom();
0238 }
0239 
0240 QPointF WebView::mapToViewport(const QPointF &pos) const
0241 {
0242     return page()->mapToViewport(pos);
0243 }
0244 
0245 QRect WebView::scrollBarGeometry(Qt::Orientation orientation) const
0246 {
0247     QScrollBar *s = WebScrollBarManager::instance()->scrollBar(orientation, const_cast<WebView*>(this));
0248     return s && s->isVisible() ? s->geometry() : QRect();
0249 }
0250 
0251 QWidget *WebView::inputWidget() const
0252 {
0253     return m_rwhvqt ? m_rwhvqt : const_cast<WebView*>(this);
0254 }
0255 
0256 // static
0257 bool WebView::isUrlValid(const QUrl &url)
0258 {
0259     // Valid url must have scheme and actually contains something (therefore scheme:// is invalid)
0260     return url.isValid() && !url.scheme().isEmpty() && (!url.host().isEmpty() || !url.path().isEmpty() || url.hasQuery());
0261 }
0262 
0263 // static
0264 QList<int> WebView::zoomLevels()
0265 {
0266     return QList<int>() << 30 << 40 << 50 << 67 << 80 << 90 << 100
0267            << 110 << 120 << 133 << 150 << 170 << 200
0268            << 220 << 233 << 250 << 270 << 285 << 300;
0269 }
0270 
0271 // static
0272 bool WebView::forceContextMenuOnMouseRelease()
0273 {
0274     return s_forceContextMenuOnMouseRelease;
0275 }
0276 
0277 // static
0278 void WebView::setForceContextMenuOnMouseRelease(bool force)
0279 {
0280     // Windows open context menu on mouse release by default
0281 #ifndef Q_OS_WIN
0282     s_forceContextMenuOnMouseRelease = force;
0283 #endif
0284 }
0285 
0286 void WebView::addNotification(QWidget* notif)
0287 {
0288     Q_EMIT showNotification(notif);
0289 }
0290 
0291 void WebView::applyZoom()
0292 {
0293     setZoomFactor(qreal(zoomLevels().at(m_currentZoomLevel)) / 100.0);
0294 
0295     Q_EMIT zoomLevelChanged(m_currentZoomLevel);
0296 }
0297 
0298 void WebView::zoomIn()
0299 {
0300     if (m_currentZoomLevel < zoomLevels().count() - 1) {
0301         m_currentZoomLevel++;
0302         applyZoom();
0303     }
0304 }
0305 
0306 void WebView::zoomOut()
0307 {
0308     if (m_currentZoomLevel > 0) {
0309         m_currentZoomLevel--;
0310         applyZoom();
0311     }
0312 }
0313 
0314 void WebView::zoomReset()
0315 {
0316     if (m_currentZoomLevel != qzSettings->defaultZoomLevel) {
0317         m_currentZoomLevel = qzSettings->defaultZoomLevel;
0318         applyZoom();
0319     }
0320 }
0321 
0322 void WebView::editUndo()
0323 {
0324     triggerPageAction(QWebEnginePage::Undo);
0325 }
0326 
0327 void WebView::editRedo()
0328 {
0329     triggerPageAction(QWebEnginePage::Redo);
0330 }
0331 
0332 void WebView::editCut()
0333 {
0334     triggerPageAction(QWebEnginePage::Cut);
0335 }
0336 
0337 void WebView::editCopy()
0338 {
0339     triggerPageAction(QWebEnginePage::Copy);
0340 }
0341 
0342 void WebView::editPaste()
0343 {
0344     triggerPageAction(QWebEnginePage::Paste);
0345 }
0346 
0347 void WebView::editSelectAll()
0348 {
0349     triggerPageAction(QWebEnginePage::SelectAll);
0350 }
0351 
0352 void WebView::editDelete()
0353 {
0354     QKeyEvent ev(QEvent::KeyPress, Qt::Key_Delete, Qt::NoModifier);
0355     QApplication::sendEvent(this, &ev);
0356 }
0357 
0358 void WebView::reloadBypassCache()
0359 {
0360     triggerPageAction(QWebEnginePage::ReloadAndBypassCache);
0361 }
0362 
0363 void WebView::back()
0364 {
0365     QWebEngineHistory* history = page()->history();
0366 
0367     if (history->canGoBack()) {
0368         history->back();
0369 
0370         Q_EMIT urlChanged(url());
0371     }
0372 }
0373 
0374 void WebView::forward()
0375 {
0376     QWebEngineHistory* history = page()->history();
0377 
0378     if (history->canGoForward()) {
0379         history->forward();
0380 
0381         Q_EMIT urlChanged(url());
0382     }
0383 }
0384 
0385 void WebView::printPage()
0386 {
0387     Q_ASSERT(m_page);
0388 
0389     auto *printer = new QPrinter();
0390     printer->setCreator(tr("Falkon %1 (%2)").arg(QString::fromLatin1(Qz::VERSION), QString::fromLatin1(Qz::WWWADDRESS)));
0391     printer->setDocName(QzTools::filterCharsFromFilename(title()));
0392 
0393     auto *dialog = new QPrintDialog(printer, this);
0394     dialog->setOptions(QAbstractPrintDialog::PrintToFile | QAbstractPrintDialog::PrintShowPageSize);
0395 #ifndef Q_OS_WIN
0396     dialog->setOption(QAbstractPrintDialog::PrintPageRange);
0397     dialog->setOption(QAbstractPrintDialog::PrintCollateCopies);
0398 #endif
0399 
0400     if (dialog->exec() == QDialog::Accepted) {
0401         if (dialog->printer()->outputFormat() == QPrinter::PdfFormat) {
0402             m_page->printToPdf(dialog->printer()->outputFileName(), dialog->printer()->pageLayout());
0403             delete dialog;
0404         } else {
0405             connect(this, &QWebEngineView::printFinished, this, [&dialog](bool success) {
0406                 Q_UNUSED(success);
0407                 delete dialog;
0408             });
0409         }
0410     }
0411 }
0412 
0413 void WebView::slotLoadStarted()
0414 {
0415     m_progress = 0;
0416 
0417     if (title(/*allowEmpty*/true).isEmpty()) {
0418         Q_EMIT titleChanged(title());
0419     }
0420 }
0421 
0422 void WebView::slotLoadProgress(int progress)
0423 {
0424     if (m_progress < 100) {
0425         m_progress = progress;
0426     }
0427 
0428     // QtWebEngine sometimes forgets applied zoom factor
0429     if (!qFuzzyCompare(zoomFactor(), zoomLevels().at(m_currentZoomLevel) / 100.0)) {
0430         applyZoom();
0431     }
0432 }
0433 
0434 void WebView::slotLoadFinished(bool ok)
0435 {
0436     m_progress = 100;
0437 
0438     if (ok)
0439         mApp->history()->addHistoryEntry(this);
0440 }
0441 
0442 void WebView::slotIconChanged()
0443 {
0444     IconProvider::instance()->saveIcon(this);
0445 }
0446 
0447 void WebView::slotUrlChanged(const QUrl &url)
0448 {
0449     if (!url.isEmpty() && title(/*allowEmpty*/true).isEmpty()) {
0450         // Don't treat this as background activity change
0451         const bool oldActivity = m_backgroundActivity;
0452         m_backgroundActivity = true;
0453         Q_EMIT titleChanged(title());
0454         m_backgroundActivity = oldActivity;
0455     }
0456 }
0457 
0458 void WebView::slotTitleChanged(const QString &title)
0459 {
0460     Q_UNUSED(title)
0461 
0462     if (!isVisible() && !isLoading() && !m_backgroundActivity) {
0463         m_backgroundActivity = true;
0464         Q_EMIT backgroundActivityChanged(m_backgroundActivity);
0465     }
0466 }
0467 
0468 void WebView::openUrlInNewWindow()
0469 {
0470     if (auto* action = qobject_cast<QAction*>(sender())) {
0471         mApp->createWindow(Qz::BW_NewWindow, action->data().toUrl());
0472     }
0473 }
0474 
0475 void WebView::sendTextByMail()
0476 {
0477     if (auto* action = qobject_cast<QAction*>(sender())) {
0478         const QUrl mailUrl = QUrl::fromEncoded(
0479             QByteArray("mailto:%20?body=" +
0480                        QUrl::toPercentEncoding(action->data().toString())));
0481         QDesktopServices::openUrl(mailUrl);
0482     }
0483 }
0484 
0485 void WebView::sendPageByMail()
0486 {
0487     const QUrl mailUrl = QUrl::fromEncoded(QByteArray(
0488         "mailto:%20?body=" +
0489         QUrl::toPercentEncoding(QString::fromUtf8(url().toEncoded())) +
0490         "&subject=" + QUrl::toPercentEncoding(title())));
0491     QDesktopServices::openUrl(mailUrl);
0492 }
0493 
0494 void WebView::copyLinkToClipboard()
0495 {
0496     if (auto* action = qobject_cast<QAction*>(sender())) {
0497         QApplication::clipboard()->setText(QString::fromUtf8(action->data().toUrl().toEncoded()));
0498     }
0499 }
0500 
0501 void WebView::savePageAs()
0502 {
0503     page()->runJavaScript(QSL("document.contentType"), WebPage::SafeJsWorld, [this](const QVariant &res) {
0504         const QSet<QString> webPageTypes = {
0505             QSL("text/html"),
0506             QSL("application/xhtml+xml")
0507         };
0508         if (res.isNull() || webPageTypes.contains(res.toString())) {
0509             triggerPageAction(QWebEnginePage::SavePage);
0510         } else {
0511             page()->download(url());
0512         }
0513     });
0514 }
0515 
0516 void WebView::copyImageToClipboard()
0517 {
0518     triggerPageAction(QWebEnginePage::CopyImageToClipboard);
0519 }
0520 
0521 void WebView::downloadLinkToDisk()
0522 {
0523     triggerPageAction(QWebEnginePage::DownloadLinkToDisk);
0524 }
0525 
0526 void WebView::downloadImageToDisk()
0527 {
0528     triggerPageAction(QWebEnginePage::DownloadImageToDisk);
0529 }
0530 
0531 void WebView::downloadMediaToDisk()
0532 {
0533     triggerPageAction(QWebEnginePage::DownloadMediaToDisk);
0534 }
0535 
0536 void WebView::openUrlInNewTab(const QUrl &url, Qz::NewTabPositionFlags position)
0537 {
0538     loadInNewTab(url, position);
0539 }
0540 
0541 void WebView::openActionUrl()
0542 {
0543     if (auto* action = qobject_cast<QAction*>(sender())) {
0544         load(action->data().toUrl());
0545     }
0546 }
0547 
0548 void WebView::showSource()
0549 {
0550     // view-source: doesn't work on itself and custom schemes
0551     if (url().scheme() == QL1S("view-source") || url().scheme() == QL1S("falkon") || url().scheme() == QL1S("qrc")) {
0552         page()->toHtml([](const QString &html) {
0553             std::cout << html.toLocal8Bit().constData() << std::endl;
0554         });
0555         return;
0556     }
0557 
0558     triggerPageAction(QWebEnginePage::ViewSource);
0559 }
0560 
0561 void WebView::showSiteInfo()
0562 {
0563     auto* s = new SiteInfo(this);
0564     s->show();
0565 }
0566 
0567 void WebView::searchSelectedText()
0568 {
0569     SearchEngine engine = mApp->searchEnginesManager()->defaultEngine();
0570     if (auto* act = qobject_cast<QAction*>(sender())) {
0571         if (act->data().isValid()) {
0572             engine = act->data().value<SearchEngine>();
0573         }
0574     }
0575 
0576     const LoadRequest req = mApp->searchEnginesManager()->searchResult(engine, selectedText());
0577     loadInNewTab(req, Qz::NT_SelectedTab);
0578 }
0579 
0580 void WebView::searchSelectedTextInBackgroundTab()
0581 {
0582     SearchEngine engine = mApp->searchEnginesManager()->defaultEngine();
0583     if (auto* act = qobject_cast<QAction*>(sender())) {
0584         if (act->data().isValid()) {
0585             engine = act->data().value<SearchEngine>();
0586         }
0587     }
0588 
0589     const LoadRequest req = mApp->searchEnginesManager()->searchResult(engine, selectedText());
0590     loadInNewTab(req, Qz::NT_NotSelectedTab);
0591 }
0592 
0593 void WebView::bookmarkLink()
0594 {
0595     if (auto* action = qobject_cast<QAction*>(sender())) {
0596         if (action->data().isNull()) {
0597             BookmarksTools::addBookmarkDialog(this, url(), title());
0598         }
0599         else {
0600             const QVariantList bData = action->data().value<QVariantList>();
0601             const QString bookmarkTitle = bData.at(1).toString().isEmpty() ? title() : bData.at(1).toString();
0602 
0603             BookmarksTools::addBookmarkDialog(this, bData.at(0).toUrl(), bookmarkTitle);
0604         }
0605     }
0606 }
0607 
0608 void WebView::openUrlInSelectedTab()
0609 {
0610     if (auto* action = qobject_cast<QAction*>(sender())) {
0611         openUrlInNewTab(action->data().toUrl(), Qz::NT_CleanSelectedTab);
0612     }
0613 }
0614 
0615 void WebView::openUrlInBackgroundTab()
0616 {
0617     if (auto* action = qobject_cast<QAction*>(sender())) {
0618         openUrlInNewTab(action->data().toUrl(), Qz::NT_CleanNotSelectedTab);
0619     }
0620 }
0621 
0622 void WebView::userDefinedOpenUrlInNewTab(const QUrl &url, bool invert)
0623 {
0624     Qz::NewTabPositionFlags position = qzSettings->newTabPosition;
0625     if (invert) {
0626         if (position & Qz::NT_SelectedTab) {
0627             position &= ~Qz::NT_SelectedTab;
0628             position |= Qz::NT_NotSelectedTab;
0629         }
0630         else {
0631             position &= ~Qz::NT_NotSelectedTab;
0632             position |= Qz::NT_SelectedTab;
0633         }
0634     }
0635 
0636     QUrl actionUrl;
0637 
0638     if (!url.isEmpty()) {
0639         actionUrl = url;
0640     }
0641     else if (auto* action = qobject_cast<QAction*>(sender())) {
0642         actionUrl = action->data().toUrl();
0643     }
0644 
0645     openUrlInNewTab(actionUrl, position);
0646 }
0647 
0648 void WebView::userDefinedOpenUrlInBgTab(const QUrl &url)
0649 {
0650     QUrl actionUrl;
0651 
0652     if (!url.isEmpty()) {
0653         actionUrl = url;
0654     }
0655     else if (auto* action = qobject_cast<QAction*>(sender())) {
0656         actionUrl = action->data().toUrl();
0657     }
0658 
0659     userDefinedOpenUrlInNewTab(actionUrl, true);
0660 }
0661 
0662 void WebView::showEvent(QShowEvent *event)
0663 {
0664     QWebEngineView::showEvent(event);
0665 
0666     if (m_backgroundActivity) {
0667         m_backgroundActivity = false;
0668         Q_EMIT backgroundActivityChanged(m_backgroundActivity);
0669     }
0670 }
0671 
0672 void WebView::createContextMenu(QMenu *menu, WebHitTestResult &hitTest)
0673 {
0674     // cppcheck-suppress variableScope
0675     int spellCheckActionCount = 0;
0676 
0677     const QWebEngineContextMenuRequest *contextMenuDataPtr = lastContextMenuRequest();
0678     if (contextMenuDataPtr == NULL) {
0679         return;
0680     }
0681     const QWebEngineContextMenuRequest &contextMenuData = *contextMenuDataPtr;
0682 
0683     hitTest.updateWithContextMenuData(contextMenuData);
0684 
0685     if (!contextMenuData.misspelledWord().isEmpty()) {
0686         QFont boldFont = menu->font();
0687         boldFont.setBold(true);
0688 
0689         for (const QString &suggestion : contextMenuData.spellCheckerSuggestions()) {
0690             QAction *action = menu->addAction(suggestion);
0691             action->setFont(boldFont);
0692 
0693             connect(action, &QAction::triggered, this, [=]() {
0694                 page()->replaceMisspelledWord(suggestion);
0695             });
0696         }
0697 
0698         if (menu->actions().isEmpty()) {
0699             menu->addAction(tr("No suggestions"))->setEnabled(false);
0700         }
0701 
0702         menu->addSeparator();
0703         spellCheckActionCount = menu->actions().count();
0704     }
0705 
0706     if (!hitTest.linkUrl().isEmpty() && hitTest.linkUrl().scheme() != QL1S("javascript")) {
0707         createLinkContextMenu(menu, hitTest);
0708     }
0709 
0710     if (!hitTest.imageUrl().isEmpty()) {
0711         createImageContextMenu(menu, hitTest);
0712     }
0713 
0714     if (!hitTest.mediaUrl().isEmpty()) {
0715         createMediaContextMenu(menu, hitTest);
0716     }
0717 
0718     if (hitTest.isContentEditable()) {
0719         // This only checks if the menu is empty (only spellchecker actions added)
0720         if (menu->actions().count() == spellCheckActionCount) {
0721             menu->addAction(pageAction(QWebEnginePage::Undo));
0722             menu->addAction(pageAction(QWebEnginePage::Redo));
0723             menu->addSeparator();
0724             menu->addAction(pageAction(QWebEnginePage::Cut));
0725             menu->addAction(pageAction(QWebEnginePage::Copy));
0726             menu->addAction(pageAction(QWebEnginePage::Paste));
0727         }
0728 
0729         if (hitTest.tagName() == QL1S("input")) {
0730             QAction *act = menu->addAction(QString());
0731             act->setVisible(false);
0732             checkForForm(act, hitTest.pos());
0733         }
0734     }
0735 
0736     if (!selectedText().isEmpty()) {
0737         createSelectedTextContextMenu(menu, hitTest);
0738     }
0739 
0740     if (menu->isEmpty()) {
0741         createPageContextMenu(menu);
0742     }
0743 
0744     menu->addSeparator();
0745     mApp->plugins()->populateWebViewMenu(menu, this, hitTest);
0746 }
0747 
0748 void WebView::createPageContextMenu(QMenu* menu)
0749 {
0750     QAction* action = menu->addAction(tr("&Back"), this, SLOT(back()));
0751     action->setIcon(IconProvider::standardIcon(QStyle::SP_ArrowBack));
0752     action->setEnabled(history()->canGoBack());
0753 
0754     action = menu->addAction(tr("&Forward"), this, SLOT(forward()));
0755     action->setIcon(IconProvider::standardIcon(QStyle::SP_ArrowForward));
0756     action->setEnabled(history()->canGoForward());
0757 
0758     // Special menu for Speed Dial page
0759     if (url().toString() == QL1S("falkon:speeddial")) {
0760         menu->addSeparator();
0761         menu->addAction(QIcon::fromTheme(QSL("list-add")), tr("&Add New Page"), this, &WebView::addSpeedDial);
0762         menu->addAction(IconProvider::settingsIcon(), tr("&Configure Speed Dial"), this, &WebView::configureSpeedDial);
0763         menu->addSeparator();
0764         menu->addAction(QIcon::fromTheme(QSL("view-refresh")), tr("Reload All Dials"), this, &WebView::reloadAllSpeedDials);
0765         return;
0766     }
0767 
0768     QAction *reloadAction = pageAction(QWebEnginePage::Reload);
0769     action = menu->addAction(reloadAction->icon(), reloadAction->text(), reloadAction, &QAction::trigger);
0770     action->setVisible(reloadAction->isEnabled());
0771     connect(reloadAction, &QAction::changed, action, [=]() {
0772         action->setVisible(reloadAction->isEnabled());
0773     });
0774 
0775     QAction *stopAction = pageAction(QWebEnginePage::Stop);
0776     action = menu->addAction(stopAction->icon(), stopAction->text(), stopAction, &QAction::trigger);
0777     action->setVisible(stopAction->isEnabled());
0778     connect(stopAction, &QAction::changed, action, [=]() {
0779         action->setVisible(stopAction->isEnabled());
0780     });
0781 
0782     menu->addSeparator();
0783     menu->addAction(QIcon::fromTheme(QSL("bookmark-new")), tr("Book&mark page"), this, &WebView::bookmarkLink);
0784     menu->addAction(QIcon::fromTheme(QSL("document-save")), tr("&Save page as..."), this, &WebView::savePageAs);
0785     menu->addAction(QIcon::fromTheme(QSL("edit-copy")), tr("&Copy page link"), this, &WebView::copyLinkToClipboard)->setData(url());
0786     menu->addAction(QIcon::fromTheme(QSL("mail-message-new")), tr("Send page link..."), this, &WebView::sendPageByMail);
0787     menu->addSeparator();
0788     menu->addAction(QIcon::fromTheme(QSL("edit-select-all")), tr("Select &all"), this, &WebView::editSelectAll);
0789     menu->addSeparator();
0790 
0791     const QString scheme = url().scheme();
0792 
0793     if (scheme != QL1S("view-source") && WebPage::internalSchemes().contains(scheme)) {
0794         menu->addAction(QIcon::fromTheme(QSL("text-html")), tr("Show so&urce code"), this, &WebView::showSource);
0795     }
0796 
0797     if (SiteInfo::canShowSiteInfo(url()))
0798         menu->addAction(QIcon::fromTheme(QSL("dialog-information")), tr("Show info ab&out site"), this, &WebView::showSiteInfo);
0799 }
0800 
0801 void WebView::createLinkContextMenu(QMenu* menu, const WebHitTestResult &hitTest)
0802 {
0803     menu->addSeparator();
0804     auto* act = new Action(IconProvider::newTabIcon(), tr("Open link in new &tab"));
0805     act->setData(hitTest.linkUrl());
0806     connect(act, SIGNAL(triggered()), this, SLOT(userDefinedOpenUrlInNewTab()));
0807     connect(act, SIGNAL(ctrlTriggered()), this, SLOT(userDefinedOpenUrlInBgTab()));
0808     menu->addAction(act);
0809     menu->addAction(IconProvider::newWindowIcon(), tr("Open link in new &window"), this, &WebView::openUrlInNewWindow)->setData(hitTest.linkUrl());
0810     menu->addAction(IconProvider::privateBrowsingIcon(), tr("Open link in &private window"), mApp, SLOT(startPrivateBrowsing()))->setData(hitTest.linkUrl());
0811     menu->addSeparator();
0812 
0813     QVariantList bData;
0814     bData << hitTest.linkUrl() << hitTest.linkTitle();
0815     menu->addAction(QIcon::fromTheme(QSL("bookmark-new")), tr("B&ookmark link"), this, &WebView::bookmarkLink)->setData(bData);
0816 
0817     menu->addAction(QIcon::fromTheme(QSL("document-save")), tr("&Save link as..."), this, &WebView::downloadLinkToDisk);
0818     menu->addAction(QIcon::fromTheme(QSL("mail-message-new")), tr("Send link..."), this, &WebView::sendTextByMail)->setData(hitTest.linkUrl().toEncoded());
0819     menu->addAction(QIcon::fromTheme(QSL("edit-copy")), tr("&Copy link address"), this, &WebView::copyLinkToClipboard)->setData(hitTest.linkUrl());
0820     menu->addSeparator();
0821 
0822     if (!selectedText().isEmpty()) {
0823         pageAction(QWebEnginePage::Copy)->setIcon(QIcon::fromTheme(QSL("edit-copy")));
0824         menu->addAction(pageAction(QWebEnginePage::Copy));
0825     }
0826 }
0827 
0828 void WebView::createImageContextMenu(QMenu* menu, const WebHitTestResult &hitTest)
0829 {
0830     menu->addSeparator();
0831     if (hitTest.imageUrl() != url()) {
0832         auto *act = new Action(tr("Show i&mage"));
0833         act->setData(hitTest.imageUrl());
0834         connect(act, &QAction::triggered, this, &WebView::openActionUrl);
0835         connect(act, SIGNAL(ctrlTriggered()), this, SLOT(userDefinedOpenUrlInNewTab()));
0836         menu->addAction(act);
0837     }
0838     menu->addAction(tr("Copy image"), this, &WebView::copyImageToClipboard);
0839     menu->addAction(QIcon::fromTheme(QSL("edit-copy")), tr("Copy image ad&dress"), this, &WebView::copyLinkToClipboard)->setData(hitTest.imageUrl());
0840     menu->addSeparator();
0841     menu->addAction(QIcon::fromTheme(QSL("document-save")), tr("&Save image as..."), this, &WebView::downloadImageToDisk);
0842     menu->addAction(QIcon::fromTheme(QSL("mail-message-new")), tr("Send image..."), this, &WebView::sendTextByMail)->setData(hitTest.imageUrl().toEncoded());
0843     menu->addSeparator();
0844 
0845     if (!selectedText().isEmpty()) {
0846         pageAction(QWebEnginePage::Copy)->setIcon(QIcon::fromTheme(QSL("edit-copy")));
0847         menu->addAction(pageAction(QWebEnginePage::Copy));
0848     }
0849 }
0850 
0851 void WebView::createSelectedTextContextMenu(QMenu* menu, const WebHitTestResult &hitTest)
0852 {
0853     Q_UNUSED(hitTest)
0854 
0855     QString selectedText = page()->selectedText();
0856 
0857     menu->addSeparator();
0858     if (!menu->actions().contains(pageAction(QWebEnginePage::Copy))) {
0859         menu->addAction(pageAction(QWebEnginePage::Copy));
0860     }
0861     menu->addAction(QIcon::fromTheme(QSL("mail-message-new")), tr("Send text..."), this, &WebView::sendTextByMail)->setData(selectedText);
0862     menu->addSeparator();
0863 
0864     // #379: Remove newlines
0865     QString selectedString = selectedText.trimmed().remove(QLatin1Char('\n'));
0866     if (!selectedString.contains(QLatin1Char('.'))) {
0867         // Try to add .com
0868         selectedString.append(QLatin1String(".com"));
0869     }
0870     QUrl guessedUrl = QUrl::fromUserInput(selectedString);
0871 
0872     if (isUrlValid(guessedUrl)) {
0873         auto* act = new Action(QIcon::fromTheme(QSL("document-open-remote")), tr("Go to &web address"));
0874         act->setData(guessedUrl);
0875 
0876         connect(act, &QAction::triggered, this, &WebView::openActionUrl);
0877         connect(act, SIGNAL(ctrlTriggered()), this, SLOT(userDefinedOpenUrlInNewTab()));
0878         menu->addAction(act);
0879     }
0880 
0881     menu->addSeparator();
0882     selectedText.truncate(20);
0883     // KDE is displaying newlines in menu actions ... weird -,-
0884     selectedText.replace(QLatin1Char('\n'), QLatin1Char(' ')).replace(QLatin1Char('\t'), QLatin1Char(' '));
0885 
0886     SearchEngine engine = mApp->searchEnginesManager()->defaultEngine();
0887     auto* act = new Action(engine.icon, tr("Search \"%1 ..\" with %2").arg(selectedText, engine.name));
0888     connect(act, &QAction::triggered, this, &WebView::searchSelectedText);
0889     connect(act, &Action::ctrlTriggered, this, &WebView::searchSelectedTextInBackgroundTab);
0890     menu->addAction(act);
0891 
0892     // Search with ...
0893     Menu* swMenu = new Menu(tr("Search with..."), menu);
0894     swMenu->setCloseOnMiddleClick(true);
0895     SearchEnginesManager* searchManager = mApp->searchEnginesManager();
0896     const auto engines = searchManager->allEngines();
0897     for (const SearchEngine &en : engines) {
0898         auto* act = new Action(en.icon, en.name);
0899         act->setData(QVariant::fromValue(en));
0900 
0901         connect(act, &QAction::triggered, this, &WebView::searchSelectedText);
0902         connect(act, &Action::ctrlTriggered, this, &WebView::searchSelectedTextInBackgroundTab);
0903         swMenu->addAction(act);
0904     }
0905 
0906     menu->addMenu(swMenu);
0907 }
0908 
0909 void WebView::createMediaContextMenu(QMenu *menu, const WebHitTestResult &hitTest)
0910 {
0911     bool paused = hitTest.mediaPaused();
0912     bool muted = hitTest.mediaMuted();
0913 
0914     menu->addSeparator();
0915     menu->addAction(paused ? tr("&Play") : tr("&Pause"), this, &WebView::toggleMediaPause)->setIcon(QIcon::fromTheme(paused ? QSL("media-playback-start") : QSL("media-playback-pause")));
0916     menu->addAction(muted ? tr("Un&mute") : tr("&Mute"), this, &WebView::toggleMediaMute)->setIcon(QIcon::fromTheme(muted ? QSL("audio-volume-muted") : QSL("audio-volume-high")));
0917     menu->addSeparator();
0918     menu->addAction(QIcon::fromTheme(QSL("edit-copy")), tr("&Copy Media Address"), this, &WebView::copyLinkToClipboard)->setData(hitTest.mediaUrl());
0919     menu->addAction(QIcon::fromTheme(QSL("mail-message-new")), tr("&Send Media Address"), this, &WebView::sendTextByMail)->setData(hitTest.mediaUrl().toEncoded());
0920     menu->addAction(QIcon::fromTheme(QSL("document-save")), tr("Save Media To &Disk"), this, &WebView::downloadMediaToDisk);
0921 }
0922 
0923 void WebView::checkForForm(QAction *action, const QPoint &pos)
0924 {
0925     m_clickedPos = mapToViewport(pos);
0926     QPointer<QAction> act = action;
0927 
0928     page()->runJavaScript(Scripts::getFormData(m_clickedPos), WebPage::SafeJsWorld, [this, act](const QVariant &res) {
0929         const QVariantMap &map = res.toMap();
0930         if (!act || map.isEmpty())
0931             return;
0932 
0933         const QUrl url = map.value(QSL("action")).toUrl();
0934         const QString method = map.value(QSL("method")).toString();
0935 
0936         if (!url.isEmpty() && (method == QL1S("get") || method == QL1S("post"))) {
0937             act->setVisible(true);
0938             act->setIcon(QIcon::fromTheme(QSL("edit-find"), QIcon(QSL(":icons/menu/search-icon.svg"))));
0939             act->setText(tr("Create Search Engine"));
0940             connect(act.data(), &QAction::triggered, this, &WebView::createSearchEngine);
0941         }
0942     });
0943 }
0944 
0945 void WebView::createSearchEngine()
0946 {
0947     page()->runJavaScript(Scripts::getFormData(m_clickedPos), WebPage::SafeJsWorld, [this](const QVariant &res) {
0948         mApp->searchEnginesManager()->addEngineFromForm(res.toMap(), this);
0949     });
0950 }
0951 
0952 void WebView::addSpeedDial()
0953 {
0954     page()->runJavaScript(QSL("addSpeedDial()"), WebPage::SafeJsWorld);
0955 }
0956 
0957 void WebView::configureSpeedDial()
0958 {
0959     page()->runJavaScript(QSL("configureSpeedDial()"), WebPage::SafeJsWorld);
0960 }
0961 
0962 void WebView::reloadAllSpeedDials()
0963 {
0964     page()->runJavaScript(QSL("reloadAll()"), WebPage::SafeJsWorld);
0965 }
0966 
0967 void WebView::toggleMediaPause()
0968 {
0969     triggerPageAction(QWebEnginePage::ToggleMediaPlayPause);
0970 }
0971 
0972 void WebView::toggleMediaMute()
0973 {
0974     triggerPageAction(QWebEnginePage::ToggleMediaMute);
0975 }
0976 
0977 void WebView::initializeActions()
0978 {
0979     QAction* undoAction = pageAction(QWebEnginePage::Undo);
0980     undoAction->setText(tr("&Undo"));
0981     undoAction->setShortcut(QKeySequence(QSL("Ctrl+Z")));
0982     undoAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
0983     undoAction->setIcon(QIcon::fromTheme(QSL("edit-undo")));
0984 
0985     QAction* redoAction = pageAction(QWebEnginePage::Redo);
0986     redoAction->setText(tr("&Redo"));
0987     redoAction->setShortcut(QKeySequence(QSL("Ctrl+Shift+Z")));
0988     redoAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
0989     redoAction->setIcon(QIcon::fromTheme(QSL("edit-redo")));
0990 
0991     QAction* cutAction = pageAction(QWebEnginePage::Cut);
0992     cutAction->setText(tr("&Cut"));
0993     cutAction->setShortcut(QKeySequence(QSL("Ctrl+X")));
0994     cutAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
0995     cutAction->setIcon(QIcon::fromTheme(QSL("edit-cut")));
0996 
0997     QAction* copyAction = pageAction(QWebEnginePage::Copy);
0998     copyAction->setText(tr("&Copy"));
0999     copyAction->setShortcut(QKeySequence(QSL("Ctrl+C")));
1000     copyAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
1001     copyAction->setIcon(QIcon::fromTheme(QSL("edit-copy")));
1002 
1003     QAction* pasteAction = pageAction(QWebEnginePage::Paste);
1004     pasteAction->setText(tr("&Paste"));
1005     pasteAction->setShortcut(QKeySequence(QSL("Ctrl+V")));
1006     pasteAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
1007     pasteAction->setIcon(QIcon::fromTheme(QSL("edit-paste")));
1008 
1009     QAction* selectAllAction = pageAction(QWebEnginePage::SelectAll);
1010     selectAllAction->setText(tr("Select All"));
1011     selectAllAction->setShortcut(QKeySequence(QSL("Ctrl+A")));
1012     selectAllAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
1013     selectAllAction->setIcon(QIcon::fromTheme(QSL("edit-select-all")));
1014 
1015     QAction* reloadAction = pageAction(QWebEnginePage::Reload);
1016     reloadAction->setText(tr("&Reload"));
1017     reloadAction->setIcon(QIcon::fromTheme(QSL("view-refresh")));
1018 
1019     QAction* stopAction = pageAction(QWebEnginePage::Stop);
1020     stopAction->setText(tr("S&top"));
1021     stopAction->setIcon(QIcon::fromTheme(QSL("process-stop")));
1022 
1023     // Make action shortcuts available for webview
1024     addAction(undoAction);
1025     addAction(redoAction);
1026     addAction(cutAction);
1027     addAction(copyAction);
1028     addAction(pasteAction);
1029     addAction(selectAllAction);
1030 }
1031 
1032 void WebView::_wheelEvent(QWheelEvent *event)
1033 {
1034     if (mApp->plugins()->processWheelEvent(Qz::ON_WebView, this, event)) {
1035         event->accept();
1036         return;
1037     }
1038 
1039     if (event->modifiers() & Qt::ControlModifier) {
1040         m_wheelHelper.processEvent(event);
1041         while (WheelHelper::Direction direction = m_wheelHelper.takeDirection()) {
1042             switch (direction) {
1043             case WheelHelper::WheelUp:
1044             case WheelHelper::WheelLeft:
1045                 zoomIn();
1046                 break;
1047 
1048             case WheelHelper::WheelDown:
1049             case WheelHelper::WheelRight:
1050                 zoomOut();
1051                 break;
1052 
1053             default:
1054                 break;
1055             }
1056         }
1057         event->accept();
1058         return;
1059     }
1060 
1061     m_wheelHelper.reset();
1062 
1063     // QtWebEngine ignores QApplication::wheelScrollLines() and instead always scrolls 3 lines
1064     if (event->spontaneous()) {
1065         const qreal multiplier = QApplication::wheelScrollLines() / 3.0;
1066         if (multiplier != 1.0) {
1067             QWheelEvent e(event->position(), event->globalPosition(), event->pixelDelta(),
1068                           event->angleDelta() * multiplier, event->buttons(),
1069                           event->modifiers(), event->phase(), event->inverted(), event->source());
1070             QApplication::sendEvent(m_rwhvqt, &e);
1071             event->accept();
1072         }
1073     }
1074 }
1075 
1076 void WebView::_mousePressEvent(QMouseEvent *event)
1077 {
1078     m_clickedUrl = QUrl();
1079     m_clickedPos = QPointF();
1080 
1081     if (mApp->plugins()->processMousePress(Qz::ON_WebView, this, event)) {
1082         event->accept();
1083         return;
1084     }
1085 
1086     switch (event->button()) {
1087     case Qt::BackButton:
1088         back();
1089         event->accept();
1090         break;
1091 
1092     case Qt::ForwardButton:
1093         forward();
1094         event->accept();
1095         break;
1096 
1097     case Qt::MiddleButton:
1098         m_clickedUrl = page()->hitTestContent(event->position().toPoint()).linkUrl();
1099         if (!m_clickedUrl.isEmpty())
1100             event->accept();
1101         break;
1102 
1103     case Qt::LeftButton:
1104         m_clickedUrl = page()->hitTestContent(event->position().toPoint()).linkUrl();
1105         break;
1106 
1107     default:
1108         break;
1109     }
1110 }
1111 
1112 void WebView::_mouseReleaseEvent(QMouseEvent *event)
1113 {
1114     if (mApp->plugins()->processMouseRelease(Qz::ON_WebView, this, event)) {
1115         event->accept();
1116         return;
1117     }
1118 
1119     switch (event->button()) {
1120     case Qt::BackButton:
1121     case Qt::ForwardButton:
1122         event->accept();
1123         break;
1124 
1125     case Qt::MiddleButton:
1126         if (!m_clickedUrl.isEmpty()) {
1127             const QUrl link = page()->hitTestContent(event->position().toPoint()).linkUrl();
1128             if (m_clickedUrl == link && isUrlValid(link)) {
1129                 userDefinedOpenUrlInNewTab(link, event->modifiers() & Qt::ShiftModifier);
1130                 event->accept();
1131             }
1132         }
1133         break;
1134 
1135     case Qt::LeftButton:
1136         if (!m_clickedUrl.isEmpty()) {
1137             const QUrl link = page()->hitTestContent(event->position().toPoint()).linkUrl();
1138             if (m_clickedUrl == link && isUrlValid(link)) {
1139                 if (event->modifiers() & Qt::ControlModifier) {
1140                     userDefinedOpenUrlInNewTab(link, event->modifiers() & Qt::ShiftModifier);
1141                     event->accept();
1142                 }
1143             }
1144         }
1145         break;
1146 
1147     case Qt::RightButton:
1148         if (s_forceContextMenuOnMouseRelease) {
1149             QContextMenuEvent ev(QContextMenuEvent::Mouse, event->position().toPoint(), event->globalPosition().toPoint(), event->modifiers());
1150             _contextMenuEvent(&ev);
1151             event->accept();
1152         }
1153         break;
1154 
1155     default:
1156         break;
1157     }
1158 }
1159 
1160 void WebView::_mouseMoveEvent(QMouseEvent *event)
1161 {
1162     if (mApp->plugins()->processMouseMove(Qz::ON_WebView, this, event)) {
1163         event->accept();
1164     }
1165 }
1166 
1167 void WebView::_keyPressEvent(QKeyEvent *event)
1168 {
1169     if (mApp->plugins()->processKeyPress(Qz::ON_WebView, this, event)) {
1170         event->accept();
1171         return;
1172     }
1173 
1174     switch (event->key()) {
1175     case Qt::Key_ZoomIn:
1176         zoomIn();
1177         event->accept();
1178         break;
1179 
1180     case Qt::Key_ZoomOut:
1181         zoomOut();
1182         event->accept();
1183         break;
1184 
1185     case Qt::Key_Plus:
1186         if (event->modifiers() & Qt::ControlModifier) {
1187             zoomIn();
1188             event->accept();
1189         }
1190         break;
1191 
1192     case Qt::Key_Minus:
1193         if (event->modifiers() & Qt::ControlModifier) {
1194             zoomOut();
1195             event->accept();
1196         }
1197         break;
1198 
1199     case Qt::Key_0:
1200         if (event->modifiers() & Qt::ControlModifier) {
1201             zoomReset();
1202             event->accept();
1203         }
1204         break;
1205 
1206     case Qt::Key_M:
1207         if (event->modifiers() & Qt::ControlModifier) {
1208             page()->setAudioMuted(!page()->isAudioMuted());
1209             event->accept();
1210         }
1211         break;
1212 
1213     default:
1214         break;
1215     }
1216 }
1217 
1218 void WebView::_keyReleaseEvent(QKeyEvent *event)
1219 {
1220     if (mApp->plugins()->processKeyRelease(Qz::ON_WebView, this, event)) {
1221         event->accept();
1222     }
1223 
1224     switch (event->key()) {
1225     case Qt::Key_Escape:
1226         if (isFullScreen()) {
1227             triggerPageAction(QWebEnginePage::ExitFullScreen);
1228             event->accept();
1229         }
1230         break;
1231 
1232     default:
1233         break;
1234     }
1235 }
1236 
1237 void WebView::_contextMenuEvent(QContextMenuEvent *event)
1238 {
1239     Q_UNUSED(event)
1240 }
1241 
1242 void WebView::resizeEvent(QResizeEvent *event)
1243 {
1244     QWebEngineView::resizeEvent(event);
1245     Q_EMIT viewportResized(size());
1246 }
1247 
1248 void WebView::contextMenuEvent(QContextMenuEvent *event)
1249 {
1250     // Context menu is created in mouseReleaseEvent
1251     if (s_forceContextMenuOnMouseRelease)
1252         return;
1253 
1254     const QPoint pos = event->pos();
1255     const QPoint globalPos = event->globalPos();
1256     const QContextMenuEvent::Reason reason = event->reason();
1257 
1258     QTimer::singleShot(0, this, [this, pos, globalPos, reason]() {
1259         QContextMenuEvent ev(reason, pos, globalPos);
1260         _contextMenuEvent(&ev);
1261     });
1262 }
1263 
1264 bool WebView::focusNextPrevChild(bool next)
1265 {
1266     return QWebEngineView::focusNextPrevChild(next);
1267 }
1268 
1269 void WebView::loadRequest(const LoadRequest &req)
1270 {
1271     QWebEngineView::load(req.webRequest());
1272 }
1273 
1274 bool WebView::eventFilter(QObject *obj, QEvent *event)
1275 {
1276     // Keyboard events are sent to parent widget
1277     if (obj == this && event->type() == QEvent::ParentChange && parentWidget()) {
1278         parentWidget()->installEventFilter(this);
1279     }
1280 
1281     // Hack to find widget that receives input events
1282     if (obj == this && event->type() == QEvent::ChildAdded) {
1283         QPointer<QWidget> child = qobject_cast<QWidget*>(static_cast<QChildEvent*>(event)->child());
1284         QTimer::singleShot(0, this, [=]() {
1285             if (child) {
1286                 m_rwhvqt = child;
1287                 m_rwhvqt->installEventFilter(this);
1288                 if (auto *w = qobject_cast<QQuickWidget*>(m_rwhvqt)) {
1289                     w->setClearColor(palette().color(QPalette::Window));
1290                 }
1291             }
1292         });
1293     }
1294 
1295     // Forward events to WebView
1296 #define HANDLE_EVENT(f, t) \
1297     { \
1298     bool wasAccepted = event->isAccepted(); \
1299     event->setAccepted(false); \
1300     f(static_cast<t*>(event)); \
1301     bool ret = event->isAccepted(); \
1302     event->setAccepted(wasAccepted); \
1303     return ret; \
1304     }
1305 
1306     if (obj == m_rwhvqt) {
1307         switch (event->type()) {
1308         case QEvent::MouseButtonPress:
1309             HANDLE_EVENT(_mousePressEvent, QMouseEvent);
1310 
1311         case QEvent::MouseButtonRelease:
1312             HANDLE_EVENT(_mouseReleaseEvent, QMouseEvent);
1313 
1314         case QEvent::MouseMove:
1315             HANDLE_EVENT(_mouseMoveEvent, QMouseEvent);
1316 
1317         case QEvent::Wheel:
1318             HANDLE_EVENT(_wheelEvent, QWheelEvent);
1319 
1320         default:
1321             break;
1322         }
1323     }
1324 
1325     if (obj == parentWidget()) {
1326         switch (event->type()) {
1327         case QEvent::KeyPress:
1328             HANDLE_EVENT(_keyPressEvent, QKeyEvent);
1329 
1330         case QEvent::KeyRelease:
1331             HANDLE_EVENT(_keyReleaseEvent, QKeyEvent);
1332 
1333         default:
1334             break;
1335         }
1336     }
1337 #undef HANDLE_EVENT
1338 
1339     // Block already handled events
1340     if (obj == this) {
1341         switch (event->type()) {
1342         case QEvent::KeyPress:
1343         case QEvent::KeyRelease:
1344         case QEvent::MouseButtonPress:
1345         case QEvent::MouseButtonRelease:
1346         case QEvent::MouseMove:
1347         case QEvent::Wheel:
1348             return true;
1349 
1350         case QEvent::Hide:
1351             if (isFullScreen()) {
1352                 triggerPageAction(QWebEnginePage::ExitFullScreen);
1353             }
1354             break;
1355 
1356         default:
1357             break;
1358         }
1359     }
1360 
1361     const bool res = QWebEngineView::eventFilter(obj, event);
1362 
1363     if (obj == m_rwhvqt) {
1364         switch (event->type()) {
1365         case QEvent::FocusIn:
1366         case QEvent::FocusOut:
1367             Q_EMIT focusChanged(hasFocus());
1368             break;
1369 
1370         default:
1371             break;
1372         }
1373     }
1374 
1375     return res;
1376 }