| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/tabs/tab_utils.h" |
| |
| #include <utility> |
| |
| #include "base/feature_list.h" |
| #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h" |
| #include "chrome/browser/media/webrtc/media_stream_capture_indicator.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/recently_audible_helper.h" |
| #include "chrome/browser/ui/tabs/tab_enums.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/browser/vr/vr_tab_helper.h" |
| #include "chrome/common/buildflags.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/content_settings/core/browser/host_content_settings_map.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/url_constants.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| #if BUILDFLAG(ENABLE_GLIC) |
| #include "chrome/browser/glic/glic_enabling.h" |
| #include "chrome/browser/glic/glic_keyed_service.h" |
| #include "chrome/browser/glic/glic_keyed_service_factory.h" |
| #endif |
| |
| std::vector<TabAlertState> GetTabAlertStatesForContents( |
| content::WebContents* contents) { |
| std::vector<TabAlertState> states; |
| if (!contents) { |
| return states; |
| } |
| |
| scoped_refptr<MediaStreamCaptureIndicator> indicator = |
| MediaCaptureDevicesDispatcher::GetInstance() |
| ->GetMediaStreamCaptureIndicator(); |
| if (indicator.get()) { |
| // Currently we only show the icon and tooltip of the highest-priority |
| // alert on a tab. |
| // TODO(crbug.com/40584226): To show the icon of the highest-priority alert |
| // with tooltip that notes all the states in play. |
| if (indicator->IsCapturingWindow(contents) || |
| indicator->IsCapturingDisplay(contents)) { |
| states.push_back(TabAlertState::DESKTOP_CAPTURING); |
| } |
| if (indicator->IsBeingMirrored(contents)) { |
| states.push_back(TabAlertState::TAB_CAPTURING); |
| } |
| |
| if (indicator->IsCapturingAudio(contents) && |
| indicator->IsCapturingVideo(contents)) { |
| states.push_back(TabAlertState::MEDIA_RECORDING); |
| } else if (indicator->IsCapturingAudio(contents)) { |
| states.push_back(TabAlertState::AUDIO_RECORDING); |
| } else if (indicator->IsCapturingVideo(contents)) { |
| states.push_back(TabAlertState::VIDEO_RECORDING); |
| } |
| } |
| |
| if (contents->IsCapabilityActive( |
| content::WebContents::CapabilityType::kBluetoothConnected)) { |
| states.push_back(TabAlertState::BLUETOOTH_CONNECTED); |
| } |
| |
| if (contents->IsCapabilityActive( |
| content::WebContents::CapabilityType::kBluetoothScanning)) { |
| states.push_back(TabAlertState::BLUETOOTH_SCAN_ACTIVE); |
| } |
| |
| if (contents->IsCapabilityActive( |
| content::WebContents::CapabilityType::kUSB)) { |
| states.push_back(TabAlertState::USB_CONNECTED); |
| } |
| |
| if (contents->IsCapabilityActive( |
| content::WebContents::CapabilityType::kHID)) { |
| states.push_back(TabAlertState::HID_CONNECTED); |
| } |
| |
| if (contents->IsCapabilityActive( |
| content::WebContents::CapabilityType::kSerial)) { |
| states.push_back(TabAlertState::SERIAL_CONNECTED); |
| } |
| |
| #if BUILDFLAG(ENABLE_GLIC) |
| if (Profile* const profile = |
| Profile::FromBrowserContext(contents->GetBrowserContext()); |
| GlicEnabling::IsProfileEligible(profile)) { |
| glic::GlicKeyedService* glic_service = |
| glic::GlicKeyedServiceFactory::GetGlicKeyedService(profile); |
| CHECK(glic_service); |
| if (glic_service->IsContextAccessIndicatorShown(contents)) { |
| states.push_back(TabAlertState::GLIC_ACCESSING); |
| } |
| } |
| #endif |
| |
| // Check if VR content is being presented in a headset. |
| // NOTE: This icon must take priority over the audio alert ones |
| // because most VR content has audio and its usage is implied by the VR |
| // icon. |
| if (vr::VrTabHelper::IsContentDisplayedInHeadset(contents)) { |
| states.push_back(TabAlertState::VR_PRESENTING_IN_HEADSET); |
| } |
| |
| if (contents->HasPictureInPictureVideo() || |
| contents->HasPictureInPictureDocument()) { |
| states.push_back(TabAlertState::PIP_PLAYING); |
| } |
| |
| // Only tabs have a RecentlyAudibleHelper, but this function is abused for |
| // some non-tab WebContents. In that case fall back to using the realtime |
| // notion of audibility. |
| bool audible = contents->IsCurrentlyAudible(); |
| auto* audible_helper = RecentlyAudibleHelper::FromWebContents(contents); |
| if (audible_helper) { |
| audible = audible_helper->WasRecentlyAudible(); |
| } |
| if (audible) { |
| if (contents->IsAudioMuted()) { |
| states.push_back(TabAlertState::AUDIO_MUTING); |
| } |
| states.push_back(TabAlertState::AUDIO_PLAYING); |
| } |
| |
| return states; |
| } |
| |
| std::u16string GetTabAlertStateText(const TabAlertState alert_state) { |
| switch (alert_state) { |
| case TabAlertState::AUDIO_PLAYING: |
| return l10n_util::GetStringUTF16( |
| IDS_TOOLTIP_TAB_ALERT_STATE_AUDIO_PLAYING); |
| case TabAlertState::AUDIO_MUTING: |
| return l10n_util::GetStringUTF16( |
| IDS_TOOLTIP_TAB_ALERT_STATE_AUDIO_MUTING); |
| case TabAlertState::MEDIA_RECORDING: |
| return l10n_util::GetStringUTF16( |
| IDS_TOOLTIP_TAB_ALERT_STATE_MEDIA_RECORDING); |
| case TabAlertState::AUDIO_RECORDING: |
| return l10n_util::GetStringUTF16( |
| IDS_TOOLTIP_TAB_ALERT_STATE_AUDIO_RECORDING); |
| case TabAlertState::VIDEO_RECORDING: |
| return l10n_util::GetStringUTF16( |
| IDS_TOOLTIP_TAB_ALERT_STATE_VIDEO_RECORDING); |
| case TabAlertState::TAB_CAPTURING: |
| return l10n_util::GetStringUTF16( |
| IDS_TOOLTIP_TAB_ALERT_STATE_TAB_CAPTURING); |
| case TabAlertState::BLUETOOTH_CONNECTED: |
| return l10n_util::GetStringUTF16( |
| IDS_TOOLTIP_TAB_ALERT_STATE_BLUETOOTH_CONNECTED); |
| case TabAlertState::BLUETOOTH_SCAN_ACTIVE: |
| return l10n_util::GetStringUTF16( |
| IDS_TOOLTIP_TAB_ALERT_STATE_BLUETOOTH_SCAN_ACTIVE); |
| case TabAlertState::USB_CONNECTED: |
| return l10n_util::GetStringUTF16( |
| IDS_TOOLTIP_TAB_ALERT_STATE_USB_CONNECTED); |
| case TabAlertState::HID_CONNECTED: |
| return l10n_util::GetStringUTF16( |
| IDS_TOOLTIP_TAB_ALERT_STATE_HID_CONNECTED); |
| case TabAlertState::SERIAL_CONNECTED: |
| return l10n_util::GetStringUTF16( |
| IDS_TOOLTIP_TAB_ALERT_STATE_SERIAL_CONNECTED); |
| case TabAlertState::PIP_PLAYING: |
| return l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_ALERT_STATE_PIP_PLAYING); |
| case TabAlertState::DESKTOP_CAPTURING: |
| return l10n_util::GetStringUTF16( |
| IDS_TOOLTIP_TAB_ALERT_STATE_DESKTOP_CAPTURING); |
| case TabAlertState::VR_PRESENTING_IN_HEADSET: |
| return l10n_util::GetStringUTF16( |
| IDS_TOOLTIP_TAB_ALERT_STATE_VR_PRESENTING); |
| case TabAlertState::GLIC_ACCESSING: |
| return l10n_util::GetStringUTF16( |
| IDS_TOOLTIP_TAB_ALERT_STATE_GLIC_ACCESSING); |
| } |
| NOTREACHED(); |
| } |
| |
| TabMutedReason GetTabAudioMutedReason(content::WebContents* contents) { |
| LastMuteMetadata::CreateForWebContents(contents); // Ensures metadata exists. |
| LastMuteMetadata* const metadata = |
| LastMuteMetadata::FromWebContents(contents); |
| return metadata->reason; |
| } |
| |
| bool SetTabAudioMuted(content::WebContents* contents, |
| bool mute, |
| TabMutedReason reason, |
| const std::string& extension_id) { |
| DCHECK(contents); |
| DCHECK(TabMutedReason::NONE != reason); |
| |
| contents->SetAudioMuted(mute); |
| |
| LastMuteMetadata::CreateForWebContents(contents); // Ensures metadata exists. |
| LastMuteMetadata* const metadata = |
| LastMuteMetadata::FromWebContents(contents); |
| metadata->reason = reason; |
| if (reason == TabMutedReason::EXTENSION) { |
| DCHECK(!extension_id.empty()); |
| metadata->extension_id = extension_id; |
| } else { |
| metadata->extension_id.clear(); |
| } |
| |
| return true; |
| } |
| |
| bool IsSiteMuted(const TabStripModel& tab_strip, const int index) { |
| content::WebContents* web_contents = tab_strip.GetWebContentsAt(index); |
| |
| // Prevent crashes with null WebContents (https://crbug.com/797647). |
| if (!web_contents) { |
| return false; |
| } |
| |
| GURL url = web_contents->GetLastCommittedURL(); |
| |
| // chrome:// URLs don't have content settings but can be muted, so just check |
| // the current muted state and TabMutedReason of the WebContents. |
| if (url.SchemeIs(content::kChromeUIScheme)) { |
| return web_contents->IsAudioMuted() && |
| GetTabAudioMutedReason(web_contents) == |
| TabMutedReason::CONTENT_SETTING_CHROME; |
| } |
| |
| Profile* profile = |
| Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
| HostContentSettingsMap* settings = |
| HostContentSettingsMapFactory::GetForProfile(profile); |
| return settings->GetContentSetting(url, url, ContentSettingsType::SOUND) == |
| CONTENT_SETTING_BLOCK; |
| } |
| |
| bool AreAllSitesMuted(const TabStripModel& tab_strip, |
| const std::vector<int>& indices) { |
| for (int tab_index : indices) { |
| if (!IsSiteMuted(tab_strip, tab_index)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| LastMuteMetadata::LastMuteMetadata(content::WebContents* contents) |
| : content::WebContentsUserData<LastMuteMetadata>(*contents) {} |
| |
| WEB_CONTENTS_USER_DATA_KEY_IMPL(LastMuteMetadata); |