Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qpermissions_wasm.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include <private/qpermissions_p.h>
6#include <private/qstdweb_p.h>
7
8#include <qglobalstatic.h>
9#include <qpermissions.h>
10#include <qmetaobject.h>
11#include <qnamespace.h>
12#include <qmetatype.h>
13#include <qstring.h>
14#include <qtimer.h>
15#include <qhash.h>
16
17#include <emscripten.h>
18#include <emscripten/bind.h>
19#include <emscripten/val.h>
20
21#include <utility>
22#include <string>
23#include <queue>
24
25QT_BEGIN_NAMESPACE
26
27using namespace QPermissions::Private;
28using namespace emscripten;
29
30namespace
31{
32 constexpr const char *wapiGranted = "granted";
33 constexpr const char *wapiDenied = "denied";
34 constexpr const char *wapiPrompt = "prompt";
35 constexpr const char *wapiCamera = "camera";
36 constexpr const char *wapiVideoCapture = "video_capture"; // Alternative to "camera".
37 constexpr const char *wapiMicrophone = "microphone";
38 constexpr const char *wapiAudioCapture = "audio_capture"; // Alternative to "microphone".
39 constexpr const char *wapiGeolocation = "geolocation";
40
41 void updatePermission(const std::string &name, const std::string &state,
42 PermissionCallback callback);
43
44 void checkPermission(const std::string &permissionName)
45 {
46 val permissions = val::global("navigator")["permissions"];
47 if (permissions.isUndefined() || permissions.isNull())
48 return;
49
50 qstdweb::PromiseCallbacks callbacks;
51 callbacks.thenFunc = [permissionName](val permissionState)
52 {
53 updatePermission(permissionName, permissionState["state"].as<std::string>(), {});
54 };
55 callbacks.catchFunc = [permissionName](val err)
56 {
57 if (err["name"].as<std::string>() == "NotAllowedError"
58 || err["name"].as<std::string>() == "NotFoundError")
59 return updatePermission(permissionName, wapiDenied, {});
60
61 qCInfo(lcPermissions, "'%s' '%s'", err["name"].as<std::string>().c_str(),
62 err["message"].as<std::string>().c_str());
63 };
64
65 val query = val::object();
66 query.set("name", val(permissionName));
67
68 qstdweb::Promise::make(permissions, QStringLiteral("query"), callbacks, query);
69 }
70
71 void checkPermissions()
72 {
73 checkPermission(wapiCamera);
74 checkPermission(wapiMicrophone);
75 checkPermission(wapiGeolocation);
76 }
77
78 void bootstrapCheckPermissions()
79 {
80 QTimer::singleShot(0, []{checkPermissions();});
81 }
82
83 Q_CONSTRUCTOR_FUNCTION(bootstrapCheckPermissions);
84
85 int permissionTypeIdFromString(const std::string &permission)
86 {
87 if (permission == wapiCamera || permission == wapiVideoCapture)
88 return qMetaTypeId<QCameraPermission>();
89 if (permission == wapiMicrophone || permission == wapiAudioCapture)
90 return qMetaTypeId<QMicrophonePermission>();
91 if (permission == wapiGeolocation)
92 return qMetaTypeId<QLocationPermission>();
93
94 qCWarning(lcPermissions, "Unknown permission type '%s'", permission.c_str());
95
96 return -1;
97 }
98
99 Qt::PermissionStatus permissionStatusFromString(const std::string &state)
100 {
101 if (state == wapiGranted)
103 if (state == wapiDenied)
105 if (state == wapiPrompt)
107
108 qCWarning(lcPermissions, "Unknown permission state '%s'", state.c_str());
109
111 }
112
113 using PermissionHash = QHash<int, Qt::PermissionStatus>;
114 Q_GLOBAL_STATIC(PermissionHash, permissionStatuses);
115
116 void updatePermission(const std::string &name, const std::string &state, PermissionCallback callback)
117 {
118 qCDebug(lcPermissions) << "Updating" << name << "permission to" << state;
119
120 const int type = permissionTypeIdFromString(name);
121 if (type == -1)
122 return; // Unknown permission type
123
124 const auto status = permissionStatusFromString(state);
125 (*permissionStatuses)[type] = status;
126
127 if (callback)
128 callback(status);
129 }
130
131 void requestMediaDevicePermission(const std::string &device, const PermissionCallback &cb)
132 {
133 Q_ASSERT(cb);
134
135 val mediaDevices = val::global("navigator")["mediaDevices"];
136 if (mediaDevices.isUndefined())
138
139 qstdweb::PromiseCallbacks queryCallbacks;
140 queryCallbacks.thenFunc = [device, cb](val stream)
141 {
142 // turn stream/light off
143 if (!stream.isNull() && !stream.isUndefined() && !stream["getTracks"].isUndefined()) {
144 emscripten::val tracks = stream.call<emscripten::val>("getTracks");
145 if (!tracks.isUndefined() && tracks["length"].as<int>() > 0) {
146 for (int i = 0; i < tracks["length"].as<int>(); i++) {
147 tracks[i].call<void>("stop");
148 }
149 }
150 }
151 stream = emscripten::val::null(); // this causes the garbage collection to turn off media light
152
153 updatePermission(device, wapiGranted, cb);
154 };
155 queryCallbacks.catchFunc = [device, cb](val error)
156 {
157 qCInfo(lcPermissions) << "error"
158 << error["name"].as<std::string>();
159
160 if (error["name"].as<std::string>() == "NotAllowedError")
161 return updatePermission(device, wapiDenied, cb);
162 updatePermission(device, wapiPrompt, cb);
163 };
164
165 val constraint = val::object();
166 if (device == wapiCamera)
167 constraint.set("video", true);
168
169 constraint.set("audio", true);
170
171 qstdweb::Promise::make(mediaDevices, QStringLiteral("getUserMedia"), queryCallbacks, constraint);
172 }
173
174 using GeoRequest = std::pair<QPermission, PermissionCallback>;
175 Q_GLOBAL_STATIC(std::deque<GeoRequest>, geolocationRequestQueue);
176
177 bool processingLocationRequest = false;
178
179 void processNextGeolocationRequest();
180
181 void geolocationSuccess(val position)
182 {
183 Q_UNUSED(position);
184 Q_ASSERT(geolocationRequestQueue->size());
185
186 processingLocationRequest = false;
187
188 auto cb = geolocationRequestQueue->front().second;
189 geolocationRequestQueue->pop_front();
190 updatePermission(wapiGeolocation, wapiGranted, cb);
191 processNextGeolocationRequest();
192 }
193
194 void geolocationError(val error)
195 {
196 Q_ASSERT(geolocationRequestQueue->size());
197
198 static int deniedError = []
199 {
200 val posErr = val::global("GeolocationPositionError");
201 if (posErr.isUndefined() || posErr.isNull())
202 return 1;
203 return posErr["PERMISSION_DENIED"].as<int>();
204 }();
205
206 processingLocationRequest = false;
207
208 auto cb = geolocationRequestQueue->front().second;
209 geolocationRequestQueue->pop_front();
210
211 const int errorCode = error["code"].as<int>();
212 updatePermission(wapiGeolocation, errorCode == deniedError ? wapiDenied : wapiPrompt, cb);
213 processNextGeolocationRequest();
214 }
215
216 EMSCRIPTEN_BINDINGS(qt_permissions) {
217 function("qtLocationSuccess", &geolocationSuccess);
218 function("qtLocationError", &geolocationError);
219 }
220
221 void processNextGeolocationRequest()
222 {
223 if (processingLocationRequest)
224 return;
225
226 if (geolocationRequestQueue->empty())
227 return;
228
229 processingLocationRequest = true;
230
231 val geolocation = val::global("navigator")["geolocation"];
232 Q_ASSERT(!geolocation.isUndefined());
233 Q_ASSERT(!geolocation.isNull());
234
235 const auto &permission = geolocationRequestQueue->front().first;
236 const auto locationPermission = *permission.value<QLocationPermission>();
237 const bool highAccuracy = locationPermission.accuracy() == QLocationPermission::Precise;
238
239 val options = val::object();
240 options.set("enableHighAccuracy", highAccuracy ? true : false);
241 geolocation.call<void>("getCurrentPosition", val::module_property("qtLocationSuccess"),
242 val::module_property("qtLocationError"), options);
243 }
244
245 void requestGeolocationPermission(const QPermission &permission, const PermissionCallback &cb)
246 {
247 Q_ASSERT(cb);
248 Q_UNUSED(permission);
249 Q_UNUSED(cb);
250
251 val geolocation = val::global("navigator")["geolocation"];
252 if (geolocation.isUndefined() || geolocation.isNull())
254
255 if (processingLocationRequest)
256 qCWarning(lcPermissions, "Permission to access location requested, while another request is in progress");
257
258 geolocationRequestQueue->push_back(std::make_pair(permission, cb));
259 processNextGeolocationRequest();
260 }
261} // Unnamed namespace
262
263namespace QPermissions::Private
264{
266 {
267 const auto it = permissionStatuses->find(permission.type().id());
268 return it != permissionStatuses->end() ? it.value() : Qt::PermissionStatus::Undetermined;
269 }
270
271 void requestPermission(const QPermission &permission, const PermissionCallback &callback)
272 {
273 Q_ASSERT(permission.type().isValid());
274 Q_ASSERT(callback);
275
276 const auto status = checkPermission(permission);
278 return callback(status);
279
280 const int requestedTypeId = permission.type().id();
281 if (requestedTypeId == qMetaTypeId<QCameraPermission>())
282 return requestMediaDevicePermission(wapiCamera, callback);
283
284 if (requestedTypeId == qMetaTypeId<QMicrophonePermission>())
285 return requestMediaDevicePermission(wapiMicrophone, callback);
286
287 if (requestedTypeId == qMetaTypeId<QLocationPermission>())
288 return requestGeolocationPermission(permission, callback);
289
290 (*permissionStatuses)[requestedTypeId] = Qt::PermissionStatus::Denied;
292 }
293}
294
295QT_END_NAMESPACE
\inmodule QtCore
Definition qhash.h:843
\inmodule QtCore \inheaderfile QPermissions
void requestPermission(const QPermission &permission, const PermissionCallback &callback)
Qt::PermissionStatus checkPermission(const QPermission &permission)
Definition qcompare.h:111
PermissionStatus
#define Q_GLOBAL_STATIC(TYPE, NAME,...)
#define QStringLiteral(str)
Definition qstring.h:1825