summaryrefslogtreecommitdiffstats
path: root/src/corelib/kernel
diff options
context:
space:
mode:
authorTor Arne Vestbø <tor.arne.vestbo@qt.io>2022-05-18 13:42:33 +0200
committerTimur Pocheptsov <timur.pocheptsov@qt.io>2022-11-24 18:48:47 +0100
commit1ccb0bd3efcf3b1cc4643c8bfb77b5e93618b98a (patch)
treef23e4796a803cd48b3ebc255641d12f3f843193d /src/corelib/kernel
parentb4ef0031c67a99ebd98fb324eff8ad2ce5af025d (diff)
Add permission backend for WASM
The only permissions we support so far are camera, microphone, and location. The permission API works even for browsers that don't provide the Web Permission API, as we plumb the individual permission requests for media and geolocation back to our API. Change-Id: I7f5fc2266afee9ada78f2015614a8224e28afa59 Reviewed-by: Mikołaj Boc <Mikolaj.Boc@qt.io> Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
Diffstat (limited to 'src/corelib/kernel')
-rw-r--r--src/corelib/kernel/qpermissions_wasm.cpp278
1 files changed, 278 insertions, 0 deletions
diff --git a/src/corelib/kernel/qpermissions_wasm.cpp b/src/corelib/kernel/qpermissions_wasm.cpp
new file mode 100644
index 0000000000..60e2def853
--- /dev/null
+++ b/src/corelib/kernel/qpermissions_wasm.cpp
@@ -0,0 +1,278 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <private/qpermissions_p.h>
+#include <private/qstdweb_p.h>
+
+#include <qglobalstatic.h>
+#include <qpermissions.h>
+#include <qmetaobject.h>
+#include <qnamespace.h>
+#include <qmetatype.h>
+#include <qstring.h>
+#include <qtimer.h>
+#include <qhash.h>
+
+#include <emscripten.h>
+#include <emscripten/bind.h>
+#include <emscripten/val.h>
+
+#include <utility>
+#include <string>
+#include <queue>
+
+QT_BEGIN_NAMESPACE
+
+using namespace QPermissions::Private;
+using namespace emscripten;
+
+namespace
+{
+ constexpr const char *wapiGranted = "granted";
+ constexpr const char *wapiDenied = "denied";
+ constexpr const char *wapiPrompt = "prompt";
+ constexpr const char *wapiCamera = "camera";
+ constexpr const char *wapiVideoCapture = "video_capture"; // Alternative to "camera".
+ constexpr const char *wapiMicrophone = "microphone";
+ constexpr const char *wapiAudioCapture = "audio_capture"; // Alternative to "microphone".
+ constexpr const char *wapiGeolocation = "geolocation";
+
+ void updatePermission(const std::string &name, const std::string &state,
+ PermissionCallback callback);
+
+ void checkPermission(const std::string &permissionName)
+ {
+ val permissions = val::global("navigator")["permissions"];
+ if (permissions.isUndefined() || permissions.isNull())
+ return;
+
+ qstdweb::PromiseCallbacks callbacks;
+ callbacks.thenFunc = [permissionName](val permissionState)
+ {
+ updatePermission(permissionName, permissionState["state"].as<std::string>(), {});
+ };
+ callbacks.catchFunc = [permissionName](val)
+ {
+ updatePermission(permissionName, wapiDenied, {});
+ };
+
+ val query = val::object();
+ query.set("name", val(permissionName));
+
+ qstdweb::Promise::make(permissions, QStringLiteral("query"), callbacks, query);
+ }
+
+ void checkPermissions()
+ {
+ checkPermission(wapiCamera);
+ checkPermission(wapiMicrophone);
+ checkPermission(wapiGeolocation);
+ }
+
+ void bootstrapCheckPermissions()
+ {
+ QTimer::singleShot(0, []{checkPermissions();});
+ }
+
+ Q_CONSTRUCTOR_FUNCTION(bootstrapCheckPermissions);
+
+ int permissionTypeIdFromString(const std::string &permission)
+ {
+ if (permission == wapiCamera || permission == wapiVideoCapture)
+ return qMetaTypeId<QCameraPermission>();
+ if (permission == wapiMicrophone || permission == wapiAudioCapture)
+ return qMetaTypeId<QMicrophonePermission>();
+ if (permission == wapiGeolocation)
+ return qMetaTypeId<QLocationPermission>();
+
+ qCWarning(lcPermissions, "Unknown permission type '%s'", permission.c_str());
+
+ return -1;
+ }
+
+ Qt::PermissionStatus permissionStatusFromString(const std::string &state)
+ {
+ if (state == wapiGranted)
+ return Qt::PermissionStatus::Granted;
+ if (state == wapiDenied)
+ return Qt::PermissionStatus::Denied;
+ if (state == wapiPrompt)
+ return Qt::PermissionStatus::Undetermined;
+
+ qCWarning(lcPermissions, "Unknown permission state '%s'", state.c_str());
+
+ return Qt::PermissionStatus::Denied;
+ }
+
+ using PermissionHash = QHash<int, Qt::PermissionStatus>;
+ Q_GLOBAL_STATIC(PermissionHash, permissionStatuses);
+
+ void updatePermission(const std::string &name, const std::string &state, PermissionCallback callback)
+ {
+ qCDebug(lcPermissions) << "Updating" << name << "permission to" << state;
+
+ const int type = permissionTypeIdFromString(name);
+ if (type == -1)
+ return; // Unknown permission type
+
+ const auto status = permissionStatusFromString(state);
+ (*permissionStatuses)[type] = status;
+
+ if (callback)
+ callback(status);
+ }
+
+ void requestMediaDevicePermission(const std::string &device, const PermissionCallback &cb)
+ {
+ Q_ASSERT(cb);
+
+ val mediaDevices = val::global("navigator")["mediaDevices"];
+ if (mediaDevices.isUndefined())
+ return cb(Qt::PermissionStatus::Denied);
+
+ qstdweb::PromiseCallbacks queryCallbacks;
+ queryCallbacks.thenFunc = [device, cb](val mediaStream)
+ {
+ val tracks = mediaStream.call<val>("getTracks");
+ if (!tracks.isUndefined() && !tracks.isNull())
+ tracks[0].call<void>("stop");
+ updatePermission(device, wapiGranted, cb);
+ };
+ queryCallbacks.catchFunc = [device, cb](val error)
+ {
+ if (error["name"].as<std::string>() == "NotAllowedError")
+ return updatePermission(device, wapiDenied, cb);
+ updatePermission(device, wapiPrompt, cb);
+ };
+
+ val constraint = val::object();
+ if (device == wapiCamera)
+ constraint.set("video", true);
+ else
+ constraint.set("audio", true);
+
+ qstdweb::Promise::make(mediaDevices, QStringLiteral("getUserMedia"), queryCallbacks, constraint);
+ }
+
+ using GeoRequest = std::pair<QPermission, PermissionCallback>;
+ Q_GLOBAL_STATIC(std::deque<GeoRequest>, geolocationRequestQueue);
+
+ bool processingLocationRequest = false;
+
+ void processNextGeolocationRequest();
+
+ void geolocationSuccess(val position)
+ {
+ Q_UNUSED(position);
+ Q_ASSERT(geolocationRequestQueue->size());
+
+ processingLocationRequest = false;
+
+ auto cb = geolocationRequestQueue->front().second;
+ geolocationRequestQueue->pop_front();
+ updatePermission(wapiGeolocation, wapiGranted, cb);
+ processNextGeolocationRequest();
+ }
+
+ void geolocationError(val error)
+ {
+ Q_ASSERT(geolocationRequestQueue->size());
+
+ static int deniedError = []
+ {
+ val posErr = val::global("GeolocationPositionError");
+ if (posErr.isUndefined() || posErr.isNull())
+ return 1;
+ return posErr["PERMISSION_DENIED"].as<int>();
+ }();
+
+ processingLocationRequest = false;
+
+ auto cb = geolocationRequestQueue->front().second;
+ geolocationRequestQueue->pop_front();
+
+ const int errorCode = error["code"].as<int>();
+ updatePermission(wapiGeolocation, errorCode == deniedError ? wapiDenied : wapiPrompt, cb);
+ processNextGeolocationRequest();
+ }
+
+ EMSCRIPTEN_BINDINGS(qt_permissions) {
+ function("qtLocationSuccess", &geolocationSuccess);
+ function("qtLocationError", &geolocationError);
+ }
+
+ void processNextGeolocationRequest()
+ {
+ if (processingLocationRequest)
+ return;
+
+ if (geolocationRequestQueue->empty())
+ return;
+
+ processingLocationRequest = true;
+
+ val geolocation = val::global("navigator")["geolocation"];
+ Q_ASSERT(!geolocation.isUndefined());
+ Q_ASSERT(!geolocation.isNull());
+
+ const auto &permission = geolocationRequestQueue->front().first;
+ const auto &locationPermission = permission.data<QLocationPermission>();
+ const bool highAccuracy = locationPermission.accuracy() == QLocationPermission::Precise;
+
+ val options = val::object();
+ options.set("enableHighAccuracy", highAccuracy ? true : false);
+ geolocation.call<void>("getCurrentPosition", val::module_property("qtLocationSuccess"),
+ val::module_property("qtLocationError"), options);
+ }
+
+ void requestGeolocationPermission(const QPermission &permission, const PermissionCallback &cb)
+ {
+ Q_ASSERT(cb);
+ Q_UNUSED(permission);
+ Q_UNUSED(cb);
+
+ val geolocation = val::global("navigator")["geolocation"];
+ if (geolocation.isUndefined() || geolocation.isNull())
+ return cb(Qt::PermissionStatus::Denied);
+
+ if (processingLocationRequest)
+ qCWarning(lcPermissions, "Permission to access location requested, while another request is in progress");
+
+ geolocationRequestQueue->push_back(std::make_pair(permission, cb));
+ processNextGeolocationRequest();
+ }
+} // Unnamed namespace
+
+namespace QPermissions::Private
+{
+ Qt::PermissionStatus checkPermission(const QPermission &permission)
+ {
+ const auto it = permissionStatuses->find(permission.type().id());
+ return it != permissionStatuses->end() ? it.value() : Qt::PermissionStatus::Undetermined;
+ }
+
+ void requestPermission(const QPermission &permission, const PermissionCallback &callback)
+ {
+ Q_ASSERT(permission.type().isValid());
+ Q_ASSERT(callback);
+
+ const auto status = checkPermission(permission);
+ if (status != Qt::PermissionStatus::Undetermined)
+ return callback(status);
+
+ const int requestedTypeId = permission.type().id();
+ if (requestedTypeId == qMetaTypeId<QCameraPermission>())
+ return requestMediaDevicePermission(wapiCamera, callback);
+
+ if (requestedTypeId == qMetaTypeId<QMicrophonePermission>())
+ return requestMediaDevicePermission(wapiMicrophone, callback);
+
+ if (requestedTypeId == qMetaTypeId<QLocationPermission>())
+ return requestGeolocationPermission(permission, callback);
+
+ (*permissionStatuses)[requestedTypeId] = Qt::PermissionStatus::Denied;
+ callback(Qt::PermissionStatus::Denied);
+ }
+}
+
+QT_END_NAMESPACE