Nano Browser Example¶

A web browser implemented using the WebEngineView QML type.

Nano Browser Screenshot

Download this example

# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations

"""PySide6 WebEngine QtQuick 2 Example"""

import sys
from argparse import ArgumentParser, RawTextHelpFormatter
from pathlib import Path

from PySide6.QtCore import (QCoreApplication, QFileInfo, QObject,
                            QUrl, Slot)
from PySide6.QtQml import QQmlApplicationEngine, QmlElement, QmlSingleton
from PySide6.QtGui import QGuiApplication
from PySide6.QtWebEngineQuick import QtWebEngineQuick

import rc_resources  # noqa: F401


# To be used on the @QmlElement decorator
# (QML_IMPORT_MINOR_VERSION is optional)
QML_IMPORT_NAME = "BrowserUtils"
QML_IMPORT_MAJOR_VERSION = 1


def url_from_user_input(user_input):
    file_info = QFileInfo(user_input)
    if file_info.exists():
        return QUrl.fromLocalFile(file_info.absoluteFilePath())
    return QUrl.fromUserInput(user_input)


@QmlElement
@QmlSingleton
class Utils(QObject):

    @Slot(str, result=QUrl)
    def fromUserInput(self, user_input):
        return url_from_user_input(user_input)


if __name__ == '__main__':
    QCoreApplication.setApplicationName("Quick Nano Browser")
    QCoreApplication.setOrganizationName("QtProject")

    QtWebEngineQuick.initialize()

    argument_parser = ArgumentParser(description="Quick Nano Browser",
                                     formatter_class=RawTextHelpFormatter)
    argument_parser.add_argument("--single-process", "-s", action="store_true",
                                 help="Run in single process mode (trouble shooting)")
    argument_parser.add_argument("url", help="The URL to open",
                                 nargs='?', type=str)
    options = argument_parser.parse_args()

    url = url_from_user_input(options.url) if options.url else QUrl("chrome://qt")

    app_args = sys.argv
    if options.single_process:
        app_args.extend(["--webEngineArgs", "--single-process"])
    app = QGuiApplication(app_args)
    engine = QQmlApplicationEngine()
    engine.setInitialProperties({"startupUrl": url})
    engine.addImportPath(Path(__file__).parent)
    engine.loadFromModule("BrowserUtils", "ApplicationRoot")
    if not engine.rootObjects():
        sys.exit(-1)

    exit_code = app.exec()
    del engine
    sys.exit(exit_code)
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

pragma ComponentBehavior: Bound

import QtQuick
import BrowserUtils

QtObject {
    id: root
    required property string startupUrl
    Component.onCompleted: BrowserManager.load(startupUrl)
}
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import QtQuick
import QtQuick.Window
import QtWebEngine

Window {
    id: window
    property alias currentWebView: webView
    flags: Qt.Dialog
    width: 800
    height: 600
    visible: true
    onClosing: destroy()
    WebEngineView {
        id: webView
        anchors.fill: parent

        onGeometryChangeRequested: function(geometry) {
            window.x = geometry.x
            window.y = geometry.y
            window.width = geometry.width
            window.height = geometry.height
        }
    }
}
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

pragma Singleton

import QtQuick
import QtWebEngine

QtObject {
    id: manager

    property WebEngineProfilePrototype defaultProfilePrototype : WebEngineProfilePrototype {
        storageName: "Profile"
    }

    property WebEngineProfilePrototype otrPrototype : WebEngineProfilePrototype {}

    function createWindow(profile) {
        let browserWindowComponent = Qt.createComponent("BrowserUtils", "BrowserWindow");
        let newWindow = browserWindowComponent.createObject(manager) as BrowserWindow;
        newWindow.currentWebView.profile = profile;
        profile.downloadRequested.connect(newWindow.onDownloadRequested);
        return newWindow;
    }
    function createDialog(profile) {
        let browserDialogComponent = Qt.createComponent("BrowserUtils", "BrowserDialog");
        let newDialog = browserDialogComponent.createObject(manager) as BrowserDialog;
        newDialog.closing.connect(function(){destroy()})
        newDialog.currentWebView.profile = profile;
        return newDialog;
    }
    function load(url) {
        let browserWindow = createWindow(manager.defaultProfilePrototype.instance());
        browserWindow.currentWebView.url = url;
    }

    Component.onCompleted: {
        let fullVersionList = manager.defaultProfilePrototype.instance().clientHints.fullVersionList;
        fullVersionList["QuickNanoBrowser"] = "1.0";
        manager.defaultProfilePrototype.instance().clientHints.fullVersionList = fullVersionList;
    }
}
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

pragma ComponentBehavior: Bound

import QtCore
import QtQml
import QtQuick
import QtQuick.Controls.Fusion
import QtQuick.Dialogs
import QtQuick.Layouts
import QtQuick.Window
import QtWebEngine
import BrowserUtils

ApplicationWindow {
    id: win
    property WebEngineView currentWebView: tabBar.currentIndex < tabBar.count ? tabLayout.children[tabBar.currentIndex] : null
    property int previousVisibility: Window.Windowed
    property bool lastTabClosing: false

    width: 1300
    height: 900
    visible: true
    title: win.currentWebView?.title ?? ""

    // Make sure the Qt.WindowFullscreenButtonHint is set on OS X.
    Component.onCompleted: flags = flags | Qt.WindowFullscreenButtonHint

    onCurrentWebViewChanged: {
        findBar.reset();
    }

    // When using style "mac", ToolButtons are not supposed to accept focus.
    property bool platformIsMac: Qt.platform.os === "osx"

    Settings {
        id : appSettings
        property alias autoLoadImages: loadImages.checked
        property alias javaScriptEnabled: javaScriptEnabledMenuItem.checked
        property alias errorPageEnabled: errorPageEnabledMenuItem.checked
        property alias pluginsEnabled: pluginsEnabledMenuItem.checked
        property alias fullScreenSupportEnabled: fullScreenSupportEnabledMenuItem.checked
        property alias autoLoadIconsForPage: autoLoadIconsForPageMenuItem.checked
        property alias touchIconsEnabled: touchIconsEnabledMenuItem.checked
        property alias webRTCPublicInterfacesOnly : webRTCPublicInterfacesOnlyMenuItem.checked
        property alias devToolsEnabled: devToolsEnabledMenuItem.checked
        property alias pdfViewerEnabled: pdfViewerEnabledMenuItem.checked
        property int imageAnimationPolicy: WebEngineSettings.ImageAnimationPolicy.Allow
        property alias javascriptCanAccessClipboard: javascriptCanAccessClipboardMenuItem.checked
        property alias javascriptCanPaste: javascriptCanPasteMenuItem.checked
    }

    Action {
        shortcut: "Ctrl+D"
        onTriggered: {
            downloadView.visible = !downloadView.visible;
        }
    }
    Action {
        id: focusAction
        shortcut: "Ctrl+L"
        onTriggered: {
            addressBar.forceActiveFocus();
            addressBar.selectAll();
        }
    }
    Action {
        shortcut: StandardKey.Refresh
        onTriggered: {
            if (win.currentWebView)
                win.currentWebView.reload();
        }
    }
    Action {
        shortcut: StandardKey.AddTab
        onTriggered: {
            tabBar.createTab(tabBar.count !== 0
                             ? win.currentWebView.profile
                             : BrowserManager.defaultProfilePrototype.instance());
            addressBar.forceActiveFocus();
            addressBar.selectAll();
        }
    }
    Action {
        shortcut: StandardKey.Close
        onTriggered: {
            win.currentWebView.triggerWebAction(WebEngineView.RequestClose);
        }
    }
    Action {
        shortcut: StandardKey.Quit
        onTriggered: win.close()
    }
    Action {
        shortcut: "Escape"
        onTriggered: {
            if (win.currentWebView.state === "FullScreen") {
                win.visibility = win.previousVisibility;
                fullScreenNotification.hide();
                win.currentWebView.triggerWebAction(WebEngineView.ExitFullScreen);
            }

            if (findBar.visible)
                findBar.visible = false;
        }
    }
    Action {
        shortcut: "Ctrl+0"
        onTriggered: win.currentWebView.zoomFactor = 1.0
    }
    Action {
        shortcut: StandardKey.ZoomOut
        onTriggered: win.currentWebView.zoomFactor -= 0.1
    }
    Action {
        shortcut: StandardKey.ZoomIn
        onTriggered: win.currentWebView.zoomFactor += 0.1
    }

    Action {
        shortcut: StandardKey.Copy
        onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Copy)
    }
    Action {
        shortcut: StandardKey.Cut
        onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Cut)
    }
    Action {
        shortcut: StandardKey.Paste
        onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Paste)
    }
    Action {
        shortcut: "Shift+"+StandardKey.Paste
        onTriggered: win.currentWebView.triggerWebAction(WebEngineView.PasteAndMatchStyle)
    }
    Action {
        shortcut: StandardKey.SelectAll
        onTriggered: win.currentWebView.triggerWebAction(WebEngineView.SelectAll)
    }
    Action {
        shortcut: StandardKey.Undo
        onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Undo)
    }
    Action {
        shortcut: StandardKey.Redo
        onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Redo)
    }
    Action {
        shortcut: StandardKey.Back
        onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Back)
    }
    Action {
        shortcut: StandardKey.Forward
        onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Forward)
    }
    Action {
        shortcut: StandardKey.Find
        onTriggered: {
            if (!findBar.visible)
                findBar.visible = true;
        }
    }
    Action {
        shortcut: StandardKey.FindNext
        onTriggered: findBar.findNext()
    }
    Action {
        shortcut: StandardKey.FindPrevious
        onTriggered: findBar.findPrevious()
    }

    menuBar: ToolBar {
        id: navigationBar
        RowLayout {
            anchors.fill: parent
            ToolButton {
                enabled: win.currentWebView?.canGoBack || win.currentWebView?.canGoForward
                onClicked: historyMenu.open()
                text: qsTr("â–Ľ")
                Menu {
                    id: historyMenu
                    Instantiator {
                        model: win.currentWebView?.history?.items
                        MenuItem {
                            required property var model
                            text: model.title
                            onTriggered: win.currentWebView.goBackOrForward(model.offset)
                            checkable: !enabled
                            checked: !enabled
                            enabled: model.offset
                        }

                        onObjectAdded: function(index, object) {
                            historyMenu.insertItem(index, object)
                        }
                        onObjectRemoved: function(index, object) {
                            historyMenu.removeItem(object)
                        }
                    }
                }
            }

            ToolButton {
                id: backButton
                icon.source: "qrc:///icons/3rdparty/go-previous.png"
                onClicked: win.currentWebView.goBack()
                enabled: win.currentWebView?.canGoBack ?? false
                activeFocusOnTab: !win.platformIsMac
            }
            ToolButton {
                id: forwardButton
                icon.source: "qrc:///icons/3rdparty/go-next.png"
                onClicked: win.currentWebView.goForward()
                enabled: win.currentWebView?.canGoForward ?? false
                activeFocusOnTab: !win.platformIsMac
            }
            ToolButton {
                id: reloadButton
                icon.source: win.currentWebView?.loading
                             ? "qrc:///icons/3rdparty/process-stop.png"
                             : "qrc:///icons/3rdparty/view-refresh.png"
                onClicked: win.currentWebView?.loading ? win.currentWebView.stop() : win.currentWebView.reload()
                activeFocusOnTab: !win.platformIsMac
            }
            TextField {
                id: addressBar
                Image {
                    anchors.verticalCenter: addressBar.verticalCenter;
                    x: 5
                    z: 2
                    id: faviconImage
                    width: 16; height: 16
                    sourceSize: Qt.size(width, height)
                    source: win.currentWebView?.icon ? win.currentWebView.icon : ''
                }
                MouseArea {
                    id: textFieldMouseArea
                    acceptedButtons: Qt.RightButton
                    anchors.fill: parent
                    onClicked: {
                        var textSelectionStartPos = addressBar.selectionStart;
                        var textSelectionEndPos = addressBar.selectionEnd;
                        textFieldContextMenu.open();
                        addressBar.select(textSelectionStartPos, textSelectionEndPos);
                    }
                    Menu {
                        id: textFieldContextMenu
                        x: textFieldMouseArea.mouseX
                        y: textFieldMouseArea.mouseY
                        MenuItem {
                            text: qsTr("Cut")
                            onTriggered: addressBar.cut()
                            enabled: addressBar.selectedText.length > 0
                        }
                        MenuItem {
                            text: qsTr("Copy")
                            onTriggered: addressBar.copy()
                            enabled: addressBar.selectedText.length > 0
                        }
                        MenuItem {
                            text: qsTr("Paste")
                            onTriggered: addressBar.paste()
                            enabled: addressBar.canPaste
                        }
                        MenuItem {
                            text: qsTr("Delete")
                            onTriggered: addressBar.text = qsTr("")
                            enabled: addressBar.selectedText.length > 0
                        }
                        MenuSeparator {}
                        MenuItem {
                            text: qsTr("Select All")
                            onTriggered: addressBar.selectAll()
                            enabled: addressBar.text.length > 0
                        }
                    }
                }
                leftPadding: 26
                focus: true
                Layout.fillWidth: true
                Binding on text {
                    when: win.currentWebView
                    value: win.currentWebView.url
                }
                onAccepted: win.currentWebView.url = Utils.fromUserInput(text)
                selectByMouse: true
            }
            ToolButton {
                id: settingsMenuButton
                text: qsTr("â‹®")
                onClicked: settingsMenu.open()
                Menu {
                    id: settingsMenu
                    y: settingsMenuButton.height
                    MenuItem {
                        id: loadImages
                        text: "Autoload images"
                        checkable: true
                        checked: WebEngine.settings.autoLoadImages
                    }
                    MenuItem {
                        id: javaScriptEnabledMenuItem
                        text: "JavaScript On"
                        checkable: true
                        checked: WebEngine.settings.javascriptEnabled
                    }
                    MenuItem {
                        id: errorPageEnabledMenuItem
                        text: "ErrorPage On"
                        checkable: true
                        checked: WebEngine.settings.errorPageEnabled
                    }
                    MenuItem {
                        id: pluginsEnabledMenuItem
                        text: "Plugins On"
                        checkable: true
                        checked: true
                    }
                    MenuItem {
                        id: fullScreenSupportEnabledMenuItem
                        text: "FullScreen On"
                        checkable: true
                        checked: WebEngine.settings.fullScreenSupportEnabled
                    }
                    MenuItem {
                        id: offTheRecordEnabled
                        text: "Off The Record"
                        checkable: true
                        checked: win.currentWebView?.profile === BrowserManager.otrPrototype.instance()
                        onToggled: function() {
                            if (win.currentWebView) {
                                win.currentWebView.profile = offTheRecordEnabled.checked
                                        ? BrowserManager.otrPrototype.instance()
                                        : BrowserManager.defaultProfilePrototype.instance();
                            }
                        }
                    }
                    MenuItem {
                        id: httpDiskCacheEnabled
                        text: "HTTP Disk Cache"
                        checkable: !win.currentWebView?.profile?.offTheRecord ?? false
                        checked: win.currentWebView?.profile.httpCacheType === WebEngineProfile.DiskHttpCache
                        onToggled: function() {
                            if (win.currentWebView) {
                                win.currentWebView.profile.httpCacheType = httpDiskCacheEnabled.checked
                                        ? WebEngineProfile.DiskHttpCache
                                        : WebEngineProfile.MemoryHttpCache;
                            }
                        }
                    }
                    MenuItem {
                        id: autoLoadIconsForPageMenuItem
                        text: "Icons On"
                        checkable: true
                        checked: WebEngine.settings.autoLoadIconsForPage
                    }
                    MenuItem {
                        id: touchIconsEnabledMenuItem
                        text: "Touch Icons On"
                        checkable: true
                        checked: WebEngine.settings.touchIconsEnabled
                        enabled: autoLoadIconsForPageMenuItem.checked
                    }
                    MenuItem {
                        id: webRTCPublicInterfacesOnlyMenuItem
                        text: "WebRTC Public Interfaces Only"
                        checkable: true
                        checked: WebEngine.settings.webRTCPublicInterfacesOnly
                    }
                    MenuItem {
                        id: devToolsEnabledMenuItem
                        text: "Open DevTools"
                        checkable: true
                        checked: false
                    }
                    MenuItem {
                        id: pdfViewerEnabledMenuItem
                        text: "PDF Viewer Enabled"
                        checkable: true
                        checked: WebEngine.settings.pdfViewerEnabled
                    }
                    Menu {
                        id: imageAnimationPolicyMenu
                        title: "Image Animation Policy"

                        MenuItem {
                            id: disableImageAnimation
                            text: "Disable All Image Animation"
                            checkable: true
                            autoExclusive: true
                            checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.ImageAnimationPolicy.Disallow
                            onTriggered: {
                                appSettings.imageAnimationPolicy = WebEngineSettings.ImageAnimationPolicy.Disallow
                            }
                        }

                        MenuItem {
                            id: allowImageAnimation
                            text: "Allow All Animated Images"
                            checkable: true
                            autoExclusive: true
                            checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.ImageAnimationPolicy.Allow
                            onTriggered : {
                                appSettings.imageAnimationPolicy = WebEngineSettings.ImageAnimationPolicy.Allow
                            }
                        }

                        MenuItem {
                            id: animateImageOnce
                            text: "Animate Image Once"
                            checkable: true
                            autoExclusive: true
                            checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.ImageAnimationPolicy.AnimateOnce
                            onTriggered : {
                                appSettings.imageAnimationPolicy = WebEngineSettings.ImageAnimationPolicy.AnimateOnce
                            }
                        }
                    }

                    MenuItem {
                        id: javascriptCanAccessClipboardMenuItem
                        text: "JavaScript can access clipboard"
                        checkable: true
                        checked: WebEngine.settings.javascriptCanAccessClipboard
                    }
                    MenuItem {
                        id: javascriptCanPasteMenuItem
                        text: "JavaScript can paste"
                        checkable: true
                        checked: WebEngine.settings.javascriptCanPaste
                    }
                }
            }
        }
        ProgressBar {
            id: progressBar
            height: 3
            anchors {
                left: parent.left
                top: parent.bottom
                right: parent.right
                leftMargin: parent.anchors.leftMargin
                rightMargin: parent.anchors.rightMargin
            }
            background: Item {}
            z: -2
            from: 0
            to: 100
            value: (win.currentWebView?.loadProgress < 100) ? win.currentWebView.loadProgress : 0
        }
    }

    StackLayout {
        id: tabLayout
        currentIndex: tabBar.currentIndex

        anchors.top: tabBar.bottom
        anchors.bottom: devToolsWebEngineView.top
        anchors.left: parent.left
        anchors.right: parent.right
    }

    Component {
        id: tabButtonComponent

        TabButton {
            id: tabButton
            property color frameColor: "#999999"
            property color fillColor: "#eeeeee"
            property color nonSelectedColor: "#dddddd"
            property string tabTitle: "New Tab"

            contentItem: Rectangle {
                id: tabRectangle
                color: tabButton.down ? tabButton.fillColor : tabButton.nonSelectedColor
                border.width: 1
                border.color: tabButton.frameColor
                implicitWidth: Math.max(tabText.width + 30, 80)
                implicitHeight: Math.max(tabText.height + 10, 20)
                Rectangle { height: 1 ; width: parent.width ; color: tabButton.frameColor}
                Rectangle { height: parent.height ; width: 1; color: tabButton.frameColor}
                Rectangle { x: parent.width - 2; height: parent.height ; width: 1; color: tabButton.frameColor}
                Text {
                    id: tabText
                    anchors.left: parent.left
                    anchors.verticalCenter: parent.verticalCenter
                    anchors.leftMargin: 6
                    text: tabButton.tabTitle
                    elide: Text.ElideRight
                    color: tabButton.down ? "black" : tabButton.frameColor
                    width: parent.width - button.background.width
                }
                Button {
                    id: button
                    anchors.right: parent.right
                    anchors.verticalCenter: parent.verticalCenter
                    anchors.rightMargin: 4
                    height: 12
                    background: Rectangle {
                        implicitWidth: 12
                        implicitHeight: 12
                        color: button.hovered ? "#cccccc" : tabRectangle.color
                        Text {text: "x"; anchors.centerIn: parent; color: "gray"}
                    }
                    onClicked: tabButton.closeTab()
                }
            }

            onClicked: addressBar.text = (tabLayout.itemAt(TabBar.index) as WebEngineView).url;
            function closeTab() {
                tabBar.tryCloseView(TabBar.index);
            }
        }
    }

    TabBar {
        id: tabBar
        anchors.top: parent.top
        anchors.left: parent.left
        anchors.right: parent.right
        Component.onCompleted: createTab(BrowserManager.defaultProfilePrototype.instance())

        function createTab(profile, focusOnNewTab = true, url = undefined) {
            var webview = tabComponent.createObject(tabLayout, {profile: profile});
            var newTabButton = tabButtonComponent.createObject(tabBar, {tabTitle: Qt.binding(function () { return webview.title; })});
            webview.index = Qt.binding(function () { return newTabButton.TabBar.index; })
            tabBar.addItem(newTabButton);
            if (focusOnNewTab) {
                tabBar.setCurrentIndex(tabBar.count - 1);
            }
            if (url !== undefined) {
                webview.url = url;
            }
            return webview;
        }

        function tryCloseView(index) {
            tabLayout.children[index].triggerWebAction(WebEngineView.RequestClose);
        }

        function removeView(index) {
            if (tabBar.count > 1) {
                tabBar.removeItem(tabBar.itemAt(index));
                tabLayout.children[index].destroy();
            } else {
                win.lastTabClosing = true;
                win.close();
            }
        }

        Component {
            id: tabComponent
            WebEngineView {
                id: webEngineView
                property int index: 0
                focus: true

                onLinkHovered: function(hoveredUrl) {
                    if (hoveredUrl === "")
                        hideStatusText.start();
                    else {
                        statusText.text = hoveredUrl;
                        statusBubble.visible = true;
                        hideStatusText.stop();
                    }
                }

                states: [
                    State {
                        name: "FullScreen"
                        PropertyChanges {
                            tabBar.visible: false
                            tabBar.height: 0
                            navigationBar.visible: false
                        }
                    }
                ]
                settings.localContentCanAccessRemoteUrls: true
                settings.localContentCanAccessFileUrls: false
                settings.autoLoadImages: appSettings.autoLoadImages
                settings.javascriptEnabled: appSettings.javaScriptEnabled
                settings.errorPageEnabled: appSettings.errorPageEnabled
                settings.pluginsEnabled: appSettings.pluginsEnabled
                settings.fullScreenSupportEnabled: appSettings.fullScreenSupportEnabled
                settings.autoLoadIconsForPage: appSettings.autoLoadIconsForPage
                settings.touchIconsEnabled: appSettings.touchIconsEnabled
                settings.webRTCPublicInterfacesOnly: appSettings.webRTCPublicInterfacesOnly
                settings.pdfViewerEnabled: appSettings.pdfViewerEnabled
                settings.imageAnimationPolicy: appSettings.imageAnimationPolicy
                settings.screenCaptureEnabled: true
                settings.javascriptCanAccessClipboard: appSettings.javascriptCanAccessClipboard
                settings.javascriptCanPaste: appSettings.javascriptCanPaste

                onWindowCloseRequested: function() {
                    tabBar.removeView(webEngineView.index);
                }

                onCertificateError: function(error) {
                    if (!error.isMainFrame) {
                        error.rejectCertificate();
                        return;
                    }

                    error.defer();
                    sslDialog.enqueue(error);
                }

                onNewWindowRequested: function(request) {
                    if (!request.userInitiated)
                        console.warn("Blocked a popup window.");
                    else if (request.destination === WebEngineNewWindowRequest.InNewTab) {
                        let tab = tabBar.createTab(win.currentWebView.profile, true, request.requestedUrl);
                        tab.acceptAsNewWindow(request);
                    } else if (request.destination === WebEngineNewWindowRequest.InNewBackgroundTab) {
                        let backgroundTab = tabBar.createTab(win.currentWebView.profile, false);
                        backgroundTab.acceptAsNewWindow(request);
                    } else if (request.destination === WebEngineNewWindowRequest.InNewDialog) {
                        let dialog = BrowserManager.createDialog(win.currentWebView.profile);
                        dialog.currentWebView.acceptAsNewWindow(request);
                    } else {
                        let window = BrowserManager.createWindow(win.currentWebView.profile);
                        window.currentWebView.acceptAsNewWindow(request);
                    }
                }

                onFullScreenRequested: function(request) {
                    if (request.toggleOn) {
                        webEngineView.state = "FullScreen";
                        win.previousVisibility = win.visibility;
                        win.showFullScreen();
                        fullScreenNotification.show();
                    } else {
                        webEngineView.state = "";
                        win.visibility = win.previousVisibility;
                        fullScreenNotification.hide();
                    }
                    request.accept();
                }

                onRegisterProtocolHandlerRequested: function(request) {
                    console.log("accepting registerProtocolHandler request for "
                                + request.scheme + " from " + request.origin);
                    request.accept();
                }

                onDesktopMediaRequested: function(request) {
                    // select the primary screen
                    request.selectScreen(request.screensModel.index(0, 0));
                }

                onRenderProcessTerminated: function(terminationStatus, exitCode) {
                    var status = "";
                    switch (terminationStatus) {
                    case WebEngineView.NormalTerminationStatus:
                        status = "(normal exit)";
                        break;
                    case WebEngineView.AbnormalTerminationStatus:
                        status = "(abnormal exit)";
                        break;
                    case WebEngineView.CrashedTerminationStatus:
                        status = "(crashed)";
                        break;
                    case WebEngineView.KilledTerminationStatus:
                        status = "(killed)";
                        break;
                    }

                    print("Render process exited with code " + exitCode + " " + status);
                    Qt.callLater(function() { win.currentWebView.reload() })
                }

                onSelectClientCertificate: function(selection) {
                    selection.certificates[0].select();
                }

                onFindTextFinished: function(result) {
                    if (!findBar.visible)
                        findBar.visible = true;

                    findBar.numberOfMatches = result.numberOfMatches;
                    findBar.activeMatch = result.activeMatch;
                }

                onLoadingChanged: function(loadRequest) {
                    if (loadRequest.status === WebEngineView.LoadStartedStatus)
                        findBar.reset();
                }

                onPermissionRequested: function(permission) {
                    permissionDialog.permission = permission;
                    permissionDialog.visible = true;
                }
                onWebAuthUxRequested: function(request) {
                    webAuthDialog.init(request);
                }
            }
        }
    }
    WebEngineView {
        id: devToolsWebEngineView
        visible: devToolsEnabledMenuItem.checked
        height: visible ? 400 : 0
        inspectedView: visible && tabBar.currentIndex < tabBar.count ? tabLayout.children[tabBar.currentIndex] : null
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.bottom: parent.bottom
        onNewWindowRequested: function(request) {
            var tab = tabBar.createTab(win.currentWebView.profile);
            request.openIn(tab);
        }

        onWindowCloseRequested: function() {
            // Delay hiding for keep the inspectedView set to receive the ACK message of close.
            Qt.callLater(function() { devToolsEnabledMenuItem.checked = false })
        }
    }
    Dialog {
        id: sslDialog
        anchors.centerIn: parent
        contentWidth: Math.max(mainTextForSSLDialog.width, detailedTextForSSLDialog.width)
        contentHeight: mainTextForSSLDialog.height + detailedTextForSSLDialog.height
        property var certErrors: []
        // fixme: icon!
        // icon: StandardIcon.Warning
        standardButtons: Dialog.No | Dialog.Yes
        title: "Server's certificate not trusted"
        contentItem: Item {
            Label {
                id: mainTextForSSLDialog
                text: "Do you wish to continue?"
            }
            Text {
                id: detailedTextForSSLDialog
                anchors.top: mainTextForSSLDialog.bottom
                text: "If you wish so, you may continue with an unverified certificate.\n" +
                      "Accepting an unverified certificate means\n" +
                      "you may not be connected with the host you tried to connect to.\n" +
                      "Do you wish to override the security check and continue?"
            }
        }

        onAccepted: {
            certErrors.shift().acceptCertificate();
            presentError();
        }
        onRejected: reject()

        function reject(){
            certErrors.shift().rejectCertificate();
            presentError();
        }
        function enqueue(error){
            certErrors.push(error);
            presentError();
        }
        function presentError(){
            visible = certErrors.length > 0
        }
    }
    Dialog {
        id: permissionDialog
        anchors.centerIn: parent
        width: Math.min(win.width, win.height) / 3 * 2
        contentWidth: mainTextForPermissionDialog.width
        contentHeight: mainTextForPermissionDialog.height
        standardButtons: Dialog.No | Dialog.Yes
        title: "Permission Request"

        property var permission;

        contentItem: Item {
            Label {
                id: mainTextForPermissionDialog
            }
        }

        onAccepted: permission.grant()
        onRejected: permission.deny()
        onVisibleChanged: {
            if (visible) {
                mainTextForPermissionDialog.text = questionForPermissionType();
                width = contentWidth + 20;
            }
        }

        function questionForPermissionType() {
            var question = "Allow " + permission.origin + " to "

            switch (permission.permissionType) {
            case WebEnginePermission.PermissionType.Geolocation:
                question += "access your location information?";
                break;
            case WebEnginePermission.PermissionType.MediaAudioCapture:
                question += "access your microphone?";
                break;
            case WebEnginePermission.PermissionType.MediaVideoCapture:
                question += "access your webcam?";
                break;
            case WebEnginePermission.PermissionType.MediaAudioVideoCapture:
                question += "access your microphone and webcam?";
                break;
            case WebEnginePermission.PermissionType.MouseLock:
                question += "lock your mouse cursor?";
                break;
            case WebEnginePermission.PermissionType.DesktopVideoCapture:
                question += "capture video of your desktop?";
                break;
            case WebEnginePermission.PermissionType.DesktopAudioVideoCapture:
                question += "capture audio and video of your desktop?";
                break;
            case WebEnginePermission.PermissionType.Notifications:
                question += "show notification on your desktop?";
                break;
            case WebEnginePermission.PermissionType.ClipboardReadWrite:
                question += "read from and write to your clipboard?";
                break;
            case WebEnginePermission.PermissionType.LocalFontsAccess:
                question += "access the fonts stored on your machine?";
                break;
            default:
                question += "access unknown or unsupported permission type [" + permission.permissionType + "] ?";
                break;
            }

            return question;
        }
    }

    FullScreenNotification {
        id: fullScreenNotification
    }

    DownloadView {
        id: downloadView
        visible: false
        anchors.fill: parent
    }

    WebAuthDialog {
        id: webAuthDialog
        visible: false
        width: Math.min(win.width, win.height) / 3 * 2
    }

    MessageDialog {
        id: downloadAcceptDialog
        property var downloadRequest: downloadView.pendingDownloadRequest
        title: "Download requested"
        text: downloadRequest ? downloadRequest.suggestedFileName : ""
        buttons: Dialog.No | Dialog.Yes
        onAccepted: {
            downloadView.visible = true;
            downloadView.append(downloadRequest);
            downloadRequest.accept();
        }
        onRejected: {
            downloadRequest.cancel();
        }
        onButtonClicked: {
            visible = false;
        }
        visible: false
    }

    function onDownloadRequested(download) {
        downloadView.pendingDownloadRequest = download;
        downloadAcceptDialog.visible = true;
    }

    FindBar {
        id: findBar
        visible: false
        anchors.right: parent.right
        anchors.rightMargin: 10
        anchors.top: parent.top

        onFindNext: {
            if (text)
                win.currentWebView?.findText(text);
            else if (!visible)
                visible = true;
        }
        onFindPrevious: {
            if (text)
                win.currentWebView?.findText(text, WebEngineView.FindBackward);
            else if (!visible)
                visible = true;
        }
    }


    Rectangle {
        id: statusBubble
        color: "oldlace"
        property int padding: 8
        visible: false

        anchors.left: parent.left
        anchors.bottom: parent.bottom
        width: statusText.paintedWidth + padding
        height: statusText.paintedHeight + padding

        Text {
            id: statusText
            anchors.centerIn: statusBubble
            elide: Qt.ElideMiddle

            Timer {
                id: hideStatusText
                interval: 750
                onTriggered: {
                    statusText.text = "";
                    statusBubble.visible = false;
                }
            }
        }
    }

    onClosing: function(closeEvent) {
       if (lastTabClosing) {
           return;
       }
       closeEvent.accepted = false
       for (let i = 0; i < tabBar.count; i++)  {
           tabBar.tryCloseView(i);
       }
    }
}
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

pragma ComponentBehavior: Bound

import QtQuick
import QtQuick.Controls.Fusion

Rectangle {
    id: downloadView
    color: "lightgray"
    property var pendingDownloadRequest: null

    ListModel {
        id: downloadModel
        property var downloads: []
    }

    function append(download) {
        downloadModel.append(download);
        downloadModel.downloads.push(download);
    }

    Component {
        id: downloadItemDelegate

        Rectangle {
            id: downloadItem
            width: listView.width
            height: childrenRect.height
            anchors.margins: 10
            radius: 3
            color: "transparent"
            border.color: "black"

            required property int index

            Rectangle {
                id: progressBar

                property real progress: {
                    let d = downloadModel.downloads[downloadItem.index]
                    return d ? d.receivedBytes / d.totalBytes : 0
                }

                radius: 3
                color: width === listView.width ? "green" : "#2b74c7"
                width: listView.width * progress
                height: cancelButton.height

                Behavior on width {
                    SmoothedAnimation { duration: 100 }
                }
            }
            Rectangle {
                anchors {
                    left: parent.left
                    right: parent.right
                    leftMargin: 20
                }
                Label {
                    id: label
                    text: {
                        let d = downloadModel.downloads[downloadItem.index]
                        return d ? d.downloadDirectory + "/" + d.downloadFileName : qsTr("")
                    }
                    anchors {
                        verticalCenter: cancelButton.verticalCenter
                        left: parent.left
                        right: cancelButton.left
                    }
                }
                Button {
                    id: cancelButton
                    anchors.right: parent.right
                    icon.source: "qrc:///icons/3rdparty/process-stop.png"
                    onClicked: {
                        var download = downloadModel.downloads[downloadItem.index];

                        download.cancel();

                        downloadModel.downloads = downloadModel.downloads.filter(function (el) {
                            return el.id !== download.id;
                        });
                        downloadModel.remove(downloadItem.index);
                    }
                }
            }
        }

    }
    ListView {
        id: listView
        anchors {
            topMargin: 10
            top: parent.top
            bottom: parent.bottom
            horizontalCenter: parent.horizontalCenter
        }
        width: parent.width - 20
        spacing: 5

        model: downloadModel
        delegate: downloadItemDelegate

        Text {
            visible: !listView.count
            horizontalAlignment: Text.AlignHCenter
            height: 30
            anchors {
                top: parent.top
                left: parent.left
                right: parent.right
            }
            font.pixelSize: 20
            text: "No active downloads."
        }

        Rectangle {
            color: "gray"
            anchors {
                bottom: parent.bottom
                left: parent.left
                right: parent.right
            }
            height: 30
            Button {
                id: okButton
                text: "OK"
                anchors.centerIn: parent
                onClicked: {
                    downloadView.visible = false;
                }
            }
        }
    }
}
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import QtQuick
import QtQuick.Controls.Fusion
import QtQuick.Layouts

Rectangle {
    id: root

    property int numberOfMatches: 0
    property int activeMatch: 0
    property alias text: findTextField.text

    function reset() {
        numberOfMatches = 0;
        activeMatch = 0;
        visible = false;
    }

    signal findNext()
    signal findPrevious()

    width: 250
    height: 35
    radius: 2

    border.width: 1
    border.color: "black"
    color: "white"

    onVisibleChanged: {
        if (visible)
            findTextField.forceActiveFocus();
    }


    RowLayout {
        anchors.fill: parent
        anchors.topMargin: 5
        anchors.bottomMargin: 5
        anchors.leftMargin: 10
        anchors.rightMargin: 10

        spacing: 5

        Rectangle {
            Layout.fillWidth: true
            Layout.fillHeight: true

            TextField {
                id: findTextField
                anchors.fill: parent
                color: "black"
                background: Rectangle {
                    color: "transparent"
                }

                onAccepted: root.findNext()
                onTextChanged: root.findNext()
                onActiveFocusChanged: activeFocus ? selectAll() : deselect()
            }
        }

        Label {
            text: root.activeMatch + "/" + root.numberOfMatches
            visible: findTextField.text !== ""
            color: "black"
        }

        Rectangle {
            border.width: 1
            border.color: "#dddddd"
            Layout.preferredWidth: 2
            Layout.preferredHeight: parent.height
        }

        ToolButton {
            id: findBtnLeft
            text: "<"
            enabled: root.numberOfMatches > 0
            onClicked: root.findPrevious()
            contentItem: Text {
                color: "black"
                text: findBtnLeft.text
            }
        }

        ToolButton {
            id: findBtnRight
            text: ">"
            enabled: root.numberOfMatches > 0
            onClicked: root.findNext()
            contentItem: Text {
                color: "black"
                text: findBtnRight.text
            }
        }

        ToolButton {
            id: findBtnClose
            text: "x"
            onClicked: root.visible = false
            contentItem: Text {
                color: "black"
                text: findBtnClose.text
            }
        }
    }
}
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import QtQuick

Rectangle {
    id: fullScreenNotification
    width: 500
    height: 40
    color: "white"
    radius: 7

    visible: false
    opacity: 0

    function show() {
        visible = true;
        opacity = 1;
        reset.start();
    }

    function hide() {
        reset.stop();
        opacity = 0;
    }

    Behavior on opacity {
        NumberAnimation {
            duration: 750
            onStopped: {
                if (fullScreenNotification.opacity === 0)
                    fullScreenNotification.visible = false;
            }
        }
    }

    Timer {
        id: reset
        interval: 5000
        onTriggered: fullScreenNotification.hide()
    }

    anchors.horizontalCenter: parent.horizontalCenter
    y: 125

    Text {
        id: message
        width: parent.width

        anchors.horizontalCenter: parent.horizontalCenter
        anchors.verticalCenter: parent.verticalCenter

        horizontalAlignment: Text.AlignHCenter
        verticalAlignment: Text.AlignVCenter

        wrapMode: Text.WordWrap
        elide: Text.ElideNone
        clip: true

        text: qsTr("You are now in fullscreen mode. Press ESC to quit!")
    }
}
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

pragma ComponentBehavior: Bound

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtWebEngine

Dialog {
    id: webAuthDialog
    anchors.centerIn: parent
    contentWidth: verticalLayout.width +10;
    contentHeight: verticalLayout.height +10;
    standardButtons: Dialog.Cancel | Dialog.Apply
    title: "WebAuth Request"

    property var selectAccount;
    property var authrequest: null;

    Connections {
        id: webauthConnection
        ignoreUnknownSignals: true
        function onStateChanged(state) {
            webAuthDialog.setupUI(state);
        }
    }

    onApplied: {
        switch (webAuthDialog.authrequest.state) {
            case WebEngineWebAuthUxRequest.WebAuthUxState.CollectPin:
                webAuthDialog.authrequest.setPin(pinEdit.text);
                break;
            case WebEngineWebAuthUxRequest.WebAuthUxState.SelectAccount:
                webAuthDialog.authrequest.setSelectedAccount(webAuthDialog.selectAccount);
                break;
             default:
                 break;
        }
    }

    onRejected: {
        webAuthDialog.authrequest.cancel();
    }

    function init(request) {
        pinLabel.visible = false;
        pinEdit.visible = false;
        confirmPinLabel.visible = false;
        confirmPinEdit.visible = false;
        selectAccountModel.clear();
        webAuthDialog.authrequest = request;
        webauthConnection.target = request;
        setupUI(webAuthDialog.authrequest.state)
        webAuthDialog.visible = true;
        pinEntryError.visible = false;
    }

    function setupUI(state) {
        switch (state) {
        case WebEngineWebAuthUxRequest.WebAuthUxState.SelectAccount:
            setupSelectAccountUI();
            break;
        case WebEngineWebAuthUxRequest.WebAuthUxState.CollectPin:
            setupCollectPin();
            break;
        case WebEngineWebAuthUxRequest.WebAuthUxState.FinishTokenCollection:
            setupFinishCollectToken();
            break;
        case WebEngineWebAuthUxRequest.WebAuthUxState.RequestFailed:
            setupErrorUI();
            break;
        case WebEngineWebAuthUxRequest.WebAuthUxState.Completed:
            webAuthDialog.close();
            break;
        }
    }

    ButtonGroup {
        id : selectAccountButtonGroup;
        exclusive: true;
    }

    ListModel {
        id: selectAccountModel

    }
    contentItem: Item {
        ColumnLayout  {
            id : verticalLayout
            spacing : 10

            Label {
                id: heading
                text: "";
            }

            Label {
                id: description
                text: "";
            }

            Row {
                spacing : 10
                Label {
                    id: pinLabel
                    text: "PIN";
                }
                TextInput {
                    id: pinEdit
                    text: "EnterPin"
                    enabled: true
                    focus: true
                    color: "white"
                    layer.sourceRect: Qt.rect(0, 0, 20, 20)
                }
            }

            Row {
                spacing : 10
                Label {
                    id: confirmPinLabel
                    text: "Confirm PIN";
                }
                TextEdit {
                    id: confirmPinEdit
                    text: ""
                }
            }

            Label {
                id: pinEntryError
                text: "";
            }

            Repeater {
                id : selectAccountRepeater
                model: selectAccountModel
                Column {
                    id: selectAccountRepeaterColumn
                    required property string modelData
                    spacing : 5
                    RadioButton {
                        text: selectAccountRepeaterColumn.modelData
                        ButtonGroup.group : selectAccountButtonGroup;
                        onClicked: function(){
                            webAuthDialog.selectAccount = text;
                        }
                    }
                }
            }
        }
    }

    function setupSelectAccountUI() {
        webAuthDialog.selectAccount = "";
        heading.text = "Choose a passkey";
        description.text = "Which passkey do you want to use for " + webAuthDialog.authrequest.relyingPartyId;

        selectAccountModel.clear();
        var userNames = webAuthDialog.authrequest.userNames;
        for (let i = 0; i < userNames.length; i++) {
            selectAccountModel.append( {"name" : userNames[i]});
        }
        pinLabel.visible = false;
        pinEdit.visible = false;
        confirmPinLabel.visible = false;
        confirmPinEdit.visible = false;
        pinEntryError.visible = false;
        standardButton(Dialog.Apply).visible = true;
        standardButton(Dialog.Cancel).visible = true;
        standardButton(Dialog.Cancel).text ="Cancel"
    }

    function setupCollectPin() {
        var requestInfo = webAuthDialog.authrequest.pinRequest;

        pinEdit.clear();

        if (requestInfo.reason === WebEngineWebAuthUxRequest.PinEntryReason.Challenge) {
            heading.text = "PIN required";
            description.text = "Enter the PIN for your security key";
            pinLabel.visible = true;
            pinEdit.visible = true;
            confirmPinLabel.visible = false;
            confirmPinEdit.visible = false;
        } else if (requestInfo.reason === WebEngineWebAuthUxRequest.PinEntryReason.Set) {
            heading.text = "Set PIN ";
            description.text = "Set new PIN for your security key";
            pinLabel.visible = true;
            pinEdit.visible = true;
            confirmPinLabel.visible = true;
            confirmPinEdit.visible = true;
        }
        pinEntryError.text = getPINErrorDetails() + " " + requestInfo.remainingAttempts + " attempts reamining";
        pinEntryError.visible = true;
        selectAccountModel.clear();
        standardButton(Dialog.Cancel).visible = true;
        standardButton(Dialog.Cancel).text ="Cancel"
        standardButton(Dialog.Apply).visible = true;
    }

    function getPINErrorDetails() {
        var requestInfo = webAuthDialog.authrequest.pinRequest;
        switch (requestInfo.error) {
        case WebEngineWebAuthUxRequest.PinEntryError.NoError:
            return "";
        case WebEngineWebAuthUxRequest.PinEntryError.TooShort:
            return "Too short";
        case WebEngineWebAuthUxRequest.PinEntryError.InternalUvLocked:
            return "Internal Uv locked";
        case WebEngineWebAuthUxRequest.PinEntryError.WrongPin:
            return "Wrong PIN";
        case WebEngineWebAuthUxRequest.PinEntryError.InvalidCharacters:
            return "Invalid characters";
        case WebEngineWebAuthUxRequest.PinEntryError.SameAsCurrentPin:
            return "Same as current PIN";
        }
    }

    function getRequestFailureResaon() {
        var requestFailureReason = webAuthDialog.authrequest.requestFailureReason;
        switch (requestFailureReason) {
        case WebEngineWebAuthUxRequest.RequestFailureReason.Timeout:
            return " Request Timeout";
        case WebEngineWebAuthUxRequest.RequestFailureReason.KeyNotRegistered:
            return "Key not registered";
        case WebEngineWebAuthUxRequest.RequestFailureReason.KeyAlreadyRegistered:
            return "You already registered this device. You don't have to register it again\n"
                     + "Try again with different key or device.";
        case WebEngineWebAuthUxRequest.RequestFailureReason.SoftPinBlock:
            return "The security key is locked because the wrong PIN was entered too many times.\n"
                    + "To unlock it, remove and reinsert it.";
        case WebEngineWebAuthUxRequest.RequestFailureReason.HardPinBlock:
            return "The security key is locked because the wrong PIN was entered too many times.\n"
                    + "You'll need to reset the security key.";
        case WebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorRemovedDuringPinEntry:
            return "Authenticator removed during verification. Please reinsert and try again";
        case WebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorMissingResidentKeys:
            return "Authenticator doesn't have resident key support";
        case WebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorMissingUserVerification:
            return "Authenticator missing user verification";
        case WebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorMissingLargeBlob:
            return "Authenticator missing Large Blob support";
        case WebEngineWebAuthUxRequest.RequestFailureReason.NoCommonAlgorithms:
            return "No common Algorithms";
        case WebEngineWebAuthUxRequest.RequestFailureReason.StorageFull:
            return "Storage full";
        case WebEngineWebAuthUxRequest.RequestFailureReason.UserConsentDenied:
            return "User consent denied";
        case WebEngineWebAuthUxRequest.RequestFailureReason.WinUserCancelled:
            return "User cancelled request";
        }
    }

    function setupFinishCollectToken() {
        heading.text = "Use your security key with " + webAuthDialog.authrequest.relyingPartyId;
        description.text = "Touch your security key again to complete the request.";
        pinLabel.visible = false;
        pinEdit.visible = false;
        confirmPinLabel.visible = false;
        confirmPinEdit.visible = false;
        selectAccountModel.clear();
        pinEntryError.visible = false;
        standardButton(Dialog.Apply).visible = false;
        standardButton(Dialog.Cancel).visible = true;
        standardButton(Dialog.Cancel).text ="Cancel"
    }

    function setupErrorUI() {
        heading.text = "Something went wrong";
        description.text = getRequestFailureResaon();
        pinLabel.visible = false;
        pinEdit.visible = false;
        confirmPinLabel.visible = false;
        confirmPinEdit.visible = false;
        selectAccountModel.clear();
        pinEntryError.visible = false;
        standardButton(Dialog.Apply).visible = false;
        standardButton(Dialog.Cancel).visible = true;
        standardButton(Dialog.Cancel).text ="Close"
    }
}
module BrowserUtils
ApplicationRoot 254.0 ApplicationRoot.qml
BrowserDialog 254.0 BrowserDialog.qml
singleton BrowserManager 254.0 BrowserManager.qml
BrowserWindow 254.0 BrowserWindow.qml
DownloadView 254.0 DownloadView.qml
FindBar 254.0 FindBar.qml
FullScreenNotification 254.0 FullScreenNotification.qml
WebAuthDialog 254.0 WebAuthDialog.qml
depends QtQuick
<RCC>
    <qresource>
        <file>icons/3rdparty/go-next.png</file>
        <file>icons/3rdparty/go-previous.png</file>
        <file>icons/3rdparty/process-stop.png</file>
        <file>icons/3rdparty/view-refresh.png</file>
    </qresource>
</RCC>