| // Copyright 2019 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 "ash/webui/camera_app_ui/camera_app_ui.h" |
| |
| #include "ash/public/cpp/window_properties.h" |
| #include "ash/webui/camera_app_ui/camera_app_helper_impl.h" |
| #include "ash/webui/camera_app_ui/resources.h" |
| #include "ash/webui/camera_app_ui/url_constants.h" |
| #include "ash/webui/grit/ash_camera_app_resources_map.h" |
| #include "base/bind.h" |
| #include "base/feature_list.h" |
| #include "base/files/file_util.h" |
| #include "base/memory/ref_counted_memory.h" |
| #include "base/strings/string_util.h" |
| #include "components/arc/intent_helper/arc_intent_helper_bridge.h" |
| #include "components/content_settings/core/common/content_settings_types.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/devtools_agent_host.h" |
| #include "content/public/browser/media_device_id.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/video_capture_service.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_ui_data_source.h" |
| #include "content/public/common/url_constants.h" |
| #include "media/capture/video/chromeos/camera_app_device_provider_impl.h" |
| #include "media/capture/video/chromeos/mojom/camera_app.mojom.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| #include "services/network/public/mojom/content_security_policy.mojom.h" |
| #include "services/video_capture/public/mojom/video_capture_service.mojom.h" |
| #include "ui/aura/window.h" |
| #include "ui/webui/webui_allowlist.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| const base::Feature kCCALocalOverride{"CCALocalOverride", |
| base::FEATURE_DISABLED_BY_DEFAULT}; |
| const base::FilePath::CharType kCCALocalOverrideDirectoryPath[] = |
| FILE_PATH_LITERAL("/etc/camera/cca"); |
| |
| content::WebUIDataSource* CreateCameraAppUIHTMLSource( |
| CameraAppUIDelegate* delegate) { |
| content::WebUIDataSource* source = |
| content::WebUIDataSource::Create(kChromeUICameraAppHost); |
| |
| source->DisableTrustedTypesCSP(); |
| |
| // Add all settings resources. |
| source->AddResourcePaths( |
| base::make_span(kAshCameraAppResources, kAshCameraAppResourcesSize)); |
| |
| delegate->PopulateLoadTimeData(source); |
| |
| for (const auto& str : kStringResourceMap) { |
| source->AddLocalizedString(str.name, str.id); |
| } |
| |
| source->UseStringsJs(); |
| |
| if (base::FeatureList::IsEnabled(kCCALocalOverride)) { |
| source->SetRequestFilter( |
| base::BindRepeating([](const std::string& url) { |
| // Only override files that are copied locally with cca.py deploy. |
| if (!(base::StartsWith(url, "js/") || base::StartsWith(url, "css/") || |
| base::StartsWith(url, "images/") || |
| base::StartsWith(url, "views/") || |
| base::StartsWith(url, "sounds/"))) { |
| return false; |
| } |
| // This file is written by `cca.py deploy` and contains version |
| // information of deployed file. |
| base::FilePath version_path = |
| base::FilePath(kCCALocalOverrideDirectoryPath) |
| .Append("js/deployed_version.js"); |
| if (!base::PathExists(version_path)) { |
| return false; |
| } |
| return true; |
| }), |
| base::BindRepeating( |
| [](const std::string& url, |
| content::WebUIDataSource::GotDataCallback callback) { |
| base::FilePath path = |
| base::FilePath(kCCALocalOverrideDirectoryPath).Append(url); |
| std::string result; |
| if (base::ReadFileToString(path, &result)) { |
| std::move(callback).Run( |
| base::RefCountedString::TakeString(&result)); |
| } else { |
| std::move(callback).Run(nullptr); |
| } |
| })); |
| } |
| |
| source->OverrideContentSecurityPolicy( |
| network::mojom::CSPDirectiveName::WorkerSrc, |
| std::string("worker-src 'self';")); |
| source->OverrideContentSecurityPolicy( |
| network::mojom::CSPDirectiveName::FrameAncestors, |
| std::string("frame-ancestors 'self';")); |
| source->OverrideContentSecurityPolicy( |
| network::mojom::CSPDirectiveName::ChildSrc, |
| std::string("frame-src 'self' ") + kChromeUIUntrustedCameraAppURL + ";"); |
| source->OverrideContentSecurityPolicy( |
| network::mojom::CSPDirectiveName::ObjectSrc, |
| std::string("object-src 'self';")); |
| |
| return source; |
| } |
| |
| // Translates the renderer-side source ID to video device id. |
| void TranslateVideoDeviceId( |
| const std::string& salt, |
| const url::Origin& origin, |
| const std::string& source_id, |
| base::OnceCallback<void(const absl::optional<std::string>&)> callback) { |
| auto callback_on_io_thread = base::BindOnce( |
| [](const std::string& salt, const url::Origin& origin, |
| const std::string& source_id, |
| base::OnceCallback<void(const absl::optional<std::string>&)> |
| callback) { |
| content::GetMediaDeviceIDForHMAC( |
| blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, salt, |
| std::move(origin), source_id, std::move(callback)); |
| }, |
| salt, std::move(origin), source_id, std::move(callback)); |
| content::GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, std::move(callback_on_io_thread)); |
| } |
| |
| void HandleCameraResult( |
| content::BrowserContext* context, |
| uint32_t intent_id, |
| arc::mojom::CameraIntentAction action, |
| const std::vector<uint8_t>& data, |
| camera_app::mojom::CameraAppHelper::HandleCameraResultCallback callback) { |
| auto* intent_helper = |
| arc::ArcIntentHelperBridge::GetForBrowserContext(context); |
| intent_helper->HandleCameraResult(intent_id, action, data, |
| std::move(callback)); |
| } |
| |
| void SendNewCaptureBroadcast(content::BrowserContext* context, |
| bool is_video, |
| std::string file_path) { |
| auto* intent_helper = |
| arc::ArcIntentHelperBridge::GetForBrowserContext(context); |
| intent_helper->SendNewCaptureBroadcast(is_video, file_path); |
| } |
| |
| std::unique_ptr<media::CameraAppDeviceProviderImpl> |
| CreateCameraAppDeviceProvider(const url::Origin& security_origin, |
| content::BrowserContext* context) { |
| auto media_device_id_salt = context->GetMediaDeviceIDSalt(); |
| |
| mojo::PendingRemote<cros::mojom::CameraAppDeviceBridge> device_bridge; |
| auto device_bridge_receiver = device_bridge.InitWithNewPipeAndPassReceiver(); |
| |
| // Connects to CameraAppDeviceBridge from video_capture service. |
| content::GetVideoCaptureService().ConnectToCameraAppDeviceBridge( |
| std::move(device_bridge_receiver)); |
| |
| auto mapping_callback = |
| base::BindRepeating(&TranslateVideoDeviceId, media_device_id_salt, |
| std::move(security_origin)); |
| |
| return std::make_unique<media::CameraAppDeviceProviderImpl>( |
| std::move(device_bridge), std::move(mapping_callback)); |
| } |
| |
| std::unique_ptr<CameraAppHelperImpl> CreateCameraAppHelper( |
| CameraAppUI* camera_app_ui, |
| content::BrowserContext* browser_context, |
| aura::Window* window) { |
| DCHECK_NE(window, nullptr); |
| auto handle_result_callback = |
| base::BindRepeating(&HandleCameraResult, browser_context); |
| auto send_broadcast_callback = |
| base::BindRepeating(&SendNewCaptureBroadcast, browser_context); |
| |
| return std::make_unique<CameraAppHelperImpl>( |
| camera_app_ui, std::move(handle_result_callback), |
| std::move(send_broadcast_callback), window); |
| } |
| |
| } // namespace |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // |
| // CameraAppUI |
| // |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| CameraAppUI::CameraAppUI(content::WebUI* web_ui, |
| std::unique_ptr<CameraAppUIDelegate> delegate) |
| : ui::MojoWebUIController(web_ui), delegate_(std::move(delegate)) { |
| content::BrowserContext* browser_context = |
| web_ui->GetWebContents()->GetBrowserContext(); |
| |
| // Register auto-granted permissions. |
| auto* allowlist = WebUIAllowlist::GetOrCreate(browser_context); |
| const url::Origin host_origin = |
| url::Origin::Create(GURL(kChromeUICameraAppURL)); |
| allowlist->RegisterAutoGrantedPermission( |
| host_origin, ContentSettingsType::MEDIASTREAM_MIC); |
| allowlist->RegisterAutoGrantedPermission( |
| host_origin, ContentSettingsType::MEDIASTREAM_CAMERA); |
| allowlist->RegisterAutoGrantedPermission( |
| host_origin, ContentSettingsType::FILE_SYSTEM_READ_GUARD); |
| allowlist->RegisterAutoGrantedPermission( |
| host_origin, ContentSettingsType::FILE_SYSTEM_WRITE_GUARD); |
| allowlist->RegisterAutoGrantedPermission(host_origin, |
| ContentSettingsType::COOKIES); |
| allowlist->RegisterAutoGrantedPermission(host_origin, |
| ContentSettingsType::IDLE_DETECTION); |
| |
| delegate_->SetLaunchDirectory(); |
| |
| window()->SetProperty(kMinimizeOnBackKey, false); |
| |
| // Set up the data source. |
| content::WebUIDataSource::Add(browser_context, |
| CreateCameraAppUIHTMLSource(delegate_.get())); |
| |
| // Add ability to request chrome-untrusted: URLs |
| web_ui->AddRequestableScheme(content::kChromeUIUntrustedScheme); |
| |
| if (app_window_manager()->IsDevToolsEnabled()) { |
| delegate_->OpenDevToolsWindow(web_ui->GetWebContents()); |
| } |
| |
| content::DevToolsAgentHost::AddObserver(this); |
| } |
| |
| CameraAppUI::~CameraAppUI() { |
| content::DevToolsAgentHost::RemoveObserver(this); |
| } |
| |
| void CameraAppUI::BindInterface( |
| mojo::PendingReceiver<cros::mojom::CameraAppDeviceProvider> receiver) { |
| provider_ = CreateCameraAppDeviceProvider( |
| url::Origin::Create(GURL(kChromeUICameraAppURL)), |
| web_ui()->GetWebContents()->GetBrowserContext()); |
| provider_->Bind(std::move(receiver)); |
| } |
| |
| void CameraAppUI::BindInterface( |
| mojo::PendingReceiver<camera_app::mojom::CameraAppHelper> receiver) { |
| helper_ = CreateCameraAppHelper( |
| this, web_ui()->GetWebContents()->GetBrowserContext(), window()); |
| helper_->Bind(std::move(receiver)); |
| } |
| |
| aura::Window* CameraAppUI::window() { |
| return web_ui()->GetWebContents()->GetTopLevelNativeWindow(); |
| } |
| |
| CameraAppWindowManager* CameraAppUI::app_window_manager() { |
| return CameraAppWindowManager::GetInstance(); |
| } |
| |
| const GURL& CameraAppUI::url() { |
| return web_ui()->GetWebContents()->GetLastCommittedURL(); |
| } |
| |
| void CameraAppUI::DevToolsAgentHostAttached( |
| content::DevToolsAgentHost* agent_host) { |
| if (agent_host->GetWebContents() == nullptr || |
| !base::StartsWith( |
| agent_host->GetWebContents()->GetLastCommittedURL().spec(), |
| kChromeUICameraAppMainURL)) { |
| return; |
| } |
| app_window_manager()->SetDevToolsEnabled(true); |
| } |
| |
| void CameraAppUI::DevToolsAgentHostDetached( |
| content::DevToolsAgentHost* agent_host) { |
| if (agent_host->GetWebContents() == nullptr || |
| !base::StartsWith( |
| agent_host->GetWebContents()->GetLastCommittedURL().spec(), |
| kChromeUICameraAppMainURL)) { |
| return; |
| } |
| app_window_manager()->SetDevToolsEnabled(false); |
| } |
| |
| bool CameraAppUI::IsJavascriptErrorReportingEnabled() { |
| // Since we proactively call CrashReportPrivate.reportError() in CCA now. |
| return false; |
| } |
| |
| WEB_UI_CONTROLLER_TYPE_IMPL(CameraAppUI) |
| |
| } // namespace ash |