diff options
Diffstat (limited to 'examples/webenginewidgets/simplebrowser')
24 files changed, 876 insertions, 126 deletions
diff --git a/examples/webenginewidgets/simplebrowser/CMakeLists.txt b/examples/webenginewidgets/simplebrowser/CMakeLists.txt index f23bce709..cbaffa6d9 100644 --- a/examples/webenginewidgets/simplebrowser/CMakeLists.txt +++ b/examples/webenginewidgets/simplebrowser/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + cmake_minimum_required(VERSION 3.16) project(simplebrowser LANGUAGES CXX) @@ -24,11 +27,20 @@ qt_add_executable(simplebrowser webpage.cpp webpage.h webpopupwindow.cpp webpopupwindow.h webview.cpp webview.h + webauthdialog.cpp webauthdialog.h webauthdialog.ui ) +if(WIN32) + set_property( + TARGET simplebrowser + APPEND PROPERTY + SOURCES simplebrowser.exe.manifest) +endif() + set_target_properties(simplebrowser PROPERTIES WIN32_EXECUTABLE TRUE MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_GUI_IDENTIFIER "io.qt.examples.webenginewidgets.simplebrowser" ) target_link_libraries(simplebrowser PUBLIC @@ -72,6 +84,24 @@ qt_add_resources(simplebrowser "simplebrowser1" ${simplebrowser1_resource_files} ) +if (APPLE) + set_target_properties(simplebrowser 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 simplebrowser + POST_BUILD COMMAND codesign --force -s - ${exe_path}simplebrowser.app + ) + endif() +endif() + install(TARGETS simplebrowser RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" diff --git a/examples/webenginewidgets/simplebrowser/Info.cmake.macos.plist b/examples/webenginewidgets/simplebrowser/Info.cmake.macos.plist new file mode 100644 index 000000000..7abb7e01a --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/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>Simple Browser would like to give web sites access to your location for demo purposes.</string> + <key>NSMicrophoneUsageDescription</key> + <string>Simple Browser would like to give web sites access to your computer's microphone for demo purposes.</string> + <key>NSCameraUsageDescription</key> + <string>Simple Browser would like to give web sites access to your computer's camera for demo purposes.</string> +</dict> +</plist> diff --git a/examples/webenginewidgets/simplebrowser/browser.cpp b/examples/webenginewidgets/simplebrowser/browser.cpp index 551edcc81..fd68246d0 100644 --- a/examples/webenginewidgets/simplebrowser/browser.cpp +++ b/examples/webenginewidgets/simplebrowser/browser.cpp @@ -6,6 +6,8 @@ #include <QWebEngineSettings> +using namespace Qt::StringLiterals; + Browser::Browser() { // Quit application if the download manager window is the only remaining window @@ -16,13 +18,16 @@ Browser::Browser() &m_downloadManagerWidget, &DownloadManagerWidget::downloadRequested); } -BrowserWindow *Browser::createWindow(bool offTheRecord) +BrowserWindow *Browser::createHiddenWindow(bool offTheRecord) { if (!offTheRecord && !m_profile) { - m_profile.reset(new QWebEngineProfile( - QString::fromLatin1("simplebrowser.%1").arg(qWebEngineChromiumVersion()))); + const QString name = u"simplebrowser."_s + QLatin1StringView(qWebEngineChromiumVersion()); + m_profile.reset(new QWebEngineProfile(name)); m_profile->settings()->setAttribute(QWebEngineSettings::PluginsEnabled, true); m_profile->settings()->setAttribute(QWebEngineSettings::DnsPrefetchEnabled, true); + m_profile->settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true); + m_profile->settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessFileUrls, false); + m_profile->settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, true); QObject::connect(m_profile.get(), &QWebEngineProfile::downloadRequested, &m_downloadManagerWidget, &DownloadManagerWidget::downloadRequested); } @@ -32,6 +37,12 @@ BrowserWindow *Browser::createWindow(bool offTheRecord) QObject::connect(mainWindow, &QObject::destroyed, [this, mainWindow]() { m_windows.removeOne(mainWindow); }); + return mainWindow; +} + +BrowserWindow *Browser::createWindow(bool offTheRecord) +{ + auto *mainWindow = createHiddenWindow(offTheRecord); mainWindow->show(); return mainWindow; } diff --git a/examples/webenginewidgets/simplebrowser/browser.h b/examples/webenginewidgets/simplebrowser/browser.h index 604c45820..dcee68c79 100644 --- a/examples/webenginewidgets/simplebrowser/browser.h +++ b/examples/webenginewidgets/simplebrowser/browser.h @@ -18,6 +18,7 @@ public: QList<BrowserWindow*> windows() { return m_windows; } + BrowserWindow *createHiddenWindow(bool offTheRecord = false); BrowserWindow *createWindow(bool offTheRecord = false); BrowserWindow *createDevToolsWindow(); diff --git a/examples/webenginewidgets/simplebrowser/browserwindow.cpp b/examples/webenginewidgets/simplebrowser/browserwindow.cpp index 6fc9f9d1d..a5a83a2d3 100644 --- a/examples/webenginewidgets/simplebrowser/browserwindow.cpp +++ b/examples/webenginewidgets/simplebrowser/browserwindow.cpp @@ -21,18 +21,12 @@ #include <QWebEngineFindTextResult> #include <QWebEngineProfile> +using namespace Qt::StringLiterals; + BrowserWindow::BrowserWindow(Browser *browser, QWebEngineProfile *profile, bool forDevTools) : m_browser(browser) , m_profile(profile) , m_tabWidget(new TabWidget(profile, this)) - , m_progressBar(nullptr) - , m_historyBackAction(nullptr) - , m_historyForwardAction(nullptr) - , m_stopAction(nullptr) - , m_reloadAction(nullptr) - , m_stopReloadAction(nullptr) - , m_urlLineEdit(nullptr) - , m_favAction(nullptr) { setAttribute(Qt::WA_DeleteOnClose, true); setFocusPolicy(Qt::ClickFocus); @@ -58,7 +52,7 @@ BrowserWindow::BrowserWindow(Browser *browser, QWebEngineProfile *profile, bool m_progressBar->setMaximumHeight(1); m_progressBar->setTextVisible(false); - m_progressBar->setStyleSheet(QStringLiteral("QProgressBar {border: 0px} QProgressBar::chunk {background-color: #da4453}")); + m_progressBar->setStyleSheet(u"QProgressBar {border: 0px} QProgressBar::chunk {background-color: #da4453}"_s); layout->addWidget(m_progressBar); } @@ -300,6 +294,20 @@ QMenu *BrowserWindow::createHelpMenu() return helpMenu; } +static bool isBackspace(const QKeySequence &k) +{ + return (k[0].key() & Qt::Key_unknown) == Qt::Key_Backspace; +} + +// Chromium already handles navigate on backspace when appropriate. +static QList<QKeySequence> removeBackspace(QList<QKeySequence> keys) +{ + const auto it = std::find_if(keys.begin(), keys.end(), isBackspace); + if (it != keys.end()) + keys.erase(it); + return keys; +} + QToolBar *BrowserWindow::createToolBar() { QToolBar *navigationBar = new QToolBar(tr("Navigation")); @@ -307,19 +315,13 @@ QToolBar *BrowserWindow::createToolBar() navigationBar->toggleViewAction()->setEnabled(false); m_historyBackAction = new QAction(this); - QList<QKeySequence> backShortcuts = QKeySequence::keyBindings(QKeySequence::Back); - for (auto it = backShortcuts.begin(); it != backShortcuts.end();) { - // Chromium already handles navigate on backspace when appropriate. - if ((*it)[0].key() == Qt::Key_Backspace) - it = backShortcuts.erase(it); - else - ++it; - } + auto backShortcuts = removeBackspace(QKeySequence::keyBindings(QKeySequence::Back)); // For some reason Qt doesn't bind the dedicated Back key to Back. backShortcuts.append(QKeySequence(Qt::Key_Back)); m_historyBackAction->setShortcuts(backShortcuts); m_historyBackAction->setIconVisibleInMenu(false); - m_historyBackAction->setIcon(QIcon(QStringLiteral(":go-previous.png"))); + m_historyBackAction->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::GoPrevious, + QIcon(":go-previous.png"_L1))); m_historyBackAction->setToolTip(tr("Go back in history")); connect(m_historyBackAction, &QAction::triggered, [this]() { m_tabWidget->triggerWebPageAction(QWebEnginePage::Back); @@ -327,17 +329,12 @@ QToolBar *BrowserWindow::createToolBar() navigationBar->addAction(m_historyBackAction); m_historyForwardAction = new QAction(this); - QList<QKeySequence> fwdShortcuts = QKeySequence::keyBindings(QKeySequence::Forward); - for (auto it = fwdShortcuts.begin(); it != fwdShortcuts.end();) { - if (((*it)[0].key() & Qt::Key_unknown) == Qt::Key_Backspace) - it = fwdShortcuts.erase(it); - else - ++it; - } + auto fwdShortcuts = removeBackspace(QKeySequence::keyBindings(QKeySequence::Forward)); fwdShortcuts.append(QKeySequence(Qt::Key_Forward)); m_historyForwardAction->setShortcuts(fwdShortcuts); m_historyForwardAction->setIconVisibleInMenu(false); - m_historyForwardAction->setIcon(QIcon(QStringLiteral(":go-next.png"))); + m_historyForwardAction->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::GoNext, + QIcon(":go-next.png"_L1))); m_historyForwardAction->setToolTip(tr("Go forward in history")); connect(m_historyForwardAction, &QAction::triggered, [this]() { m_tabWidget->triggerWebPageAction(QWebEnginePage::Forward); @@ -357,12 +354,11 @@ QToolBar *BrowserWindow::createToolBar() navigationBar->addWidget(m_urlLineEdit); auto downloadsAction = new QAction(this); - downloadsAction->setIcon(QIcon(QStringLiteral(":go-bottom.png"))); + downloadsAction->setIcon(QIcon(u":go-bottom.png"_s)); downloadsAction->setToolTip(tr("Show downloads")); navigationBar->addAction(downloadsAction); - connect(downloadsAction, &QAction::triggered, [this]() { - m_browser->downloadManagerWidget().show(); - }); + connect(downloadsAction, &QAction::triggered, + &m_browser->downloadManagerWidget(), &QWidget::show); return navigationBar; } @@ -462,8 +458,10 @@ WebView *BrowserWindow::currentTab() const void BrowserWindow::handleWebViewLoadProgress(int progress) { - static QIcon stopIcon(QStringLiteral(":process-stop.png")); - static QIcon reloadIcon(QStringLiteral(":view-refresh.png")); + static QIcon stopIcon = QIcon::fromTheme(QIcon::ThemeIcon::ProcessStop, + QIcon(":process-stop.png"_L1)); + static QIcon reloadIcon = QIcon::fromTheme(QIcon::ThemeIcon::ViewRefresh, + QIcon(":view-refresh.png"_L1)); if (0 < progress && progress < 100) { m_stopReloadAction->setData(QWebEnginePage::Stop); diff --git a/examples/webenginewidgets/simplebrowser/browserwindow.h b/examples/webenginewidgets/simplebrowser/browserwindow.h index 47fdf6314..55eeb46c2 100644 --- a/examples/webenginewidgets/simplebrowser/browserwindow.h +++ b/examples/webenginewidgets/simplebrowser/browserwindow.h @@ -22,7 +22,8 @@ class BrowserWindow : public QMainWindow Q_OBJECT public: - BrowserWindow(Browser *browser, QWebEngineProfile *profile, bool forDevTools = false); + explicit BrowserWindow(Browser *browser, QWebEngineProfile *profile, + bool forDevTools = false); QSize sizeHint() const override; TabWidget *tabWidget() const; WebView *currentTab() const; @@ -55,14 +56,14 @@ private: Browser *m_browser; QWebEngineProfile *m_profile; TabWidget *m_tabWidget; - QProgressBar *m_progressBar; - QAction *m_historyBackAction; - QAction *m_historyForwardAction; - QAction *m_stopAction; - QAction *m_reloadAction; - QAction *m_stopReloadAction; - QLineEdit *m_urlLineEdit; - QAction *m_favAction; + QProgressBar *m_progressBar = nullptr; + QAction *m_historyBackAction = nullptr; + QAction *m_historyForwardAction = nullptr; + QAction *m_stopAction = nullptr; + QAction *m_reloadAction = nullptr; + QAction *m_stopReloadAction = nullptr; + QLineEdit *m_urlLineEdit = nullptr; + QAction *m_favAction = nullptr; QString m_lastSearch; }; diff --git a/examples/webenginewidgets/simplebrowser/data/3rdparty/qt_attribution.json b/examples/webenginewidgets/simplebrowser/data/3rdparty/qt_attribution.json index d81f5bf23..fbc96416e 100644 --- a/examples/webenginewidgets/simplebrowser/data/3rdparty/qt_attribution.json +++ b/examples/webenginewidgets/simplebrowser/data/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/webenginewidgets/simplebrowser/doc/src/simplebrowser.qdoc b/examples/webenginewidgets/simplebrowser/doc/src/simplebrowser.qdoc index 70d58c1e4..211535204 100644 --- a/examples/webenginewidgets/simplebrowser/doc/src/simplebrowser.qdoc +++ b/examples/webenginewidgets/simplebrowser/doc/src/simplebrowser.qdoc @@ -6,6 +6,7 @@ \title WebEngine Widgets Simple Browser Example \ingroup webengine-widgetexamples \brief A simple browser based on \QWE Widgets. + \examplecategory {Web Technologies} \image simplebrowser.png @@ -67,6 +68,9 @@ \skipto main \printuntil } + To suppress flicker when switching the window to OpenGL rendering, we call + show after the first browser tab has been added. + \section1 Creating Tabs The \c BrowserWindow constructor initializes all the necessary user interface @@ -99,6 +103,25 @@ \skipto TabWidget::setupView \printuntil /^\}/ + \section1 Closing Tabs + + When the user closes a tab, we first trigger the \l {QWebEnginePage::}{RequestClose} web action + on the corresponding \c WebView: + + \quotefromfile webenginewidgets/simplebrowser/tabwidget.cpp + \skipto QTabBar::tabCloseRequested + \printuntil } + + This allows any JavaScript \c beforeunload event listeners to fire, which may + prompt the user with a dialog to confirm that they want to close the page. + In this case, the user can reject the close request and leave the tab open, + otherwise the \l {QWebEnginePage::}{windowCloseRequested} signal is emitted and we close the + tab: + + \quotefromfile webenginewidgets/simplebrowser/tabwidget.cpp + \skipto QWebEnginePage::windowCloseRequested + \printuntil } + \section1 Implementing WebView Functionality The \c WebView is derived from QWebEngineView to support the following @@ -256,6 +279,8 @@ \skipto /^class Browser$/ \printuntil public: \dots + \skipto createHiddenWindow + \printline createHiddenWindow \skipto createWindow \printline createWindow \skipto private: @@ -271,7 +296,7 @@ \quotefromfile webenginewidgets/simplebrowser/browser.cpp - \skipto Browser::createWindow + \skipto Browser::createHiddenWindow \printuntil m_profile.reset \dots @@ -316,6 +341,37 @@ finished or when an error occurs. See \c downloadmanagerwidget.cpp for an example of how these signals can be handled. + \section1 Managing WebAuth/FIDO UX Requests + + WebAuth UX requests are associated with \l QWebEnginePage. Whenever an authenticator + requires user interaction, a UX request is triggered on the QWebEnginePage and + the \l QWebEnginePage::webAuthUxRequested signal is emitted with + \l QWebEngineWebAuthUxRequest, which in this example is forwarded + to \c WebView::handleAuthenticatorRequired: + + \quotefromfile webenginewidgets/simplebrowser/webview.cpp + \skipto connect(page, &QWebEnginePage::webAuthUxRequested + \printline connect(page, &QWebEnginePage::webAuthUxRequested + + This method creates a WebAuth UX dialog and initiates the UX request flow. + + \quotefromfile webenginewidgets/simplebrowser/webview.cpp + \skipto void WebView::handleWebAuthUxRequested(QWebEngineWebAuthUxRequest *request) + \printuntil /^\}/ + + The \l QWebEngineWebAuthUxRequest object periodically emits the \l + {QWebEngineWebAuthUxRequest::}{stateChanged} signal to notify potential + observers of the current WebAuth UX states. The observers update the WebAuth + dialog accordingly. See \c webview.cpp and \c webauthdialog.cpp for an example + of how these signals can be handled. + + \section1 Signing Requirement for macOS + + To allow web sites access to the location, camera, and microphone when running + \e {Simple 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/webenginewidgets/simplebrowser/downloadmanagerwidget.cpp b/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.cpp index bfb857cd8..fdddc4fb0 100644 --- a/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.cpp +++ b/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.cpp @@ -13,7 +13,6 @@ DownloadManagerWidget::DownloadManagerWidget(QWidget *parent) : QWidget(parent) - , m_numDownloads(0) { setupUi(this); } diff --git a/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.h b/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.h index b9d5e9bd7..67df492b9 100644 --- a/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.h +++ b/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.h @@ -30,7 +30,7 @@ private: void add(DownloadWidget *downloadWidget); void remove(DownloadWidget *downloadWidget); - int m_numDownloads; + int m_numDownloads = 0; }; #endif // DOWNLOADMANAGERWIDGET_H diff --git a/examples/webenginewidgets/simplebrowser/downloadwidget.cpp b/examples/webenginewidgets/simplebrowser/downloadwidget.cpp index d4998853e..2fb65e1a8 100644 --- a/examples/webenginewidgets/simplebrowser/downloadwidget.cpp +++ b/examples/webenginewidgets/simplebrowser/downloadwidget.cpp @@ -7,6 +7,8 @@ #include <QUrl> #include <QWebEngineDownloadRequest> +using namespace Qt::StringLiterals; + DownloadWidget::DownloadWidget(QWebEngineDownloadRequest *download, QWidget *parent) : QFrame(parent) , m_download(download) @@ -38,12 +40,11 @@ inline QString DownloadWidget::withUnit(qreal bytes) { if (bytes < (1 << 10)) return tr("%L1 B").arg(bytes); - else if (bytes < (1 << 20)) + if (bytes < (1 << 20)) return tr("%L1 KiB").arg(bytes / (1 << 10), 0, 'f', 2); - else if (bytes < (1 << 30)) + if (bytes < (1 << 30)) return tr("%L1 MiB").arg(bytes / (1 << 20), 0, 'f', 2); - else - return tr("%L1 GiB").arg(bytes / (1 << 30), 0, 'f', 2); + return tr("%L1 GiB").arg(bytes / (1 << 30), 0, 'f', 2); } void DownloadWidget::updateWidget() @@ -63,16 +64,14 @@ void DownloadWidget::updateWidget() m_progressBar->setDisabled(false); m_progressBar->setFormat( tr("%p% - %1 of %2 downloaded - %3/s") - .arg(withUnit(receivedBytes)) - .arg(withUnit(totalBytes)) - .arg(withUnit(bytesPerSecond))); + .arg(withUnit(receivedBytes), withUnit(totalBytes), + withUnit(bytesPerSecond))); } else { m_progressBar->setValue(0); m_progressBar->setDisabled(false); m_progressBar->setFormat( tr("unknown size - %1 downloaded - %2/s") - .arg(withUnit(receivedBytes)) - .arg(withUnit(bytesPerSecond))); + .arg(withUnit(receivedBytes), withUnit(bytesPerSecond))); } break; case QWebEngineDownloadRequest::DownloadCompleted: @@ -80,16 +79,14 @@ void DownloadWidget::updateWidget() m_progressBar->setDisabled(true); m_progressBar->setFormat( tr("completed - %1 downloaded - %2/s") - .arg(withUnit(receivedBytes)) - .arg(withUnit(bytesPerSecond))); + .arg(withUnit(receivedBytes), withUnit(bytesPerSecond))); break; case QWebEngineDownloadRequest::DownloadCancelled: m_progressBar->setValue(0); m_progressBar->setDisabled(true); m_progressBar->setFormat( tr("cancelled - %1 downloaded - %2/s") - .arg(withUnit(receivedBytes)) - .arg(withUnit(bytesPerSecond))); + .arg(withUnit(receivedBytes), withUnit(bytesPerSecond))); break; case QWebEngineDownloadRequest::DownloadInterrupted: m_progressBar->setValue(0); @@ -101,11 +98,13 @@ void DownloadWidget::updateWidget() } if (state == QWebEngineDownloadRequest::DownloadInProgress) { - static QIcon cancelIcon(QStringLiteral(":process-stop.png")); + static QIcon cancelIcon(QIcon::fromTheme(QIcon::ThemeIcon::ProcessStop, + QIcon(":process-stop.png"_L1))); m_cancelButton->setIcon(cancelIcon); m_cancelButton->setToolTip(tr("Stop downloading")); } else { - static QIcon removeIcon(QStringLiteral(":edit-clear.png")); + static QIcon removeIcon(QIcon::fromTheme(QIcon::ThemeIcon::EditClear, + QIcon(":edit-clear.png"_L1))); m_cancelButton->setIcon(removeIcon); m_cancelButton->setToolTip(tr("Remove from list")); } diff --git a/examples/webenginewidgets/simplebrowser/main.cpp b/examples/webenginewidgets/simplebrowser/main.cpp index d0ac175f6..ff4811eae 100644 --- a/examples/webenginewidgets/simplebrowser/main.cpp +++ b/examples/webenginewidgets/simplebrowser/main.cpp @@ -5,17 +5,20 @@ #include "browserwindow.h" #include "tabwidget.h" #include <QApplication> +#include <QLoggingCategory> #include <QWebEngineProfile> #include <QWebEngineSettings> +using namespace Qt::StringLiterals; + QUrl commandLineUrlArgument() { const QStringList args = QCoreApplication::arguments(); for (const QString &arg : args.mid(1)) { - if (!arg.startsWith(QLatin1Char('-'))) + if (!arg.startsWith(u'-')) return QUrl::fromUserInput(arg); } - return QUrl(QStringLiteral("https://www.qt.io")); + return QUrl(u"chrome://qt"_s); } int main(int argc, char **argv) @@ -23,16 +26,19 @@ int main(int argc, char **argv) QCoreApplication::setOrganizationName("QtExamples"); QApplication app(argc, argv); - app.setWindowIcon(QIcon(QStringLiteral(":AppLogoColor.png"))); + 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.createWindow(); + BrowserWindow *window = browser.createHiddenWindow(); window->tabWidget()->setUrl(url); - + window->show(); return app.exec(); } diff --git a/examples/webenginewidgets/simplebrowser/simplebrowser.exe.manifest b/examples/webenginewidgets/simplebrowser/simplebrowser.exe.manifest new file mode 100644 index 000000000..acc401776 --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/simplebrowser.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/webenginewidgets/simplebrowser/simplebrowser.pro b/examples/webenginewidgets/simplebrowser/simplebrowser.pro index 7584cfb01..8598d237a 100644 --- a/examples/webenginewidgets/simplebrowser/simplebrowser.pro +++ b/examples/webenginewidgets/simplebrowser/simplebrowser.pro @@ -10,7 +10,8 @@ HEADERS += \ tabwidget.h \ webpage.h \ webpopupwindow.h \ - webview.h + webview.h \ + webauthdialog.h SOURCES += \ browser.cpp \ @@ -21,13 +22,20 @@ SOURCES += \ tabwidget.cpp \ webpage.cpp \ webpopupwindow.cpp \ - webview.cpp + webview.cpp \ + webauthdialog.cpp + +win32 { + CONFIG -= embed_manifest_exe + QMAKE_MANIFEST = $$PWD/simplebrowser.exe.manifest +} FORMS += \ certificateerrordialog.ui \ passworddialog.ui \ downloadmanagerwidget.ui \ - downloadwidget.ui + downloadwidget.ui \ + webauthdialog.ui RESOURCES += data/simplebrowser.qrc diff --git a/examples/webenginewidgets/simplebrowser/tabwidget.cpp b/examples/webenginewidgets/simplebrowser/tabwidget.cpp index 9e19cf782..acdf49510 100644 --- a/examples/webenginewidgets/simplebrowser/tabwidget.cpp +++ b/examples/webenginewidgets/simplebrowser/tabwidget.cpp @@ -9,6 +9,8 @@ #include <QTabBar> #include <QWebEngineProfile> +using namespace Qt::StringLiterals; + TabWidget::TabWidget(QWebEngineProfile *profile, QWidget *parent) : QTabWidget(parent) , m_profile(profile) @@ -19,7 +21,10 @@ TabWidget::TabWidget(QWebEngineProfile *profile, QWidget *parent) tabBar->setMovable(true); tabBar->setContextMenuPolicy(Qt::CustomContextMenu); connect(tabBar, &QTabBar::customContextMenuRequested, this, &TabWidget::handleContextMenuRequested); - connect(tabBar, &QTabBar::tabCloseRequested, this, &TabWidget::closeTab); + connect(tabBar, &QTabBar::tabCloseRequested, [this](int index) { + if (WebView *view = webView(index)) + view->page()->triggerAction(QWebEnginePage::WebAction::RequestClose); + }); connect(tabBar, &QTabBar::tabBarDoubleClicked, [this](int index) { if (index == -1) createTab(); @@ -32,9 +37,9 @@ TabWidget::TabWidget(QWebEngineProfile *profile, QWidget *parent) if (profile->isOffTheRecord()) { QLabel *icon = new QLabel(this); - QPixmap pixmap(QStringLiteral(":ninja.png")); + QPixmap pixmap(u":ninja.png"_s); icon->setPixmap(pixmap.scaledToHeight(tabBar->height())); - setStyleSheet(QStringLiteral("QTabWidget::tab-bar { left: %1px; }"). + setStyleSheet(u"QTabWidget::tab-bar { left: %1px; }"_s. arg(icon->pixmap().width())); } } diff --git a/examples/webenginewidgets/simplebrowser/tabwidget.h b/examples/webenginewidgets/simplebrowser/tabwidget.h index 08caab52c..a1a893b62 100644 --- a/examples/webenginewidgets/simplebrowser/tabwidget.h +++ b/examples/webenginewidgets/simplebrowser/tabwidget.h @@ -19,7 +19,7 @@ class TabWidget : public QTabWidget Q_OBJECT public: - TabWidget(QWebEngineProfile *profile, QWidget *parent = nullptr); + explicit TabWidget(QWebEngineProfile *profile, QWidget *parent = nullptr); WebView *currentWebView() const; diff --git a/examples/webenginewidgets/simplebrowser/webauthdialog.cpp b/examples/webenginewidgets/simplebrowser/webauthdialog.cpp new file mode 100644 index 000000000..85d944db6 --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/webauthdialog.cpp @@ -0,0 +1,294 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "webauthdialog.h" + +#include <QVBoxLayout> +#include <QRadioButton> +#include <QLineEdit> +#include <QTextEdit> +#include <QPushButton> +#include <QWebEngineView> + +WebAuthDialog::WebAuthDialog(QWebEngineWebAuthUxRequest *request, QWidget *parent) + : QDialog(parent), uxRequest(request), uiWebAuthDialog(new Ui::WebAuthDialog) +{ + uiWebAuthDialog->setupUi(this); + + buttonGroup = new QButtonGroup(this); + buttonGroup->setExclusive(true); + + scrollArea = new QScrollArea(this); + selectAccountWidget = new QWidget(this); + scrollArea->setWidget(selectAccountWidget); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + selectAccountWidget->resize(400, 150); + selectAccountLayout = new QVBoxLayout(selectAccountWidget); + uiWebAuthDialog->m_mainVerticalLayout->addWidget(scrollArea); + selectAccountLayout->setAlignment(Qt::AlignTop); + + updateDisplay(); + + connect(uiWebAuthDialog->buttonBox, &QDialogButtonBox::rejected, this, + qOverload<>(&WebAuthDialog::onCancelRequest)); + + connect(uiWebAuthDialog->buttonBox, &QDialogButtonBox::accepted, this, + qOverload<>(&WebAuthDialog::onAcceptRequest)); + QAbstractButton *button = uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Retry); + connect(button, &QAbstractButton::clicked, this, qOverload<>(&WebAuthDialog::onRetry)); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); +} + +WebAuthDialog::~WebAuthDialog() +{ + QList<QAbstractButton *> buttons = buttonGroup->buttons(); + auto itr = buttons.begin(); + while (itr != buttons.end()) { + QAbstractButton *radioButton = *itr; + delete radioButton; + itr++; + } + + if (buttonGroup) { + delete buttonGroup; + buttonGroup = nullptr; + } + + if (uiWebAuthDialog) { + delete uiWebAuthDialog; + uiWebAuthDialog = nullptr; + } + + // selectAccountWidget and it's children will get deleted when scroll area is destroyed + if (scrollArea) { + delete scrollArea; + scrollArea = nullptr; + } +} + +void WebAuthDialog::updateDisplay() +{ + switch (uxRequest->state()) { + case QWebEngineWebAuthUxRequest::WebAuthUxState::SelectAccount: + setupSelectAccountUI(); + break; + case QWebEngineWebAuthUxRequest::WebAuthUxState::CollectPin: + setupCollectPinUI(); + break; + case QWebEngineWebAuthUxRequest::WebAuthUxState::FinishTokenCollection: + setupFinishCollectTokenUI(); + break; + case QWebEngineWebAuthUxRequest::WebAuthUxState::RequestFailed: + setupErrorUI(); + break; + default: + break; + } + adjustSize(); +} + +void WebAuthDialog::setupSelectAccountUI() +{ + uiWebAuthDialog->m_headingLabel->setText(tr("Choose a Passkey")); + uiWebAuthDialog->m_description->setText(tr("Which passkey do you want to use for ") + + uxRequest->relyingPartyId() + tr("? ")); + uiWebAuthDialog->m_pinGroupBox->setVisible(false); + uiWebAuthDialog->m_mainVerticalLayout->removeWidget(uiWebAuthDialog->m_pinGroupBox); + uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Retry)->setVisible(false); + + clearSelectAccountButtons(); + scrollArea->setVisible(true); + selectAccountWidget->resize(this->width(), this->height()); + QStringList userNames = uxRequest->userNames(); + // Create radio buttons for each name + for (const QString &name : userNames) { + QRadioButton *radioButton = new QRadioButton(name); + selectAccountLayout->addWidget(radioButton); + buttonGroup->addButton(radioButton); + } + + uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Ok")); + uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Ok)->setVisible(true); + uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Cancel)->setVisible(true); + uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Retry)->setVisible(false); +} + +void WebAuthDialog::setupFinishCollectTokenUI() +{ + clearSelectAccountButtons(); + uiWebAuthDialog->m_headingLabel->setText(tr("Use your security key with") + + uxRequest->relyingPartyId()); + uiWebAuthDialog->m_description->setText( + tr("Touch your security key again to complete the request.")); + uiWebAuthDialog->m_pinGroupBox->setVisible(false); + uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Ok)->setVisible(false); + uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Retry)->setVisible(false); + scrollArea->setVisible(false); +} +void WebAuthDialog::setupCollectPinUI() +{ + clearSelectAccountButtons(); + uiWebAuthDialog->m_mainVerticalLayout->addWidget(uiWebAuthDialog->m_pinGroupBox); + uiWebAuthDialog->m_pinGroupBox->setVisible(true); + uiWebAuthDialog->m_confirmPinLabel->setVisible(false); + uiWebAuthDialog->m_confirmPinLineEdit->setVisible(false); + uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Next")); + uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Ok)->setVisible(true); + uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Cancel)->setVisible(true); + uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Retry)->setVisible(false); + scrollArea->setVisible(false); + + QWebEngineWebAuthPinRequest pinRequestInfo = uxRequest->pinRequest(); + + if (pinRequestInfo.reason == QWebEngineWebAuthUxRequest::PinEntryReason::Challenge) { + uiWebAuthDialog->m_headingLabel->setText(tr("PIN Required")); + uiWebAuthDialog->m_description->setText(tr("Enter the PIN for your security key")); + uiWebAuthDialog->m_confirmPinLabel->setVisible(false); + uiWebAuthDialog->m_confirmPinLineEdit->setVisible(false); + } else { + if (pinRequestInfo.reason == QWebEngineWebAuthUxRequest::PinEntryReason::Set) { + uiWebAuthDialog->m_headingLabel->setText(tr("New PIN Required")); + uiWebAuthDialog->m_description->setText(tr("Set new PIN for your security key")); + } else { + uiWebAuthDialog->m_headingLabel->setText(tr("Change PIN Required")); + uiWebAuthDialog->m_description->setText(tr("Change PIN for your security key")); + } + uiWebAuthDialog->m_confirmPinLabel->setVisible(true); + uiWebAuthDialog->m_confirmPinLineEdit->setVisible(true); + } + + QString errorDetails; + switch (pinRequestInfo.error) { + case QWebEngineWebAuthUxRequest::PinEntryError::NoError: + break; + case QWebEngineWebAuthUxRequest::PinEntryError::InternalUvLocked: + errorDetails = tr("Internal User Verification Locked "); + break; + case QWebEngineWebAuthUxRequest::PinEntryError::WrongPin: + errorDetails = tr("Wrong PIN"); + break; + case QWebEngineWebAuthUxRequest::PinEntryError::TooShort: + errorDetails = tr("Too Short"); + break; + case QWebEngineWebAuthUxRequest::PinEntryError::InvalidCharacters: + errorDetails = tr("Invalid Characters"); + break; + case QWebEngineWebAuthUxRequest::PinEntryError::SameAsCurrentPin: + errorDetails = tr("Same as current PIN"); + break; + } + if (!errorDetails.isEmpty()) { + errorDetails += tr(" ") + QString::number(pinRequestInfo.remainingAttempts) + + tr(" attempts remaining"); + } + uiWebAuthDialog->m_pinEntryErrorLabel->setText(errorDetails); +} + +void WebAuthDialog::onCancelRequest() +{ + uxRequest->cancel(); +} + +void WebAuthDialog::onAcceptRequest() +{ + switch (uxRequest->state()) { + case QWebEngineWebAuthUxRequest::WebAuthUxState::SelectAccount: + if (buttonGroup->checkedButton()) { + uxRequest->setSelectedAccount(buttonGroup->checkedButton()->text()); + } + break; + case QWebEngineWebAuthUxRequest::WebAuthUxState::CollectPin: + uxRequest->setPin(uiWebAuthDialog->m_pinLineEdit->text()); + break; + default: + break; + } +} + +void WebAuthDialog::setupErrorUI() +{ + clearSelectAccountButtons(); + QString errorDescription; + QString errorHeading = tr("Something went wrong"); + bool isVisibleRetry = false; + switch (uxRequest->requestFailureReason()) { + case QWebEngineWebAuthUxRequest::RequestFailureReason::Timeout: + errorDescription = tr("Request Timeout"); + break; + case QWebEngineWebAuthUxRequest::RequestFailureReason::KeyNotRegistered: + errorDescription = tr("Key not registered"); + break; + case QWebEngineWebAuthUxRequest::RequestFailureReason::KeyAlreadyRegistered: + errorDescription = tr("You already registered this device." + "Try again with device"); + isVisibleRetry = true; + break; + case QWebEngineWebAuthUxRequest::RequestFailureReason::SoftPinBlock: + errorDescription = + tr("The security key is locked because the wrong PIN was entered too many times." + "To unlock it, remove and reinsert it."); + isVisibleRetry = true; + break; + case QWebEngineWebAuthUxRequest::RequestFailureReason::HardPinBlock: + errorDescription = + tr("The security key is locked because the wrong PIN was entered too many times." + " You'll need to reset the security key."); + break; + case QWebEngineWebAuthUxRequest::RequestFailureReason::AuthenticatorRemovedDuringPinEntry: + errorDescription = + tr("Authenticator removed during verification. Please reinsert and try again"); + break; + case QWebEngineWebAuthUxRequest::RequestFailureReason::AuthenticatorMissingResidentKeys: + errorDescription = tr("Authenticator doesn't have resident key support"); + break; + case QWebEngineWebAuthUxRequest::RequestFailureReason::AuthenticatorMissingUserVerification: + errorDescription = tr("Authenticator missing user verification"); + break; + case QWebEngineWebAuthUxRequest::RequestFailureReason::AuthenticatorMissingLargeBlob: + errorDescription = tr("Authenticator missing Large Blob support"); + break; + case QWebEngineWebAuthUxRequest::RequestFailureReason::NoCommonAlgorithms: + errorDescription = tr("Authenticator missing Large Blob support"); + break; + case QWebEngineWebAuthUxRequest::RequestFailureReason::StorageFull: + errorDescription = tr("Storage Full"); + break; + case QWebEngineWebAuthUxRequest::RequestFailureReason::UserConsentDenied: + errorDescription = tr("User consent denied"); + break; + case QWebEngineWebAuthUxRequest::RequestFailureReason::WinUserCancelled: + errorDescription = tr("User Cancelled Request"); + break; + } + + uiWebAuthDialog->m_headingLabel->setText(errorHeading); + uiWebAuthDialog->m_description->setText(errorDescription); + uiWebAuthDialog->m_description->adjustSize(); + uiWebAuthDialog->m_pinGroupBox->setVisible(false); + uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Ok)->setVisible(false); + uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Retry)->setVisible(isVisibleRetry); + if (isVisibleRetry) + uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Retry)->setFocus(); + uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Cancel)->setVisible(true); + uiWebAuthDialog->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Close")); + scrollArea->setVisible(false); +} + +void WebAuthDialog::onRetry() +{ + uxRequest->retry(); +} + +void WebAuthDialog::clearSelectAccountButtons() +{ + QList<QAbstractButton *> buttons = buttonGroup->buttons(); + auto itr = buttons.begin(); + while (itr != buttons.end()) { + QAbstractButton *radioButton = *itr; + selectAccountLayout->removeWidget(radioButton); + buttonGroup->removeButton(radioButton); + delete radioButton; + itr++; + } +} diff --git a/examples/webenginewidgets/simplebrowser/webauthdialog.h b/examples/webenginewidgets/simplebrowser/webauthdialog.h new file mode 100644 index 000000000..47832c1bb --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/webauthdialog.h @@ -0,0 +1,41 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef WEBAUTHDIALOG_H +#define WEBAUTHDIALOG_H + +#include <QDialog> +#include <QButtonGroup> +#include <QScrollArea> +#include "ui_webauthdialog.h" +#include "qwebenginewebauthuxrequest.h" + +class WebAuthDialog : public QDialog +{ + Q_OBJECT +public: + WebAuthDialog(QWebEngineWebAuthUxRequest *request, QWidget *parent = nullptr); + ~WebAuthDialog(); + + void updateDisplay(); + +private: + QWebEngineWebAuthUxRequest *uxRequest; + QButtonGroup *buttonGroup = nullptr; + QScrollArea *scrollArea = nullptr; + QWidget *selectAccountWidget = nullptr; + QVBoxLayout *selectAccountLayout = nullptr; + + void setupSelectAccountUI(); + void setupCollectPinUI(); + void setupFinishCollectTokenUI(); + void setupErrorUI(); + void onCancelRequest(); + void onRetry(); + void onAcceptRequest(); + void clearSelectAccountButtons(); + + Ui::WebAuthDialog *uiWebAuthDialog; +}; + +#endif // WEBAUTHDIALOG_H diff --git a/examples/webenginewidgets/simplebrowser/webauthdialog.ui b/examples/webenginewidgets/simplebrowser/webauthdialog.ui new file mode 100644 index 000000000..c8a0456d6 --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/webauthdialog.ui @@ -0,0 +1,151 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>WebAuthDialog</class> + <widget class="QDialog" name="WebAuthDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>563</width> + <height>397</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="geometry"> + <rect> + <x>20</x> + <y>320</y> + <width>471</width> + <height>32</height> + </rect> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Retry</set> + </property> + </widget> + <widget class="QLabel" name="m_headingLabel"> + <property name="geometry"> + <rect> + <x>30</x> + <y>20</y> + <width>321</width> + <height>16</height> + </rect> + </property> + <property name="text"> + <string>Heading</string> + </property> + <property name="wordWrap"> + <bool>false</bool> + </property> + </widget> + <widget class="QLabel" name="m_description"> + <property name="geometry"> + <rect> + <x>30</x> + <y>60</y> + <width>491</width> + <height>31</height> + </rect> + </property> + <property name="text"> + <string>Description</string> + </property> + <property name="wordWrap"> + <bool>false</bool> + </property> + </widget> + <widget class="QWidget" name="layoutWidget"> + <property name="geometry"> + <rect> + <x>20</x> + <y>100</y> + <width>471</width> + <height>171</height> + </rect> + </property> + <layout class="QVBoxLayout" name="m_mainVerticalLayout"> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <item> + <widget class="QGroupBox" name="m_pinGroupBox"> + <property name="title"> + <string/> + </property> + <property name="flat"> + <bool>true</bool> + </property> + <widget class="QLabel" name="m_pinLabel"> + <property name="geometry"> + <rect> + <x>10</x> + <y>20</y> + <width>58</width> + <height>16</height> + </rect> + </property> + <property name="text"> + <string>PIN</string> + </property> + </widget> + <widget class="QLineEdit" name="m_pinLineEdit"> + <property name="geometry"> + <rect> + <x>90</x> + <y>20</y> + <width>113</width> + <height>21</height> + </rect> + </property> + </widget> + <widget class="QLabel" name="m_confirmPinLabel"> + <property name="geometry"> + <rect> + <x>10</x> + <y>50</y> + <width>81</width> + <height>16</height> + </rect> + </property> + <property name="text"> + <string>Confirm PIN</string> + </property> + </widget> + <widget class="QLineEdit" name="m_confirmPinLineEdit"> + <property name="geometry"> + <rect> + <x>90</x> + <y>50</y> + <width>113</width> + <height>21</height> + </rect> + </property> + </widget> + <widget class="QLabel" name="m_pinEntryErrorLabel"> + <property name="geometry"> + <rect> + <x>10</x> + <y>80</y> + <width>441</width> + <height>16</height> + </rect> + </property> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </widget> + </item> + </layout> + </widget> + </widget> + <resources/> + <connections/> +</ui> diff --git a/examples/webenginewidgets/simplebrowser/webpage.cpp b/examples/webenginewidgets/simplebrowser/webpage.cpp index 66de5d6d9..807cdf0ff 100644 --- a/examples/webenginewidgets/simplebrowser/webpage.cpp +++ b/examples/webenginewidgets/simplebrowser/webpage.cpp @@ -13,6 +13,8 @@ WebPage::WebPage(QWebEngineProfile *profile, QObject *parent) { connect(this, &QWebEnginePage::selectClientCertificate, this, &WebPage::handleSelectClientCertificate); connect(this, &QWebEnginePage::certificateError, this, &WebPage::handleCertificateError); + connect(this, &QWebEnginePage::desktopMediaRequested, this, + &WebPage::handleDesktopMediaRequest); } void WebPage::handleCertificateError(QWebEngineCertificateError error) @@ -22,31 +24,14 @@ void WebPage::handleCertificateError(QWebEngineCertificateError error) [this, error]() mutable { emit createCertificateErrorDialog(error); }); } -inline QString questionForFeature(QWebEnginePage::Feature feature) -{ - switch (feature) { - case QWebEnginePage::Geolocation: - return WebPage::tr("Allow %1 to access your location information?"); - case QWebEnginePage::MediaAudioCapture: - return WebPage::tr("Allow %1 to access your microphone?"); - case QWebEnginePage::MediaVideoCapture: - return WebPage::tr("Allow %1 to access your webcam?"); - case QWebEnginePage::MediaAudioVideoCapture: - return WebPage::tr("Allow %1 to access your microphone and webcam?"); - case QWebEnginePage::MouseLock: - return WebPage::tr("Allow %1 to lock your mouse cursor?"); - case QWebEnginePage::DesktopVideoCapture: - return WebPage::tr("Allow %1 to capture video of your desktop?"); - case QWebEnginePage::DesktopAudioVideoCapture: - return WebPage::tr("Allow %1 to capture audio and video of your desktop?"); - case QWebEnginePage::Notifications: - return WebPage::tr("Allow %1 to show notification on your desktop?"); - } - return QString(); -} - void WebPage::handleSelectClientCertificate(QWebEngineClientCertificateSelection selection) { // Just select one. selection.select(selection.certificates().at(0)); } + +void WebPage::handleDesktopMediaRequest(const QWebEngineDesktopMediaRequest &request) +{ + // select the primary screen + request.selectScreen(request.screensModel()->index(0)); +} diff --git a/examples/webenginewidgets/simplebrowser/webpage.h b/examples/webenginewidgets/simplebrowser/webpage.h index 7fa2be335..56740f817 100644 --- a/examples/webenginewidgets/simplebrowser/webpage.h +++ b/examples/webenginewidgets/simplebrowser/webpage.h @@ -7,13 +7,14 @@ #include <QWebEnginePage> #include <QWebEngineRegisterProtocolHandlerRequest> #include <QWebEngineCertificateError> +#include <QWebEngineDesktopMediaRequest> class WebPage : public QWebEnginePage { Q_OBJECT public: - WebPage(QWebEngineProfile *profile, QObject *parent = nullptr); + explicit WebPage(QWebEngineProfile *profile, QObject *parent = nullptr); signals: void createCertificateErrorDialog(QWebEngineCertificateError error); @@ -21,6 +22,7 @@ signals: private slots: void handleCertificateError(QWebEngineCertificateError error); void handleSelectClientCertificate(QWebEngineClientCertificateSelection clientCertSelection); + void handleDesktopMediaRequest(const QWebEngineDesktopMediaRequest &request); }; #endif // WEBPAGE_H diff --git a/examples/webenginewidgets/simplebrowser/webpopupwindow.h b/examples/webenginewidgets/simplebrowser/webpopupwindow.h index d13f5f183..0726bf0c2 100644 --- a/examples/webenginewidgets/simplebrowser/webpopupwindow.h +++ b/examples/webenginewidgets/simplebrowser/webpopupwindow.h @@ -19,7 +19,7 @@ class WebPopupWindow : public QWidget Q_OBJECT public: - WebPopupWindow(QWebEngineProfile *profile); + explicit WebPopupWindow(QWebEngineProfile *profile); WebView *view() const; private slots: diff --git a/examples/webenginewidgets/simplebrowser/webview.cpp b/examples/webenginewidgets/simplebrowser/webview.cpp index f882db670..08e044f70 100644 --- a/examples/webenginewidgets/simplebrowser/webview.cpp +++ b/examples/webenginewidgets/simplebrowser/webview.cpp @@ -9,6 +9,7 @@ #include "webview.h" #include "ui_certificateerrordialog.h" #include "ui_passworddialog.h" +#include "webauthdialog.h" #include <QContextMenuEvent> #include <QDebug> #include <QMenu> @@ -17,9 +18,10 @@ #include <QTimer> #include <QStyle> +using namespace Qt::StringLiterals; + WebView::WebView(QWidget *parent) : QWebEngineView(parent) - , m_loadProgress(100) { connect(this, &QWebEngineView::loadStarted, [this]() { m_loadProgress = 0; @@ -57,10 +59,18 @@ WebView::WebView(QWidget *parent) 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] { reload(); }); + QTimer::singleShot(0, this, &WebView::reload); }); } +WebView::~WebView() +{ + if (m_imageAnimationGroup) + delete m_imageAnimationGroup; + + m_imageAnimationGroup = nullptr; +} + inline QString questionForFeature(QWebEnginePage::Feature feature) { switch (feature) { @@ -80,6 +90,8 @@ inline QString questionForFeature(QWebEnginePage::Feature feature) return QObject::tr("Allow %1 to capture audio and video of your desktop?"); case QWebEnginePage::Notifications: return QObject::tr("Allow %1 to show notification on your desktop?"); + case QWebEnginePage::ClipboardReadWrite: + return QObject::tr("Allow %1 to read from and write to the clipboard?"); } return QString(); } @@ -97,8 +109,12 @@ void WebView::setPage(WebPage *page) &WebView::handleProxyAuthenticationRequired); disconnect(oldPage, &QWebEnginePage::registerProtocolHandlerRequested, this, &WebView::handleRegisterProtocolHandlerRequested); + disconnect(oldPage, &QWebEnginePage::webAuthUxRequested, this, + &WebView::handleWebAuthUxRequested); +#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) disconnect(oldPage, &QWebEnginePage::fileSystemAccessRequested, this, &WebView::handleFileSystemAccessRequested); +#endif } createWebActionTrigger(page,QWebEnginePage::Forward); createWebActionTrigger(page,QWebEnginePage::Back); @@ -114,8 +130,11 @@ void WebView::setPage(WebPage *page) &WebView::handleProxyAuthenticationRequired); connect(page, &QWebEnginePage::registerProtocolHandlerRequested, this, &WebView::handleRegisterProtocolHandlerRequested); +#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) connect(page, &QWebEnginePage::fileSystemAccessRequested, this, &WebView::handleFileSystemAccessRequested); +#endif + connect(page, &QWebEnginePage::webAuthUxRequested, this, &WebView::handleWebAuthUxRequested); } int WebView::loadProgress() const @@ -143,15 +162,17 @@ QIcon WebView::favIcon() const return favIcon; if (m_loadProgress < 0) { - static QIcon errorIcon(QStringLiteral(":dialog-error.png")); + static QIcon errorIcon(u":dialog-error.png"_s); return errorIcon; - } else if (m_loadProgress < 100) { - static QIcon loadingIcon(QStringLiteral(":view-refresh.png")); + } + if (m_loadProgress < 100) { + static QIcon loadingIcon = QIcon::fromTheme(QIcon::ThemeIcon::ViewRefresh, + QIcon(":view-refresh.png"_L1)); return loadingIcon; - } else { - static QIcon defaultIcon(QStringLiteral(":text-html.png")); - return defaultIcon; } + + static QIcon defaultIcon(u":text-html.png"_s); + return defaultIcon; } QWebEngineView *WebView::createWindow(QWebEnginePage::WebWindowType type) @@ -189,15 +210,54 @@ void WebView::contextMenuEvent(QContextMenuEvent *event) if (viewSource == actions.cend()) menu->addSeparator(); - QAction *action = new QAction(menu); - action->setText("Open inspector in new window"); + QAction *action = menu->addAction("Open inspector in new window"); connect(action, &QAction::triggered, [this]() { emit devToolsRequested(page()); }); - - QAction *before(inspectElement == actions.cend() ? nullptr : *inspectElement); - menu->insertAction(before, action); } 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::DisallowImageAnimation); + }); + 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::AnimateImageOnce); }); + QAction *allowImageAnimation = editImageAnimation->addAction(tr("Allow all animated images")); + allowImageAnimation->setCheckable(true); + m_imageAnimationGroup->addAction(allowImageAnimation); + connect(allowImageAnimation, &QAction::triggered, [this]() { + handleImageAnimationPolicyChange(QWebEngineSettings::AllowImageAnimation); + }); + + switch (page()->settings()->imageAnimationPolicy()) { + case QWebEngineSettings::AllowImageAnimation: + allowImageAnimation->setChecked(true); + break; + case QWebEngineSettings::AnimateImageOnce: + allowImageAnimationOnce->setChecked(true); + break; + case QWebEngineSettings::DisallowImageAnimation: + disableImageAnimation->setChecked(true); + break; + default: + allowImageAnimation->setChecked(true); + break; + } + + menu->addMenu(editImageAnimation); menu->popup(event->globalPos()); } @@ -235,8 +295,8 @@ void WebView::handleAuthenticationRequired(const QUrl &requestUrl, QAuthenticato passwordDialog.m_iconLabel->setPixmap(icon.pixmap(32, 32)); QString introMessage(tr("Enter username and password for \"%1\" at %2") - .arg(auth->realm()) - .arg(requestUrl.toString().toHtmlEscaped())); + .arg(auth->realm(), + requestUrl.toString().toHtmlEscaped())); passwordDialog.m_infoLabel->setText(introMessage); passwordDialog.m_infoLabel->setWordWrap(true); @@ -290,6 +350,32 @@ void WebView::handleProxyAuthenticationRequired(const QUrl &, QAuthenticator *au } } +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(); +} + +void WebView::onStateChanged(QWebEngineWebAuthUxRequest::WebAuthUxState state) +{ + if (QWebEngineWebAuthUxRequest::WebAuthUxState::Completed == state + || QWebEngineWebAuthUxRequest::WebAuthUxState::Cancelled == state) { + if (m_authDialog) { + delete m_authDialog; + m_authDialog = nullptr; + } + } else { + m_authDialog->updateDisplay(); + } +} + //! [registerProtocolHandlerRequested] void WebView::handleRegisterProtocolHandlerRequested( QWebEngineRegisterProtocolHandlerRequest request) @@ -305,6 +391,7 @@ void WebView::handleRegisterProtocolHandlerRequested( } //! [registerProtocolHandlerRequested] +#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) void WebView::handleFileSystemAccessRequested(QWebEngineFileSystemAccessRequest request) { QString accessType; @@ -322,7 +409,7 @@ void WebView::handleFileSystemAccessRequested(QWebEngineFileSystemAccessRequest Q_UNREACHABLE(); } - auto answer = QMessageBox::question(window(), tr("File system access reques"), + auto answer = QMessageBox::question(window(), tr("File system access request"), tr("Give %1 %2 access to %3?") .arg(request.origin().host()) .arg(accessType) @@ -332,3 +419,12 @@ void WebView::handleFileSystemAccessRequested(QWebEngineFileSystemAccessRequest else request.reject(); } + +void WebView::handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy policy) +{ + if (!page()) + return; + + page()->settings()->setImageAnimationPolicy(policy); +} +#endif // QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) diff --git a/examples/webenginewidgets/simplebrowser/webview.h b/examples/webenginewidgets/simplebrowser/webview.h index 1ba2e74df..c7e7f394c 100644 --- a/examples/webenginewidgets/simplebrowser/webview.h +++ b/examples/webenginewidgets/simplebrowser/webview.h @@ -7,18 +7,25 @@ #include <QIcon> #include <QWebEngineView> #include <QWebEngineCertificateError> +#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) #include <QWebEngineFileSystemAccessRequest> +#endif #include <QWebEnginePage> #include <QWebEngineRegisterProtocolHandlerRequest> +#include <QWebEngineWebAuthUxRequest> +#include <QWebEngineSettings> +#include <QActionGroup> class WebPage; +class WebAuthDialog; class WebView : public QWebEngineView { Q_OBJECT public: - WebView(QWidget *parent = nullptr); + explicit WebView(QWidget *parent = nullptr); + ~WebView(); void setPage(WebPage *page); int loadProgress() const; @@ -41,13 +48,20 @@ private slots: 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; + int m_loadProgress = 100; + WebAuthDialog *m_authDialog = nullptr; + QActionGroup *m_imageAnimationGroup = nullptr; }; #endif |