blob: 5a153b79b095503b2c05873c489bcaad1a5fcc3d [file] [log] [blame]
tommyclie8722702015-01-16 11:40:411// 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.
4
5#include "content/renderer/pepper/plugin_instance_throttler_impl.h"
6
7#include "base/metrics/histogram.h"
8#include "base/time/time.h"
9#include "content/public/common/content_constants.h"
tommyclie8722702015-01-16 11:40:4110#include "content/public/renderer/render_thread.h"
tommycli0dd13012015-04-24 20:27:2211#include "content/renderer/pepper/pepper_plugin_instance_impl.h"
tommyclid7798e12015-02-09 21:08:5612#include "content/renderer/render_frame_impl.h"
tommycli0dd13012015-04-24 20:27:2213#include "ppapi/shared_impl/ppapi_constants.h"
tommyclid7798e12015-02-09 21:08:5614#include "third_party/WebKit/public/platform/WebRect.h"
tommyclie8722702015-01-16 11:40:4115#include "third_party/WebKit/public/web/WebInputEvent.h"
tommycli58e3172c2015-09-15 18:18:2616#include "third_party/WebKit/public/web/WebLocalFrame.h"
tommycli73d390b2015-08-04 23:00:5317#include "third_party/WebKit/public/web/WebPluginContainer.h"
tommyclid7798e12015-02-09 21:08:5618#include "third_party/WebKit/public/web/WebPluginParams.h"
tommycli58e3172c2015-09-15 18:18:2619#include "third_party/WebKit/public/web/WebView.h"
tommyclie8722702015-01-16 11:40:4120#include "ui/gfx/color_utils.h"
tommycli58e3172c2015-09-15 18:18:2621#include "url/origin.h"
tommyclie8722702015-01-16 11:40:4122
23namespace content {
24
25namespace {
26
tommyclie8722702015-01-16 11:40:4127// Threshold for 'boring' score to accept a frame as good enough to be a
28// representative keyframe. Units are the ratio of all pixels that are within
29// the most common luma bin. The same threshold is used for history thumbnails.
30const double kAcceptableFrameMaximumBoringness = 0.94;
31
tommycli43d8cc42015-03-20 17:48:1932// When plugin audio is throttled, the plugin will sometimes stop generating
33// video frames. We use this timeout to prevent waiting forever for a good
34// poster image. Chosen arbitrarily.
35const int kAudioThrottledFrameTimeoutMilliseconds = 500;
36
tommyclie8722702015-01-16 11:40:4137} // namespace
38
tommycli09d498192015-01-23 19:29:3839// static
tommyclid7798e12015-02-09 21:08:5640const int PluginInstanceThrottlerImpl::kMaximumFramesToExamine = 150;
tommycli09d498192015-01-23 19:29:3841
tommyclid7798e12015-02-09 21:08:5642// static
tommyclidb0458c2015-03-26 20:38:4343scoped_ptr<PluginInstanceThrottler> PluginInstanceThrottler::Create() {
44 return make_scoped_ptr(new PluginInstanceThrottlerImpl);
tommycli09d498192015-01-23 19:29:3845}
46
47// static
48void PluginInstanceThrottler::RecordUnthrottleMethodMetric(
49 PluginInstanceThrottlerImpl::PowerSaverUnthrottleMethod method) {
50 UMA_HISTOGRAM_ENUMERATION(
51 "Plugin.PowerSaver.Unthrottle", method,
52 PluginInstanceThrottler::UNTHROTTLE_METHOD_NUM_ITEMS);
53}
54
tommyclidb0458c2015-03-26 20:38:4355PluginInstanceThrottlerImpl::PluginInstanceThrottlerImpl()
56 : state_(THROTTLER_STATE_AWAITING_KEYFRAME),
tommyclibde09712015-02-05 02:38:5957 is_hidden_for_placeholder_(false),
tommycli260ce722015-02-24 18:35:2158 web_plugin_(nullptr),
tommyclid7798e12015-02-09 21:08:5659 frames_examined_(0),
tommycli43d8cc42015-03-20 17:48:1960 audio_throttled_(false),
61 audio_throttled_frame_timeout_(
62 FROM_HERE,
63 base::TimeDelta::FromMilliseconds(
64 kAudioThrottledFrameTimeoutMilliseconds),
65 this,
66 &PluginInstanceThrottlerImpl::EngageThrottle),
tommyclie8722702015-01-16 11:40:4167 weak_factory_(this) {
tommyclie8722702015-01-16 11:40:4168}
69
70PluginInstanceThrottlerImpl::~PluginInstanceThrottlerImpl() {
tommyclibde09712015-02-05 02:38:5971 FOR_EACH_OBSERVER(Observer, observer_list_, OnThrottlerDestroyed());
tommyclid7798e12015-02-09 21:08:5672 if (state_ != THROTTLER_STATE_MARKED_ESSENTIAL)
tommycli09d498192015-01-23 19:29:3873 RecordUnthrottleMethodMetric(UNTHROTTLE_METHOD_NEVER);
tommyclie8722702015-01-16 11:40:4174}
75
76void PluginInstanceThrottlerImpl::AddObserver(Observer* observer) {
77 observer_list_.AddObserver(observer);
78}
79
80void PluginInstanceThrottlerImpl::RemoveObserver(Observer* observer) {
81 observer_list_.RemoveObserver(observer);
82}
83
84bool PluginInstanceThrottlerImpl::IsThrottled() const {
tommyclid7798e12015-02-09 21:08:5685 return state_ == THROTTLER_STATE_PLUGIN_THROTTLED;
tommyclie8722702015-01-16 11:40:4186}
87
tommyclibde09712015-02-05 02:38:5988bool PluginInstanceThrottlerImpl::IsHiddenForPlaceholder() const {
89 return is_hidden_for_placeholder_;
90}
91
tommyclie8722702015-01-16 11:40:4192void PluginInstanceThrottlerImpl::MarkPluginEssential(
93 PowerSaverUnthrottleMethod method) {
tommyclid7798e12015-02-09 21:08:5694 if (state_ == THROTTLER_STATE_MARKED_ESSENTIAL)
tommyclie8722702015-01-16 11:40:4195 return;
96
97 bool was_throttled = IsThrottled();
tommyclid7798e12015-02-09 21:08:5698 state_ = THROTTLER_STATE_MARKED_ESSENTIAL;
tommyclie8722702015-01-16 11:40:4199 RecordUnthrottleMethodMetric(method);
100
tommycliee0a49b2015-05-05 19:40:49101 FOR_EACH_OBSERVER(Observer, observer_list_, OnPeripheralStateChange());
102
tommyclie8722702015-01-16 11:40:41103 if (was_throttled)
104 FOR_EACH_OBSERVER(Observer, observer_list_, OnThrottleStateChange());
tommyclie8722702015-01-16 11:40:41105}
106
tommyclibde09712015-02-05 02:38:59107void PluginInstanceThrottlerImpl::SetHiddenForPlaceholder(bool hidden) {
108 is_hidden_for_placeholder_ = hidden;
109 FOR_EACH_OBSERVER(Observer, observer_list_, OnHiddenForPlaceholder(hidden));
110}
111
tommycliee0a49b2015-05-05 19:40:49112PepperWebPluginImpl* PluginInstanceThrottlerImpl::GetWebPlugin() const {
tommycli260ce722015-02-24 18:35:21113 DCHECK(web_plugin_);
114 return web_plugin_;
115}
116
tommyclic3ace4f2015-04-01 21:55:59117const gfx::Size& PluginInstanceThrottlerImpl::GetSize() const {
118 return unobscured_size_;
119}
120
tommycli43d8cc42015-03-20 17:48:19121void PluginInstanceThrottlerImpl::NotifyAudioThrottled() {
122 audio_throttled_ = true;
123 audio_throttled_frame_timeout_.Reset();
124}
125
tommycli0dd13012015-04-24 20:27:22126void PluginInstanceThrottlerImpl::SetWebPlugin(
127 PepperWebPluginImpl* web_plugin) {
tommycli260ce722015-02-24 18:35:21128 DCHECK(!web_plugin_);
129 web_plugin_ = web_plugin;
130}
131
tommyclid7798e12015-02-09 21:08:56132void PluginInstanceThrottlerImpl::Initialize(
133 RenderFrameImpl* frame,
tommycli58e3172c2015-09-15 18:18:26134 const url::Origin& content_origin,
tommyclid7798e12015-02-09 21:08:56135 const std::string& plugin_module_name,
tommyclic3ace4f2015-04-01 21:55:59136 const gfx::Size& unobscured_size) {
tommycliee0a49b2015-05-05 19:40:49137 DCHECK(unobscured_size_.IsEmpty());
tommyclic3ace4f2015-04-01 21:55:59138 unobscured_size_ = unobscured_size;
139
tommyclid7798e12015-02-09 21:08:56140 // |frame| may be nullptr in tests.
141 if (frame) {
tommycli73d390b2015-08-04 23:00:53142 float zoom_factor = GetWebPlugin()->container()->pageZoomFactor();
tommycli9c7cbd92015-12-18 23:34:34143 auto status = frame->GetPeripheralContentStatus(
144 frame->GetWebFrame()->top()->securityOrigin(), content_origin,
145 gfx::Size(roundf(unobscured_size.width() / zoom_factor),
146 roundf(unobscured_size.height() / zoom_factor)));
147 if (status != RenderFrame::CONTENT_STATUS_PERIPHERAL) {
tommycliee0a49b2015-05-05 19:40:49148 DCHECK_NE(THROTTLER_STATE_MARKED_ESSENTIAL, state_);
tommyclid7798e12015-02-09 21:08:56149 state_ = THROTTLER_STATE_MARKED_ESSENTIAL;
tommycliee0a49b2015-05-05 19:40:49150 FOR_EACH_OBSERVER(Observer, observer_list_, OnPeripheralStateChange());
tommyclid7798e12015-02-09 21:08:56151
tommycli9c7cbd92015-12-18 23:34:34152 if (status == RenderFrame::CONTENT_STATUS_ESSENTIAL_CROSS_ORIGIN_BIG)
tommyclibae63b92015-10-23 02:56:20153 frame->WhitelistContentOrigin(content_origin);
tommyclid7798e12015-02-09 21:08:56154
155 return;
156 }
157
158 // To collect UMAs, register peripheral content even if power saver mode
159 // is disabled.
tommyclibae63b92015-10-23 02:56:20160 frame->RegisterPeripheralPlugin(
tommyclid7798e12015-02-09 21:08:56161 content_origin,
162 base::Bind(&PluginInstanceThrottlerImpl::MarkPluginEssential,
163 weak_factory_.GetWeakPtr(), UNTHROTTLE_METHOD_BY_WHITELIST));
164 }
165}
166
tommyclie8722702015-01-16 11:40:41167void PluginInstanceThrottlerImpl::OnImageFlush(const SkBitmap* bitmap) {
168 DCHECK(needs_representative_keyframe());
169 if (!bitmap)
170 return;
171
tommyclid7798e12015-02-09 21:08:56172 ++frames_examined_;
173
tommycli43d8cc42015-03-20 17:48:19174 // Does not make a copy, just takes a reference to the underlying pixel data.
175 last_received_frame_ = *bitmap;
176
177 if (audio_throttled_)
178 audio_throttled_frame_timeout_.Reset();
179
tommycli8c4e0c52015-04-30 01:25:06180 double boring_score = color_utils::CalculateBoringScore(*bitmap);
181 if (boring_score <= kAcceptableFrameMaximumBoringness ||
182 frames_examined_ >= kMaximumFramesToExamine) {
tommyclie8722702015-01-16 11:40:41183 EngageThrottle();
tommyclibde09712015-02-05 02:38:59184 }
tommyclie8722702015-01-16 11:40:41185}
186
187bool PluginInstanceThrottlerImpl::ConsumeInputEvent(
188 const blink::WebInputEvent& event) {
tommyclie86b2982015-03-16 20:16:45189 // Always allow right-clicks through so users may verify it's a plugin.
tommyclie8722702015-01-16 11:40:41190 // TODO(tommycli): We should instead show a custom context menu (probably
191 // using PluginPlaceholder) so users aren't confused and try to click the
192 // Flash-internal 'Play' menu item. This is a stopgap solution.
193 if (event.modifiers & blink::WebInputEvent::Modifiers::RightButtonDown)
194 return false;
195
tommyclid7798e12015-02-09 21:08:56196 if (state_ != THROTTLER_STATE_MARKED_ESSENTIAL &&
tommyclie8722702015-01-16 11:40:41197 event.type == blink::WebInputEvent::MouseUp &&
198 (event.modifiers & blink::WebInputEvent::LeftButtonDown)) {
199 bool was_throttled = IsThrottled();
200 MarkPluginEssential(UNTHROTTLE_METHOD_BY_CLICK);
201 return was_throttled;
202 }
203
204 return IsThrottled();
205}
206
207void PluginInstanceThrottlerImpl::EngageThrottle() {
tommyclid7798e12015-02-09 21:08:56208 if (state_ != THROTTLER_STATE_AWAITING_KEYFRAME)
tommyclie8722702015-01-16 11:40:41209 return;
210
tommycli43d8cc42015-03-20 17:48:19211 if (!last_received_frame_.empty()) {
212 FOR_EACH_OBSERVER(Observer, observer_list_,
213 OnKeyframeExtracted(&last_received_frame_));
214
215 // Release our reference to the underlying pixel data.
216 last_received_frame_.reset();
217 }
218
tommyclid7798e12015-02-09 21:08:56219 state_ = THROTTLER_STATE_PLUGIN_THROTTLED;
tommyclie8722702015-01-16 11:40:41220 FOR_EACH_OBSERVER(Observer, observer_list_, OnThrottleStateChange());
221}
222
tommyclie8722702015-01-16 11:40:41223} // namespace content