blob: c8209bde039c7212a00b5dad5929af31ad122ca4 [file] [log] [blame]
// Copyright 2013 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 "content/browser/renderer_host/media/media_stream_ui_proxy.h"
#include <utility>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/macros.h"
#include "base/task/post_task.h"
#include "content/browser/frame_host/frame_tree_node.h"
#include "content/browser/frame_host/render_frame_host_delegate.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/media_stream_request.h"
#include "content/public/common/content_switches.h"
#include "media/capture/video/fake_video_capture_device.h"
#include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
bool IsFeatureEnabled(RenderFrameHost* rfh,
bool tests_use_fake_render_frame_hosts,
blink::mojom::FeaturePolicyFeature feature) {
// Some tests don't (or can't) set up the RenderFrameHost. In these cases we
// just ignore feature policy checks (there is no feature policy to test).
if (!rfh && tests_use_fake_render_frame_hosts)
return true;
if (!rfh)
return false;
return rfh->IsFeatureEnabled(feature);
}
void SetAndCheckAncestorFlag(MediaStreamRequest* request) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RenderFrameHostImpl* rfh =
RenderFrameHostImpl::FromID(request->render_process_id,
request->render_frame_id);
if (rfh == nullptr) {
// RenderFrame destroyed before the request is handled?
return;
}
FrameTreeNode* node = rfh->frame_tree_node();
while (node->parent() != nullptr) {
if (!node->HasSameOrigin(*node->parent())) {
request->all_ancestors_have_same_origin = false;
return;
}
node = node->parent();
}
request->all_ancestors_have_same_origin = true;
}
class MediaStreamUIProxy::Core {
public:
explicit Core(const base::WeakPtr<MediaStreamUIProxy>& proxy,
RenderFrameHostDelegate* test_render_delegate);
~Core();
void RequestAccess(std::unique_ptr<MediaStreamRequest> request);
void OnStarted(gfx::NativeViewId* window_id, bool has_source_callback);
void ProcessAccessRequestResponse(int render_process_id,
int render_frame_id,
const blink::MediaStreamDevices& devices,
blink::MediaStreamRequestResult result,
std::unique_ptr<MediaStreamUI> stream_ui);
private:
friend class FakeMediaStreamUIProxy;
void ProcessStopRequestFromUI();
void ProcessChangeSourceRequestFromUI();
RenderFrameHostDelegate* GetRenderFrameHostDelegate(int render_process_id,
int render_frame_id);
base::WeakPtr<MediaStreamUIProxy> proxy_;
std::unique_ptr<MediaStreamUI> ui_;
bool tests_use_fake_render_frame_hosts_;
RenderFrameHostDelegate* const test_render_delegate_;
// WeakPtr<> is used to RequestMediaAccessPermission() because there is no way
// cancel media requests.
base::WeakPtrFactory<Core> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(Core);
};
MediaStreamUIProxy::Core::Core(const base::WeakPtr<MediaStreamUIProxy>& proxy,
RenderFrameHostDelegate* test_render_delegate)
: proxy_(proxy),
tests_use_fake_render_frame_hosts_(test_render_delegate != nullptr),
test_render_delegate_(test_render_delegate),
weak_factory_(this) {}
MediaStreamUIProxy::Core::~Core() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
void MediaStreamUIProxy::Core::RequestAccess(
std::unique_ptr<MediaStreamRequest> request) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RenderFrameHostDelegate* render_delegate = GetRenderFrameHostDelegate(
request->render_process_id, request->render_frame_id);
// Tab may have gone away, or has no delegate from which to request access.
if (!render_delegate) {
ProcessAccessRequestResponse(
request->render_process_id, request->render_frame_id,
blink::MediaStreamDevices(), blink::MEDIA_DEVICE_FAILED_DUE_TO_SHUTDOWN,
std::unique_ptr<MediaStreamUI>());
return;
}
SetAndCheckAncestorFlag(request.get());
render_delegate->RequestMediaAccessPermission(
*request,
base::BindOnce(&Core::ProcessAccessRequestResponse,
weak_factory_.GetWeakPtr(), request->render_process_id,
request->render_frame_id));
}
void MediaStreamUIProxy::Core::OnStarted(gfx::NativeViewId* window_id,
bool has_source_callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
base::RepeatingClosure device_change_cb;
if (has_source_callback) {
device_change_cb = base::BindRepeating(
&Core::ProcessChangeSourceRequestFromUI, base::Unretained(this));
}
if (ui_) {
*window_id = ui_->OnStarted(
base::BindOnce(&Core::ProcessStopRequestFromUI, base::Unretained(this)),
device_change_cb);
}
}
void MediaStreamUIProxy::Core::ProcessAccessRequestResponse(
int render_process_id,
int render_frame_id,
const blink::MediaStreamDevices& devices,
blink::MediaStreamRequestResult result,
std::unique_ptr<MediaStreamUI> stream_ui) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
blink::MediaStreamDevices filtered_devices;
RenderFrameHost* host =
RenderFrameHost::FromID(render_process_id, render_frame_id);
for (const blink::MediaStreamDevice& device : devices) {
if (device.type == blink::MEDIA_DEVICE_AUDIO_CAPTURE &&
!IsFeatureEnabled(host, tests_use_fake_render_frame_hosts_,
blink::mojom::FeaturePolicyFeature::kMicrophone)) {
continue;
}
if (device.type == blink::MEDIA_DEVICE_VIDEO_CAPTURE &&
!IsFeatureEnabled(host, tests_use_fake_render_frame_hosts_,
blink::mojom::FeaturePolicyFeature::kCamera)) {
continue;
}
filtered_devices.push_back(device);
}
if (filtered_devices.empty() && result == blink::MEDIA_DEVICE_OK)
result = blink::MEDIA_DEVICE_PERMISSION_DENIED;
if (stream_ui)
ui_ = std::move(stream_ui);
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&MediaStreamUIProxy::ProcessAccessRequestResponse, proxy_,
filtered_devices, result));
}
void MediaStreamUIProxy::Core::ProcessStopRequestFromUI() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&MediaStreamUIProxy::ProcessStopRequestFromUI, proxy_));
}
void MediaStreamUIProxy::Core::ProcessChangeSourceRequestFromUI() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&MediaStreamUIProxy::ProcessChangeSourceRequestFromUI,
proxy_));
}
RenderFrameHostDelegate* MediaStreamUIProxy::Core::GetRenderFrameHostDelegate(
int render_process_id,
int render_frame_id) {
if (test_render_delegate_)
return test_render_delegate_;
RenderFrameHostImpl* host =
RenderFrameHostImpl::FromID(render_process_id, render_frame_id);
return host ? host->delegate() : nullptr;
}
// static
std::unique_ptr<MediaStreamUIProxy> MediaStreamUIProxy::Create() {
return std::unique_ptr<MediaStreamUIProxy>(new MediaStreamUIProxy(nullptr));
}
// static
std::unique_ptr<MediaStreamUIProxy> MediaStreamUIProxy::CreateForTests(
RenderFrameHostDelegate* render_delegate) {
return std::unique_ptr<MediaStreamUIProxy>(
new MediaStreamUIProxy(render_delegate));
}
MediaStreamUIProxy::MediaStreamUIProxy(
RenderFrameHostDelegate* test_render_delegate)
: weak_factory_(this) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
core_.reset(new Core(weak_factory_.GetWeakPtr(), test_render_delegate));
}
MediaStreamUIProxy::~MediaStreamUIProxy() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
}
void MediaStreamUIProxy::RequestAccess(
std::unique_ptr<MediaStreamRequest> request,
ResponseCallback response_callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
response_callback_ = std::move(response_callback);
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(&Core::RequestAccess, base::Unretained(core_.get()),
std::move(request)));
}
void MediaStreamUIProxy::OnStarted(base::OnceClosure stop_callback,
base::RepeatingClosure source_callback,
WindowIdCallback window_id_callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
stop_callback_ = std::move(stop_callback);
source_callback_ = std::move(source_callback);
// Owned by the PostTaskAndReply callback.
gfx::NativeViewId* window_id = new gfx::NativeViewId(0);
base::PostTaskWithTraitsAndReply(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(&Core::OnStarted, base::Unretained(core_.get()), window_id,
!!source_callback_),
base::BindOnce(&MediaStreamUIProxy::OnWindowId,
weak_factory_.GetWeakPtr(), std::move(window_id_callback),
base::Owned(window_id)));
}
void MediaStreamUIProxy::ProcessAccessRequestResponse(
const blink::MediaStreamDevices& devices,
blink::MediaStreamRequestResult result) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(!response_callback_.is_null());
std::move(response_callback_).Run(devices, result);
}
void MediaStreamUIProxy::ProcessStopRequestFromUI() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(!stop_callback_.is_null());
std::move(stop_callback_).Run();
}
void MediaStreamUIProxy::ProcessChangeSourceRequestFromUI() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (source_callback_)
source_callback_.Run();
}
void MediaStreamUIProxy::OnWindowId(WindowIdCallback window_id_callback,
gfx::NativeViewId* window_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!window_id_callback.is_null())
std::move(window_id_callback).Run(*window_id);
}
FakeMediaStreamUIProxy::FakeMediaStreamUIProxy(
bool tests_use_fake_render_frame_hosts)
: MediaStreamUIProxy(nullptr), mic_access_(true), camera_access_(true) {
core_->tests_use_fake_render_frame_hosts_ = tests_use_fake_render_frame_hosts;
}
FakeMediaStreamUIProxy::~FakeMediaStreamUIProxy() {}
void FakeMediaStreamUIProxy::SetAvailableDevices(
const blink::MediaStreamDevices& devices) {
devices_ = devices;
}
void FakeMediaStreamUIProxy::SetMicAccess(bool access) {
mic_access_ = access;
}
void FakeMediaStreamUIProxy::SetCameraAccess(bool access) {
camera_access_ = access;
}
void FakeMediaStreamUIProxy::RequestAccess(
std::unique_ptr<MediaStreamRequest> request,
ResponseCallback response_callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
response_callback_ = std::move(response_callback);
if (base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kUseFakeUIForMediaStream) == "deny") {
// Immediately deny the request.
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(&MediaStreamUIProxy::Core::ProcessAccessRequestResponse,
base::Unretained(core_.get()),
request->render_process_id, request->render_frame_id,
blink::MediaStreamDevices(),
blink::MEDIA_DEVICE_PERMISSION_DENIED,
std::unique_ptr<MediaStreamUI>()));
return;
}
blink::MediaStreamDevices devices_to_use;
bool accepted_audio = false;
bool accepted_video = false;
// Use the first capture device of the same media type in the list for the
// fake UI.
for (blink::MediaStreamDevices::const_iterator it = devices_.begin();
it != devices_.end(); ++it) {
if (!accepted_audio &&
IsAudioInputMediaType(request->audio_type) &&
IsAudioInputMediaType(it->type) &&
(request->requested_audio_device_id.empty() ||
request->requested_audio_device_id == it->id)) {
devices_to_use.push_back(*it);
accepted_audio = true;
} else if (!accepted_video && IsVideoInputMediaType(request->video_type) &&
IsVideoInputMediaType(it->type) &&
(request->requested_video_device_id.empty() ||
request->requested_video_device_id == it->id)) {
devices_to_use.push_back(*it);
accepted_video = true;
}
}
// Fail the request if a device doesn't exist for the requested type.
if ((request->audio_type != blink::MEDIA_NO_SERVICE && !accepted_audio) ||
(request->video_type != blink::MEDIA_NO_SERVICE && !accepted_video)) {
devices_to_use.clear();
}
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(&MediaStreamUIProxy::Core::ProcessAccessRequestResponse,
base::Unretained(core_.get()), request->render_process_id,
request->render_frame_id, devices_to_use,
devices_to_use.empty() ? blink::MEDIA_DEVICE_NO_HARDWARE
: blink::MEDIA_DEVICE_OK,
std::unique_ptr<MediaStreamUI>()));
}
void FakeMediaStreamUIProxy::OnStarted(base::OnceClosure stop_callback,
base::RepeatingClosure source_callback,
WindowIdCallback window_id_callback) {}
} // namespace content