File indexing completed on 2024-04-28 16:01:21

0001 /*
0002     SPDX-FileCopyrightText: 2007 Trolltech ASA
0003     SPDX-FileCopyrightText: 2008-2010 Urs Wolfer <uwolfer@kde.org>
0004     SPDX-FileCopyrightText: 2008 Laurent Montel <montel@kde.org>
0005     SPDX-FileCopyrightText: 2009 Dawit Alemayehu <adawit@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.1-or-later
0008 */
0009 
0010 #include "webview.h"
0011 #include "webpage.h"
0012 #include "kwebkitpart_debug.h"
0013 #include "kwebkitpart.h"
0014 #include "settings/webkitsettings.h"
0015 
0016 #include <KIO/Global>
0017 #include <KAboutData>
0018 #include <KActionCollection>
0019 #include <KConfigGroup>
0020 #include <KUriFilter>
0021 
0022 #include <KActionMenu>
0023 #include <KIO/AccessManager>
0024 #include <KStringHandler>
0025 #include <KLocalizedString>
0026 
0027 #include <QDropEvent>
0028 #include <QLabel>
0029 #include <QWebElement>
0030 #include <QWebInspector>
0031 #include <QToolTip>
0032 #include <QCoreApplication>
0033 #include <QMimeType>
0034 #include <QMimeDatabase>
0035 
0036 #include <unistd.h>
0037 
0038 #define QL1S(x)   QLatin1String(x)
0039 #define QL1C(x)   QLatin1Char(x)
0040 
0041 #define ALTERNATE_DEFAULT_WEB_SHORTCUT    QL1S("google")
0042 #define ALTERNATE_WEB_SHORTCUTS           QStringList() << QL1S("google") << QL1S("wikipedia") << QL1S("webster") << QL1S("dmoz")
0043 
0044 WebView::WebView(KWebKitPart* part, QWidget* parent)
0045         :KWebView(parent, false),
0046          m_actionCollection(new KActionCollection(this)),
0047          m_part(part),
0048          m_webInspector(nullptr),
0049          m_autoScrollTimerId(-1),
0050          m_verticalAutoScrollSpeed(0),
0051          m_horizontalAutoScrollSpeed(0),
0052          m_accessKeyActivated(NotActivated)
0053 {
0054     setAcceptDrops(true);
0055 
0056     // Create the custom page...
0057     setPage(new WebPage(part, this));
0058 
0059     connect(this, SIGNAL(loadStarted()), this, SLOT(slotStopAutoScroll()));
0060     connect(this, SIGNAL(loadStarted()), this, SLOT(hideAccessKeys()));
0061     connect(page(), SIGNAL(scrollRequested(int,int,QRect)), this, SLOT(hideAccessKeys()));
0062     
0063     if (WebKitSettings::self()->zoomToDPI())
0064         setZoomFactor(logicalDpiY() / 96.0f);
0065 }
0066 
0067 WebView::~WebView()
0068 {
0069     //qCDebug(KWEBKITPART_LOG);
0070 }
0071 
0072 void WebView::loadUrl(const QUrl& url, const KParts::OpenUrlArguments& args, const KParts::BrowserArguments& bargs)
0073 {
0074     page()->setProperty("NavigationTypeUrlEntered", true);
0075 
0076     if (args.reload() && url == this->url()) {
0077       reload();
0078       return;
0079     }
0080 
0081     QNetworkRequest request(url);
0082     if (args.reload()) {
0083         request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
0084     }
0085 
0086     if (bargs.postData.isEmpty()) {
0087         KWebView::load(request);
0088     } else {
0089         KWebView::load(request, QNetworkAccessManager::PostOperation, bargs.postData);
0090     }
0091 }
0092 
0093 QWebHitTestResult WebView::contextMenuResult() const
0094 {
0095     return m_result;
0096 }
0097 
0098 static bool isMultimediaElement(const QWebElement& element)
0099 {
0100     if (element.tagName().compare(QL1S("video"), Qt::CaseInsensitive) == 0)
0101         return true;
0102 
0103     if (element.tagName().compare(QL1S("audio"), Qt::CaseInsensitive) == 0)
0104         return true;
0105 
0106     return false;
0107 }
0108 
0109 static void extractMimeTypeFor(const QUrl& url, QString& mimeType)
0110 {
0111     if (url.fileName().isEmpty() || url.hasFragment() || url.hasQuery())
0112         return;
0113 
0114     QMimeDatabase db;
0115     QMimeType pmt = db.mimeTypeForUrl(url);
0116 
0117     // Further check for mime types guessed from the extension which,
0118     // on a web page, are more likely to be a script delivering content
0119     // of undecidable type. If the mime type from the extension is one
0120     // of these, don't use it.  Retain the original type 'text/html'.
0121     if (pmt.isDefault() ||
0122         pmt.inherits(QL1S("application/x-perl")) ||
0123         pmt.inherits(QL1S("application/x-perl-module")) ||
0124         pmt.inherits(QL1S("application/x-php")) ||
0125         pmt.inherits(QL1S("application/x-python-bytecode")) ||
0126         pmt.inherits(QL1S("application/x-python")) ||
0127         pmt.inherits(QL1S("application/x-shellscript")))
0128         return;
0129 
0130     mimeType = pmt.name();
0131 }
0132 
0133 void WebView::contextMenuEvent(QContextMenuEvent* e)
0134 {
0135     m_result = page()->mainFrame()->hitTestContent(e->pos());
0136 
0137     // Clear the previous collection entries first...
0138     m_actionCollection->clear();
0139 
0140     KParts::BrowserExtension::PopupFlags flags = KParts::BrowserExtension::DefaultPopupItems;
0141     KParts::BrowserExtension::ActionGroupMap mapAction;
0142     QString mimeType (QL1S("text/html"));
0143     bool forcesNewWindow = false;
0144 
0145     QUrl emitUrl;
0146     if (m_result.isContentEditable()) {
0147         if (m_result.element().hasAttribute(QL1S("disabled"))) {
0148             e->accept();
0149             return;
0150         }
0151         flags |= KParts::BrowserExtension::ShowTextSelectionItems;
0152         editableContentActionPopupMenu(mapAction);
0153     } else if (isMultimediaElement(m_result.element())) {
0154         multimediaActionPopupMenu(mapAction);
0155     } else if (!m_result.linkUrl().isValid()) {
0156         if (m_result.imageUrl().isValid()) {
0157             emitUrl = m_result.imageUrl();
0158             extractMimeTypeFor(emitUrl, mimeType);
0159         } else {
0160             flags |= KParts::BrowserExtension::ShowBookmark;
0161             flags |= KParts::BrowserExtension::ShowReload;
0162             emitUrl = m_part->url();
0163 
0164             if (m_result.isContentSelected()) {
0165                 flags |= KParts::BrowserExtension::ShowTextSelectionItems;
0166                 selectActionPopupMenu(mapAction);
0167             } else {
0168                 flags |= KParts::BrowserExtension::ShowNavigationItems;
0169             }
0170         }
0171         partActionPopupMenu(mapAction);
0172     } else {
0173         flags |= KParts::BrowserExtension::ShowBookmark;
0174         flags |= KParts::BrowserExtension::ShowReload;
0175         flags |= KParts::BrowserExtension::IsLink;
0176         emitUrl = m_result.linkUrl();
0177         linkActionPopupMenu(mapAction);
0178         if (emitUrl.isLocalFile()) {
0179             QMimeDatabase db;
0180             mimeType = db.mimeTypeForUrl(emitUrl).name();
0181         }
0182         else {
0183             extractMimeTypeFor(emitUrl, mimeType);
0184         }
0185         partActionPopupMenu(mapAction);
0186 
0187         // Show the OpenInThisWindow context menu item
0188         forcesNewWindow = (page()->currentFrame() != m_result.linkTargetFrame());
0189     }
0190 
0191     if (!mapAction.isEmpty()) {
0192         KParts::OpenUrlArguments args;
0193         KParts::BrowserArguments bargs;
0194         args.setMimeType(mimeType);
0195         bargs.setForcesNewWindow(forcesNewWindow);
0196         e->accept();
0197         emit m_part->browserExtension()->popupMenu(e->globalPos(), emitUrl, static_cast<mode_t>(-1), args, bargs, flags, mapAction);
0198         return;
0199     }
0200 
0201     KWebView::contextMenuEvent(e);
0202 }
0203 
0204 static bool isEditableElement(QWebPage* page)
0205 {
0206     const QWebFrame* frame = (page ? page->currentFrame() : nullptr);
0207     QWebElement element = (frame ? frame->findFirstElement(QL1S(":focus")) : QWebElement());
0208     if (!element.isNull()) {
0209         const QString tagName(element.tagName());
0210         if (tagName.compare(QL1S("textarea"), Qt::CaseInsensitive) == 0) {
0211             return true;
0212         }
0213         const QString type(element.attribute(QL1S("type")).toLower());
0214         if (tagName.compare(QL1S("input"), Qt::CaseInsensitive) == 0
0215             && (type.isEmpty() || type == QL1S("text") || type == QL1S("password"))) {
0216             return true;
0217         }
0218         if (element.evaluateJavaScript("this.isContentEditable").toBool()) {
0219             return true;
0220         }
0221     }
0222     return false;
0223 }
0224 
0225 void WebView::keyPressEvent(QKeyEvent* e)
0226 {
0227     if (e && hasFocus()) {
0228         const int key = e->key();
0229         if (WebKitSettings::self()->accessKeysEnabled()) {
0230             if (m_accessKeyActivated == Activated) {
0231                 if (checkForAccessKey(e)) {
0232                     hideAccessKeys();
0233                     e->accept();
0234                     return;
0235                 }
0236                 hideAccessKeys();
0237             } else if (e->key() == Qt::Key_Control && e->modifiers() == Qt::ControlModifier && !isEditableElement(page())) {
0238                 m_accessKeyActivated = PreActivated; // Only preactive here, it will be actually activated in key release.
0239             }
0240         }
0241 
0242         if (e->modifiers() & Qt::ShiftModifier) {
0243             switch (key) {
0244             case Qt::Key_Up:
0245                 if (!isEditableElement(page())) {
0246                     m_verticalAutoScrollSpeed--;
0247                     if (m_autoScrollTimerId == -1)
0248                         m_autoScrollTimerId = startTimer(100);
0249                     e->accept();
0250                     return;
0251                 }
0252                 break;
0253             case Qt::Key_Down:
0254                 if (!isEditableElement(page())) {
0255                     m_verticalAutoScrollSpeed++;
0256                     if (m_autoScrollTimerId == -1)
0257                         m_autoScrollTimerId = startTimer(100);
0258                     e->accept();
0259                     return;
0260                 }
0261                 break;
0262             case Qt::Key_Left:
0263                 if (!isEditableElement(page())) {
0264                     m_horizontalAutoScrollSpeed--;
0265                     if (m_autoScrollTimerId == -1)
0266                         m_autoScrollTimerId = startTimer(100);
0267                     e->accept();
0268                     return;
0269                 }
0270                 break;
0271             case Qt::Key_Right:
0272                 if (!isEditableElement(page())) {
0273                     m_horizontalAutoScrollSpeed--;
0274                     if (m_autoScrollTimerId == -1)
0275                         m_autoScrollTimerId = startTimer(100);
0276                     e->accept();
0277                     return;
0278                 }
0279                 break;
0280             default:
0281                 break;
0282             }
0283         } else if (m_autoScrollTimerId != -1) {
0284             // qCDebug(KWEBKITPART_LOG) << "scroll timer id:" << m_autoScrollTimerId;
0285             slotStopAutoScroll();
0286             e->accept();
0287             return;
0288         }
0289     }
0290     KWebView::keyPressEvent(e);
0291 }
0292 
0293 void WebView::keyReleaseEvent(QKeyEvent *e)
0294 {
0295     if (WebKitSettings::self()->accessKeysEnabled() && m_accessKeyActivated == PreActivated) {
0296         // Activate only when the CTRL key is pressed and released by itself.
0297         if (e->key() == Qt::Key_Control && e->modifiers() == Qt::NoModifier) {
0298             showAccessKeys();
0299             emit statusBarMessage(i18n("Access keys activated"));
0300             m_accessKeyActivated = Activated;
0301         } else {
0302             m_accessKeyActivated = NotActivated;
0303         }
0304     }
0305     KWebView::keyReleaseEvent(e);
0306 }
0307 
0308 void WebView::mouseReleaseEvent(QMouseEvent* e)
0309 {
0310     if (WebKitSettings::self()->accessKeysEnabled() && m_accessKeyActivated == PreActivated
0311         && e->button() != Qt::NoButton && (e->modifiers() & Qt::ControlModifier)) {
0312         m_accessKeyActivated = NotActivated;
0313     }
0314     KWebView::mouseReleaseEvent(e);
0315 }
0316 
0317 void WebView::wheelEvent (QWheelEvent* e)
0318 {
0319     if (WebKitSettings::self()->accessKeysEnabled() &&
0320         m_accessKeyActivated == PreActivated &&
0321         (e->modifiers() & Qt::ControlModifier)) {
0322         m_accessKeyActivated = NotActivated;
0323     }
0324     KWebView::wheelEvent(e);
0325 }
0326 
0327 
0328 void WebView::timerEvent(QTimerEvent* e)
0329 {
0330     if (e && e->timerId() == m_autoScrollTimerId) {
0331         // do the scrolling
0332         page()->currentFrame()->scroll(m_horizontalAutoScrollSpeed, m_verticalAutoScrollSpeed);
0333 
0334         // check if we reached the end
0335         const int y = page()->currentFrame()->scrollPosition().y();
0336         if (y == page()->currentFrame()->scrollBarMinimum(Qt::Vertical) ||
0337             y == page()->currentFrame()->scrollBarMaximum(Qt::Vertical)) {
0338             m_verticalAutoScrollSpeed = 0;
0339         }
0340 
0341         const int x = page()->currentFrame()->scrollPosition().x();
0342         if (x == page()->currentFrame()->scrollBarMinimum(Qt::Horizontal) ||
0343             x == page()->currentFrame()->scrollBarMaximum(Qt::Horizontal)) {
0344             m_horizontalAutoScrollSpeed = 0;
0345         }
0346 
0347         // Kill the timer once the max/min scroll limit is reached.
0348         if (m_horizontalAutoScrollSpeed == 0  && m_verticalAutoScrollSpeed == 0) {
0349             killTimer(m_autoScrollTimerId);
0350             m_autoScrollTimerId = -1;
0351         }
0352 
0353         e->accept();
0354         return;
0355     }
0356     KWebView::timerEvent(e);
0357 }
0358 
0359 static bool showSpellCheckAction(const QWebElement& element)
0360 {
0361     if (element.hasAttribute(QL1S("readonly")))
0362         return false;
0363 
0364     if (element.attribute(QL1S("spellcheck"), QL1S("true")).compare(QL1S("false"), Qt::CaseInsensitive) == 0)
0365         return false;
0366 
0367     if (element.hasAttribute(QL1S("type"))
0368         && element.attribute(QL1S("type")).compare(QL1S("text"), Qt::CaseInsensitive) != 0)
0369         return false;
0370 
0371     return true;
0372 }
0373 
0374 void WebView::editableContentActionPopupMenu(KParts::BrowserExtension::ActionGroupMap& partGroupMap)
0375 {
0376     QList<QAction*> editableContentActions;
0377 
0378     KActionMenu* menu = new KActionMenu(i18nc("Text direction", "Direction"), this);
0379     QActionGroup* group = new QActionGroup(this);
0380     group->setExclusive(true);
0381 
0382     QAction *action = m_actionCollection->addAction(QL1S("text-direction-default"),  m_part->browserExtension(), SLOT(slotTextDirectionChanged()));
0383     action->setText(i18n("Default"));
0384     action->setCheckable(true);
0385     action->setData(QWebPage::SetTextDirectionDefault);
0386     action->setEnabled(pageAction(QWebPage::SetTextDirectionDefault)->isEnabled());
0387     action->setCheckable(pageAction(QWebPage::SetTextDirectionDefault)->isChecked());
0388     action->setActionGroup(group);
0389     menu->addAction(action);
0390 
0391     action = m_actionCollection->addAction(QL1S("text-direction-left-to-right"),  m_part->browserExtension(), SLOT(slotTextDirectionChanged()));
0392     action->setText(i18n("Left to right"));
0393     action->setCheckable(true);
0394     action->setData(QWebPage::SetTextDirectionLeftToRight);
0395     action->setEnabled(pageAction(QWebPage::SetTextDirectionLeftToRight)->isEnabled());
0396     action->setChecked(pageAction(QWebPage::SetTextDirectionLeftToRight)->isChecked());
0397     action->setActionGroup(group);
0398     menu->addAction(action);
0399 
0400     action = m_actionCollection->addAction(QL1S("text-direction-right-to-left"),  m_part->browserExtension(), SLOT(slotTextDirectionChanged()));
0401     action->setText(i18n("Right to left"));
0402     action->setCheckable(true);
0403     action->setData(QWebPage::SetTextDirectionRightToLeft);
0404     action->setEnabled(pageAction(QWebPage::SetTextDirectionRightToLeft)->isEnabled());
0405     action->setChecked(pageAction(QWebPage::SetTextDirectionRightToLeft)->isChecked());
0406     action->setActionGroup(group);
0407     menu->addAction(action);
0408 
0409     editableContentActions.append(menu);
0410 
0411     action = new QAction(m_actionCollection);
0412     action->setSeparator(true);
0413     editableContentActions.append(action);
0414 
0415     action = m_actionCollection->addAction(KStandardAction::Copy, QL1S("copy"),  m_part->browserExtension(), SLOT(copy()));
0416     action->setEnabled(pageAction(QWebPage::Copy)->isEnabled());
0417     editableContentActions.append(action);
0418 
0419     action = m_actionCollection->addAction(KStandardAction::Cut, QL1S("cut"),  m_part->browserExtension(), SLOT(cut()));
0420     action->setEnabled(pageAction(QWebPage::Cut)->isEnabled());
0421     editableContentActions.append(action);
0422 
0423     action = m_actionCollection->addAction(KStandardAction::Paste, QL1S("paste"),  m_part->browserExtension(), SLOT(paste()));
0424     action->setEnabled(pageAction(QWebPage::Paste)->isEnabled());
0425     editableContentActions.append(action);
0426 
0427     action = new QAction(m_actionCollection);
0428     action->setSeparator(true);
0429     editableContentActions.append(action);
0430 
0431     const bool hasContent = (!m_result.element().evaluateJavaScript(QL1S("this.value")).toString().isEmpty());
0432     action = m_actionCollection->addAction(KStandardAction::SelectAll, QL1S("selectall"),  m_part->browserExtension(), SLOT(slotSelectAll()));
0433     action->setEnabled((pageAction(QWebPage::SelectAll)->isEnabled() && hasContent));
0434     editableContentActions.append(action);
0435 
0436     if (showSpellCheckAction(m_result.element())) {
0437         action = new QAction(m_actionCollection);
0438         action->setSeparator(true);
0439         editableContentActions.append(action);
0440         action = m_actionCollection->addAction(KStandardAction::Spelling, QL1S("spelling"), m_part->browserExtension(), SLOT(slotCheckSpelling()));
0441         action->setText(i18n("Check Spelling..."));
0442         action->setEnabled(hasContent);
0443         editableContentActions.append(action);
0444 
0445         const bool hasSelection = (hasContent && m_result.isContentSelected());
0446         action = m_actionCollection->addAction(KStandardAction::Spelling, QL1S("spellcheckSelection"), m_part->browserExtension(), SLOT(slotSpellCheckSelection()));
0447         action->setText(i18n("Spellcheck selection..."));
0448         action->setEnabled(hasSelection);
0449         editableContentActions.append(action);
0450     }
0451 
0452     if (settings()->testAttribute(QWebSettings::DeveloperExtrasEnabled)) {
0453         if (!m_webInspector) {
0454             m_webInspector = new QWebInspector;
0455             m_webInspector->setPage(page());
0456             connect(page(), SIGNAL(destroyed()), m_webInspector, SLOT(deleteLater()));
0457         }
0458         action = new QAction(m_actionCollection);
0459         action->setSeparator(true);
0460         editableContentActions.append(action);
0461         editableContentActions.append(pageAction(QWebPage::InspectElement));
0462     }
0463 
0464     partGroupMap.insert("editactions" , editableContentActions);
0465 }
0466 
0467 
0468 void WebView::partActionPopupMenu(KParts::BrowserExtension::ActionGroupMap& partGroupMap)
0469 {
0470     QList<QAction*> partActions;
0471 
0472     if (m_result.imageUrl().isValid()) {
0473         QAction *action;
0474         action = new QAction(i18n("Save Image As..."), this);
0475         m_actionCollection->addAction(QL1S("saveimageas"), action);
0476         connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotSaveImageAs()));
0477         partActions.append(action);
0478 
0479         action = new QAction(i18n("Send Image..."), this);
0480         m_actionCollection->addAction(QL1S("sendimage"), action);
0481         connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotSendImage()));
0482         partActions.append(action);
0483 
0484         action = new QAction(i18n("Copy Image URL"), this);
0485         m_actionCollection->addAction(QL1S("copyimageurl"), action);
0486         connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotCopyImageURL()));
0487         partActions.append(action);
0488 
0489         action = new QAction(i18n("Copy Image"), this);
0490         m_actionCollection->addAction(QL1S("copyimage"), action);
0491         connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotCopyImage()));
0492         action->setEnabled(!m_result.pixmap().isNull());
0493         partActions.append(action);
0494 
0495         action = new QAction(i18n("View Image (%1)", QUrl(m_result.imageUrl()).fileName()), this);
0496         m_actionCollection->addAction(QL1S("viewimage"), action);
0497         connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotViewImage()));
0498         partActions.append(action);
0499 
0500         if (WebKitSettings::self()->isAdFilterEnabled()) {
0501             action = new QAction(i18n("Block Image..."), this);
0502             m_actionCollection->addAction(QL1S("blockimage"), action);
0503             connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotBlockImage()));
0504             partActions.append(action);
0505 
0506             if (!m_result.imageUrl().host().isEmpty() &&
0507                 !m_result.imageUrl().scheme().isEmpty())
0508             {
0509                 action = new QAction(i18n("Block Images From %1" , m_result.imageUrl().host()), this);
0510                 m_actionCollection->addAction(QL1S("blockhost"), action);
0511                 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotBlockHost()));
0512                 partActions.append(action);
0513             }
0514         }
0515     } else if (m_result.frame() && m_result.frame()->parentFrame() && !m_result.isContentSelected() && m_result.linkUrl().isEmpty()) {
0516         KActionMenu * menu = new KActionMenu(i18nc("@title:menu HTML frame/iframe", "Frame"), this);
0517 
0518         QAction* action = new QAction(QIcon::fromTheme(QStringLiteral("window-new")), i18n("Open in New &Window"), this);
0519         m_actionCollection->addAction(QL1S("frameinwindow"), action);
0520         connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotFrameInWindow()));
0521         menu->addAction(action);
0522 
0523         action = new QAction(i18n("Open in &This Window"), this);
0524         m_actionCollection->addAction(QL1S("frameintop"), action);
0525         connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotFrameInTop()));
0526         menu->addAction(action);
0527 
0528         action = new QAction(QIcon::fromTheme(QStringLiteral("tab-new")), i18n("Open in &New Tab"), this);
0529         m_actionCollection->addAction(QL1S("frameintab"), action);
0530         connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotFrameInTab()));
0531         menu->addAction(action);
0532 
0533         action = new QAction(m_actionCollection);
0534         action->setSeparator(true);
0535         menu->addAction(action);
0536 
0537         action = new QAction(i18n("Reload Frame"), this);
0538         m_actionCollection->addAction(QL1S("reloadframe"), action);
0539         connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotReloadFrame()));
0540         menu->addAction(action);
0541 
0542         action = new QAction(QIcon::fromTheme(QStringLiteral("document-print-frame")), i18n("Print Frame..."), this);
0543         m_actionCollection->addAction(QL1S("printFrame"), action);
0544         connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(print()));
0545         menu->addAction(action);
0546 
0547         action = new QAction(i18n("Save &Frame As..."), this);
0548         m_actionCollection->addAction(QL1S("saveFrame"), action);
0549         connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotSaveFrame()));
0550         menu->addAction(action);
0551 
0552         action = new QAction(i18n("View Frame Source"), this);
0553         m_actionCollection->addAction(QL1S("viewFrameSource"), action);
0554         connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotViewFrameSource()));
0555         menu->addAction(action);
0556 ///TODO Slot not implemented yet
0557 //         action = new QAction(i18n("View Frame Information"), this);
0558 //         m_actionCollection->addAction(QL1S("viewFrameInfo"), action);
0559 //         connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotViewPageInfo()));
0560 
0561         action = new QAction(m_actionCollection);
0562         action->setSeparator(true);
0563         menu->addAction(action);
0564 
0565         if (WebKitSettings::self()->isAdFilterEnabled()) {
0566             action = new QAction(i18n("Block IFrame..."), this);
0567             m_actionCollection->addAction(QL1S("blockiframe"), action);
0568             connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotBlockIFrame()));
0569             menu->addAction(action);
0570         }
0571 
0572         partActions.append(menu);
0573     }
0574 
0575     const bool showDocSourceAction = (!m_result.linkUrl().isValid() &&
0576                                       !m_result.imageUrl().isValid() &&
0577                                       !m_result.isContentSelected());
0578     const bool showInspectorAction = settings()->testAttribute(QWebSettings::DeveloperExtrasEnabled);
0579 
0580     if (showDocSourceAction || showInspectorAction) {
0581         QAction *separatorAction = new QAction(m_actionCollection);
0582         separatorAction->setSeparator(true);
0583         partActions.append(separatorAction);
0584     }
0585 
0586     if (showDocSourceAction)
0587         partActions.append(m_part->actionCollection()->action("viewDocumentSource"));
0588 
0589     if (showInspectorAction) {
0590         if (!m_webInspector) {
0591             m_webInspector = new QWebInspector;
0592             m_webInspector->setPage(page());
0593             connect(page(), SIGNAL(destroyed()), m_webInspector, SLOT(deleteLater()));
0594         }
0595         partActions.append(pageAction(QWebPage::InspectElement));
0596     } else {
0597         if (m_webInspector) {
0598             delete m_webInspector;
0599             m_webInspector = nullptr;
0600         }
0601     }
0602 
0603     partGroupMap.insert("partactions", partActions);
0604 }
0605 
0606 void WebView::selectActionPopupMenu(KParts::BrowserExtension::ActionGroupMap& selectGroupMap)
0607 {
0608     QList<QAction*> selectActions;
0609 
0610     QAction* copyAction = m_actionCollection->addAction(KStandardAction::Copy, QL1S("copy"),  m_part->browserExtension(), SLOT(copy()));
0611     copyAction->setText(i18n("&Copy Text"));
0612     copyAction->setEnabled(m_part->browserExtension()->isActionEnabled("copy"));
0613     selectActions.append(copyAction);
0614 
0615     addSearchActions(selectActions, this);
0616 
0617     KUriFilterData data (selectedText().simplified().left(256));
0618     data.setCheckForExecutables(false);
0619     if (KUriFilter::self()->filterUri(data, QStringList() << "kshorturifilter" << "fixhosturifilter") &&
0620         data.uri().isValid() && data.uriType() == KUriFilterData::NetProtocol) {
0621         QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("window-new")), i18nc("open selected url", "Open '%1'",
0622                                             KStringHandler::rsqueeze(data.uri().url(), 18)), this);
0623         m_actionCollection->addAction(QL1S("openSelection"), action);
0624         action->setData(QUrl(data.uri()));
0625         connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotOpenSelection()));
0626         selectActions.append(action);
0627     }
0628 
0629     selectGroupMap.insert("editactions", selectActions);
0630 }
0631 
0632 void WebView::linkActionPopupMenu(KParts::BrowserExtension::ActionGroupMap& linkGroupMap)
0633 {
0634     Q_ASSERT(!m_result.linkUrl().isEmpty());
0635 
0636     const QUrl url(m_result.linkUrl());
0637 
0638     QList<QAction*> linkActions;
0639 
0640     QAction* action;
0641 
0642     if (m_result.isContentSelected()) {
0643         action = m_actionCollection->addAction(KStandardAction::Copy, QL1S("copy"),  m_part->browserExtension(), SLOT(copy()));
0644         action->setText(i18n("&Copy Text"));
0645         action->setEnabled(m_part->browserExtension()->isActionEnabled("copy"));
0646         linkActions.append(action);
0647     }
0648 
0649     if (url.scheme() == "mailto") {
0650         action = new QAction(i18n("&Copy Email Address"), this);
0651         m_actionCollection->addAction(QL1S("copylinklocation"), action);
0652         connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotCopyEmailAddress()));
0653         linkActions.append(action);
0654     } else {
0655         if (!m_result.isContentSelected()) {
0656             action = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy Link &Text"), this);
0657             m_actionCollection->addAction(QL1S("copylinktext"), action);
0658             connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotCopyLinkText()));
0659             linkActions.append(action);
0660         }
0661 
0662         action = new QAction(i18n("Copy Link &URL"), this);
0663         m_actionCollection->addAction(QL1S("copylinkurl"), action);
0664         connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotCopyLinkURL()));
0665         linkActions.append(action);
0666 
0667         action = new QAction(i18n("&Save Link As..."), this);
0668         m_actionCollection->addAction(QL1S("savelinkas"), action);
0669         connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotSaveLinkAs()));
0670         linkActions.append(action);
0671     }
0672 
0673     linkGroupMap.insert("linkactions", linkActions);
0674 }
0675 
0676 void WebView::multimediaActionPopupMenu(KParts::BrowserExtension::ActionGroupMap& mmGroupMap)
0677 {
0678     QList<QAction*> multimediaActions;
0679 
0680     QWebElement element (m_result.element());
0681     const bool isPaused = element.evaluateJavaScript(QL1S("this.paused")).toBool();
0682     const bool isMuted = element.evaluateJavaScript(QL1S("this.muted")).toBool();
0683     const bool isLoopOn = element.evaluateJavaScript(QL1S("this.loop")).toBool();
0684     const bool areControlsOn = element.evaluateJavaScript(QL1S("this.controls")).toBool();
0685     const bool isVideoElement = (element.tagName().compare(QL1S("video"), Qt::CaseInsensitive) == 0);
0686     const bool isAudioElement = (element.tagName().compare(QL1S("audio"), Qt::CaseInsensitive) == 0);
0687 
0688     QAction* action = new QAction((isPaused ? i18n("&Play") : i18n("&Pause")), this);
0689     m_actionCollection->addAction(QL1S("playmultimedia"), action);
0690     connect(action, SIGNAL(triggered()), m_part->browserExtension(), SLOT(slotPlayMedia()));
0691     multimediaActions.append(action);
0692 
0693     action = new QAction((isMuted ? i18n("Un&mute") : i18n("&Mute")), this);
0694     m_actionCollection->addAction(QL1S("mutemultimedia"), action);
0695     connect(action, SIGNAL(triggered()), m_part->browserExtension(), SLOT(slotMuteMedia()));
0696     multimediaActions.append(action);
0697 
0698     action = new QAction(i18n("&Loop"), this);
0699     action->setCheckable(true);
0700     action->setChecked(isLoopOn);
0701     m_actionCollection->addAction(QL1S("loopmultimedia"), action);
0702     connect(action, SIGNAL(triggered()), m_part->browserExtension(), SLOT(slotLoopMedia()));
0703     multimediaActions.append(action);
0704 
0705     action = new QAction(i18n("Show &Controls"), this);
0706     action->setCheckable(true);
0707     action->setChecked(areControlsOn);
0708     m_actionCollection->addAction(QL1S("showmultimediacontrols"), action);
0709     connect(action, SIGNAL(triggered()), m_part->browserExtension(), SLOT(slotShowMediaControls()));
0710     multimediaActions.append(action);
0711 
0712     action = new QAction(m_actionCollection);
0713     action->setSeparator(true);
0714     multimediaActions.append(action);
0715 
0716     QString saveMediaText, copyMediaText;
0717     if (isVideoElement) {
0718         saveMediaText = i18n("Sa&ve Video As...");
0719         copyMediaText = i18n("C&opy Video URL");
0720     } else if (isAudioElement) {
0721         saveMediaText = i18n("Sa&ve Audio As...");
0722         copyMediaText = i18n("C&opy Audio URL");
0723     } else {
0724         saveMediaText = i18n("Sa&ve Media As...");
0725         copyMediaText = i18n("C&opy Media URL");
0726     }
0727 
0728     action = new QAction(saveMediaText, this);
0729     m_actionCollection->addAction(QL1S("savemultimedia"), action);
0730     connect(action, SIGNAL(triggered()), m_part->browserExtension(), SLOT(slotSaveMedia()));
0731     multimediaActions.append(action);
0732 
0733     action = new QAction(copyMediaText, this);
0734     m_actionCollection->addAction(QL1S("copymultimediaurl"), action);
0735     connect(action, SIGNAL(triggered()), m_part->browserExtension(), SLOT(slotCopyMedia()));
0736     multimediaActions.append(action);
0737 
0738     mmGroupMap.insert("partactions", multimediaActions);
0739 }
0740 
0741 void WebView::slotStopAutoScroll()
0742 {
0743     if (m_autoScrollTimerId == -1) {
0744         return;
0745     }
0746 
0747     killTimer(m_autoScrollTimerId);
0748     m_autoScrollTimerId = -1;
0749     m_verticalAutoScrollSpeed = 0;
0750     m_horizontalAutoScrollSpeed = 0;
0751 
0752 }
0753 
0754 void WebView::addSearchActions(QList<QAction*>& selectActions, QWebView* view)
0755 {
0756    // search text
0757     const QString selectedText = view->selectedText().simplified();
0758     if (selectedText.isEmpty())
0759         return;
0760 
0761     KUriFilterData data;
0762     data.setData(selectedText);
0763     data.setAlternateDefaultSearchProvider(ALTERNATE_DEFAULT_WEB_SHORTCUT);
0764     data.setAlternateSearchProviders(ALTERNATE_WEB_SHORTCUTS);
0765 
0766     if (KUriFilter::self()->filterSearchUri(data, KUriFilter::NormalTextFilter)) {
0767         const QString squeezedText = KStringHandler::rsqueeze(selectedText, 20);
0768         QAction *action = new QAction(QIcon::fromTheme(data.iconName()),
0769                                       i18nc("Search \"search provider\" for \"text\"", "Search %1 for '%2'",
0770                                             data.searchProvider(), squeezedText), view);
0771         action->setData(QUrl(data.uri()));
0772         connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(searchProvider()));
0773         m_actionCollection->addAction(QL1S("defaultSearchProvider"), action);
0774         selectActions.append(action);
0775 
0776 
0777         const QStringList preferredSearchProviders = data.preferredSearchProviders();
0778         if (!preferredSearchProviders.isEmpty()) {
0779             KActionMenu* providerList = new KActionMenu(i18nc("Search for \"text\" with",
0780                                                               "Search for '%1' with", squeezedText), view);
0781             Q_FOREACH(const QString &searchProvider, preferredSearchProviders) {
0782                 if (searchProvider == data.searchProvider())
0783                     continue;
0784 
0785                 QAction *action = new QAction(QIcon::fromTheme(data.iconNameForPreferredSearchProvider(searchProvider)), searchProvider, view);
0786                 action->setData(data.queryForPreferredSearchProvider(searchProvider));
0787                 m_actionCollection->addAction(searchProvider, action);
0788                 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(searchProvider()));
0789 
0790                 providerList->addAction(action);
0791             }
0792             m_actionCollection->addAction(QL1S("searchProviderList"), providerList);
0793             selectActions.append(providerList);
0794         }
0795     }
0796 }
0797 
0798 bool WebView::checkForAccessKey(QKeyEvent *event)
0799 {
0800     if (m_accessKeyLabels.isEmpty())
0801         return false;
0802 
0803     QString text = event->text();
0804     if (text.isEmpty())
0805         return false;
0806     QChar key = text.at(0).toUpper();
0807     bool handled = false;
0808     if (m_accessKeyNodes.contains(key)) {
0809         QWebElement element = m_accessKeyNodes[key];
0810         QPoint p = element.geometry().center();
0811         QWebFrame *frame = element.webFrame();
0812         Q_ASSERT(frame);
0813         do {
0814             p -= frame->scrollPosition();
0815             frame = frame->parentFrame();
0816         } while (frame && frame != page()->mainFrame());
0817         QMouseEvent pevent(QEvent::MouseButtonPress, p, Qt::LeftButton, {}, {});
0818         QCoreApplication::sendEvent(this, &pevent);
0819         QMouseEvent revent(QEvent::MouseButtonRelease, p, Qt::LeftButton, {}, {});
0820         QCoreApplication::sendEvent(this, &revent);
0821         handled = true;
0822     }
0823     return handled;
0824 }
0825 
0826 void WebView::hideAccessKeys()
0827 {
0828     if (!m_accessKeyLabels.isEmpty()) {
0829         for (int i = 0, count = m_accessKeyLabels.count(); i < count; ++i) {
0830             QLabel *label = m_accessKeyLabels[i];
0831             label->hide();
0832             label->deleteLater();
0833         }
0834         m_accessKeyLabels.clear();
0835         m_accessKeyNodes.clear();
0836         m_duplicateLinkElements.clear();
0837         m_accessKeyActivated = NotActivated;
0838         emit statusBarMessage(QString());
0839         update();
0840     }
0841 }
0842 
0843 static QString linkElementKey(const QWebElement& element)
0844 {
0845     if (element.hasAttribute(QL1S("href"))) {
0846         const QUrl url = element.webFrame()->baseUrl().resolved(QUrl(element.attribute(QL1S("href"))));
0847         QString linkKey (url.toString());
0848         if (element.hasAttribute(QL1S("target"))) {
0849             linkKey += QL1C('+');
0850             linkKey += element.attribute(QL1S("target"));
0851         }
0852         return linkKey; 
0853     }
0854     return QString();
0855 }
0856 
0857 static void handleDuplicateLinkElements(const QWebElement& element, QHash<QString, QChar>* dupLinkList, QChar* accessKey)
0858 {
0859     if (element.tagName().compare(QL1S("A"), Qt::CaseInsensitive) == 0) {
0860         const QString linkKey (linkElementKey(element));
0861         // qCDebug(KWEBKITPART_LOG) << "LINK KEY:" << linkKey;
0862         if (dupLinkList->contains(linkKey)) {
0863             // qCDebug(KWEBKITPART_LOG) << "***** Found duplicate link element:" << linkKey << endl;
0864             *accessKey = dupLinkList->value(linkKey);
0865         } else if (!linkKey.isEmpty()) {
0866             dupLinkList->insert(linkKey, *accessKey);
0867         }
0868         if (linkKey.isEmpty())
0869             *accessKey = QChar();
0870     }
0871 }
0872 
0873 static bool isHiddenElement(const QWebElement& element)
0874 {
0875     // width property set to less than zero
0876     if (element.hasAttribute(QL1S("width")) && element.attribute(QL1S("width")).toInt() < 1) {
0877         return true;
0878     }
0879 
0880     // height property set to less than zero
0881     if (element.hasAttribute(QL1S("height")) && element.attribute(QL1S("height")).toInt() < 1) {
0882         return true;
0883     }
0884 
0885     // visibility set to 'hidden' in the element itself or its parent elements.
0886     if (element.styleProperty(QL1S("visibility"),QWebElement::ComputedStyle).compare(QL1S("hidden"), Qt::CaseInsensitive) == 0) {
0887         return true;
0888     }
0889 
0890     // display set to 'none' in the element itself or its parent elements.
0891     if (element.styleProperty(QL1S("display"),QWebElement::ComputedStyle).compare(QL1S("none"), Qt::CaseInsensitive) == 0) {
0892         return true;
0893     }
0894 
0895     return false;
0896 }
0897 
0898 void WebView::showAccessKeys()
0899 {
0900     QList<QChar> unusedKeys;
0901     for (char c = 'A'; c <= 'Z'; ++c)
0902         unusedKeys << QLatin1Char(c);
0903     for (char c = '0'; c <= '9'; ++c)
0904         unusedKeys << QLatin1Char(c);
0905 
0906     QList<QWebElement> unLabeledElements;
0907     QRect viewport = QRect(page()->mainFrame()->scrollPosition(), page()->viewportSize());
0908     const QString selectorQuery (QLatin1String("a[href],"
0909                                                "area,"
0910                                                "button:not([disabled]),"
0911                                                "input:not([disabled]):not([hidden]),"
0912                                                "label[for],"
0913                                                "legend,"
0914                                                "select:not([disabled]),"
0915                                                "textarea:not([disabled])"));
0916     QList<QWebElement> result = page()->mainFrame()->findAllElements(selectorQuery).toList();
0917 
0918     // Priority first goes to elements with accesskey attributes
0919     Q_FOREACH (const QWebElement& element, result) {
0920         const QRect geometry = element.geometry();
0921         if (geometry.size().isEmpty() || !viewport.contains(geometry.topLeft())) {
0922             continue;
0923         }
0924         if (isHiddenElement(element)) {
0925             continue;    // Do not show access key for hidden elements...
0926         }
0927         const QString accessKeyAttribute (element.attribute(QLatin1String("accesskey")).toUpper());
0928         if (accessKeyAttribute.isEmpty()) {
0929             unLabeledElements.append(element);
0930             continue;
0931         }
0932         QChar accessKey;
0933         for (int i = 0; i < accessKeyAttribute.count(); i+=2) {
0934             const QChar &possibleAccessKey = accessKeyAttribute[i];
0935             if (unusedKeys.contains(possibleAccessKey)) {
0936                 accessKey = possibleAccessKey;
0937                 break;
0938             }
0939         }
0940         if (accessKey.isNull()) {
0941             unLabeledElements.append(element);
0942             continue;
0943         }
0944 
0945         handleDuplicateLinkElements(element, &m_duplicateLinkElements, &accessKey);
0946         if (!accessKey.isNull()) {
0947             unusedKeys.removeOne(accessKey);
0948             makeAccessKeyLabel(accessKey, element);
0949         }
0950     }
0951 
0952 
0953     // Pick an access key first from the letters in the text and then from the
0954     // list of unused access keys
0955     Q_FOREACH (const QWebElement &element, unLabeledElements) {
0956         const QRect geometry = element.geometry();
0957         if (unusedKeys.isEmpty()
0958             || geometry.size().isEmpty()
0959             || !viewport.contains(geometry.topLeft()))
0960             continue;
0961         QChar accessKey;
0962         QString text = element.toPlainText().toUpper();
0963         for (int i = 0; i < text.count(); ++i) {
0964             const QChar &c = text.at(i);
0965             if (unusedKeys.contains(c)) {
0966                 accessKey = c;
0967                 break;
0968             }
0969         }
0970         if (accessKey.isNull())
0971             accessKey = unusedKeys.takeFirst();
0972 
0973         handleDuplicateLinkElements(element, &m_duplicateLinkElements, &accessKey);
0974         if (!accessKey.isNull()) {
0975             unusedKeys.removeOne(accessKey);
0976             makeAccessKeyLabel(accessKey, element);
0977         }
0978     }
0979 
0980     m_accessKeyActivated = (m_accessKeyLabels.isEmpty() ? Activated : NotActivated);
0981 }
0982 
0983 void WebView::makeAccessKeyLabel(const QChar &accessKey, const QWebElement &element)
0984 {
0985     QLabel *label = new QLabel(this);
0986     QFont font (label->font());
0987     font.setBold(true);
0988     label->setFont(font);
0989     label->setText(accessKey);
0990     label->setPalette(QToolTip::palette());
0991     label->setAutoFillBackground(true);
0992     label->setFrameStyle(QFrame::Box | QFrame::Plain);
0993     QPoint point = element.geometry().center();
0994     point -= page()->mainFrame()->scrollPosition();
0995     label->move(point);
0996     label->show();
0997     point.setX(point.x() - label->width() / 2);
0998     label->move(point);
0999     m_accessKeyLabels.append(label);
1000     m_accessKeyNodes.insertMulti(accessKey, element);
1001 }