WebEngine Quick Nano Browser
Ein Webbrowser, der unter Verwendung des WebEngineView QML-Typs implementiert wurde.
Quick Nano Browser demonstriert, wie man mit dem Qt WebEngine QML types um eine kleine Webbrowser-Anwendung zu entwickeln, die aus einem Browserfenster mit Titelleiste, Symbolleiste, Registerkartenansicht und Statusleiste besteht. Der Webinhalt wird in einer Web-Engine-Ansicht innerhalb der Registerkartenansicht geladen. Treten Zertifikatsfehler auf, wird der Benutzer in einem Meldungsdialog zum Handeln aufgefordert. Die Statusleiste wird eingeblendet, um die URL eines mit dem Mauszeiger gehaltenen Links anzuzeigen.
Eine Webseite kann eine Anfrage zur Anzeige im Vollbildmodus stellen. Der Benutzer kann den Vollbildmodus über eine Schaltfläche in der Symbolleiste zulassen. Sie können den Vollbildmodus mit einem Tastaturkürzel verlassen. Weitere Schaltflächen in der Symbolleiste ermöglichen es, im Browserverlauf vor- und zurückzuspringen, den Inhalt von Registerkarten neu zu laden und ein Einstellungsmenü zum Aktivieren der folgenden Funktionen zu öffnen: JavaScript, Plugins, Vollbildmodus, Off the Record, HTTP Disk Cache, Autoloading von Bildern und Ignorieren von Zertifikatsfehlern.
Ausführen des Beispiels
Zum Ausführen des Beispiels von Qt Creatorzu starten, öffnen Sie den Modus Welcome und wählen Sie das Beispiel unter Examples aus. Weitere Informationen finden Sie unter Erstellen und Ausführen eines Beispiels.
Erstellen des Browser-Hauptfensters
Wenn das Hauptfenster des Browsers geladen wird, wird eine leere Registerkarte unter Verwendung des Standardprofils erstellt. Jede Registerkarte ist eine Web-Engine-Ansicht, die das Hauptfenster füllt.
Wir erstellen das Hauptfenster in der Datei BrowserWindow.qml unter Verwendung des Typs ApplicationWindow:
ApplicationWindow { id: browserWindow property QtObject applicationRoot property Item currentWebView: tabBar.currentIndex < tabBar.count ? tabLayout.children[tabBar.currentIndex] : null ... width: 1300 height: 900 visible: true title: currentWebView && currentWebView.title
Wir verwenden das Steuerelement TabBar Qt Quick , um eine Registerkartenleiste zu erstellen, die am oberen Rand des Fensters verankert ist, und erstellen eine neue, leere Registerkarte:
TabBar { id: tabBar anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right Component.onCompleted: createTab(defaultProfile) 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; })}); tabBar.addItem(newTabButton); if (focusOnNewTab) { tabBar.setCurrentIndex(tabBar.count - 1); }
Die Registerkarte enthält eine Web-Engine-Ansicht, die Webinhalte lädt:
Component { id: tabComponent WebEngineView { id: webEngineView focus: true onLinkHovered: function(hoveredUrl) { if (hoveredUrl == "") hideStatusText.start(); else { statusText.text = hoveredUrl; statusBubble.visible = true; hideStatusText.stop(); } } states: [ State { name: "FullScreen" PropertyChanges { target: tabBar visible: false height: 0 } PropertyChanges { target: 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 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) { var tab = tabBar.createTab(currentWebView.profile, true, request.requestedUrl); tab.acceptAsNewWindow(request); } else if (request.destination === WebEngineNewWindowRequest.InNewBackgroundTab) { var backgroundTab = tabBar.createTab(currentWebView.profile, false); backgroundTab.acceptAsNewWindow(request); } else if (request.destination === WebEngineNewWindowRequest.InNewDialog) { var dialog = applicationRoot.createDialog(currentWebView.profile); dialog.currentWebView.acceptAsNewWindow(request); } else { var window = applicationRoot.createWindow(currentWebView.profile); window.currentWebView.acceptAsNewWindow(request); } } onFullScreenRequested: function(request) { if (request.toggleOn) { webEngineView.state = "FullScreen"; browserWindow.previousVisibility = browserWindow.visibility; browserWindow.showFullScreen(); fullScreenNotification.show(); } else { webEngineView.state = ""; browserWindow.visibility = browserWindow.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); reloadTimer.running = true; } 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); } Timer { id: reloadTimer interval: 0 running: false repeat: false onTriggered: currentWebView.reload() } } }
Wir verwenden den Typ Action, um neue Registerkarten zu erstellen:
Action { shortcut: StandardKey.AddTab onTriggered: { tabBar.createTab(tabBar.count != 0 ? currentWebView.profile : defaultProfile); addressBar.forceActiveFocus(); addressBar.selectAll(); }
Wir verwenden das TextField Qt Quick Control innerhalb eines ToolBar, um eine Adressleiste zu erstellen, die die aktuelle URL anzeigt und in die der Benutzer eine andere URL eingeben kann:
menuBar: ToolBar { id: navigationBar RowLayout { anchors.fill: parent ... TextField { id: addressBar ... focus: true Layout.fillWidth: true Binding on text { when: currentWebView value: currentWebView.url } onAccepted: currentWebView.url = Utils.fromUserInput(text) selectByMouse: true }
Behandlung von Zertifikatsfehlern
Im Falle eines Zertifikatsfehlers prüfen wir, ob er vom Hauptrahmen oder von einer Ressource innerhalb der Seite stammt. Fehler in der Ressource führen automatisch zu einer Ablehnung des Zertifikats, da der Benutzer nicht genug Kontext hat, um eine Entscheidung zu treffen. In allen anderen Fällen rufen wir die QML-Methode defer() auf, um die URL-Anfrage zu unterbrechen und auf Benutzereingaben zu warten:
onCertificateError: function(error) { if (!error.isMainFrame) { error.rejectCertificate(); return; } error.defer(); sslDialog.enqueue(error); }
Wir verwenden den Typ Dialog, um den Benutzer aufzufordern, das Laden der Webseite fortzusetzen oder abzubrechen. Wenn der Benutzer Yes auswählt, rufen wir die Methode acceptCertificate() auf, um das Laden von Inhalten aus der URL fortzusetzen. Wenn der Benutzer No auswählt, rufen wir die Methode rejectCertificate() auf, um die Anfrage abzulehnen und das Laden von Inhalten aus der URL zu beenden:
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 } }
Behandlung von Erlaubnisanfragen
Wir verwenden den Signalhandler onPermissionRequested()
, um Anfragen für den Zugriff auf eine bestimmte Funktion oder ein Gerät zu behandeln. Der Parameter permission
ist ein Objekt vom Typ WebEnginePermission, das zur Bearbeitung der eingehenden Anfrage verwendet werden kann. Wir speichern dieses Objekt vorübergehend, da wir es zum Aufbau der Nachricht des Dialogs verwenden müssen:
onPermissionRequested: function(permission) { permissionDialog.permission = permission; permissionDialog.visible = true; }
Wir zeigen einen Dialog an, in dem der Benutzer gefragt wird, ob er den Zugriff gewähren oder verweigern möchte. Die benutzerdefinierte JavaScript-Funktion questionForFeature()
generiert eine für den Menschen lesbare Frage über die Anfrage. Wenn der Benutzer Yes wählt, rufen wir die Methode grant() auf, und wenn er No wählt, rufen wir deny() auf.
Dialog { id: permissionDialog 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 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; } }
Aufrufen und Verlassen des Vollbildmodus
Wir erstellen einen Menüpunkt für den Vollbildmodus in einem Einstellungsmenü, das wir in der Symbolleiste platzieren. Außerdem erstellen wir eine Aktion zum Verlassen des Vollbildmodus mit Hilfe eines Tastaturkürzels. Wir rufen die Methode accept() auf, um die Anforderung des Vollbildmodus zu akzeptieren. Die Methode setzt die Eigenschaft isFullScreen gleich der Eigenschaft toggleOn.
onFullScreenRequested: function(request) { if (request.toggleOn) { webEngineView.state = "FullScreen"; browserWindow.previousVisibility = browserWindow.visibility; browserWindow.showFullScreen(); fullScreenNotification.show(); } else { webEngineView.state = ""; browserWindow.visibility = browserWindow.previousVisibility; fullScreenNotification.hide(); } request.accept(); }
Wenn der Vollbildmodus aktiviert wird, wird eine Benachrichtigung mit dem benutzerdefinierten Typ FullScreenNotification angezeigt, den wir in FullScreenNotification.qml erstellen.
Wir verwenden den Typ Action im Einstellungsmenü, um eine Abkürzung für das Verlassen des Vollbildmodus durch Drücken der Escape-Taste zu erstellen:
Settings { id : appSettings property alias fullScreenSupportEnabled: fullScreenSupportEnabled.checked property alias autoLoadIconsForPage: autoLoadIconsForPage.checked property alias touchIconsEnabled: touchIconsEnabled.checked property alias webRTCPublicInterfacesOnly : webRTCPublicInterfacesOnly.checked property alias devToolsEnabled: devToolsEnabled.checked property alias pdfViewerEnabled: pdfViewerEnabled.checked property int imageAnimationPolicy: WebEngineSettings.ImageAnimationPolicy.Allow } Action { shortcut: "Escape" onTriggered: { if (currentWebView.state == "FullScreen") { browserWindow.visibility = browserWindow.previousVisibility; fullScreenNotification.hide(); currentWebView.triggerWebAction(WebEngineView.ExitFullScreen); } if (findBar.visible) findBar.visible = false; } }
Behandlung von WebAuth/FIDO UX-Anfragen
Wir verwenden den onWebAuthUxRequested()
Signalhandler, um Anfragen für WebAuth/FIDO UX zu behandeln. Der Parameter request
ist eine Instanz von WebEngineWebAuthUxRequest, die UX-Anfragedetails und APIs enthält, die zur Verarbeitung der Anfrage erforderlich sind. Wir verwenden es, um den WebAuthUX-Dialog zu erstellen und den UX-Anfragefluss zu initiieren.
onWebAuthUxRequested: function(request) { webAuthDialog.init(request); }
Das Objekt WebEngineWebAuthUxRequest sendet in regelmäßigen Abständen das Signal stateChanged aus, um potenzielle Beobachter über den aktuellen WebAuth-UX-Status zu informieren. Die Beobachter aktualisieren den WebAuth-Dialog entsprechend. Wir verwenden den onStateChanged()-Signalhandler, um Zustandsänderungsanforderungen zu behandeln. Siehe WebAuthDialog.qml
für ein Beispiel, wie diese Signale behandelt werden können.
Connections { id: webauthConnection ignoreUnknownSignals: true function onStateChanged(state) { webAuthDialog.setupUI(state); } 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; }
Signieranforderung für macOS
Um Websites den Zugriff auf den Standort, die Kamera und das Mikrofon zu ermöglichen, wenn Quick Nano Browser auf macOS läuft, muss die Anwendung signiert werden. Dies geschieht automatisch beim Erstellen, aber Sie müssen eine gültige Signieridentität für die Build-Umgebung einrichten.
Dateien und Attribute
Das Beispiel verwendet Icons aus der Tango Icon Library:
Tango-Symbolbibliothek | Öffentlicher Bereich |
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.