1 // Copyright 2014 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 "content/renderer/pepper/plugin_instance_throttler_impl.h"
7 #include "base/metrics/histogram.h"
8 #include "base/time/time.h"
9 #include "content/public/common/content_constants.h"
10 #include "content/public/renderer/render_thread.h"
11 #include "content/renderer/render_frame_impl.h"
12 #include "third_party/WebKit/public/platform/WebRect.h"
13 #include "third_party/WebKit/public/web/WebInputEvent.h"
14 #include "third_party/WebKit/public/web/WebPluginParams.h"
15 #include "ui/gfx/color_utils.h"
22 // Threshold for 'boring' score to accept a frame as good enough to be a
23 // representative keyframe. Units are the ratio of all pixels that are within
24 // the most common luma bin. The same threshold is used for history thumbnails.
25 const double kAcceptableFrameMaximumBoringness
= 0.94;
27 const int kMinimumConsecutiveInterestingFrames
= 4;
29 // When plugin audio is throttled, the plugin will sometimes stop generating
30 // video frames. We use this timeout to prevent waiting forever for a good
31 // poster image. Chosen arbitrarily.
32 const int kAudioThrottledFrameTimeoutMilliseconds
= 500;
37 const int PluginInstanceThrottlerImpl::kMaximumFramesToExamine
= 150;
40 scoped_ptr
<PluginInstanceThrottler
> PluginInstanceThrottler::Create() {
41 return make_scoped_ptr(new PluginInstanceThrottlerImpl
);
45 void PluginInstanceThrottler::RecordUnthrottleMethodMetric(
46 PluginInstanceThrottlerImpl::PowerSaverUnthrottleMethod method
) {
47 UMA_HISTOGRAM_ENUMERATION(
48 "Plugin.PowerSaver.Unthrottle", method
,
49 PluginInstanceThrottler::UNTHROTTLE_METHOD_NUM_ITEMS
);
52 PluginInstanceThrottlerImpl::PluginInstanceThrottlerImpl()
53 : state_(THROTTLER_STATE_AWAITING_KEYFRAME
),
54 is_hidden_for_placeholder_(false),
56 consecutive_interesting_frames_(0),
58 audio_throttled_(false),
59 audio_throttled_frame_timeout_(
61 base::TimeDelta::FromMilliseconds(
62 kAudioThrottledFrameTimeoutMilliseconds
),
64 &PluginInstanceThrottlerImpl::EngageThrottle
),
68 PluginInstanceThrottlerImpl::~PluginInstanceThrottlerImpl() {
69 FOR_EACH_OBSERVER(Observer
, observer_list_
, OnThrottlerDestroyed());
70 if (state_
!= THROTTLER_STATE_MARKED_ESSENTIAL
)
71 RecordUnthrottleMethodMetric(UNTHROTTLE_METHOD_NEVER
);
74 void PluginInstanceThrottlerImpl::AddObserver(Observer
* observer
) {
75 observer_list_
.AddObserver(observer
);
78 void PluginInstanceThrottlerImpl::RemoveObserver(Observer
* observer
) {
79 observer_list_
.RemoveObserver(observer
);
82 bool PluginInstanceThrottlerImpl::IsThrottled() const {
83 return state_
== THROTTLER_STATE_PLUGIN_THROTTLED
;
86 bool PluginInstanceThrottlerImpl::IsHiddenForPlaceholder() const {
87 return is_hidden_for_placeholder_
;
90 void PluginInstanceThrottlerImpl::MarkPluginEssential(
91 PowerSaverUnthrottleMethod method
) {
92 if (state_
== THROTTLER_STATE_MARKED_ESSENTIAL
)
95 bool was_throttled
= IsThrottled();
96 state_
= THROTTLER_STATE_MARKED_ESSENTIAL
;
97 RecordUnthrottleMethodMetric(method
);
100 FOR_EACH_OBSERVER(Observer
, observer_list_
, OnThrottleStateChange());
103 void PluginInstanceThrottlerImpl::SetHiddenForPlaceholder(bool hidden
) {
104 is_hidden_for_placeholder_
= hidden
;
105 FOR_EACH_OBSERVER(Observer
, observer_list_
, OnHiddenForPlaceholder(hidden
));
108 blink::WebPlugin
* PluginInstanceThrottlerImpl::GetWebPlugin() const {
113 const gfx::Size
& PluginInstanceThrottlerImpl::GetSize() const {
114 return unobscured_size_
;
117 void PluginInstanceThrottlerImpl::NotifyAudioThrottled() {
118 audio_throttled_
= true;
119 audio_throttled_frame_timeout_
.Reset();
122 void PluginInstanceThrottlerImpl::SetWebPlugin(blink::WebPlugin
* web_plugin
) {
123 DCHECK(!web_plugin_
);
124 web_plugin_
= web_plugin
;
127 void PluginInstanceThrottlerImpl::Initialize(
128 RenderFrameImpl
* frame
,
129 const GURL
& content_origin
,
130 const std::string
& plugin_module_name
,
131 const gfx::Size
& unobscured_size
) {
132 unobscured_size_
= unobscured_size
;
134 // |frame| may be nullptr in tests.
136 PluginPowerSaverHelper
* helper
= frame
->plugin_power_saver_helper();
137 bool cross_origin_main_content
= false;
138 if (!helper
->ShouldThrottleContent(content_origin
, plugin_module_name
,
139 unobscured_size
.width(),
140 unobscured_size
.height(),
141 &cross_origin_main_content
)) {
142 state_
= THROTTLER_STATE_MARKED_ESSENTIAL
;
144 if (cross_origin_main_content
)
145 helper
->WhitelistContentOrigin(content_origin
);
150 // To collect UMAs, register peripheral content even if power saver mode
152 helper
->RegisterPeripheralPlugin(
154 base::Bind(&PluginInstanceThrottlerImpl::MarkPluginEssential
,
155 weak_factory_
.GetWeakPtr(), UNTHROTTLE_METHOD_BY_WHITELIST
));
159 void PluginInstanceThrottlerImpl::OnImageFlush(const SkBitmap
* bitmap
) {
160 DCHECK(needs_representative_keyframe());
166 double boring_score
= color_utils::CalculateBoringScore(*bitmap
);
167 if (boring_score
<= kAcceptableFrameMaximumBoringness
)
168 ++consecutive_interesting_frames_
;
170 consecutive_interesting_frames_
= 0;
172 // Does not make a copy, just takes a reference to the underlying pixel data.
173 last_received_frame_
= *bitmap
;
175 if (audio_throttled_
)
176 audio_throttled_frame_timeout_
.Reset();
178 if (frames_examined_
>= kMaximumFramesToExamine
||
179 consecutive_interesting_frames_
>= kMinimumConsecutiveInterestingFrames
) {
184 bool PluginInstanceThrottlerImpl::ConsumeInputEvent(
185 const blink::WebInputEvent
& event
) {
186 // Always allow right-clicks through so users may verify it's a plugin.
187 // TODO(tommycli): We should instead show a custom context menu (probably
188 // using PluginPlaceholder) so users aren't confused and try to click the
189 // Flash-internal 'Play' menu item. This is a stopgap solution.
190 if (event
.modifiers
& blink::WebInputEvent::Modifiers::RightButtonDown
)
193 if (state_
!= THROTTLER_STATE_MARKED_ESSENTIAL
&&
194 event
.type
== blink::WebInputEvent::MouseUp
&&
195 (event
.modifiers
& blink::WebInputEvent::LeftButtonDown
)) {
196 bool was_throttled
= IsThrottled();
197 MarkPluginEssential(UNTHROTTLE_METHOD_BY_CLICK
);
198 return was_throttled
;
201 return IsThrottled();
204 void PluginInstanceThrottlerImpl::EngageThrottle() {
205 if (state_
!= THROTTLER_STATE_AWAITING_KEYFRAME
)
208 if (!last_received_frame_
.empty()) {
209 FOR_EACH_OBSERVER(Observer
, observer_list_
,
210 OnKeyframeExtracted(&last_received_frame_
));
212 // Release our reference to the underlying pixel data.
213 last_received_frame_
.reset();
216 state_
= THROTTLER_STATE_PLUGIN_THROTTLED
;
217 FOR_EACH_OBSERVER(Observer
, observer_list_
, OnThrottleStateChange());
220 } // namespace content