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

Side by Side Diff: chrome/browser/media/media_stream_capture_indicator.cc

Issue 12035046: Fix bug causing tab favicon media indicator to not turn off. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fix issue with browser shutdown race condition. Created 7 years, 10 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 "chrome/browser/media/media_stream_capture_indicator.h" 5 #include "chrome/browser/media/media_stream_capture_indicator.h"
6 6
7 #include "base/bind.h" 7 #include "base/bind.h"
8 #include "base/i18n/rtl.h" 8 #include "base/i18n/rtl.h"
9 #include "base/logging.h" 9 #include "base/logging.h"
10 #include "base/memory/scoped_ptr.h"
10 #include "base/utf_string_conversions.h" 11 #include "base/utf_string_conversions.h"
11 #include "chrome/app/chrome_command_ids.h" 12 #include "chrome/app/chrome_command_ids.h"
12 #include "chrome/browser/browser_process.h" 13 #include "chrome/browser/browser_process.h"
13 #include "chrome/browser/extensions/extension_service.h" 14 #include "chrome/browser/extensions/extension_service.h"
14 #include "chrome/browser/extensions/image_loader.h" 15 #include "chrome/browser/extensions/image_loader.h"
15 #include "chrome/browser/profiles/profile.h" 16 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/status_icons/status_icon.h" 17 #include "chrome/browser/status_icons/status_icon.h"
17 #include "chrome/browser/status_icons/status_tray.h" 18 #include "chrome/browser/status_icons/status_tray.h"
18 #include "chrome/browser/tab_contents/tab_util.h" 19 #include "chrome/browser/tab_contents/tab_util.h"
19 #include "content/public/browser/browser_thread.h" 20 #include "content/public/browser/browser_thread.h"
20 #include "content/public/browser/content_browser_client.h" 21 #include "content/public/browser/content_browser_client.h"
21 #include "content/public/browser/invalidate_type.h" 22 #include "content/public/browser/invalidate_type.h"
22 #include "content/public/browser/render_view_host.h"
23 #include "content/public/browser/web_contents.h" 23 #include "content/public/browser/web_contents.h"
24 #include "content/public/browser/web_contents_delegate.h" 24 #include "content/public/browser/web_contents_delegate.h"
25 #include "content/public/common/media_stream_request.h" 25 #include "content/public/browser/web_contents_observer.h"
26 #include "grit/chromium_strings.h" 26 #include "grit/chromium_strings.h"
27 #include "grit/generated_resources.h" 27 #include "grit/generated_resources.h"
28 #include "grit/theme_resources.h" 28 #include "grit/theme_resources.h"
29 #include "net/base/net_util.h" 29 #include "net/base/net_util.h"
30 #include "ui/base/l10n/l10n_util.h" 30 #include "ui/base/l10n/l10n_util.h"
31 #include "ui/base/resource/resource_bundle.h" 31 #include "ui/base/resource/resource_bundle.h"
32 #include "ui/gfx/image/image_skia.h"
32 33
33 using content::BrowserThread; 34 using content::BrowserThread;
34 using content::WebContents; 35 using content::WebContents;
35 36
36 namespace { 37 namespace {
37 38
38 const extensions::Extension* GetExtension(int render_process_id, 39 const extensions::Extension* GetExtension(WebContents* web_contents) {
39 int render_view_id) {
40 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 40 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
41 41
42 WebContents* web_contents = tab_util::GetWebContentsByID(
43 render_process_id, render_view_id);
44 if (!web_contents) 42 if (!web_contents)
45 return NULL; 43 return NULL;
46 44
47 Profile* profile = 45 Profile* profile =
48 Profile::FromBrowserContext(web_contents->GetBrowserContext()); 46 Profile::FromBrowserContext(web_contents->GetBrowserContext());
49 if (!profile) 47 if (!profile)
50 return NULL; 48 return NULL;
51 49
52 ExtensionService* extension_service = profile->GetExtensionService(); 50 ExtensionService* extension_service = profile->GetExtensionService();
53 if (!extension_service) 51 if (!extension_service)
54 return NULL; 52 return NULL;
55 53
56 return extension_service->extensions()->GetExtensionOrAppByURL( 54 return extension_service->extensions()->GetExtensionOrAppByURL(
57 ExtensionURLInfo(web_contents->GetURL())); 55 ExtensionURLInfo(web_contents->GetURL()));
58 } 56 }
59 57
60 // Gets the security originator of the tab. It returns a string with no '/' 58 // Gets the security originator of the tab. It returns a string with no '/'
61 // at the end to display in the UI. 59 // at the end to display in the UI.
62 string16 GetSecurityOrigin(int render_process_id, int render_view_id) { 60 string16 GetSecurityOrigin(WebContents* web_contents) {
63 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 61 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
64 WebContents* tab_content = tab_util::GetWebContentsByID( 62
65 render_process_id, render_view_id); 63 if (!web_contents)
66 if (!tab_content)
67 return string16(); 64 return string16();
68 65
69 std::string security_origin = tab_content->GetURL().GetOrigin().spec(); 66 std::string security_origin = web_contents->GetURL().GetOrigin().spec();
70 67
71 // Remove the last character if it is a '/'. 68 // Remove the last character if it is a '/'.
72 if (!security_origin.empty()) { 69 if (!security_origin.empty()) {
73 std::string::iterator it = security_origin.end() - 1; 70 std::string::iterator it = security_origin.end() - 1;
74 if (*it == '/') 71 if (*it == '/')
75 security_origin.erase(it); 72 security_origin.erase(it);
76 } 73 }
77 74
78 return UTF8ToUTF16(security_origin); 75 return UTF8ToUTF16(security_origin);
79 } 76 }
80 77
81 string16 GetTitle(int render_process_id, int render_view_id) { 78 string16 GetTitle(WebContents* web_contents) {
82 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 79 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
83 80
84 const extensions::Extension* extension = 81 if (!web_contents)
85 GetExtension(render_process_id, render_view_id); 82 return string16();
83
84 const extensions::Extension* const extension = GetExtension(web_contents);
86 if (extension) 85 if (extension)
87 return UTF8ToUTF16(extension->name()); 86 return UTF8ToUTF16(extension->name());
88 87
89 WebContents* tab_content = tab_util::GetWebContentsByID( 88 string16 tab_title = web_contents->GetTitle();
90 render_process_id, render_view_id);
91 if (!tab_content)
92 return string16();
93
94 string16 tab_title = tab_content->GetTitle();
95 89
96 if (tab_title.empty()) { 90 if (tab_title.empty()) {
97 // If the page's title is empty use its security originator. 91 // If the page's title is empty use its security originator.
98 tab_title = GetSecurityOrigin(render_process_id, render_view_id); 92 tab_title = GetSecurityOrigin(web_contents);
99 } else { 93 } else {
100 // If the page's title matches its URL, use its security originator. 94 // If the page's title matches its URL, use its security originator.
101 std::string languages = 95 std::string languages =
102 content::GetContentClient()->browser()->GetAcceptLangs( 96 content::GetContentClient()->browser()->GetAcceptLangs(
103 tab_content->GetBrowserContext()); 97 web_contents->GetBrowserContext());
104 if (tab_title == net::FormatUrl(tab_content->GetURL(), languages)) 98 if (tab_title == net::FormatUrl(web_contents->GetURL(), languages))
105 tab_title = GetSecurityOrigin(render_process_id, render_view_id); 99 tab_title = GetSecurityOrigin(web_contents);
106 } 100 }
107 101
108 return tab_title; 102 return tab_title;
109 } 103 }
110 104
111 } // namespace 105 } // namespace
112 106
113 MediaStreamCaptureIndicator::TabEquals::TabEquals(WebContents* web_contents, 107 // Stores usage counts for all the capture devices associated with a single
114 int render_process_id, 108 // WebContents instance, and observes for the destruction of the WebContents
115 int render_view_id) 109 // instance.
116 : web_contents_(web_contents), 110 class MediaStreamCaptureIndicator::WebContentsDeviceUsage
117 render_process_id_(render_process_id), 111 : protected content::WebContentsObserver {
118 render_view_id_(render_view_id) {} 112 public:
113 explicit WebContentsDeviceUsage(WebContents* web_contents);
119 114
120 bool MediaStreamCaptureIndicator::TabEquals::operator() ( 115 bool IsWebContentsDestroyed() const { return web_contents() == NULL; }
121 const MediaStreamCaptureIndicator::CaptureDeviceTab& tab) { 116
122 return (web_contents_ == tab.web_contents || 117 bool IsCapturingAudio() const { return audio_ref_count_ > 0; }
123 (render_process_id_ == tab.render_process_id && 118 bool IsCapturingVideo() const { return video_ref_count_ > 0; }
124 render_view_id_ == tab.render_view_id)); 119 bool IsMirroring() const { return mirroring_ref_count_ > 0; }
120
121 // Updates ref-counts up (|direction| is true) or down by one, based on the
122 // type of each device provided. In the increment-upward case, the return
123 // value is the message ID for the balloon body to show, or zero if the
124 // balloon should not be shown.
125 int TallyUsage(const content::MediaStreamDevices& devices, bool direction);
126
127 private:
128 int audio_ref_count_;
129 int video_ref_count_;
130 int mirroring_ref_count_;
131
132 DISALLOW_COPY_AND_ASSIGN(WebContentsDeviceUsage);
133 };
134
135 MediaStreamCaptureIndicator::WebContentsDeviceUsage::WebContentsDeviceUsage(
136 WebContents* web_contents)
137 : content::WebContentsObserver(web_contents),
138 audio_ref_count_(0), video_ref_count_(0), mirroring_ref_count_(0) {
139 }
140
141 int MediaStreamCaptureIndicator::WebContentsDeviceUsage::TallyUsage(
142 const content::MediaStreamDevices& devices, bool direction) {
143 const int inc_amount = direction ? +1 : -1;
144 bool incremented_audio_count = false;
145 bool incremented_video_count = false;
146 for (content::MediaStreamDevices::const_iterator it = devices.begin();
147 it != devices.end(); ++it) {
148 if (it->type == content::MEDIA_TAB_AUDIO_CAPTURE ||
149 it->type == content::MEDIA_TAB_VIDEO_CAPTURE) {
150 mirroring_ref_count_ += inc_amount;
151 } else if (content::IsAudioMediaType(it->type)) {
152 audio_ref_count_ += inc_amount;
153 incremented_audio_count = (inc_amount > 0);
154 } else if (content::IsVideoMediaType(it->type)) {
155 video_ref_count_ += inc_amount;
156 incremented_video_count = (inc_amount > 0);
157 } else {
158 NOTIMPLEMENTED();
159 }
160 }
161
162 DCHECK_LE(0, audio_ref_count_);
163 DCHECK_LE(0, video_ref_count_);
164 DCHECK_LE(0, mirroring_ref_count_);
165
166 if (incremented_audio_count && incremented_video_count)
167 return IDS_MEDIA_STREAM_STATUS_TRAY_BALLOON_BODY_AUDIO_AND_VIDEO;
168 else if (incremented_audio_count)
169 return IDS_MEDIA_STREAM_STATUS_TRAY_BALLOON_BODY_AUDIO_ONLY;
170 else if (incremented_video_count)
171 return IDS_MEDIA_STREAM_STATUS_TRAY_BALLOON_BODY_VIDEO_ONLY;
172 else
173 return 0;
125 } 174 }
126 175
127 MediaStreamCaptureIndicator::MediaStreamCaptureIndicator() 176 MediaStreamCaptureIndicator::MediaStreamCaptureIndicator()
128 : status_icon_(NULL), 177 : status_icon_(NULL),
129 mic_image_(NULL), 178 mic_image_(NULL),
130 camera_image_(NULL), 179 camera_image_(NULL),
131 balloon_image_(NULL), 180 balloon_image_(NULL),
132 should_show_balloon_(false) { 181 should_show_balloon_(false) {
133 } 182 }
134 183
135 MediaStreamCaptureIndicator::~MediaStreamCaptureIndicator() { 184 MediaStreamCaptureIndicator::~MediaStreamCaptureIndicator() {
136 // The user is responsible for cleaning up by closing all the opened devices. 185 // The user is responsible for cleaning up by reporting the closure of any
137 DCHECK(tabs_.empty()); 186 // opened devices. However, there exists a race condition at shutdown: The UI
187 // thread may be stopped before CaptureDevicesClosed() posts the task to
188 // invoke DoDevicesClosedOnUIThread(). In this case, usage_map_ won't be
189 // empty like it should.
190 DCHECK(usage_map_.empty() ||
191 !BrowserThread::IsMessageLoopValid(BrowserThread::UI));
192
193 // Free any WebContentsDeviceUsage objects left over.
194 for (UsageMap::const_iterator it = usage_map_.begin(); it != usage_map_.end();
195 ++it) {
196 delete it->second;
197 }
138 } 198 }
139 199
140 bool MediaStreamCaptureIndicator::IsCommandIdChecked( 200 bool MediaStreamCaptureIndicator::IsCommandIdChecked(
141 int command_id) const { 201 int command_id) const {
142 NOTIMPLEMENTED() << "There are no checked items in the MediaStream menu."; 202 NOTIMPLEMENTED() << "There are no checked items in the MediaStream menu.";
143 return false; 203 return false;
144 } 204 }
145 205
146 bool MediaStreamCaptureIndicator::IsCommandIdEnabled( 206 bool MediaStreamCaptureIndicator::IsCommandIdEnabled(
147 int command_id) const { 207 int command_id) const {
148 return command_id != IDC_MinimumLabelValue; 208 return command_id != IDC_MinimumLabelValue;
149 } 209 }
150 210
151 bool MediaStreamCaptureIndicator::GetAcceleratorForCommandId( 211 bool MediaStreamCaptureIndicator::GetAcceleratorForCommandId(
152 int command_id, ui::Accelerator* accelerator) { 212 int command_id, ui::Accelerator* accelerator) {
153 // No accelerators for status icon context menu. 213 // No accelerators for status icon context menu.
154 return false; 214 return false;
155 } 215 }
156 216
157 void MediaStreamCaptureIndicator::ExecuteCommand(int command_id) { 217 void MediaStreamCaptureIndicator::ExecuteCommand(int command_id) {
158 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 218 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
159 DCHECK(command_id >= IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_FIRST && 219
160 command_id <= IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_LAST); 220 const int index =
161 int index = command_id - IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_FIRST; 221 command_id - IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_FIRST;
162 WebContents* web_content = tab_util::GetWebContentsByID( 222 DCHECK_LE(0, index);
163 tabs_[index].render_process_id, tabs_[index].render_view_id); 223 DCHECK_GT(static_cast<int>(command_targets_.size()), index);
164 DCHECK(web_content); 224 WebContents* const web_contents = command_targets_[index];
165 if (!web_content) { 225 UsageMap::const_iterator it = usage_map_.find(web_contents);
166 NOTREACHED(); 226 if (it == usage_map_.end() || it->second->IsWebContentsDestroyed())
167 return; 227 return;
168 } 228 web_contents->GetDelegate()->ActivateContents(web_contents);
169
170 web_content->GetDelegate()->ActivateContents(web_content);
171 } 229 }
172 230
173 void MediaStreamCaptureIndicator::CaptureDevicesOpened( 231 void MediaStreamCaptureIndicator::CaptureDevicesOpened(
174 int render_process_id, 232 int render_process_id,
175 int render_view_id, 233 int render_view_id,
176 const content::MediaStreamDevices& devices) { 234 const content::MediaStreamDevices& devices) {
177 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 235 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
178 DCHECK(!devices.empty()); 236 DCHECK(!devices.empty());
179 237
180 BrowserThread::PostTask( 238 BrowserThread::PostTask(
(...skipping 14 matching lines...) Expand all
195 base::Bind(&MediaStreamCaptureIndicator::DoDevicesClosedOnUIThread, 253 base::Bind(&MediaStreamCaptureIndicator::DoDevicesClosedOnUIThread,
196 this, render_process_id, render_view_id, devices)); 254 this, render_process_id, render_view_id, devices));
197 } 255 }
198 256
199 void MediaStreamCaptureIndicator::DoDevicesOpenedOnUIThread( 257 void MediaStreamCaptureIndicator::DoDevicesOpenedOnUIThread(
200 int render_process_id, 258 int render_process_id,
201 int render_view_id, 259 int render_view_id,
202 const content::MediaStreamDevices& devices) { 260 const content::MediaStreamDevices& devices) {
203 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 261 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
204 262
205 CreateStatusTray(); 263 AddCaptureDevices(render_process_id, render_view_id, devices);
206
207 AddCaptureDeviceTab(render_process_id, render_view_id, devices);
208 } 264 }
209 265
210 void MediaStreamCaptureIndicator::DoDevicesClosedOnUIThread( 266 void MediaStreamCaptureIndicator::DoDevicesClosedOnUIThread(
211 int render_process_id, 267 int render_process_id,
212 int render_view_id, 268 int render_view_id,
213 const content::MediaStreamDevices& devices) { 269 const content::MediaStreamDevices& devices) {
214 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 270 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
215 271
216 RemoveCaptureDeviceTab(render_process_id, render_view_id, devices); 272 RemoveCaptureDevices(render_process_id, render_view_id, devices);
217 } 273 }
218 274
219 void MediaStreamCaptureIndicator::CreateStatusTray() { 275 void MediaStreamCaptureIndicator::MaybeCreateStatusTrayIcon() {
220 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 276 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
221 if (status_icon_) 277 if (status_icon_)
222 return; 278 return;
223 279
224 // If there is no browser process, we should not create the status tray. 280 // If there is no browser process, we should not create the status tray.
225 if (!g_browser_process) 281 if (!g_browser_process)
226 return; 282 return;
227 283
228 StatusTray* status_tray = g_browser_process->status_tray(); 284 StatusTray* status_tray = g_browser_process->status_tray();
229 if (!status_tray) 285 if (!status_tray)
(...skipping 17 matching lines...) Expand all
247 if (!balloon_image_) { 303 if (!balloon_image_) {
248 balloon_image_ = ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 304 balloon_image_ = ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
249 IDR_PRODUCT_LOGO_32); 305 IDR_PRODUCT_LOGO_32);
250 } 306 }
251 DCHECK(mic_image_); 307 DCHECK(mic_image_);
252 DCHECK(camera_image_); 308 DCHECK(camera_image_);
253 DCHECK(balloon_image_); 309 DCHECK(balloon_image_);
254 } 310 }
255 311
256 void MediaStreamCaptureIndicator::ShowBalloon( 312 void MediaStreamCaptureIndicator::ShowBalloon(
257 int render_process_id, 313 WebContents* web_contents, int balloon_body_message_id) {
258 int render_view_id,
259 bool audio,
260 bool video) {
261 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 314 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
262 DCHECK(audio || video); 315 DCHECK_NE(0, balloon_body_message_id);
263
264 int message_id = IDS_MEDIA_STREAM_STATUS_TRAY_BALLOON_BODY_AUDIO_AND_VIDEO;
265 if (audio && !video)
266 message_id = IDS_MEDIA_STREAM_STATUS_TRAY_BALLOON_BODY_AUDIO_ONLY;
267 else if (!audio && video)
268 message_id = IDS_MEDIA_STREAM_STATUS_TRAY_BALLOON_BODY_VIDEO_ONLY;
269 316
270 // Only show the balloon for extensions. 317 // Only show the balloon for extensions.
271 const extensions::Extension* extension = 318 const extensions::Extension* const extension = GetExtension(web_contents);
272 GetExtension(render_process_id, render_view_id);
273 if (!extension) { 319 if (!extension) {
274 DVLOG(1) << "Balloon is shown only for extensions"; 320 DVLOG(1) << "Balloon is shown only for extensions";
275 return; 321 return;
276 } 322 }
277 323
278 string16 message = 324 string16 message =
279 l10n_util::GetStringFUTF16(message_id, 325 l10n_util::GetStringFUTF16(balloon_body_message_id,
280 UTF8ToUTF16(extension->name())); 326 UTF8ToUTF16(extension->name()));
281
282 WebContents* web_contents = tab_util::GetWebContentsByID(
283 render_process_id, render_view_id);
284
285 Profile* profile = 327 Profile* profile =
286 Profile::FromBrowserContext(web_contents->GetBrowserContext()); 328 Profile::FromBrowserContext(web_contents->GetBrowserContext());
287 329
288 should_show_balloon_ = true; 330 should_show_balloon_ = true;
289 extensions::ImageLoader::Get(profile)->LoadImageAsync( 331 extensions::ImageLoader::Get(profile)->LoadImageAsync(
290 extension, 332 extension,
291 extension->GetIconResource(32, ExtensionIconSet::MATCH_BIGGER), 333 extension->GetIconResource(32, ExtensionIconSet::MATCH_BIGGER),
292 gfx::Size(32, 32), 334 gfx::Size(32, 32),
293 base::Bind(&MediaStreamCaptureIndicator::OnImageLoaded, 335 base::Bind(&MediaStreamCaptureIndicator::OnImageLoaded,
294 this, message)); 336 this, message));
295 } 337 }
296 338
297 void MediaStreamCaptureIndicator::OnImageLoaded( 339 void MediaStreamCaptureIndicator::OnImageLoaded(
298 const string16& message, 340 const string16& message,
299 const gfx::Image& image) { 341 const gfx::Image& image) {
300 if (!should_show_balloon_) 342 if (!should_show_balloon_ || !status_icon_)
301 return; 343 return;
302 344
303 const gfx::ImageSkia* image_skia = !image.IsEmpty() ? image.ToImageSkia() : 345 const gfx::ImageSkia* image_skia = !image.IsEmpty() ? image.ToImageSkia() :
304 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 346 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
305 IDR_APP_DEFAULT_ICON); 347 IDR_APP_DEFAULT_ICON);
306 status_icon_->DisplayBalloon(*image_skia, string16(), message); 348 status_icon_->DisplayBalloon(*image_skia, string16(), message);
307 } 349 }
308 350
309 void MediaStreamCaptureIndicator::Hide() { 351 void MediaStreamCaptureIndicator::MaybeDestroyStatusTrayIcon() {
310 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 352 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
311 353
312 // Make sure images that finish loading don't cause a balloon to be shown. 354 // Make sure images that finish loading don't cause a balloon to be shown.
313 should_show_balloon_ = false; 355 should_show_balloon_ = false;
314 356
315 if (!status_icon_) 357 if (!status_icon_)
316 return; 358 return;
317 359
318 // If there is no browser process, we should not do anything. 360 // If there is no browser process, we should not do anything.
319 if (!g_browser_process) 361 if (!g_browser_process)
320 return; 362 return;
321 363
322 StatusTray* status_tray = g_browser_process->status_tray(); 364 StatusTray* status_tray = g_browser_process->status_tray();
323 if (status_tray != NULL) { 365 if (status_tray != NULL) {
324 status_tray->RemoveStatusIcon(status_icon_); 366 status_tray->RemoveStatusIcon(status_icon_);
325 status_icon_ = NULL; 367 status_icon_ = NULL;
326 } 368 }
327 } 369 }
328 370
329 void MediaStreamCaptureIndicator::UpdateStatusTrayIconContextMenu() { 371 void MediaStreamCaptureIndicator::UpdateStatusTrayIconContextMenu() {
330 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 372 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
331 scoped_ptr<ui::SimpleMenuModel> menu(new ui::SimpleMenuModel(this)); 373 scoped_ptr<ui::SimpleMenuModel> menu(new ui::SimpleMenuModel(this));
332 374
333 bool audio = false; 375 bool audio = false;
334 bool video = false; 376 bool video = false;
335 int command_id = IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_FIRST; 377 int command_id = IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_FIRST;
336 for (CaptureDeviceTabs::iterator iter = tabs_.begin(); 378 command_targets_.clear();
337 iter != tabs_.end();) { 379 for (UsageMap::const_iterator iter = usage_map_.begin();
338 string16 tab_title = GetTitle(iter->render_process_id, 380 iter != usage_map_.end(); ++iter) {
339 iter->render_view_id); 381 // Check if any audio and video devices have been used.
340 if (tab_title.empty()) { 382 const WebContentsDeviceUsage& usage = *iter->second;
341 // Delete the entry since the tab has gone away. 383 if (usage.IsWebContentsDestroyed() ||
342 iter = tabs_.erase(iter); 384 (!usage.IsCapturingAudio() && !usage.IsCapturingVideo())) {
343 continue; 385 continue;
344 } 386 }
387 audio = audio || usage.IsCapturingAudio();
388 video = video || usage.IsCapturingVideo();
345 389
346 // Check if any audio and video devices have been used. 390 WebContents* const web_contents = iter->first;
347 audio = audio || iter->audio_ref_count > 0; 391 command_targets_.push_back(web_contents);
348 video = video || iter->video_ref_count > 0; 392 menu->AddItem(command_id, GetTitle(web_contents));
349
350 menu->AddItem(command_id, tab_title);
351 393
352 // If reaching the maximum number, no more item will be added to the menu. 394 // If reaching the maximum number, no more item will be added to the menu.
353 if (command_id == IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_LAST) 395 if (command_id == IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_LAST)
354 break; 396 break;
355
356 ++command_id; 397 ++command_id;
357 ++iter;
358 } 398 }
359 399
360 if (!audio && !video) { 400 if (command_targets_.empty()) {
361 Hide(); 401 MaybeDestroyStatusTrayIcon();
362 return; 402 return;
363 } 403 }
364 404
365 // The icon will take the ownership of the passed context menu. 405 // The icon will take the ownership of the passed context menu.
366 status_icon_->SetContextMenu(menu.release()); 406 MaybeCreateStatusTrayIcon();
367 UpdateStatusTrayIconDisplay(audio, video); 407 if (status_icon_) {
408 status_icon_->SetContextMenu(menu.release());
409 UpdateStatusTrayIconDisplay(audio, video);
410 }
368 } 411 }
369 412
370 void MediaStreamCaptureIndicator::UpdateStatusTrayIconDisplay( 413 void MediaStreamCaptureIndicator::UpdateStatusTrayIconDisplay(
371 bool audio, bool video) { 414 bool audio, bool video) {
372 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 415 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
373 DCHECK(audio || video); 416 DCHECK(audio || video);
417 DCHECK(status_icon_);
374 int message_id = 0; 418 int message_id = 0;
375 if (audio && video) { 419 if (audio && video) {
376 message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_AUDIO_AND_VIDEO; 420 message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_AUDIO_AND_VIDEO;
377 status_icon_->SetImage(*camera_image_); 421 status_icon_->SetImage(*camera_image_);
378 } else if (audio && !video) { 422 } else if (audio && !video) {
379 message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_AUDIO_ONLY; 423 message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_AUDIO_ONLY;
380 status_icon_->SetImage(*mic_image_); 424 status_icon_->SetImage(*mic_image_);
381 } else if (!audio && video) { 425 } else if (!audio && video) {
382 message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_VIDEO_ONLY; 426 message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_VIDEO_ONLY;
383 status_icon_->SetImage(*camera_image_); 427 status_icon_->SetImage(*camera_image_);
384 } 428 }
385 429
386 status_icon_->SetToolTip(l10n_util::GetStringFUTF16( 430 status_icon_->SetToolTip(l10n_util::GetStringFUTF16(
387 message_id, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME))); 431 message_id, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
388 } 432 }
389 433
390 void MediaStreamCaptureIndicator::AddCaptureDeviceTab( 434 WebContents* MediaStreamCaptureIndicator::LookUpByKnownAlias(
435 int render_process_id, int render_view_id) const {
436 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
437
438 WebContents* result =
439 tab_util::GetWebContentsByID(render_process_id, render_view_id);
440 if (!result) {
441 const RenderViewIDs key(render_process_id, render_view_id);
442 AliasMap::const_iterator it = aliases_.find(key);
443 if (it != aliases_.end())
444 result = it->second;
445 }
446 return result;
447 }
448
449 void MediaStreamCaptureIndicator::AddCaptureDevices(
391 int render_process_id, 450 int render_process_id,
392 int render_view_id, 451 int render_view_id,
393 const content::MediaStreamDevices& devices) { 452 const content::MediaStreamDevices& devices) {
394 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 453 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
395 454
396 WebContents* web_contents = tab_util::GetWebContentsByID(render_process_id, 455 WebContents* const web_contents =
397 render_view_id); 456 LookUpByKnownAlias(render_process_id, render_view_id);
398 if (!web_contents) 457 if (!web_contents)
399 return; 458 return;
400 459
401 CaptureDeviceTabs::iterator iter = std::find_if( 460 // Increase the usage ref-counts.
402 tabs_.begin(), tabs_.end(), TabEquals(web_contents, 461 WebContentsDeviceUsage*& usage = usage_map_[web_contents];
403 render_process_id, 462 if (!usage)
404 render_view_id)); 463 usage = new WebContentsDeviceUsage(web_contents);
405 if (iter == tabs_.end()) { 464 const int balloon_body_message_id = usage->TallyUsage(devices, true);
406 tabs_.push_back(CaptureDeviceTab(web_contents,
407 render_process_id,
408 render_view_id));
409 iter = tabs_.end() - 1;
410 }
411 465
412 bool audio = false; 466 // Keep track of the IDs as a known alias to the WebContents instance.
413 bool video = false; 467 const AliasMap::iterator insert_it = aliases_.insert(
414 bool tab_capture = false; 468 make_pair(RenderViewIDs(render_process_id, render_view_id),
415 content::MediaStreamDevices::const_iterator dev = devices.begin(); 469 web_contents)).first;
416 for (; dev != devices.end(); ++dev) { 470 DCHECK_EQ(web_contents, insert_it->second)
417 if (dev->type == content::MEDIA_TAB_AUDIO_CAPTURE || 471 << "BUG: IDs refer to two different WebContents instances.";
418 dev->type == content::MEDIA_TAB_VIDEO_CAPTURE) {
419 ++iter->tab_capture_ref_count;
420 tab_capture = true;
421 } else if (content::IsAudioMediaType(dev->type)) {
422 ++iter->audio_ref_count;
423 audio = true;
424 } else if (content::IsVideoMediaType(dev->type)) {
425 ++iter->video_ref_count;
426 video = true;
427 } else {
428 NOTIMPLEMENTED();
429 }
430 }
431 472
432 DCHECK(web_contents);
433 web_contents->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB); 473 web_contents->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
434 474
435 // Don't use desktop notifications for tab capture. We use a favicon
436 // glow notification instead.
437 if (!status_icon_ || tab_capture)
438 return;
439
440 UpdateStatusTrayIconContextMenu(); 475 UpdateStatusTrayIconContextMenu();
441 ShowBalloon(render_process_id, render_view_id, audio, video); 476 if (balloon_body_message_id)
477 ShowBalloon(web_contents, balloon_body_message_id);
442 } 478 }
443 479
444 void MediaStreamCaptureIndicator::RemoveCaptureDeviceTab( 480 void MediaStreamCaptureIndicator::RemoveCaptureDevices(
445 int render_process_id, 481 int render_process_id,
446 int render_view_id, 482 int render_view_id,
447 const content::MediaStreamDevices& devices) { 483 const content::MediaStreamDevices& devices) {
448 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 484 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
449 WebContents* web_contents = tab_util::GetWebContentsByID(render_process_id,
450 render_view_id);
451 CaptureDeviceTabs::iterator iter = std::find_if(
452 tabs_.begin(), tabs_.end(), TabEquals(web_contents,
453 render_process_id,
454 render_view_id));
455 485
456 if (iter != tabs_.end()) { 486 WebContents* const web_contents =
457 content::MediaStreamDevices::const_iterator dev = devices.begin(); 487 LookUpByKnownAlias(render_process_id, render_view_id);
458 for (; dev != devices.end(); ++dev) { 488 if (!web_contents)
459 if (dev->type == content::MEDIA_TAB_AUDIO_CAPTURE || 489 return;
460 dev->type == content::MEDIA_TAB_VIDEO_CAPTURE) {
461 --iter->tab_capture_ref_count;
462 } else if (content::IsAudioMediaType(dev->type)) {
463 --iter->audio_ref_count;
464 } else if (content::IsVideoMediaType(dev->type)) {
465 --iter->video_ref_count;
466 } else {
467 NOTIMPLEMENTED();
468 }
469 490
470 DCHECK_GE(iter->audio_ref_count, 0); 491 // Decrease the usage ref-counts.
471 DCHECK_GE(iter->video_ref_count, 0); 492 WebContentsDeviceUsage* const usage = usage_map_[web_contents];
472 } 493 DCHECK(usage);
494 usage->TallyUsage(devices, false);
473 495
474 // Remove the tab if all the devices have been closed. 496 if (!usage->IsWebContentsDestroyed())
475 if (iter->audio_ref_count == 0 && iter->video_ref_count == 0 &&
476 iter->tab_capture_ref_count == 0)
477 tabs_.erase(iter);
478 }
479
480 if (web_contents)
481 web_contents->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB); 497 web_contents->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
482 498
483 if (!status_icon_) 499 // Remove the usage and alias mappings if all the devices have been closed.
484 return; 500 if (!usage->IsCapturingAudio() && !usage->IsCapturingVideo() &&
501 !usage->IsMirroring()) {
502 for (AliasMap::iterator alias_it = aliases_.begin();
503 alias_it != aliases_.end(); ) {
504 if (alias_it->second == web_contents)
505 aliases_.erase(alias_it++);
506 else
507 ++alias_it;
508 }
509 delete usage;
510 usage_map_.erase(web_contents);
511 }
485 512
486 UpdateStatusTrayIconContextMenu(); 513 UpdateStatusTrayIconContextMenu();
487 } 514 }
488 515
489 bool MediaStreamCaptureIndicator::IsProcessCapturing(int render_process_id, 516 bool MediaStreamCaptureIndicator::IsCapturingUserMedia(
490 int render_view_id) const { 517 int render_process_id, int render_view_id) const {
491 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 518 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
492 WebContents* web_contents = tab_util::GetWebContentsByID(render_process_id, 519
493 render_view_id); 520 WebContents* const web_contents =
521 LookUpByKnownAlias(render_process_id, render_view_id);
494 if (!web_contents) 522 if (!web_contents)
495 return false; 523 return false;
496 524
497 CaptureDeviceTabs::const_iterator iter = std::find_if( 525 UsageMap::const_iterator it = usage_map_.find(web_contents);
498 tabs_.begin(), tabs_.end(), TabEquals(web_contents, 526 return (it != usage_map_.end() &&
499 render_process_id, 527 (it->second->IsCapturingAudio() || it->second->IsCapturingVideo()));
500 render_view_id));
501 if (iter == tabs_.end())
502 return false;
503 return (iter->audio_ref_count > 0 || iter->video_ref_count > 0);
504 } 528 }
505 529
506 bool MediaStreamCaptureIndicator::IsProcessCapturingTab( 530 bool MediaStreamCaptureIndicator::IsBeingMirrored(
507 int render_process_id, int render_view_id) const { 531 int render_process_id, int render_view_id) const {
508 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 532 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
509 WebContents* web_contents = tab_util::GetWebContentsByID(render_process_id, 533
510 render_view_id); 534 WebContents* const web_contents =
535 LookUpByKnownAlias(render_process_id, render_view_id);
511 if (!web_contents) 536 if (!web_contents)
512 return false; 537 return false;
513 538
514 CaptureDeviceTabs::const_iterator iter = std::find_if( 539 UsageMap::const_iterator it = usage_map_.find(web_contents);
515 tabs_.begin(), tabs_.end(), TabEquals(web_contents, 540 return it != usage_map_.end() && it->second->IsMirroring();
516 render_process_id,
517 render_view_id));
518 if (iter == tabs_.end())
519 return false;
520 return (iter->tab_capture_ref_count > 0);
521 } 541 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698