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

0001 /* ============================================================
0002 * Falkon - Qt web browser
0003 * Copyright (C) 2016-2017 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 
0019 #include "webscrollbarmanager.h"
0020 #include "webscrollbar.h"
0021 #include "webview.h"
0022 #include "webpage.h"
0023 #include "mainapplication.h"
0024 #include "scripts.h"
0025 #include "settings.h"
0026 
0027 #include <QPointer>
0028 #include <QPaintEvent>
0029 #include <QPainter>
0030 #include <QWebEngineProfile>
0031 #include <QWebEngineScriptCollection>
0032 #include <QStyle>
0033 #include <QStyleOption>
0034 
0035 Q_GLOBAL_STATIC(WebScrollBarManager, qz_web_scrollbar_manager)
0036 
0037 class WebScrollBarCornerWidget : public QWidget
0038 {
0039 public:
0040     explicit WebScrollBarCornerWidget(WebView *view)
0041         : QWidget()
0042         , m_view(view)
0043     {
0044         setAutoFillBackground(true);
0045     }
0046 
0047     void updateVisibility(bool visible, int thickness)
0048     {
0049         if (visible) {
0050             setParent(m_view->overlayWidget());
0051             resize(thickness, thickness);
0052             move(m_view->width() - width(), m_view->height() - height());
0053             show();
0054         } else {
0055             hide();
0056         }
0057     }
0058 
0059 private:
0060     void paintEvent(QPaintEvent *ev) override
0061     {
0062         QStyleOption option;
0063         option.initFrom(this);
0064         option.rect = rect();
0065 
0066         QPainter p(this);
0067         if (mApp->styleName() == QL1S("breeze")) {
0068             p.fillRect(ev->rect(), option.palette.window());
0069         } else {
0070             style()->drawPrimitive(QStyle::PE_PanelScrollAreaCorner, &option, &p, this);
0071         }
0072     }
0073 
0074     WebView *m_view;
0075 };
0076 
0077 struct ScrollBarData {
0078     ~ScrollBarData() {
0079         delete vscrollbar;
0080         delete hscrollbar;
0081         delete corner;
0082     }
0083 
0084     WebScrollBar *vscrollbar;
0085     WebScrollBar *hscrollbar;
0086     bool vscrollbarVisible = false;
0087     bool hscrollbarVisible = false;
0088     WebScrollBarCornerWidget *corner;
0089 };
0090 
0091 WebScrollBarManager::WebScrollBarManager(QObject *parent)
0092     : QObject(parent)
0093 {
0094     m_scrollbarJs = QL1S("(function() {"
0095                          "var head = document.getElementsByTagName('head')[0];"
0096                          "if (!head) return;"
0097                          "var css = document.createElement('style');"
0098                          "css.setAttribute('type', 'text/css');"
0099                          "var size = %1 / window.devicePixelRatio + 'px';"
0100                          "css.appendChild(document.createTextNode('"
0101                          "   body::-webkit-scrollbar{width:'+size+';height:'+size+';}"
0102                          "'));"
0103                          "head.appendChild(css);"
0104                          "})()");
0105 
0106     loadSettings();
0107 }
0108 
0109 void WebScrollBarManager::loadSettings()
0110 {
0111     m_enabled = Settings().value(QSL("Web-Browser-Settings/UseNativeScrollbars"), false).toBool();
0112 
0113     if (!m_enabled) {
0114         for (WebView *view : m_scrollbars.keys()) {
0115             removeWebView(view);
0116         }
0117     }
0118 }
0119 
0120 void WebScrollBarManager::addWebView(WebView *view)
0121 {
0122     if (!m_enabled) {
0123         return;
0124     }
0125 
0126     delete m_scrollbars.value(view);
0127 
0128     auto *data = new ScrollBarData;
0129     data->vscrollbar = new WebScrollBar(Qt::Vertical, view);
0130     data->hscrollbar = new WebScrollBar(Qt::Horizontal, view);
0131     data->corner = new WebScrollBarCornerWidget(view);
0132     m_scrollbars[view] = data;
0133 
0134     const int thickness = data->vscrollbar->thickness();
0135 
0136     auto updateValues = [=]() {
0137         const QSize viewport = viewportSize(view, thickness);
0138         data->vscrollbar->updateValues(viewport);
0139         data->vscrollbar->setVisible(data->vscrollbarVisible);
0140         data->hscrollbar->updateValues(viewport);
0141         data->hscrollbar->setVisible(data->hscrollbarVisible);
0142         data->corner->updateVisibility(data->vscrollbarVisible && data->hscrollbarVisible, thickness);
0143     };
0144 
0145     connect(view, &WebView::viewportResized, data->vscrollbar, updateValues);
0146     connect(view->page(), &WebPage::scrollPositionChanged, data->vscrollbar, updateValues);
0147 
0148     connect(view->page(), &WebPage::contentsSizeChanged, data->vscrollbar, [=]() {
0149         const QString source = QL1S("var out = {"
0150                                     "vertical: document.documentElement && window.innerWidth > document.documentElement.clientWidth,"
0151                                     "horizontal: document.documentElement && window.innerHeight > document.documentElement.clientHeight"
0152                                     "};out;");
0153 
0154         QPointer<WebView> p(view);
0155         view->page()->runJavaScript(source, WebPage::SafeJsWorld, [=](const QVariant &res) {
0156             if (!p || !m_scrollbars.contains(view)) {
0157                 return;
0158             }
0159             const QVariantMap map = res.toMap();
0160             data->vscrollbarVisible = map.value(QSL("vertical")).toBool();
0161             data->hscrollbarVisible = map.value(QSL("horizontal")).toBool();
0162             updateValues();
0163         });
0164     });
0165 
0166     connect(view, &WebView::zoomLevelChanged, data->vscrollbar, [=]() {
0167         view->page()->runJavaScript(m_scrollbarJs.arg(thickness));
0168     });
0169 
0170     if (m_scrollbars.size() == 1) {
0171         createUserScript(thickness);
0172     }
0173 }
0174 
0175 void WebScrollBarManager::removeWebView(WebView *view)
0176 {
0177     if (!m_scrollbars.contains(view)) {
0178         return;
0179     }
0180 
0181     if (m_scrollbars.size() == 1) {
0182         removeUserScript();
0183     }
0184 
0185     delete m_scrollbars.take(view);
0186 }
0187 
0188 QScrollBar *WebScrollBarManager::scrollBar(Qt::Orientation orientation, WebView *view) const
0189 {
0190     ScrollBarData *d = m_scrollbars.value(view);
0191     if (!d) {
0192         return nullptr;
0193     }
0194     return orientation == Qt::Vertical ? d->vscrollbar : d->hscrollbar;
0195 }
0196 
0197 WebScrollBarManager *WebScrollBarManager::instance()
0198 {
0199     return qz_web_scrollbar_manager();
0200 }
0201 
0202 void WebScrollBarManager::createUserScript(int thickness)
0203 {
0204     QWebEngineScript script;
0205     script.setName(QSL("_falkon_scrollbar"));
0206     script.setInjectionPoint(QWebEngineScript::DocumentReady);
0207     script.setWorldId(WebPage::SafeJsWorld);
0208     script.setSourceCode(m_scrollbarJs.arg(thickness));
0209     mApp->webProfile()->scripts()->insert(script);
0210 }
0211 
0212 void WebScrollBarManager::removeUserScript()
0213 {
0214     for (const QWebEngineScript &script : mApp->webProfile()->scripts()->find(QSL("_falkon_scrollbar"))) {
0215         mApp->webProfile()->scripts()->remove(script);
0216     }
0217 }
0218 
0219 QSize WebScrollBarManager::viewportSize(WebView *view, int thickness) const
0220 {
0221     QSize viewport = view->size();
0222 
0223     thickness /= view->devicePixelRatioF();
0224 
0225     ScrollBarData *data = m_scrollbars.value(view);
0226     Q_ASSERT(data);
0227 
0228     if (data->vscrollbarVisible) {
0229         viewport.setWidth(viewport.width() - thickness);
0230     }
0231     if (data->hscrollbarVisible) {
0232         viewport.setHeight(viewport.height() - thickness);
0233     }
0234 
0235 #if 0
0236     const QSize content = view->page()->contentsSize().toSize();
0237 
0238     // Check both axis
0239     if (content.width() - viewport.width() > 0) {
0240         viewport.setHeight(viewport.height() - thickness);
0241     }
0242 
0243     if (content.height() - viewport.height() > 0) {
0244         viewport.setWidth(viewport.width() - thickness);
0245     }
0246 
0247     // Check again against adjusted size
0248     if (viewport.height() == view->height() && content.width() - viewport.width() > 0) {
0249         viewport.setHeight(viewport.height() - thickness);
0250     }
0251 
0252     if (viewport.width() == view->width() && content.height() - viewport.height() > 0) {
0253         viewport.setWidth(viewport.width() - thickness);
0254     }
0255 #endif
0256 
0257     return viewport;
0258 }