blob: 96e4cf2b80a1a9d063e54be2d2396fac77b9b721 [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/xr/service/vr_service_impl.h"
#include <algorithm>
#include <utility>
#include <vector>
#include "base/containers/contains.h"
#include "base/dcheck_is_on.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/stl_util.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/common/trace_event_common.h"
#include "build/build_config.h"
#include "components/viz/common/surfaces/frame_sink_id.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/xr/metrics/session_metrics_helper.h"
#include "content/browser/xr/service/browser_xr_runtime_impl.h"
#include "content/browser/xr/service/xr_permission_results.h"
#include "content/browser/xr/service/xr_runtime_manager_impl.h"
#include "content/browser/xr/webxr_internals/mojom/webxr_internals.mojom.h"
#include "content/browser/xr/webxr_internals/webxr_internals_handler_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/permission_controller.h"
#include "content/public/browser/permission_descriptor_util.h"
#include "content/public/browser/permission_request_description.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/origin_util.h"
#include "device/vr/buildflags/buildflags.h"
#include "device/vr/public/cpp/features.h"
#include "device/vr/public/cpp/session_mode.h"
#include "device/vr/public/mojom/vr_service.mojom-shared.h"
#include "device/vr/public/mojom/xr_device.mojom-shared.h"
#include "device/vr/public/mojom/xr_session.mojom-shared.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"
#include "third_party/blink/public/mojom/permissions/permission_status.mojom-shared.h"
namespace {
device::mojom::XRRuntimeSessionOptionsPtr GetRuntimeOptions(
device::mojom::XRSessionOptions* options) {
device::mojom::XRRuntimeSessionOptionsPtr runtime_options =
device::mojom::XRRuntimeSessionOptions::New();
runtime_options->mode = options->mode;
return runtime_options;
}
// Helper, returns collection of permissions required for XR session creation
// for session with mode set to |mode|. The order in the result does not matter
// as the permissions API does not honor it.
std::vector<blink::PermissionType> GetRequiredPermissionsForMode(
device::mojom::XRSessionMode mode) {
std::vector<blink::PermissionType> permissions;
auto mode_permission = content::XrPermissionResults::GetPermissionFor(mode);
if (mode_permission) {
permissions.push_back(*mode_permission);
}
return permissions;
}
// Helper, returns collection of permissions required for XR session creation
// for session with enabled features listed in |required_features| and
// |optional_features|. The order in the result does not matter as the
// permissions API does not honor it.
std::vector<blink::PermissionType> GetRequiredPermissionsForFeatures(
const std::unordered_set<device::mojom::XRSessionFeature>&
required_features,
const std::unordered_set<device::mojom::XRSessionFeature>&
optional_features) {
std::vector<blink::PermissionType> permissions;
for (const auto& required_feature : required_features) {
auto feature_permission =
content::XrPermissionResults::GetPermissionFor(required_feature);
if (feature_permission &&
!base::Contains(permissions, *feature_permission)) {
permissions.push_back(*feature_permission);
}
}
for (const auto& optional_feature : optional_features) {
auto feature_permission =
content::XrPermissionResults::GetPermissionFor(optional_feature);
if (feature_permission &&
!base::Contains(permissions, *feature_permission)) {
permissions.push_back(*feature_permission);
}
}
return permissions;
}
// TODO(crbug.com/40930146): Replace with std::ranges::set_difference
std::unordered_set<device::mojom::XRSessionFeature> GetMissingRequiredFeatures(
const std::unordered_set<device::mojom::XRSessionFeature>& enabled_features,
const std::unordered_set<device::mojom::XRSessionFeature>&
required_features) {
DVLOG(3) << __func__
<< ": enabled_features.size()=" << enabled_features.size();
std::unordered_set<device::mojom::XRSessionFeature> missing_required_features;
for (const auto& required_feature : required_features) {
if (!base::Contains(enabled_features, required_feature)) {
DVLOG(2) << __func__
<< ": one of the required features was not enabled on the "
"created session, feature: "
<< required_feature;
missing_required_features.insert(required_feature);
}
}
return missing_required_features;
}
void RejectSession(device::mojom::VRService::RequestSessionCallback callback,
size_t trace_id,
device::mojom::RequestSessionError error,
const std::string& failure_reason_description,
std::unordered_set<device::mojom::XRSessionFeature>*
rejected_features = nullptr) {
DVLOG(2) << __func__
<< ": failure reason description=" << failure_reason_description;
webxr::mojom::SessionRejectedRecordPtr session_rejected_record =
webxr::mojom::SessionRejectedRecord::New();
session_rejected_record->trace_id = trace_id;
session_rejected_record->failure_reason = error;
session_rejected_record->rejected_time = base::Time::Now();
session_rejected_record->failure_reason_description =
failure_reason_description;
if (rejected_features) {
session_rejected_record->rejected_features.assign(
rejected_features->begin(), rejected_features->end());
}
auto* runtime_manager_impl = static_cast<content::XRRuntimeManagerImpl*>(
content::XRRuntimeManager::GetInstanceIfCreated());
if (runtime_manager_impl) {
runtime_manager_impl->GetLoggerManager().RecordSessionRejected(
std::move(session_rejected_record));
}
std::move(callback).Run(
device::mojom::RequestSessionResult::NewFailureReason(error));
}
} // namespace
namespace content {
VRServiceImpl::SessionRequestData::SessionRequestData(
device::mojom::XRSessionOptionsPtr options,
device::mojom::VRService::RequestSessionCallback callback,
device::mojom::XRDeviceId runtime_id)
: callback(std::move(callback)),
required_features(options->required_features.begin(),
options->required_features.end()),
optional_features(options->optional_features.begin(),
options->optional_features.end()),
options(std::move(options)),
runtime_id(runtime_id) {}
VRServiceImpl::SessionRequestData::~SessionRequestData() {
// In some cases, we may get dropped before the VRService pipe is closed. In
// these cases we need to try to ensure that the callback is run or else we
// hit DCHECKs for dropping the callback without closing the pipe.
// This most often occurs when the Permissions prompt is dismissed.
if (callback) {
RejectSession(std::move(callback), options->trace_id,
device::mojom::RequestSessionError::UNKNOWN_FAILURE,
"SessionRequestData destroyed without running callback.");
}
}
VRServiceImpl::SessionRequestData::SessionRequestData(SessionRequestData&&) =
default;
VRServiceImpl::XrCompatibleCallback::XrCompatibleCallback(
device::mojom::VRService::MakeXrCompatibleCallback callback)
: callback(std::move(callback)) {}
VRServiceImpl::XrCompatibleCallback::XrCompatibleCallback(
XrCompatibleCallback&& wrapper) {
this->callback = std::move(wrapper.callback);
}
VRServiceImpl::XrCompatibleCallback::~XrCompatibleCallback() {
if (!callback.is_null())
std::move(callback).Run(
device::mojom::XrCompatibleResult::kNoDeviceAvailable);
}
VRServiceImpl::VRServiceImpl(content::RenderFrameHost* render_frame_host)
: WebContentsObserver(
content::WebContents::FromRenderFrameHost(render_frame_host)),
render_frame_host_(render_frame_host),
in_focused_frame_(render_frame_host->GetView()->HasFocus()) {
DCHECK(render_frame_host_);
DVLOG(2) << __func__;
runtime_manager_ =
XRRuntimeManagerImpl::GetOrCreateInstance(*GetWebContents());
runtime_manager_->AddService(this);
magic_window_controllers_.set_disconnect_handler(base::BindRepeating(
&VRServiceImpl::OnInlineSessionDisconnected,
base::Unretained(this))); // Unretained is OK since the collection is
// owned by VRServiceImpl.
}
// Constructor for testing.
VRServiceImpl::VRServiceImpl(base::PassKey<XRRuntimeManagerTest>)
: render_frame_host_(nullptr) {
DVLOG(2) << __func__;
runtime_manager_ = XRRuntimeManagerImpl::GetOrCreateInstanceForTesting();
runtime_manager_->AddService(this);
}
VRServiceImpl::~VRServiceImpl() {
DVLOG(2) << __func__;
// Ensure that any active magic window sessions are disconnected to avoid
// collisions when a new session starts. See https://crbug.com/1017959, the
// disconnect handler doesn't get called automatically on page navigation.
for (auto it = magic_window_controllers_.begin();
it != magic_window_controllers_.end(); ++it) {
OnInlineSessionDisconnected(it.id());
}
if (on_exit_present_) {
std::move(on_exit_present_).Run();
}
runtime_manager_->RemoveService(this);
}
void VRServiceImpl::Create(
content::RenderFrameHost* render_frame_host,
mojo::PendingReceiver<device::mojom::VRService> receiver) {
DVLOG(2) << __func__;
std::unique_ptr<VRServiceImpl> vr_service_impl =
std::make_unique<VRServiceImpl>(render_frame_host);
VRServiceImpl* impl = vr_service_impl.get();
impl->receiver_ = mojo::MakeSelfOwnedReceiver(std::move(vr_service_impl),
std::move(receiver));
}
void VRServiceImpl::InitializationComplete() {
// After initialization has completed, we can correctly answer
// supportsSession, and can provide correct display capabilities.
DVLOG(2) << __func__;
initialization_complete_ = true;
ResolvePendingRequests();
}
void VRServiceImpl::SetClient(
mojo::PendingRemote<device::mojom::VRServiceClient> service_client) {
if (service_client_) {
mojo::ReportBadMessage("ServiceClient should only be set once.");
return;
}
DVLOG(2) << __func__;
service_client_.Bind(std::move(service_client));
}
void VRServiceImpl::ResolvePendingRequests() {
DVLOG(2) << __func__
<< ": pending_requests_.size()=" << pending_requests_.size();
for (auto& callback : pending_requests_) {
std::move(callback).Run();
}
pending_requests_.clear();
}
void VRServiceImpl::RuntimesChanged() {
DVLOG(2) << __func__;
if (service_client_) {
service_client_->OnDeviceChanged();
}
}
void VRServiceImpl::OnWebContentsFocused(content::RenderWidgetHost* host) {
OnWebContentsFocusChanged(host, true);
}
void VRServiceImpl::OnWebContentsLostFocus(content::RenderWidgetHost* host) {
OnWebContentsFocusChanged(host, false);
}
void VRServiceImpl::RenderFrameDeleted(content::RenderFrameHost* host) {
DVLOG(2) << __func__;
if (host != render_frame_host_)
return;
// Receiver should always be live here, as this is a SelfOwnedReceiver.
// Close the receiver (and delete this VrServiceImpl) when the RenderFrameHost
// is deleted.
DCHECK(receiver_.get());
receiver_->Close();
}
void VRServiceImpl::OnWebContentsFocusChanged(content::RenderWidgetHost* host,
bool focused) {
if (!render_frame_host_->GetView() ||
render_frame_host_->GetView()->GetRenderWidgetHost() != host) {
return;
}
in_focused_frame_ = focused;
for (const auto& controller : magic_window_controllers_)
controller->SetFrameDataRestricted(!focused);
}
void VRServiceImpl::OnInlineSessionCreated(
SessionRequestData request,
device::mojom::XRRuntimeSessionResultPtr session_result) {
if (!session_result) {
TRACE_EVENT("xr",
"VRServiceImpl::OnInlineSessionCreated: no session_result",
perfetto::Flow::Global(request.options->trace_id));
RejectSession(std::move(request.callback), request.options->trace_id,
device::mojom::RequestSessionError::UNKNOWN_RUNTIME_ERROR,
"Runtime did not provide a session.");
return;
}
mojo::Remote<device::mojom::XRSessionController> controller(
std::move(session_result->controller));
// Start giving out magic window data if we are focused.
controller->SetFrameDataRestricted(!in_focused_frame_);
auto id = magic_window_controllers_.Add(std::move(controller));
DVLOG(2) << __func__ << ": session_id=" << id.GetUnsafeValue()
<< " runtime_id=" << request.runtime_id;
auto* session = session_result->session.get();
std::unordered_set<device::mojom::XRSessionFeature> enabled_features(
session->enabled_features.begin(), session->enabled_features.end());
auto missing_required_features =
GetMissingRequiredFeatures(enabled_features, request.required_features);
if (!missing_required_features.empty()) {
// UNKNOWN_FAILURE since a runtime should not return a session if there
// exists a required feature that was not enabled - this would signify a bug
// in the runtime.
TRACE_EVENT(
"xr",
"VRServiceImpl::OnInlineSessionCreated: required feature not granted",
perfetto::Flow::Global(request.options->trace_id));
RejectSession(std::move(request.callback), request.options->trace_id,
device::mojom::RequestSessionError::UNKNOWN_FAILURE,
"Required feature not granted.", &missing_required_features);
return;
}
mojo::PendingRemote<device::mojom::XRSessionMetricsRecorder>
session_metrics_recorder = GetSessionMetricsHelper()->StartInlineSession(
*(request.options), enabled_features, id.GetUnsafeValue());
OnSessionCreated(
std::move(request), std::move(session_result->session),
std::move(session_metrics_recorder),
mojo::PendingRemote<device::mojom::WebXrInternalsRendererListener>());
}
void VRServiceImpl::OnImmersiveSessionCreated(
SessionRequestData request,
device::mojom::XRRuntimeSessionResultPtr session_result) {
DCHECK(request.options);
if (!session_result) {
TRACE_EVENT("xr",
"VRServiceImpl::OnImmersiveSessionCreated: no session_result",
perfetto::Flow::Global(request.options->trace_id));
RejectSession(std::move(request.callback), request.options->trace_id,
device::mojom::RequestSessionError::UNKNOWN_RUNTIME_ERROR,
"Runtime did not provide a session.");
return;
}
auto* session = session_result->session.get();
std::unordered_set<device::mojom::XRSessionFeature> enabled_features(
session->enabled_features.begin(), session->enabled_features.end());
auto missing_required_features =
GetMissingRequiredFeatures(enabled_features, request.required_features);
if (!missing_required_features.empty()) {
// UNKNOWN_FAILURE since a runtime should not return a session if there
// exists a required feature that was not enabled - this would signify a bug
// in the runtime.
TRACE_EVENT("xr",
"VRServiceImpl::OnImmersiveSessionCreated: required feature "
"not granted",
perfetto::Flow::Global(request.options->trace_id));
RejectSession(std::move(request.callback), request.options->trace_id,
device::mojom::RequestSessionError::UNKNOWN_FAILURE,
"Required feature not granted.", &missing_required_features);
return;
}
// Get the metrics tracker for the new immersive session
mojo::PendingRemote<device::mojom::XRSessionMetricsRecorder>
session_metrics_recorder =
GetSessionMetricsHelper()->StartImmersiveSession(
request.runtime_id, *(request.options), enabled_features);
render_frame_host_->GetProcess()->OnImmersiveXrSessionStarted();
// If the session specified a FrameSinkId that means that it is handling its
// own compositing in a way that we should notify the WebContents about.
if (session_result->frame_sink_id) {
if (session_result->frame_sink_id->is_valid()) {
static_cast<WebContentsImpl*>(GetWebContents())
->OnXrHasRenderTarget(*session_result->frame_sink_id);
} else {
DLOG(ERROR) << __func__ << " frame_sink_id was specified but was invalid";
}
}
OnSessionCreated(std::move(request), std::move(session_result->session),
std::move(session_metrics_recorder),
runtime_manager_->GetLoggerManager().BindRenderListener());
}
void VRServiceImpl::OnInlineSessionDisconnected(
mojo::RemoteSetElementId session_id) {
DVLOG(2) << __func__ << ": session_id=" << session_id.GetUnsafeValue();
// Notify metrics helper that inline session was stopped.
auto* metrics_helper = GetSessionMetricsHelper();
metrics_helper->StopAndRecordInlineSession(session_id.GetUnsafeValue());
}
SessionMetricsHelper* VRServiceImpl::GetSessionMetricsHelper() {
content::WebContents* web_contents =
content::WebContents::FromRenderFrameHost(render_frame_host_);
SessionMetricsHelper* metrics_helper =
SessionMetricsHelper::FromWebContents(web_contents);
if (!metrics_helper) {
// This will only happen if we are not already in VR; set start params
// accordingly.
metrics_helper = SessionMetricsHelper::CreateForWebContents(web_contents);
}
return metrics_helper;
}
void VRServiceImpl::OnSessionCreated(
SessionRequestData request,
device::mojom::XRSessionPtr session,
mojo::PendingRemote<device::mojom::XRSessionMetricsRecorder>
session_metrics_recorder,
mojo::PendingRemote<device::mojom::WebXrInternalsRendererListener>
xr_internals_listener) {
DVLOG(2) << __func__ << ": session_runtime_id=" << request.runtime_id;
// Not checking for validity of |session|, since that's done by
// |OnInlineSessionCreated| and |OnImmersiveSessionCreated|.
UMA_HISTOGRAM_ENUMERATION("XR.RuntimeUsed", request.runtime_id);
TRACE_EVENT("xr", "VRServiceImpl::OnSessionCreated: succeeded",
perfetto::Flow::Global(request.options->trace_id));
mojo::Remote<device::mojom::XRSessionClient> client;
session->client_receiver = client.BindNewPipeAndPassReceiver();
client->OnVisibilityStateChanged(visibility_state_);
session_clients_.Add(std::move(client));
auto success = device::mojom::RequestSessionSuccess::New();
success->session = std::move(session);
success->metrics_recorder = std::move(session_metrics_recorder);
success->trace_id = request.options->trace_id;
success->xr_internals_listener = std::move(xr_internals_listener);
std::move(request.callback)
.Run(device::mojom::RequestSessionResult::NewSuccess(std::move(success)));
}
void VRServiceImpl::RequestSession(
device::mojom::XRSessionOptionsPtr options,
device::mojom::VRService::RequestSessionCallback callback) {
DVLOG(2) << __func__;
DCHECK(options);
webxr::mojom::SessionRequestedRecordPtr session_requested_record =
webxr::mojom::SessionRequestedRecord::New();
session_requested_record->options = options->Clone();
session_requested_record->requested_time = base::Time::Now();
runtime_manager_->GetLoggerManager().RecordSessionRequested(
std::move(session_requested_record));
// Queue the request to get to when initialization has completed.
if (!initialization_complete_) {
DVLOG(2) << __func__ << ": initialization not yet complete, defer request";
pending_requests_.push_back(
base::BindOnce(&VRServiceImpl::RequestSession, base::Unretained(this),
std::move(options), std::move(callback)));
return;
}
if (runtime_manager_->IsOtherClientPresenting(this) ||
runtime_manager_->HasPendingImmersiveRequest()) {
DVLOG(2) << __func__
<< ": can't create sessions while an immersive session exists";
// Can't create sessions while an immersive session exists.
RejectSession(
std::move(callback), options->trace_id,
device::mojom::RequestSessionError::EXISTING_IMMERSIVE_SESSION,
"There is an existing immersive session.");
return;
}
auto* runtime = runtime_manager_->GetRuntimeForOptions(options.get());
if (!runtime) {
RejectSession(std::move(callback), options->trace_id,
device::mojom::RequestSessionError::NO_RUNTIME_FOUND,
"No runtime found for the given session options.");
return;
}
const bool has_user_activation =
render_frame_host_->HasTransientUserActivation();
if (!has_user_activation) {
// User activation is verified blink-side, so this should never fail
// (everything that happens up to this point should not take enough time for
// the user activation to expire). Treat lack of user activation as unknown
// failure:
RejectSession(std::move(callback), options->trace_id,
device::mojom::RequestSessionError::UNKNOWN_FAILURE,
"Missing user activation.");
return;
}
// The consent flow cannot differentiate between optional and required
// features, but we don't need to block creation if an optional feature is
// not supported. Remove all unsupported optional features from the
// optional_features collection before handing it off.
std::erase_if(options->optional_features, [runtime](auto& feature) {
return !runtime->SupportsFeature(feature);
});
SessionRequestData request(std::move(options), std::move(callback),
runtime->GetId());
GetPermissionStatus(std::move(request), runtime);
}
void VRServiceImpl::DoRequestPermissions(
const std::vector<blink::PermissionType> request_permissions,
base::OnceCallback<void(const std::vector<PermissionResult>&)>
result_callback) {
PermissionController* permission_controller =
GetWebContents()->GetBrowserContext()->GetPermissionController();
CHECK(permission_controller);
permission_controller->RequestPermissionsFromCurrentDocument(
render_frame_host_,
PermissionRequestDescription(
PermissionDescriptorUtil::
CreatePermissionDescriptorForPermissionTypes(request_permissions),
/*user_gesture=*/true),
std::move(result_callback));
}
void VRServiceImpl::GetPermissionStatus(SessionRequestData request,
BrowserXRRuntimeImpl* runtime) {
DVLOG(2) << __func__;
DCHECK(request.options);
DCHECK(runtime);
DCHECK_EQ(runtime->GetId(), request.runtime_id);
#if BUILDFLAG(ENABLE_OPENXR)
if (request.options->mode == device::mojom::XRSessionMode::kImmersiveAr &&
runtime->GetId() == device::mojom::XRDeviceId::OPENXR_DEVICE_ID) {
DCHECK(device::features::IsOpenXrArEnabled());
}
#endif
// Need to calculate the permissions before the call below, as otherwise
// std::move nulls options out before `GetRequiredPermissions()` runs.
const std::vector<blink::PermissionType> permissions_for_mode =
GetRequiredPermissionsForMode(request.options->mode);
DoRequestPermissions(
permissions_for_mode,
base::BindOnce(&VRServiceImpl::OnPermissionResultsForMode,
weak_ptr_factory_.GetWeakPtr(), std::move(request),
permissions_for_mode));
}
void VRServiceImpl::OnPermissionResultsForMode(
SessionRequestData request,
const std::vector<blink::PermissionType>& permissions,
const std::vector<PermissionResult>& results) {
DVLOG(2) << __func__ << ": permissions.size()=" << permissions.size();
DCHECK_EQ(permissions.size(), results.size());
// Prolong the user activation since the user may have taken long enough to
// answer the permission prompts that the transient user activation expired.
// This is fine to do here, since we enforce that the activation existed prior
// to requesting permissions.
DVLOG(3) << __func__ << ": prolonging user activation, current status="
<< render_frame_host_->HasTransientUserActivation();
render_frame_host_->NotifyUserActivation(
blink::mojom::UserActivationNotificationType::kInteraction);
const XrPermissionResults permission_results(permissions, results);
bool is_consent_granted =
permission_results.HasPermissionsFor(request.options->mode);
DVLOG(2) << __func__ << ": is_consent_granted=" << is_consent_granted;
if (!is_consent_granted) {
RejectSession(std::move(request.callback), request.options->trace_id,
device::mojom::RequestSessionError::USER_DENIED_CONSENT,
"Consent was not granted for the requested mode.");
return;
}
const std::vector<blink::PermissionType> permissions_for_features =
GetRequiredPermissionsForFeatures(request.required_features,
request.optional_features);
auto result_callback =
base::BindOnce(&VRServiceImpl::OnPermissionResultsForFeatures,
weak_ptr_factory_.GetWeakPtr(), std::move(request),
permissions_for_features);
if (permissions_for_features.empty()) {
std::move(result_callback).Run({});
return;
}
DoRequestPermissions(permissions_for_features, std::move(result_callback));
}
void VRServiceImpl::OnPermissionResultsForFeatures(
SessionRequestData request,
const std::vector<blink::PermissionType>& permissions,
const std::vector<PermissionResult>& results) {
const XrPermissionResults permission_results(permissions, results);
std::unordered_set<device::mojom::XRSessionFeature> rejected_features;
for (auto& required_feature : request.required_features) {
if (!permission_results.HasPermissionsFor(required_feature)) {
DVLOG(1) << __func__ << ": required_feature=" << required_feature
<< " lacks neccessary permissions";
rejected_features.insert(required_feature);
}
}
if (!rejected_features.empty()) {
RejectSession(std::move(request.callback), request.options->trace_id,
device::mojom::RequestSessionError::USER_DENIED_CONSENT,
"Lacks necessary permissions for the required feature.",
&rejected_features);
return;
}
std::unordered_set<device::mojom::XRSessionFeature> granted_optional_features;
for (auto& optional_feature : request.optional_features) {
if (permission_results.HasPermissionsFor(optional_feature)) {
granted_optional_features.insert(optional_feature);
} else {
DVLOG(2) << __func__ << ": optional_feature=" << optional_feature
<< " lacks neccessary permissions";
}
}
// Replace optional features on the request with the ones that have been
// granted by the user:
std::swap(request.optional_features, granted_optional_features);
// Re-check for another client instance after a potential user consent.
if (runtime_manager_->IsOtherClientPresenting(this)) {
// Can't create sessions while an immersive session exists.
RejectSession(
std::move(request.callback), request.options->trace_id,
device::mojom::RequestSessionError::EXISTING_IMMERSIVE_SESSION,
"Another client started presenting while waiting for permissions.");
return;
}
EnsureRuntimeInstalled(std::move(request), nullptr);
}
void VRServiceImpl::EnsureRuntimeInstalled(SessionRequestData request,
BrowserXRRuntimeImpl* runtime) {
DVLOG(2) << __func__;
// If we were not provided the runtime, try to get it again.
if (!runtime)
runtime = runtime_manager_->GetRuntimeForOptions(request.options.get());
// Ensure that it's the same runtime as the one we expect.
if (!runtime || runtime->GetId() != request.runtime_id) {
DVLOG(1) << __func__
<< ": failed to obtain the runtime or the runtime id does not "
"match the expected ID, request.runtime_id="
<< request.runtime_id;
RejectSession(std::move(request.callback), request.options->trace_id,
device::mojom::RequestSessionError::RUNTIMES_CHANGED,
"failed to obtain the runtime or the runtime id does not "
"match the expected ID.");
return;
}
runtime->EnsureInstalled(
render_frame_host_->GetProcess()->GetDeprecatedID(),
render_frame_host_->GetRoutingID(),
base::BindOnce(&VRServiceImpl::OnInstallResult,
weak_ptr_factory_.GetWeakPtr(), std::move(request)));
}
void VRServiceImpl::OnInstallResult(SessionRequestData request,
bool install_succeeded) {
DVLOG(2) << __func__ << ": install_succeeded=" << install_succeeded;
if (!install_succeeded) {
RejectSession(std::move(request.callback), request.options->trace_id,
device::mojom::RequestSessionError::RUNTIME_INSTALL_FAILURE,
"Runtime installation failed.");
return;
}
// Prolong the user activation since the user may have taken long enough to
// install the runtime that the transient user activation expired. This is
// fine to do here, since we enforce that the activation existed prior to
// kicking off installation.
DVLOG(3) << __func__ << ": prolonging user activation, current status="
<< render_frame_host_->HasTransientUserActivation();
render_frame_host_->NotifyUserActivation(
blink::mojom::UserActivationNotificationType::kInteraction);
DoRequestSession(std::move(request));
}
void VRServiceImpl::DoRequestSession(SessionRequestData request) {
DVLOG(2) << __func__;
// Get the runtime again, since we're running in an async context
// and the pointer returned from `GetRuntimeForOptions` is non-owning.
auto* runtime = runtime_manager_->GetRuntimeForOptions(request.options.get());
// Ensure that it's the same runtime as the one we expect.
if (!runtime || runtime->GetId() != request.runtime_id) {
TRACE_EVENT("xr", "VRServiceImpl::DoRequestSession: mismatching runtime",
perfetto::Flow::Global(request.options->trace_id));
RejectSession(std::move(request.callback), request.options->trace_id,
device::mojom::RequestSessionError::UNKNOWN_RUNTIME_ERROR,
"Mismatching runtime or invalid runtime.");
return;
}
TRACE_EVENT_INSTANT1("xr", "GetRuntimeForOptions", TRACE_EVENT_SCOPE_THREAD,
"id", request.runtime_id);
auto runtime_options = GetRuntimeOptions(request.options.get());
// Make the resolved enabled features available to the runtime.
runtime_options->required_features.assign(request.required_features.begin(),
request.required_features.end());
runtime_options->optional_features.assign(request.optional_features.begin(),
request.optional_features.end());
if constexpr (BUILDFLAG(IS_ANDROID)) {
bool send_renderer_information = false;
#if BUILDFLAG(ENABLE_ARCORE)
send_renderer_information =
send_renderer_information ||
request.runtime_id == device::mojom::XRDeviceId::ARCORE_DEVICE_ID;
#endif
#if BUILDFLAG(ENABLE_CARDBOARD)
send_renderer_information =
send_renderer_information ||
request.runtime_id == device::mojom::XRDeviceId::CARDBOARD_DEVICE_ID;
#endif
#if BUILDFLAG(ENABLE_OPENXR) && BUILDFLAG(IS_ANDROID)
send_renderer_information =
send_renderer_information ||
request.runtime_id == device::mojom::XRDeviceId::OPENXR_DEVICE_ID;
#endif
if (send_renderer_information) {
runtime_options->render_process_id =
render_frame_host_->GetProcess()->GetDeprecatedID();
runtime_options->render_frame_id = render_frame_host_->GetRoutingID();
}
}
bool use_dom_overlay =
base::Contains(runtime_options->required_features,
device::mojom::XRSessionFeature::DOM_OVERLAY) ||
base::Contains(runtime_options->optional_features,
device::mojom::XRSessionFeature::DOM_OVERLAY);
if (use_dom_overlay) {
// Tell RenderFrameHostImpl that we're setting up the WebXR DOM Overlay,
// it checks for this in EnterFullscreen via HasSeenRecentXrOverlaySetup().
render_frame_host_->SetIsXrOverlaySetup();
}
if (device::XRSessionModeUtils::IsImmersive(runtime_options->mode)) {
if (!request.options->tracked_images.empty()) {
DVLOG(3) << __func__ << ": request.options->tracked_images.size()="
<< request.options->tracked_images.size();
runtime_options->tracked_images.resize(
request.options->tracked_images.size());
for (std::size_t i = 0; i < request.options->tracked_images.size(); ++i) {
runtime_options->tracked_images[i] =
request.options->tracked_images[i].Clone();
}
}
runtime_options->depth_options = std::move(request.options->depth_options);
auto immersive_callback =
base::BindOnce(&VRServiceImpl::OnImmersiveSessionCreated,
weak_ptr_factory_.GetWeakPtr(), std::move(request));
runtime->RequestImmersiveSession(this, std::move(runtime_options),
std::move(immersive_callback));
} else {
auto non_immersive_callback =
base::BindOnce(&VRServiceImpl::OnInlineSessionCreated,
weak_ptr_factory_.GetWeakPtr(), std::move(request));
runtime->RequestInlineSession(std::move(runtime_options),
std::move(non_immersive_callback));
}
}
void VRServiceImpl::SupportsSession(
device::mojom::XRSessionOptionsPtr options,
device::mojom::VRService::SupportsSessionCallback callback) {
if (!initialization_complete_) {
pending_requests_.push_back(
base::BindOnce(&VRServiceImpl::SupportsSession, base::Unretained(this),
std::move(options), std::move(callback)));
return;
}
TRACE_EVENT("xr", "VRServiceImpl::SupportsSession: received",
perfetto::Flow::Global(options->trace_id));
runtime_manager_->SupportsSession(std::move(options), std::move(callback));
}
void VRServiceImpl::ExitPresent(ExitPresentCallback on_exited) {
BrowserXRRuntimeImpl* immersive_runtime =
runtime_manager_->GetCurrentlyPresentingImmersiveRuntime();
DVLOG(2) << __func__ << ": !!immersive_runtime=" << !!immersive_runtime;
if (immersive_runtime) {
on_exit_present_ = std::move(on_exited);
immersive_runtime->ExitPresent(this);
} else {
std::move(on_exited).Run();
}
}
void VRServiceImpl::SetFramesThrottled(bool throttled) {
if (throttled != frames_throttled_) {
frames_throttled_ = throttled;
BrowserXRRuntimeImpl* immersive_runtime =
runtime_manager_->GetCurrentlyPresentingImmersiveRuntime();
if (immersive_runtime) {
immersive_runtime->SetFramesThrottled(this, frames_throttled_);
}
}
}
void VRServiceImpl::MakeXrCompatible(
device::mojom::VRService::MakeXrCompatibleCallback callback) {
if (!initialization_complete_) {
pending_requests_.push_back(base::BindOnce(&VRServiceImpl::MakeXrCompatible,
base::Unretained(this),
std::move(callback)));
return;
}
xr_compatible_callbacks_.emplace_back(std::move(callback));
// Only request compatibility if there aren't any pending calls.
// OnMakeXrCompatibleComplete will run all callbacks.
if (xr_compatible_callbacks_.size() == 1)
runtime_manager_->MakeXrCompatible();
}
void VRServiceImpl::OnMakeXrCompatibleComplete(
device::mojom::XrCompatibleResult result) {
for (XrCompatibleCallback& wrapper : xr_compatible_callbacks_)
std::move(wrapper.callback).Run(result);
xr_compatible_callbacks_.clear();
}
void VRServiceImpl::OnExitPresent() {
DVLOG(2) << __func__;
// Clear any XrRenderTarget that may have been set.
viz::FrameSinkId default_frame_sink_id;
static_cast<WebContentsImpl*>(GetWebContents())
->OnXrHasRenderTarget(default_frame_sink_id);
render_frame_host_->GetProcess()->OnImmersiveXrSessionStopped();
GetSessionMetricsHelper()->StopAndRecordImmersiveSession();
if (on_exit_present_) {
std::move(on_exit_present_).Run();
}
for (auto& client : session_clients_) {
// https://crbug.com/1160940 has a fairly generic callstack, in mojom
// generated code, which appears to aggregate a few different actual crashes
// into the same bug. For the crashes that appear to be our fault, the
// common "start" is this call. By causing a CHECK here instead of in the
// mojom generated code, we can isolate our crashes.
CHECK(client);
client->OnExitPresent();
}
// Ensure that the client list is erased to avoid "Cannot issue Interface
// method calls on an unbound Remote" errors: https://crbug.com/991747
session_clients_.Clear();
}
void VRServiceImpl::OnVisibilityStateChanged(
device::mojom::XRVisibilityState visiblity_state) {
visibility_state_ = visiblity_state;
for (auto& client : session_clients_)
client->OnVisibilityStateChanged(visiblity_state);
}
content::WebContents* VRServiceImpl::GetWebContents() {
return content::WebContents::FromRenderFrameHost(render_frame_host_);
}
} // namespace content