summaryrefslogtreecommitdiffstats
path: root/examples/webenginewidgets/simplebrowser
diff options
context:
space:
mode:
Diffstat (limited to 'examples/webenginewidgets/simplebrowser')
-rw-r--r--examples/webenginewidgets/simplebrowser/CMakeLists.txt30
-rw-r--r--examples/webenginewidgets/simplebrowser/Info.cmake.macos.plist36
-rw-r--r--examples/webenginewidgets/simplebrowser/browser.cpp17
-rw-r--r--examples/webenginewidgets/simplebrowser/browser.h1
-rw-r--r--examples/webenginewidgets/simplebrowser/browserwindow.cpp62
-rw-r--r--examples/webenginewidgets/simplebrowser/browserwindow.h19
-rw-r--r--examples/webenginewidgets/simplebrowser/data/3rdparty/qt_attribution.json18
-rw-r--r--examples/webenginewidgets/simplebrowser/doc/src/simplebrowser.qdoc58
-rw-r--r--examples/webenginewidgets/simplebrowser/downloadmanagerwidget.cpp1
-rw-r--r--examples/webenginewidgets/simplebrowser/downloadmanagerwidget.h2
-rw-r--r--examples/webenginewidgets/simplebrowser/downloadwidget.cpp29
-rw-r--r--examples/webenginewidgets/simplebrowser/main.cpp16
-rw-r--r--examples/webenginewidgets/simplebrowser/simplebrowser.exe.manifest17
-rw-r--r--examples/webenginewidgets/simplebrowser/simplebrowser.pro14
-rw-r--r--examples/webenginewidgets/simplebrowser/tabwidget.cpp11
-rw-r--r--examples/webenginewidgets/simplebrowser/tabwidget.h2
-rw-r--r--examples/webenginewidgets/simplebrowser/webauthdialog.cpp294
-rw-r--r--examples/webenginewidgets/simplebrowser/webauthdialog.h41
-rw-r--r--examples/webenginewidgets/simplebrowser/webauthdialog.ui151
-rw-r--r--examples/webenginewidgets/simplebrowser/webpage.cpp31
-rw-r--r--examples/webenginewidgets/simplebrowser/webpage.h4
-rw-r--r--examples/webenginewidgets/simplebrowser/webpopupwindow.h2
-rw-r--r--examples/webenginewidgets/simplebrowser/webview.cpp128
-rw-r--r--examples/webenginewidgets/simplebrowser/webview.h18
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