diff options
Diffstat (limited to 'examples/webenginequick/quicknanobrowser')
16 files changed, 645 insertions, 60 deletions
diff --git a/examples/webenginequick/quicknanobrowser/ApplicationRoot.qml b/examples/webenginequick/quicknanobrowser/ApplicationRoot.qml index 9a40fab15..55c414409 100644 --- a/examples/webenginequick/quicknanobrowser/ApplicationRoot.qml +++ b/examples/webenginequick/quicknanobrowser/ApplicationRoot.qml @@ -1,4 +1,4 @@ -// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import QtQuick diff --git a/examples/webenginequick/quicknanobrowser/BrowserDialog.qml b/examples/webenginequick/quicknanobrowser/BrowserDialog.qml index a19ab62bf..7af347ec3 100644 --- a/examples/webenginequick/quicknanobrowser/BrowserDialog.qml +++ b/examples/webenginequick/quicknanobrowser/BrowserDialog.qml @@ -1,4 +1,4 @@ -// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import QtQuick @@ -8,7 +8,7 @@ import QtWebEngine Window { id: window property alias currentWebView: webView - flags: Qt.Dialog | Qt.WindowStaysOnTopHint + flags: Qt.Dialog width: 800 height: 600 visible: true diff --git a/examples/webenginequick/quicknanobrowser/BrowserWindow.qml b/examples/webenginequick/quicknanobrowser/BrowserWindow.qml index 3fa481787..3b911262b 100644 --- a/examples/webenginequick/quicknanobrowser/BrowserWindow.qml +++ b/examples/webenginequick/quicknanobrowser/BrowserWindow.qml @@ -1,13 +1,14 @@ -// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -import Qt.labs.settings +import QtCore import QtQml import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Fusion import QtQuick.Layouts import QtQuick.Window import QtWebEngine +import BrowserUtils ApplicationWindow { id: browserWindow @@ -43,6 +44,7 @@ ApplicationWindow { property alias webRTCPublicInterfacesOnly : webRTCPublicInterfacesOnly.checked property alias devToolsEnabled: devToolsEnabled.checked property alias pdfViewerEnabled: pdfViewerEnabled.checked + property int imageAnimationPolicy: WebEngineSettings.AllowImageAnimation } Action { @@ -272,7 +274,7 @@ ApplicationWindow { when: currentWebView value: currentWebView.url } - onAccepted: currentWebView.url = utils.fromUserInput(text) + onAccepted: currentWebView.url = Utils.fromUserInput(text) selectByMouse: true } ToolButton { @@ -361,10 +363,49 @@ ApplicationWindow { } MenuItem { id: pdfViewerEnabled - text: "PDF viewer enabled" + text: "PDF Viewer Enabled" checkable: true checked: WebEngine.settings.pdfViewerEnabled } + + Menu { + id: imageAnimationPolicy + title: "Image Animation Policy" + + MenuItem { + id: disableImageAnimation + text: "Disable All Image Animation" + checkable: true + autoExclusive: true + checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.DisallowImageAnimation + onTriggered: { + appSettings.imageAnimationPolicy = WebEngineSettings.DisallowImageAnimation + } + } + + MenuItem { + id: allowImageAnimation + text: "Allow All Animated Images" + checkable: true + autoExclusive: true + checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.AllowImageAnimation + onTriggered : { + appSettings.imageAnimationPolicy = WebEngineSettings.AllowImageAnimation + } + } + + MenuItem { + id: animateImageOnce + text: "Animate Image Once" + checkable: true + autoExclusive: true + checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.AnimateImageOnce + onTriggered : { + appSettings.imageAnimationPolicy = WebEngineSettings.AnimateImageOnce + } + } + } + } } } @@ -470,7 +511,6 @@ ApplicationWindow { } function removeView(index) { - tabBar.removeItem(index); if (tabBar.count > 1) { tabBar.removeItem(tabBar.itemAt(index)); tabLayout.children[index].destroy(); @@ -509,6 +549,8 @@ ApplicationWindow { } } ] + settings.localContentCanAccessRemoteUrls: true + settings.localContentCanAccessFileUrls: false settings.autoLoadImages: appSettings.autoLoadImages settings.javascriptEnabled: appSettings.javaScriptEnabled settings.errorPageEnabled: appSettings.errorPageEnabled @@ -518,6 +560,7 @@ ApplicationWindow { settings.touchIconsEnabled: appSettings.touchIconsEnabled settings.webRTCPublicInterfacesOnly: appSettings.webRTCPublicInterfacesOnly settings.pdfViewerEnabled: appSettings.pdfViewerEnabled + settings.imageAnimationPolicy: appSettings.imageAnimationPolicy onCertificateError: function(error) { error.defer(); @@ -556,13 +599,6 @@ ApplicationWindow { request.accept(); } - onQuotaRequested: function(request) { - if (request.requestedSize <= 5 * 1024 * 1024) - request.accept(); - else - request.reject(); - } - onRegisterProtocolHandlerRequested: function(request) { console.log("accepting registerProtocolHandler request for " + request.scheme + " from " + request.origin); @@ -607,6 +643,15 @@ ApplicationWindow { findBar.reset(); } + onFeaturePermissionRequested: function(securityOrigin, feature) { + featurePermissionDialog.securityOrigin = securityOrigin; + featurePermissionDialog.feature = feature; + featurePermissionDialog.visible = true; + } + onWebAuthUxRequested: function(request) { + webAuthDialog.init(request); + } + Timer { id: reloadTimer interval: 0 @@ -645,22 +690,21 @@ ApplicationWindow { Dialog { id: sslDialog anchors.centerIn: parent - contentWidth: Math.max(mainText.width, detailedText.width) - contentHeight: mainText.height + detailedText.height + 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 { - id: textContentItem Label { - id: mainText + id: mainTextForSSLDialog text: "Do you wish to continue?" } Text { - id: detailedText - anchors.top: mainText.bottom + 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" + @@ -686,6 +730,74 @@ ApplicationWindow { visible = certErrors.length > 0 } } + Dialog { + id: featurePermissionDialog + anchors.centerIn: parent + width: Math.min(browserWindow.width, browserWindow.height) / 3 * 2 + contentWidth: mainTextForPermissionDialog.width + contentHeight: mainTextForPermissionDialog.height + standardButtons: Dialog.No | Dialog.Yes + title: "Permission Request" + + property var feature; + property url securityOrigin; + + contentItem: Item { + Label { + id: mainTextForPermissionDialog + text: featurePermissionDialog.questionForFeature() + } + } + + onAccepted: currentWebView && currentWebView.grantFeaturePermission(securityOrigin, feature, true) + onRejected: currentWebView && currentWebView.grantFeaturePermission(securityOrigin, feature, false) + onVisibleChanged: { + if (visible) + width = contentWidth + 20; + } + + function questionForFeature() { + var question = "Allow " + securityOrigin + " to " + + switch (feature) { + case WebEngineView.Geolocation: + question += "access your location information?"; + break; + case WebEngineView.MediaAudioCapture: + question += "access your microphone?"; + break; + case WebEngineView.MediaVideoCapture: + question += "access your webcam?"; + break; + case WebEngineView.MediaVideoCapture: + question += "access your microphone and webcam?"; + break; + case WebEngineView.MouseLock: + question += "lock your mouse cursor?"; + break; + case WebEngineView.DesktopVideoCapture: + question += "capture video of your desktop?"; + break; + case WebEngineView.DesktopAudioVideoCapture: + question += "capture audio and video of your desktop?"; + break; + case WebEngineView.Notifications: + question += "show notification on your desktop?"; + break; + case WebEngineView.ClipboardReadWrite: + question += "read from and write to your clipboard?"; + break; + case WebEngineView.LocalFontsAccess: + question += "access the fonts stored on your machine?"; + break; + default: + question += "access unknown or unsupported feature [" + feature + "] ?"; + break; + } + + return question; + } + } FullScreenNotification { id: fullScreenNotification @@ -697,6 +809,11 @@ ApplicationWindow { anchors.fill: parent } + WebAuthDialog { + id: webAuthDialog + visible: false + } + function onDownloadRequested(download) { downloadView.visible = true; downloadView.append(download); diff --git a/examples/webenginequick/quicknanobrowser/CMakeLists.txt b/examples/webenginequick/quicknanobrowser/CMakeLists.txt index cdccc26be..7efb61127 100644 --- a/examples/webenginequick/quicknanobrowser/CMakeLists.txt +++ b/examples/webenginequick/quicknanobrowser/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + cmake_minimum_required(VERSION 3.16) project(quicknanobrowser LANGUAGES CXX) @@ -16,9 +19,17 @@ qt_add_executable(quicknanobrowser utils.h ) +if(WIN32) + set_property( + TARGET quicknanobrowser + APPEND PROPERTY + SOURCES quicknanobrowser.exe.manifest) +endif() + set_target_properties(quicknanobrowser PROPERTIES WIN32_EXECUTABLE TRUE MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_GUI_IDENTIFIER "io.qt.examples.webenginequick.quicknanobrowser" ) target_link_libraries(quicknanobrowser PUBLIC @@ -29,6 +40,12 @@ target_link_libraries(quicknanobrowser PUBLIC Qt::WebEngineQuick ) +qt_add_qml_module(quicknanobrowser + URI BrowserUtils + VERSION 1.0 + RESOURCE_PREFIX / +) + # Resources: set(resources_resource_files "ApplicationRoot.qml" @@ -37,6 +54,7 @@ set(resources_resource_files "DownloadView.qml" "FindBar.qml" "FullScreenNotification.qml" + "WebAuthDialog.qml" ) qt_add_resources(quicknanobrowser "resources" @@ -68,6 +86,24 @@ if(TARGET Qt::Widgets) ) endif() +if (APPLE) + set_target_properties(quicknanobrowser PROPERTIES + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.cmake.macos.plist" + ) + + if (NOT CMAKE_GENERATOR STREQUAL "Xcode") + # Need to sign application for location permissions to work + if(QT_FEATURE_debug_and_release) + set(exe_path "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/") + else() + unset(exe_path) + endif() + add_custom_command(TARGET quicknanobrowser + POST_BUILD COMMAND codesign --force -s - ${exe_path}quicknanobrowser.app + ) + endif() +endif() + install(TARGETS quicknanobrowser RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" diff --git a/examples/webenginequick/quicknanobrowser/DownloadView.qml b/examples/webenginequick/quicknanobrowser/DownloadView.qml index 124ae4e9d..421b4f55c 100644 --- a/examples/webenginequick/quicknanobrowser/DownloadView.qml +++ b/examples/webenginequick/quicknanobrowser/DownloadView.qml @@ -1,8 +1,8 @@ -// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Fusion import QtWebEngine import QtQuick.Layouts diff --git a/examples/webenginequick/quicknanobrowser/FindBar.qml b/examples/webenginequick/quicknanobrowser/FindBar.qml index 34713479f..409d8dcff 100644 --- a/examples/webenginequick/quicknanobrowser/FindBar.qml +++ b/examples/webenginequick/quicknanobrowser/FindBar.qml @@ -1,8 +1,8 @@ -// Copyright (C) 2019 The Qt Company Ltd. +// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Fusion import QtQuick.Layouts Rectangle { @@ -51,6 +51,7 @@ Rectangle { TextField { id: findTextField anchors.fill: parent + color: "black" background: Rectangle { color: "transparent" } @@ -64,6 +65,7 @@ Rectangle { Label { text: activeMatch + "/" + numberOfMatches visible: findTextField.text != "" + color: "black" } Rectangle { @@ -79,17 +81,29 @@ Rectangle { text: "<" enabled: numberOfMatches > 0 onClicked: root.findPrevious() + contentItem: Text { + color: "black" + text: parent.text + } } ToolButton { text: ">" enabled: numberOfMatches > 0 onClicked: root.findNext() + contentItem: Text { + color: "black" + text: parent.text + } } ToolButton { text: "x" onClicked: root.visible = false + contentItem: Text { + color: "black" + text: parent.text + } } } } diff --git a/examples/webenginequick/quicknanobrowser/FullScreenNotification.qml b/examples/webenginequick/quicknanobrowser/FullScreenNotification.qml index e0f3da411..779406432 100644 --- a/examples/webenginequick/quicknanobrowser/FullScreenNotification.qml +++ b/examples/webenginequick/quicknanobrowser/FullScreenNotification.qml @@ -1,4 +1,4 @@ -// Copyright (C) 2015 The Qt Company Ltd. +// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import QtQuick diff --git a/examples/webenginequick/quicknanobrowser/Info.cmake.macos.plist b/examples/webenginequick/quicknanobrowser/Info.cmake.macos.plist new file mode 100644 index 000000000..cac4fa1f4 --- /dev/null +++ b/examples/webenginequick/quicknanobrowser/Info.cmake.macos.plist @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleName</key> + <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string> + <key>CFBundleIdentifier</key> + <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string> + <key>CFBundleExecutable</key> + <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string> + <key>CFBundleVersion</key> + <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string> + <key>CFBundleShortVersionString</key> + <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string> + <key>LSMinimumSystemVersion</key> + <string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string> + <key>NSHumanReadableCopyright</key> + <string>${MACOSX_BUNDLE_COPYRIGHT}</string> + <key>CFBundleIconFile</key> + <string>${MACOSX_BUNDLE_ICON_FILE}</string> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>NSSupportsAutomaticGraphicsSwitching</key> + <true/> + <key>NSLocationUsageDescription</key> + <string>Quick Nano Browser would like to give web sites access to your location for demo purposes.</string> + <key>NSMicrophoneUsageDescription</key> + <string>Quick Nano Browser would like to give web sites access to your computer's microphone for demo purposes.</string> + <key>NSCameraUsageDescription</key> + <string>Quick Nano Browser would like to give web sites access to your computer's camera for demo purposes.</string> +</dict> +</plist> diff --git a/examples/webenginequick/quicknanobrowser/WebAuthDialog.qml b/examples/webenginequick/quicknanobrowser/WebAuthDialog.qml new file mode 100644 index 000000000..aeb6f5a0f --- /dev/null +++ b/examples/webenginequick/quicknanobrowser/WebAuthDialog.qml @@ -0,0 +1,281 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtWebEngine + +Dialog { + id: webAuthDialog + anchors.centerIn: parent + width: Math.min(browserWindow.width, browserWindow.height) / 3 * 2 + 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 : selectAccount; + 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 { + spacing : 5 + RadioButton { + text: modelData + ButtonGroup.group : selectAccount; + 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 (var 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 (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 + Try agin with different key or device"; + case WebEngineWebAuthUxRequest.RequestFailureReason.SoftPinBlock: + return "The security key is locked because the wrong PIN was entered too many times. + 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. + 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" + } +} diff --git a/examples/webenginequick/quicknanobrowser/doc/src/quicknanobrowser.qdoc b/examples/webenginequick/quicknanobrowser/doc/src/quicknanobrowser.qdoc index b1910df95..1dc209c2e 100644 --- a/examples/webenginequick/quicknanobrowser/doc/src/quicknanobrowser.qdoc +++ b/examples/webenginequick/quicknanobrowser/doc/src/quicknanobrowser.qdoc @@ -8,6 +8,8 @@ \brief A web browser implemented using the WebEngineView QML type. \image quicknanobrowser-demo.jpg + \examplecategory {Application Examples} + \examplecategory {Web Technologies} \e {Quick Nano Browser} demonstrates how to use the \l{Qt WebEngine QML Types} {Qt WebEngine QML types} to develop a small web browser application that consists of a browser @@ -91,6 +93,31 @@ \skipto Dialog { \printuntil /^\ {4}\}/ + \section1 Handling Feature Permission Requests + + We use the \c onFeaturePermissionRequested() signal handler to handle requests for + accessing a certain feature or device. The \c securityOrigin parameter identifies the + requester web site, and the \c feature parameter is the requested feature. We use these + to construct the message of the dialog: + + \quotefromfile webenginequick/quicknanobrowser/BrowserWindow.qml + \skipto onFeaturePermissionRequested + \printuntil } + + We show a dialog where the user is asked to grant or deny access. The custom + \c questionForFeature() JavaScript function generates a human-readable question about + the request. + If users select \uicontrol Yes, we call the \l{WebEngineView::}{grantFeaturePermission()} + method with a third \c true parameter to grant the \c securityOrigin web site the permission + to access the \c feature. + If users select \uicontrol No, we call the same method but with the \c false parameter to + deny access: + + \skipto id: sslDialog + \skipto Dialog { + \printuntil /^\ {4}\}/ + + \section1 Entering and Leaving Fullscreen Mode We create a menu item for allowing fullscreen mode in a settings menu that we place on the tool @@ -117,6 +144,37 @@ \skipto Escape \printuntil /^\ {4}\}/ + \section1 Handling WebAuth/FIDO UX Requests + + We use the \c onWebAuthUxRequested() signal handler to handle requests for + WebAuth/FIDO UX. The \c request parameter is an instance of WebEngineWebAuthUxRequest + which contains UX request details and APIs required to process the request. + We use it to construct WebAuthUX dialog and initiates the UX request flow. + + \quotefromfile webenginequick/quicknanobrowser/BrowserWindow.qml + \skipto onWebAuthUxRequested + \printuntil } + + The \l WebEngineWebAuthUxRequest object periodically emits the \l + {WebEngineWebAuthUxRequest::}{stateChanged} signal to notify potential + observers of the current WebAuth UX states. The observers update the WebAuth + dialog accordingly. We use onStateChanged() signal handler to handle + state change requests. See \c WebAuthDialog.qml for an example + of how these signals can be handled. + + \quotefromfile webenginequick/quicknanobrowser/WebAuthDialog.qml + \skipto Connections + \printuntil } + \skipto function init(request) + \printuntil } + + \section1 Signing Requirement for macOS + + To allow web sites access to the location, camera, and microphone when running + \e {Quick Nano Browser} on macOS, the application needs to be signed. This is + done automatically when building, but you need to set up a valid signing identity + for the build environment. + \section1 Files and Attributions The example uses icons from the Tango Icon Library: diff --git a/examples/webenginequick/quicknanobrowser/icons/3rdparty/qt_attribution.json b/examples/webenginequick/quicknanobrowser/icons/3rdparty/qt_attribution.json index 681223b12..d8d85d6f1 100644 --- a/examples/webenginequick/quicknanobrowser/icons/3rdparty/qt_attribution.json +++ b/examples/webenginequick/quicknanobrowser/icons/3rdparty/qt_attribution.json @@ -12,13 +12,13 @@ "LicenseId": "urn:dje:license:public-domain", "License": "Public Domain", "LicenseFile": "COPYING", - "Copyright": "Ulisse Perusin <uli.peru@gmail.com> -Steven Garrity <sgarrity@silverorange.com> -Lapo Calamandrei <calamandrei@gmail.com> -Ryan Collier <rcollier@novell.com> -Rodney Dawes <dobey@novell.com> -Andreas Nilsson <nisses.mail@home.se> -Tuomas Kuosmanen <tigert@tigert.com> -Garrett LeSage <garrett@novell.com> -Jakub Steiner <jimmac@novell.com>" + "Copyright": ["Ulisse Perusin <uli.peru@gmail.com>", + "Steven Garrity <sgarrity@silverorange.com>", + "Lapo Calamandrei <calamandrei@gmail.com>", + "Ryan Collier <rcollier@novell.com>", + "Rodney Dawes <dobey@novell.com>", + "Andreas Nilsson <nisses.mail@home.se>", + "Tuomas Kuosmanen <tigert@tigert.com>", + "Garrett LeSage <garrett@novell.com>", + "Jakub Steiner <jimmac@novell.com>"] } diff --git a/examples/webenginequick/quicknanobrowser/main.cpp b/examples/webenginequick/quicknanobrowser/main.cpp index bdb31644f..1e693cbcd 100644 --- a/examples/webenginequick/quicknanobrowser/main.cpp +++ b/examples/webenginequick/quicknanobrowser/main.cpp @@ -1,43 +1,53 @@ -// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "utils.h" -#include <QtGui/QGuiApplication> +#include <QtWebEngineQuick/qtwebenginequickglobal.h> + #include <QtQml/QQmlApplicationEngine> #include <QtQml/QQmlContext> -#include <QtWebEngineQuick/qtwebenginequickglobal.h> -static QUrl startupUrl() +#include <QtGui/QGuiApplication> + +#include <QtCore/QCommandLineParser> +#include <QtCore/QCommandLineOption> +#include <QtCore/QLoggingCategory> + +static QUrl startupUrl(const QCommandLineParser &parser) { - QUrl ret; - QStringList args(qApp->arguments()); - args.takeFirst(); - for (const QString &arg : qAsConst(args)) { - if (arg.startsWith(QLatin1Char('-'))) - continue; - ret = Utils::fromUserInput(arg); - if (ret.isValid()) - return ret; + if (!parser.positionalArguments().isEmpty()) { + const QUrl url = Utils::fromUserInput(parser.positionalArguments().constFirst()); + if (url.isValid()) + return url; } - return QUrl(QStringLiteral("https://www.qt.io")); + return QUrl(QStringLiteral("chrome://qt")); } int main(int argc, char **argv) { - QCoreApplication::setOrganizationName("QtExamples"); + QCoreApplication::setApplicationName("Quick Nano Browser"); + QCoreApplication::setOrganizationName("QtProject"); + QtWebEngineQuick::initialize(); QGuiApplication app(argc, argv); + QLoggingCategory::setFilterRules(QStringLiteral("qt.webenginecontext.debug=true")); + + QCommandLineParser parser; + parser.addHelpOption(); + parser.addVersionOption(); + parser.addPositionalArgument("url", "The URL to open."); + parser.process(app); QQmlApplicationEngine appEngine; - Utils utils; - appEngine.rootContext()->setContextProperty("utils", &utils); appEngine.load(QUrl("qrc:/ApplicationRoot.qml")); - if (!appEngine.rootObjects().isEmpty()) - QMetaObject::invokeMethod(appEngine.rootObjects().first(), "load", Q_ARG(QVariant, startupUrl())); - else + if (appEngine.rootObjects().isEmpty()) qFatal("Failed to load sources"); + const QUrl url = startupUrl(parser); + QMetaObject::invokeMethod(appEngine.rootObjects().constFirst(), + "load", Q_ARG(QVariant, url)); + return app.exec(); } diff --git a/examples/webenginequick/quicknanobrowser/quicknanobrowser.exe.manifest b/examples/webenginequick/quicknanobrowser/quicknanobrowser.exe.manifest new file mode 100644 index 000000000..acc401776 --- /dev/null +++ b/examples/webenginequick/quicknanobrowser/quicknanobrowser.exe.manifest @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <!--The ID below indicates application support for Windows Vista --> + <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/> + <!--The ID below indicates application support for Windows 7 --> + <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/> + <!--The ID below indicates application support for Windows 8 --> + <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> + <!--The ID below indicates application support for Windows 8.1 --> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> + <!--The ID below indicates application support for Windows 10/11 --> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> + </application> +</compatibility> +</assembly> diff --git a/examples/webenginequick/quicknanobrowser/quicknanobrowser.pro b/examples/webenginequick/quicknanobrowser/quicknanobrowser.pro index 3a014fe20..bd5427dc5 100644 --- a/examples/webenginequick/quicknanobrowser/quicknanobrowser.pro +++ b/examples/webenginequick/quicknanobrowser/quicknanobrowser.pro @@ -6,10 +6,19 @@ TARGET = quicknanobrowser HEADERS = utils.h SOURCES = main.cpp +win32 { + CONFIG -= embed_manifest_exe + QMAKE_MANIFEST = $$PWD/quicknanobrowser.exe.manifest +} + RESOURCES += resources.qrc QT += qml quick webenginequick +CONFIG += qmltypes +QML_IMPORT_NAME = BrowserUtils +QML_IMPORT_MAJOR_VERSION = 1 + qtHaveModule(widgets) { QT += widgets # QApplication is required to get native styling with QtQuickControls } diff --git a/examples/webenginequick/quicknanobrowser/resources.qrc b/examples/webenginequick/quicknanobrowser/resources.qrc index 9d1f927d3..0a0b42bbb 100644 --- a/examples/webenginequick/quicknanobrowser/resources.qrc +++ b/examples/webenginequick/quicknanobrowser/resources.qrc @@ -6,6 +6,7 @@ <file>DownloadView.qml</file> <file>FindBar.qml</file> <file>FullScreenNotification.qml</file> + <file>WebAuthDialog.qml</file> </qresource> <qresource prefix="/icons"> <file alias="go-next.png">icons/3rdparty/go-next.png</file> diff --git a/examples/webenginequick/quicknanobrowser/utils.h b/examples/webenginequick/quicknanobrowser/utils.h index 0c57ab19b..6c11e75fb 100644 --- a/examples/webenginequick/quicknanobrowser/utils.h +++ b/examples/webenginequick/quicknanobrowser/utils.h @@ -1,18 +1,24 @@ -// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + #ifndef UTILS_H #define UTILS_H +#include <QtQml/qqml.h> + #include <QtCore/QFileInfo> #include <QtCore/QUrl> -class Utils : public QObject { +class Utils : public QObject +{ Q_OBJECT + QML_ELEMENT + QML_SINGLETON public: - Q_INVOKABLE static QUrl fromUserInput(const QString& userInput); + Q_INVOKABLE static QUrl fromUserInput(const QString &userInput); }; -inline QUrl Utils::fromUserInput(const QString& userInput) +inline QUrl Utils::fromUserInput(const QString &userInput) { QFileInfo fileInfo(userInput); if (fileInfo.exists()) |