| // Copyright 2014 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/permissions/Permissions.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "bindings/core/v8/Dictionary.h" |
| #include "bindings/core/v8/Nullable.h" |
| #include "bindings/core/v8/ScriptPromise.h" |
| #include "bindings/core/v8/ScriptPromiseResolver.h" |
| #include "bindings/modules/v8/V8ClipboardPermissionDescriptor.h" |
| #include "bindings/modules/v8/V8MidiPermissionDescriptor.h" |
| #include "bindings/modules/v8/V8PermissionDescriptor.h" |
| #include "bindings/modules/v8/V8PushPermissionDescriptor.h" |
| #include "core/dom/DOMException.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/ExceptionCode.h" |
| #include "core/dom/ExecutionContext.h" |
| #include "core/frame/Frame.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/origin_trials/origin_trials.h" |
| #include "modules/permissions/PermissionDescriptor.h" |
| #include "modules/permissions/PermissionStatus.h" |
| #include "modules/permissions/PermissionUtils.h" |
| #include "platform/runtime_enabled_features.h" |
| #include "platform/wtf/Functional.h" |
| #include "platform/wtf/NotFound.h" |
| #include "platform/wtf/PtrUtil.h" |
| #include "platform/wtf/Vector.h" |
| #include "public/platform/Platform.h" |
| |
| namespace blink { |
| |
| using mojom::blink::PermissionDescriptorPtr; |
| using mojom::blink::PermissionName; |
| using mojom::blink::PermissionService; |
| |
| namespace { |
| |
| // Parses the raw permission dictionary and returns the Mojo |
| // PermissionDescriptor if parsing was successful. If an exception occurs, it |
| // will be stored in |exceptionState| and null will be returned. Therefore, the |
| // |exceptionState| should be checked before attempting to use the returned |
| // permission as the non-null assert will be fired otherwise. |
| // |
| // Websites will be able to run code when `name()` is called, changing the |
| // current context. The caller should make sure that no assumption is made |
| // after this has been called. |
| PermissionDescriptorPtr ParsePermission(ScriptState* script_state, |
| const Dictionary raw_permission, |
| ExceptionState& exception_state) { |
| PermissionDescriptor permission = |
| NativeValueTraits<PermissionDescriptor>::NativeValue( |
| script_state->GetIsolate(), raw_permission.V8Value(), |
| exception_state); |
| |
| if (exception_state.HadException()) { |
| exception_state.ThrowTypeError(exception_state.Message()); |
| return nullptr; |
| } |
| |
| const String& name = permission.name(); |
| if (name == "geolocation") |
| return CreatePermissionDescriptor(PermissionName::GEOLOCATION); |
| if (name == "camera") |
| return CreatePermissionDescriptor(PermissionName::VIDEO_CAPTURE); |
| if (name == "microphone") |
| return CreatePermissionDescriptor(PermissionName::AUDIO_CAPTURE); |
| if (name == "notifications") |
| return CreatePermissionDescriptor(PermissionName::NOTIFICATIONS); |
| if (name == "push") { |
| PushPermissionDescriptor push_permission = |
| NativeValueTraits<PushPermissionDescriptor>::NativeValue( |
| script_state->GetIsolate(), raw_permission.V8Value(), |
| exception_state); |
| if (exception_state.HadException()) { |
| exception_state.ThrowTypeError(exception_state.Message()); |
| return nullptr; |
| } |
| |
| // Only "userVisibleOnly" push is supported for now. |
| if (!push_permission.userVisibleOnly()) { |
| exception_state.ThrowDOMException( |
| kNotSupportedError, |
| "Push Permission without userVisibleOnly:true isn't supported yet."); |
| return nullptr; |
| } |
| |
| return CreatePermissionDescriptor(PermissionName::NOTIFICATIONS); |
| } |
| if (name == "midi") { |
| MidiPermissionDescriptor midi_permission = |
| NativeValueTraits<MidiPermissionDescriptor>::NativeValue( |
| script_state->GetIsolate(), raw_permission.V8Value(), |
| exception_state); |
| return CreateMidiPermissionDescriptor(midi_permission.sysex()); |
| } |
| if (name == "background-sync") |
| return CreatePermissionDescriptor(PermissionName::BACKGROUND_SYNC); |
| // TODO(riju): Remove runtime flag check when Generic Sensor feature is |
| // stable. |
| if (name == "ambient-light-sensor" || name == "accelerometer" || |
| name == "gyroscope" || name == "magnetometer") { |
| if (!OriginTrials::sensorEnabled(ExecutionContext::From(script_state))) { |
| exception_state.ThrowTypeError("GenericSensor flag is not enabled."); |
| return nullptr; |
| } |
| |
| // Magnetometer and ALS require an extra flag. |
| if (name == "ambient-light-sensor") { |
| if (!RuntimeEnabledFeatures::SensorExtraClassesEnabled()) { |
| exception_state.ThrowTypeError( |
| "GenericSensorExtraClasses flag is not enabled."); |
| return nullptr; |
| } |
| } |
| |
| return CreatePermissionDescriptor(PermissionName::SENSORS); |
| } |
| if (name == "accessibility-events") { |
| if (!RuntimeEnabledFeatures::AccessibilityObjectModelEnabled()) { |
| exception_state.ThrowTypeError( |
| "Accessibility Object Model is not enabled."); |
| return nullptr; |
| } |
| return CreatePermissionDescriptor(PermissionName::ACCESSIBILITY_EVENTS); |
| } |
| if (name == "clipboard-read" || name == "clipboard-write") { |
| if (!RuntimeEnabledFeatures::AsyncClipboardEnabled()) { |
| exception_state.ThrowTypeError("Async Clipboard flag is not enabled."); |
| return nullptr; |
| } |
| |
| PermissionName permission_name = PermissionName::CLIPBOARD_READ; |
| if (name == "clipboard-write") |
| permission_name = PermissionName::CLIPBOARD_WRITE; |
| |
| ClipboardPermissionDescriptor clipboard_permission = |
| NativeValueTraits<ClipboardPermissionDescriptor>::NativeValue( |
| script_state->GetIsolate(), raw_permission.V8Value(), |
| exception_state); |
| return CreateClipboardPermissionDescriptor( |
| permission_name, clipboard_permission.allowWithoutGesture()); |
| } |
| |
| return nullptr; |
| } |
| |
| } // anonymous namespace |
| |
| ScriptPromise Permissions::query(ScriptState* script_state, |
| const Dictionary& raw_permission) { |
| ExceptionState exception_state(script_state->GetIsolate(), |
| ExceptionState::kGetterContext, "Permissions", |
| "query"); |
| PermissionDescriptorPtr descriptor = |
| ParsePermission(script_state, raw_permission, exception_state); |
| if (exception_state.HadException()) |
| return exception_state.Reject(script_state); |
| |
| ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state); |
| ScriptPromise promise = resolver->Promise(); |
| |
| // If the current origin is a file scheme, it will unlikely return a |
| // meaningful value because most APIs are broken on file scheme and no |
| // permission prompt will be shown even if the returned permission will most |
| // likely be "prompt". |
| PermissionDescriptorPtr descriptor_copy = descriptor->Clone(); |
| GetService(ExecutionContext::From(script_state)) |
| .HasPermission(std::move(descriptor), |
| ExecutionContext::From(script_state)->GetSecurityOrigin(), |
| WTF::Bind(&Permissions::TaskComplete, WrapPersistent(this), |
| WrapPersistent(resolver), |
| WTF::Passed(std::move(descriptor_copy)))); |
| return promise; |
| } |
| |
| ScriptPromise Permissions::request(ScriptState* script_state, |
| const Dictionary& raw_permission) { |
| ExceptionState exception_state(script_state->GetIsolate(), |
| ExceptionState::kGetterContext, "Permissions", |
| "request"); |
| PermissionDescriptorPtr descriptor = |
| ParsePermission(script_state, raw_permission, exception_state); |
| if (exception_state.HadException()) |
| return exception_state.Reject(script_state); |
| |
| ExecutionContext* context = ExecutionContext::From(script_state); |
| |
| ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state); |
| ScriptPromise promise = resolver->Promise(); |
| |
| PermissionDescriptorPtr descriptor_copy = descriptor->Clone(); |
| Document* doc = ToDocumentOrNull(context); |
| Frame* frame = doc ? doc->GetFrame() : nullptr; |
| GetService(ExecutionContext::From(script_state)) |
| .RequestPermission( |
| std::move(descriptor), context->GetSecurityOrigin(), |
| Frame::HasTransientUserActivation(frame, |
| true /* checkIfMainThread */), |
| WTF::Bind(&Permissions::TaskComplete, WrapPersistent(this), |
| WrapPersistent(resolver), |
| WTF::Passed(std::move(descriptor_copy)))); |
| return promise; |
| } |
| |
| ScriptPromise Permissions::revoke(ScriptState* script_state, |
| const Dictionary& raw_permission) { |
| ExceptionState exception_state(script_state->GetIsolate(), |
| ExceptionState::kGetterContext, "Permissions", |
| "revoke"); |
| PermissionDescriptorPtr descriptor = |
| ParsePermission(script_state, raw_permission, exception_state); |
| if (exception_state.HadException()) |
| return exception_state.Reject(script_state); |
| |
| ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state); |
| ScriptPromise promise = resolver->Promise(); |
| |
| PermissionDescriptorPtr descriptor_copy = descriptor->Clone(); |
| GetService(ExecutionContext::From(script_state)) |
| .RevokePermission( |
| std::move(descriptor), |
| ExecutionContext::From(script_state)->GetSecurityOrigin(), |
| WTF::Bind(&Permissions::TaskComplete, WrapPersistent(this), |
| WrapPersistent(resolver), |
| WTF::Passed(std::move(descriptor_copy)))); |
| return promise; |
| } |
| |
| ScriptPromise Permissions::requestAll( |
| ScriptState* script_state, |
| const Vector<Dictionary>& raw_permissions) { |
| ExceptionState exception_state(script_state->GetIsolate(), |
| ExceptionState::kGetterContext, "Permissions", |
| "requestAll"); |
| Vector<PermissionDescriptorPtr> internal_permissions; |
| Vector<int> caller_index_to_internal_index; |
| caller_index_to_internal_index.resize(raw_permissions.size()); |
| for (size_t i = 0; i < raw_permissions.size(); ++i) { |
| const Dictionary& raw_permission = raw_permissions[i]; |
| |
| auto descriptor = |
| ParsePermission(script_state, raw_permission, exception_state); |
| if (exception_state.HadException()) |
| return exception_state.Reject(script_state); |
| |
| // Only append permissions types that are not already present in the vector. |
| size_t internal_index = kNotFound; |
| for (size_t j = 0; j < internal_permissions.size(); ++j) { |
| if (internal_permissions[j]->name == descriptor->name) { |
| internal_index = j; |
| break; |
| } |
| } |
| if (internal_index == kNotFound) { |
| internal_index = internal_permissions.size(); |
| internal_permissions.push_back(std::move(descriptor)); |
| } |
| caller_index_to_internal_index[i] = internal_index; |
| } |
| |
| ExecutionContext* context = ExecutionContext::From(script_state); |
| |
| ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state); |
| ScriptPromise promise = resolver->Promise(); |
| |
| Vector<PermissionDescriptorPtr> internal_permissions_copy; |
| internal_permissions_copy.ReserveCapacity(internal_permissions.size()); |
| for (const auto& descriptor : internal_permissions) |
| internal_permissions_copy.push_back(descriptor->Clone()); |
| |
| Document* doc = ToDocumentOrNull(context); |
| Frame* frame = doc ? doc->GetFrame() : nullptr; |
| GetService(ExecutionContext::From(script_state)) |
| .RequestPermissions( |
| std::move(internal_permissions), context->GetSecurityOrigin(), |
| Frame::HasTransientUserActivation(frame, |
| true /* checkIfMainThread */), |
| WTF::Bind(&Permissions::BatchTaskComplete, WrapPersistent(this), |
| WrapPersistent(resolver), |
| WTF::Passed(std::move(internal_permissions_copy)), |
| WTF::Passed(std::move(caller_index_to_internal_index)))); |
| return promise; |
| } |
| |
| PermissionService& Permissions::GetService( |
| ExecutionContext* execution_context) { |
| if (!service_) { |
| ConnectToPermissionService(execution_context, mojo::MakeRequest(&service_)); |
| service_.set_connection_error_handler(WTF::Bind( |
| &Permissions::ServiceConnectionError, WrapWeakPersistent(this))); |
| } |
| return *service_; |
| } |
| |
| void Permissions::ServiceConnectionError() { |
| service_.reset(); |
| } |
| |
| void Permissions::TaskComplete(ScriptPromiseResolver* resolver, |
| mojom::blink::PermissionDescriptorPtr descriptor, |
| mojom::blink::PermissionStatus result) { |
| if (!resolver->GetExecutionContext() || |
| resolver->GetExecutionContext()->IsContextDestroyed()) |
| return; |
| resolver->Resolve( |
| PermissionStatus::Take(resolver, result, std::move(descriptor))); |
| } |
| |
| void Permissions::BatchTaskComplete( |
| ScriptPromiseResolver* resolver, |
| Vector<mojom::blink::PermissionDescriptorPtr> descriptors, |
| Vector<int> caller_index_to_internal_index, |
| const Vector<mojom::blink::PermissionStatus>& results) { |
| if (!resolver->GetExecutionContext() || |
| resolver->GetExecutionContext()->IsContextDestroyed()) |
| return; |
| |
| // Create the response vector by finding the status for each index by |
| // using the caller to internal index mapping and looking up the status |
| // using the internal index obtained. |
| HeapVector<Member<PermissionStatus>> result; |
| result.ReserveInitialCapacity(caller_index_to_internal_index.size()); |
| for (int internal_index : caller_index_to_internal_index) { |
| result.push_back(PermissionStatus::CreateAndListen( |
| resolver->GetExecutionContext(), results[internal_index], |
| descriptors[internal_index]->Clone())); |
| } |
| resolver->Resolve(result); |
| } |
| |
| } // namespace blink |