blob: 726c1bec163f7f0ce33acb412680b6d0be8642de [file] [log] [blame]
// Copyright 2020 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 "third_party/blink/renderer/modules/device/device_service.h"
#include "third_party/blink/public/common/browser_interface_broker_proxy.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_object_builder.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/navigator.h"
#include "third_party/blink/renderer/modules/event_target_modules.h"
namespace blink {
namespace {
const char kNotHighTrustedAppExceptionMessage[] =
"This API is available only for high trusted apps.";
} // namespace
const char DeviceService::kSupplementName[] = "DeviceService";
DeviceService* DeviceService::device(Navigator& navigator) {
if (!navigator.DomWindow())
return nullptr;
DeviceService* device_service =
Supplement<Navigator>::From<DeviceService>(navigator);
if (!device_service) {
device_service = MakeGarbageCollected<DeviceService>(navigator);
ProvideTo(navigator, device_service);
}
return device_service;
}
DeviceService::DeviceService(Navigator& navigator)
: Supplement<Navigator>(navigator),
device_api_service_(navigator.DomWindow()),
configuration_observer_(this, navigator.DomWindow()) {}
const AtomicString& DeviceService::InterfaceName() const {
return event_target_names::kDeviceService;
}
ExecutionContext* DeviceService::GetExecutionContext() const {
return GetSupplementable()->DomWindow();
}
bool DeviceService::HasPendingActivity() const {
// Prevents garbage collecting of this object when not hold by another
// object but still has listeners registered.
return !pending_promises_.IsEmpty() || HasEventListeners();
}
void DeviceService::Trace(Visitor* visitor) const {
EventTargetWithInlineData::Trace(visitor);
ActiveScriptWrappable::Trace(visitor);
Supplement<Navigator>::Trace(visitor);
visitor->Trace(device_api_service_);
visitor->Trace(pending_promises_);
visitor->Trace(configuration_observer_);
}
mojom::blink::DeviceAPIService* DeviceService::GetService() {
if (!device_api_service_.is_bound()) {
GetExecutionContext()->GetBrowserInterfaceBroker().GetInterface(
device_api_service_.BindNewPipeAndPassReceiver(
GetExecutionContext()->GetTaskRunner(TaskType::kMiscPlatformAPI)));
// The access status of Device API can change dynamically. Hence, we have to
// properly handle cases when we are losing this access.
device_api_service_.set_disconnect_handler(WTF::Bind(
&DeviceService::OnServiceConnectionError, WrapWeakPersistent(this)));
}
return device_api_service_.get();
}
void DeviceService::OnServiceConnectionError() {
device_api_service_.reset();
// Resolve all pending promises with a failure.
for (ScriptPromiseResolver* resolver : pending_promises_) {
resolver->Reject(
MakeGarbageCollected<DOMException>(DOMExceptionCode::kNotAllowedError,
kNotHighTrustedAppExceptionMessage));
}
}
ScriptPromise DeviceService::getManagedConfiguration(ScriptState* script_state,
Vector<String> keys) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
pending_promises_.insert(resolver);
ScriptPromise promise = resolver->Promise();
GetService()->GetManagedConfiguration(
keys, Bind(&DeviceService::OnConfigurationReceived,
WrapWeakPersistent(this), WrapPersistent(resolver)));
return promise;
}
ScriptPromise DeviceService::getDirectoryId(ScriptState* script_state) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
pending_promises_.insert(resolver);
ScriptPromise promise = resolver->Promise();
GetService()->GetDirectoryId(
WTF::Bind(&DeviceService::OnAttributeReceived, WrapWeakPersistent(this),
WrapPersistent(script_state), WrapPersistent(resolver)));
return promise;
}
ScriptPromise DeviceService::getSerialNumber(ScriptState* script_state) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
pending_promises_.insert(resolver);
ScriptPromise promise = resolver->Promise();
GetService()->GetSerialNumber(
WTF::Bind(&DeviceService::OnAttributeReceived, WrapWeakPersistent(this),
WrapPersistent(script_state), WrapPersistent(resolver)));
return promise;
}
ScriptPromise DeviceService::getAnnotatedAssetId(ScriptState* script_state) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
pending_promises_.insert(resolver);
ScriptPromise promise = resolver->Promise();
GetService()->GetAnnotatedAssetId(
WTF::Bind(&DeviceService::OnAttributeReceived, WrapWeakPersistent(this),
WrapPersistent(script_state), WrapPersistent(resolver)));
return promise;
}
ScriptPromise DeviceService::getAnnotatedLocation(ScriptState* script_state) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
pending_promises_.insert(resolver);
ScriptPromise promise = resolver->Promise();
GetService()->GetAnnotatedLocation(
WTF::Bind(&DeviceService::OnAttributeReceived, WrapWeakPersistent(this),
WrapPersistent(script_state), WrapPersistent(resolver)));
return promise;
}
void DeviceService::OnConfigurationReceived(
ScriptPromiseResolver* scoped_resolver,
const HashMap<String, String>& configurations) {
pending_promises_.erase(scoped_resolver);
ScriptState* script_state = scoped_resolver->GetScriptState();
ScriptState::Scope scope(script_state);
V8ObjectBuilder result(script_state);
for (const auto& config_pair : configurations) {
v8::Local<v8::Value> v8_object;
if (v8::JSON::Parse(script_state->GetContext(),
V8String(script_state->GetIsolate(), config_pair.value))
.ToLocal(&v8_object)) {
result.Add(config_pair.key, v8_object);
}
}
scoped_resolver->Resolve(result.GetScriptValue());
}
void DeviceService::OnAttributeReceived(
ScriptState* script_state,
ScriptPromiseResolver* scoped_resolver,
mojom::blink::DeviceAttributeResultPtr result) {
pending_promises_.erase(scoped_resolver);
if (result->is_error_message()) {
scoped_resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kUnknownError, result->get_error_message()));
} else if (result->get_attribute().IsNull()) {
scoped_resolver->Resolve(v8::Null(script_state->GetIsolate()));
} else {
scoped_resolver->Resolve(result->get_attribute());
}
}
void DeviceService::OnConfigurationChanged() {
DispatchEvent(*Event::Create(event_type_names::kManagedconfigurationchange));
}
void DeviceService::AddedEventListener(
const AtomicString& event_type,
RegisteredEventListener& registered_listener) {
EventTargetWithInlineData::AddedEventListener(event_type,
registered_listener);
if (event_type == event_type_names::kManagedconfigurationchange) {
if (!configuration_observer_.is_bound()) {
GetService()->SubscribeToManagedConfiguration(
configuration_observer_.BindNewPipeAndPassRemote(
GetExecutionContext()->GetTaskRunner(
TaskType::kMiscPlatformAPI)));
}
}
}
void DeviceService::RemovedEventListener(
const AtomicString& event_type,
const RegisteredEventListener& registered_listener) {
EventTargetWithInlineData::RemovedEventListener(event_type,
registered_listener);
if (!HasEventListeners())
StopObserving();
}
void DeviceService::StopObserving() {
if (!configuration_observer_.is_bound())
return;
configuration_observer_.reset();
}
} // namespace blink