File indexing completed on 2025-01-26 04:57:25

0001 /*
0002    SPDX-FileCopyrightText: 2016-2024 Laurent Montel <montel@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "webhittest.h"
0008 #include "webenginemanagescript.h"
0009 #include "webhittestresult.h"
0010 #include <QWebEnginePage>
0011 
0012 using namespace WebEngineViewer;
0013 template<typename Arg, typename R, typename C>
0014 
0015 struct InvokeWrapperWebhittest {
0016     R *receiver;
0017     void (C::*memberFunction)(Arg);
0018     void operator()(Arg result)
0019     {
0020         (receiver->*memberFunction)(result);
0021     }
0022 };
0023 
0024 template<typename Arg, typename R, typename C>
0025 
0026 InvokeWrapperWebhittest<Arg, R, C> invoke(R *receiver, void (C::*memberFunction)(Arg))
0027 {
0028     InvokeWrapperWebhittest<Arg, R, C> wrapper = {receiver, memberFunction};
0029     return wrapper;
0030 }
0031 
0032 class WebEngineViewer::WebHitTestPrivate
0033 {
0034 public:
0035     WebHitTestPrivate(const QPoint &pos, const QPoint &zoomedPos)
0036         : m_zoomedPos(zoomedPos)
0037         , m_pos(pos)
0038     {
0039     }
0040 
0041     QPoint m_zoomedPos;
0042     QPoint m_pos;
0043     QUrl m_pageUrl;
0044 };
0045 
0046 WebHitTest::WebHitTest(QWebEnginePage *page, const QPoint &zoomedPos, const QPoint &pos, QObject *parent)
0047     : QObject(parent)
0048     , d(new WebHitTestPrivate(pos, zoomedPos))
0049 {
0050     QString source = QStringLiteral(
0051         "(function() {"
0052         "var e = document.elementFromPoint(%1, %2);"
0053         "if (!e)"
0054         "    return;"
0055         "function isMediaElement(e) {"
0056         "    return e.tagName.toLowerCase() == 'audio' || e.tagName.toLowerCase() == 'video';"
0057         "}"
0058         "function isEditableElement(e) {"
0059         "    if (e.isContentEditable)"
0060         "        return true;"
0061         "    if (e.tagName.toLowerCase() == 'input' || e.tagName.toLowerCase() == 'textarea')"
0062         "        return e.getAttribute('readonly') != 'readonly';"
0063         "    return false;"
0064         "}"
0065         "function isSelected(e) {"
0066         "    var selection = window.getSelection();"
0067         "    if (selection.type != 'Range')"
0068         "        return false;"
0069         "    return window.getSelection().containsNode(e, true);"
0070         "}"
0071         "function attributeStr(e, a) {"
0072         "    return e.getAttribute(a) || '';"
0073         "}"
0074         "var res = {"
0075         "    alternateText: e.getAttribute('alt'),"
0076         "    boundingRect: '',"
0077         "    imageUrl: '',"
0078         "    contentEditable: isEditableElement(e),"
0079         "    contentSelected: isSelected(e),"
0080         "    linkTitle: '',"
0081         "    linkUrl: '',"
0082         "    mediaUrl: '',"
0083         "    tagName: e.tagName.toLowerCase()"
0084         "};"
0085         "var r = e.getBoundingClientRect();"
0086         "res.boundingRect = [r.top, r.left, r.width, r.height];"
0087         "if (e.tagName.toLowerCase() == 'img')"
0088         "    res.imageUrl = attributeStr(e, 'src').trim();"
0089         "if (e.tagName.toLowerCase() == 'a') {"
0090         "    res.linkTitle = e.text;"
0091         "    res.linkUrl = attributeStr(e, 'href').trim();"
0092         "}"
0093         "while (e) {"
0094         "    if (res.linkTitle == '' && e.tagName.toLowerCase() == 'a')"
0095         "        res.linkTitle = e.text;"
0096         "    if (res.linkUrl == '' && e.tagName.toLowerCase() == 'a')"
0097         "        res.linkUrl = attributeStr(e, 'href').trim();"
0098         "    if (res.mediaUrl == '' && isMediaElement(e)) {"
0099         "        res.mediaUrl = e.currentSrc;"
0100         "        res.mediaPaused = e.paused;"
0101         "        res.mediaMuted = e.muted;"
0102         "    }"
0103         "    e = e.parentElement;"
0104         "}"
0105         "return res;"
0106         "})()");
0107 
0108     const QString &js = source.arg(d->m_zoomedPos.x()).arg(d->m_zoomedPos.y());
0109     d->m_pageUrl = page->url();
0110     page->runJavaScript(js, WebEngineViewer::WebEngineManageScript::scriptWordId(), invoke(this, &WebHitTest::handleHitTest));
0111 }
0112 
0113 WebHitTest::~WebHitTest() = default;
0114 
0115 void WebHitTest::handleHitTest(const QVariant &result)
0116 {
0117     const WebHitTestResult webHitResult(d->m_pos, d->m_pageUrl, result);
0118     Q_EMIT finished(webHitResult);
0119     deleteLater();
0120 }
0121 
0122 #include "moc_webhittest.cpp"