diff options
Diffstat (limited to 'examples')
32 files changed, 1554 insertions, 94 deletions
diff --git a/examples/webenginequick/quicknanobrowser/BrowserWindow.qml b/examples/webenginequick/quicknanobrowser/BrowserWindow.qml index 3b911262b..2c5b3d86e 100644 --- a/examples/webenginequick/quicknanobrowser/BrowserWindow.qml +++ b/examples/webenginequick/quicknanobrowser/BrowserWindow.qml @@ -563,6 +563,11 @@ ApplicationWindow { settings.imageAnimationPolicy: appSettings.imageAnimationPolicy onCertificateError: function(error) { + if (!error.isMainFrame) { + error.rejectCertificate(); + return; + } + error.defer(); sslDialog.enqueue(error); } @@ -643,10 +648,9 @@ ApplicationWindow { findBar.reset(); } - onFeaturePermissionRequested: function(securityOrigin, feature) { - featurePermissionDialog.securityOrigin = securityOrigin; - featurePermissionDialog.feature = feature; - featurePermissionDialog.visible = true; + onPermissionRequested: function(permission) { + permissionDialog.permission = permission; + permissionDialog.visible = true; } onWebAuthUxRequested: function(request) { webAuthDialog.init(request); @@ -731,7 +735,7 @@ ApplicationWindow { } } Dialog { - id: featurePermissionDialog + id: permissionDialog anchors.centerIn: parent width: Math.min(browserWindow.width, browserWindow.height) / 3 * 2 contentWidth: mainTextForPermissionDialog.width @@ -739,59 +743,58 @@ ApplicationWindow { standardButtons: Dialog.No | Dialog.Yes title: "Permission Request" - property var feature; - property url securityOrigin; + property var permission; contentItem: Item { Label { id: mainTextForPermissionDialog - text: featurePermissionDialog.questionForFeature() + text: permissionDialog.questionForFeature() } } - onAccepted: currentWebView && currentWebView.grantFeaturePermission(securityOrigin, feature, true) - onRejected: currentWebView && currentWebView.grantFeaturePermission(securityOrigin, feature, false) + onAccepted: permission.grant() + onRejected: permission.deny() onVisibleChanged: { if (visible) width = contentWidth + 20; } function questionForFeature() { - var question = "Allow " + securityOrigin + " to " + var question = "Allow " + permission.origin + " to " - switch (feature) { - case WebEngineView.Geolocation: + switch (permission.feature) { + case WebEnginePermission.Geolocation: question += "access your location information?"; break; - case WebEngineView.MediaAudioCapture: + case WebEnginePermission.MediaAudioCapture: question += "access your microphone?"; break; - case WebEngineView.MediaVideoCapture: + case WebEnginePermission.MediaVideoCapture: question += "access your webcam?"; break; - case WebEngineView.MediaVideoCapture: + case WebEnginePermission.MediaAudioVideoCapture: question += "access your microphone and webcam?"; break; - case WebEngineView.MouseLock: + case WebEnginePermission.MouseLock: question += "lock your mouse cursor?"; break; - case WebEngineView.DesktopVideoCapture: + case WebEnginePermission.DesktopVideoCapture: question += "capture video of your desktop?"; break; - case WebEngineView.DesktopAudioVideoCapture: + case WebEnginePermission.DesktopAudioVideoCapture: question += "capture audio and video of your desktop?"; break; - case WebEngineView.Notifications: + case WebEnginePermission.Notifications: question += "show notification on your desktop?"; break; - case WebEngineView.ClipboardReadWrite: + case WebEnginePermission.ClipboardReadWrite: question += "read from and write to your clipboard?"; break; - case WebEngineView.LocalFontsAccess: + case WebEnginePermission.LocalFontsAccess: question += "access the fonts stored on your machine?"; break; default: - question += "access unknown or unsupported feature [" + feature + "] ?"; + question += "access unknown or unsupported feature [" + permission.feature + "] ?"; break; } diff --git a/examples/webenginequick/quicknanobrowser/doc/src/quicknanobrowser.qdoc b/examples/webenginequick/quicknanobrowser/doc/src/quicknanobrowser.qdoc index 1dc209c2e..cff4d3354 100644 --- a/examples/webenginequick/quicknanobrowser/doc/src/quicknanobrowser.qdoc +++ b/examples/webenginequick/quicknanobrowser/doc/src/quicknanobrowser.qdoc @@ -75,13 +75,16 @@ \section1 Handling Certificate Errors - If the certificate of the site being loaded triggers a certificate error, we call the - \l{WebEngineCertificateError::}{defer()} QML method to pause the URL request and wait for user - input: + In case of a certificate error, we check whether it came from the main frame, or from a + resource inside the page. Resource errors automatically trigger a certificate rejection, + since a user won't have enough context to make a decision. + For all other cases, we call the \l{WebEngineCertificateError::}{defer()} QML method to pause + the URL request and wait for user input: \quotefromfile webenginequick/quicknanobrowser/BrowserWindow.qml \skipto onCertificateError \printuntil } + \printuntil } We use the Dialog type to prompt users to continue or cancel the loading of the web page. If users select \uicontrol Yes, we call the @@ -93,25 +96,22 @@ \skipto Dialog { \printuntil /^\ {4}\}/ - \section1 Handling Feature Permission Requests + \section1 Handling Permission Requests - We use the \c onFeaturePermissionRequested() signal handler to handle requests for - accessing a certain feature or device. The \c securityOrigin parameter identifies the - requester web site, and the \c feature parameter is the requested feature. We use these - to construct the message of the dialog: + We use the \c onPermissionRequested() signal handler to handle requests for + accessing a certain feature or device. The \c permission parameter is an object of the + WebEnginePermission type, which can be used to handle the incoming request. We temporarily store + this object, since we need to use it to construct the message of the dialog: \quotefromfile webenginequick/quicknanobrowser/BrowserWindow.qml - \skipto onFeaturePermissionRequested + \skipto onPermissionRequested \printuntil } - We show a dialog where the user is asked to grant or deny access. The custom + We display a dialog where the user is asked to grant or deny access. The custom \c questionForFeature() JavaScript function generates a human-readable question about the request. - If users select \uicontrol Yes, we call the \l{WebEngineView::}{grantFeaturePermission()} - method with a third \c true parameter to grant the \c securityOrigin web site the permission - to access the \c feature. - If users select \uicontrol No, we call the same method but with the \c false parameter to - deny access: + If user selects \uicontrol Yes, we call the \l{webEnginePermission::grant}{grant()} + method, and if they select \uicontrol No we call \l{webEnginePermission::deny}{deny()}. \skipto id: sslDialog \skipto Dialog { diff --git a/examples/webenginewidgets/CMakeLists.txt b/examples/webenginewidgets/CMakeLists.txt index 3ce666d72..ebe34d668 100644 --- a/examples/webenginewidgets/CMakeLists.txt +++ b/examples/webenginewidgets/CMakeLists.txt @@ -3,6 +3,7 @@ qt_internal_add_example(contentmanipulation) qt_internal_add_example(cookiebrowser) +qt_internal_add_example(permissionbrowser) qt_internal_add_example(notifications) qt_internal_add_example(simplebrowser) qt_internal_add_example(push-notifications) diff --git a/examples/webenginewidgets/maps/doc/src/maps.qdoc b/examples/webenginewidgets/maps/doc/src/maps.qdoc index 0175f8b65..f9bd19bfc 100644 --- a/examples/webenginewidgets/maps/doc/src/maps.qdoc +++ b/examples/webenginewidgets/maps/doc/src/maps.qdoc @@ -48,10 +48,10 @@ \printuntil setCentralWidget We then proceed to connect a lambda function to the \l - QWebEnginePage::featurePermissionRequested signal: + QWebEnginePage::permissionRequested signal: \skipto m_view->page() - \printuntil QWebEnginePage::Feature + \printuntil QWebEnginePermission permission This signal is emitted whenever a web page requests to make use of a certain feature or device, including not only location services but also audio @@ -62,15 +62,14 @@ Now comes the part where we actually ask the user for permission: - \printuntil securityOrigin \printuntil }); Note that the question includes the host component of the web site's URI (\c - securityOrigin) to inform the user as to exactly which web site will be + permission.origin()) to inform the user as to exactly which web site will be receiving their location data. - We use the \l QWebEnginePage::setFeaturePermission method to communicate the - user's answer back to the web page. + We use the \l QWebEnginePermission::grant() and \l QWebEnginePermission::deny() + methods to communicate the user's answer back to the web page. Finally we ask the \l QWebEnginePage to load the web page that might want to use location services: diff --git a/examples/webenginewidgets/maps/mainwindow.cpp b/examples/webenginewidgets/maps/mainwindow.cpp index 0d2b49911..ca98ca263 100644 --- a/examples/webenginewidgets/maps/mainwindow.cpp +++ b/examples/webenginewidgets/maps/mainwindow.cpp @@ -13,25 +13,22 @@ MainWindow::MainWindow(QWidget *parent) QWebEnginePage *page = m_view->page(); - connect(page, &QWebEnginePage::featurePermissionRequested, - [this, page](const QUrl &securityOrigin, QWebEnginePage::Feature feature) { - if (feature != QWebEnginePage::Geolocation) + connect(page, &QWebEnginePage::permissionRequested, + [this, page](QWebEnginePermission permission) { + if (permission.feature() != QWebEnginePermission::Geolocation) return; QMessageBox msgBox(this); - msgBox.setText(tr("%1 wants to know your location").arg(securityOrigin.host())); + msgBox.setText(tr("%1 wants to know your location").arg(permission.origin().host())); msgBox.setInformativeText(tr("Do you want to send your current location to this website?")); msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); msgBox.setDefaultButton(QMessageBox::Yes); - if (msgBox.exec() == QMessageBox::Yes) { - page->setFeaturePermission( - securityOrigin, feature, QWebEnginePage::PermissionGrantedByUser); - } else { - page->setFeaturePermission( - securityOrigin, feature, QWebEnginePage::PermissionDeniedByUser); - } + if (msgBox.exec() == QMessageBox::Yes) + permission.grant(); + else + permission.deny(); }); - page->load(QUrl(QStringLiteral("https://maps.google.com"))); + page->load(QUrl(QStringLiteral("https://bing.com/maps"))); } diff --git a/examples/webenginewidgets/notifications/doc/src/notifications.qdoc b/examples/webenginewidgets/notifications/doc/src/notifications.qdoc index f4fe1818f..bd0def910 100644 --- a/examples/webenginewidgets/notifications/doc/src/notifications.qdoc +++ b/examples/webenginewidgets/notifications/doc/src/notifications.qdoc @@ -45,11 +45,11 @@ \section2 Requesting Feature Permissions - We then use the \l QWebEnginePage::featurePermissionRequested() call to + We then use the \l QWebEnginePage::permissionRequested() call to request the user's permission to show notifications on their device. \quotefromfile webenginewidgets/notifications/main.cpp - \skipto featurePermissionRequested + \skipto permissionRequested \printuntil }); \section2 Handling New Notifications diff --git a/examples/webenginewidgets/notifications/main.cpp b/examples/webenginewidgets/notifications/main.cpp index c754aff3f..df9ebff73 100644 --- a/examples/webenginewidgets/notifications/main.cpp +++ b/examples/webenginewidgets/notifications/main.cpp @@ -33,11 +33,11 @@ int main(int argc, char *argv[]) // set custom page to open all page's links for https scheme in system browser view.setPage(new WebEnginePage(&view)); - QObject::connect(view.page(), &QWebEnginePage::featurePermissionRequested, - [&] (const QUrl &origin, QWebEnginePage::Feature feature) { - if (feature != QWebEnginePage::Notifications) + QObject::connect(view.page(), &QWebEnginePage::permissionRequested, + [&] (QWebEnginePermission permission) { + if (permission.feature() != QWebEnginePermission::Notifications) return; - view.page()->setFeaturePermission(origin, feature, QWebEnginePage::PermissionGrantedByUser); + permission.grant(); }); auto profile = view.page()->profile(); @@ -50,4 +50,3 @@ int main(int argc, char *argv[]) view.setUrl(QStringLiteral("qrc:/index.html")); return app.exec(); } - diff --git a/examples/webenginewidgets/permissionbrowser/CMakeLists.txt b/examples/webenginewidgets/permissionbrowser/CMakeLists.txt new file mode 100644 index 000000000..0a92691ba --- /dev/null +++ b/examples/webenginewidgets/permissionbrowser/CMakeLists.txt @@ -0,0 +1,84 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.5) +project(permissionbrowser LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/webenginewidgets/permissionbrowser") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui WebEngineWidgets) + +qt_add_executable(permissionbrowser + permissiondialog.ui + permissionwidget.ui + main.cpp + mainwindow.cpp mainwindow.h mainwindow.ui +) + +if(WIN32) + set_property( + TARGET permissionbrowser + APPEND PROPERTY + SOURCES permissionbrowser.exe.manifest) +endif() + +set_target_properties(permissionbrowser PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_GUI_IDENTIFIER "io.qt.examples.webenginewidgets.permissionbrowser" +) + +target_link_libraries(permissionbrowser PUBLIC + Qt::Core + Qt::Gui + Qt::WebEngineWidgets +) + +# Resources: +set(permissionbrowser_resource_files + "resources/3rdparty/view-refresh.png" + "resources/3rdparty/go-next.png" + "resources/3rdparty/go-previous.png" + "resources/AppLogoColor.png" + "resources/landing.html" +) + +qt_add_resources(permissionbrowser "permissionbrowser" + PREFIX + "/" + BASE + "resources" + FILES + ${permissionbrowser_resource_files} +) + +if (APPLE) + set_target_properties(permissionbrowser 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 permissionbrowser + POST_BUILD COMMAND codesign --force -s - ${exe_path}permissionbrowser.app + ) + endif() +endif() + +install(TARGETS permissionbrowser + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/webenginewidgets/permissionbrowser/Info.cmake.macos.plist b/examples/webenginewidgets/permissionbrowser/Info.cmake.macos.plist new file mode 100644 index 000000000..48ddaadc6 --- /dev/null +++ b/examples/webenginewidgets/permissionbrowser/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>Permission Browser would like to give web sites access to your location for demo purposes.</string> + <key>NSMicrophoneUsageDescription</key> + <string>Permission Browser would like to give web sites access to your computer's microphone for demo purposes.</string> + <key>NSCameraUsageDescription</key> + <string>Permission Browser would like to give web sites access to your computer's camera for demo purposes.</string> +</dict> +</plist> diff --git a/examples/webenginewidgets/permissionbrowser/doc/images/permissionbrowser-example.png b/examples/webenginewidgets/permissionbrowser/doc/images/permissionbrowser-example.png Binary files differnew file mode 100644 index 000000000..56ea60025 --- /dev/null +++ b/examples/webenginewidgets/permissionbrowser/doc/images/permissionbrowser-example.png diff --git a/examples/webenginewidgets/permissionbrowser/doc/src/permissionbrowser.qdoc b/examples/webenginewidgets/permissionbrowser/doc/src/permissionbrowser.qdoc new file mode 100644 index 000000000..a266ec62a --- /dev/null +++ b/examples/webenginewidgets/permissionbrowser/doc/src/permissionbrowser.qdoc @@ -0,0 +1,221 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example webenginewidgets/permissionbrowser + \examplecategory {Web Technologies} + \title WebEngine Widgets Permission Browser Example + \ingroup webengine-widgetexamples + \brief Demonstrates how to handle website permission requests, and manage + existing permissions. + + \image permissionbrowser-example.png + + Permission Browser demonstrates how to use the \l{QWebEnginePermission} class + to manage website permissions. The example includes code for handling incoming + permission requests, as well as modifying already existing permissions. Also + demonstrated are the effects of the different permission persistence policies + defined within the \l{QWebEngineProfile} class. + + \include examples-run.qdocinc + + \section1 Class Definitions + + \section2 MainWindow + + The \c MainWindow class inherits \l QMainWindow. Inside, we declare a convenience + pointer to the \l QVBoxLayout which will lay out the widgets used to manipulate + individual permissions, as well as another convenience pointer to the widget + displaying the currently pending permission request. We also declare a + \l QWebEngineView, which will be used to display the actual webpage contents. + + \quotefromfile webenginewidgets/permissionbrowser/mainwindow.h + \skipto class MainWindow : + \printuntil /^\}/ + + The rest of the layout for the application is defined inside mainwindow.ui, + and was created using Qt Creator's Design mode. \c MainWindow is a child class + of Ui_MainWindow, which is a C++ class generated at compile time from the + definitions found inside mainwindow.ui. + + \section2 PermissionWidget and PermissionDialog + + The \c PermissionWidget class defines a widget corresponding to a single + \l QWebEnginePermission instance. For convenience, the \l QWebEnginePermission + object is stored within. The widget itself has controls for granting, denying, + or deleting the permission; all of this is defined inside \c PermissionWidget.ui. + + \quotefromfile webenginewidgets/permissionbrowser/mainwindow.h + \skipto class PermissionWidget : + \printuntil /^\}/ + + When clicking the "New" button in the main window's UI, a pop-up window will + appear, allowing the user to pre-grant permission to a known origin. That + pop-up is defined by the \c PermissionDialog class: + + \quotefromfile webenginewidgets/permissionbrowser/mainwindow.h + \skipto class PermissionDialog : + \printuntil /^\}/ + + \section1 Handling incoming permission requests + + Whenever a website uses an API that might compromise the user's privacy, the + browser is expected to show them a prompt asking to either grant or deny permission. + The PermissionBrowser example has a dedicated section at the bottom right, which + gets populated with a \c PermissionWidget whenever that happens. + + The \c PermissionWidget displays the permission's origin, the requested + \l QWebEnginePermission::Feature, as well as the current status of that permission. + It also has buttons for granting and denying the permission. Since the permission status + is (by default) remembered, the delete button allows the user to remove the permission + from the underlying storage. + + To achieve all this, we first connect QWebEnginePage's \c permissionRequested signal + to \c MainWindow's \c handlePermissionRequested slot: + + \quotefromfile webenginewidgets/permissionbrowser/mainwindow.cpp + \skipto connect(m_webview->page(), + \printline connect(m_webview->page(), + + The signal handler is relatively simple: it attempts to create a \c PermissionWidget + instance for the provided QWebEnginePermission object, and if it succeeds it + plugs that widget into the QFrame designated for pending permissions. We also + subscribe to \c PermissionWidget's \c permissionModified signal so that we can later move + the \c PermissionWidget from the bottom right to the list of existing widgets above. + + \quotefromfile webenginewidgets/permissionbrowser/mainwindow.cpp + \skipto void MainWindow::handlePermissionRequested + \printuntil /^\}/ + + We only create a new \c PermissionWidget if we don't already have an existing one: + + \quotefromfile webenginewidgets/permissionbrowser/mainwindow.cpp + \skipto PermissionWidget *MainWindow::create PermissionWidget + \printuntil /^\}/ + + \target PermissionWidgetConstructor + \section1 Modifying a permission and displaying it to the user + + The QWebEnginePermission interface provides the \l {QWebEnginePermission::grant}{grant}() + and {QWebEnginePermission::deny}{deny}() functions, which are all that's needed to + change the status of a permission. If the application needs to forget about a permission, + we use the {QWebEnginePermission::reset}{reset}() function. + + Inside the \c PermissionWidget constructor, we hook those function up to the buttons' + \c clicked signal, so that we can execute the relevant functionality on the + QWebEnginePermission object. + + Whenever a button is pressed, we emit the \c permissionModified signal, which + \c MainWindow uses to know when it needs to move the widget from the bottom-right + to the list of existing permissions. We also make sure to call \c updateState(), which + handles visual updates to the widget. When the delete button is pressed, we also make + sure mark the widget for deletion, since we only want to display existing permissions to + the user. + + \quotefromfile webenginewidgets/permissionbrowser/mainwindow.cpp + \skipto PermissionWidget::PermissionWidget + \printuntil /^\}/ + + The \c updateState() function displays the data supplied by QWebEnginePermission + to the user. It also makes sure that, when a permission is in the + \l QWebEnginePermission::Invalid state, the buttons for granting or denying it + are disabled. + + \quotefromfile webenginewidgets/permissionbrowser/mainwindow.cpp + \skipto void PermissionWidget::updateState() + \printuntil /^\}/ + + When a pending permission is granted or denied, we want to move the associated + widget to the list above, which contains all currently existing permissions. + We do this in the MainWindow::handlePermissionModified slot. + + \quotefromfile webenginewidgets/permissionbrowser/mainwindow.cpp + \skipto void MainWindow::handlePermissionModified + \printuntil /^\}/ + + Notably, we only do this in cases where we know the permission is remembered. + This is not true for transient \c Features, + which require a permission prompt be shown to the user every time they're needed. + We also exclude permissions with a \l QWebEnginePermission::Ask state, which + indicates that the permission was \l {QWebEnginePermission::reset}{reset}(), + and we don't add anything to the list of existing permissions when + \l QWebEngineProfile::persistentPermissionsPolicy is set to + \c NoPersistentPermissions. + + \note Check the \l QWebEnginePermission::Feature documentation to see which + \c Features are transient. + + \section1 Displaying and modifying existing permissions + + By default, permissions are stored to disk and retrieved again on application + startup. To get a list of all existing website permissions, we call + \l QWebEngineProfile::listPermissions(): + + \quotefromfile webenginewidgets/permissionbrowser/mainwindow.cpp + \skipto void MainWindow::loadStoredPermissions() + \printuntil /^\}/ + + For every permission in the list, we simply construct a new \c PermissionWidget, and + add it to the list on the right-hand side of the screen. Existing permissions + are modified \l {PermissionWidgetConstructor}{using the exact same API as pending ones}. + + \section1 Pre-granting permissions + + Certain permissions may be granted in advance, provided the origin and Feature type + are known. Clicking on the "New" button in the top right will create a pop-up + dialog that allows you to do just that. The dialog is implemented by the + \c PermissionDialog class: + + \quotefromfile webenginewidgets/permissionbrowser/mainwindow.cpp + \skipto PermissionDialog::PermissionDialog + \printuntil /^\}/ + + We populate the \l QComboBox using the QMetaEnum type associated with + \l QWebEnginePermission::Feature. We make sure to filter out transient + features, since pre-granting these is not supported. + + We display the dialog and add show the resulting \c PermissionWidget + in the UI inside the \c MainWindow::handleNewClicked slot. The new + permission is handled the same way we would if a website requested it: + by calling \c handlePermissionRequested(). + + \quotefromfile webenginewidgets/permissionbrowser/mainwindow.cpp + \skipto void MainWindow::handleNewClicked() + \printuntil /^\}/ + + \section1 Changing the permission persistence policy + + By default, permissions are stored to disk for every named QWebEngineProfile, + and in memory for every unnamed/off-the-record one. Normally, this setting + won't be changed at runtime, but this example explores the effects + of each option. + + \list + \li \l QWebEngineProfile::PersistentPermissionsOnDisk is the default, and it ensures + that any permissions that have been granted in the current application run will be + loaded back up at next startup. A permission onlycneeds to be granted once, and + subsequent uses of the API that triggered the request will automatically be granted, + until the application calls QWebEnginePermission::reset(). + li \l QWebEngineProfile::PersistentPermissionsInMemory Has the same behavior as above, + except that permissions will be destroyed at application exit, and not committed + to disk. + li \l QWebEngineProfile::NoPersistentPermissions makes sure permissions are never + remembered, and all act as if they're transient. Thus, every time a web API needs + a permission, a new prompt will be shown to the user. This option is intended for + backwards compatibility and applications which implement their own permission storage. + \endlist + + To ensure the user will be shown previously existing permissions, we need to call + \l QWebEngineProfile::listPermissions(): + + \quotefromfile webenginewidgets/permissionbrowser/mainwindow.cpp + \skipto void MainWindow::loadStoredPermissions() + \printuntil /^\}/ + + This is done one time at startup, as well as whenever the user changes the policy + from the \l QComboBox from the top right. + + \quotefromfile webenginewidgets/permissionbrowser/mainwindow.cpp + \skipto void MainWindow::handlePolicyComboBoxIndexChanged + \printuntil /^\}/ +*/ diff --git a/examples/webenginewidgets/permissionbrowser/main.cpp b/examples/webenginewidgets/permissionbrowser/main.cpp new file mode 100644 index 000000000..c1e2c2566 --- /dev/null +++ b/examples/webenginewidgets/permissionbrowser/main.cpp @@ -0,0 +1,18 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "mainwindow.h" +#include <QApplication> +#include <QLoggingCategory> +#include <QUrl> + +int main(int argc, char *argv[]) +{ + QCoreApplication::setOrganizationName("QtExamples"); + QApplication app(argc, argv); + app.setWindowIcon(QIcon(QString(u":AppLogoColor.png"))); + MainWindow window(QUrl("qrc:/landing.html")); + window.resize(1024, 768); + window.show(); + return app.exec(); +} diff --git a/examples/webenginewidgets/permissionbrowser/mainwindow.cpp b/examples/webenginewidgets/permissionbrowser/mainwindow.cpp new file mode 100644 index 000000000..2fe3c3b17 --- /dev/null +++ b/examples/webenginewidgets/permissionbrowser/mainwindow.cpp @@ -0,0 +1,270 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "mainwindow.h" +#include <QWebEngineProfile> +#include <QWebEnginePermission> +#include <QWebEngineSettings> +#include <QMetaEnum> +#include <QUrl> + +PermissionDialog::PermissionDialog(const QWebEngineProfile *profile, QWidget *parent) + : QDialog(parent) + , m_profile(profile) + , m_permission(nullptr) +{ + setupUi(this); + + auto metaEnum = QMetaEnum::fromType<QWebEnginePermission::Feature>(); + Q_ASSERT(metaEnum.value(QWebEnginePermission::Unsupported) == 0); + for (int i = 1; i < metaEnum.keyCount(); ++i) { + QWebEnginePermission::Feature feature = QWebEnginePermission::Feature(metaEnum.value(i)); + if (!QWebEnginePermission::isTransient(feature)) + m_featureComboBox->addItem(metaEnum.valueToKey(feature), QVariant(feature)); + } +} + +PermissionDialog::~PermissionDialog() +{ + delete m_permission; +} + +QWebEnginePermission PermissionDialog::permission() +{ + return m_profile->getPermission(QUrl(m_originLineEdit->text()), + QWebEnginePermission::Feature(m_featureComboBox->itemData(m_featureComboBox->currentIndex()).toInt())); +} + +PermissionWidget::PermissionWidget(const QWebEnginePermission &permission, QWidget *parent) + : QWidget(parent) + , m_permission(permission) +{ + setupUi(this); + connect(m_deleteButton, &QPushButton::clicked, [this]() { + m_permission.reset(); + emit permissionModified(this); + deleteLater(); + }); + + connect(m_grantButton, &QPushButton::clicked, [this]() { + m_permission.grant(); + updateState(); + emit permissionModified(this); + }); + + connect(m_denyButton, &QPushButton::clicked, [this]() { + m_permission.deny(); + updateState(); + emit permissionModified(this); + }); + + updateState(); +} + +void PermissionWidget::updateState() +{ + switch (m_permission.state()) { + case QWebEnginePermission::Invalid: + m_stateLabel->setText("<font color='gray'>Invalid</font>"); + m_grantButton->setEnabled(false); + m_denyButton->setEnabled(false); + break; + case QWebEnginePermission::Ask: + m_stateLabel->setText("<font color='yellow'>Waiting for response</font>"); + break; + case QWebEnginePermission::Granted: + m_stateLabel->setText("<font color='green'>Granted</font>"); + break; + case QWebEnginePermission::Denied: + m_stateLabel->setText("<font color='red'>Denied</font>"); + break; + } + + m_typeLabel->setText(QMetaEnum::fromType<QWebEnginePermission::Feature>().valueToKey(m_permission.feature())); + m_originLabel->setText(m_permission.origin().toDisplayString()); +} + +MainWindow::MainWindow(const QUrl &url) + : QMainWindow() + , m_layout(new QVBoxLayout) + , m_profile(new QWebEngineProfile("permissionbrowser")) + , m_webview(new QWebEngineView(m_profile, this)) + , m_pendingWidget(nullptr) +{ + setupUi(this); + m_urlLineEdit->setText(url.toString()); + + m_layout->addItem(new QSpacerItem(0,0, QSizePolicy::Minimum, QSizePolicy::Expanding)); + m_layout->setContentsMargins(0, 0, 0, 0); + m_layout->setSpacing(0); + + QWidget *w = new QWidget(); + w->setLayout(m_layout); + + m_storedScrollArea->setWidget(w); + m_storedScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + (new QVBoxLayout(m_pendingFrame))->setContentsMargins(0, 0, 0, 0); + + loadStoredPermissions(); + + connect(m_deleteAllButton, &QPushButton::clicked, this, &MainWindow::handleDeleteAllClicked); + connect(m_newButton, &QPushButton::clicked, this, &MainWindow::handleNewClicked); + connect(m_refreshButton, &QPushButton::clicked, this, &MainWindow::handleRefreshClicked); + connect(m_backButton, &QPushButton::clicked, this, &MainWindow::handleBackClicked); + connect(m_forwardButton, &QPushButton::clicked, this, &MainWindow::handleForwardClicked); + connect(m_policyComboBox, &QComboBox::currentIndexChanged, this, &MainWindow::handlePolicyComboBoxIndexChanged); + connect(m_webview, &QWebEngineView::urlChanged, this, &MainWindow::handleUrlChanged); + connect(m_webview->page(), &QWebEnginePage::permissionRequested, this, &MainWindow::handlePermissionRequested); + + m_profile->settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, true); + m_profile->settings()->setAttribute(QWebEngineSettings::JavascriptCanAccessClipboard, true); + + m_frame->layout()->addWidget(m_webview); + static_cast<QVBoxLayout *>(m_frame->layout())->setStretchFactor(m_webview, 1); + m_webview->load(url); +} + +MainWindow::~MainWindow() +{ + delete m_webview; + delete m_profile; +} + +void MainWindow::handlePermissionRequested(QWebEnginePermission permission) +{ + PermissionWidget *widget = createPermissionWidget(permission); + if (widget) { + m_pendingFrame->layout()->addWidget(widget); + connect(widget, &PermissionWidget::permissionModified, this, &MainWindow::handlePermissionModified); + + if (m_pendingWidget) + m_pendingWidget->deleteLater(); + + m_pendingWidget = widget; + } +} + +void MainWindow::handlePermissionModified(PermissionWidget *widget) +{ + if (!m_pendingWidget || m_pendingWidget != widget) + return; + + m_pendingFrame->layout()->removeWidget(widget); + m_pendingWidget = nullptr; + + if (QWebEnginePermission::isTransient(widget->m_permission.feature()) + || widget->m_permission.state() == QWebEnginePermission::Ask + || m_profile->persistentPermissionsPolicy() == QWebEngineProfile::NoPersistentPermissions) { + + widget->deleteLater(); + return; + } + + m_layout->insertWidget(0, widget); +} + +void MainWindow::handleUrlChanged(const QUrl &url) +{ + m_urlLineEdit->setText(url.toString()); +} + +void MainWindow::handleDeleteAllClicked() +{ + for (int i = m_layout->count() - 1; i >= 0; i--) { + PermissionWidget *widget = qobject_cast<PermissionWidget *>(m_layout->itemAt(i)->widget()); + if (!widget) + continue; + + widget->m_permission.reset(); + widget->deleteLater(); + } +} + +void MainWindow::handleNewClicked() +{ + PermissionDialog dialog(m_profile); + if (dialog.exec() == QDialog::Accepted) { + handlePermissionRequested(dialog.permission()); + } +} + +void MainWindow::handleRefreshClicked() +{ + m_webview->load(QUrl::fromUserInput(m_urlLineEdit->text())); +} + +void MainWindow::handleBackClicked() +{ + m_webview->triggerPageAction(QWebEnginePage::Back); +} + +void MainWindow::handleForwardClicked() +{ + m_webview->triggerPageAction(QWebEnginePage::Forward); +} + +void MainWindow::handlePolicyComboBoxIndexChanged(int index) +{ + QWebEngineProfile::PersistentPermissionsPolicy policy; + switch (index) { + case 0: + policy = QWebEngineProfile::PersistentPermissionsOnDisk; + break; + case 1: + policy = QWebEngineProfile::PersistentPermissionsInMemory; + break; + case 2: + policy = QWebEngineProfile::NoPersistentPermissions; + break; + } + + if (policy == m_profile->persistentPermissionsPolicy()) + return; + + for (int i = m_layout->count() - 1; i >= 0; i--) { + PermissionWidget *widget = qobject_cast<PermissionWidget *>(m_layout->itemAt(i)->widget()); + if (!widget) + continue; + + widget->deleteLater(); + } + + m_profile->setPersistentPermissionsPolicy(policy); + loadStoredPermissions(); +} + +bool MainWindow::containsPermission(const QWebEnginePermission &permission) +{ + for (const auto *w: std::as_const(m_storedScrollArea->widget()->children())) { + const PermissionWidget *widget = qobject_cast<const PermissionWidget *>(w); + if (!widget) + continue; + const QWebEnginePermission &widgetPermission = widget->m_permission; + if (widgetPermission == permission) + return true; + } + + if (m_pendingWidget && m_pendingWidget->m_permission == permission) + return true; + + return false; +} + +PermissionWidget *MainWindow::createPermissionWidget(const QWebEnginePermission &permission) +{ + if (containsPermission(permission)) + return nullptr; + + return new PermissionWidget(permission, this); +} + +void MainWindow::loadStoredPermissions() +{ + QList<QWebEnginePermission> permissionsList = m_profile->listPermissions(); + for (QWebEnginePermission &permission : permissionsList) { + PermissionWidget *widget = createPermissionWidget(permission); + if (widget) + m_layout->insertWidget(0, widget); + } +} diff --git a/examples/webenginewidgets/permissionbrowser/mainwindow.h b/examples/webenginewidgets/permissionbrowser/mainwindow.h new file mode 100644 index 000000000..8f7a09450 --- /dev/null +++ b/examples/webenginewidgets/permissionbrowser/mainwindow.h @@ -0,0 +1,74 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "ui_mainwindow.h" +#include "ui_permissiondialog.h" +#include "ui_permissionwidget.h" +#include <QDialog> +#include <QMainWindow> +#include <QWebEngineView> +#include <QWebEnginePermission> + +QT_BEGIN_NAMESPACE +class QWebEngineView; +class QWebEngineProfile; +class QLineEdit; +QT_END_NAMESPACE + +class PermissionDialog : public QDialog, public Ui_PermissionDialog +{ + Q_OBJECT +public: + PermissionDialog(const QWebEngineProfile *profile, QWidget *parent = nullptr); + ~PermissionDialog(); + QWebEnginePermission permission(); + +private: + const QWebEngineProfile *m_profile; + QWebEnginePermission *m_permission; +}; + +class PermissionWidget : public QWidget, public Ui_PermissionWidget +{ + Q_OBJECT +public: + PermissionWidget(const QWebEnginePermission &permission, QWidget *parent = nullptr); + + QWebEnginePermission m_permission; + +signals: + void permissionModified(PermissionWidget *widget); + +private: + void updateState(); +}; + +class MainWindow : public QMainWindow, public Ui_MainWindow +{ + Q_OBJECT +public: + explicit MainWindow(const QUrl &url); + ~MainWindow(); + +private slots: + void handlePermissionRequested(QWebEnginePermission permission); + void handleUrlChanged(const QUrl &url); + + void handlePermissionModified(PermissionWidget *widget); + void handleDeleteAllClicked(); + void handleNewClicked(); + void handleRefreshClicked(); + void handleBackClicked(); + void handleForwardClicked(); + void handlePolicyComboBoxIndexChanged(int index); + +private: + bool containsPermission(const QWebEnginePermission &permission); + PermissionWidget *createPermissionWidget(const QWebEnginePermission &permission); + void loadStoredPermissions(); + + QVBoxLayout *m_layout; + QWebEngineProfile *m_profile; + QWebEngineView *m_webview; + PermissionWidget *m_pendingWidget; +}; diff --git a/examples/webenginewidgets/permissionbrowser/mainwindow.ui b/examples/webenginewidgets/permissionbrowser/mainwindow.ui new file mode 100644 index 000000000..4496b8c07 --- /dev/null +++ b/examples/webenginewidgets/permissionbrowser/mainwindow.ui @@ -0,0 +1,356 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>1600</width> + <height>900</height> + </rect> + </property> + <property name="windowTitle"> + <string>Permission Browser</string> + </property> + <widget class="QWidget" name="centralWidget"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QFrame" name="m_frame"> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <layout class="QVBoxLayout" name="verticalLayout" stretch="0"> + <item> + <widget class="QWidget" name="widget" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QPushButton" name="m_backButton"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset resource="permissionbrowser.qrc"> + <normaloff>:3rdparty/go-previous.png</normaloff>:3rdparty/go-previous.png</iconset> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="m_forwardButton"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset resource="permissionbrowser.qrc"> + <normaloff>:3rdparty/go-next.png</normaloff>:3rdparty/go-next.png</iconset> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="m_refreshButton"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset resource="permissionbrowser.qrc"> + <normaloff>:3rdparty/view-refresh.png</normaloff>:3rdparty/view-refresh.png</iconset> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="m_urlLineEdit"/> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QFrame" name="frame_2"> + <property name="maximumSize"> + <size> + <width>336</width> + <height>16777215</height> + </size> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,0,0,0,1,0,0,0"> + <item> + <widget class="QWidget" name="widget_2" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Permissions:</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>87</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="m_newButton"> + <property name="text"> + <string>New</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="m_deleteAllButton"> + <property name="text"> + <string>Delete All</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="widget_3" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Persistence policy:</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QComboBox" name="m_policyComboBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>25</height> + </size> + </property> + <property name="currentIndex"> + <number>0</number> + </property> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToContents</enum> + </property> + <item> + <property name="text"> + <string>Persist on disk</string> + </property> + </item> + <item> + <property name="text"> + <string>Persist in memory</string> + </property> + </item> + <item> + <property name="text"> + <string>Do not persist</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="Line" name="line"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Stored permissions</string> + </property> + </widget> + </item> + <item> + <widget class="QScrollArea" name="m_storedScrollArea"> + <property name="minimumSize"> + <size> + <width>320</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>320</width> + <height>16777215</height> + </size> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="lineWidth"> + <number>2</number> + </property> + <property name="verticalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="widgetResizable"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="Line" name="line_2"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Pending permission</string> + </property> + </widget> + </item> + <item> + <widget class="QFrame" name="m_pendingFrame"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>104</height> + </size> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Sunken</enum> + </property> + <property name="lineWidth"> + <number>2</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </widget> + <layoutdefault spacing="6" margin="11"/> + <resources> + <include location="permissionbrowser.qrc"/> + </resources> + <connections> + <connection> + <sender>m_urlLineEdit</sender> + <signal>returnPressed()</signal> + <receiver>m_refreshButton</receiver> + <slot>click()</slot> + <hints> + <hint type="sourcelabel"> + <x>509</x> + <y>28</y> + </hint> + <hint type="destinationlabel"> + <x>1024</x> + <y>27</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/examples/webenginewidgets/permissionbrowser/permissionbrowser.exe.manifest b/examples/webenginewidgets/permissionbrowser/permissionbrowser.exe.manifest new file mode 100644 index 000000000..acc401776 --- /dev/null +++ b/examples/webenginewidgets/permissionbrowser/permissionbrowser.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/permissionbrowser/permissionbrowser.qrc b/examples/webenginewidgets/permissionbrowser/permissionbrowser.qrc new file mode 100644 index 000000000..80a7d20f2 --- /dev/null +++ b/examples/webenginewidgets/permissionbrowser/permissionbrowser.qrc @@ -0,0 +1,7 @@ +<RCC> + <qresource prefix="/"> + <file alias="view-refresh.png">resources/3rdparty/view-refresh.png</file> + <file alias="go-next.png">resources/3rdparty/go-next.png</file> + <file alias="go-previous.png">resources/3rdparty/go-previous.png</file> + </qresource> +</RCC> diff --git a/examples/webenginewidgets/permissionbrowser/permissiondialog.ui b/examples/webenginewidgets/permissionbrowser/permissiondialog.ui new file mode 100644 index 000000000..a98432131 --- /dev/null +++ b/examples/webenginewidgets/permissionbrowser/permissiondialog.ui @@ -0,0 +1,117 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PermissionDialog</class> + <widget class="QDialog" name="PermissionDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>110</height> + </rect> + </property> + <property name="windowTitle"> + <string>Permission</string> + </property> + <layout class="QFormLayout" name="formLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Origin</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="m_originLineEdit"> + <property name="readOnly"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Feature</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="m_addButton"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string>Add</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="m_cancelButton"> + <property name="text"> + <string>Cancel</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="1"> + <widget class="QComboBox" name="m_featureComboBox"/> + </item> + </layout> + </widget> + <tabstops> + <tabstop>m_originLineEdit</tabstop> + <tabstop>m_addButton</tabstop> + <tabstop>m_cancelButton</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>m_cancelButton</sender> + <signal>clicked()</signal> + <receiver>PermissionDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>349</x> + <y>89</y> + </hint> + <hint type="destinationlabel"> + <x>334</x> + <y>67</y> + </hint> + </hints> + </connection> + <connection> + <sender>m_addButton</sender> + <signal>clicked()</signal> + <receiver>PermissionDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>264</x> + <y>88</y> + </hint> + <hint type="destinationlabel"> + <x>218</x> + <y>68</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/examples/webenginewidgets/permissionbrowser/permissionwidget.ui b/examples/webenginewidgets/permissionbrowser/permissionwidget.ui new file mode 100644 index 000000000..0a09da5d3 --- /dev/null +++ b/examples/webenginewidgets/permissionbrowser/permissionwidget.ui @@ -0,0 +1,119 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PermissionWidget</class> + <widget class="QWidget" name="PermissionWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>300</width> + <height>104</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>300</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>310</width> + <height>16777215</height> + </size> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0"> + <item> + <layout class="QFormLayout" name="formLayout"> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <property name="formAlignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + <property name="verticalSpacing"> + <number>10</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Origin:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="m_originLabel"> + <property name="text"> + <string>Empty</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Type:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="m_typeLabel"> + <property name="text"> + <string>Empty</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>State:</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="m_stateLabel"> + <property name="text"> + <string>Empty</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QPushButton" name="m_grantButton"> + <property name="text"> + <string>Grant</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="m_denyButton"> + <property name="text"> + <string>Deny</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="m_deleteButton"> + <property name="text"> + <string>Delete</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/examples/webenginewidgets/permissionbrowser/resources/3rdparty/COPYING b/examples/webenginewidgets/permissionbrowser/resources/3rdparty/COPYING new file mode 100644 index 000000000..220881da6 --- /dev/null +++ b/examples/webenginewidgets/permissionbrowser/resources/3rdparty/COPYING @@ -0,0 +1 @@ +The icons in this repository are herefore released into the Public Domain. diff --git a/examples/webenginewidgets/permissionbrowser/resources/3rdparty/go-next.png b/examples/webenginewidgets/permissionbrowser/resources/3rdparty/go-next.png Binary files differnew file mode 100644 index 000000000..a68e2db77 --- /dev/null +++ b/examples/webenginewidgets/permissionbrowser/resources/3rdparty/go-next.png diff --git a/examples/webenginewidgets/permissionbrowser/resources/3rdparty/go-previous.png b/examples/webenginewidgets/permissionbrowser/resources/3rdparty/go-previous.png Binary files differnew file mode 100644 index 000000000..c37bc0414 --- /dev/null +++ b/examples/webenginewidgets/permissionbrowser/resources/3rdparty/go-previous.png diff --git a/examples/webenginewidgets/permissionbrowser/resources/3rdparty/qt_attribution.json b/examples/webenginewidgets/permissionbrowser/resources/3rdparty/qt_attribution.json new file mode 100644 index 000000000..e11663d5e --- /dev/null +++ b/examples/webenginewidgets/permissionbrowser/resources/3rdparty/qt_attribution.json @@ -0,0 +1,24 @@ +{ + "Id": "permissionbrowser-tango", + "Name": "Tango Icon Library", + "QDocModule": "qtwebengine", + "QtUsage": "Used in WebEngine Permission Browser example.", + + "QtParts": [ "examples" ], + "Description": "Selected icons from the Tango Icon Library", + "Homepage": "http://tango.freedesktop.org/Tango_Icon_Library", + "Version": "0.8.90", + "DownloadLocation": "http://tango.freedesktop.org/releases/tango-icon-theme-0.8.90.tar.gz", + "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>"] +} diff --git a/examples/webenginewidgets/permissionbrowser/resources/3rdparty/view-refresh.png b/examples/webenginewidgets/permissionbrowser/resources/3rdparty/view-refresh.png Binary files differnew file mode 100644 index 000000000..cab4d02c7 --- /dev/null +++ b/examples/webenginewidgets/permissionbrowser/resources/3rdparty/view-refresh.png diff --git a/examples/webenginewidgets/permissionbrowser/resources/AppLogoColor.png b/examples/webenginewidgets/permissionbrowser/resources/AppLogoColor.png Binary files differnew file mode 100644 index 000000000..2a4971782 --- /dev/null +++ b/examples/webenginewidgets/permissionbrowser/resources/AppLogoColor.png diff --git a/examples/webenginewidgets/permissionbrowser/resources/landing.html b/examples/webenginewidgets/permissionbrowser/resources/landing.html new file mode 100644 index 000000000..5d921671f --- /dev/null +++ b/examples/webenginewidgets/permissionbrowser/resources/landing.html @@ -0,0 +1,88 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <title>Permissions example page</title> + <script> + function copyText() { + navigator.clipboard.writeText(document.getElementById("copyTextInput").value); + } + + function pasteText() { + navigator.clipboard.readText() + .then((text) => ( + document.getElementById("pasteTextInput").value = text + )) + .catch((err) => ( + document.getElementById("pasteTextInput").value = "<permission denied>" + )); + } + + function showNotification() { + new Notification("Example notification", { + body: "Notification contents", + tag: "notifTag", + lang: "", + dir: "auto", + icon: "./3rdparty/go-next.png" + }); + } + + function tryShowNotification() { + if (Notification.permission !== "granted") { + Notification.requestPermission() + .then((permission) => { + if (permission === "granted") { showNotification() } + + }) + .catch((err) => ( + document.getElementById("pasteTextInput").value = err + )); + } else { + showNotification(); + } + } + + function listFonts() { + var fontSelect = document.getElementById("fontSelect"); + var i, length = fontSelect.options.length - 1; + for (i = length; i >= 0; i--) { + fontSelect.remove(i); + } + + window.queryLocalFonts() + .then((fontsList) => { + for (const font of fontsList) { + fontSelect.add(new Option(text = font.fullName)); + } + }) + .catch() + } + </script> + </head> + <body> + <h1>Permission Browser Example</h1> + <div> + <h2>Clipboard</h2> + <div> + <button onclick="copyText()">Copy text</button> + <input id="copyTextInput" value="Example text"></input> + </div> + <div> + <button onclick="pasteText()">Paste text</button> + <input id="pasteTextInput" placeholder="Text will be pasted here"></input> + </div> + </div> + <div> + <h2>Notifications</h2> + <div> + <button onclick="tryShowNotification()">Show notification</button> + </div> + </div> + <div> + <h2>Local fonts</h2> + <button onclick="listFonts()">List fonts</button> + <select id="fontSelect"></select> + </div> + </body> +</html> diff --git a/examples/webenginewidgets/push-notifications/main.cpp b/examples/webenginewidgets/push-notifications/main.cpp index 18a862182..950ebfc9f 100644 --- a/examples/webenginewidgets/push-notifications/main.cpp +++ b/examples/webenginewidgets/push-notifications/main.cpp @@ -20,13 +20,12 @@ int main(int argc, char *argv[]) QWebEngineView view(profile.data()); auto popup = new NotificationPopup(&view); - QObject::connect(view.page(), &QWebEnginePage::featurePermissionRequested, - [&](const QUrl &origin, QWebEnginePage::Feature feature) { - if (feature != QWebEnginePage::Notifications) + QObject::connect(view.page(), &QWebEnginePage::permissionRequested, + [&](QWebEnginePermission permission) { + if (permission.feature() != QWebEnginePage::Notifications) return; - view.page()->setFeaturePermission(origin, feature, - QWebEnginePage::PermissionGrantedByUser); + permission.grant(); }); profile->setPushServiceEnabled(true); diff --git a/examples/webenginewidgets/simplebrowser/doc/src/simplebrowser.qdoc b/examples/webenginewidgets/simplebrowser/doc/src/simplebrowser.qdoc index dd7a8b998..a312da3ad 100644 --- a/examples/webenginewidgets/simplebrowser/doc/src/simplebrowser.qdoc +++ b/examples/webenginewidgets/simplebrowser/doc/src/simplebrowser.qdoc @@ -103,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 @@ -204,13 +223,17 @@ The \c handleProxyAuthenticationRequired signal handler implements the very same steps for the authentication of HTTP proxies. - In case of SSL errors, we just need to return a boolean value indicating - whether the certificate should be ignored. + In case of SSL errors, we check whether they come from the main frame, or + from a resource inside the page. Resource errors automatically trigger a + certificate rejection, since a user won't have enough context to make a + decision. For all other cases, we trigger a dialog where the user can + allow or reject the certificate. \quotefromfile webenginewidgets/simplebrowser/webpage.cpp \skipto WebPage::handleCertificateError( \printuntil } \printuntil } + \printuntil } \section1 Opening a Web Page diff --git a/examples/webenginewidgets/simplebrowser/tabwidget.cpp b/examples/webenginewidgets/simplebrowser/tabwidget.cpp index f9037a8e1..acdf49510 100644 --- a/examples/webenginewidgets/simplebrowser/tabwidget.cpp +++ b/examples/webenginewidgets/simplebrowser/tabwidget.cpp @@ -21,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(); diff --git a/examples/webenginewidgets/simplebrowser/webpage.cpp b/examples/webenginewidgets/simplebrowser/webpage.cpp index 807cdf0ff..6fc9aeaf9 100644 --- a/examples/webenginewidgets/simplebrowser/webpage.cpp +++ b/examples/webenginewidgets/simplebrowser/webpage.cpp @@ -19,6 +19,13 @@ WebPage::WebPage(QWebEngineProfile *profile, QObject *parent) void WebPage::handleCertificateError(QWebEngineCertificateError error) { + // Automatically block certificate errors from page resources without prompting the user. + // This mirrors the behavior found in other major browsers. + if (!error.isMainFrame()) { + error.rejectCertificate(); + return; + } + error.defer(); QTimer::singleShot(0, this, [this, error]() mutable { emit createCertificateErrorDialog(error); }); diff --git a/examples/webenginewidgets/simplebrowser/webview.cpp b/examples/webenginewidgets/simplebrowser/webview.cpp index 08e044f70..3dcfc0c47 100644 --- a/examples/webenginewidgets/simplebrowser/webview.cpp +++ b/examples/webenginewidgets/simplebrowser/webview.cpp @@ -71,26 +71,26 @@ WebView::~WebView() m_imageAnimationGroup = nullptr; } -inline QString questionForFeature(QWebEnginePage::Feature feature) +inline QString questionForFeature(QWebEnginePermission::Feature feature) { switch (feature) { - case QWebEnginePage::Geolocation: + case QWebEnginePermission::Geolocation: return QObject::tr("Allow %1 to access your location information?"); - case QWebEnginePage::MediaAudioCapture: + case QWebEnginePermission::MediaAudioCapture: return QObject::tr("Allow %1 to access your microphone?"); - case QWebEnginePage::MediaVideoCapture: + case QWebEnginePermission::MediaVideoCapture: return QObject::tr("Allow %1 to access your webcam?"); - case QWebEnginePage::MediaAudioVideoCapture: + case QWebEnginePermission::MediaAudioVideoCapture: return QObject::tr("Allow %1 to access your microphone and webcam?"); - case QWebEnginePage::MouseLock: + case QWebEnginePermission::MouseLock: return QObject::tr("Allow %1 to lock your mouse cursor?"); - case QWebEnginePage::DesktopVideoCapture: + case QWebEnginePermission::DesktopVideoCapture: return QObject::tr("Allow %1 to capture video of your desktop?"); - case QWebEnginePage::DesktopAudioVideoCapture: + case QWebEnginePermission::DesktopAudioVideoCapture: return QObject::tr("Allow %1 to capture audio and video of your desktop?"); - case QWebEnginePage::Notifications: + case QWebEnginePermission::Notifications: return QObject::tr("Allow %1 to show notification on your desktop?"); - case QWebEnginePage::ClipboardReadWrite: + case QWebEnginePermission::ClipboardReadWrite: return QObject::tr("Allow %1 to read from and write to the clipboard?"); } return QString(); @@ -103,8 +103,8 @@ void WebView::setPage(WebPage *page) &WebView::handleCertificateError); disconnect(oldPage, &QWebEnginePage::authenticationRequired, this, &WebView::handleAuthenticationRequired); - disconnect(oldPage, &QWebEnginePage::featurePermissionRequested, this, - &WebView::handleFeaturePermissionRequested); + disconnect(oldPage, &QWebEnginePage::permissionRequested, this, + &WebView::handlePermissionRequested); disconnect(oldPage, &QWebEnginePage::proxyAuthenticationRequired, this, &WebView::handleProxyAuthenticationRequired); disconnect(oldPage, &QWebEnginePage::registerProtocolHandlerRequested, this, @@ -124,8 +124,8 @@ void WebView::setPage(WebPage *page) connect(page, &WebPage::createCertificateErrorDialog, this, &WebView::handleCertificateError); connect(page, &QWebEnginePage::authenticationRequired, this, &WebView::handleAuthenticationRequired); - connect(page, &QWebEnginePage::featurePermissionRequested, this, - &WebView::handleFeaturePermissionRequested); + connect(page, &QWebEnginePage::permissionRequested, this, + &WebView::handlePermissionRequested); connect(page, &QWebEnginePage::proxyAuthenticationRequired, this, &WebView::handleProxyAuthenticationRequired); connect(page, &QWebEnginePage::registerProtocolHandlerRequested, this, @@ -309,17 +309,14 @@ void WebView::handleAuthenticationRequired(const QUrl &requestUrl, QAuthenticato } } -void WebView::handleFeaturePermissionRequested(const QUrl &securityOrigin, - QWebEnginePage::Feature feature) +void WebView::handlePermissionRequested(QWebEnginePermission permission) { QString title = tr("Permission Request"); - QString question = questionForFeature(feature).arg(securityOrigin.host()); + QString question = questionForFeature(permission.feature()).arg(permission.origin().host()); if (!question.isEmpty() && QMessageBox::question(window(), title, question) == QMessageBox::Yes) - page()->setFeaturePermission(securityOrigin, feature, - QWebEnginePage::PermissionGrantedByUser); + permission.grant(); else - page()->setFeaturePermission(securityOrigin, feature, - QWebEnginePage::PermissionDeniedByUser); + permission.deny(); } void WebView::handleProxyAuthenticationRequired(const QUrl &, QAuthenticator *auth, diff --git a/examples/webenginewidgets/simplebrowser/webview.h b/examples/webenginewidgets/simplebrowser/webview.h index c7e7f394c..d652fbdc9 100644 --- a/examples/webenginewidgets/simplebrowser/webview.h +++ b/examples/webenginewidgets/simplebrowser/webview.h @@ -14,6 +14,7 @@ #include <QWebEngineRegisterProtocolHandlerRequest> #include <QWebEngineWebAuthUxRequest> #include <QWebEngineSettings> +#include <QWebEnginePermission> #include <QActionGroup> class WebPage; @@ -43,8 +44,7 @@ signals: private slots: void handleCertificateError(QWebEngineCertificateError error); void handleAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth); - void handleFeaturePermissionRequested(const QUrl &securityOrigin, - QWebEnginePage::Feature feature); + void handlePermissionRequested(QWebEnginePermission permission); void handleProxyAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth, const QString &proxyHost); void handleRegisterProtocolHandlerRequested(QWebEngineRegisterProtocolHandlerRequest request); |