summaryrefslogtreecommitdiffstats
path: root/src/corelib/kernel/qpermissions_wasm.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/kernel/qpermissions_wasm.cpp')
-rw-r--r--src/corelib/kernel/qpermissions_wasm.cpp275
1 files changed, 275 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..846e62ccf7
--- /dev/null
+++ b/src/corelib/kernel/qpermissions_wasm.cpp
@@ -0,0 +1,275 @@
+// 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)
+ {
+ 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.value<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