blob: fd3789dd1db66a471c36bbefb9070aa12704163f [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.
#include "modules/vr/latest/VR.h"
#include "bindings/core/v8/ScriptPromiseResolver.h"
#include "core/dom/DOMException.h"
#include "core/dom/Document.h"
#include "core/frame/LocalFrame.h"
#include "modules/EventTargetModules.h"
#include "modules/vr/latest/VRDevice.h"
#include "modules/vr/latest/VRDeviceEvent.h"
#include "platform/feature_policy/FeaturePolicy.h"
#include "public/platform/InterfaceProvider.h"
#include "services/service_manager/public/cpp/interface_provider.h"
namespace blink {
namespace {
const char kNavigatorDetachedError[] =
"The navigator.vr object is no longer associated with a document.";
const char kFeaturePolicyBlocked[] =
"Access to the feature \"vr\" is disallowed by feature policy.";
const char kCrossOriginSubframeBlocked[] =
"Blocked call to navigator.vr.getDevices inside a cross-origin "
"iframe because the frame has never been activated by the user.";
} // namespace
VR::VR(LocalFrame& frame)
: ContextLifecycleObserver(frame.GetDocument()),
devices_synced_(false),
binding_(this) {
frame.GetInterfaceProvider().GetInterface(mojo::MakeRequest(&service_));
service_.set_connection_error_handler(
ConvertToBaseCallback(WTF::Bind(&VR::Dispose, WrapWeakPersistent(this))));
device::mojom::blink::VRServiceClientPtr client;
binding_.Bind(mojo::MakeRequest(&client));
// Setting the client kicks off a request for the details of any connected
// VRDevices.
service_->SetClient(std::move(client),
ConvertToBaseCallback(WTF::Bind(&VR::OnDevicesSynced,
WrapPersistent(this))));
}
ExecutionContext* VR::GetExecutionContext() const {
return ContextLifecycleObserver::GetExecutionContext();
}
const AtomicString& VR::InterfaceName() const {
return EventTargetNames::VR;
}
ScriptPromise VR::getDevices(ScriptState* script_state) {
LocalFrame* frame = GetFrame();
if (!frame) {
// Reject if the frame is inaccessible.
return ScriptPromise::RejectWithDOMException(
script_state,
DOMException::Create(kInvalidStateError, kNavigatorDetachedError));
}
if (IsSupportedInFeaturePolicy(WebFeaturePolicyFeature::kWebVr)) {
if (!frame->IsFeatureEnabled(WebFeaturePolicyFeature::kWebVr)) {
// Only allow the call to be made if the appropraite feature policy is in
// place.
return ScriptPromise::RejectWithDOMException(
script_state,
DOMException::Create(kSecurityError, kFeaturePolicyBlocked));
}
} else if (!frame->HasReceivedUserGesture() &&
frame->IsCrossOriginSubframe()) {
// Block calls from cross-origin iframes that have never had a user gesture.
return ScriptPromise::RejectWithDOMException(
script_state,
DOMException::Create(kSecurityError, kCrossOriginSubframeBlocked));
}
// If we're still waiting for a previous call to resolve return that promise
// again.
if (pending_devices_resolver_) {
return pending_devices_resolver_->Promise();
}
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
// If we've previously synced the VRDevices or no longer have a valid service
// connection just return the current list. In the case of the service being
// disconnected this will be an empty array.
if (!service_ || devices_synced_) {
resolver->Resolve(devices_);
return promise;
}
// Otherwise wait for the full list of devices to be populated and resolve
// when onDevicesSynced is called.
pending_devices_resolver_ = resolver;
return promise;
}
// Each time a new VRDevice is connected we'll recieve a VRDisplayPtr for it
// here. Upon calling SetClient in the constructor we should receive one call
// for each VRDevice that was already connected at the time.
void VR::OnDisplayConnected(
device::mojom::blink::VRDisplayPtr display,
device::mojom::blink::VRDisplayClientRequest client_request,
device::mojom::blink::VRDisplayInfoPtr display_info) {
VRDevice* vr_device =
new VRDevice(this, std::move(display), std::move(client_request),
std::move(display_info));
devices_.push_back(vr_device);
DispatchEvent(
VRDeviceEvent::Create(EventTypeNames::deviceconnect, vr_device));
}
// Called when the VRService has called OnDevicesConnected for all active
// VRDevices.
void VR::OnDevicesSynced() {
devices_synced_ = true;
OnGetDevices();
}
// Called when details for every connected VRDevice has been received.
void VR::OnGetDevices() {
if (pending_devices_resolver_) {
pending_devices_resolver_->Resolve(devices_);
pending_devices_resolver_ = nullptr;
}
}
void VR::ContextDestroyed(ExecutionContext*) {
Dispose();
}
void VR::Dispose() {
// If the document context was destroyed, shut down the client connection
// and never call the mojo service again.
service_.reset();
binding_.Close();
// Shutdown all devices' message pipe
for (const auto& device : devices_)
device->Dispose();
devices_.clear();
// Ensure that any outstanding getDevices promises are resolved. They will
// receive an empty array of devices.
OnGetDevices();
}
DEFINE_TRACE(VR) {
visitor->Trace(devices_);
visitor->Trace(pending_devices_resolver_);
ContextLifecycleObserver::Trace(visitor);
EventTargetWithInlineData::Trace(visitor);
}
} // namespace blink