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

0001 /* ============================================================
0002 * Falkon - Qt web browser
0003 * Copyright (C) 2015-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 "webhittestresult.h"
0019 #include "webpage.h"
0020 
0021 #include <QWebEngineContextMenuRequest>
0022 
0023 WebHitTestResult::WebHitTestResult(const WebPage *page, const QPoint &pos)
0024     : m_isNull(true)
0025     , m_isContentEditable(false)
0026     , m_isContentSelected(false)
0027     , m_mediaPaused(false)
0028     , m_mediaMuted(false)
0029     , m_pos(pos)
0030 {
0031     QString source = QL1S("(function() {"
0032                           "var e = document.elementFromPoint(%1, %2);"
0033                           "if (!e)"
0034                           "    return;"
0035                           "function isMediaElement(e) {"
0036                           "    return e.tagName.toLowerCase() == 'audio' || e.tagName.toLowerCase() == 'video';"
0037                           "}"
0038                           "function isEditableElement(e) {"
0039                           "    if (e.isContentEditable)"
0040                           "        return true;"
0041                           "    if (e.tagName.toLowerCase() == 'input' || e.tagName.toLowerCase() == 'textarea')"
0042                           "        return e.getAttribute('readonly') != 'readonly';"
0043                           "    return false;"
0044                           "}"
0045                           "function isSelected(e) {"
0046                           "    var selection = window.getSelection();"
0047                           "    if (selection.type != 'Range')"
0048                           "        return false;"
0049                           "    return window.getSelection().containsNode(e, true);"
0050                           "}"
0051                           "function attributeStr(e, a) {"
0052                           "    return e.getAttribute(a) || '';"
0053                           "}"
0054                           "var res = {"
0055                           "    baseUrl: document.baseURI,"
0056                           "    alternateText: e.getAttribute('alt'),"
0057                           "    boundingRect: '',"
0058                           "    imageUrl: '',"
0059                           "    contentEditable: isEditableElement(e),"
0060                           "    contentSelected: isSelected(e),"
0061                           "    linkTitle: '',"
0062                           "    linkUrl: '',"
0063                           "    mediaUrl: '',"
0064                           "    tagName: e.tagName.toLowerCase()"
0065                           "};"
0066                           "var r = e.getBoundingClientRect();"
0067                           "res.boundingRect = [r.top, r.left, r.width, r.height];"
0068                           "if (e.tagName.toLowerCase() == 'img')"
0069                           "    res.imageUrl = attributeStr(e, 'src').trim();"
0070                           "if (e.tagName.toLowerCase() == 'a') {"
0071                           "    res.linkTitle = e.text;"
0072                           "    res.linkUrl = attributeStr(e, 'href').trim();"
0073                           "}"
0074                           "while (e) {"
0075                           "    if (res.linkTitle == '' && e.tagName.toLowerCase() == 'a')"
0076                           "        res.linkTitle = e.text;"
0077                           "    if (res.linkUrl == '' && e.tagName.toLowerCase() == 'a')"
0078                           "        res.linkUrl = attributeStr(e, 'href').trim();"
0079                           "    if (res.mediaUrl == '' && isMediaElement(e)) {"
0080                           "        res.mediaUrl = e.currentSrc;"
0081                           "        res.mediaPaused = e.paused;"
0082                           "        res.mediaMuted = e.muted;"
0083                           "    }"
0084                           "    e = e.parentElement;"
0085                           "}"
0086                           "return res;"
0087                           "})()");
0088 
0089     auto *p = const_cast<WebPage*>(page);
0090     m_viewportPos = p->mapToViewport(m_pos);
0091     const QString &js = source.arg(m_viewportPos.x()).arg(m_viewportPos.y());
0092     init(p->url(), p->execJavaScript(js, WebPage::SafeJsWorld).toMap());
0093 }
0094 
0095 void WebHitTestResult::updateWithContextMenuData(const QWebEngineContextMenuRequest &data)
0096 {
0097     if (data.position() != m_pos) {
0098         return;
0099     }
0100 
0101     m_linkTitle = data.linkText();
0102     m_linkUrl = data.linkUrl();
0103     m_isContentEditable = data.isContentEditable();
0104     m_isContentSelected = !data.selectedText().isEmpty();
0105 
0106     switch (data.mediaType()) {
0107     case QWebEngineContextMenuRequest::MediaTypeImage:
0108         m_imageUrl = data.mediaUrl();
0109         break;
0110 
0111     case QWebEngineContextMenuRequest::MediaTypeVideo:
0112     case QWebEngineContextMenuRequest::MediaTypeAudio:
0113         m_mediaUrl = data.mediaUrl();
0114         break;
0115 
0116     default:
0117         break;
0118     }
0119 }
0120 
0121 QUrl WebHitTestResult::baseUrl() const
0122 {
0123     return m_baseUrl;
0124 }
0125 
0126 QString WebHitTestResult::alternateText() const
0127 {
0128     return m_alternateText;
0129 }
0130 
0131 QRect WebHitTestResult::boundingRect() const
0132 {
0133     return m_boundingRect;
0134 }
0135 
0136 QUrl WebHitTestResult::imageUrl() const
0137 {
0138     return m_imageUrl;
0139 }
0140 
0141 bool WebHitTestResult::isContentEditable() const
0142 {
0143     return m_isContentEditable;
0144 }
0145 
0146 bool WebHitTestResult::isContentSelected() const
0147 {
0148     return m_isContentSelected;
0149 }
0150 
0151 bool WebHitTestResult::isNull() const
0152 {
0153     return m_isNull;
0154 }
0155 
0156 QString WebHitTestResult::linkTitle() const
0157 {
0158     return m_linkTitle;
0159 }
0160 
0161 QUrl WebHitTestResult::linkUrl() const
0162 {
0163     return m_linkUrl;
0164 }
0165 
0166 QUrl WebHitTestResult::mediaUrl() const
0167 {
0168     return m_mediaUrl;
0169 }
0170 
0171 bool WebHitTestResult::mediaPaused() const
0172 {
0173     return m_mediaPaused;
0174 }
0175 
0176 bool WebHitTestResult::mediaMuted() const
0177 {
0178     return m_mediaMuted;
0179 }
0180 
0181 QPoint WebHitTestResult::pos() const
0182 {
0183     return m_pos;
0184 }
0185 
0186 QPointF WebHitTestResult::viewportPos() const
0187 {
0188     return m_viewportPos;
0189 }
0190 
0191 QString WebHitTestResult::tagName() const
0192 {
0193     return m_tagName;
0194 }
0195 
0196 void WebHitTestResult::init(const QUrl &url, const QVariantMap &map)
0197 {
0198     if (map.isEmpty())
0199         return;
0200 
0201     m_isNull = false;
0202     m_baseUrl = map.value(QSL("baseUrl")).toUrl();
0203     m_alternateText = map.value(QSL("alternateText")).toString();
0204     m_imageUrl = map.value(QSL("imageUrl")).toUrl();
0205     m_isContentEditable = map.value(QSL("contentEditable")).toBool();
0206     m_isContentSelected = map.value(QSL("contentSelected")).toBool();
0207     m_linkTitle = map.value(QSL("linkTitle")).toString();
0208     m_linkUrl = map.value(QSL("linkUrl")).toUrl();
0209     m_mediaUrl = map.value(QSL("mediaUrl")).toUrl();
0210     m_mediaPaused = map.value(QSL("mediaPaused")).toBool();
0211     m_mediaMuted = map.value(QSL("mediaMuted")).toBool();
0212     m_tagName = map.value(QSL("tagName")).toString();
0213 
0214     const QVariantList &rect = map.value(QSL("boundingRect")).toList();
0215     if (rect.size() == 4)
0216         m_boundingRect = QRect(rect.at(0).toInt(), rect.at(1).toInt(), rect.at(2).toInt(), rect.at(3).toInt());
0217 
0218     if (!m_imageUrl.isEmpty())
0219         m_imageUrl = url.resolved(m_imageUrl);
0220     if (!m_linkUrl.isEmpty())
0221         m_linkUrl = m_baseUrl.resolved(m_linkUrl);
0222     if (!m_mediaUrl.isEmpty())
0223         m_mediaUrl = url.resolved(m_mediaUrl);
0224 }