Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / tabs / tab_utils.cc
blob09f63b0b3fffddb9dfa4390dde1e7146f1b4e37a
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.
5 #include "chrome/browser/ui/tabs/tab_utils.h"
7 #include "base/command_line.h"
8 #include "base/strings/string16.h"
9 #include "chrome/browser/media/media_capture_devices_dispatcher.h"
10 #include "chrome/browser/media/media_stream_capture_indicator.h"
11 #include "chrome/browser/themes/theme_properties.h"
12 #include "chrome/browser/ui/tabs/tab_strip_model.h"
13 #include "chrome/common/chrome_switches.h"
14 #include "chrome/grit/generated_resources.h"
15 #include "content/public/browser/web_contents.h"
16 #include "grit/theme_resources.h"
17 #include "ui/base/l10n/l10n_util.h"
18 #include "ui/base/resource/resource_bundle.h"
19 #include "ui/base/theme_provider.h"
20 #include "ui/gfx/animation/multi_animation.h"
21 #include "ui/gfx/vector_icons_public.h"
22 #include "ui/native_theme/common_theme.h"
23 #include "ui/native_theme/native_theme.h"
25 #if !defined(OS_MACOSX)
26 #include "ui/gfx/paint_vector_icon.h"
27 #endif
29 struct LastMuteMetadata
30 : public content::WebContentsUserData<LastMuteMetadata> {
31 TabMutedReason reason = TAB_MUTED_REASON_NONE;
32 std::string extension_id;
34 private:
35 explicit LastMuteMetadata(content::WebContents* contents) {}
36 friend class content::WebContentsUserData<LastMuteMetadata>;
39 DEFINE_WEB_CONTENTS_USER_DATA_KEY(LastMuteMetadata);
41 namespace chrome {
43 namespace {
45 // Interval between frame updates of the tab indicator animations. This is not
46 // the usual 60 FPS because a trade-off must be made between tab UI animation
47 // smoothness and media recording/playback performance on low-end hardware.
48 const int kIndicatorFrameIntervalMs = 50; // 20 FPS
50 // Fade-in/out duration for the tab indicator animations. Fade-in is quick to
51 // immediately notify the user. Fade-out is more gradual, so that the user has
52 // a chance of finding a tab that has quickly "blipped" on and off.
53 const int kIndicatorFadeInDurationMs = 200;
54 const int kIndicatorFadeOutDurationMs = 1000;
56 // Animation that throbs in (towards 1.0) and out (towards 0.0), and ends in the
57 // "in" state.
58 class TabRecordingIndicatorAnimation : public gfx::MultiAnimation {
59 public:
60 ~TabRecordingIndicatorAnimation() override {}
62 // Overridden to provide alternating "towards in" and "towards out" behavior.
63 double GetCurrentValue() const override;
65 static scoped_ptr<TabRecordingIndicatorAnimation> Create();
67 private:
68 TabRecordingIndicatorAnimation(const gfx::MultiAnimation::Parts& parts,
69 const base::TimeDelta interval)
70 : MultiAnimation(parts, interval) {}
72 // Number of times to "toggle throb" the recording and tab capture indicators
73 // when they first appear.
74 static const int kCaptureIndicatorThrobCycles = 5;
77 double TabRecordingIndicatorAnimation::GetCurrentValue() const {
78 return current_part_index() % 2 ?
79 1.0 - MultiAnimation::GetCurrentValue() :
80 MultiAnimation::GetCurrentValue();
83 scoped_ptr<TabRecordingIndicatorAnimation>
84 TabRecordingIndicatorAnimation::Create() {
85 MultiAnimation::Parts parts;
86 static_assert(kCaptureIndicatorThrobCycles % 2 != 0,
87 "odd number of cycles required so animation finishes in showing state");
88 for (int i = 0; i < kCaptureIndicatorThrobCycles; ++i) {
89 parts.push_back(MultiAnimation::Part(
90 i % 2 ? kIndicatorFadeOutDurationMs : kIndicatorFadeInDurationMs,
91 gfx::Tween::EASE_IN));
93 const base::TimeDelta interval =
94 base::TimeDelta::FromMilliseconds(kIndicatorFrameIntervalMs);
95 scoped_ptr<TabRecordingIndicatorAnimation> animation(
96 new TabRecordingIndicatorAnimation(parts, interval));
97 animation->set_continuous(false);
98 return animation.Pass();
101 } // namespace
103 bool ShouldTabShowFavicon(int capacity,
104 bool is_pinned_tab,
105 bool is_active_tab,
106 bool has_favicon,
107 TabMediaState media_state) {
108 if (!has_favicon)
109 return false;
110 int required_capacity = 1;
111 if (ShouldTabShowCloseButton(capacity, is_pinned_tab, is_active_tab))
112 ++required_capacity;
113 if (ShouldTabShowMediaIndicator(
114 capacity, is_pinned_tab, is_active_tab, has_favicon, media_state)) {
115 ++required_capacity;
117 return capacity >= required_capacity;
120 bool ShouldTabShowMediaIndicator(int capacity,
121 bool is_pinned_tab,
122 bool is_active_tab,
123 bool has_favicon,
124 TabMediaState media_state) {
125 if (media_state == TAB_MEDIA_STATE_NONE)
126 return false;
127 if (ShouldTabShowCloseButton(capacity, is_pinned_tab, is_active_tab))
128 return capacity >= 2;
129 return capacity >= 1;
132 bool ShouldTabShowCloseButton(int capacity,
133 bool is_pinned_tab,
134 bool is_active_tab) {
135 if (is_pinned_tab)
136 return false;
137 else if (is_active_tab)
138 return true;
139 else
140 return capacity >= 3;
143 TabMediaState GetTabMediaStateForContents(content::WebContents* contents) {
144 if (!contents)
145 return TAB_MEDIA_STATE_NONE;
147 scoped_refptr<MediaStreamCaptureIndicator> indicator =
148 MediaCaptureDevicesDispatcher::GetInstance()->
149 GetMediaStreamCaptureIndicator();
150 if (indicator.get()) {
151 if (indicator->IsBeingMirrored(contents))
152 return TAB_MEDIA_STATE_CAPTURING;
153 if (indicator->IsCapturingUserMedia(contents))
154 return TAB_MEDIA_STATE_RECORDING;
157 if (contents->IsAudioMuted())
158 return TAB_MEDIA_STATE_AUDIO_MUTING;
159 if (contents->WasRecentlyAudible())
160 return TAB_MEDIA_STATE_AUDIO_PLAYING;
162 return TAB_MEDIA_STATE_NONE;
165 gfx::Image GetTabMediaIndicatorImage(TabMediaState media_state,
166 const ui::ThemeProvider* tp) {
167 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
168 switch (media_state) {
169 #if !defined(OS_MACOSX)
170 case TAB_MEDIA_STATE_AUDIO_PLAYING:
171 case TAB_MEDIA_STATE_AUDIO_MUTING: {
172 SkColor icon_color = tp->GetColor(ThemeProperties::COLOR_TAB_ICON);
173 return gfx::Image(
174 gfx::CreateVectorIcon(media_state == TAB_MEDIA_STATE_AUDIO_PLAYING
175 ? gfx::VectorIconId::TAB_AUDIO
176 : gfx::VectorIconId::TAB_AUDIO_MUTING,
177 16, icon_color));
179 #else
180 case TAB_MEDIA_STATE_AUDIO_PLAYING:
181 return rb.GetNativeImageNamed(IDR_TAB_AUDIO_INDICATOR);
182 case TAB_MEDIA_STATE_AUDIO_MUTING:
183 return rb.GetNativeImageNamed(IDR_TAB_AUDIO_MUTING_INDICATOR);
184 #endif
185 case TAB_MEDIA_STATE_RECORDING:
186 return rb.GetNativeImageNamed(IDR_TAB_RECORDING_INDICATOR);
187 case TAB_MEDIA_STATE_CAPTURING:
188 return rb.GetNativeImageNamed(IDR_TAB_CAPTURE_INDICATOR);
189 case TAB_MEDIA_STATE_NONE:
190 break;
192 NOTREACHED();
193 return rb.GetNativeImageNamed(IDR_SAD_FAVICON);
196 gfx::Image GetTabMediaIndicatorAffordanceImage(TabMediaState media_state,
197 const ui::ThemeProvider* tp) {
198 switch (media_state) {
199 case TAB_MEDIA_STATE_AUDIO_PLAYING:
200 return GetTabMediaIndicatorImage(TAB_MEDIA_STATE_AUDIO_MUTING, tp);
201 case TAB_MEDIA_STATE_AUDIO_MUTING:
202 case TAB_MEDIA_STATE_NONE:
203 case TAB_MEDIA_STATE_RECORDING:
204 case TAB_MEDIA_STATE_CAPTURING:
205 return GetTabMediaIndicatorImage(media_state, tp);
207 NOTREACHED();
208 return GetTabMediaIndicatorImage(media_state, tp);
211 scoped_ptr<gfx::Animation> CreateTabMediaIndicatorFadeAnimation(
212 TabMediaState media_state) {
213 if (media_state == TAB_MEDIA_STATE_RECORDING ||
214 media_state == TAB_MEDIA_STATE_CAPTURING) {
215 return TabRecordingIndicatorAnimation::Create();
218 // Note: While it seems silly to use a one-part MultiAnimation, it's the only
219 // gfx::Animation implementation that lets us control the frame interval.
220 gfx::MultiAnimation::Parts parts;
221 const bool is_for_fade_in = (media_state != TAB_MEDIA_STATE_NONE);
222 parts.push_back(gfx::MultiAnimation::Part(
223 is_for_fade_in ? kIndicatorFadeInDurationMs : kIndicatorFadeOutDurationMs,
224 gfx::Tween::EASE_IN));
225 const base::TimeDelta interval =
226 base::TimeDelta::FromMilliseconds(kIndicatorFrameIntervalMs);
227 scoped_ptr<gfx::MultiAnimation> animation(
228 new gfx::MultiAnimation(parts, interval));
229 animation->set_continuous(false);
230 return animation.Pass();
233 base::string16 AssembleTabTooltipText(const base::string16& title,
234 TabMediaState media_state) {
235 if (media_state == TAB_MEDIA_STATE_NONE)
236 return title;
238 base::string16 result = title;
239 if (!result.empty())
240 result.append(1, '\n');
241 switch (media_state) {
242 case TAB_MEDIA_STATE_AUDIO_PLAYING:
243 result.append(
244 l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_MEDIA_STATE_AUDIO_PLAYING));
245 break;
246 case TAB_MEDIA_STATE_AUDIO_MUTING:
247 result.append(
248 l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_MEDIA_STATE_AUDIO_MUTING));
249 break;
250 case TAB_MEDIA_STATE_RECORDING:
251 result.append(
252 l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_MEDIA_STATE_RECORDING));
253 break;
254 case TAB_MEDIA_STATE_CAPTURING:
255 result.append(
256 l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_MEDIA_STATE_CAPTURING));
257 break;
258 case TAB_MEDIA_STATE_NONE:
259 NOTREACHED();
260 break;
262 return result;
265 bool AreExperimentalMuteControlsEnabled() {
266 return base::CommandLine::ForCurrentProcess()->HasSwitch(
267 switches::kEnableTabAudioMuting);
270 bool CanToggleAudioMute(content::WebContents* contents) {
271 switch (GetTabMediaStateForContents(contents)) {
272 case TAB_MEDIA_STATE_NONE:
273 case TAB_MEDIA_STATE_AUDIO_PLAYING:
274 case TAB_MEDIA_STATE_AUDIO_MUTING:
275 return true;
276 case TAB_MEDIA_STATE_RECORDING:
277 case TAB_MEDIA_STATE_CAPTURING:
278 return false;
280 NOTREACHED();
281 return false;
284 TabMutedReason GetTabAudioMutedReason(content::WebContents* contents) {
285 LastMuteMetadata::CreateForWebContents(contents); // Ensures metadata exists.
286 LastMuteMetadata* const metadata =
287 LastMuteMetadata::FromWebContents(contents);
288 if (GetTabMediaStateForContents(contents) == TAB_MEDIA_STATE_CAPTURING) {
289 // For tab capture, libcontent forces muting off.
290 metadata->reason = TAB_MUTED_REASON_MEDIA_CAPTURE;
291 metadata->extension_id.clear();
293 return metadata->reason;
296 const std::string& GetExtensionIdForMutedTab(content::WebContents* contents) {
297 DCHECK_EQ(GetTabAudioMutedReason(contents) != TAB_MUTED_REASON_EXTENSION,
298 LastMuteMetadata::FromWebContents(contents)->extension_id.empty());
299 return LastMuteMetadata::FromWebContents(contents)->extension_id;
302 TabMutedResult SetTabAudioMuted(content::WebContents* contents,
303 bool mute,
304 TabMutedReason reason,
305 const std::string& extension_id) {
306 DCHECK(contents);
307 DCHECK_NE(TAB_MUTED_REASON_NONE, reason);
309 if (reason == TAB_MUTED_REASON_AUDIO_INDICATOR &&
310 !AreExperimentalMuteControlsEnabled()) {
311 return TAB_MUTED_RESULT_FAIL_NOT_ENABLED;
314 if (!chrome::CanToggleAudioMute(contents))
315 return TAB_MUTED_RESULT_FAIL_TABCAPTURE;
317 contents->SetAudioMuted(mute);
319 LastMuteMetadata::CreateForWebContents(contents); // Ensures metadata exists.
320 LastMuteMetadata* const metadata =
321 LastMuteMetadata::FromWebContents(contents);
322 metadata->reason = reason;
323 if (reason == TAB_MUTED_REASON_EXTENSION) {
324 DCHECK(!extension_id.empty());
325 metadata->extension_id = extension_id;
326 } else {
327 metadata->extension_id.clear();
330 return TAB_MUTED_RESULT_SUCCESS;
333 void UnmuteIfMutedByExtension(content::WebContents* contents,
334 const std::string& extension_id) {
335 LastMuteMetadata::CreateForWebContents(contents); // Ensures metadata exists.
336 LastMuteMetadata* const metadata =
337 LastMuteMetadata::FromWebContents(contents);
338 if (metadata->reason == TAB_MUTED_REASON_EXTENSION &&
339 metadata->extension_id == extension_id) {
340 SetTabAudioMuted(contents, false, TAB_MUTED_REASON_EXTENSION, extension_id);
344 bool AreAllTabsMuted(const TabStripModel& tab_strip,
345 const std::vector<int>& indices) {
346 for (std::vector<int>::const_iterator i = indices.begin(); i != indices.end();
347 ++i) {
348 if (!tab_strip.GetWebContentsAt(*i)->IsAudioMuted())
349 return false;
351 return true;
354 } // namespace chrome