// Copyright 2015 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 "chrome/browser/media/permission_bubble_media_access_handler.h" #include #include "base/metrics/field_trial.h" #include "chrome/browser/media/media_permission.h" #include "chrome/browser/media/media_stream_device_permissions.h" #include "chrome/browser/media/media_stream_devices_controller.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/features.h" #include "chrome/common/pref_names.h" #include "components/content_settings/core/browser/host_content_settings_map.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_types.h" #include "content/public/browser/web_contents.h" #if BUILDFLAG(ANDROID_JAVA_UI) #include #include "base/bind.h" #include "base/bind_helpers.h" #include "chrome/browser/media/media_stream_infobar_delegate_android.h" #include "chrome/browser/permissions/permission_update_infobar_delegate_android.h" #else #include "chrome/browser/ui/website_settings/permission_bubble_manager.h" #endif // BUILDFLAG(ANDROID_JAVA_UI) #if BUILDFLAG(ANDROID_JAVA_UI) namespace { // Callback for the permission update infobar when the site and Chrome // permissions are mismatched on Android. void OnPermissionConflictResolved( scoped_ptr controller, bool allowed) { if (allowed) controller->PermissionGranted(); else controller->ForcePermissionDeniedTemporarily(); } } // namespace #endif // BUILDFLAG(ANDROID_JAVA_UI) using content::BrowserThread; struct PermissionBubbleMediaAccessHandler::PendingAccessRequest { PendingAccessRequest(const content::MediaStreamRequest& request, const content::MediaResponseCallback& callback) : request(request), callback(callback) {} ~PendingAccessRequest() {} // TODO(gbillock): make the MediaStreamDevicesController owned by // this object when we're using bubbles. content::MediaStreamRequest request; content::MediaResponseCallback callback; }; PermissionBubbleMediaAccessHandler::PermissionBubbleMediaAccessHandler() { // PermissionBubbleMediaAccessHandler should be created on UI thread. // Otherwise, it will not receive // content::NOTIFICATION_WEB_CONTENTS_DESTROYED, and that will result in // possible use after free. DCHECK_CURRENTLY_ON(BrowserThread::UI); notifications_registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, content::NotificationService::AllSources()); } PermissionBubbleMediaAccessHandler::~PermissionBubbleMediaAccessHandler() { } bool PermissionBubbleMediaAccessHandler::SupportsStreamType( const content::MediaStreamType type, const extensions::Extension* extension) { return type == content::MEDIA_DEVICE_VIDEO_CAPTURE || type == content::MEDIA_DEVICE_AUDIO_CAPTURE; } bool PermissionBubbleMediaAccessHandler::CheckMediaAccessPermission( content::WebContents* web_contents, const GURL& security_origin, content::MediaStreamType type, const extensions::Extension* extension) { Profile* profile = Profile::FromBrowserContext(web_contents->GetBrowserContext()); ContentSettingsType content_settings_type = type == content::MEDIA_DEVICE_AUDIO_CAPTURE ? CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC : CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA; // TODO(raymes): This function may be called for a media request coming from // Flash or from WebRTC. However, whether or not this is a request for Flash, // in which case we would use MEDIA_OPEN_DEVICE_PEPPER_ONLY, isn't plumbed // through. Fortunately, post M47, WebRTC requests can't be made from HTTP so // we can assume all HTTP requests are for Flash for the purpose of the // permission check. This special case should be removed after HTTP access for // Flash has been deprecated (crbug.com/526324). See crbug.com/547654 for more // details. bool is_insecure_pepper_request = security_origin.SchemeIs(url::kHttpScheme); MediaPermission permission( content_settings_type, is_insecure_pepper_request, security_origin, web_contents->GetLastCommittedURL().GetOrigin(), profile); content::MediaStreamRequestResult unused; return permission.GetPermissionStatus(&unused) == CONTENT_SETTING_ALLOW; } void PermissionBubbleMediaAccessHandler::HandleRequest( content::WebContents* web_contents, const content::MediaStreamRequest& request, const content::MediaResponseCallback& callback, const extensions::Extension* extension) { DCHECK_CURRENTLY_ON(BrowserThread::UI); RequestsQueue& queue = pending_requests_[web_contents]; queue.push_back(PendingAccessRequest(request, callback)); // If this is the only request then show the infobar. if (queue.size() == 1) ProcessQueuedAccessRequest(web_contents); } void PermissionBubbleMediaAccessHandler::ProcessQueuedAccessRequest( content::WebContents* web_contents) { DCHECK_CURRENTLY_ON(BrowserThread::UI); std::map::iterator it = pending_requests_.find(web_contents); if (it == pending_requests_.end() || it->second.empty()) { // Don't do anything if the tab was closed. return; } DCHECK(!it->second.empty()); scoped_ptr controller( new MediaStreamDevicesController( web_contents, it->second.front().request, base::Bind( &PermissionBubbleMediaAccessHandler::OnAccessRequestResponse, base::Unretained(this), web_contents))); if (!controller->IsAskingForAudio() && !controller->IsAskingForVideo()) { #if BUILDFLAG(ANDROID_JAVA_UI) // If either audio or video was previously allowed and Chrome no longer has // the necessary permissions, show a infobar to attempt to address this // mismatch. std::vector content_settings_types; if (controller->IsAllowedForAudio()) content_settings_types.push_back(CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC); if (controller->IsAllowedForVideo()) { content_settings_types.push_back( CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA); } if (!content_settings_types.empty() && PermissionUpdateInfoBarDelegate::ShouldShowPermissionInfobar( web_contents, content_settings_types)) { PermissionUpdateInfoBarDelegate::Create( web_contents, content_settings_types, base::Bind( &OnPermissionConflictResolved, base::Passed(&controller))); } #endif return; } #if BUILDFLAG(ANDROID_JAVA_UI) MediaStreamInfoBarDelegateAndroid::Create(web_contents, std::move(controller)); #else PermissionBubbleManager* bubble_manager = PermissionBubbleManager::FromWebContents(web_contents); if (bubble_manager) bubble_manager->AddRequest(controller.release()); #endif } void PermissionBubbleMediaAccessHandler::UpdateMediaRequestState( int render_process_id, int render_frame_id, int page_request_id, content::MediaStreamType stream_type, content::MediaRequestState state) { DCHECK_CURRENTLY_ON(BrowserThread::UI); if (state != content::MEDIA_REQUEST_STATE_CLOSING) return; bool found = false; for (RequestsQueues::iterator rqs_it = pending_requests_.begin(); rqs_it != pending_requests_.end(); ++rqs_it) { RequestsQueue& queue = rqs_it->second; for (RequestsQueue::iterator it = queue.begin(); it != queue.end(); ++it) { if (it->request.render_process_id == render_process_id && it->request.render_frame_id == render_frame_id && it->request.page_request_id == page_request_id) { queue.erase(it); found = true; break; } } if (found) break; } } void PermissionBubbleMediaAccessHandler::OnAccessRequestResponse( content::WebContents* web_contents, const content::MediaStreamDevices& devices, content::MediaStreamRequestResult result, scoped_ptr ui) { DCHECK_CURRENTLY_ON(BrowserThread::UI); std::map::iterator it = pending_requests_.find(web_contents); if (it == pending_requests_.end()) { // WebContents has been destroyed. Don't need to do anything. return; } RequestsQueue& queue(it->second); if (queue.empty()) return; content::MediaResponseCallback callback = queue.front().callback; queue.pop_front(); if (!queue.empty()) { // Post a task to process next queued request. It has to be done // asynchronously to make sure that calling infobar is not destroyed until // after this function returns. BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind( &PermissionBubbleMediaAccessHandler::ProcessQueuedAccessRequest, base::Unretained(this), web_contents)); } callback.Run(devices, result, std::move(ui)); } void PermissionBubbleMediaAccessHandler::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { DCHECK_CURRENTLY_ON(BrowserThread::UI); if (type == content::NOTIFICATION_WEB_CONTENTS_DESTROYED) { content::WebContents* web_contents = content::Source(source).ptr(); pending_requests_.erase(web_contents); } }