Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(20)

Side by Side Diff: content/browser/renderer_host/media/media_stream_device_settings.cc

Issue 10829190: Resolve the problems where we can leak the system tray UI (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: addressed the comments and added unit tests to media_stream_device_settings_unittest Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "content/browser/renderer_host/media/media_stream_device_settings.h" 5 #include "content/browser/renderer_host/media/media_stream_device_settings.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 8
9 #include "base/bind.h" 9 #include "base/bind.h"
10 #include "base/callback.h" 10 #include "base/callback.h"
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after
79 }; 79 };
80 80
81 } // namespace 81 } // namespace
82 82
83 namespace media_stream { 83 namespace media_stream {
84 84
85 typedef std::map<MediaStreamType, StreamDeviceInfoArray> DeviceMap; 85 typedef std::map<MediaStreamType, StreamDeviceInfoArray> DeviceMap;
86 86
87 // Device request contains all data needed to keep track of requests between the 87 // Device request contains all data needed to keep track of requests between the
88 // different calls. 88 // different calls.
89 class MediaStreamDeviceSettingsRequest : public MediaStreamRequest { 89 struct MediaStreamDeviceSettingsRequest : public MediaStreamRequest {
90 public: 90 public:
91 MediaStreamDeviceSettingsRequest( 91 MediaStreamDeviceSettingsRequest(
92 int render_pid, 92 int render_pid,
93 int render_vid, 93 int render_vid,
94 const GURL& origin, 94 const GURL& origin,
95 const StreamOptions& request_options) 95 const StreamOptions& request_options)
96 : MediaStreamRequest(render_pid, render_vid, origin), 96 : MediaStreamRequest(render_pid, render_vid, origin),
97 options(request_options), 97 options(request_options),
98 posted_task(false) {} 98 posted_task(false) {}
99 99
100 ~MediaStreamDeviceSettingsRequest() {} 100 ~MediaStreamDeviceSettingsRequest() {}
101 101
102 // Request options. 102 // Request options.
103 StreamOptions options; 103 StreamOptions options;
104 // Map containing available devices for the requested capture types. 104 // Map containing available devices for the requested capture types.
105 // Note, never call devices_full[stream_type].empty() before making sure
106 // that type of device has existed on the map, otherwise it will create an
107 // empty device entry on the map.
105 DeviceMap devices_full; 108 DeviceMap devices_full;
106 // Whether or not a task was posted to make the call to 109 // Whether or not a task was posted to make the call to
107 // RequestMediaAccessPermission, to make sure that we never post twice to it. 110 // RequestMediaAccessPermission, to make sure that we never post twice to it.
108 bool posted_task; 111 bool posted_task;
109 }; 112 };
110 113
111 namespace { 114 namespace {
112 115
113 // Sends the request to the appropriate WebContents. 116 // Sends the request to the appropriate WebContents.
114 void DoDeviceRequest( 117 void DoDeviceRequest(const MediaStreamDeviceSettingsRequest& request,
115 const MediaStreamDeviceSettingsRequest& request, 118 const content::MediaResponseCallback& callback) {
116 const content::MediaResponseCallback& callback) {
117 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 119 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
118 120
119 // Send the permission request to the web contents. 121 // Send the permission request to the web contents.
120 content::RenderViewHostImpl* host = 122 content::RenderViewHostImpl* host =
121 content::RenderViewHostImpl::FromID(request.render_process_id, 123 content::RenderViewHostImpl::FromID(request.render_process_id,
122 request.render_view_id); 124 request.render_view_id);
123 125
124 // Tab may have gone away. 126 // Tab may have gone away.
125 if (!host || !host->GetDelegate()) { 127 if (!host || !host->GetDelegate()) {
126 callback.Run(content::MediaStreamDevices()); 128 callback.Run(content::MediaStreamDevices());
127 return; 129 return;
128 } 130 }
129 131
130 host->GetDelegate()->RequestMediaAccessPermission(&request, callback); 132 host->GetDelegate()->RequestMediaAccessPermission(&request, callback);
131 } 133 }
132 134
135 bool IsRequestReadyForView(
136 media_stream::MediaStreamDeviceSettingsRequest* request) {
137 if ((request->options.audio && request->devices_full.count(
138 content::MEDIA_STREAM_DEVICE_TYPE_AUDIO_CAPTURE) == 0) ||
139 (request->options.video && request->devices_full.count(
140 content::MEDIA_STREAM_DEVICE_TYPE_VIDEO_CAPTURE) == 0))
141 return false;
142
143 // We have got all the requested devices, it is ready if it has not
perkj_chrome 2012/08/10 12:26:58 nit: already been posted
no longer working on chromium 2012/08/13 11:39:53 Done.
144 // been posted for UI.
145 return !request->posted_task;
146 }
147
133 } // namespace 148 } // namespace
134 149
135 MediaStreamDeviceSettings::MediaStreamDeviceSettings( 150 MediaStreamDeviceSettings::MediaStreamDeviceSettings(
136 SettingsRequester* requester) 151 SettingsRequester* requester)
137 : requester_(requester), 152 : requester_(requester),
138 use_fake_ui_(false), 153 use_fake_ui_(false),
139 weak_ptr_factory_(this) { 154 weak_ptr_factory_(this) {
140 DCHECK(requester_); 155 DCHECK(requester_);
141 } 156 }
142 157
143 MediaStreamDeviceSettings::~MediaStreamDeviceSettings() { 158 MediaStreamDeviceSettings::~MediaStreamDeviceSettings() {
144 STLDeleteValues(&requests_); 159 STLDeleteValues(&requests_);
145 } 160 }
146 161
147 void MediaStreamDeviceSettings::RequestCaptureDeviceUsage( 162 void MediaStreamDeviceSettings::RequestCaptureDeviceUsage(
148 const std::string& label, int render_process_id, int render_view_id, 163 const std::string& label, int render_process_id, int render_view_id,
149 const StreamOptions& request_options, const GURL& security_origin) { 164 const StreamOptions& request_options, const GURL& security_origin) {
150 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 165 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
151 166 DCHECK(requests_.find(label) == requests_.end());
152 if (requests_.find(label) != requests_.end()) {
153 // Request with this id already exists.
154 requester_->SettingsError(label);
155 return;
156 }
157 167
158 // Create a new request. 168 // Create a new request.
159 requests_.insert(std::make_pair(label, new MediaStreamDeviceSettingsRequest( 169 requests_.insert(std::make_pair(label, new MediaStreamDeviceSettingsRequest(
160 render_process_id, render_view_id, security_origin, request_options))); 170 render_process_id, render_view_id, security_origin, request_options)));
161 } 171 }
162 172
163 void MediaStreamDeviceSettings::RemovePendingCaptureRequest( 173 void MediaStreamDeviceSettings::RemovePendingCaptureRequest(
164 const std::string& label) { 174 const std::string& label) {
165 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 175 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
166
167 SettingsRequests::iterator request_it = requests_.find(label); 176 SettingsRequests::iterator request_it = requests_.find(label);
168 if (request_it != requests_.end()) { 177 if (request_it != requests_.end()) {
169 // Proceed the next pending request for the same page.
170 MediaStreamDeviceSettingsRequest* request = request_it->second; 178 MediaStreamDeviceSettingsRequest* request = request_it->second;
171 std::string new_label = FindReadyRequestForView(request->render_view_id, 179 int render_view_id = request->render_view_id;
172 request->render_process_id); 180 int render_process_id = request->render_process_id;
173 if (!new_label.empty()) { 181 bool was_posted = request->posted_task;
174 PostRequestToUi(new_label);
175 }
176
177 // TODO(xians): Post a cancel request on UI thread to dismiss the infobar 182 // TODO(xians): Post a cancel request on UI thread to dismiss the infobar
178 // if request has been sent to the UI. 183 // if request has been sent to the UI.
179 // Remove the request from the queue. 184 // Remove the request from the queue.
180 requests_.erase(request_it); 185 requests_.erase(request_it);
181 delete request; 186 delete request;
187
188 // Simply return if the canceled request has not been brought to UI.
189 if (!was_posted)
190 return;
191
192 // Proceed the next pending request to replace the old infobar on the same
193 // page.
194 std::string new_label = FindReadyRequestForView(render_view_id,
195 render_process_id);
196 if (!new_label.empty()) {
197 if (use_fake_ui_)
198 PostRequestToFakeUI(new_label);
199 else
200 PostRequestToUi(new_label);
201 }
182 } 202 }
183 } 203 }
184 204
185 void MediaStreamDeviceSettings::AvailableDevices( 205 void MediaStreamDeviceSettings::AvailableDevices(
186 const std::string& label, 206 const std::string& label,
187 MediaStreamType stream_type, 207 MediaStreamType stream_type,
188 const StreamDeviceInfoArray& devices) { 208 const StreamDeviceInfoArray& devices) {
189 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 209 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
190 210
191 SettingsRequests::iterator request_it = requests_.find(label); 211 SettingsRequests::iterator request_it = requests_.find(label);
192 DCHECK(request_it != requests_.end()); 212 DCHECK(request_it != requests_.end());
193
194 // Add the answer for the request. 213 // Add the answer for the request.
195 MediaStreamDeviceSettingsRequest* request = request_it->second; 214 MediaStreamDeviceSettingsRequest* request = request_it->second;
196 DCHECK_EQ(request->devices_full.count(stream_type), static_cast<size_t>(0)) << 215 DCHECK_EQ(request->devices_full.count(stream_type), static_cast<size_t>(0))
197 "This request already has a list of devices for this stream type."; 216 << "This request already has a list of devices for this stream type.";
198 request->devices_full[stream_type] = devices; 217 request->devices_full[stream_type] = devices;
199 218
200 // Check if we're done. 219 if (IsRequestReadyForView(request)) {
201 size_t num_media_requests = 0; 220 if (use_fake_ui_) {
202 if (request->options.audio) { 221 PostRequestToFakeUI(label);
203 num_media_requests++; 222 return;
204 } 223 }
205 if (request->options.video) {
206 num_media_requests++;
207 }
208 224
209 if (request->devices_full.size() == num_media_requests) { 225 // Since the UI can only handle one request at the time, verify there
210 // We have all answers needed. 226 // is no unanswered request posted for this view. If there is, this
211 if (!use_fake_ui_) { 227 // new request will be handled once we get a response for the first one.
212 // Abort if the task was already posted: wait for it to PostResponse. 228 if (IsUiBusy(request->render_view_id, request->render_process_id))
213 if (request->posted_task) { 229 return;
wjia(left Chromium) 2012/08/12 16:05:45 No need to have "return" here and above. It can be
no longer working on chromium 2012/08/13 11:39:53 Done.
214 return;
215 }
216 // Since the UI can only handle one request at the time, verify there
217 // is no unanswered request posted for this view. If there is, this
218 // new request will be handled once we get a response for the first one.
219 if (IsUiBusy(request->render_view_id, request->render_process_id)) {
220 return;
221 }
222 PostRequestToUi(label);
223 } else {
224 // Used to fake UI, which is needed for server based testing.
225 // Choose first non-opened device for each media type.
226 StreamDeviceInfoArray devices_to_use;
227 for (DeviceMap::iterator it = request->devices_full.begin();
228 it != request->devices_full.end(); ++it) {
229 for (StreamDeviceInfoArray::iterator device_it = it->second.begin();
230 device_it != it->second.end(); ++device_it) {
231 if (!device_it->in_use) {
232 devices_to_use.push_back(*device_it);
233 break;
234 }
235 }
236 }
237 230
238 if (!request->devices_full[ 231 PostRequestToUi(label);
239 content::MEDIA_STREAM_DEVICE_TYPE_VIDEO_CAPTURE].empty() &&
240 num_media_requests != devices_to_use.size()) {
241 // Not all requested device types were opened. This happens if all
242 // video capture devices are already opened, |in_use| isn't set for
243 // audio devices. Allow the first video capture device in the list to be
244 // opened for this user too.
245 StreamDeviceInfoArray device_array =
246 request->devices_full[
247 content::MEDIA_STREAM_DEVICE_TYPE_VIDEO_CAPTURE];
248 devices_to_use.push_back(*(device_array.begin()));
249 }
250
251 // Post result and delete request.
252 requester_->DevicesAccepted(label, devices_to_use);
253 requests_.erase(request_it);
254 delete request;
255 }
256 } 232 }
257 } 233 }
258 234
259 void MediaStreamDeviceSettings::PostResponse( 235 void MediaStreamDeviceSettings::PostResponse(
260 const std::string& label, 236 const std::string& label,
261 const content::MediaStreamDevices& devices) { 237 const content::MediaStreamDevices& devices) {
262 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 238 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
263
264 SettingsRequests::iterator req = requests_.find(label); 239 SettingsRequests::iterator req = requests_.find(label);
265 // Return if the request has been removed. 240 // Return if the request has been removed.
266 if (req == requests_.end()) 241 if (req == requests_.end())
267 return; 242 return;
268 243
269 DCHECK(requester_); 244 DCHECK(requester_);
270 MediaStreamDeviceSettingsRequest* request = req->second; 245 scoped_ptr<MediaStreamDeviceSettingsRequest> request(req->second);
271 requests_.erase(req); 246 requests_.erase(req);
272 247
273 // Look for queued requests for the same view. If there is a pending request, 248 // Look for queued requests for the same view. If there is a pending request,
274 // post it for user approval. 249 // post it for user approval.
275 std::string new_label = FindReadyRequestForView(request->render_view_id, 250 std::string new_label = FindReadyRequestForView(request->render_view_id,
276 request->render_process_id); 251 request->render_process_id);
277 if (!new_label.empty()) { 252 if (!new_label.empty()) {
278 PostRequestToUi(new_label); 253 if (use_fake_ui_)
254 PostRequestToFakeUI(new_label);
255 else
256 PostRequestToUi(new_label);
279 } 257 }
wjia(left Chromium) 2012/08/12 16:05:45 This portion of code is same as in RemovePendingCa
no longer working on chromium 2012/08/13 11:39:53 Done.
280 258
281 if (devices.size() > 0) { 259 if (devices.size() > 0) {
282 // Build a list of "full" device objects for the accepted devices. 260 // Build a list of "full" device objects for the accepted devices.
283 StreamDeviceInfoArray deviceList; 261 StreamDeviceInfoArray deviceList;
284 for (content::MediaStreamDevices::const_iterator dev = devices.begin(); 262 for (content::MediaStreamDevices::const_iterator dev = devices.begin();
285 dev != devices.end(); ++dev) { 263 dev != devices.end(); ++dev) {
286 DeviceMap::iterator subList = request->devices_full.find(dev->type); 264 DeviceMap::iterator subList = request->devices_full.find(dev->type);
287 DCHECK(subList != request->devices_full.end()); 265 DCHECK(subList != request->devices_full.end());
288 266
289 deviceList.push_back(*std::find_if(subList->second.begin(), 267 deviceList.push_back(*std::find_if(subList->second.begin(),
290 subList->second.end(), DeviceIdEquals(dev->device_id))); 268 subList->second.end(), DeviceIdEquals(dev->device_id)));
291 } 269 }
292 requester_->DevicesAccepted(label, deviceList); 270 requester_->DevicesAccepted(label, deviceList);
293 } else { 271 } else {
294 requester_->SettingsError(label); 272 requester_->SettingsError(label);
295 } 273 }
296 delete request;
297 } 274 }
298 275
299 void MediaStreamDeviceSettings::UseFakeUI() { 276 void MediaStreamDeviceSettings::UseFakeUI() {
300 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 277 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
301 use_fake_ui_ = true; 278 use_fake_ui_ = true;
302 } 279 }
303 280
304 bool MediaStreamDeviceSettings::IsUiBusy(int render_view_id, 281 bool MediaStreamDeviceSettings::IsUiBusy(int render_view_id,
305 int render_process_id) { 282 int render_process_id) {
306 for (SettingsRequests::iterator it = requests_.begin(); 283 for (SettingsRequests::iterator it = requests_.begin();
307 it != requests_.end(); ++it) { 284 it != requests_.end(); ++it) {
308 if (it->second->render_process_id == render_process_id && 285 if (it->second->render_process_id == render_process_id &&
309 it->second->render_view_id == render_view_id && 286 it->second->render_view_id == render_view_id &&
310 it->second->posted_task) { 287 it->second->posted_task) {
311 return true; 288 return true;
312 } 289 }
313 } 290 }
314 return false; 291 return false;
315 } 292 }
316 293
317 std::string MediaStreamDeviceSettings::FindReadyRequestForView( 294 std::string MediaStreamDeviceSettings::FindReadyRequestForView(
318 int render_view_id, int render_process_id) { 295 int render_view_id, int render_process_id) {
319 for (SettingsRequests::iterator it = requests_.begin(); it != requests_.end(); 296 for (SettingsRequests::iterator it = requests_.begin(); it != requests_.end();
320 ++it) { 297 ++it) {
321 if (it->second->render_process_id == render_process_id && 298 if (it->second->render_process_id == render_process_id &&
322 it->second->render_view_id == render_view_id) { 299 it->second->render_view_id == render_view_id) {
323 // This request belongs to the given render view. 300 // This request belongs to the given render view.
324 MediaStreamDeviceSettingsRequest* request = it->second; 301 MediaStreamDeviceSettingsRequest* request = it->second;
325 if (request->options.audio && 302 if (IsRequestReadyForView(request))
326 request->devices_full[ 303 return it->first;
327 content::MEDIA_STREAM_DEVICE_TYPE_AUDIO_CAPTURE].empty()) {
328 // Audio requested, but no devices enumerated yet. Continue to next
329 // request.
330 continue;
331 }
332 if (request->options.video &&
333 request->devices_full[
334 content::MEDIA_STREAM_DEVICE_TYPE_VIDEO_CAPTURE].empty()) {
335 // Video requested, but no devices enumerated yet. Continue to next
336 // request.
337 continue;
338 }
339 // This request belongs to the same view as the treated request and is
340 // ready to be requested. Return its label.
341 return it->first;
342 } 304 }
343 } 305 }
344 return std::string(); 306 return std::string();
345 } 307 }
346 308
347 void MediaStreamDeviceSettings::PostRequestToUi(const std::string& label) { 309 void MediaStreamDeviceSettings::PostRequestToUi(const std::string& label) {
348 MediaStreamDeviceSettingsRequest* request = requests_[label]; 310 MediaStreamDeviceSettingsRequest* request = requests_[label];
349 DCHECK(request != NULL); 311 DCHECK(request != NULL);
350 312
351 request->posted_task = true; 313 request->posted_task = true;
(...skipping 13 matching lines...) Expand all
365 new ResponseCallbackHelper(weak_ptr_factory_.GetWeakPtr()); 327 new ResponseCallbackHelper(weak_ptr_factory_.GetWeakPtr());
366 content::MediaResponseCallback callback = 328 content::MediaResponseCallback callback =
367 base::Bind(&ResponseCallbackHelper::PostResponse, 329 base::Bind(&ResponseCallbackHelper::PostResponse,
368 helper.get(), label); 330 helper.get(), label);
369 331
370 BrowserThread::PostTask( 332 BrowserThread::PostTask(
371 BrowserThread::UI, FROM_HERE, 333 BrowserThread::UI, FROM_HERE,
372 base::Bind(&DoDeviceRequest, *request, callback)); 334 base::Bind(&DoDeviceRequest, *request, callback));
373 } 335 }
374 336
337 void MediaStreamDeviceSettings::PostRequestToFakeUI(const std::string& label) {
338 SettingsRequests::iterator request_it = requests_.find(label);
339 DCHECK(request_it != requests_.end());
340 MediaStreamDeviceSettingsRequest* request = request_it->second;
341 // Used to fake UI, which is needed for server based testing.
342 // Choose first non-opened device for each media type.
343 content::MediaStreamDevices devices_to_use;
344 for (DeviceMap::iterator it = request->devices_full.begin();
345 it != request->devices_full.end(); ++it) {
346 StreamDeviceInfoArray::iterator device_it = it->second.begin();
347 for (; device_it != it->second.end(); ++device_it) {
348 if (!device_it->in_use) {
349 devices_to_use.push_back(content::MediaStreamDevice(
350 device_it->stream_type, device_it->device_id, device_it->name));
351 break;
352 }
353 }
354
355 if (it->second.size() != 0 && device_it == it->second.end()) {
356 // Use the first capture device in the list if all the devices are
357 // being used.
358 devices_to_use.push_back(
359 content::MediaStreamDevice(it->second.begin()->stream_type,
360 it->second.begin()->device_id,
361 it->second.begin()->name));
362 }
363 }
364
365 BrowserThread::PostTask(
366 BrowserThread::IO, FROM_HERE,
367 base::Bind(&MediaStreamDeviceSettings::PostResponse,
368 weak_ptr_factory_.GetWeakPtr(), label, devices_to_use));
369 }
370
375 } // namespace media_stream 371 } // namespace media_stream
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698