Nano Browser Example¶
A web browser implemented using the WebEngineView QML type.
# 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();
}
Settings {
id : appSettings
property alias autoLoadImages: navigationBar.loadImagesChecked
property alias javaScriptEnabled: navigationBar.javaScriptEnabledChecked
property alias errorPageEnabled: navigationBar.errorPageEnabledChecked
property alias pluginsEnabled: navigationBar.pluginsEnabledChecked
property alias fullScreenSupportEnabled: navigationBar.fullScreenSupportEnabledChecked
property alias autoLoadIconsForPage: navigationBar.autoLoadIconsForPageChecked
property alias touchIconsEnabled: navigationBar.touchIconsEnabledChecked
property alias webRTCPublicInterfacesOnly : navigationBar.webRTCPublicInterfacesOnlyChecked
property alias devToolsEnabled: navigationBar.devToolsEnabledChecked
property alias pdfViewerEnabled: navigationBar.pdfViewerEnabledChecked
property alias javascriptCanAccessClipboard: navigationBar.javascriptCanAccessClipboardChecked
property alias javascriptCanPaste: navigationBar.javascriptCanPasteChecked
property int imageAnimationPolicy: {
return navigationBar.animateImageOnceChecked ? WebEngineSettings.ImageAnimationPolicy.AnimateOnce :
navigationBar.allowImageAnimationChecked ? WebEngineSettings.ImageAnimationPolicy.Allow :
navigationBar.disableImageAnimationChecked ? WebEngineSettings.ImageAnimationPolicy.Disallow :
WebEngineSettings.ImageAnimationPolicy.AnimateOnce
}
}
Action {
shortcut: "Ctrl+D"
onTriggered: {
downloadView.visible = !downloadView.visible;
}
}
Action {
id: focusAction
shortcut: "Ctrl+L"
onTriggered: {
navigationBar.addressBar.forceActiveFocus();
navigationBar.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());
navigationBar.addressBar.forceActiveFocus();
navigationBar.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: WebToolBar {
id: navigationBar
currentWebView: win.currentWebView
}
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: navigationBar.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: appSettings.devToolsEnabled
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() { appSettings.devToolsEnabled = false })
}
}
SslDialog {
id: sslDialog
anchors.centerIn: parent
}
PermissionDialog {
id: permissionDialog
anchors.centerIn: parent
width: Math.min(win.width, win.height) / 3 * 2
}
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
PermissionDialog 254.0 PermissionDialog.qml
SslDialog 254.0 SslDialog.qml
WebAuthDialog 254.0 WebAuthDialog.qml
WebToolBar 254.0 WebToolBar.qml
depends QtQuick
typeinfo main.qmltypes
<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>