Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

QML WebEngineView flick content

I'm trying to make a simple web-browser for desktop with Ubuntu 14.04 using QML and WebEngineView component. The application will be working on devices with touchpad so it would be nice to make the content displayed inside WebEngineView flickable.

I tried to do it this way, but it does not work:

...    
        WebEngineView {
            id: webView
            url: "http://google.com"
            width: parent.width
            height: winternet.height-navigationBar.height-iStatusBar.height-iBackButton.height
            anchors.top: navigationBar.bottom

            MouseArea {
                anchors.fill: parent
                drag.target: parent.data
            }

            onLinkHovered: {
                webView.url = hoveredUrl
            }
       }
...

If you have any idea's or experience with this, please help!

like image 270
Evgeny Slastnikov Avatar asked Sep 29 '22 19:09

Evgeny Slastnikov


1 Answers

I wanted to make WebEngineView flickable too. I decided, that it's better to do it using Flickable. But naive approach of making something like:

...
Flickable {
    WebEngineView {...}
}
...

will not work.

Further investigation led me to Qt based Web browser for embedded touch devices. I have tried it out on my PC. It seems like it's doing exactly what I want, but it's too complicated and GPL license renders it useless for any kind of use.

After some experiments I found out that flicking will work if Flickable.contentHeight and Flickable.contentWidth at least match the actual size of Web page being shown by WebEngineView. Those properties of Flickable may have greater values than actual page size have. In this case you'll be able to flick beyond content of page. If Flickable.contentHeight and/or Flickable.contentWidth are less than page size you'll still be able to flick. It's up to you whether you want it this way or not)

So it ended up to acquiring actual page size shown and setting it as Flickable.contentHeight and Flickable.contentWidth. I'll give you a short story here: there is no way to get desired values with WebEngineView API (or at least I didn't find anything in Qt 5.7/5.8 documentation). But I've accidentally found this answer on SO. Using this answer I've managed to make everything work:

...
Item {
    Layout.fillHeight: true
    Layout.fillWidth: true

    Flickable {
        id: flick
        anchors.fill: parent

        WebEngineView {
            anchors.fill: parent
            id: webView
        }
    }

    webView.onLoadingChanged: {
        if (webView.loadProgress == 100) {
            webView.runJavaScript(
                "document.documentElement.scrollHeight;",
                function (i_actualPageHeight) {
                    flick.contentHeight = Math.max (
                        i_actualPageHeight, flick.height);
                })
            webView.runJavaScript(
                "document.documentElement.scrollWidth;",
                function (i_actualPageWidth) {
                    flick.contentWidth = Math.max (
                        i_actualPageWidth, flick.width);
                })
        }
    }
}
...

The code snipped above may need some adjustments but it's nearly a copy of the code I have that works.

UPD 1: I have found out that this is not the final solution, because for some reason after new page is loaded document.documentElement.scrollWidth may not be reset and remain the same it was for previous page.

UPD 2: I've resolved the aforementioned problem, but the solution is a bit ugly: reset Flickable.contentWidth in WebEngineView.onLoadingChanged to Flickable.width. Setting Flickable.contentWidth to 0 will result in inappropriately large height of content after loading.

Another adjustment I've made was removing of requirement for 100% loading state.

UPD 3: A more complete version of the flickable WebEngiveView. User scripts are used instead of directly invoking JavaScript because I encountered some strange errors with the latter that resulted in WebEngineView closing.

// Copyright 2019 Utility Tool Kit Open Source Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License.  You may obtain a copy
// of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
// License for the specific language governing permissions and limitations under
// the License.
//
// Author: Innokentiy Alaytsev <[email protected]>
//
// File name: qml/Utk/Qml/FlickableWebEngineView.qml
//
// Description: The QML FlickableWebEngineView QtQuick 2 component.


import QtQuick 2.7

import QtWebEngine 1.3


Item {
    property alias flickable: flickable;
    property alias webView: webView;

    property bool userDragImgEnabled: true;
    property bool userSelectEnabled: true;


    readonly property string kDisableUserDragCssId:
    "utk_qml_flickable_web_engine_view_disable_user_drag_css";

    readonly property string kDisableUserSelectCssId:
    "utk_qml_flickable_web_engine_view_disable_user_select_css";

    readonly property string kDisableUserDragCss:
    "{                                  \\                    \
        -webkit-user-drag: none;        \\                    \
        -khtml-user-drag: none;         \\                    \
        -moz-user-drag: none;           \\                    \
        -ms-user-drag: none;            \\                    \
        user-drag: none;                \\                    \
    }";

    readonly property string kDisableUserSelectCss:
    "{                                  \\                    \
        -webkit-touch-callout: none;    \\                    \
        -webkit-user-select: none;      \\                    \
        -khtml-user-select: none;       \\                    \
        -moz-user-select: none;         \\                    \
        -ms-user-select: none;          \\                    \
        user-select: none;              \\                    \
    }";


    WebEngineScript {
        id: disableUserDragScript;
        name: kDisableUserDragCssId;
        injectionPoint: WebEngineScript.DocumentReady;
        sourceCode: applyCssJavaScript ("img", kDisableUserDragCss, kDisableUserDragCssId);
        worldId: WebEngineScript.MainWorld;
    }

    WebEngineScript {
        id: disableUserSelectScript;
        name: kDisableUserSelectCssId;
        injectionPoint: WebEngineScript.DocumentReady;
        sourceCode: applyCssJavaScript ("body", kDisableUserSelectCss, kDisableUserSelectCssId);
        worldId: WebEngineScript.MainWorld;
    }


    Flickable {
        id: flickable;
        anchors.fill : parent;

        clip: true;

        WebEngineView {
            id: webView;

            anchors.fill : parent;

            scale: 1;

            onLoadingChanged: {
                if (loadRequest.status !== WebEngineView.LoadSucceededStatus) {
                    return;
                }

                flickable.contentHeight = 0;
                flickable.contentWidth = flickable.width;

                runJavaScript (
                    "document.documentElement.scrollHeight;",
                    function (actualPageHeight) {
                        flickable.contentHeight = Math.max (
                            actualPageHeight, flickable.height);
                    });

                runJavaScript (
                    "document.documentElement.scrollWidth;",
                    function (actualPageWidth) {
                        flickable.contentWidth = Math.max (
                            actualPageWidth, flickable.width);
                    });
            }
        }
    }


    onUserDragImgEnabledChanged: {
        if (userDragImgEnabled &&
            (webView.loadRequest.status === WebEngineView.LoadSucceededStatus)) {
            runJavaScript (revertCssJavaScript (kDisableUserDragCssId));
        }
        else {
            webView.userScripts = currentUserScripts ();
        }
    }


    onUserSelectEnabledChanged: {
        if (userSelectEnabled &&
            (webView.loadRequest.status === WebEngineView.LoadSucceededStatus)) {
            runJavaScript (revertCssJavaScript (kDisableUserSelectCssId));
        }
        else {
            webView.userScripts = currentUserScripts ();
        }
    }


    function currentUserScripts () {
        var userScriptsToSkip = [
            disableUserDragScript.name,
            disableUserSelectScript.name
        ];

        var updatedUserScripts = [];

        for (var i in webView.userScripts) {
            var script = webView.userScripts[ i ];

            if (-1 == userScriptsToSkip.indexOf (script.name)) {
                updatedUserScripts.push (script);
            }
        }

        if (!userDragImgEnabled) {
            updatedUserScripts.push (disableUserDragScript);
        }

        if (!userSelectEnabled) {
            updatedUserScripts.push (disableUserSelectScript);
        }

        return updatedUserScripts;
    }


    function applyCssJavaScript (selector, css, cssId) {
        var applyCssJavaScript =
            "(function () {                                               \
                cssElement = document.createElement ('style');            \
                                                                          \
                head = document.head ||                                   \
                    document.getElementsByTagName ('head')[ 0 ];          \
                                                                          \
                head.appendChild (cssElement);                            \
                                                                          \
                cssElement.type = 'text/css';                             \
                cssElement.id = '%1';                                     \
                                                                          \
                if (cssElement.styleSheet)                                \
                {                                                         \
                    cssElement.styleSheet.cssText = '%2 %3';              \
                }                                                         \
                else                                                      \
                {                                                         \
                    cssElement.appendChild (                              \
                        document.createTextNode ('%2 %3'));               \
                }                                                         \
            })();";

        return applyCssJavaScript
            .arg (cssId)
            .arg (selector)
            .arg (css);
    }


    function revertCssJavaScript (cssId) {
        var revertCssJavaScript =
            "(function () {                                               \
                 var element = document.getElementById('%1');             \
                                                                          \
                 if (element) {                                           \
                     element.outerHTML = '';                              \
                                                                          \
                     delete element;                                      \
                 }                                                        \
            })()";

        return revertCssJavaScript.arg (cssId);
    }
}

like image 150
Innokentiy Alaytsev Avatar answered Oct 01 '22 07:10

Innokentiy Alaytsev