199 // 2. If the requesting document is not responsible, return false.
200 if (&document != globalObject.document())
201 return false;
202
203 return true;
204}
205
206
207struct WebXRSystem::ResolvedRequestedFeatures {
208 FeaturesArray granted;
209 FeaturesArray consentRequired;
210 FeaturesArray consentOptional;
211};
212
213#define RETURN_FALSE_OR_CONTINUE(mustReturn) { \
214 if (mustReturn) {\
215 return false; \
216 } \
217 continue; \
218}
219
220// https://immersive-web.github.io/webxr/#resolve-the-requested-features
221Optional<WebXRSystem::ResolvedRequestedFeatures> WebXRSystem::resolveRequestedFeatures(XRSessionMode mode, const XRSessionInit& init, WeakPtr<PlatformXR::Device>& device, JSC::JSGlobalObject* globalObject) const
222{
223 // 1. Let consentRequired be an empty list of DOMString.
224 // 2. Let consentOptional be an empty list of DOMString.
225 ResolvedRequestedFeatures resolvedFeatures;
226
227 // 3. Let device be the result of obtaining the current device for mode, requiredFeatures, and optionalFeatures.
228 // 4. Let granted be a list of DOMString initialized to device’s list of enabled features for mode.
229 if (device)
230 resolvedFeatures.granted = device->enabledFeatures(mode);
231
232 // 5. If device is null or device’s list of supported modes does not contain mode, run the following steps:
233 // 5.1 Return the tuple (consentRequired, consentOptional, granted)
234 if (!device || !device->supports(mode))
235 return resolvedFeatures;
236
237 // 6. Add every feature descriptor in the default features table associated
238 // with mode to the indicated feature list if it is not already present.
239 // https://immersive-web.github.io/webxr/#default-features
240 ASSERT(globalObject);
241 JSFeaturesArray requiredFeaturesWithDefaultFeatures = init.requiredFeatures;
242 requiredFeaturesWithDefaultFeatures.append(convertEnumerationToJS(*globalObject, XRReferenceSpaceType::Viewer));
243 if (mode == XRSessionMode::ImmersiveAr || mode == XRSessionMode::ImmersiveVr)
244 requiredFeaturesWithDefaultFeatures.append(convertEnumerationToJS(*globalObject, XRReferenceSpaceType::Local));
245
246 // 7. For each feature in requiredFeatures|optionalFeatures perform the following steps:
247 // 8. For each feature in optionalFeatures perform the following steps:
248 // We're merging both loops in a single lambda. The only difference is that a failure on any required features
249 // implies cancelling the whole process while failures in optional features are just skipped.
250 enum class ParsingMode { Strict, Loose };
251 auto parseFeatures = [&device, &globalObject, mode, &resolvedFeatures] (const JSFeaturesArray& sessionFeatures, ParsingMode parsingMode) -> bool {
252 bool returnOnFailure = parsingMode == ParsingMode::Strict;
253 for (const auto& sessionFeature : sessionFeatures) {
254 // 1. If the feature is null, continue to the next entry.
255 if (sessionFeature.isNull())
256 continue;
257
258 // 2. If feature is not a valid feature descriptor, perform the following steps
259 // 2.1. Let s be the result of calling ? ToString(feature).
260 // 2.2. If s is not a valid feature descriptor or is undefined, (return null|continue to next entry).
261 // 2.3. Set feature to s.
262 auto feature = parseEnumeration<XRReferenceSpaceType>(*globalObject, sessionFeature);
263 if (!feature)
264 RETURN_FALSE_OR_CONTINUE(returnOnFailure);
265
266 // 3. If feature is already in granted, continue to the next entry.
267 if (resolvedFeatures.granted.contains(feature.value()))
268 continue;
269
270 // 4. If the requesting document’s origin is not allowed to use any feature policy required by feature
271 // as indicated by the feature requirements table, (return null|continue to next entry).
272
273 // 5. If session’s XR device is not capable of supporting the functionality described by feature or the
274 // user agent has otherwise determined to reject the feature, (return null|continue to next entry).
275 if (!device->enabledFeatures(mode).contains(feature.value()))
276 RETURN_FALSE_OR_CONTINUE(returnOnFailure);
277
278 // 6. If the functionality described by feature requires explicit consent, append it to (consentRequired|consentOptional).
279 // 7. Else append feature to granted.
280 resolvedFeatures.granted.append(feature.value());
281 }
282 return true;
283 };
284
285 if (!parseFeatures(requiredFeaturesWithDefaultFeatures, ParsingMode::Strict))
286 return WTF::nullopt;
287
288 parseFeatures(init.optionalFeatures, ParsingMode::Loose);
289 return resolvedFeatures;
290}
291
292// https://immersive-web.github.io/webxr/#request-the-xr-permission
293bool WebXRSystem::isXRPermissionGranted(XRSessionMode mode, const XRSessionInit& init, WeakPtr<PlatformXR::Device>& device, JSC::JSGlobalObject* globalObject) const
294{
295 // 1. Set status’s granted to an empty FrozenArray.
296 // 2. Let requiredFeatures be descriptor’s requiredFeatures.
297 // 3. Let optionalFeatures be descriptor’s optionalFeatures.
298 // 4. Let device be the result of obtaining the current device for mode, requiredFeatures, and optionalFeatures.
299
300 // 5. Let result be the result of resolving the requested features given requiredFeatures,optionalFeatures, and mode.
301 auto resolvedFeatures = resolveRequestedFeatures(mode, init, device, globalObject);
302
303 // 6. If result is null, run the following steps:
304 // 6.1. Set status’s state to "denied".
305 // 6.2. Abort these steps.
306 if (!resolvedFeatures)
307 return false;
308
309 // 7. Let (consentRequired, consentOptional, granted) be the fields of result.
310 // 8. The user agent MAY at this point ask the user’s permission for the calling algorithm to use any of the features
311 // in consentRequired and consentOptional. The results of these prompts should be included when determining if there
312 // is a clear signal of user intent for enabling these features.
313 // 9. For each feature in consentRequired perform the following steps:
314 // 9.1. The user agent MAY at this point ask the user’s permission for the calling algorithm to use feature. The results
315 // of these prompts should be included when determining if there is a clear signal of user intent to enable feature.
316 // 9.2. If a clear signal of user intent to enable feature has not been determined, set status’s state to "denied" and
317 // abort these steps.
318 // 9.3. If feature is not in granted, append feature to granted.
319 // 10. For each feature in consentOptional perform the following steps:
320 // 10.1. The user agent MAY at this point ask the user’s permission for the calling algorithm to use feature. The results
321 // of these prompts should be included when determining if there is a clear signal of user intent to enable feature.
322 // 10.2. If a clear signal of user intent to enable feature has not been determined, continue to the next entry.
323 // 10.3. If feature is not in granted, append feature to granted.
324 // 11. Set status’s granted to granted.
325 // 12. Set device’s list of enabled features for mode to granted.
326 // 13. Set status’s state to "granted".
327 return true;
328}
329
330
331// https://immersive-web.github.io/webxr/#dom-xrsystem-requestsession
332void WebXRSystem::requestSession(Document& document, XRSessionMode mode, const XRSessionInit& init, RequestSessionPromise&& promise)
333{
334 // 1. Let promise be a new Promise.
335 // 2. Let immersive be true if mode is an immersive session mode, and false otherwise.
336 // 3. Let global object be the relevant Global object for the XRSystem on which this method was invoked.
337 bool immersive = mode == XRSessionMode::ImmersiveAr || mode == XRSessionMode::ImmersiveVr;
338 auto* globalObject = document.domWindow();
339 ASSERT(globalObject);
340
341 // 4. Check whether the session request is allowed as follows:
342 if (immersive) {
343 if (!immersiveSessionRequestIsAllowedForGlobalObject(*globalObject, document)) {
344 promise.reject(Exception { SecurityError });
345 return;
346 }
347 if (m_pendingImmersiveSession || m_activeImmersiveSession) {
348 promise.reject(Exception { InvalidStateError });
349 return;
350 }
351 m_pendingImmersiveSession = true;
352 } else {
353 if (!inlineSessionRequestIsAllowedForGlobalObject(*globalObject, document, init)) {
354 promise.reject(Exception { SecurityError });
355 return;
356 }
357 }
358
359 // 5. Run the following steps in parallel:
360 document.postTask([this, immersive, init, mode, promise = WTFMove(promise)] (ScriptExecutionContext& context) mutable {
361 // 5.1 Let requiredFeatures be options' requiredFeatures.
362 // 5.2 Let optionalFeatures be options' optionalFeatures.
363 // 5.3 Set device to the result of obtaining the current device for mode, requiredFeatures, and optionalFeatures.
364 PlatformXR::Device* device = obtainCurrentDevice(mode, init.requiredFeatures, init.optionalFeatures);
365
366 // 5.4 Queue a task to perform the following steps:
367 queueTaskKeepingObjectAlive(*this, TaskSource::WebXR, [this, context = makeRef(context), device = makeWeakPtr(device), immersive, init, mode, promise = WTFMove(promise)] () mutable {
368 // 5.4.1 If device is null or device's list of supported modes does not contain mode, run the following steps:
369 if (!device || !device->supports(mode)) {
370 promise.reject(Exception { NotSupportedError });
371 m_pendingImmersiveSession = false;
372 return;
373 }
374
375 // WebKit does not currently support the Permissions API. https://w3c.github.io/permissions/
376 // However we do implement here the permission request algorithm without the
377 // Permissions API bits as it handles, among others, the session features parsing. We also
378 // do it here before creating the session as there is no need to do it on advance.
379 // TODO: we just perform basic checks without asking any permission to the user so far. Maybe we should implement
380 // a mechanism similar to what others do involving passing a message to the UI process.
381
382 // 5.4.4 Let descriptor be an XRPermissionDescriptor initialized with session, requiredFeatures, and optionalFeatures
383 // 5.4.5 Let status be an XRPermissionStatus, initially null
384 // 5.4.6 Request the xr permission with descriptor and status.
385 // 5.4.7 If status' state is "denied" run the following steps:
386 if (!isXRPermissionGranted(mode, init, device, context->execState())) {
387 promise.reject(Exception { NotSupportedError });
388 m_pendingImmersiveSession = false;
389 return;
390 }
391
392 // 5.4.2 Let session be a new XRSession object.
393 // 5.4.3 Initialize the session with session, mode, and device.
394 auto session = WebXRSession::create(context, *this, mode, WTFMove(device));
395
396 // 5.4.8 Potentially set the active immersive session as follows:
397 if (immersive) {
398 m_activeImmersiveSession = session.copyRef();
399 m_pendingImmersiveSession = false;
400 } else
401 m_listOfInlineSessions.append(session.copyRef());
402
403 // 5.4.9 Resolve promise with session.
404 promise.resolve(session);
405
406 // TODO:
407 // 5.4.10 Queue a task to perform the following steps: NOTE: These steps ensure that initial inputsourceschange
408 // events occur after the initial session is resolved.
409 // 1. Set session's promise resolved flag to true.
410 // 2. Let sources be any existing input sources attached to session.
411 // 3. If sources is non-empty, perform the following steps:
412 // 1. Set session's list of active XR input sources to sources.
413 // 2. Fire an XRInputSourcesChangeEvent named inputsourceschange on session with added set to sources.
414
415 });
416 });