| OLD | NEW |
| (Empty) |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/media/permission_bubble_media_access_handler.h" | |
| 6 | |
| 7 #include <utility> | |
| 8 | |
| 9 #include "base/metrics/field_trial.h" | |
| 10 #include "chrome/browser/media/media_permission.h" | |
| 11 #include "chrome/browser/media/media_stream_device_permissions.h" | |
| 12 #include "chrome/browser/media/media_stream_devices_controller.h" | |
| 13 #include "chrome/browser/profiles/profile.h" | |
| 14 #include "chrome/common/features.h" | |
| 15 #include "chrome/common/pref_names.h" | |
| 16 #include "components/content_settings/core/browser/host_content_settings_map.h" | |
| 17 #include "content/public/browser/browser_thread.h" | |
| 18 #include "content/public/browser/notification_service.h" | |
| 19 #include "content/public/browser/notification_types.h" | |
| 20 #include "content/public/browser/web_contents.h" | |
| 21 | |
| 22 #if BUILDFLAG(ANDROID_JAVA_UI) | |
| 23 #include <vector> | |
| 24 | |
| 25 #include "base/bind.h" | |
| 26 #include "base/bind_helpers.h" | |
| 27 #include "chrome/browser/media/media_stream_infobar_delegate_android.h" | |
| 28 #include "chrome/browser/permissions/permission_update_infobar_delegate_android.
h" | |
| 29 #else | |
| 30 #include "chrome/browser/permissions/permission_request_manager.h" | |
| 31 #endif // BUILDFLAG(ANDROID_JAVA_UI) | |
| 32 | |
| 33 #if BUILDFLAG(ANDROID_JAVA_UI) | |
| 34 namespace { | |
| 35 // Callback for the permission update infobar when the site and Chrome | |
| 36 // permissions are mismatched on Android. | |
| 37 void OnPermissionConflictResolved( | |
| 38 std::unique_ptr<MediaStreamDevicesController> controller, | |
| 39 bool allowed) { | |
| 40 if (allowed) | |
| 41 controller->PermissionGranted(); | |
| 42 else | |
| 43 controller->ForcePermissionDeniedTemporarily(); | |
| 44 } | |
| 45 } // namespace | |
| 46 | |
| 47 #endif // BUILDFLAG(ANDROID_JAVA_UI) | |
| 48 | |
| 49 using content::BrowserThread; | |
| 50 | |
| 51 struct PermissionBubbleMediaAccessHandler::PendingAccessRequest { | |
| 52 PendingAccessRequest(const content::MediaStreamRequest& request, | |
| 53 const content::MediaResponseCallback& callback) | |
| 54 : request(request), callback(callback) {} | |
| 55 ~PendingAccessRequest() {} | |
| 56 | |
| 57 // TODO(gbillock): make the MediaStreamDevicesController owned by | |
| 58 // this object when we're using bubbles. | |
| 59 content::MediaStreamRequest request; | |
| 60 content::MediaResponseCallback callback; | |
| 61 }; | |
| 62 | |
| 63 PermissionBubbleMediaAccessHandler::PermissionBubbleMediaAccessHandler() { | |
| 64 // PermissionBubbleMediaAccessHandler should be created on UI thread. | |
| 65 // Otherwise, it will not receive | |
| 66 // content::NOTIFICATION_WEB_CONTENTS_DESTROYED, and that will result in | |
| 67 // possible use after free. | |
| 68 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 69 notifications_registrar_.Add(this, | |
| 70 content::NOTIFICATION_WEB_CONTENTS_DESTROYED, | |
| 71 content::NotificationService::AllSources()); | |
| 72 } | |
| 73 | |
| 74 PermissionBubbleMediaAccessHandler::~PermissionBubbleMediaAccessHandler() { | |
| 75 } | |
| 76 | |
| 77 bool PermissionBubbleMediaAccessHandler::SupportsStreamType( | |
| 78 const content::MediaStreamType type, | |
| 79 const extensions::Extension* extension) { | |
| 80 return type == content::MEDIA_DEVICE_VIDEO_CAPTURE || | |
| 81 type == content::MEDIA_DEVICE_AUDIO_CAPTURE; | |
| 82 } | |
| 83 | |
| 84 bool PermissionBubbleMediaAccessHandler::CheckMediaAccessPermission( | |
| 85 content::WebContents* web_contents, | |
| 86 const GURL& security_origin, | |
| 87 content::MediaStreamType type, | |
| 88 const extensions::Extension* extension) { | |
| 89 Profile* profile = | |
| 90 Profile::FromBrowserContext(web_contents->GetBrowserContext()); | |
| 91 ContentSettingsType content_settings_type = | |
| 92 type == content::MEDIA_DEVICE_AUDIO_CAPTURE | |
| 93 ? CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC | |
| 94 : CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA; | |
| 95 | |
| 96 MediaPermission permission(content_settings_type, security_origin, | |
| 97 web_contents->GetLastCommittedURL().GetOrigin(), profile); | |
| 98 content::MediaStreamRequestResult unused; | |
| 99 return permission.GetPermissionStatus(&unused) == CONTENT_SETTING_ALLOW; | |
| 100 } | |
| 101 | |
| 102 void PermissionBubbleMediaAccessHandler::HandleRequest( | |
| 103 content::WebContents* web_contents, | |
| 104 const content::MediaStreamRequest& request, | |
| 105 const content::MediaResponseCallback& callback, | |
| 106 const extensions::Extension* extension) { | |
| 107 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 108 | |
| 109 RequestsQueue& queue = pending_requests_[web_contents]; | |
| 110 queue.push_back(PendingAccessRequest(request, callback)); | |
| 111 | |
| 112 // If this is the only request then show the infobar. | |
| 113 if (queue.size() == 1) | |
| 114 ProcessQueuedAccessRequest(web_contents); | |
| 115 } | |
| 116 | |
| 117 void PermissionBubbleMediaAccessHandler::ProcessQueuedAccessRequest( | |
| 118 content::WebContents* web_contents) { | |
| 119 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 120 | |
| 121 std::map<content::WebContents*, RequestsQueue>::iterator it = | |
| 122 pending_requests_.find(web_contents); | |
| 123 | |
| 124 if (it == pending_requests_.end() || it->second.empty()) { | |
| 125 // Don't do anything if the tab was closed. | |
| 126 return; | |
| 127 } | |
| 128 | |
| 129 DCHECK(!it->second.empty()); | |
| 130 | |
| 131 std::unique_ptr<MediaStreamDevicesController> controller( | |
| 132 new MediaStreamDevicesController( | |
| 133 web_contents, it->second.front().request, | |
| 134 base::Bind( | |
| 135 &PermissionBubbleMediaAccessHandler::OnAccessRequestResponse, | |
| 136 base::Unretained(this), web_contents))); | |
| 137 if (!controller->IsAskingForAudio() && !controller->IsAskingForVideo()) { | |
| 138 #if BUILDFLAG(ANDROID_JAVA_UI) | |
| 139 // If either audio or video was previously allowed and Chrome no longer has | |
| 140 // the necessary permissions, show a infobar to attempt to address this | |
| 141 // mismatch. | |
| 142 std::vector<ContentSettingsType> content_settings_types; | |
| 143 if (controller->IsAllowedForAudio()) | |
| 144 content_settings_types.push_back(CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC); | |
| 145 | |
| 146 if (controller->IsAllowedForVideo()) { | |
| 147 content_settings_types.push_back( | |
| 148 CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA); | |
| 149 } | |
| 150 if (!content_settings_types.empty() && | |
| 151 PermissionUpdateInfoBarDelegate::ShouldShowPermissionInfobar( | |
| 152 web_contents, content_settings_types)) { | |
| 153 PermissionUpdateInfoBarDelegate::Create( | |
| 154 web_contents, content_settings_types, | |
| 155 base::Bind( | |
| 156 &OnPermissionConflictResolved, base::Passed(&controller))); | |
| 157 } | |
| 158 #endif | |
| 159 return; | |
| 160 } | |
| 161 | |
| 162 #if BUILDFLAG(ANDROID_JAVA_UI) | |
| 163 MediaStreamInfoBarDelegateAndroid::Create(web_contents, | |
| 164 std::move(controller)); | |
| 165 #else | |
| 166 PermissionRequestManager* permission_request_manager = | |
| 167 PermissionRequestManager::FromWebContents(web_contents); | |
| 168 if (permission_request_manager) | |
| 169 permission_request_manager->AddRequest(controller.release()); | |
| 170 #endif | |
| 171 } | |
| 172 | |
| 173 void PermissionBubbleMediaAccessHandler::UpdateMediaRequestState( | |
| 174 int render_process_id, | |
| 175 int render_frame_id, | |
| 176 int page_request_id, | |
| 177 content::MediaStreamType stream_type, | |
| 178 content::MediaRequestState state) { | |
| 179 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 180 if (state != content::MEDIA_REQUEST_STATE_CLOSING) | |
| 181 return; | |
| 182 | |
| 183 bool found = false; | |
| 184 for (RequestsQueues::iterator rqs_it = pending_requests_.begin(); | |
| 185 rqs_it != pending_requests_.end(); ++rqs_it) { | |
| 186 RequestsQueue& queue = rqs_it->second; | |
| 187 for (RequestsQueue::iterator it = queue.begin(); it != queue.end(); ++it) { | |
| 188 if (it->request.render_process_id == render_process_id && | |
| 189 it->request.render_frame_id == render_frame_id && | |
| 190 it->request.page_request_id == page_request_id) { | |
| 191 queue.erase(it); | |
| 192 found = true; | |
| 193 break; | |
| 194 } | |
| 195 } | |
| 196 if (found) | |
| 197 break; | |
| 198 } | |
| 199 } | |
| 200 | |
| 201 void PermissionBubbleMediaAccessHandler::OnAccessRequestResponse( | |
| 202 content::WebContents* web_contents, | |
| 203 const content::MediaStreamDevices& devices, | |
| 204 content::MediaStreamRequestResult result, | |
| 205 std::unique_ptr<content::MediaStreamUI> ui) { | |
| 206 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 207 | |
| 208 std::map<content::WebContents*, RequestsQueue>::iterator it = | |
| 209 pending_requests_.find(web_contents); | |
| 210 if (it == pending_requests_.end()) { | |
| 211 // WebContents has been destroyed. Don't need to do anything. | |
| 212 return; | |
| 213 } | |
| 214 | |
| 215 RequestsQueue& queue(it->second); | |
| 216 if (queue.empty()) | |
| 217 return; | |
| 218 | |
| 219 content::MediaResponseCallback callback = queue.front().callback; | |
| 220 queue.pop_front(); | |
| 221 | |
| 222 if (!queue.empty()) { | |
| 223 // Post a task to process next queued request. It has to be done | |
| 224 // asynchronously to make sure that calling infobar is not destroyed until | |
| 225 // after this function returns. | |
| 226 BrowserThread::PostTask( | |
| 227 BrowserThread::UI, FROM_HERE, | |
| 228 base::Bind( | |
| 229 &PermissionBubbleMediaAccessHandler::ProcessQueuedAccessRequest, | |
| 230 base::Unretained(this), web_contents)); | |
| 231 } | |
| 232 | |
| 233 callback.Run(devices, result, std::move(ui)); | |
| 234 } | |
| 235 | |
| 236 void PermissionBubbleMediaAccessHandler::Observe( | |
| 237 int type, | |
| 238 const content::NotificationSource& source, | |
| 239 const content::NotificationDetails& details) { | |
| 240 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 241 DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, type); | |
| 242 | |
| 243 pending_requests_.erase(content::Source<content::WebContents>(source).ptr()); | |
| 244 } | |
| OLD | NEW |