blob: d48dc2517dfb0d36b657b7f8bf4fdd5a5e47598f [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/controlled_frame/controlled_frame_media_access_handler.h"
#include "base/types/expected.h"
#include "chrome/browser/media/webrtc/media_stream_device_permissions.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "content/public/browser/media_stream_request.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/browser_frame_context_data.h"
#include "third_party/blink/public/common/permissions_policy/permissions_policy.h"
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
#include "third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom-shared.h"
namespace controlled_frame {
namespace {
blink::mojom::PermissionsPolicyFeature
GetPermissionPolicyFeatureForMediaStreamType(
blink::mojom::MediaStreamType type) {
switch (type) {
case blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE:
return blink::mojom::PermissionsPolicyFeature::kMicrophone;
case blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE:
return blink::mojom::PermissionsPolicyFeature::kCamera;
default:
return blink::mojom::PermissionsPolicyFeature::kNotFound;
}
}
} // namespace
ControlledFrameMediaAccessHandler::PendingMediaAccessRequestDetails::
PendingMediaAccessRequestDetails(const url::Origin& embedded_frame_origin,
blink::mojom::MediaStreamType type)
: embedded_frame_origin(embedded_frame_origin), type(type) {}
ControlledFrameMediaAccessHandler::ControlledFrameMediaAccessHandler() =
default;
ControlledFrameMediaAccessHandler::~ControlledFrameMediaAccessHandler() =
default;
bool ControlledFrameMediaAccessHandler::SupportsStreamType(
content::WebContents* web_contents,
const blink::mojom::MediaStreamType type,
const extensions::Extension* extension) {
if (!web_contents || extension) {
return false;
}
extensions::BrowserFrameContextData context_data(
web_contents->GetPrimaryMainFrame());
content::RenderFrameHost* embedder_rfh =
web_contents->GetOutermostWebContents()->GetPrimaryMainFrame();
extensions::BrowserFrameContextData embedder_context_data(embedder_rfh);
// This first call to this function will be from the embedded frame. Store
// a mapping of the IWA URL to the embedded page's URL and return false to
// allow for the permission request event to be fired to the Isolated Web App
// that embeds this frame.
bool is_controlled_frame = !context_data.IsIsolatedApplication() &&
embedder_context_data.IsIsolatedApplication();
if (is_controlled_frame) {
pending_requests_[embedder_rfh->GetLastCommittedOrigin()].emplace_back(
web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin(), type);
return false;
}
// The second call to this function will be from the Isolated Web App after
// handling and allowing the permission request event that was fired from the
// Controlled Frame.
bool is_isolated_web_app = context_data.IsIsolatedApplication() &&
embedder_context_data.IsIsolatedApplication();
return is_isolated_web_app &&
pending_requests_.contains(
web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin()) &&
(type == blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE ||
type == blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE);
}
bool ControlledFrameMediaAccessHandler::CheckMediaAccessPermission(
content::RenderFrameHost* render_frame_host,
const url::Origin& security_origin,
blink::mojom::MediaStreamType type,
const extensions::Extension* extension) {
CHECK(!extension);
return IsPermissionAllowed(render_frame_host, security_origin, type);
}
void ControlledFrameMediaAccessHandler::HandleRequest(
content::WebContents* web_contents,
const content::MediaStreamRequest& request,
content::MediaResponseCallback callback,
const extensions::Extension* extension) {
CHECK(!extension);
if (!pending_requests_.contains(
web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin())) {
return;
}
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
bool audio_allowed =
request.audio_type ==
blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE &&
IsPermissionAllowed(web_contents->GetPrimaryMainFrame(),
request.url_origin, request.audio_type) &&
GetDevicePolicy(profile, web_contents->GetLastCommittedURL(),
prefs::kAudioCaptureAllowed,
prefs::kAudioCaptureAllowedUrls) != ALWAYS_DENY;
bool video_allowed =
request.video_type ==
blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE &&
IsPermissionAllowed(web_contents->GetPrimaryMainFrame(),
request.url_origin, request.video_type) &&
GetDevicePolicy(profile, web_contents->GetLastCommittedURL(),
prefs::kVideoCaptureAllowed,
prefs::kVideoCaptureAllowedUrls) != ALWAYS_DENY;
CheckDevicesAndRunCallback(web_contents, request, std::move(callback),
audio_allowed, video_allowed);
}
bool ControlledFrameMediaAccessHandler::IsPermissionAllowed(
content::RenderFrameHost* embedder_rfh,
const url::Origin& request_origin,
blink::mojom::MediaStreamType type) {
extensions::BrowserFrameContextData embedder_context_data(embedder_rfh);
if (!embedder_context_data.IsIsolatedApplication()) {
return false;
}
const url::Origin& embedder_origin = embedder_rfh->GetLastCommittedOrigin();
if (!pending_requests_.contains(embedder_origin)) {
return false;
}
// Verify that there is a pending request for the given permission type and
// remove the request.
std::vector<PendingMediaAccessRequestDetails> details =
pending_requests_.at(embedder_origin);
auto it = std::find_if(details.cbegin(), details.cend(),
[&](const PendingMediaAccessRequestDetails& detail) {
return detail.type == type;
});
if (it == details.cend() || it->embedded_frame_origin != request_origin) {
return false;
}
details.erase(it);
const blink::PermissionsPolicy* permissions_policy =
embedder_rfh->GetPermissionsPolicy();
return permissions_policy->IsFeatureEnabledForOrigin(
GetPermissionPolicyFeatureForMediaStreamType(type), request_origin);
}
} // namespace controlled_frame