tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 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. |
| 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" |
tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 10 | #include "content/public/renderer/render_thread.h" |
tommycli | 0dd1301 | 2015-04-24 20:27:22 | [diff] [blame] | 11 | #include "content/renderer/pepper/pepper_plugin_instance_impl.h" |
tommycli | d7798e1 | 2015-02-09 21:08:56 | [diff] [blame] | 12 | #include "content/renderer/render_frame_impl.h" |
tommycli | 0dd1301 | 2015-04-24 20:27:22 | [diff] [blame] | 13 | #include "ppapi/shared_impl/ppapi_constants.h" |
tommycli | d7798e1 | 2015-02-09 21:08:56 | [diff] [blame] | 14 | #include "third_party/WebKit/public/platform/WebRect.h" |
tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 15 | #include "third_party/WebKit/public/web/WebInputEvent.h" |
tommycli | 58e3172c | 2015-09-15 18:18:26 | [diff] [blame] | 16 | #include "third_party/WebKit/public/web/WebLocalFrame.h" |
tommycli | 73d390b | 2015-08-04 23:00:53 | [diff] [blame] | 17 | #include "third_party/WebKit/public/web/WebPluginContainer.h" |
tommycli | d7798e1 | 2015-02-09 21:08:56 | [diff] [blame] | 18 | #include "third_party/WebKit/public/web/WebPluginParams.h" |
tommycli | 58e3172c | 2015-09-15 18:18:26 | [diff] [blame] | 19 | #include "third_party/WebKit/public/web/WebView.h" |
tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 20 | #include "ui/gfx/color_utils.h" |
tommycli | 58e3172c | 2015-09-15 18:18:26 | [diff] [blame] | 21 | #include "url/origin.h" |
tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 22 | |
| 23 | namespace content { |
| 24 | |
| 25 | namespace { |
| 26 | |
tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 27 | // 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. |
| 30 | const double kAcceptableFrameMaximumBoringness = 0.94; |
| 31 | |
tommycli | 43d8cc4 | 2015-03-20 17:48:19 | [diff] [blame] | 32 | // 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. |
| 35 | const int kAudioThrottledFrameTimeoutMilliseconds = 500; |
| 36 | |
tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 37 | } // namespace |
| 38 | |
tommycli | 09d49819 | 2015-01-23 19:29:38 | [diff] [blame] | 39 | // static |
tommycli | d7798e1 | 2015-02-09 21:08:56 | [diff] [blame] | 40 | const int PluginInstanceThrottlerImpl::kMaximumFramesToExamine = 150; |
tommycli | 09d49819 | 2015-01-23 19:29:38 | [diff] [blame] | 41 | |
tommycli | d7798e1 | 2015-02-09 21:08:56 | [diff] [blame] | 42 | // static |
tommycli | db0458c | 2015-03-26 20:38:43 | [diff] [blame] | 43 | scoped_ptr<PluginInstanceThrottler> PluginInstanceThrottler::Create() { |
| 44 | return make_scoped_ptr(new PluginInstanceThrottlerImpl); |
tommycli | 09d49819 | 2015-01-23 19:29:38 | [diff] [blame] | 45 | } |
| 46 | |
| 47 | // static |
| 48 | void PluginInstanceThrottler::RecordUnthrottleMethodMetric( |
| 49 | PluginInstanceThrottlerImpl::PowerSaverUnthrottleMethod method) { |
| 50 | UMA_HISTOGRAM_ENUMERATION( |
| 51 | "Plugin.PowerSaver.Unthrottle", method, |
| 52 | PluginInstanceThrottler::UNTHROTTLE_METHOD_NUM_ITEMS); |
| 53 | } |
| 54 | |
tommycli | db0458c | 2015-03-26 20:38:43 | [diff] [blame] | 55 | PluginInstanceThrottlerImpl::PluginInstanceThrottlerImpl() |
| 56 | : state_(THROTTLER_STATE_AWAITING_KEYFRAME), |
tommycli | bde0971 | 2015-02-05 02:38:59 | [diff] [blame] | 57 | is_hidden_for_placeholder_(false), |
tommycli | 260ce72 | 2015-02-24 18:35:21 | [diff] [blame] | 58 | web_plugin_(nullptr), |
tommycli | d7798e1 | 2015-02-09 21:08:56 | [diff] [blame] | 59 | frames_examined_(0), |
tommycli | 43d8cc4 | 2015-03-20 17:48:19 | [diff] [blame] | 60 | audio_throttled_(false), |
| 61 | audio_throttled_frame_timeout_( |
| 62 | FROM_HERE, |
| 63 | base::TimeDelta::FromMilliseconds( |
| 64 | kAudioThrottledFrameTimeoutMilliseconds), |
| 65 | this, |
| 66 | &PluginInstanceThrottlerImpl::EngageThrottle), |
tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 67 | weak_factory_(this) { |
tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 68 | } |
| 69 | |
| 70 | PluginInstanceThrottlerImpl::~PluginInstanceThrottlerImpl() { |
tommycli | bde0971 | 2015-02-05 02:38:59 | [diff] [blame] | 71 | FOR_EACH_OBSERVER(Observer, observer_list_, OnThrottlerDestroyed()); |
tommycli | d7798e1 | 2015-02-09 21:08:56 | [diff] [blame] | 72 | if (state_ != THROTTLER_STATE_MARKED_ESSENTIAL) |
tommycli | 09d49819 | 2015-01-23 19:29:38 | [diff] [blame] | 73 | RecordUnthrottleMethodMetric(UNTHROTTLE_METHOD_NEVER); |
tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 74 | } |
| 75 | |
| 76 | void PluginInstanceThrottlerImpl::AddObserver(Observer* observer) { |
| 77 | observer_list_.AddObserver(observer); |
| 78 | } |
| 79 | |
| 80 | void PluginInstanceThrottlerImpl::RemoveObserver(Observer* observer) { |
| 81 | observer_list_.RemoveObserver(observer); |
| 82 | } |
| 83 | |
| 84 | bool PluginInstanceThrottlerImpl::IsThrottled() const { |
tommycli | d7798e1 | 2015-02-09 21:08:56 | [diff] [blame] | 85 | return state_ == THROTTLER_STATE_PLUGIN_THROTTLED; |
tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 86 | } |
| 87 | |
tommycli | bde0971 | 2015-02-05 02:38:59 | [diff] [blame] | 88 | bool PluginInstanceThrottlerImpl::IsHiddenForPlaceholder() const { |
| 89 | return is_hidden_for_placeholder_; |
| 90 | } |
| 91 | |
tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 92 | void PluginInstanceThrottlerImpl::MarkPluginEssential( |
| 93 | PowerSaverUnthrottleMethod method) { |
tommycli | d7798e1 | 2015-02-09 21:08:56 | [diff] [blame] | 94 | if (state_ == THROTTLER_STATE_MARKED_ESSENTIAL) |
tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 95 | return; |
| 96 | |
| 97 | bool was_throttled = IsThrottled(); |
tommycli | d7798e1 | 2015-02-09 21:08:56 | [diff] [blame] | 98 | state_ = THROTTLER_STATE_MARKED_ESSENTIAL; |
tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 99 | RecordUnthrottleMethodMetric(method); |
| 100 | |
tommycli | ee0a49b | 2015-05-05 19:40:49 | [diff] [blame] | 101 | FOR_EACH_OBSERVER(Observer, observer_list_, OnPeripheralStateChange()); |
| 102 | |
tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 103 | if (was_throttled) |
| 104 | FOR_EACH_OBSERVER(Observer, observer_list_, OnThrottleStateChange()); |
tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 105 | } |
| 106 | |
tommycli | bde0971 | 2015-02-05 02:38:59 | [diff] [blame] | 107 | void PluginInstanceThrottlerImpl::SetHiddenForPlaceholder(bool hidden) { |
| 108 | is_hidden_for_placeholder_ = hidden; |
| 109 | FOR_EACH_OBSERVER(Observer, observer_list_, OnHiddenForPlaceholder(hidden)); |
| 110 | } |
| 111 | |
tommycli | ee0a49b | 2015-05-05 19:40:49 | [diff] [blame] | 112 | PepperWebPluginImpl* PluginInstanceThrottlerImpl::GetWebPlugin() const { |
tommycli | 260ce72 | 2015-02-24 18:35:21 | [diff] [blame] | 113 | DCHECK(web_plugin_); |
| 114 | return web_plugin_; |
| 115 | } |
| 116 | |
tommycli | c3ace4f | 2015-04-01 21:55:59 | [diff] [blame] | 117 | const gfx::Size& PluginInstanceThrottlerImpl::GetSize() const { |
| 118 | return unobscured_size_; |
| 119 | } |
| 120 | |
tommycli | 43d8cc4 | 2015-03-20 17:48:19 | [diff] [blame] | 121 | void PluginInstanceThrottlerImpl::NotifyAudioThrottled() { |
| 122 | audio_throttled_ = true; |
| 123 | audio_throttled_frame_timeout_.Reset(); |
| 124 | } |
| 125 | |
tommycli | 0dd1301 | 2015-04-24 20:27:22 | [diff] [blame] | 126 | void PluginInstanceThrottlerImpl::SetWebPlugin( |
| 127 | PepperWebPluginImpl* web_plugin) { |
tommycli | 260ce72 | 2015-02-24 18:35:21 | [diff] [blame] | 128 | DCHECK(!web_plugin_); |
| 129 | web_plugin_ = web_plugin; |
| 130 | } |
| 131 | |
tommycli | d7798e1 | 2015-02-09 21:08:56 | [diff] [blame] | 132 | void PluginInstanceThrottlerImpl::Initialize( |
| 133 | RenderFrameImpl* frame, |
tommycli | 58e3172c | 2015-09-15 18:18:26 | [diff] [blame] | 134 | const url::Origin& content_origin, |
tommycli | d7798e1 | 2015-02-09 21:08:56 | [diff] [blame] | 135 | const std::string& plugin_module_name, |
tommycli | c3ace4f | 2015-04-01 21:55:59 | [diff] [blame] | 136 | const gfx::Size& unobscured_size) { |
tommycli | ee0a49b | 2015-05-05 19:40:49 | [diff] [blame] | 137 | DCHECK(unobscured_size_.IsEmpty()); |
tommycli | c3ace4f | 2015-04-01 21:55:59 | [diff] [blame] | 138 | unobscured_size_ = unobscured_size; |
| 139 | |
tommycli | d7798e1 | 2015-02-09 21:08:56 | [diff] [blame] | 140 | // |frame| may be nullptr in tests. |
| 141 | if (frame) { |
tommycli | 73d390b | 2015-08-04 23:00:53 | [diff] [blame] | 142 | float zoom_factor = GetWebPlugin()->container()->pageZoomFactor(); |
tommycli | 9c7cbd9 | 2015-12-18 23:34:34 | [diff] [blame] | 143 | 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) { |
tommycli | ee0a49b | 2015-05-05 19:40:49 | [diff] [blame] | 148 | DCHECK_NE(THROTTLER_STATE_MARKED_ESSENTIAL, state_); |
tommycli | d7798e1 | 2015-02-09 21:08:56 | [diff] [blame] | 149 | state_ = THROTTLER_STATE_MARKED_ESSENTIAL; |
tommycli | ee0a49b | 2015-05-05 19:40:49 | [diff] [blame] | 150 | FOR_EACH_OBSERVER(Observer, observer_list_, OnPeripheralStateChange()); |
tommycli | d7798e1 | 2015-02-09 21:08:56 | [diff] [blame] | 151 | |
tommycli | 9c7cbd9 | 2015-12-18 23:34:34 | [diff] [blame] | 152 | if (status == RenderFrame::CONTENT_STATUS_ESSENTIAL_CROSS_ORIGIN_BIG) |
tommycli | bae63b9 | 2015-10-23 02:56:20 | [diff] [blame] | 153 | frame->WhitelistContentOrigin(content_origin); |
tommycli | d7798e1 | 2015-02-09 21:08:56 | [diff] [blame] | 154 | |
| 155 | return; |
| 156 | } |
| 157 | |
| 158 | // To collect UMAs, register peripheral content even if power saver mode |
| 159 | // is disabled. |
tommycli | bae63b9 | 2015-10-23 02:56:20 | [diff] [blame] | 160 | frame->RegisterPeripheralPlugin( |
tommycli | d7798e1 | 2015-02-09 21:08:56 | [diff] [blame] | 161 | content_origin, |
| 162 | base::Bind(&PluginInstanceThrottlerImpl::MarkPluginEssential, |
| 163 | weak_factory_.GetWeakPtr(), UNTHROTTLE_METHOD_BY_WHITELIST)); |
| 164 | } |
| 165 | } |
| 166 | |
tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 167 | void PluginInstanceThrottlerImpl::OnImageFlush(const SkBitmap* bitmap) { |
| 168 | DCHECK(needs_representative_keyframe()); |
| 169 | if (!bitmap) |
| 170 | return; |
| 171 | |
tommycli | d7798e1 | 2015-02-09 21:08:56 | [diff] [blame] | 172 | ++frames_examined_; |
| 173 | |
tommycli | 43d8cc4 | 2015-03-20 17:48:19 | [diff] [blame] | 174 | // 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 | |
tommycli | 8c4e0c5 | 2015-04-30 01:25:06 | [diff] [blame] | 180 | double boring_score = color_utils::CalculateBoringScore(*bitmap); |
| 181 | if (boring_score <= kAcceptableFrameMaximumBoringness || |
| 182 | frames_examined_ >= kMaximumFramesToExamine) { |
tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 183 | EngageThrottle(); |
tommycli | bde0971 | 2015-02-05 02:38:59 | [diff] [blame] | 184 | } |
tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 185 | } |
| 186 | |
| 187 | bool PluginInstanceThrottlerImpl::ConsumeInputEvent( |
| 188 | const blink::WebInputEvent& event) { |
tommycli | e86b298 | 2015-03-16 20:16:45 | [diff] [blame] | 189 | // Always allow right-clicks through so users may verify it's a plugin. |
tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 190 | // 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 | |
tommycli | d7798e1 | 2015-02-09 21:08:56 | [diff] [blame] | 196 | if (state_ != THROTTLER_STATE_MARKED_ESSENTIAL && |
tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 197 | 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 | |
| 207 | void PluginInstanceThrottlerImpl::EngageThrottle() { |
tommycli | d7798e1 | 2015-02-09 21:08:56 | [diff] [blame] | 208 | if (state_ != THROTTLER_STATE_AWAITING_KEYFRAME) |
tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 209 | return; |
| 210 | |
tommycli | 43d8cc4 | 2015-03-20 17:48:19 | [diff] [blame] | 211 | 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 | |
tommycli | d7798e1 | 2015-02-09 21:08:56 | [diff] [blame] | 219 | state_ = THROTTLER_STATE_PLUGIN_THROTTLED; |
tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 220 | FOR_EACH_OBSERVER(Observer, observer_list_, OnThrottleStateChange()); |
| 221 | } |
| 222 | |
tommycli | e872270 | 2015-01-16 11:40:41 | [diff] [blame] | 223 | } // namespace content |