- Qt 6.12
- Qt WebEngine
- Qt WebEngine Quick Examples
- WebEngine Quick Nano Browser
- Qt 6.12.0 Reference Documentation

# WebEngine Quick Nano Browser

A web browser implemented using the WebEngineView QML type.

Quick Nano Browser demonstrates how to use the Qt WebEngine QML types to develop a small web browser application that consists of a browser window with a title bar, toolbar, tab view, and status bar. The web content is loaded in a web engine view within the tab view. If certificate errors occur, users are prompted for action in a message dialog. The status bar pops up to display the URL of a hovered link.

A web page can issue a request for being displayed in fullscreen mode. Users can allow full screen mode by using a toolbar button. They can leave fullscreen mode by using a keyboard shortcut. Additional toolbar buttons enable moving backwards and forwards in the browser history, reloading tab content, and opening a settings menu for enabling the following features: JavaScript, plugins, fullscreen mode, off the record, HTTP disk cache, autoloading images, and ignoring certificate errors.

#### Running the Example

You can run the example from:

- Qt Creator

  Open the Welcome mode and select the example from Examples. For more information, see Qt Creator: Tutorial: Build and run.

- Qt Extension for Visual Studio Code

  Run the Qt: Open Qt examples command from the Command Palette, and select the example from the list. For more information, see Qt Extension for Visual Studio Code: Tutorial: Build and run.

#### Creating the Main Browser Window

When the browser main window is loaded, it creates an empty tab using the default profile. Each tab is a web engine view that fills the main window.

We create the main window in the BrowserWindow.qml file using the ApplicationWindow type:

     ApplicationWindow {
         id: win
         property WebEngineView currentWebView: tabBar.currentIndex < tabBar.count ? tabLayout.children[tabBar.currentIndex] : null
         ...
         width: 1300
         height: 900
         visible: true
         title: win.currentWebView?.title ?? ""

We use the TabBar Qt Quick control to create a tab bar anchored to the top of the window, and create a new, empty tab:

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

The tab contains a web engine view that loads web content:

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

We use the Action type to create new tabs:

         Action {
             shortcut: StandardKey.AddTab
             onTriggered: {
                 tabBar.createTab(tabBar.count !== 0
                                  ? win.currentWebView.profile
                                  : BrowserManager.defaultProfilePrototype.instance());
                 navigationBar.addressBar.forceActiveFocus();
                 navigationBar.addressBar.selectAll();
             }

We use the TextField Qt Quick Control within a ToolBar to create an address bar that shows the current URL and where users can enter another URL:

     ToolBar {
         id: root
         ...
             TextField {
                 id: addressBar
         ...
                 focus: true
                 Layout.fillWidth: true
                 Binding on text {
                     when: root.currentWebView
                     value: root.currentWebView.url
                 }
                 onAccepted: root.currentWebView.url = Utils.fromUserInput(text)
                 selectByMouse: true
             }

#### Handling Certificate Errors

In case of a certificate error, we check whether it came from the main frame, or from a resource inside the page. Resource errors automatically trigger a certificate rejection, since a user won't have enough context to make a decision. For all other cases, we call the defer() QML method to pause the URL request and wait for user input:

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

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

We use the Dialog type to prompt users to continue or cancel the loading of the web page. If users select Yes, we call the acceptCertificate() method to continue loading content from the URL. If users select No, we call the rejectCertificate() method to reject the request and stop loading content from the URL:

         SslDialog {
             id: sslDialog
             anchors.centerIn: parent
         }

#### Handling Permission Requests

We use the `onPermissionRequested()` signal handler to handle requests for accessing a certain feature or device. The `permission` parameter is an object of the WebEnginePermission type, which can be used to handle the incoming request. We temporarily store this object, since we need to use it to construct the message of the dialog:

                     onPermissionRequested: function(permission) {
                         permissionDialog.permission = permission;
                         permissionDialog.visible = true;
                     }

We display a dialog where the user is asked to grant or deny access. The custom `questionForFeature()` JavaScript function generates a human-readable question about the request. If user selects Yes, we call the grant() method, and if they select No we call deny().

         PermissionDialog {
             id: permissionDialog
             anchors.centerIn: parent
             width: Math.min(win.width, win.height) / 3 * 2
         }

#### Entering and Leaving Fullscreen Mode

We create a menu item for allowing fullscreen mode in a settings menu that we place on the tool bar. Also, we create an action for leaving fullscreen mode by using a keyboard shortcut. We call the accept() method to accept the fullscreen request. The methdod sets the isFullScreen property to be equal to the toggleOn property.

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

When entering fullscreen mode, we display a notification using the FullScreenNotification custom type that we create in FullScreenNotification.qml.

We use the Action type in the settings menu to create a shortcut for leaving fullscreen mode by pressing the escape key:

         Settings {
             id : appSettings
             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: "Escape"
             onTriggered: {
                 if (win.currentWebView.state === "FullScreen") {
                     win.visibility = win.previousVisibility;
                     fullScreenNotification.hide();
                     win.currentWebView.triggerWebAction(WebEngineView.ExitFullScreen);
                 }

                 if (findBar.visible)
                     findBar.visible = false;
             }
         }

#### Handling WebAuth/FIDO UX Requests

We use the `onWebAuthUxRequested()` signal handler to handle requests for WebAuth/FIDO UX. The `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.

                     onWebAuthUxRequested: function(request) {
                         webAuthDialog.init(request);
                     }

The WebEngineWebAuthUxRequest object periodically emits the 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 `WebAuthDialog.qml` for an example of how these signals can be handled.

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

#### Signing Requirement for macOS

To allow web sites access to the location, camera, and microphone when running 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.

#### Files and Attributions

The example uses icons from the Tango Icon Library:

Tango Icon Library | Public Domain

Example project @ code.qt.io