Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / content / renderer / pepper / plugin_instance_throttler_impl.cc
bloba79201cb56ccab90324a9ee5be93d81af476270a
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"
16 #include "url/gurl.h"
18 namespace content {
20 namespace {
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;
34 } // namespace
36 // static
37 const int PluginInstanceThrottlerImpl::kMaximumFramesToExamine = 150;
39 // static
40 scoped_ptr<PluginInstanceThrottler> PluginInstanceThrottler::Create() {
41 return make_scoped_ptr(new PluginInstanceThrottlerImpl);
44 // static
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),
55 web_plugin_(nullptr),
56 consecutive_interesting_frames_(0),
57 frames_examined_(0),
58 audio_throttled_(false),
59 audio_throttled_frame_timeout_(
60 FROM_HERE,
61 base::TimeDelta::FromMilliseconds(
62 kAudioThrottledFrameTimeoutMilliseconds),
63 this,
64 &PluginInstanceThrottlerImpl::EngageThrottle),
65 weak_factory_(this) {
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)
93 return;
95 bool was_throttled = IsThrottled();
96 state_ = THROTTLER_STATE_MARKED_ESSENTIAL;
97 RecordUnthrottleMethodMetric(method);
99 if (was_throttled)
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 {
109 DCHECK(web_plugin_);
110 return web_plugin_;
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.
135 if (frame) {
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);
147 return;
150 // To collect UMAs, register peripheral content even if power saver mode
151 // is disabled.
152 helper->RegisterPeripheralPlugin(
153 content_origin,
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());
161 if (!bitmap)
162 return;
164 ++frames_examined_;
166 double boring_score = color_utils::CalculateBoringScore(*bitmap);
167 if (boring_score <= kAcceptableFrameMaximumBoringness)
168 ++consecutive_interesting_frames_;
169 else
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) {
180 EngageThrottle();
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)
191 return false;
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)
206 return;
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