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
3 // found in the LICENSE file.
4
5 #include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
6
7 #include <stddef.h>
8
9 #include <memory>
10 #include <string>
11 #include <utility>
12
13 #include "base/check_op.h"
14 #include "base/macros.h"
15 #include "base/notreached.h"
16 #include "build/build_config.h"
17 #include "chrome/app/chrome_command_ids.h"
18 #include "chrome/browser/browser_process.h"
19 #include "chrome/browser/content_settings/chrome_content_settings_utils.h"
20 #include "chrome/browser/status_icons/status_icon.h"
21 #include "chrome/browser/status_icons/status_tray.h"
22 #include "chrome/browser/tab_contents/tab_util.h"
23 #include "components/url_formatter/elide_url.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "content/public/browser/content_browser_client.h"
26 #include "content/public/browser/web_contents.h"
27 #include "content/public/browser/web_contents_delegate.h"
28 #include "content/public/browser/web_contents_observer.h"
29 #include "extensions/buildflags/buildflags.h"
30 #include "ui/gfx/image/image_skia.h"
31
32 #if !defined(OS_ANDROID)
33 #include "chrome/grit/chromium_strings.h"
34 #include "components/vector_icons/vector_icons.h"
35 #include "ui/base/l10n/l10n_util.h"
36 #include "ui/gfx/color_palette.h"
37 #include "ui/gfx/paint_vector_icon.h"
38 #endif
39
40 #if BUILDFLAG(ENABLE_EXTENSIONS)
41 #include "base/strings/utf_string_conversions.h"
42 #include "chrome/common/extensions/extension_constants.h"
43 #include "extensions/browser/extension_registry.h"
44 #include "extensions/common/extension.h"
45 #endif
46
47 #if defined(OS_CHROMEOS)
48 #include "chrome/browser/chromeos/policy/dlp/dlp_content_manager.h"
49 #endif
50
51 using content::BrowserThread;
52 using content::WebContents;
53
54 namespace {
55
56 #if BUILDFLAG(ENABLE_EXTENSIONS)
GetExtension(WebContents * web_contents)57 const extensions::Extension* GetExtension(WebContents* web_contents) {
58 DCHECK_CURRENTLY_ON(BrowserThread::UI);
59
60 if (!web_contents)
61 return nullptr;
62
63 extensions::ExtensionRegistry* registry =
64 extensions::ExtensionRegistry::Get(web_contents->GetBrowserContext());
65 return registry->enabled_extensions().GetExtensionOrAppByURL(
66 web_contents->GetURL());
67 }
68
69 #endif // BUILDFLAG(ENABLE_EXTENSIONS)
70
GetTitle(WebContents * web_contents)71 base::string16 GetTitle(WebContents* web_contents) {
72 DCHECK_CURRENTLY_ON(BrowserThread::UI);
73
74 if (!web_contents)
75 return base::string16();
76
77 #if BUILDFLAG(ENABLE_EXTENSIONS)
78 const extensions::Extension* const extension = GetExtension(web_contents);
79 if (extension)
80 return base::UTF8ToUTF16(extension->name());
81 #endif
82
83 return url_formatter::FormatUrlForSecurityDisplay(web_contents->GetURL());
84 }
85
86 // Returns if the passed |device| is capturing the whole display. This is
87 // different from capturing a tab or a single window on a desktop.
IsDeviceCapturingDisplay(const blink::MediaStreamDevice & device)88 bool IsDeviceCapturingDisplay(const blink::MediaStreamDevice& device) {
89 return device.display_media_info &&
90 device.display_media_info.value()->display_surface ==
91 media::mojom::DisplayCaptureSurfaceType::MONITOR;
92 }
93
94 typedef void (MediaStreamCaptureIndicator::Observer::*ObserverMethod)(
95 content::WebContents* web_contents,
96 bool value);
97
GetObserverMethodToCall(const blink::MediaStreamDevice & device)98 ObserverMethod GetObserverMethodToCall(const blink::MediaStreamDevice& device) {
99 switch (device.type) {
100 case blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE:
101 return &MediaStreamCaptureIndicator::Observer::OnIsCapturingAudioChanged;
102
103 case blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE:
104 return &MediaStreamCaptureIndicator::Observer::OnIsCapturingVideoChanged;
105
106 case blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE:
107 case blink::mojom::MediaStreamType::GUM_TAB_VIDEO_CAPTURE:
108 return &MediaStreamCaptureIndicator::Observer::OnIsBeingMirroredChanged;
109
110 case blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE:
111 case blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE:
112 case blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE:
113 case blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE:
114 case blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE_THIS_TAB:
115 return IsDeviceCapturingDisplay(device)
116 ? &MediaStreamCaptureIndicator::Observer::
117 OnIsCapturingDisplayChanged
118 : &MediaStreamCaptureIndicator::Observer::
119 OnIsCapturingWindowChanged;
120
121 case blink::mojom::MediaStreamType::NO_SERVICE:
122 case blink::mojom::MediaStreamType::NUM_MEDIA_TYPES:
123 NOTREACHED();
124 return nullptr;
125 }
126 }
127
128 } // namespace
129
130 // Stores usage counts for all the capture devices associated with a single
131 // WebContents instance. Instances of this class are owned by
132 // MediaStreamCaptureIndicator. They also observe for the destruction of their
133 // corresponding WebContents and trigger their own deletion from their
134 // MediaStreamCaptureIndicator.
135 class MediaStreamCaptureIndicator::WebContentsDeviceUsage
136 : public content::WebContentsObserver {
137 public:
WebContentsDeviceUsage(scoped_refptr<MediaStreamCaptureIndicator> indicator,WebContents * web_contents)138 WebContentsDeviceUsage(scoped_refptr<MediaStreamCaptureIndicator> indicator,
139 WebContents* web_contents)
140 : WebContentsObserver(web_contents), indicator_(std::move(indicator)) {}
141
IsCapturingAudio() const142 bool IsCapturingAudio() const { return audio_stream_count_ > 0; }
IsCapturingVideo() const143 bool IsCapturingVideo() const { return video_stream_count_ > 0; }
IsMirroring() const144 bool IsMirroring() const { return mirroring_stream_count_ > 0; }
IsCapturingWindow() const145 bool IsCapturingWindow() const { return window_stream_count_ > 0; }
IsCapturingDisplay() const146 bool IsCapturingDisplay() const { return display_stream_count_ > 0; }
147
148 std::unique_ptr<content::MediaStreamUI> RegisterMediaStream(
149 const blink::MediaStreamDevices& devices,
150 std::unique_ptr<MediaStreamUI> ui);
151
152 // Increment ref-counts up based on the type of each device provided.
153 void AddDevices(const blink::MediaStreamDevices& devices,
154 base::OnceClosure stop_callback);
155
156 // Decrement ref-counts up based on the type of each device provided.
157 void RemoveDevices(const blink::MediaStreamDevices& devices);
158
159 // Helper to call |stop_callback_|.
160 void NotifyStopped();
161
162 private:
163 int& GetStreamCount(const blink::MediaStreamDevice& device);
164
165 // content::WebContentsObserver overrides.
WebContentsDestroyed()166 void WebContentsDestroyed() override {
167 indicator_->UnregisterWebContents(web_contents());
168 }
169
170 scoped_refptr<MediaStreamCaptureIndicator> indicator_;
171 int audio_stream_count_ = 0;
172 int video_stream_count_ = 0;
173 int mirroring_stream_count_ = 0;
174 int window_stream_count_ = 0;
175 int display_stream_count_ = 0;
176
177 base::OnceClosure stop_callback_;
178 base::WeakPtrFactory<WebContentsDeviceUsage> weak_factory_{this};
179
180 DISALLOW_COPY_AND_ASSIGN(WebContentsDeviceUsage);
181 };
182
183 // Implements MediaStreamUI interface. Instances of this class are created for
184 // each MediaStream and their ownership is passed to MediaStream implementation
185 // in the content layer. Each UIDelegate keeps a weak pointer to the
186 // corresponding WebContentsDeviceUsage object to deliver updates about state of
187 // the stream.
188 class MediaStreamCaptureIndicator::UIDelegate : public content::MediaStreamUI {
189 public:
UIDelegate(base::WeakPtr<WebContentsDeviceUsage> device_usage,const blink::MediaStreamDevices & devices,std::unique_ptr<::MediaStreamUI> ui)190 UIDelegate(base::WeakPtr<WebContentsDeviceUsage> device_usage,
191 const blink::MediaStreamDevices& devices,
192 std::unique_ptr<::MediaStreamUI> ui)
193 : device_usage_(device_usage), devices_(devices), ui_(std::move(ui)) {
194 DCHECK(!devices_.empty());
195 }
196
~UIDelegate()197 ~UIDelegate() override {
198 if (started_ && device_usage_)
199 device_usage_->RemoveDevices(devices_);
200 }
201
202 private:
203 // content::MediaStreamUI interface.
OnStarted(base::OnceClosure stop_callback,content::MediaStreamUI::SourceCallback source_callback,const std::string & label,std::vector<content::DesktopMediaID> screen_capture_ids,StateChangeCallback state_change_callback)204 gfx::NativeViewId OnStarted(
205 base::OnceClosure stop_callback,
206 content::MediaStreamUI::SourceCallback source_callback,
207 const std::string& label,
208 std::vector<content::DesktopMediaID> screen_capture_ids,
209 StateChangeCallback state_change_callback) override {
210 if (started_) {
211 // Ignore possibly-compromised renderers that might call
212 // MediaStreamDispatcherHost::OnStreamStarted() more than once.
213 // See: https://crbug.com/1155426
214 return 0;
215 }
216 started_ = true;
217
218 if (device_usage_) {
219 // |device_usage_| handles |stop_callback| when |ui_| is unspecified.
220 device_usage_->AddDevices(
221 devices_, ui_ ? base::OnceClosure() : std::move(stop_callback));
222 }
223
224 #if defined(OS_CHROMEOS)
225 policy::DlpContentManager::Get()->OnScreenCaptureStarted(
226 label, screen_capture_ids, state_change_callback);
227 #endif
228
229 // If a custom |ui_| is specified, notify it that the stream started and let
230 // it handle the |stop_callback| and |source_callback|.
231 if (ui_)
232 return ui_->OnStarted(std::move(stop_callback),
233 std::move(source_callback));
234
235 return 0;
236 }
237
OnDeviceStopped(const std::string & label,const content::DesktopMediaID & media_id)238 void OnDeviceStopped(const std::string& label,
239 const content::DesktopMediaID& media_id) override {
240 #if defined(OS_CHROMEOS)
241 policy::DlpContentManager::Get()->OnScreenCaptureStopped(label, media_id);
242 #endif
243 }
244
SetStopCallback(base::OnceClosure stop_callback)245 void SetStopCallback(base::OnceClosure stop_callback) override {
246 if (ui_) {
247 ui_->SetStopCallback(std::move(stop_callback));
248 }
249 }
250
251 base::WeakPtr<WebContentsDeviceUsage> device_usage_;
252 const blink::MediaStreamDevices devices_;
253 const std::unique_ptr<::MediaStreamUI> ui_;
254 bool started_ = false;
255
256 DISALLOW_COPY_AND_ASSIGN(UIDelegate);
257 };
258
259 std::unique_ptr<content::MediaStreamUI>
RegisterMediaStream(const blink::MediaStreamDevices & devices,std::unique_ptr<MediaStreamUI> ui)260 MediaStreamCaptureIndicator::WebContentsDeviceUsage::RegisterMediaStream(
261 const blink::MediaStreamDevices& devices,
262 std::unique_ptr<MediaStreamUI> ui) {
263 return std::make_unique<UIDelegate>(weak_factory_.GetWeakPtr(), devices,
264 std::move(ui));
265 }
266
AddDevices(const blink::MediaStreamDevices & devices,base::OnceClosure stop_callback)267 void MediaStreamCaptureIndicator::WebContentsDeviceUsage::AddDevices(
268 const blink::MediaStreamDevices& devices,
269 base::OnceClosure stop_callback) {
270 for (const auto& device : devices) {
271 int& stream_count = GetStreamCount(device);
272 ++stream_count;
273
274 if (web_contents() && stream_count == 1) {
275 ObserverMethod obs_func = GetObserverMethodToCall(device);
276 DCHECK(obs_func);
277 for (Observer& obs : indicator_->observers_)
278 (obs.*obs_func)(web_contents(), true);
279 }
280 }
281
282 if (web_contents()) {
283 stop_callback_ = std::move(stop_callback);
284 web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
285 }
286
287 indicator_->UpdateNotificationUserInterface();
288 }
289
RemoveDevices(const blink::MediaStreamDevices & devices)290 void MediaStreamCaptureIndicator::WebContentsDeviceUsage::RemoveDevices(
291 const blink::MediaStreamDevices& devices) {
292 for (const auto& device : devices) {
293 int& stream_count = GetStreamCount(device);
294 --stream_count;
295 DCHECK_GE(stream_count, 0);
296
297 if (web_contents() && stream_count == 0) {
298 ObserverMethod obs_func = GetObserverMethodToCall(device);
299 DCHECK(obs_func);
300 for (Observer& obs : indicator_->observers_)
301 (obs.*obs_func)(web_contents(), false);
302 }
303 }
304
305 if (web_contents()) {
306 web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
307 content_settings::UpdateLocationBarUiForWebContents(web_contents());
308 }
309
310 indicator_->UpdateNotificationUserInterface();
311 }
312
NotifyStopped()313 void MediaStreamCaptureIndicator::WebContentsDeviceUsage::NotifyStopped() {
314 if (stop_callback_)
315 std::move(stop_callback_).Run();
316 }
317
GetStreamCount(const blink::MediaStreamDevice & device)318 int& MediaStreamCaptureIndicator::WebContentsDeviceUsage::GetStreamCount(
319 const blink::MediaStreamDevice& device) {
320 switch (device.type) {
321 case blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE:
322 return audio_stream_count_;
323
324 case blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE:
325 return video_stream_count_;
326
327 case blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE:
328 case blink::mojom::MediaStreamType::GUM_TAB_VIDEO_CAPTURE:
329 return mirroring_stream_count_;
330
331 case blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE:
332 case blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE:
333 case blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE:
334 case blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE:
335 case blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE_THIS_TAB:
336 return IsDeviceCapturingDisplay(device) ? display_stream_count_
337 : window_stream_count_;
338
339 case blink::mojom::MediaStreamType::NO_SERVICE:
340 case blink::mojom::MediaStreamType::NUM_MEDIA_TYPES:
341 NOTREACHED();
342 return video_stream_count_;
343 }
344 }
345
~Observer()346 MediaStreamCaptureIndicator::Observer::~Observer() {
347 DCHECK(!IsInObserverList());
348 }
349
MediaStreamCaptureIndicator()350 MediaStreamCaptureIndicator::MediaStreamCaptureIndicator() {}
351
~MediaStreamCaptureIndicator()352 MediaStreamCaptureIndicator::~MediaStreamCaptureIndicator() {
353 // The user is responsible for cleaning up by reporting the closure of any
354 // opened devices. However, there exists a race condition at shutdown: The UI
355 // thread may be stopped before CaptureDevicesClosed() posts the task to
356 // invoke DoDevicesClosedOnUIThread(). In this case, usage_map_ won't be
357 // empty like it should.
358 DCHECK(usage_map_.empty() ||
359 !BrowserThread::IsThreadInitialized(BrowserThread::UI));
360 }
361
362 std::unique_ptr<content::MediaStreamUI>
RegisterMediaStream(content::WebContents * web_contents,const blink::MediaStreamDevices & devices,std::unique_ptr<MediaStreamUI> ui)363 MediaStreamCaptureIndicator::RegisterMediaStream(
364 content::WebContents* web_contents,
365 const blink::MediaStreamDevices& devices,
366 std::unique_ptr<MediaStreamUI> ui) {
367 DCHECK(web_contents);
368 auto& usage = usage_map_[web_contents];
369 if (!usage)
370 usage = std::make_unique<WebContentsDeviceUsage>(this, web_contents);
371
372 return usage->RegisterMediaStream(devices, std::move(ui));
373 }
374
ExecuteCommand(int command_id,int event_flags)375 void MediaStreamCaptureIndicator::ExecuteCommand(int command_id,
376 int event_flags) {
377 DCHECK_CURRENTLY_ON(BrowserThread::UI);
378
379 const int index =
380 command_id - IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_FIRST;
381 DCHECK_LE(0, index);
382 DCHECK_GT(static_cast<int>(command_targets_.size()), index);
383 WebContents* web_contents = command_targets_[index];
384 if (base::Contains(usage_map_, web_contents))
385 web_contents->GetDelegate()->ActivateContents(web_contents);
386 }
387
CheckUsage(content::WebContents * web_contents,const WebContentsDeviceUsagePredicate & pred) const388 bool MediaStreamCaptureIndicator::CheckUsage(
389 content::WebContents* web_contents,
390 const WebContentsDeviceUsagePredicate& pred) const {
391 auto it = usage_map_.find(web_contents);
392 if (it != usage_map_.end() && pred.Run(it->second.get()))
393 return true;
394
395 for (auto* inner_contents : web_contents->GetInnerWebContents()) {
396 if (CheckUsage(inner_contents, pred))
397 return true;
398 }
399
400 return false;
401 }
402
IsCapturingUserMedia(content::WebContents * web_contents) const403 bool MediaStreamCaptureIndicator::IsCapturingUserMedia(
404 content::WebContents* web_contents) const {
405 DCHECK_CURRENTLY_ON(BrowserThread::UI);
406 return CheckUsage(
407 web_contents,
408 base::BindRepeating([](const WebContentsDeviceUsage* usage) {
409 return usage->IsCapturingAudio() || usage->IsCapturingVideo();
410 }));
411 }
412
IsCapturingVideo(content::WebContents * web_contents) const413 bool MediaStreamCaptureIndicator::IsCapturingVideo(
414 content::WebContents* web_contents) const {
415 DCHECK_CURRENTLY_ON(BrowserThread::UI);
416 return CheckUsage(
417 web_contents,
418 base::BindRepeating(&WebContentsDeviceUsage::IsCapturingVideo));
419 }
420
IsCapturingAudio(content::WebContents * web_contents) const421 bool MediaStreamCaptureIndicator::IsCapturingAudio(
422 content::WebContents* web_contents) const {
423 DCHECK_CURRENTLY_ON(BrowserThread::UI);
424 return CheckUsage(
425 web_contents,
426 base::BindRepeating(&WebContentsDeviceUsage::IsCapturingAudio));
427 }
428
IsBeingMirrored(content::WebContents * web_contents) const429 bool MediaStreamCaptureIndicator::IsBeingMirrored(
430 content::WebContents* web_contents) const {
431 DCHECK_CURRENTLY_ON(BrowserThread::UI);
432 return CheckUsage(web_contents,
433 base::BindRepeating(&WebContentsDeviceUsage::IsMirroring));
434 }
435
IsCapturingWindow(content::WebContents * web_contents) const436 bool MediaStreamCaptureIndicator::IsCapturingWindow(
437 content::WebContents* web_contents) const {
438 DCHECK_CURRENTLY_ON(BrowserThread::UI);
439 return CheckUsage(
440 web_contents,
441 base::BindRepeating(&WebContentsDeviceUsage::IsCapturingWindow));
442 }
443
IsCapturingDisplay(content::WebContents * web_contents) const444 bool MediaStreamCaptureIndicator::IsCapturingDisplay(
445 content::WebContents* web_contents) const {
446 DCHECK_CURRENTLY_ON(BrowserThread::UI);
447 return CheckUsage(
448 web_contents,
449 base::BindRepeating(&WebContentsDeviceUsage::IsCapturingDisplay));
450 }
451
NotifyStopped(content::WebContents * web_contents) const452 void MediaStreamCaptureIndicator::NotifyStopped(
453 content::WebContents* web_contents) const {
454 DCHECK_CURRENTLY_ON(BrowserThread::UI);
455
456 auto it = usage_map_.find(web_contents);
457 DCHECK(it != usage_map_.end());
458 it->second->NotifyStopped();
459
460 for (auto* inner_contents : web_contents->GetInnerWebContents())
461 NotifyStopped(inner_contents);
462 }
463
UnregisterWebContents(WebContents * web_contents)464 void MediaStreamCaptureIndicator::UnregisterWebContents(
465 WebContents* web_contents) {
466 if (IsCapturingVideo(web_contents)) {
467 for (Observer& observer : observers_)
468 observer.OnIsCapturingVideoChanged(web_contents, false);
469 }
470 if (IsCapturingAudio(web_contents)) {
471 for (Observer& observer : observers_)
472 observer.OnIsCapturingAudioChanged(web_contents, false);
473 }
474 if (IsBeingMirrored(web_contents)) {
475 for (Observer& observer : observers_)
476 observer.OnIsBeingMirroredChanged(web_contents, false);
477 }
478 if (IsCapturingWindow(web_contents)) {
479 for (Observer& observer : observers_)
480 observer.OnIsCapturingWindowChanged(web_contents, false);
481 }
482 if (IsCapturingDisplay(web_contents)) {
483 for (Observer& observer : observers_)
484 observer.OnIsCapturingDisplayChanged(web_contents, false);
485 }
486 usage_map_.erase(web_contents);
487 UpdateNotificationUserInterface();
488 }
489
MaybeCreateStatusTrayIcon(bool audio,bool video)490 void MediaStreamCaptureIndicator::MaybeCreateStatusTrayIcon(bool audio,
491 bool video) {
492 DCHECK_CURRENTLY_ON(BrowserThread::UI);
493
494 if (status_icon_)
495 return;
496
497 // If there is no browser process, we should not create the status tray.
498 if (!g_browser_process)
499 return;
500
501 StatusTray* status_tray = g_browser_process->status_tray();
502 if (!status_tray)
503 return;
504
505 gfx::ImageSkia image;
506 base::string16 tool_tip;
507 GetStatusTrayIconInfo(audio, video, &image, &tool_tip);
508 DCHECK(!image.isNull());
509 DCHECK(!tool_tip.empty());
510
511 status_icon_ = status_tray->CreateStatusIcon(
512 StatusTray::MEDIA_STREAM_CAPTURE_ICON, image, tool_tip);
513 }
514
MaybeDestroyStatusTrayIcon()515 void MediaStreamCaptureIndicator::MaybeDestroyStatusTrayIcon() {
516 DCHECK_CURRENTLY_ON(BrowserThread::UI);
517
518 if (!status_icon_)
519 return;
520
521 // If there is no browser process, we should not do anything.
522 if (!g_browser_process)
523 return;
524
525 StatusTray* status_tray = g_browser_process->status_tray();
526 if (status_tray != nullptr) {
527 status_tray->RemoveStatusIcon(status_icon_);
528 status_icon_ = nullptr;
529 }
530 }
531
UpdateNotificationUserInterface()532 void MediaStreamCaptureIndicator::UpdateNotificationUserInterface() {
533 DCHECK_CURRENTLY_ON(BrowserThread::UI);
534
535 std::unique_ptr<StatusIconMenuModel> menu(new StatusIconMenuModel(this));
536 bool audio = false;
537 bool video = false;
538 int command_id = IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_FIRST;
539 command_targets_.clear();
540
541 for (const auto& it : usage_map_) {
542 // Check if any audio and video devices have been used.
543 const WebContentsDeviceUsage& usage = *it.second;
544 if (!usage.IsCapturingAudio() && !usage.IsCapturingVideo())
545 continue;
546
547 WebContents* const web_contents = it.first;
548
549 // The audio/video icon is shown for extensions or on Android. For
550 // regular tabs on desktop, we show an indicator in the tab icon.
551 #if BUILDFLAG(ENABLE_EXTENSIONS)
552 const extensions::Extension* extension = GetExtension(web_contents);
553 if (!extension)
554 continue;
555 #endif
556
557 audio = audio || usage.IsCapturingAudio();
558 video = video || usage.IsCapturingVideo();
559
560 command_targets_.push_back(web_contents);
561 menu->AddItem(command_id, GetTitle(web_contents));
562
563 // If the menu item is not a label, enable it.
564 menu->SetCommandIdEnabled(command_id, command_id != IDC_MinimumLabelValue);
565
566 // If reaching the maximum number, no more item will be added to the menu.
567 if (command_id == IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_LAST)
568 break;
569 ++command_id;
570 }
571
572 if (command_targets_.empty()) {
573 MaybeDestroyStatusTrayIcon();
574 return;
575 }
576
577 // The icon will take the ownership of the passed context menu.
578 MaybeCreateStatusTrayIcon(audio, video);
579 if (status_icon_) {
580 status_icon_->SetContextMenu(std::move(menu));
581 }
582 }
583
GetStatusTrayIconInfo(bool audio,bool video,gfx::ImageSkia * image,base::string16 * tool_tip)584 void MediaStreamCaptureIndicator::GetStatusTrayIconInfo(
585 bool audio,
586 bool video,
587 gfx::ImageSkia* image,
588 base::string16* tool_tip) {
589 #if defined(OS_ANDROID)
590 NOTREACHED();
591 #else // !defined(OS_ANDROID)
592 DCHECK_CURRENTLY_ON(BrowserThread::UI);
593 DCHECK(audio || video);
594 DCHECK(image);
595 DCHECK(tool_tip);
596
597 int message_id = 0;
598 const gfx::VectorIcon* icon = nullptr;
599 if (audio && video) {
600 message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_AUDIO_AND_VIDEO;
601 icon = &vector_icons::kVideocamIcon;
602 } else if (audio && !video) {
603 message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_AUDIO_ONLY;
604 icon = &vector_icons::kMicIcon;
605 } else if (!audio && video) {
606 message_id = IDS_MEDIA_STREAM_STATUS_TRAY_TEXT_VIDEO_ONLY;
607 icon = &vector_icons::kVideocamIcon;
608 }
609
610 *tool_tip = l10n_util::GetStringUTF16(message_id);
611 *image = gfx::CreateVectorIcon(*icon, 16, gfx::kChromeIconGrey);
612 #endif // !defined(OS_ANDROID)
613 }
614