blob: a54b89a96ccf9a3c4b4230cdfc1e3d6de8ee2e7d [file] [log] [blame]
// Copyright 2015 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/extensions/api/desktop_capture/desktop_capture_base.h"
#include <memory>
#include <tuple>
#include <utility>
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/containers/cxx20_erase.h"
#include "base/functional/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/media/webrtc/capture_policy_utils.h"
#include "chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h"
#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
#include "chrome/browser/media/webrtc/native_desktop_media_list.h"
#include "chrome/browser/media/webrtc/tab_desktop_media_list.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/grit/branded_strings.h"
#include "content/public/browser/desktop_capture.h"
#include "content/public/browser/desktop_streams_registry.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "extensions/common/manifest.h"
#include "extensions/common/switches.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/origin.h"
using content::DesktopMediaID;
using extensions::api::desktop_capture::ChooseDesktopMedia::Results::Options;
namespace extensions {
namespace {
const char kTargetNotActiveError[] = "The specified target is not active.";
const char kInvalidSourceNameError[] = "Invalid source type specified.";
DesktopMediaPickerFactory* g_picker_factory = nullptr;
} // namespace
// static
void DesktopCaptureChooseDesktopMediaFunctionBase::SetPickerFactoryForTests(
DesktopMediaPickerFactory* factory) {
g_picker_factory = factory;
}
const char
DesktopCaptureChooseDesktopMediaFunctionBase::kTargetNotFoundError[] =
"The specified target is not found.";
DesktopCaptureChooseDesktopMediaFunctionBase::
DesktopCaptureChooseDesktopMediaFunctionBase() = default;
DesktopCaptureChooseDesktopMediaFunctionBase::
~DesktopCaptureChooseDesktopMediaFunctionBase() {
DesktopCaptureRequestsRegistry::GetInstance()->RemoveRequest(
source_process_id(), request_id_);
}
void DesktopCaptureChooseDesktopMediaFunctionBase::Cancel() {
// Keep reference to |this| to ensure the object doesn't get destroyed before
// we return.
scoped_refptr<DesktopCaptureChooseDesktopMediaFunctionBase> self(this);
// If this picker dialog is open, this will close it.
picker_controller_.reset();
Respond(ArgumentList(Create(std::string(), Options())));
}
ExtensionFunction::ResponseAction
DesktopCaptureChooseDesktopMediaFunctionBase::Execute(
const std::vector<api::desktop_capture::DesktopCaptureSourceType>& sources,
bool exclude_system_audio,
bool exclude_self_browser_surface,
bool suppress_local_audio_playback_intended,
content::RenderFrameHost* render_frame_host,
const GURL& origin,
const std::u16string target_name) {
DCHECK(!picker_controller_);
if (!render_frame_host->IsActive())
return RespondNow(Error(kTargetNotActiveError));
content::WebContents* web_contents =
content::WebContents::FromRenderFrameHost(render_frame_host);
if (!web_contents)
return RespondNow(Error(kTargetNotFoundError));
gfx::NativeWindow parent_window = web_contents->GetTopLevelNativeWindow();
// In case of coming from background extension page, |parent_window| will
// be null. We are going to make the picker modal to the current browser
// window.
if (!parent_window) {
Browser* target_browser = chrome::FindLastActiveWithProfile(
Profile::FromBrowserContext(web_contents->GetBrowserContext()));
if (target_browser)
parent_window = target_browser->window()->GetNativeWindow();
}
bool request_audio = false;
std::vector<DesktopMediaList::Type> media_types;
for (auto source_type : sources) {
switch (source_type) {
case api::desktop_capture::DesktopCaptureSourceType::kNone: {
return RespondNow(Error(kInvalidSourceNameError));
}
case api::desktop_capture::DesktopCaptureSourceType::kScreen: {
media_types.push_back(DesktopMediaList::Type::kScreen);
break;
}
case api::desktop_capture::DesktopCaptureSourceType::kWindow: {
media_types.push_back(DesktopMediaList::Type::kWindow);
break;
}
case api::desktop_capture::DesktopCaptureSourceType::kTab: {
media_types.push_back(DesktopMediaList::Type::kWebContents);
break;
}
case api::desktop_capture::DesktopCaptureSourceType::kAudio: {
request_audio = true;
break;
}
}
}
AllowedScreenCaptureLevel capture_level =
capture_policy::GetAllowedCaptureLevel(origin, web_contents);
capture_policy::FilterMediaList(media_types, capture_level);
DesktopMediaList::WebContentsFilter includable_web_contents_filter =
capture_policy::GetIncludableWebContentsFilter(origin, capture_level);
if (exclude_self_browser_surface) {
includable_web_contents_filter = DesktopMediaList::ExcludeWebContents(
std::move(includable_web_contents_filter), web_contents);
}
DesktopMediaPickerController::DoneCallback callback = base::BindOnce(
&DesktopCaptureChooseDesktopMediaFunctionBase::OnPickerDialogResults,
this, origin, render_frame_host->GetGlobalId());
DesktopMediaPickerController::Params picker_params(
DesktopMediaPickerController::Params::RequestSource::kExtension);
picker_params.web_contents = web_contents;
picker_params.context = parent_window;
picker_params.parent = parent_window;
picker_params.app_name = base::UTF8ToUTF16(GetCallerDisplayName());
picker_params.target_name = target_name;
picker_params.request_audio = request_audio;
picker_params.exclude_system_audio = exclude_system_audio;
picker_params.suppress_local_audio_playback =
suppress_local_audio_playback_intended;
picker_controller_ =
std::make_unique<DesktopMediaPickerController>(g_picker_factory);
picker_params.restricted_by_policy =
(capture_level != AllowedScreenCaptureLevel::kUnrestricted);
picker_controller_->Show(picker_params, std::move(media_types),
includable_web_contents_filter, std::move(callback));
return RespondLater();
}
std::string DesktopCaptureChooseDesktopMediaFunctionBase::GetCallerDisplayName()
const {
if (extension()->location() == mojom::ManifestLocation::kComponent ||
extension()->location() == mojom::ManifestLocation::kExternalComponent) {
return l10n_util::GetStringUTF8(IDS_SHORT_PRODUCT_NAME);
} else {
return extension()->name();
}
}
void DesktopCaptureChooseDesktopMediaFunctionBase::OnPickerDialogResults(
const GURL& origin,
const content::GlobalRenderFrameHostId& render_frame_host_id,
const std::string& err,
DesktopMediaID source) {
picker_controller_.reset();
if (!err.empty()) {
Respond(Error(err));
return;
}
if (source.is_null()) {
DLOG(ERROR) << "Sending empty results.";
Respond(ArgumentList(Create(std::string(), Options())));
return;
}
std::string result;
if (source.type != DesktopMediaID::TYPE_NONE) {
result = content::DesktopStreamsRegistry::GetInstance()->RegisterStream(
render_frame_host_id.child_id, render_frame_host_id.frame_routing_id,
url::Origin::Create(origin), source,
content::kRegistryStreamTypeDesktop);
}
Options options;
options.can_request_audio_track = source.audio_share;
Respond(ArgumentList(Create(result, options)));
}
DesktopCaptureRequestsRegistry::RequestId::RequestId(int process_id,
int request_id)
: process_id(process_id), request_id(request_id) {}
bool DesktopCaptureRequestsRegistry::RequestId::operator<(
const RequestId& other) const {
return std::tie(process_id, request_id) <
std::tie(other.process_id, other.request_id);
}
DesktopCaptureCancelChooseDesktopMediaFunctionBase::
DesktopCaptureCancelChooseDesktopMediaFunctionBase() {}
DesktopCaptureCancelChooseDesktopMediaFunctionBase::
~DesktopCaptureCancelChooseDesktopMediaFunctionBase() {}
ExtensionFunction::ResponseAction
DesktopCaptureCancelChooseDesktopMediaFunctionBase::Run() {
EXTENSION_FUNCTION_VALIDATE(args().size() >= 1);
EXTENSION_FUNCTION_VALIDATE(args()[0].is_int());
int request_id = args()[0].GetInt();
DesktopCaptureRequestsRegistry::GetInstance()->CancelRequest(
source_process_id(), request_id);
return RespondNow(NoArguments());
}
DesktopCaptureRequestsRegistry::DesktopCaptureRequestsRegistry() {}
DesktopCaptureRequestsRegistry::~DesktopCaptureRequestsRegistry() {}
// static
DesktopCaptureRequestsRegistry* DesktopCaptureRequestsRegistry::GetInstance() {
return base::Singleton<DesktopCaptureRequestsRegistry>::get();
}
void DesktopCaptureRequestsRegistry::AddRequest(
int process_id,
int request_id,
DesktopCaptureChooseDesktopMediaFunctionBase* handler) {
requests_.insert(
RequestsMap::value_type(RequestId(process_id, request_id), handler));
}
void DesktopCaptureRequestsRegistry::RemoveRequest(int process_id,
int request_id) {
requests_.erase(RequestId(process_id, request_id));
}
void DesktopCaptureRequestsRegistry::CancelRequest(int process_id,
int request_id) {
auto it = requests_.find(RequestId(process_id, request_id));
if (it != requests_.end())
it->second->Cancel();
}
} // namespace extensions