blob: fb649c82b47ef0c90b5bc575bba10019cbd5089d [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_SYSTEM_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_SYSTEM_H_
#include "device/vr/public/mojom/vr_service.mojom-blink.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "third_party/blink/public/mojom/devtools/console_message.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_xr_dom_overlay_init.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_xr_session_init.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/dom/events/event_target.h"
#include "third_party/blink/renderer/core/dom/events/native_event_listener.h"
#include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
#include "third_party/blink/renderer/core/page/focus_changed_observer.h"
#include "third_party/blink/renderer/modules/xr/xr_session.h"
#include "third_party/blink/renderer/platform/graphics/color.h"
#include "third_party/blink/renderer/platform/heap/handle.h"
#include "third_party/blink/renderer/platform/mojo/heap_mojo_associated_remote.h"
#include "third_party/blink/renderer/platform/mojo/heap_mojo_receiver.h"
#include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
#include "third_party/blink/renderer/platform/mojo/heap_mojo_wrapper_mode.h"
#include "third_party/blink/renderer/platform/scheduler/public/frame_or_worker_scheduler.h"
#include "third_party/blink/renderer/platform/supplementable.h"
#include "third_party/blink/renderer/platform/wtf/forward.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
namespace blink {
class Navigator;
class ScriptPromiseResolver;
class XRFrameProvider;
class XRSessionInit;
// Implementation of the XRSystem interface according to
// https://immersive-web.github.io/webxr/#xrsystem-interface . This is created
// lazily on first access to the navigator.xr attrib,
// and disposed when the execution context is destroyed or on mojo communication
// errors with the browser/device process.
//
// When the XRSystem is used for promises, it uses query objects to store state
// including the associated ScriptPromiseResolver. These query objects are owned
// by the XRSystem and remain alive until the promise is resolved or rejected.
// (See comments below for PendingSupportsSessionQuery and
// PendingRequestSessionQuery.) These query objects are destroyed and any
// outstanding promises rejected when the XRSystem is disposed.
//
// The XRSystem owns mojo connections with the Browser process through
// VRService, used for capability queries and session lifetime
// management. The XRSystem is also the receiver for the VRServiceClient.
//
// The XRSystem owns mojo connections with the Device process (either a
// separate utility process, or implemented as part of the Browser process,
// depending on the runtime and options) through XRFrameProvider and
// XREnvironmentIntegrationProvider. These are used to transport per-frame data
// such as image data and input poses. These are lazily created when first
// needed for a sensor-backed session (all except sensorless inline sessions),
// and destroyed when the XRSystem is disposed.
//
// The XRSystem keeps weak references to XRSession objects after they were
// returned through a successful requestSession promise, but does not own them.
class XRSystem final : public EventTargetWithInlineData,
public Supplement<Navigator>,
public ExecutionContextLifecycleObserver,
public device::mojom::blink::VRServiceClient,
public FocusChangedObserver {
DEFINE_WRAPPERTYPEINFO();
public:
static const char kSupplementName[];
static XRSystem* From(Document&);
static XRSystem* FromIfExists(Document&);
static XRSystem* xr(Navigator&);
// TODO(crbug.com/976796): Fix lint errors.
explicit XRSystem(Navigator&);
DEFINE_ATTRIBUTE_EVENT_LISTENER(devicechange, kDevicechange)
ScriptPromise supportsSession(ScriptState*,
const String&,
ExceptionState& exception_state);
ScriptPromise isSessionSupported(ScriptState*,
const String&,
ExceptionState& exception_state);
ScriptPromise requestSession(ScriptState*,
const String&,
XRSessionInit*,
ExceptionState& exception_state);
XRFrameProvider* frameProvider();
device::mojom::blink::XREnvironmentIntegrationProvider*
xrEnvironmentProviderRemote();
device::mojom::blink::VRService* BrowserService();
// VRServiceClient overrides.
void OnDeviceChanged() override;
// EventTarget overrides.
ExecutionContext* GetExecutionContext() const override;
const AtomicString& InterfaceName() const override;
// ExecutionContextLifecycleObserver overrides.
void ContextDestroyed() override;
void Trace(Visitor*) const override;
// FocusChangedObserver overrides.
void FocusedFrameChanged() override;
bool IsFrameFocused();
using EnvironmentProviderErrorCallback = base::OnceCallback<void()>;
// Registers a callback that'll be invoked when mojo invokes a disconnect
// handler on the underlying XREnvironmentIntegrationProvider remote.
void AddEnvironmentProviderErrorHandler(
EnvironmentProviderErrorCallback callback);
void ExitPresent(base::OnceClosure on_exited);
void SetFramesThrottled(const XRSession* session, bool throttled);
base::TimeTicks NavigationStart() const { return navigation_start_; }
bool IsContextDestroyed() const { return is_context_destroyed_; }
void MakeXrCompatibleAsync(
device::mojom::blink::VRService::MakeXrCompatibleCallback callback);
void MakeXrCompatibleSync(
device::mojom::XrCompatibleResult* xr_compatible_result);
private:
enum SensorRequirement {
kNone,
kOptional,
kRequired,
};
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class SessionRequestStatus : int {
// `requestSession` succeeded.
kSuccess = 0,
// `requestSession` failed with other (unknown) error.
kOtherError = 1,
kMaxValue = kOtherError,
};
struct RequestedXRSessionFeatureSet {
// Set of requested features which are known and valid for the current mode.
XRSessionFeatureSet valid_features;
// Whether any requested features were unknown or invalid
bool invalid_features = false;
};
// Encapsulates blink-side `XRSystem::requestSession()` call. It is a wrapper
// around ScriptPromiseResolver that allows us to add additional logic as
// certain things related to promise's life cycle happen. Instances are owned
// by the XRSystem, see outstanding_request_queries_ below.
class PendingRequestSessionQuery final
: public GarbageCollected<PendingRequestSessionQuery> {
public:
PendingRequestSessionQuery(int64_t ukm_source_id,
ScriptPromiseResolver* resolver,
device::mojom::blink::XRSessionMode mode,
RequestedXRSessionFeatureSet required_features,
RequestedXRSessionFeatureSet optional_features);
virtual ~PendingRequestSessionQuery() = default;
// Resolves underlying promise with passed in XR session.
// If metrics are to be recorded for this session, an
// |XRSessionMetricsRecorded| may be passed in as well.
void Resolve(
XRSession* session,
mojo::PendingRemote<device::mojom::blink::XRSessionMetricsRecorder>
metrics_recorder = mojo::NullRemote());
// Rejects underlying promise with a DOMException.
// Do not call this with |DOMExceptionCode::kSecurityError|, use
// |RejectWithSecurityError| for that. If the exception is thrown
// synchronously, an ExceptionState must be passed in. Otherwise it may be
// null.
void RejectWithDOMException(DOMExceptionCode exception_code,
const String& message,
ExceptionState* exception_state);
// Rejects underlying promise with a SecurityError.
// If the exception is thrown synchronously, an ExceptionState must
// be passed in. Otherwise it may be null.
void RejectWithSecurityError(const String& sanitized_message,
ExceptionState* exception_state);
// Rejects underlying promise with a TypeError.
// If the exception is thrown synchronously, an ExceptionState must
// be passed in. Otherwise it may be null.
void RejectWithTypeError(const String& message,
ExceptionState* exception_state);
device::mojom::blink::XRSessionMode mode() const;
const XRSessionFeatureSet& RequiredFeatures() const;
const XRSessionFeatureSet& OptionalFeatures() const;
bool InvalidRequiredFeatures() const;
bool InvalidOptionalFeatures() const;
bool HasFeature(device::mojom::XRSessionFeature) const;
SensorRequirement GetSensorRequirement() const {
return sensor_requirement_;
}
// Returns underlying resolver's script state.
ScriptState* GetScriptState() const;
void SetDOMOverlayElement(Element* element) {
dom_overlay_element_ = element;
}
Element* DOMOverlayElement() { return dom_overlay_element_; }
void SetTrackedImages(
const Vector<device::mojom::blink::XRTrackedImage>& images) {
tracked_images_ = images;
}
Vector<device::mojom::blink::XRTrackedImage> TrackedImages() const {
return tracked_images_;
}
void SetDepthSensingConfiguration(
const Vector<device::mojom::XRDepthUsage>& preferred_usage,
const Vector<device::mojom::XRDepthDataFormat>& preferred_format) {
preferred_usage_ = preferred_usage;
preferred_format_ = preferred_format;
}
const Vector<device::mojom::XRDepthUsage>& PreferredUsage() const {
return preferred_usage_;
}
const Vector<device::mojom::XRDepthDataFormat>& PreferredFormat() const {
return preferred_format_;
}
virtual void Trace(Visitor*) const;
private:
void ParseSensorRequirement();
device::mojom::XRSessionFeatureRequestStatus GetFeatureRequestStatus(
device::mojom::XRSessionFeature feature,
const XRSession* session) const;
void ReportRequestSessionResult(
SessionRequestStatus status,
XRSession* session = nullptr,
mojo::PendingRemote<device::mojom::blink::XRSessionMetricsRecorder>
metrics_recorder = mojo::NullRemote());
Member<ScriptPromiseResolver> resolver_;
const device::mojom::blink::XRSessionMode mode_;
RequestedXRSessionFeatureSet required_features_;
RequestedXRSessionFeatureSet optional_features_;
SensorRequirement sensor_requirement_ = SensorRequirement::kNone;
const int64_t ukm_source_id_;
Member<Element> dom_overlay_element_;
Vector<device::mojom::blink::XRTrackedImage> tracked_images_;
Vector<device::mojom::XRDepthUsage> preferred_usage_;
Vector<device::mojom::XRDepthDataFormat> preferred_format_;
DISALLOW_COPY_AND_ASSIGN(PendingRequestSessionQuery);
};
static device::mojom::blink::XRSessionOptionsPtr XRSessionOptionsFromQuery(
const PendingRequestSessionQuery& query);
// Encapsulates blink-side `XRSystem::isSessionSupported()` call. It is a
// wrapper around ScriptPromiseResolver that allows us to add additional logic
// as certain things related to promise's life cycle happen. Instances are
// owned by the XRSystem, see outstanding_support_queries_ below.
class PendingSupportsSessionQuery final
: public GarbageCollected<PendingSupportsSessionQuery> {
public:
PendingSupportsSessionQuery(ScriptPromiseResolver*,
device::mojom::blink::XRSessionMode,
bool throw_on_unsupported);
virtual ~PendingSupportsSessionQuery() = default;
// Resolves underlying promise.
void Resolve(bool supported, ExceptionState* exception_state = nullptr);
// Rejects underlying promise with a DOMException.
// Do not call this with |DOMExceptionCode::kSecurityError|, use
// |RejectWithSecurityError| for that. If the exception is thrown
// synchronously, an ExceptionState must be passed in. Otherwise it may be
// null.
void RejectWithDOMException(DOMExceptionCode exception_code,
const String& message,
ExceptionState* exception_state);
// Rejects underlying promise with a SecurityError.
// If the exception is thrown synchronously, an ExceptionState must
// be passed in. Otherwise it may be null.
void RejectWithSecurityError(const String& sanitized_message,
ExceptionState* exception_state);
// Rejects underlying promise with a TypeError.
// If the exception is thrown synchronously, an ExceptionState must
// be passed in. Otherwise it may be null.
void RejectWithTypeError(const String& message,
ExceptionState* exception_state);
bool ThrowOnUnsupported() const { return throw_on_unsupported_; }
device::mojom::blink::XRSessionMode mode() const;
virtual void Trace(Visitor*) const;
private:
Member<ScriptPromiseResolver> resolver_;
const device::mojom::blink::XRSessionMode mode_;
// Only set when calling the deprecated supportsSession method.
const bool throw_on_unsupported_ = false;
DISALLOW_COPY_AND_ASSIGN(PendingSupportsSessionQuery);
};
// Native event listener for fullscreen change / error events when starting an
// immersive-ar session that uses DOM Overlay mode. See
// OnRequestSessionReturned().
class OverlayFullscreenEventManager : public NativeEventListener {
public:
OverlayFullscreenEventManager(
XRSystem* xr,
XRSystem::PendingRequestSessionQuery*,
device::mojom::blink::RequestSessionResultPtr);
~OverlayFullscreenEventManager() override;
// NativeEventListener
void Invoke(ExecutionContext*, Event*) override;
void RequestFullscreen();
void OnSessionStarting();
void Trace(Visitor*) const override;
private:
Member<XRSystem> xr_;
Member<PendingRequestSessionQuery> query_;
device::mojom::blink::RequestSessionResultPtr result_;
DISALLOW_COPY_AND_ASSIGN(OverlayFullscreenEventManager);
};
// Native event listener used when waiting for fullscreen mode to fully exit
// when starting or ending an XR session.
class OverlayFullscreenExitObserver : public NativeEventListener {
public:
explicit OverlayFullscreenExitObserver(XRSystem* xr);
~OverlayFullscreenExitObserver() override;
// NativeEventListener
void Invoke(ExecutionContext*, Event*) override;
void ExitFullscreen(Document* doc, base::OnceClosure on_exited);
void Trace(Visitor*) const override;
private:
Member<XRSystem> xr_;
Member<Document> document_;
base::OnceClosure on_exited_;
DISALLOW_COPY_AND_ASSIGN(OverlayFullscreenExitObserver);
};
// Helper, logs message to the console as well as DVLOGs.
void AddConsoleMessage(mojom::blink::ConsoleMessageLevel error_level,
const String& message);
ScriptPromise InternalIsSessionSupported(ScriptState*,
const String&,
ExceptionState& exception_state,
bool throw_on_unsupported);
const char* CheckInlineSessionRequestAllowed(
LocalFrame* frame,
const PendingRequestSessionQuery& query);
RequestedXRSessionFeatureSet ParseRequestedFeatures(
const HeapVector<ScriptValue>& features,
const device::mojom::blink::XRSessionMode& session_mode,
XRSessionInit* session_init,
mojom::ConsoleMessageLevel error_level);
void RequestImmersiveSession(PendingRequestSessionQuery* query,
ExceptionState* exception_state);
void RequestInlineSession(PendingRequestSessionQuery* query,
ExceptionState* exception_state);
void DoRequestSession(
PendingRequestSessionQuery* query,
device::mojom::blink::XRSessionOptionsPtr session_options);
void OnRequestSessionSetupForDomOverlay(
PendingRequestSessionQuery*,
device::mojom::blink::RequestSessionResultPtr result);
void OnRequestSessionReturned(
PendingRequestSessionQuery*,
device::mojom::blink::RequestSessionResultPtr result);
void OnSupportsSessionReturned(PendingSupportsSessionQuery*,
bool supports_session);
void ResolveSessionRequest(
PendingRequestSessionQuery*,
device::mojom::blink::RequestSessionResultPtr result);
void RejectSessionRequest(PendingRequestSessionQuery*);
void EnsureDevice();
void AddedEventListener(const AtomicString& event_type,
RegisteredEventListener&) override;
XRSession* CreateSession(
device::mojom::blink::XRSessionMode mode,
device::mojom::blink::XREnvironmentBlendMode blend_mode,
device::mojom::blink::XRInteractionMode interaction_mode,
mojo::PendingReceiver<device::mojom::blink::XRSessionClient>
client_receiver,
device::mojom::blink::VRDisplayInfoPtr display_info,
device::mojom::blink::XRSessionDeviceConfigPtr device_config,
XRSessionFeatureSet enabled_features,
bool sensorless_session = false);
XRSession* CreateSensorlessInlineSession();
enum class DisposeType {
kContextDestroyed = 0,
kDisconnected = 1,
};
void Dispose(DisposeType);
void OnEnvironmentProviderDisconnect();
void TryEnsureService();
// Helper, returns true if immersive AR session creation is supported.
// Currently, it checks whether AR is enabled in runtime features, and in web
// settings (controlled by enterprise policy).
bool IsImmersiveArAllowed();
// Indicates whether use of requestDevice has already been logged.
bool did_log_supports_immersive_ = false;
// Indicates whether we've already logged a request for an immersive session.
bool did_log_request_immersive_session_ = false;
// The XR object owns outstanding pending session queries, these live until
// the underlying promise is either resolved or rejected.
HeapHashSet<Member<PendingSupportsSessionQuery>> outstanding_support_queries_;
HeapHashSet<Member<PendingRequestSessionQuery>> outstanding_request_queries_;
bool has_outstanding_immersive_request_ = false;
Vector<EnvironmentProviderErrorCallback>
environment_provider_error_callbacks_;
Member<XRFrameProvider> frame_provider_;
HeapHashSet<WeakMember<XRSession>> sessions_;
HeapMojoRemote<device::mojom::blink::VRService> service_;
HeapMojoAssociatedRemote<
device::mojom::blink::XREnvironmentIntegrationProvider,
HeapMojoWrapperMode::kWithoutContextObserver>
environment_provider_;
HeapMojoReceiver<device::mojom::blink::VRServiceClient, XRSystem> receiver_;
// Time at which navigation started. Used as the base for relative timestamps,
// such as for Gamepad objects.
base::TimeTicks navigation_start_;
FrameOrWorkerScheduler::SchedulingAffectingFeatureHandle
feature_handle_for_scheduler_;
// In DOM overlay mode, use a fullscreen event listener to detect when
// transition to fullscreen mode completes or fails, and reject/resolve
// the pending request session promise accordingly.
Member<OverlayFullscreenEventManager> fullscreen_event_manager_;
// DOM overlay mode uses a separate temporary fullscreen event listener
// if it needs to wait for fullscreen mode to fully exit when ending
// the session.
Member<OverlayFullscreenExitObserver> fullscreen_exit_observer_;
bool is_context_destroyed_ = false;
bool did_service_ever_disconnect_ = false;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_SYSTEM_H_