5
votes

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!

1
I guess all mouse/touchpad actions are managed by WebEngineView itself so it's most likely impossible. May be it makes sense if you can disable WebEngineView's scrollbars and mouse interactions and put it inside Flickablefolibis
I tried to do it also that way.. But didn't found how to set the width of WebEngineView equal to the width of the page that it contains.Evgeny Slastnikov
I have the same question. Did you ever find an answer?feedbackloop
I cannot find it in Chromium API chromium.org/developers/design-documents/aura/…. Am I using a wrong documentation?user1415536
It seems, Qt is not about to fix this issue in near time. Creating bug-reports will not help at all. They are simply ignoring them. This canbe really seen from the majority of non-fixed bugs with priority 2 or higher or promises to fix bug in version 5.6, but not actually fixing them. I am wondering what is the point to release a new version of Qt, if bugs of the old one are not fixed... It is a rhetorical question...user1415536

1 Answers

4
votes

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);
    }
}