WebEngineクイックナノブラウザ
WebEngineView QML タイプを使用して実装されたウェブブラウザです。
クイックナノブラウザは、Qt WebEngine QML types を使用して、タイトルバー、ツールバー、タブビュー、ステータスバーを備えたブラウザウィンドウで構成される小さなウェブブラウザアプリケーションを開発する方法を示します。ウェブ・コンテンツは、タブ・ビュー内のウェブ・エンジン・ビューにロードされます。証明書エラーが発生した場合、ユーザはメッセージダイアログで対応を求められます。ステータスバーがポップアップして、カーソルを合わせたリンクの URL が表示されます。
ウェブページは、フルスクリーンモードでの表示要求を発行できます。ユーザーはツールバーボタンを使ってフルスクリーンモードを許可できます。キーボードショートカットを使えば、フルスクリーンモードから抜けることができます。追加のツールバーボタンを使用すると、ブラウザの履歴を前後に移動したり、タブの内容を再読み込みしたり、次の機能を有効にするための設定メニューを開いたりできます:JavaScript、プラグイン、フルスクリーンモード、オフザレコード、HTTPディスクキャッシュ、画像のオートロード、証明書エラーの無視。
サンプルを実行する
Qt Creator からサンプルを実行するには、Welcome モードを開き、Examples からサンプルを選択します。詳細については、Building and Running an Example を参照してください。
メインブラウザウィンドウの作成
ブラウザのメインウィンドウがロードされると、デフォルトのプロファイルを使用して空のタブが作成されます。各タブはメインウィンドウを埋めるウェブエンジン・ビューです。
ApplicationWindow タイプを使用して、BrowserWindow.qmlファイルにメインウィンドウを作成します:
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
TabBar Qt Quickコントロールを使用して、ウィンドウの上部に固定されたタブバーを作成し、新しい空のタブを作成します:
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); }
このタブには、ウェブコンテンツをロードするウェブエンジン・ビューが含まれています:
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() } } }
Action 、新しいタブを作成します:
Action { shortcut: StandardKey.AddTab onTriggered: { tabBar.createTab(tabBar.count != 0 ? currentWebView.profile : defaultProfile); addressBar.forceActiveFocus(); addressBar.selectAll(); }
ToolBar 内のTextField Qt Quick Control を使用して、現在の URL を表示し、ユーザーが別の URL を入力できるアドレスバーを作成します:
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 }
証明書エラーの処理
証明書エラーが発生した場合、メインフレームから発生したのか、ページ内のリソースから発生したのかをチェックします。リソースのエラーは自動的に証明書拒否のトリガーとなります。それ以外のケースでは、defer() QMLメソッドを呼び出してURLリクエストを一時停止し、ユーザーの入力を待ちます:
onCertificateError: function(error) { if (!error.isMainFrame) { error.rejectCertificate(); return; } error.defer(); sslDialog.enqueue(error); }
ウェブページの読み込みを続行するかキャンセルするかをユーザーに促すために、ダイアログタイプを使用します。ユーザーがYes を選択した場合、acceptCertificate()メソッドを呼び出し、URLからのコンテンツの読み込みを継続します。ユーザーがNo を選択した場合、rejectCertificate() メソッドを呼び出して要求を拒否し、URL からのコンテンツのロードを停止します:
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 } }
許可リクエストの処理
onPermissionRequested()
シグナルハンドラを使用して、特定の機能またはデバイスへのアクセス要求を処理します。permission
パラメータは WebEnginePermission タイプのオブジェクトです。このオブジェクトは、ダイアログのメッセージを作成するために使用する必要があるため、一時的に保存しておきます:
onPermissionRequested: function(permission) { permissionDialog.permission = permission; permissionDialog.visible = true; }
ユーザーにアクセスを許可するか拒否するかを尋ねるダイアログを表示します。カスタムquestionForFeature()
JavaScript関数は、リクエストに関する人間が読める質問を生成する。ユーザーがYes を選択した場合、grant() メソッドを呼び出し、No を選択した場合、deny() を呼び出します。
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; } }
フルスクリーンモードへの入室と退室
ツールバー上の設定メニューに、フルスクリーンモードを許可するメニュー項目を作成します。また、キーボードショートカットを使ってフルスクリーンモードを解除するアクションも作成します。accept() メソッドを呼び出してフルスクリーン要求を受け付けます。このメソッドでは、isFullScreen プロパティを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(); }
フルスクリーンモードに入るときは、FullScreenNotification.qmlで作成したFullScreenNotificationカスタムタイプを使用して通知を表示します。
設定メニューのAction タイプを使用して、エスケープキーを押してフルスクリーンモードを終了するショートカットを作成します:
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; } }
WebAuth/FIDO UXリクエストの処理
onWebAuthUxRequested()
シグナルハンドラを使用して、WebAuth/FIDO UX のリクエストを処理します。request
パラメータはWebEngineWebAuthUxRequest のインスタンスで、UX リクエストの詳細とリクエストの処理に必要な API が含まれています。これを使用して WebAuthUX ダイアログを構築し、UX リクエストフローを開始します。
onWebAuthUxRequested: function(request) { webAuthDialog.init(request); }
WebEngineWebAuthUxRequest オブジェクトはstateChanged シグナルを定期的に発信し、現在の WebAuth UX の状態を潜在的なオブザーバーに通知します。オブザーバーはそれに応じて WebAuth ダイアログを更新します。状態の変更要求を処理するために onStateChanged() シグナルハンドラを使用します。これらのシグナルの処理方法の例については、WebAuthDialog.qml
を参照してください。
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; }
macOS の署名要件
macOS上でQuick Nano Browserを実行する際、Webサイトが位置情報、カメラ、マイクにアクセスできるようにするには、アプリケーションに署名する必要があります。これはビルド時に自動的に行われますが、ビルド環境に有効な署名IDを設定する必要があります。
ファイルと帰属
このサンプルではTango Icon Libraryのアイコンを使用しています:
Tangoアイコンライブラリ | パブリックドメイン |
©2024 The Qt Company Ltd. 本書に含まれる文書の著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。