summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKaloyan Chehlarski <kaloyan.chehlarski@qt.io>2024-03-07 16:20:01 +0100
committerKaloyan Chehlarski <kaloyan.chehlarski@qt.io>2024-06-01 13:28:22 +0200
commit02b8b5afb45c2f926a4c0026cdb05e2fbb47dc46 (patch)
treeb71d95563b1fc99ff7c2963ffec86564cb54bcce
parentebf9ad043daa53c310ea2d5ee9987afbc615e4cd (diff)
Implement optional website permission persistence
This change introduces a new API allowing application developers to choose whether they want to retain the granted/denied status of permissions between different pages or browsing sessions. The previous behavior of asking for permission every time is still optionally available, but the default behavior is to persist permissions inbetween sessions (except for off-the-record profiles, where the permissions get destroyed alongside the profile). Storage is handled via a PrefService, which writes to a permissions.json file stored inside the profile folder. This is different to Chromium's implementation, which is massively overengineered and would require enabling a ton of code we will never need to use. [ChangeLog][QtWebEngineCore][QWebEngineProfile] Added new API to control permission persistence. [ChangeLog][QtWebEngineQuick] Added new API to control permission persistence. Fixes: QTBUG-55108 Change-Id: Ib3057feda3bfbbf2a17a86356feca35a67180806 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
m---------src/3rdparty0
-rw-r--r--src/core/api/qwebengineprofile.cpp49
-rw-r--r--src/core/api/qwebengineprofile.h10
-rw-r--r--src/core/permission_manager_qt.cpp174
-rw-r--r--src/core/permission_manager_qt.h15
-rw-r--r--src/core/profile_adapter.cpp23
-rw-r--r--src/core/profile_adapter.h10
-rw-r--r--src/core/profile_qt.cpp7
-rw-r--r--src/core/profile_qt.h1
-rw-r--r--src/webenginequick/api/qquickwebengineprofile.cpp83
-rw-r--r--src/webenginequick/api/qquickwebengineprofile.h12
-rw-r--r--tests/auto/quick/publicapi/tst_publicapi.cpp5
-rw-r--r--tests/auto/quick/qmltests/data/tst_geopermission.qml2
-rw-r--r--tests/auto/quick/qmltests/data/tst_getUserMedia.qml1
-rw-r--r--tests/auto/quick/qmltests/data/tst_notification.qml2
-rw-r--r--tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp10
-rw-r--r--tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp78
17 files changed, 440 insertions, 42 deletions
diff --git a/src/3rdparty b/src/3rdparty
-Subproject 814db44bc99f79d0c4a847e0cac4a398034ee2f
+Subproject f3033f5aa9be1e98096c55972166d0be6cb6492
diff --git a/src/core/api/qwebengineprofile.cpp b/src/core/api/qwebengineprofile.cpp
index dbb98102c..a9477929b 100644
--- a/src/core/api/qwebengineprofile.cpp
+++ b/src/core/api/qwebengineprofile.cpp
@@ -93,7 +93,7 @@ using QtWebEngineCore::ProfileAdapter;
/*!
\enum QWebEngineProfile::PersistentCookiesPolicy
- This enum describes policy for cookie persistency:
+ This enum describes policy for cookie persistence:
\value NoPersistentCookies
Both session and persistent cookies are stored in memory. This is the only setting
@@ -105,6 +105,28 @@ using QtWebEngineCore::ProfileAdapter;
Both session and persistent cookies are saved to and restored from disk.
*/
+/*!
+ \enum QWebEngineProfile::PersistentPermissionsPolicy
+
+ \since 6.8
+
+ This enum describes the policy for permission persistence:
+
+ \value NoPersistentPermissions
+ The application will ask for permissions every time they're needed, regardless of
+ whether they've been granted before or not. This is intended for backwards compatibility
+ with existing applications, and otherwise not recommended.
+ \value PersistentPermissionsInMemory
+ A request will be made only the first time a permission is needed. Any subsequent
+ requests will be automatically granted or denied, depending on the initial user choice.
+ This carries over to all pages that use the same QWebEngineProfile instance, until the
+ application is shut down. This is the setting applied if \c off-the-record is set
+ or no persistent data path is available.
+ \value PersistentPermissionsOnDisk
+ Works the same way as \c PersistentPermissionsInMemory, but the permissions are saved to
+ and restored from disk. This is the default setting.
+*/
+
void QWebEngineProfilePrivate::showNotification(QSharedPointer<QtWebEngineCore::UserNotificationController> &controller)
{
if (m_notificationPresenter) {
@@ -571,6 +593,31 @@ void QWebEngineProfile::setPersistentCookiesPolicy(QWebEngineProfile::Persistent
}
/*!
+ Returns the current policy for persistent permissions.
+
+ Off-the-record profiles are not allowed to save data to the disk, so they can only return
+ PersistentPermissionsInMemory or NoPersistentPermissions.
+
+ \sa setPersistentPermissionsPolicy()
+*/
+QWebEngineProfile::PersistentPermissionsPolicy QWebEngineProfile::persistentPermissionsPolicy() const
+{
+ Q_D(const QWebEngineProfile);
+ return QWebEngineProfile::PersistentPermissionsPolicy(d->profileAdapter()->persistentPermissionsPolicy());
+}
+
+/*!
+ Sets the policy for persistent permissions to \a newPersistentPermissionsPolicy.
+
+ \sa persistentPermissionsPolicy()
+*/
+void QWebEngineProfile::setPersistentPermissionsPolicy(QWebEngineProfile::PersistentPermissionsPolicy newPersistentPermissionsPolicy)
+{
+ Q_D(QWebEngineProfile);
+ d->profileAdapter()->setPersistentPermissionsPolicy(ProfileAdapter::PersistentPermissionsPolicy(newPersistentPermissionsPolicy));
+}
+
+/*!
Returns the maximum size of the HTTP cache in bytes.
Will return \c 0 if the size is automatically controlled by QtWebEngine.
diff --git a/src/core/api/qwebengineprofile.h b/src/core/api/qwebengineprofile.h
index a0027cb81..259de4bff 100644
--- a/src/core/api/qwebengineprofile.h
+++ b/src/core/api/qwebengineprofile.h
@@ -49,6 +49,13 @@ public:
};
Q_ENUM(PersistentCookiesPolicy)
+ enum PersistentPermissionsPolicy : quint8 {
+ NoPersistentPermissions,
+ PersistentPermissionsInMemory,
+ PersistentPermissionsOnDisk,
+ };
+ Q_ENUM(PersistentPermissionsPolicy)
+
QString storageName() const;
bool isOffTheRecord() const;
@@ -70,6 +77,9 @@ public:
PersistentCookiesPolicy persistentCookiesPolicy() const;
void setPersistentCookiesPolicy(QWebEngineProfile::PersistentCookiesPolicy);
+ PersistentPermissionsPolicy persistentPermissionsPolicy() const;
+ void setPersistentPermissionsPolicy(QWebEngineProfile::PersistentPermissionsPolicy);
+
int httpCacheMaximumSize() const;
void setHttpCacheMaximumSize(int maxSize);
diff --git a/src/core/permission_manager_qt.cpp b/src/core/permission_manager_qt.cpp
index b6e727ef8..100b7eb7c 100644
--- a/src/core/permission_manager_qt.cpp
+++ b/src/core/permission_manager_qt.cpp
@@ -3,12 +3,23 @@
#include "permission_manager_qt.h"
+#include "base/threading/thread_restrictions.h"
#include "content/browser/renderer_host/render_view_host_delegate.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/permission_controller.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_view_host.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"
+#include "chrome/browser/prefs/chrome_command_line_pref_store.h"
+#include "components/prefs/pref_member.h"
+#include "components/prefs/in_memory_pref_store.h"
+#include "components/prefs/json_pref_store.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/pref_service_factory.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/proxy_config/pref_proxy_config_tracker_impl.h"
+#include "components/prefs/pref_service.h"
#include "type_conversion.h"
#include "web_contents_delegate_qt.h"
@@ -57,12 +68,33 @@ static ProfileAdapter::PermissionType toQt(blink::PermissionType type)
case blink::PermissionType::DISPLAY_CAPTURE:
case blink::PermissionType::TOP_LEVEL_STORAGE_ACCESS:
case blink::PermissionType::NUM:
- LOG(INFO) << "Unexpected unsupported permission type: " << static_cast<int>(type);
+ LOG(INFO) << "Unexpected unsupported WebEngine permission type: " << static_cast<int>(type);
break;
}
return ProfileAdapter::UnsupportedPermission;
}
+static blink::PermissionType toBlink(ProfileAdapter::PermissionType type)
+{
+ switch (type) {
+ case ProfileAdapter::GeolocationPermission:
+ return blink::PermissionType::GEOLOCATION;
+ case ProfileAdapter::AudioCapturePermission:
+ return blink::PermissionType::AUDIO_CAPTURE;
+ case ProfileAdapter::VideoCapturePermission:
+ return blink::PermissionType::VIDEO_CAPTURE;
+ case ProfileAdapter::ClipboardReadWrite:
+ return blink::PermissionType::CLIPBOARD_READ_WRITE;
+ case ProfileAdapter::NotificationPermission:
+ return blink::PermissionType::NOTIFICATIONS;
+ case ProfileAdapter::LocalFontsPermission:
+ return blink::PermissionType::LOCAL_FONTS;
+ }
+
+ LOG(INFO) << "Unexpected unsupported Blink permission type: " << static_cast<int>(type);
+ return blink::PermissionType::NUM;
+}
+
static bool canRequestPermissionFor(ProfileAdapter::PermissionType type)
{
switch (type) {
@@ -103,13 +135,50 @@ static blink::mojom::PermissionStatus getStatusFromSettings(blink::PermissionTyp
}
}
-PermissionManagerQt::PermissionManagerQt()
+PermissionManagerQt::PermissionManagerQt(ProfileAdapter *profileAdapter)
: m_requestIdCount(0)
+ , m_persistence(true)
+ , m_profileAdapter(profileAdapter)
{
+ PrefServiceFactory factory;
+ factory.set_async(false);
+ factory.set_command_line_prefs(base::MakeRefCounted<ChromeCommandLinePrefStore>(
+ base::CommandLine::ForCurrentProcess()));
+
+ QString userPrefStorePath = profileAdapter->dataPath();
+ auto prefRegistry = base::MakeRefCounted<PrefRegistrySimple>();
+
+ auto policy = profileAdapter->persistentPermissionsPolicy();
+ if (!profileAdapter->isOffTheRecord() && policy == ProfileAdapter::PersistentPermissionsOnDisk &&
+ !userPrefStorePath.isEmpty() && profileAdapter->ensureDataPathExists()) {
+ userPrefStorePath += QDir::separator();
+ userPrefStorePath += QStringLiteral("permissions.json");
+ factory.set_user_prefs(base::MakeRefCounted<JsonPrefStore>(toFilePath(userPrefStorePath)));
+ } else {
+ factory.set_user_prefs(new InMemoryPrefStore);
+ }
+
+ // Register all preference types as keys prior to doing anything else
+ prefRegistry->RegisterDictionaryPref(GetPermissionString(toBlink(ProfileAdapter::GeolocationPermission)));
+ prefRegistry->RegisterDictionaryPref(GetPermissionString(toBlink(ProfileAdapter::AudioCapturePermission)));
+ prefRegistry->RegisterDictionaryPref(GetPermissionString(toBlink(ProfileAdapter::VideoCapturePermission)));
+ prefRegistry->RegisterDictionaryPref(GetPermissionString(toBlink(ProfileAdapter::ClipboardReadWrite)));
+ prefRegistry->RegisterDictionaryPref(GetPermissionString(toBlink(ProfileAdapter::NotificationPermission)));
+ prefRegistry->RegisterDictionaryPref(GetPermissionString(toBlink(ProfileAdapter::LocalFontsPermission)));
+ PrefProxyConfigTrackerImpl::RegisterPrefs(prefRegistry.get());
+
+ if (policy == ProfileAdapter::NoPersistentPermissions)
+ m_persistence = false;
+
+ {
+ base::ScopedAllowBlocking allowBlock;
+ m_prefService = factory.Create(prefRegistry);
+ }
}
PermissionManagerQt::~PermissionManagerQt()
{
+ commit();
}
void PermissionManagerQt::permissionRequestReply(const QUrl &url, ProfileAdapter::PermissionType type, ProfileAdapter::PermissionState reply)
@@ -119,11 +188,10 @@ void PermissionManagerQt::permissionRequestReply(const QUrl &url, ProfileAdapter
const QUrl origin = gorigin.is_empty() ? url : toQt(gorigin);
if (origin.isEmpty())
return;
- QPair<QUrl, ProfileAdapter::PermissionType> key(origin, type);
if (reply == ProfileAdapter::AskPermission)
- m_permissions.remove(key);
+ ResetPermission(toBlink(type), gorigin, gorigin);
else
- m_permissions[key] = (reply == ProfileAdapter::AllowedPermission);
+ setPermission(toBlink(type), gorigin, reply == ProfileAdapter::AllowedPermission);
blink::mojom::PermissionStatus status = toBlink(reply);
if (reply != ProfileAdapter::AskPermission) {
auto it = m_requests.begin();
@@ -150,21 +218,23 @@ void PermissionManagerQt::permissionRequestReply(const QUrl &url, ProfileAdapter
std::vector<blink::mojom::PermissionStatus> result;
result.reserve(it->types.size());
for (blink::PermissionType permission : it->types) {
- const ProfileAdapter::PermissionType permissionType = toQt(permission);
- if (permissionType == ProfileAdapter::UnsupportedPermission) {
+ if (toQt(permission) == ProfileAdapter::UnsupportedPermission) {
result.push_back(blink::mojom::PermissionStatus::DENIED);
continue;
}
- QPair<QUrl, ProfileAdapter::PermissionType> key(origin, permissionType);
- if (!m_permissions.contains(key)) {
- answerable = false;
- break;
+ blink::mojom::PermissionStatus permissionStatus = GetPermissionStatus(permission, gorigin, GURL());
+ if (permissionStatus == toBlink(reply)) {
+ if (permissionStatus == blink::mojom::PermissionStatus::ASK) {
+ answerable = false;
+ break;
+ }
+
+ result.push_back(permissionStatus);
+ } else {
+ // Reached when the PersistentPermissionsPolicy is set to NoPersistentPermissions
+ result.push_back(toBlink(reply));
}
- if (m_permissions[key])
- result.push_back(blink::mojom::PermissionStatus::GRANTED);
- else
- result.push_back(blink::mojom::PermissionStatus::DENIED);
}
if (answerable) {
std::move(it->callback).Run(result);
@@ -178,8 +248,14 @@ void PermissionManagerQt::permissionRequestReply(const QUrl &url, ProfileAdapter
bool PermissionManagerQt::checkPermission(const QUrl &origin, ProfileAdapter::PermissionType type)
{
- QPair<QUrl, ProfileAdapter::PermissionType> key(origin, type);
- return m_permissions.contains(key) && m_permissions[key];
+ return GetPermissionStatus(toBlink(type), toGurl(origin), GURL()) == blink::mojom::PermissionStatus::GRANTED;
+}
+
+void PermissionManagerQt::commit()
+{
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ // Make sure modified permissions are written to disk
+ m_prefService->CommitPendingWrite();
}
void PermissionManagerQt::RequestPermissions(content::RenderFrameHost *frameHost,
@@ -205,13 +281,22 @@ void PermissionManagerQt::RequestPermissions(content::RenderFrameHost *frameHost
continue;
}
- auto status = getStatusFromSettings(permission, contentsDelegate->webEngineSettings());
- if (status == blink::mojom::PermissionStatus::ASK) {
- answerable = false;
- break;
- } else
- result.push_back(status);
+ blink::mojom::PermissionStatus permissionStatus = getStatusFromSettings(permission, contentsDelegate->webEngineSettings());
+ if (permissionStatus == blink::mojom::PermissionStatus::ASK) {
+ permissionStatus = GetPermissionStatus(permission, requestDescription.requesting_origin, GURL());
+ if (m_persistence && permissionStatus != blink::mojom::PermissionStatus::ASK) {
+ // Automatically grant/deny without prompt if already asked once
+ result.push_back(permissionStatus);
+ } else {
+ answerable = false;
+ break;
+ }
+ } else {
+ // Reached when clipboard settings have been set
+ result.push_back(permissionStatus);
+ }
}
+
if (answerable) {
std::move(callback).Run(result);
return;
@@ -231,7 +316,6 @@ void PermissionManagerQt::RequestPermissionsFromCurrentDocument(content::RenderF
const content::PermissionRequestDescription &requestDescription,
base::OnceCallback<void(const std::vector<blink::mojom::PermissionStatus>&)> callback)
{
-
RequestPermissions(frameHost, requestDescription, std::move(callback));
}
@@ -244,10 +328,26 @@ blink::mojom::PermissionStatus PermissionManagerQt::GetPermissionStatus(
if (permissionType == ProfileAdapter::UnsupportedPermission)
return blink::mojom::PermissionStatus::DENIED;
- QPair<QUrl, ProfileAdapter::PermissionType> key(toQt(requesting_origin), permissionType);
- if (!m_permissions.contains(key))
+ permission = toBlink(toQt(permission)); // Filter out merged/unsupported permissions (e.g. clipboard)
+ auto *pref = m_prefService->FindPreference(GetPermissionString(permission));
+ if (!pref)
+ return blink::mojom::PermissionStatus::ASK; // Permission type not in database
+
+ const auto *permissions = pref->GetValue()->GetIfDict();
+ Q_ASSERT(permissions);
+
+ auto requestedPermission = permissions->FindBool(requesting_origin.DeprecatedGetOriginAsURL().spec());
+ if (!requestedPermission)
+ return blink::mojom::PermissionStatus::ASK; // Origin is not in the current permission type's database
+
+ // Workaround: local fonts are entirely managed by Chromium, which only calls RequestPermission() _after_
+ // it's checked whether the permission has been granted. By always returning ASK, we force the request to
+ // come through every time.
+ if (permission == blink::PermissionType::LOCAL_FONTS
+ && m_profileAdapter->persistentPermissionsPolicy() == ProfileAdapter::NoPersistentPermissions)
return blink::mojom::PermissionStatus::ASK;
- if (m_permissions[key])
+
+ if (requestedPermission.value())
return blink::mojom::PermissionStatus::GRANTED;
return blink::mojom::PermissionStatus::DENIED;
}
@@ -309,8 +409,8 @@ void PermissionManagerQt::ResetPermission(
if (permissionType == ProfileAdapter::UnsupportedPermission)
return;
- QPair<QUrl, ProfileAdapter::PermissionType> key(toQt(requesting_origin), permissionType);
- m_permissions.remove(key);
+ ScopedDictPrefUpdate updater(m_prefService.get(), GetPermissionString(permission));
+ updater.Get().Remove(requesting_origin.spec());
}
content::PermissionControllerDelegate::SubscriptionId PermissionManagerQt::SubscribePermissionStatusChange(
@@ -332,4 +432,20 @@ void PermissionManagerQt::UnsubscribePermissionStatusChange(content::PermissionC
LOG(WARNING) << "PermissionManagerQt::UnsubscribePermissionStatusChange called on unknown subscription id" << subscription_id;
}
+void PermissionManagerQt::setPermission(
+ blink::PermissionType permission,
+ const GURL& requesting_origin,
+ bool granted)
+{
+ const ProfileAdapter::PermissionType permissionType = toQt(permission);
+ if (permissionType == ProfileAdapter::UnsupportedPermission)
+ return;
+
+ if (!m_prefService->FindPreference(GetPermissionString(permission)))
+ return;
+
+ ScopedDictPrefUpdate updater(m_prefService.get(), GetPermissionString(permission));
+ updater.Get().Set(requesting_origin.spec(), granted);
+}
+
} // namespace QtWebEngineCore
diff --git a/src/core/permission_manager_qt.h b/src/core/permission_manager_qt.h
index b91498d3d..5dedaa612 100644
--- a/src/core/permission_manager_qt.h
+++ b/src/core/permission_manager_qt.h
@@ -11,16 +11,19 @@
#include <map>
+class PrefService;
+
namespace QtWebEngineCore {
class PermissionManagerQt : public content::PermissionControllerDelegate
{
public:
- PermissionManagerQt();
+ PermissionManagerQt(ProfileAdapter *adapter);
~PermissionManagerQt();
void permissionRequestReply(const QUrl &origin, ProfileAdapter::PermissionType type, ProfileAdapter::PermissionState reply);
bool checkPermission(const QUrl &origin, ProfileAdapter::PermissionType type);
+ void commit();
// content::PermissionManager implementation:
blink::mojom::PermissionStatus GetPermissionStatus(
@@ -61,7 +64,6 @@ public:
void UnsubscribePermissionStatusChange(content::PermissionControllerDelegate::SubscriptionId subscription_id) override;
private:
- QHash<QPair<QUrl, ProfileAdapter::PermissionType>, bool> m_permissions;
struct Request {
int id;
ProfileAdapter::PermissionType type;
@@ -79,12 +81,19 @@ private:
QUrl origin;
base::RepeatingCallback<void(blink::mojom::PermissionStatus)> callback;
};
+
+ void setPermission(blink::PermissionType permission,
+ const GURL& requesting_origin,
+ bool granted);
+
std::vector<Request> m_requests;
std::vector<MultiRequest> m_multiRequests;
std::map<content::PermissionControllerDelegate::SubscriptionId, Subscription> m_subscribers;
content::PermissionControllerDelegate::SubscriptionId::Generator subscription_id_generator_;
int m_requestIdCount;
-
+ std::unique_ptr<PrefService> m_prefService;
+ QPointer<QtWebEngineCore::ProfileAdapter> m_profileAdapter;
+ bool m_persistence;
};
} // namespace QtWebEngineCore
diff --git a/src/core/profile_adapter.cpp b/src/core/profile_adapter.cpp
index b26f9b1de..6fec8aa28 100644
--- a/src/core/profile_adapter.cpp
+++ b/src/core/profile_adapter.cpp
@@ -64,6 +64,7 @@ ProfileAdapter::ProfileAdapter(const QString &storageName):
, m_downloadPath(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation))
, m_httpCacheType(DiskHttpCache)
, m_persistentCookiesPolicy(AllowPersistentCookies)
+ , m_persistentPermissionsPolicy(PersistentPermissionsOnDisk)
, m_visitedLinksPolicy(TrackVisitedLinksOnDisk)
, m_clientHintsEnabled(true)
, m_pushServiceEnabled(false)
@@ -72,6 +73,8 @@ ProfileAdapter::ProfileAdapter(const QString &storageName):
WebEngineContext::current()->addProfileAdapter(this);
// creation of profile requires webengine context
m_profile.reset(new ProfileQt(this));
+ // initialize permissions store
+ profile()->GetPermissionControllerDelegate();
// fixme: this should not be here
m_profile->m_profileIOData->initializeOnUIThread();
m_customUrlSchemeHandlers.insert(QByteArrayLiteral("qrc"), &m_qrcHandler);
@@ -109,6 +112,7 @@ void ProfileAdapter::setStorageName(const QString &storageName)
m_name = storageName;
if (!m_offTheRecord) {
m_profile->setupPrefService();
+ m_profile->setupPermissionsManager();
if (!m_profile->m_profileIOData->isClearHttpCacheInProgress())
m_profile->m_profileIOData->resetNetworkContext();
if (m_visitedLinksManager)
@@ -124,6 +128,7 @@ void ProfileAdapter::setOffTheRecord(bool offTheRecord)
return;
m_offTheRecord = offTheRecord;
m_profile->setupPrefService();
+ m_profile->setupPermissionsManager();
if (!m_profile->m_profileIOData->isClearHttpCacheInProgress())
m_profile->m_profileIOData->resetNetworkContext();
if (m_visitedLinksManager)
@@ -371,6 +376,24 @@ void ProfileAdapter::setPersistentCookiesPolicy(ProfileAdapter::PersistentCookie
m_profile->m_profileIOData->resetNetworkContext();
}
+ProfileAdapter::PersistentPermissionsPolicy ProfileAdapter::persistentPermissionsPolicy() const
+{
+ if (m_persistentPermissionsPolicy == NoPersistentPermissions)
+ return NoPersistentPermissions;
+ if (isOffTheRecord() || m_name.isEmpty())
+ return PersistentPermissionsInMemory;
+ return m_persistentPermissionsPolicy;
+}
+
+void ProfileAdapter::setPersistentPermissionsPolicy(ProfileAdapter::PersistentPermissionsPolicy newPersistentPermissionsPolicy)
+{
+ ProfileAdapter::PersistentPermissionsPolicy oldPolicy = persistentPermissionsPolicy();
+ m_persistentPermissionsPolicy = newPersistentPermissionsPolicy;
+ if (oldPolicy == persistentPermissionsPolicy())
+ return;
+ m_profile->setupPermissionsManager();
+}
+
ProfileAdapter::VisitedLinksPolicy ProfileAdapter::visitedLinksPolicy() const
{
if (isOffTheRecord() || m_visitedLinksPolicy == DoNotTrackVisitedLinks)
diff --git a/src/core/profile_adapter.h b/src/core/profile_adapter.h
index 4be0ea51e..18d82ba96 100644
--- a/src/core/profile_adapter.h
+++ b/src/core/profile_adapter.h
@@ -127,6 +127,12 @@ public:
TrackVisitedLinksOnDisk,
};
+ enum PersistentPermissionsPolicy {
+ NoPersistentPermissions = 0,
+ PersistentPermissionsInMemory,
+ PersistentPermissionsOnDisk,
+ };
+
enum PermissionType {
UnsupportedPermission = 0,
GeolocationPermission = 1,
@@ -161,6 +167,9 @@ public:
PersistentCookiesPolicy persistentCookiesPolicy() const;
void setPersistentCookiesPolicy(ProfileAdapter::PersistentCookiesPolicy);
+ PersistentPermissionsPolicy persistentPermissionsPolicy() const;
+ void setPersistentPermissionsPolicy(ProfileAdapter::PersistentPermissionsPolicy);
+
VisitedLinksPolicy visitedLinksPolicy() const;
void setVisitedLinksPolicy(ProfileAdapter::VisitedLinksPolicy);
@@ -235,6 +244,7 @@ private:
HttpCacheType m_httpCacheType;
QString m_httpAcceptLanguage;
PersistentCookiesPolicy m_persistentCookiesPolicy;
+ PersistentPermissionsPolicy m_persistentPermissionsPolicy;
VisitedLinksPolicy m_visitedLinksPolicy;
QHash<QByteArray, QPointer<QWebEngineUrlSchemeHandler>> m_customUrlSchemeHandlers;
QHash<QByteArray, QWeakPointer<UserNotificationController>> m_ephemeralNotifications;
diff --git a/src/core/profile_qt.cpp b/src/core/profile_qt.cpp
index 293e8d557..d8a6c191c 100644
--- a/src/core/profile_qt.cpp
+++ b/src/core/profile_qt.cpp
@@ -181,7 +181,7 @@ content::BrowsingDataRemoverDelegate *ProfileQt::GetBrowsingDataRemoverDelegate(
content::PermissionControllerDelegate *ProfileQt::GetPermissionControllerDelegate()
{
if (!m_permissionManager)
- m_permissionManager.reset(new PermissionManagerQt());
+ setupPermissionsManager();
return m_permissionManager.get();
}
@@ -260,6 +260,11 @@ void ProfileQt::setupPrefService()
#endif
}
+void ProfileQt::setupPermissionsManager()
+{
+ m_permissionManager.reset(new PermissionManagerQt(profileAdapter()));
+}
+
PrefServiceAdapter &ProfileQt::prefServiceAdapter()
{
return m_prefServiceAdapter;
diff --git a/src/core/profile_qt.h b/src/core/profile_qt.h
index b5cd08db1..486563255 100644
--- a/src/core/profile_qt.h
+++ b/src/core/profile_qt.h
@@ -77,6 +77,7 @@ public:
// Build/Re-build the preference service. Call when updating the storage
// data path.
void setupPrefService();
+ void setupPermissionsManager();
PrefServiceAdapter &prefServiceAdapter();
const PrefServiceAdapter &prefServiceAdapter() const;
diff --git a/src/webenginequick/api/qquickwebengineprofile.cpp b/src/webenginequick/api/qquickwebengineprofile.cpp
index edca5e99c..ae55bb1bd 100644
--- a/src/webenginequick/api/qquickwebengineprofile.cpp
+++ b/src/webenginequick/api/qquickwebengineprofile.cpp
@@ -95,6 +95,28 @@ QT_BEGIN_NAMESPACE
*/
/*!
+ \enum QQuickWebEngineProfile::PersistentPermissionsPolicy
+
+ \since 6.8
+
+ This enum describes the policy for permission persistence:
+
+ \value NoPersistentPermissions
+ The application will ask for permissions every time they're needed, regardless of
+ whether they've been granted before or not. This is intended for backwards compatibility
+ with existing applications, and otherwise not recommended.
+ \value PersistentPermissionsInMemory
+ A request will be made only the first time a permission is needed. Any subsequent
+ requests will be automatically granted or denied, depending on the initial user choice.
+ This carries over to all pages that use the same QQuickWebEngineProfile instance, until the
+ application is shut down. This is the setting applied if \c off-the-record is set
+ or no persistent data path is available.
+ \value PersistentPermissionsOnDisk
+ Works the same way as \c PersistentPermissionsInMemory, but the permissions are saved to
+ and restored from disk. This is the default setting.
+*/
+
+/*!
\fn QQuickWebEngineProfile::downloadRequested(QQuickWebEngineDownloadRequest *download)
This signal is emitted whenever a download has been triggered.
@@ -435,15 +457,18 @@ void QQuickWebEngineProfile::setStorageName(const QString &name)
if (d->profileAdapter()->storageName() == name)
return;
ProfileAdapter::HttpCacheType oldCacheType = d->profileAdapter()->httpCacheType();
- ProfileAdapter::PersistentCookiesPolicy oldPolicy = d->profileAdapter()->persistentCookiesPolicy();
+ ProfileAdapter::PersistentCookiesPolicy oldCookiePolicy = d->profileAdapter()->persistentCookiesPolicy();
+ ProfileAdapter::PersistentPermissionsPolicy oldPermissionsPolicy = d->profileAdapter()->persistentPermissionsPolicy();
d->profileAdapter()->setStorageName(name);
emit storageNameChanged();
emit persistentStoragePathChanged();
emit cachePathChanged();
if (d->profileAdapter()->httpCacheType() != oldCacheType)
emit httpCacheTypeChanged();
- if (d->profileAdapter()->persistentCookiesPolicy() != oldPolicy)
+ if (d->profileAdapter()->persistentCookiesPolicy() != oldCookiePolicy)
emit persistentCookiesPolicyChanged();
+ if (d->profileAdapter()->persistentPermissionsPolicy() != oldPermissionsPolicy)
+ emit persistentPermissionsPolicyChanged();
}
/*!
@@ -475,13 +500,16 @@ void QQuickWebEngineProfile::setOffTheRecord(bool offTheRecord)
if (d->profileAdapter()->isOffTheRecord() == offTheRecord)
return;
ProfileAdapter::HttpCacheType oldCacheType = d->profileAdapter()->httpCacheType();
- ProfileAdapter::PersistentCookiesPolicy oldPolicy = d->profileAdapter()->persistentCookiesPolicy();
+ ProfileAdapter::PersistentCookiesPolicy oldCookiePolicy = d->profileAdapter()->persistentCookiesPolicy();
+ ProfileAdapter::PersistentPermissionsPolicy oldPermissionsPolicy = d->profileAdapter()->persistentPermissionsPolicy();
d->profileAdapter()->setOffTheRecord(offTheRecord);
emit offTheRecordChanged();
if (d->profileAdapter()->httpCacheType() != oldCacheType)
emit httpCacheTypeChanged();
- if (d->profileAdapter()->persistentCookiesPolicy() != oldPolicy)
+ if (d->profileAdapter()->persistentCookiesPolicy() != oldCookiePolicy)
emit persistentCookiesPolicyChanged();
+ if (d->profileAdapter()->persistentPermissionsPolicy() != oldPermissionsPolicy)
+ emit persistentPermissionsPolicyChanged();
}
/*!
@@ -628,7 +656,7 @@ void QQuickWebEngineProfile::setHttpCacheType(QQuickWebEngineProfile::HttpCacheT
/*!
\qmlproperty enumeration WebEngineProfile::persistentCookiesPolicy
- This enumeration describes the policy of cookie persistency:
+ This enumeration describes the policy of cookie persistence:
\value WebEngineProfile.NoPersistentCookies
Both session and persistent cookies are stored in memory. This is the only setting
@@ -664,6 +692,51 @@ void QQuickWebEngineProfile::setPersistentCookiesPolicy(QQuickWebEngineProfile::
}
/*!
+ \qmlproperty enumeration WebEngineProfile::persistentPermissionsPolicy
+
+ \since 6.8
+
+ This enumeration describes the policy for permission persistence:
+
+ \value WebEngineProfile.NoPersistentPermissions
+ The application will ask for permissions every time they're needed, regardless of
+ whether they've been granted before or not. This is intended for backwards compatibility
+ with existing applications, and otherwise not recommended.
+ \value WebEngineProfile.PersistentPermissionsInMemory
+ A request will be made only the first time a permission is needed. Any subsequent
+ requests will be automatically granted or denied, depending on the initial user choice.
+ This carries over to all pages using the same QWebEngineProfile instance, until the
+ application is shut down. This is the setting applied if \c off-the-record is set
+ or no persistent data path is available.
+ \value WebEngineProfile.PersistentPermissionsOnDisk
+ Works the same way as \c PersistentPermissionsInMemory, but the permissions are saved to
+ and restored from disk. This is the default setting.
+*/
+
+/*!
+ \property QQuickWebEngineProfile::persistentPermissionsPolicy
+ \since 6.8
+
+ Describes the policy of permission persistence.
+ If the profile is off-the-record, NoPersistentCookies is returned.
+*/
+
+QQuickWebEngineProfile::PersistentPermissionsPolicy QQuickWebEngineProfile::persistentPermissionsPolicy() const
+{
+ Q_D(const QQuickWebEngineProfile);
+ return QQuickWebEngineProfile::PersistentPermissionsPolicy(d->profileAdapter()->persistentPermissionsPolicy());
+}
+
+void QQuickWebEngineProfile::setPersistentPermissionsPolicy(QQuickWebEngineProfile::PersistentPermissionsPolicy newPersistentPermissionsPolicy)
+{
+ Q_D(QQuickWebEngineProfile);
+ ProfileAdapter::PersistentPermissionsPolicy oldPolicy = d->profileAdapter()->persistentPermissionsPolicy();
+ d->profileAdapter()->setPersistentPermissionsPolicy(ProfileAdapter::PersistentPermissionsPolicy(newPersistentPermissionsPolicy));
+ if (d->profileAdapter()->persistentPermissionsPolicy() != oldPolicy)
+ emit persistentPermissionsPolicyChanged();
+}
+
+/*!
\qmlproperty int WebEngineProfile::httpCacheMaximumSize
The maximum size of the HTTP cache. If \c 0, the size will be controlled automatically by
diff --git a/src/webenginequick/api/qquickwebengineprofile.h b/src/webenginequick/api/qquickwebengineprofile.h
index cbeb91147..7ba9105ff 100644
--- a/src/webenginequick/api/qquickwebengineprofile.h
+++ b/src/webenginequick/api/qquickwebengineprofile.h
@@ -33,6 +33,7 @@ class Q_WEBENGINEQUICK_EXPORT QQuickWebEngineProfile : public QObject {
Q_PROPERTY(HttpCacheType httpCacheType READ httpCacheType WRITE setHttpCacheType NOTIFY httpCacheTypeChanged FINAL)
Q_PROPERTY(QString httpAcceptLanguage READ httpAcceptLanguage WRITE setHttpAcceptLanguage NOTIFY httpAcceptLanguageChanged FINAL REVISION(1,1))
Q_PROPERTY(PersistentCookiesPolicy persistentCookiesPolicy READ persistentCookiesPolicy WRITE setPersistentCookiesPolicy NOTIFY persistentCookiesPolicyChanged FINAL)
+ Q_PROPERTY(PersistentPermissionsPolicy persistentPermissionsPolicy READ persistentPermissionsPolicy WRITE setPersistentPermissionsPolicy NOTIFY persistentPermissionsPolicyChanged FINAL)
Q_PROPERTY(int httpCacheMaximumSize READ httpCacheMaximumSize WRITE setHttpCacheMaximumSize NOTIFY httpCacheMaximumSizeChanged FINAL)
Q_PROPERTY(QStringList spellCheckLanguages READ spellCheckLanguages WRITE setSpellCheckLanguages NOTIFY spellCheckLanguagesChanged FINAL REVISION(1,3))
Q_PROPERTY(bool spellCheckEnabled READ isSpellCheckEnabled WRITE setSpellCheckEnabled NOTIFY spellCheckEnabledChanged FINAL REVISION(1,3))
@@ -61,6 +62,13 @@ public:
};
Q_ENUM(PersistentCookiesPolicy)
+ enum PersistentPermissionsPolicy : quint8 {
+ NoPersistentPermissions,
+ PersistentPermissionsInMemory,
+ PersistentPermissionsOnDisk,
+ };
+ Q_ENUM(PersistentPermissionsPolicy)
+
QString storageName() const;
void setStorageName(const QString &name);
@@ -82,6 +90,9 @@ public:
PersistentCookiesPolicy persistentCookiesPolicy() const;
void setPersistentCookiesPolicy(QQuickWebEngineProfile::PersistentCookiesPolicy);
+ PersistentPermissionsPolicy persistentPermissionsPolicy() const;
+ void setPersistentPermissionsPolicy(QQuickWebEngineProfile::PersistentPermissionsPolicy);
+
int httpCacheMaximumSize() const;
void setHttpCacheMaximumSize(int maxSize);
@@ -133,6 +144,7 @@ Q_SIGNALS:
Q_REVISION(1,5) void downloadPathChanged();
Q_REVISION(6,5) void pushServiceEnabledChanged();
Q_REVISION(6,7) void clearHttpCacheCompleted();
+ Q_REVISION(6,8) void persistentPermissionsPolicyChanged();
void downloadRequested(QQuickWebEngineDownloadRequest *download);
void downloadFinished(QQuickWebEngineDownloadRequest *download);
diff --git a/tests/auto/quick/publicapi/tst_publicapi.cpp b/tests/auto/quick/publicapi/tst_publicapi.cpp
index ee247ec71..c95c45bb4 100644
--- a/tests/auto/quick/publicapi/tst_publicapi.cpp
+++ b/tests/auto/quick/publicapi/tst_publicapi.cpp
@@ -360,6 +360,9 @@ static const QStringList expectedAPI = QStringList()
<< "QQuickWebEngineProfile.MemoryHttpCache --> HttpCacheType"
<< "QQuickWebEngineProfile.NoCache --> HttpCacheType"
<< "QQuickWebEngineProfile.NoPersistentCookies --> PersistentCookiesPolicy"
+ << "QQuickWebEngineProfile.NoPersistentPermissions --> PersistentPermissionsPolicy"
+ << "QQuickWebEngineProfile.PersistentPermissionsInMemory --> PersistentPermissionsPolicy"
+ << "QQuickWebEngineProfile.PersistentPermissionsOnDisk --> PersistentPermissionsPolicy"
<< "QQuickWebEngineProfile.cachePath --> QString"
<< "QQuickWebEngineProfile.cachePathChanged() --> void"
<< "QQuickWebEngineProfile.clearHttpCache() --> void"
@@ -368,6 +371,8 @@ static const QStringList expectedAPI = QStringList()
<< "QQuickWebEngineProfile.downloadRequested(QQuickWebEngineDownloadRequest*) --> void"
<< "QQuickWebEngineProfile.downloadPath --> QString"
<< "QQuickWebEngineProfile.downloadPathChanged() --> void"
+ << "QQuickWebEngineProfile.persistentPermissionsPolicy --> QQuickWebEngineProfile::PersistentPermissionsPolicy"
+ << "QQuickWebEngineProfile.persistentPermissionsPolicyChanged() --> void"
<< "QQuickWebEngineProfile.presentNotification(QWebEngineNotification*) --> void"
<< "QQuickWebEngineProfile.httpAcceptLanguage --> QString"
<< "QQuickWebEngineProfile.httpAcceptLanguageChanged() --> void"
diff --git a/tests/auto/quick/qmltests/data/tst_geopermission.qml b/tests/auto/quick/qmltests/data/tst_geopermission.qml
index b99e50acc..c474033a9 100644
--- a/tests/auto/quick/qmltests/data/tst_geopermission.qml
+++ b/tests/auto/quick/qmltests/data/tst_geopermission.qml
@@ -13,6 +13,8 @@ TestWebEngineView {
property bool deniedGeolocation: false
property bool geoPermissionRequested: false
+ profile.persistentPermissionsPolicy: WebEngineProfile.NoPersistentPermissions
+
SignalSpy {
id: featurePermissionSpy
target: webEngineView
diff --git a/tests/auto/quick/qmltests/data/tst_getUserMedia.qml b/tests/auto/quick/qmltests/data/tst_getUserMedia.qml
index 3b33b7abe..653f7a5df 100644
--- a/tests/auto/quick/qmltests/data/tst_getUserMedia.qml
+++ b/tests/auto/quick/qmltests/data/tst_getUserMedia.qml
@@ -11,6 +11,7 @@ TestWebEngineView {
height: 400
settings.screenCaptureEnabled: true
+ profile.persistentPermissionsPolicy: WebEngineProfile.NoPersistentPermissions
TestCase {
name: "GetUserMedia"
diff --git a/tests/auto/quick/qmltests/data/tst_notification.qml b/tests/auto/quick/qmltests/data/tst_notification.qml
index 5d55e1201..0bb33b41e 100644
--- a/tests/auto/quick/qmltests/data/tst_notification.qml
+++ b/tests/auto/quick/qmltests/data/tst_notification.qml
@@ -15,6 +15,8 @@ TestWebEngineView {
property bool grantPermission: false
property url securityOrigin: ''
+ profile.persistentPermissionsPolicy: WebEngineProfile.NoPersistentPermissions
+
signal consoleMessage(string message)
SignalSpy {
diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp
index f1d64776b..f6eac2880 100644
--- a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp
+++ b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp
@@ -485,6 +485,7 @@ void tst_QWebEnginePage::geolocationRequestJS()
QWebEngineView view;
JSTestPage *newPage = new JSTestPage(&view);
view.setPage(newPage);
+ newPage->profile()->setPersistentPermissionsPolicy(QWebEngineProfile::NoPersistentPermissions);
newPage->setGeolocationPermission(allowed);
connect(newPage, SIGNAL(featurePermissionRequested(const QUrl&, QWebEnginePage::Feature)),
@@ -1660,6 +1661,7 @@ public:
connect(this, &QWebEnginePage::loadFinished, [this](bool success){
m_loadSucceeded = success;
});
+ profile()->setPersistentPermissionsPolicy(QWebEngineProfile::NoPersistentPermissions);
// We need to load content from a resource in order for the securityOrigin to be valid.
load(QUrl("qrc:///resources/content.html"));
}
@@ -3841,6 +3843,7 @@ void tst_QWebEnginePage::notificationPermission()
QFETCH(QString, permission);
QWebEngineProfile otr;
+ otr.setPersistentPermissionsPolicy(QWebEngineProfile::NoPersistentPermissions);
QWebEnginePage page(&otr, nullptr);
QUrl baseUrl("https://www.example.com/somepage.html");
@@ -3948,6 +3951,7 @@ void tst_QWebEnginePage::clipboardReadWritePermissionInitialState()
QFETCH(QString, permission);
QWebEngineProfile otr;
+ otr.setPersistentPermissionsPolicy(QWebEngineProfile::NoPersistentPermissions);
QWebEngineView view(&otr);
QWebEnginePage &page = *view.page();
view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true);
@@ -4008,6 +4012,7 @@ void tst_QWebEnginePage::clipboardReadWritePermission()
QFETCH(QString, finalPermission);
QWebEngineProfile otr;
+ otr.setPersistentPermissionsPolicy(QWebEngineProfile::NoPersistentPermissions);
QWebEngineView view(&otr);
QWebEnginePage &page = *view.page();
view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true);
@@ -4110,6 +4115,7 @@ void tst_QWebEnginePage::localFontAccessPermission() {
QWebEngineView view;
QWebEnginePage page(&view);
+ page.profile()->setPersistentPermissionsPolicy(QWebEngineProfile::NoPersistentPermissions);
view.setPage(&page);
connect(&page, &QWebEnginePage::featurePermissionRequested, &page, [&] (const QUrl &o, QWebEnginePage::Feature f) {
@@ -4141,9 +4147,6 @@ void tst_QWebEnginePage::localFontAccessPermission() {
QTRY_VERIFY_WITH_TIMEOUT(evaluateJavaScriptSync(&page, QStringLiteral("done")).toBool() == true, 1000);
QVERIFY((evaluateJavaScriptSync(&page, QStringLiteral("fonts.length")).toInt() == 0) == shouldBeEmpty);
}
-
- // Reset permission, since otherwise it will be stored between runs
- page.setFeaturePermission(QUrl("qrc:///resources/fontaccess.html"), QWebEnginePage::LocalFontsAccess, QWebEnginePage::PermissionUnknown);
}
void tst_QWebEnginePage::setLifecycleState()
@@ -5721,6 +5724,7 @@ void tst_QWebEnginePage::chooseDesktopMedia()
QWebEnginePage page;
QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool)));
page.settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, true);
+ page.profile()->setPersistentPermissionsPolicy(QWebEngineProfile::NoPersistentPermissions);
bool desktopMediaRequested = false;
bool permissionRequested = false;
diff --git a/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp b/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp
index cebdaaa47..ef069ac1c 100644
--- a/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp
+++ b/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp
@@ -56,6 +56,8 @@ private Q_SLOTS:
void changePersistentCookiesPolicy();
void initiator();
void badDeleteOrder();
+ void permissionPersistence_data();
+ void permissionPersistence();
void qtbug_71895(); // this should be the last test
};
@@ -1015,6 +1017,82 @@ void tst_QWebEngineProfile::badDeleteOrder()
delete view;
}
+void tst_QWebEngineProfile::permissionPersistence_data()
+{
+ QTest::addColumn<QWebEngineProfile::PersistentPermissionsPolicy>("policy");
+ QTest::addColumn<bool>("granted");
+
+ QTest::newRow("noPersistenceNotificationsNoGrant") << QWebEngineProfile::NoPersistentPermissions << false;
+ QTest::newRow("noPersistenceNotificationsGrant") << QWebEngineProfile::NoPersistentPermissions << true;
+ QTest::newRow("memoryPersistenceNotificationsNoGrant") << QWebEngineProfile::PersistentPermissionsInMemory << false;
+ QTest::newRow("diskPersistenceNotificationsGrant") << QWebEngineProfile::PersistentPermissionsOnDisk << true;
+}
+
+void tst_QWebEngineProfile::permissionPersistence()
+{
+ QFETCH(QWebEngineProfile::PersistentPermissionsPolicy, policy);
+ QFETCH(bool, granted);
+
+ TestServer server;
+ QVERIFY(server.start());
+
+ std::unique_ptr<QWebEngineProfile> profile(new QWebEngineProfile("tst_persistence"));
+ profile->setPersistentPermissionsPolicy(policy);
+
+ std::unique_ptr<QWebEnginePage> page(new QWebEnginePage(profile.get()));
+ std::unique_ptr<QSignalSpy> loadSpy(new QSignalSpy(page.get(), &QWebEnginePage::loadFinished));
+ QDir storageDir = QDir(profile->persistentStoragePath());
+
+ // Delete permissions file if it somehow survived on disk
+ storageDir.remove("permissions.json");
+
+ page->load(server.url("/hedgehog.html"));
+ QTRY_COMPARE(loadSpy->size(), 1);
+
+ QVariant variant = granted ? "granted" : "denied";
+ QVariant defaultVariant = "default";
+ page->setFeaturePermission(server.url("/hedgehog.html"), QWebEnginePage::Notifications,
+ granted ? QWebEnginePage::PermissionGrantedByUser : QWebEnginePage::PermissionDeniedByUser);
+ QCOMPARE(evaluateJavaScriptSync(page.get(), "Notification.permission"), variant);
+
+ page.reset();
+ profile.reset();
+ loadSpy.reset();
+
+ bool expectSame = false;
+ if (policy == QWebEngineProfile::PersistentPermissionsOnDisk) {
+ expectSame = true;
+
+ // File is written asynchronously, wait for it to be created
+ QTRY_COMPARE(storageDir.exists("permissions.json"), true);
+ }
+
+ profile.reset(new QWebEngineProfile("tst_persistence"));
+ profile->setPersistentPermissionsPolicy(policy);
+
+ page.reset(new QWebEnginePage(profile.get()));
+ loadSpy.reset(new QSignalSpy(page.get(), &QWebEnginePage::loadFinished));
+ page->load(server.url("/hedgehog.html"));
+ QTRY_COMPARE(loadSpy->size(), 1);
+ QTRY_COMPARE(evaluateJavaScriptSync(page.get(), "Notification.permission"),
+ expectSame ? variant : defaultVariant);
+
+ page->setFeaturePermission(server.url("/hedgehog.html"), QWebEnginePage::Notifications, QWebEnginePage::PermissionUnknown);
+ QCOMPARE(evaluateJavaScriptSync(page.get(), "Notification.permission"), defaultVariant);
+
+ page.reset();
+ profile.reset();
+ loadSpy.reset();
+
+ if (policy == QWebEngineProfile::PersistentPermissionsOnDisk) {
+ // Wait for file to be written to before deleting
+ QTest::qWait(1000);
+ storageDir.remove("permissions.json");
+ }
+
+ QVERIFY(server.stop());
+}
+
void tst_QWebEngineProfile::qtbug_71895()
{
QWebEngineView view;