Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / content / renderer / pepper / plugin_instance_throttler_impl.cc
blob2b836ccb1085da79624443dd8a7b7106addeecec
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 <cmath>
9 #include "base/metrics/histogram.h"
10 #include "base/time/time.h"
11 #include "content/public/common/content_constants.h"
12 #include "content/public/renderer/render_thread.h"
13 #include "content/renderer/pepper/pepper_plugin_instance_impl.h"
14 #include "content/renderer/render_frame_impl.h"
15 #include "ppapi/shared_impl/ppapi_constants.h"
16 #include "third_party/WebKit/public/platform/WebRect.h"
17 #include "third_party/WebKit/public/web/WebInputEvent.h"
18 #include "third_party/WebKit/public/web/WebLocalFrame.h"
19 #include "third_party/WebKit/public/web/WebPluginContainer.h"
20 #include "third_party/WebKit/public/web/WebPluginParams.h"
21 #include "third_party/WebKit/public/web/WebView.h"
22 #include "ui/gfx/color_utils.h"
23 #include "url/origin.h"
25 namespace content {
27 namespace {
29 // Cross-origin plugin content must have a width and height both exceeding
30 // these minimums to be considered "large", and thus not peripheral.
31 const int kLargeContentMinWidth = 398;
32 const int kLargeContentMinHeight = 298;
34 // Mark some 16:9 aspect ratio plugins as essential (not peripheral). This is to
35 // mark as "large" some medium sized video content that meets a minimum area
36 // requirement, even if it is below the max width/height above.
37 const double kEssentialVideoAspectRatio = 16.0 / 9.0;
38 const double kAspectRatioEpsilon = 0.01;
39 const int kEssentialVideoMinimumArea = 120000;
41 // Threshold for 'boring' score to accept a frame as good enough to be a
42 // representative keyframe. Units are the ratio of all pixels that are within
43 // the most common luma bin. The same threshold is used for history thumbnails.
44 const double kAcceptableFrameMaximumBoringness = 0.94;
46 // When plugin audio is throttled, the plugin will sometimes stop generating
47 // video frames. We use this timeout to prevent waiting forever for a good
48 // poster image. Chosen arbitrarily.
49 const int kAudioThrottledFrameTimeoutMilliseconds = 500;
51 } // namespace
53 // static
54 const int PluginInstanceThrottlerImpl::kMaximumFramesToExamine = 150;
56 // static
57 scoped_ptr<PluginInstanceThrottler> PluginInstanceThrottler::Create() {
58 return make_scoped_ptr(new PluginInstanceThrottlerImpl);
61 // static
62 void PluginInstanceThrottler::RecordUnthrottleMethodMetric(
63 PluginInstanceThrottlerImpl::PowerSaverUnthrottleMethod method) {
64 UMA_HISTOGRAM_ENUMERATION(
65 "Plugin.PowerSaver.Unthrottle", method,
66 PluginInstanceThrottler::UNTHROTTLE_METHOD_NUM_ITEMS);
69 // static
70 bool PluginInstanceThrottler::IsLargeContent(int width, int height) {
71 if (width >= kLargeContentMinWidth && height >= kLargeContentMinHeight)
72 return true;
74 double aspect_ratio = static_cast<double>(width) / height;
75 if (std::abs(aspect_ratio - kEssentialVideoAspectRatio) <
76 kAspectRatioEpsilon &&
77 width * height >= kEssentialVideoMinimumArea) {
78 return true;
81 return false;
84 PluginInstanceThrottlerImpl::PluginInstanceThrottlerImpl()
85 : state_(THROTTLER_STATE_AWAITING_KEYFRAME),
86 is_hidden_for_placeholder_(false),
87 web_plugin_(nullptr),
88 frames_examined_(0),
89 audio_throttled_(false),
90 audio_throttled_frame_timeout_(
91 FROM_HERE,
92 base::TimeDelta::FromMilliseconds(
93 kAudioThrottledFrameTimeoutMilliseconds),
94 this,
95 &PluginInstanceThrottlerImpl::EngageThrottle),
96 weak_factory_(this) {
99 PluginInstanceThrottlerImpl::~PluginInstanceThrottlerImpl() {
100 FOR_EACH_OBSERVER(Observer, observer_list_, OnThrottlerDestroyed());
101 if (state_ != THROTTLER_STATE_MARKED_ESSENTIAL)
102 RecordUnthrottleMethodMetric(UNTHROTTLE_METHOD_NEVER);
105 void PluginInstanceThrottlerImpl::AddObserver(Observer* observer) {
106 observer_list_.AddObserver(observer);
109 void PluginInstanceThrottlerImpl::RemoveObserver(Observer* observer) {
110 observer_list_.RemoveObserver(observer);
113 bool PluginInstanceThrottlerImpl::IsThrottled() const {
114 return state_ == THROTTLER_STATE_PLUGIN_THROTTLED;
117 bool PluginInstanceThrottlerImpl::IsHiddenForPlaceholder() const {
118 return is_hidden_for_placeholder_;
121 void PluginInstanceThrottlerImpl::MarkPluginEssential(
122 PowerSaverUnthrottleMethod method) {
123 if (state_ == THROTTLER_STATE_MARKED_ESSENTIAL)
124 return;
126 bool was_throttled = IsThrottled();
127 state_ = THROTTLER_STATE_MARKED_ESSENTIAL;
128 RecordUnthrottleMethodMetric(method);
130 FOR_EACH_OBSERVER(Observer, observer_list_, OnPeripheralStateChange());
132 if (was_throttled)
133 FOR_EACH_OBSERVER(Observer, observer_list_, OnThrottleStateChange());
136 void PluginInstanceThrottlerImpl::SetHiddenForPlaceholder(bool hidden) {
137 is_hidden_for_placeholder_ = hidden;
138 FOR_EACH_OBSERVER(Observer, observer_list_, OnHiddenForPlaceholder(hidden));
141 PepperWebPluginImpl* PluginInstanceThrottlerImpl::GetWebPlugin() const {
142 DCHECK(web_plugin_);
143 return web_plugin_;
146 const gfx::Size& PluginInstanceThrottlerImpl::GetSize() const {
147 return unobscured_size_;
150 void PluginInstanceThrottlerImpl::NotifyAudioThrottled() {
151 audio_throttled_ = true;
152 audio_throttled_frame_timeout_.Reset();
155 void PluginInstanceThrottlerImpl::SetWebPlugin(
156 PepperWebPluginImpl* web_plugin) {
157 DCHECK(!web_plugin_);
158 web_plugin_ = web_plugin;
161 void PluginInstanceThrottlerImpl::Initialize(
162 RenderFrameImpl* frame,
163 const url::Origin& content_origin,
164 const std::string& plugin_module_name,
165 const gfx::Size& unobscured_size) {
166 DCHECK(unobscured_size_.IsEmpty());
167 unobscured_size_ = unobscured_size;
169 // |frame| may be nullptr in tests.
170 if (frame) {
171 PluginPowerSaverHelper* helper = frame->plugin_power_saver_helper();
172 bool cross_origin_main_content = false;
173 float zoom_factor = GetWebPlugin()->container()->pageZoomFactor();
174 if (!helper->ShouldThrottleContent(
175 frame->GetWebFrame()->top()->securityOrigin(), content_origin,
176 plugin_module_name, roundf(unobscured_size.width() / zoom_factor),
177 roundf(unobscured_size.height() / zoom_factor),
178 &cross_origin_main_content)) {
179 DCHECK_NE(THROTTLER_STATE_MARKED_ESSENTIAL, state_);
180 state_ = THROTTLER_STATE_MARKED_ESSENTIAL;
181 FOR_EACH_OBSERVER(Observer, observer_list_, OnPeripheralStateChange());
183 if (cross_origin_main_content)
184 helper->WhitelistContentOrigin(content_origin);
186 return;
189 // To collect UMAs, register peripheral content even if power saver mode
190 // is disabled.
191 helper->RegisterPeripheralPlugin(
192 content_origin,
193 base::Bind(&PluginInstanceThrottlerImpl::MarkPluginEssential,
194 weak_factory_.GetWeakPtr(), UNTHROTTLE_METHOD_BY_WHITELIST));
198 void PluginInstanceThrottlerImpl::OnImageFlush(const SkBitmap* bitmap) {
199 DCHECK(needs_representative_keyframe());
200 if (!bitmap)
201 return;
203 ++frames_examined_;
205 // Does not make a copy, just takes a reference to the underlying pixel data.
206 last_received_frame_ = *bitmap;
208 if (audio_throttled_)
209 audio_throttled_frame_timeout_.Reset();
211 double boring_score = color_utils::CalculateBoringScore(*bitmap);
212 if (boring_score <= kAcceptableFrameMaximumBoringness ||
213 frames_examined_ >= kMaximumFramesToExamine) {
214 EngageThrottle();
218 bool PluginInstanceThrottlerImpl::ConsumeInputEvent(
219 const blink::WebInputEvent& event) {
220 // Always allow right-clicks through so users may verify it's a plugin.
221 // TODO(tommycli): We should instead show a custom context menu (probably
222 // using PluginPlaceholder) so users aren't confused and try to click the
223 // Flash-internal 'Play' menu item. This is a stopgap solution.
224 if (event.modifiers & blink::WebInputEvent::Modifiers::RightButtonDown)
225 return false;
227 if (state_ != THROTTLER_STATE_MARKED_ESSENTIAL &&
228 event.type == blink::WebInputEvent::MouseUp &&
229 (event.modifiers & blink::WebInputEvent::LeftButtonDown)) {
230 bool was_throttled = IsThrottled();
231 MarkPluginEssential(UNTHROTTLE_METHOD_BY_CLICK);
232 return was_throttled;
235 return IsThrottled();
238 void PluginInstanceThrottlerImpl::EngageThrottle() {
239 if (state_ != THROTTLER_STATE_AWAITING_KEYFRAME)
240 return;
242 if (!last_received_frame_.empty()) {
243 FOR_EACH_OBSERVER(Observer, observer_list_,
244 OnKeyframeExtracted(&last_received_frame_));
246 // Release our reference to the underlying pixel data.
247 last_received_frame_.reset();
250 state_ = THROTTLER_STATE_PLUGIN_THROTTLED;
251 FOR_EACH_OBSERVER(Observer, observer_list_, OnThrottleStateChange());
254 } // namespace content