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 "base/metrics/field_trial.h" | |
8 #include "chrome/browser/media/media_stream_device_permissions.h" | |
9 #include "chrome/browser/media/media_stream_infobar_delegate.h" | |
10 #include "chrome/browser/profiles/profile.h" | |
11 #include "chrome/browser/ui/website_settings/permission_bubble_manager.h" | |
12 #include "chrome/common/pref_names.h" | |
13 #include "components/content_settings/core/browser/host_content_settings_map.h" | |
14 #include "content/public/browser/browser_thread.h" | |
15 #include "content/public/browser/notification_service.h" | |
16 #include "content/public/browser/notification_types.h" | |
17 #include "content/public/browser/web_contents.h" | |
18 | |
19 #if defined(ENABLE_EXTENSIONS) | |
20 #include "extensions/common/extension.h" | |
Sergey Ulanov
2015/06/01 23:39:18
I don't think you need this include
| |
21 #endif | |
22 | |
23 using content::BrowserThread; | |
24 | |
25 namespace { | |
26 | |
27 // A finch experiment to enable the permission bubble for media requests only. | |
28 bool MediaStreamPermissionBubbleExperimentEnabled() { | |
29 const std::string group = | |
30 base::FieldTrialList::FindFullName("MediaStreamPermissionBubble"); | |
31 if (group == "enabled") | |
32 return true; | |
33 | |
34 return false; | |
35 } | |
36 | |
37 } // namespace | |
38 | |
39 PendingAccessRequest::PendingAccessRequest( | |
40 const content::MediaStreamRequest& request, | |
41 const content::MediaResponseCallback& callback) | |
42 : request(request), callback(callback) { | |
43 } | |
44 | |
45 PendingAccessRequest::~PendingAccessRequest() { | |
46 } | |
47 | |
48 PermissionBubbleMediaAccessHandler::PermissionBubbleMediaAccessHandler() { | |
49 // PermissionBubbleMediaAccessHandler should be created on UI thread. | |
50 // Otherwise, it will not receive | |
51 // content::NOTIFICATION_WEB_CONTENTS_DESTROYED, and that will result in | |
52 // possible use after free. | |
53 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
54 notifications_registrar_.Add(this, | |
55 content::NOTIFICATION_WEB_CONTENTS_DESTROYED, | |
56 content::NotificationService::AllSources()); | |
57 } | |
58 | |
59 PermissionBubbleMediaAccessHandler::~PermissionBubbleMediaAccessHandler() { | |
60 } | |
61 | |
62 bool PermissionBubbleMediaAccessHandler::SupportsStreamType( | |
63 const content::MediaStreamType type, | |
64 const extensions::Extension* extension) { | |
65 return type == content::MEDIA_DEVICE_VIDEO_CAPTURE || | |
66 type == content::MEDIA_DEVICE_AUDIO_CAPTURE; | |
67 } | |
68 | |
69 bool PermissionBubbleMediaAccessHandler::CheckMediaAccessPermission( | |
70 content::WebContents* web_contents, | |
71 const GURL& security_origin, | |
72 content::MediaStreamType type, | |
73 const extensions::Extension* extension) { | |
74 Profile* profile = | |
75 Profile::FromBrowserContext(web_contents->GetBrowserContext()); | |
76 | |
77 ContentSettingsType contentSettingsType = | |
Sergey Ulanov
2015/06/01 23:39:18
content_settings_type
| |
78 type == content::MEDIA_DEVICE_AUDIO_CAPTURE | |
79 ? CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC | |
80 : CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA; | |
81 | |
82 if (CheckAllowAllMediaStreamContentForOrigin(profile, security_origin, | |
83 contentSettingsType)) { | |
84 return true; | |
85 } | |
86 | |
87 const char* policy_name = type == content::MEDIA_DEVICE_AUDIO_CAPTURE | |
88 ? prefs::kAudioCaptureAllowed | |
89 : prefs::kVideoCaptureAllowed; | |
90 const char* list_policy_name = type == content::MEDIA_DEVICE_AUDIO_CAPTURE | |
91 ? prefs::kAudioCaptureAllowedUrls | |
92 : prefs::kVideoCaptureAllowedUrls; | |
93 if (GetDevicePolicy(profile, security_origin, policy_name, | |
94 list_policy_name) == ALWAYS_ALLOW) { | |
95 return true; | |
96 } | |
97 | |
98 // There's no secondary URL for these content types, hence duplicating | |
99 // |security_origin|. | |
100 if (profile->GetHostContentSettingsMap()->GetContentSetting( | |
101 security_origin, security_origin, contentSettingsType, | |
102 content_settings::ResourceIdentifier()) == CONTENT_SETTING_ALLOW) { | |
103 return true; | |
104 } | |
105 | |
106 return false; | |
107 } | |
108 | |
109 void PermissionBubbleMediaAccessHandler::HandleRequest( | |
110 content::WebContents* web_contents, | |
111 const content::MediaStreamRequest& request, | |
112 const content::MediaResponseCallback& callback, | |
113 const extensions::Extension* extension) { | |
114 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
115 | |
116 RequestsQueue& queue = pending_requests_[web_contents]; | |
117 queue.push_back(PendingAccessRequest(request, callback)); | |
118 | |
119 // If this is the only request then show the infobar. | |
120 if (queue.size() == 1) | |
121 ProcessQueuedAccessRequest(web_contents); | |
122 } | |
123 | |
124 void PermissionBubbleMediaAccessHandler::ProcessQueuedAccessRequest( | |
125 content::WebContents* web_contents) { | |
126 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
127 | |
128 std::map<content::WebContents*, RequestsQueue>::iterator it = | |
129 pending_requests_.find(web_contents); | |
130 | |
131 if (it == pending_requests_.end() || it->second.empty()) { | |
132 // Don't do anything if the tab was closed. | |
133 return; | |
134 } | |
135 | |
136 DCHECK(!it->second.empty()); | |
137 | |
138 if (PermissionBubbleManager::Enabled() || | |
139 MediaStreamPermissionBubbleExperimentEnabled()) { | |
140 scoped_ptr<MediaStreamDevicesController> controller( | |
141 new MediaStreamDevicesController( | |
142 web_contents, it->second.front().request, | |
143 base::Bind( | |
144 &PermissionBubbleMediaAccessHandler::OnAccessRequestResponse, | |
145 base::Unretained(this), web_contents))); | |
146 if (controller->DismissInfoBarAndTakeActionOnSettings()) | |
147 return; | |
148 PermissionBubbleManager* bubble_manager = | |
149 PermissionBubbleManager::FromWebContents(web_contents); | |
150 if (bubble_manager) | |
151 bubble_manager->AddRequest(controller.release()); | |
152 return; | |
153 } | |
154 | |
155 // TODO(gbillock): delete this block and the MediaStreamInfoBarDelegate | |
156 // when we've transitioned to bubbles. (crbug/337458) | |
157 MediaStreamInfoBarDelegate::Create( | |
158 web_contents, it->second.front().request, | |
159 base::Bind(&PermissionBubbleMediaAccessHandler::OnAccessRequestResponse, | |
160 base::Unretained(this), web_contents)); | |
161 } | |
162 | |
163 void PermissionBubbleMediaAccessHandler::UpdateMediaRequest( | |
164 int render_process_id, | |
165 int render_frame_id, | |
166 int page_request_id, | |
167 content::MediaStreamType stream_type, | |
168 content::MediaRequestState state) { | |
169 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
170 if (state != content::MEDIA_REQUEST_STATE_CLOSING) | |
171 return; | |
172 | |
173 bool found = false; | |
174 for (RequestsQueues::iterator rqs_it = pending_requests_.begin(); | |
175 rqs_it != pending_requests_.end(); ++rqs_it) { | |
176 RequestsQueue& queue = rqs_it->second; | |
177 for (RequestsQueue::iterator it = queue.begin(); it != queue.end(); ++it) { | |
178 if (it->request.render_process_id == render_process_id && | |
179 it->request.render_frame_id == render_frame_id && | |
180 it->request.page_request_id == page_request_id) { | |
181 queue.erase(it); | |
182 found = true; | |
183 break; | |
184 } | |
185 } | |
186 if (found) | |
187 break; | |
188 } | |
189 } | |
190 | |
191 void PermissionBubbleMediaAccessHandler::OnAccessRequestResponse( | |
192 content::WebContents* web_contents, | |
193 const content::MediaStreamDevices& devices, | |
194 content::MediaStreamRequestResult result, | |
195 scoped_ptr<content::MediaStreamUI> ui) { | |
196 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
197 | |
198 std::map<content::WebContents*, RequestsQueue>::iterator it = | |
199 pending_requests_.find(web_contents); | |
200 if (it == pending_requests_.end()) { | |
201 // WebContents has been destroyed. Don't need to do anything. | |
202 return; | |
203 } | |
204 | |
205 RequestsQueue& queue(it->second); | |
206 if (queue.empty()) | |
207 return; | |
208 | |
209 content::MediaResponseCallback callback = queue.front().callback; | |
210 queue.pop_front(); | |
211 | |
212 if (!queue.empty()) { | |
213 // Post a task to process next queued request. It has to be done | |
214 // asynchronously to make sure that calling infobar is not destroyed until | |
215 // after this function returns. | |
216 BrowserThread::PostTask( | |
217 BrowserThread::UI, FROM_HERE, | |
218 base::Bind( | |
219 &PermissionBubbleMediaAccessHandler::ProcessQueuedAccessRequest, | |
220 base::Unretained(this), web_contents)); | |
221 } | |
222 | |
223 callback.Run(devices, result, ui.Pass()); | |
224 } | |
225 | |
226 void PermissionBubbleMediaAccessHandler::Observe( | |
227 int type, | |
228 const content::NotificationSource& source, | |
229 const content::NotificationDetails& details) { | |
230 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
231 if (type == content::NOTIFICATION_WEB_CONTENTS_DESTROYED) { | |
232 content::WebContents* web_contents = | |
233 content::Source<content::WebContents>(source).ptr(); | |
234 pending_requests_.erase(web_contents); | |
235 } | |
236 } | |
OLD | NEW |