WebEngine ウィジェット シンプルなブラウザの例
Qt WebEngine ウィジェットをベースにしたシンプルなブラウザです。
Simple Browserは、Qt WebEngine C++ classes を使用して、以下の要素を含む小さなウェブブラウザアプリケーションを開発する方法を示します:
- 保存されたページを開いたり、ウィンドウやタブを管理するためのメニューバー
- URLを入力したり、ウェブページの閲覧履歴を前後に移動したりするためのナビゲーションバー。
- タブ内にウェブコンテンツを表示するためのマルチタブエリア。
- ホバーされたリンクを表示するステータスバー
- シンプルなダウンロードマネージャー
ウェブコンテンツは、新しいタブまたは別のウィンドウで開くことができます。ウェブページへのアクセスにHTTP認証とプロキシ認証を使用できます。
サンプルを実行する
Qt Creator からサンプルを実行するには、Welcome モードを開き、Examples からサンプルを選択します。詳細は、Building and Running an Example を参照してください。
クラス階層
まず、これから実装する主なクラスをスケッチします:
Browser
はアプリケーションのウィンドウを管理するクラスです。BrowserWindow
は 、メニュー、ナビゲーション・バー、 、ステータス・バーを示します。QMainWindowTabWidget
TabWidget
は で、1 つまたは複数のブラウザー・タブを含みます。QTabWidgetWebView
は で、 のビューを提供し、 のタブとして追加されます。QWebEngineViewWebPage
TabWidget
WebPage
は で、ウェブサイトのコンテンツを表します。QWebEnginePage
さらに、いくつかの補助クラスを実装します:
ブラウザのメインウィンドウの作成
この例では、Browser
オブジェクトが所有する複数のメインウィンドウをサポートしています。このクラスは、DownloadManagerWidget
も所有し、ブックマークや履歴マネージャなどのさらなる機能にも使用できます。
main.cpp
で、最初のBrowserWindow
インスタンスを作成し、Browser
オブジェクトに追加します。コマンドラインに引数が渡されなければ、Qtホームページを開きます:
int main(int argc, char **argv) { QCoreApplication::setOrganizationName("QtExamples"); QApplication app(argc, argv); app.setWindowIcon(QIcon(u":AppLogoColor.png"_s)); QLoggingCategory::setFilterRules(u"qt.webenginecontext.debug=true"_s); QWebEngineProfile::defaultProfile()->settings()->setAttribute(QWebEngineSettings::PluginsEnabled, true); QWebEngineProfile::defaultProfile()->settings()->setAttribute(QWebEngineSettings::DnsPrefetchEnabled, true); QWebEngineProfile::defaultProfile()->settings()->setAttribute( QWebEngineSettings::ScreenCaptureEnabled, true); QUrl url = commandLineUrlArgument(); Browser browser; BrowserWindow *window = browser.createHiddenWindow(); window->tabWidget()->setUrl(url); window->show(); return app.exec(); }
ウィンドウをOpenGLレンダリングに切り替えたときのちらつきを抑えるため、最初のブラウザ・タブが追加された後にshowを呼び出します。
タブの作成
BrowserWindow
コンストラクタは、必要なユーザーインターフェース関連オブジェクトをすべて初期化します。BrowserWindow
のcentralWidget
にはTabWidget
のインスタンスが含まれます。TabWidget
には、1つまたは複数のWebView
インスタンスがタブとして含まれ、現在選択されているものにシグナルとスロットを委譲します:
class TabWidget : public QTabWidget { ... signals: // current tab/page signals void linkHovered(const QString &link); void loadProgress(int progress); void titleChanged(const QString &title); void urlChanged(const QUrl &url); void favIconChanged(const QIcon &icon); void webActionEnabledChanged(QWebEnginePage::WebAction action, bool enabled); void devToolsRequested(QWebEnginePage *source); void findTextFinished(const QWebEngineFindTextResult &result); public slots: // current tab/page slots void setUrl(const QUrl &url); void triggerWebPageAction(QWebEnginePage::WebAction action); ... };
各タブはWebView
のインスタンスを含む:
WebView *TabWidget::createTab() { WebView *webView = createBackgroundTab(); setCurrentWidget(webView); return webView; } WebView *TabWidget::createBackgroundTab() { WebView *webView = new WebView; WebPage *webPage = new WebPage(m_profile, webView); webView->setPage(webPage); setupView(webView); int index = addTab(webView, tr("(Untitled)")); setTabIcon(index, webView->favIcon()); // Workaround for QTBUG-61770 webView->resize(currentWidget()->size()); webView->show(); return webView; }
TabWidget::setupView()
では、TabWidget
が常に現在選択されているWebView
のシグナルを転送するようにしています:
void TabWidget::setupView(WebView *webView) { QWebEnginePage *webPage = webView->page(); connect(webView, &QWebEngineView::titleChanged, [this, webView](const QString &title) { int index = indexOf(webView); if (index != -1) { setTabText(index, title); setTabToolTip(index, title); } if (currentIndex() == index) emit titleChanged(title); }); connect(webView, &QWebEngineView::urlChanged, [this, webView](const QUrl &url) { int index = indexOf(webView); if (index != -1) tabBar()->setTabData(index, url); if (currentIndex() == index) emit urlChanged(url); }); connect(webView, &QWebEngineView::loadProgress, [this, webView](int progress) { if (currentIndex() == indexOf(webView)) emit loadProgress(progress); }); connect(webPage, &QWebEnginePage::linkHovered, [this, webView](const QString &url) { if (currentIndex() == indexOf(webView)) emit linkHovered(url); }); connect(webView, &WebView::favIconChanged, [this, webView](const QIcon &icon) { int index = indexOf(webView); if (index != -1) setTabIcon(index, icon); if (currentIndex() == index) emit favIconChanged(icon); }); connect(webView, &WebView::webActionEnabledChanged, [this, webView](QWebEnginePage::WebAction action, bool enabled) { if (currentIndex() == indexOf(webView)) emit webActionEnabledChanged(action,enabled); }); connect(webPage, &QWebEnginePage::windowCloseRequested, [this, webView]() { int index = indexOf(webView); if (webView->page()->inspectedPage()) window()->close(); else if (index >= 0) closeTab(index); }); connect(webView, &WebView::devToolsRequested, this, &TabWidget::devToolsRequested); connect(webPage, &QWebEnginePage::findTextFinished, [this, webView](const QWebEngineFindTextResult &result) { if (currentIndex() == indexOf(webView)) emit findTextFinished(result); }); }
タブを閉じる
ユーザーがタブを閉じると、まず対応するWebView
のRequestClose Web アクションをトリガーします:
connect(tabBar, &QTabBar::tabCloseRequested, [this](int index) { if (WebView *view = webView(index)) view->page()->triggerAction(QWebEnginePage::WebAction::RequestClose); });
これにより、JavaScript のbeforeunload
イベントリスナーが起動し、ページを閉じるかどうかを確認するダイアログが表示されます。この場合、ユーザーは閉じる要求を拒否し、タブを開いたままにすることができます。そうでなければ、windowCloseRequested シグナルが発せられ、タブを閉じます:
connect(webPage, &QWebEnginePage::windowCloseRequested, [this, webView]() { int index = indexOf(webView); if (webView->page()->inspectedPage()) window()->close(); else if (index >= 0) closeTab(index); });
WebView 機能の実装
WebView
はQWebEngineView から派生したもので、以下の機能をサポートしています:
renderProcess
が終了した場合のエラーメッセージの表示createWindow
リクエストの処理- コンテキストメニューへのカスタムメニュー項目の追加
まず、必要なメソッドとシグナルを持つWebView を作成します:
class WebView : public QWebEngineView { Q_OBJECT public: explicit WebView(QWidget *parent = nullptr); ... protected: void contextMenuEvent(QContextMenuEvent *event) override; QWebEngineView *createWindow(QWebEnginePage::WebWindowType type) override; signals: void webActionEnabledChanged(QWebEnginePage::WebAction webAction, bool enabled); ... };
エラーメッセージの表示
レンダリング処理が終了した場合は、エラーコードとともにQMessageBox を表示し、ページを再読み込みします:
WebView::WebView(QWidget *parent) : QWebEngineView(parent) { ... connect(this, &QWebEngineView::renderProcessTerminated, [this](QWebEnginePage::RenderProcessTerminationStatus termStatus, int statusCode) { QString status; switch (termStatus) { case QWebEnginePage::NormalTerminationStatus: status = tr("Render process normal exit"); break; case QWebEnginePage::AbnormalTerminationStatus: status = tr("Render process abnormal exit"); break; case QWebEnginePage::CrashedTerminationStatus: status = tr("Render process crashed"); break; case QWebEnginePage::KilledTerminationStatus: status = tr("Render process killed"); break; } QMessageBox::StandardButton btn = QMessageBox::question(window(), status, tr("Render process exited with code: %1\n" "Do you want to reload the page ?").arg(statusCode)); if (btn == QMessageBox::Yes) QTimer::singleShot(0, this, &WebView::reload); }); }
WebWindows の管理
QWebEnginePage::WebWindowTypeたとえば、JavaScript プログラムが新しいウィンドウやダイアログでドキュメントを開くように要求した場合などです。この場合はQWebView::createWindow()
をオーバーライドします:
QWebEngineView *WebView::createWindow(QWebEnginePage::WebWindowType type) { BrowserWindow *mainWindow = qobject_cast<BrowserWindow*>(window()); if (!mainWindow) return nullptr; switch (type) { case QWebEnginePage::WebBrowserTab: { return mainWindow->tabWidget()->createTab(); }
QWebEnginePage::WebDialog
の場合、WebPopupWindow
クラスのインスタンスを作成します:
class WebPopupWindow : public QWidget { Q_OBJECT public: explicit WebPopupWindow(QWebEngineProfile *profile); WebView *view() const; private slots: void handleGeometryChangeRequested(const QRect &newGeometry); private: QLineEdit *m_urlLineEdit; QAction *m_favAction; WebView *m_view; };
コンテキストメニュー項目の追加
コンテキストメニューにメニュー項目を追加して、ユーザが右クリックでインスペクタを新しいウィンドウで開けるようにします。QWebEngineView::contextMenuEvent をオーバーライドし、QWebEnginePage::createStandardContextMenu を使用して、QWebEnginePage::WebAction アクションのデフォルトリストを持つデフォルトのQMenu を作成します。
QWebEnginePage::InspectElement アクションのデフォルト名はInspect です。わかりやすくするために、インスペクタがまだ存在しない場合はOpen Inspector In New Window に、すでに作成されている場合はInspect Element に名前を変更します。
また、QWebEnginePage::ViewSource アクションがメニューにあるかどうかもチェックする。もしなければ、セパレーターも追加しなければならないからだ。
void WebView::contextMenuEvent(QContextMenuEvent *event) { QMenu *menu = createStandardContextMenu(); const QList<QAction *> actions = menu->actions(); auto inspectElement = std::find(actions.cbegin(), actions.cend(), page()->action(QWebEnginePage::InspectElement)); if (inspectElement == actions.cend()) { auto viewSource = std::find(actions.cbegin(), actions.cend(), page()->action(QWebEnginePage::ViewSource)); if (viewSource == actions.cend()) menu->addSeparator(); QAction *action = menu->addAction("Open inspector in new window"); connect(action, &QAction::triggered, [this]() { emit devToolsRequested(page()); }); } else { (*inspectElement)->setText(tr("Inspect element")); } // add conext menu for image policy QMenu *editImageAnimation = new QMenu(tr("Image animation policy")); m_imageAnimationGroup = new QActionGroup(editImageAnimation); m_imageAnimationGroup->setExclusive(true); QAction *disableImageAnimation = editImageAnimation->addAction(tr("Disable all image animation")); disableImageAnimation->setCheckable(true); m_imageAnimationGroup->addAction(disableImageAnimation); connect(disableImageAnimation, &QAction::triggered, [this]() { handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy::Disallow); }); QAction *allowImageAnimationOnce = editImageAnimation->addAction(tr("Allow animated images, but only once")); allowImageAnimationOnce->setCheckable(true); m_imageAnimationGroup->addAction(allowImageAnimationOnce); connect(allowImageAnimationOnce, &QAction::triggered, [this]() { handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy::AnimateOnce); }); QAction *allowImageAnimation = editImageAnimation->addAction(tr("Allow all animated images")); allowImageAnimation->setCheckable(true); m_imageAnimationGroup->addAction(allowImageAnimation); connect(allowImageAnimation, &QAction::triggered, [this]() { handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy::Allow); }); switch (page()->settings()->imageAnimationPolicy()) { case QWebEngineSettings::ImageAnimationPolicy::Allow: allowImageAnimation->setChecked(true); break; case QWebEngineSettings::ImageAnimationPolicy::AnimateOnce: allowImageAnimationOnce->setChecked(true); break; case QWebEngineSettings::ImageAnimationPolicy::Disallow: disableImageAnimation->setChecked(true); break; default: allowImageAnimation->setChecked(true); break; } menu->addMenu(editImageAnimation); menu->popup(event->globalPos()); }
WebPageとWebView機能の実装
QWebEnginePage のサブクラスとしてWebPage
を実装し、QWebEngineView のサブクラスとしてWebView
を実装して、HTTP、プロキシ認証、およびウェブページへのアクセス時の SSL 証明書エラーの無視を有効にします:
class WebPage : public QWebEnginePage { Q_OBJECT public: explicit WebPage(QWebEngineProfile *profile, QObject *parent = nullptr); signals: void createCertificateErrorDialog(QWebEngineCertificateError error); private slots: void handleCertificateError(QWebEngineCertificateError error); void handleSelectClientCertificate(QWebEngineClientCertificateSelection clientCertSelection); void handleDesktopMediaRequest(const QWebEngineDesktopMediaRequest &request); }; class WebView : public QWebEngineView { Q_OBJECT public: explicit WebView(QWidget *parent = nullptr); ~WebView(); void setPage(WebPage *page); int loadProgress() const; bool isWebActionEnabled(QWebEnginePage::WebAction webAction) const; QIcon favIcon() const; protected: void contextMenuEvent(QContextMenuEvent *event) override; QWebEngineView *createWindow(QWebEnginePage::WebWindowType type) override; signals: void webActionEnabledChanged(QWebEnginePage::WebAction webAction, bool enabled); void favIconChanged(const QIcon &icon); void devToolsRequested(QWebEnginePage *source); private slots: void handleCertificateError(QWebEngineCertificateError error); void handleAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth); void handlePermissionRequested(QWebEnginePermission permission); void handleProxyAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth, const QString &proxyHost); void handleRegisterProtocolHandlerRequested(QWebEngineRegisterProtocolHandlerRequest request); #if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) void handleFileSystemAccessRequested(QWebEngineFileSystemAccessRequest request); void handleWebAuthUxRequested(QWebEngineWebAuthUxRequest *request); #endif void handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy policy); private: void createWebActionTrigger(QWebEnginePage *page, QWebEnginePage::WebAction); void onStateChanged(QWebEngineWebAuthUxRequest::WebAuthUxState state); private: int m_loadProgress = 100; WebAuthDialog *m_authDialog = nullptr; QActionGroup *m_imageAnimationGroup = nullptr; };
上記のすべての場合において、適切なダイアログをユーザーに表示します。認証の場合、QAuthenticator オブジェクトに正しいクレデンシャル値を設定する必要があります:
void WebView::handleAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth) { QDialog dialog(window()); dialog.setModal(true); dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint); Ui::PasswordDialog passwordDialog; passwordDialog.setupUi(&dialog); passwordDialog.m_iconLabel->setText(QString()); QIcon icon(window()->style()->standardIcon(QStyle::SP_MessageBoxQuestion, 0, window())); passwordDialog.m_iconLabel->setPixmap(icon.pixmap(32, 32)); QString introMessage(tr("Enter username and password for \"%1\" at %2") .arg(auth->realm(), requestUrl.toString().toHtmlEscaped())); passwordDialog.m_infoLabel->setText(introMessage); passwordDialog.m_infoLabel->setWordWrap(true); if (dialog.exec() == QDialog::Accepted) { auth->setUser(passwordDialog.m_userNameLineEdit->text()); auth->setPassword(passwordDialog.m_passwordLineEdit->text()); } else { // Set authenticator null if dialog is cancelled *auth = QAuthenticator(); } }
handleProxyAuthenticationRequired
シグナル・ハンドラは、HTTPプロキシの認証とまったく同じ手順を実装しています。
SSLエラーが発生した場合、メインフレームから発生したのか、ページ内のリソースから発生したのかを確認します。リソースのエラーは自動的に証明書拒否をトリガーします。それ以外のケースでは、ユーザが証明書を許可または拒否できるダイアログを表示します。
void WebPage::handleCertificateError(QWebEngineCertificateError error) { // Automatically block certificate errors from page resources without prompting the user. // This mirrors the behavior found in other major browsers. if (!error.isMainFrame()) { error.rejectCertificate(); return; } error.defer(); QTimer::singleShot(0, this, [this, error]() mutable { emit createCertificateErrorDialog(error); }); }
ウェブ・ページを開く
このセクションでは、新しいページを開くワークフローについて説明します。ユーザーがナビゲーション・バーに URL を入力し、Enter を押すと、QLineEdit::returnPressed
シグナルが発信され、新しい URL がTabWidget::setUrl
に引き渡される:
BrowserWindow::BrowserWindow(Browser *browser, QWebEngineProfile *profile, bool forDevTools) { ... connect(m_urlLineEdit, &QLineEdit::returnPressed, [this]() { m_tabWidget->setUrl(QUrl::fromUserInput(m_urlLineEdit->text())); }); ... }
呼び出しは現在選択されているタブに転送される:
void TabWidget::setUrl(const QUrl &url) { if (WebView *view = currentWebView()) { view->setUrl(url); view->setFocus(); } }
WebView
のsetUrl()
メソッドは、url
を関連するWebPage
に転送するだけで、 はバックグラウンドでページのコンテンツのダウンロードを開始する。
プライベートブラウジングの実装
プライベートブラウジング、インコグニートモード、または記録外モードは、多くのブラウザの機能で、クッキー、HTTP キャッシュ、または閲覧履歴のような、通常は永続的なデータがメモリ上にのみ保持され、ディスク上には痕跡を残しません。この例では、ウィンドウ・レベルでプライベート・ブラウジングを実装します。あるいは、タブレベルでプライベートブラウジングを実装し、ウィンドウ内のいくつかのタブを通常モードに、他のタブをプライベートモードにすることもできます。
プライベートブラウジングの実装は、Qt WebEngine を使えばとても簡単です。新しいQWebEngineProfile を作成し、デフォルトのプロファイルの代わりにQWebEnginePage で使用するだけです。この例では、この新しいプロファイルはBrowser
オブジェクトが所有しています:
class Browser { public: ... BrowserWindow *createHiddenWindow(bool offTheRecord = false); BrowserWindow *createWindow(bool offTheRecord = false); private: ... QScopedPointer<QWebEngineProfile> m_profile; };
プライベートブラウジングに必要なプロファイルは、最初のウィンドウと一緒に作成されます。QWebEngineProfile のデフォルトのコンストラクタは、すでにそれをオフレコ・モードにしています。
BrowserWindow *Browser::createHiddenWindow(bool offTheRecord) { if (!offTheRecord && !m_profile) { const QString name = u"simplebrowser."_s + QLatin1StringView(qWebEngineChromiumVersion()); m_profile.reset(new QWebEngineProfile(name)); ...
あとは適切なプロファイルを適切なQWebEnginePage オブジェクトに渡すだけです。Browser
オブジェクトは、グローバル・デフォルト・プロファイル(QWebEngineProfile::defaultProfile を参照)か、共有のオフ・ザ・レコード・プロファイル・インスタンスのいずれかを、新しいBrowserWindow
に渡します:
... QObject::connect(m_profile.get(), &QWebEngineProfile::downloadRequested, &m_downloadManagerWidget, &DownloadManagerWidget::downloadRequested); } auto profile = !offTheRecord ? m_profile.get() : QWebEngineProfile::defaultProfile(); auto mainWindow = new BrowserWindow(this, profile, false); return mainWindow; }
BrowserWindow
とTabWidget
オブジェクトは、ウィンドウに含まれるすべてのQWebEnginePage オブジェクトがこのプロファイルを使用するようにします。
ダウンロードの管理
ダウンロードはQWebEngineProfile に関連付けられます。ウェブページでダウンロードがトリガーされると、QWebEngineProfile::downloadRequested シグナルがQWebEngineDownloadRequest とともに発信され、この例ではDownloadManagerWidget::downloadRequested
に転送されます:
Browser::Browser() { // Quit application if the download manager window is the only remaining window m_downloadManagerWidget.setAttribute(Qt::WA_QuitOnClose, false); QObject::connect( QWebEngineProfile::defaultProfile(), &QWebEngineProfile::downloadRequested, &m_downloadManagerWidget, &DownloadManagerWidget::downloadRequested); }
このメソッドは、ユーザーにファイル名の入力を促し(事前に入力された候補が表示されます)、ダウンロードを開始します(ユーザーがSave As ダイアログをキャンセルしない限り):
void DownloadManagerWidget::downloadRequested(QWebEngineDownloadRequest *download) { Q_ASSERT(download && download->state() == QWebEngineDownloadRequest::DownloadRequested); QString path = QFileDialog::getSaveFileName(this, tr("Save as"), QDir(download->downloadDirectory()).filePath(download->downloadFileName())); if (path.isEmpty()) return; download->setDownloadDirectory(QFileInfo(path).path()); download->setDownloadFileName(QFileInfo(path).fileName()); download->accept(); add(new DownloadWidget(download)); show(); }
QWebEngineDownloadRequest オブジェクトは、潜在的なオブザーバーにダウンロードの進行状況を通知するためにreceivedBytesChanged シグナルを定期的に発信し、ダウンロードが終了したときやエラーが発生したときにはstateChanged シグナルを発信する。これらのシグナルの処理方法の例については、downloadmanagerwidget.cpp
を参照してください。
WebAuth/FIDO UXリクエストの管理
WebAuth UXリクエストはQWebEnginePage 。認証機関がユーザーとの対話を要求するたびに、QWebEnginePage で UX リクエストがトリガーされ、QWebEngineWebAuthUxRequest でQWebEnginePage::webAuthUxRequested シグナルが発行されます。この例では、WebView::handleAuthenticatorRequired
に転送されます:
connect(page, &QWebEnginePage::webAuthUxRequested, this, &WebView::handleWebAuthUxRequested);
このメソッドは WebAuth UX ダイアログを生成し、UX リクエストフローを開始する。
void WebView::handleWebAuthUxRequested(QWebEngineWebAuthUxRequest *request) { if (m_authDialog) delete m_authDialog; m_authDialog = new WebAuthDialog(request, window()); m_authDialog->setModal(false); m_authDialog->setWindowFlags(m_authDialog->windowFlags() & ~Qt::WindowContextHelpButtonHint); connect(request, &QWebEngineWebAuthUxRequest::stateChanged, this, &WebView::onStateChanged); m_authDialog->show(); }
QWebEngineWebAuthUxRequest オブジェクトはstateChanged シグナルを定期的に発信し、潜在的なオブザーバに現在の WebAuth UX の状態を通知します。オブザーバはそれに応じて WebAuth ダイアログを更新する。これらのシグナルの処理方法の例については、webview.cpp
とwebauthdialog.cpp
を参照してください。
macOS の署名要件
macOS上でSimple 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.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。