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_power_saver_helper.h"
7 #include "base/metrics/histogram.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "content/common/frame_messages.h"
10 #include "content/public/renderer/document_state.h"
11 #include "content/public/renderer/navigation_state.h"
12 #include "content/public/renderer/render_frame.h"
13 #include "third_party/WebKit/public/web/WebDocument.h"
14 #include "third_party/WebKit/public/web/WebLocalFrame.h"
15 #include "third_party/WebKit/public/web/WebPluginParams.h"
16 #include "third_party/WebKit/public/web/WebView.h"
22 const char kPosterParamName
[] = "poster";
24 // Initial decision of the peripheral content decision.
25 // These numeric values are used in UMA logs; do not change them.
26 enum PeripheralHeuristicDecision
{
27 HEURISTIC_DECISION_PERIPHERAL
= 0,
28 HEURISTIC_DECISION_ESSENTIAL_SAME_ORIGIN
= 1,
29 HEURISTIC_DECISION_ESSENTIAL_CROSS_ORIGIN_BIG
= 2,
30 HEURISTIC_DECISION_ESSENTIAL_CROSS_ORIGIN_WHITELISTED
= 3,
31 HEURISTIC_DECISION_ESSENTIAL_CROSS_ORIGIN_TINY
= 4,
32 HEURISTIC_DECISION_NUM_ITEMS
35 const char kPeripheralHeuristicHistogram
[] =
36 "Plugin.PowerSaver.PeripheralHeuristic";
38 // Maximum dimensions plug-in content may have while still being considered
39 // peripheral content. These match the sizes used by Safari.
40 const int kPeripheralContentMaxWidth
= 400;
41 const int kPeripheralContentMaxHeight
= 300;
43 // Plug-in content below this size in height and width is considered "tiny".
44 // Tiny content is never peripheral, as tiny plug-ins often serve a critical
45 // purpose, and the user often cannot find and click to unthrottle it.
46 const int kPeripheralContentTinySize
= 5;
48 void RecordDecisionMetric(PeripheralHeuristicDecision decision
) {
49 UMA_HISTOGRAM_ENUMERATION(kPeripheralHeuristicHistogram
, decision
,
50 HEURISTIC_DECISION_NUM_ITEMS
);
53 const char kWebPluginParamHeight
[] = "height";
54 const char kWebPluginParamWidth
[] = "width";
56 // Returns true if valid non-negative height and width extracted.
57 // When this returns false, |width| and |height| are set to undefined values.
58 bool ExtractDimensions(const blink::WebPluginParams
& params
,
61 DCHECK_EQ(params
.attributeNames
.size(), params
.attributeValues
.size());
64 bool width_extracted
= false;
65 bool height_extracted
= false;
66 for (size_t i
= 0; i
< params
.attributeNames
.size(); ++i
) {
67 if (params
.attributeNames
[i
].utf8() == kWebPluginParamWidth
) {
69 base::StringToInt(params
.attributeValues
[i
].utf8(), width
);
70 } else if (params
.attributeNames
[i
].utf8() == kWebPluginParamHeight
) {
72 base::StringToInt(params
.attributeValues
[i
].utf8(), height
);
75 return width_extracted
&& height_extracted
&& *width
>= 0 && *height
>= 0;
78 GURL
GetPluginInstancePosterImage(const blink::WebPluginParams
& params
,
79 const GURL
& page_base_url
) {
80 DCHECK_EQ(params
.attributeNames
.size(), params
.attributeValues
.size());
82 for (size_t i
= 0; i
< params
.attributeNames
.size(); ++i
) {
83 if (params
.attributeNames
[i
] == kPosterParamName
) {
84 std::string
poster_value(params
.attributeValues
[i
].utf8());
85 if (!poster_value
.empty())
86 return page_base_url
.Resolve(poster_value
);
94 PluginPowerSaverHelper::PeripheralPlugin::PeripheralPlugin(
95 const GURL
& content_origin
,
96 const base::Closure
& unthrottle_callback
)
97 : content_origin(content_origin
), unthrottle_callback(unthrottle_callback
) {
100 PluginPowerSaverHelper::PeripheralPlugin::~PeripheralPlugin() {
103 PluginPowerSaverHelper::PluginPowerSaverHelper(RenderFrame
* render_frame
)
104 : RenderFrameObserver(render_frame
) {
107 PluginPowerSaverHelper::~PluginPowerSaverHelper() {
110 void PluginPowerSaverHelper::DidCommitProvisionalLoad(bool is_new_navigation
) {
111 blink::WebFrame
* frame
= render_frame()->GetWebFrame();
113 return; // Not a top-level navigation.
115 DocumentState
* document_state
=
116 DocumentState::FromDataSource(frame
->dataSource());
117 NavigationState
* navigation_state
= document_state
->navigation_state();
118 if (!navigation_state
->was_within_same_page())
119 origin_whitelist_
.clear();
122 bool PluginPowerSaverHelper::OnMessageReceived(const IPC::Message
& message
) {
124 IPC_BEGIN_MESSAGE_MAP(PluginPowerSaverHelper
, message
)
125 IPC_MESSAGE_HANDLER(FrameMsg_UpdatePluginContentOriginWhitelist
,
126 OnUpdatePluginContentOriginWhitelist
)
127 IPC_MESSAGE_UNHANDLED(handled
= false)
128 IPC_END_MESSAGE_MAP()
132 void PluginPowerSaverHelper::OnUpdatePluginContentOriginWhitelist(
133 const std::set
<GURL
>& origin_whitelist
) {
134 origin_whitelist_
= origin_whitelist
;
136 // Check throttled plugin instances to see if any can be unthrottled.
137 auto it
= peripheral_plugins_
.begin();
138 while (it
!= peripheral_plugins_
.end()) {
139 if (origin_whitelist
.count(it
->content_origin
)) {
140 it
->unthrottle_callback
.Run();
141 it
= peripheral_plugins_
.erase(it
);
148 void PluginPowerSaverHelper::RegisterPeripheralPlugin(
149 const GURL
& content_origin
,
150 const base::Closure
& unthrottle_callback
) {
151 peripheral_plugins_
.push_back(
152 PeripheralPlugin(content_origin
, unthrottle_callback
));
155 bool PluginPowerSaverHelper::ShouldThrottleContent(
156 const blink::WebPluginParams
& params
,
157 const GURL
& plugin_frame_url
,
159 bool* cross_origin_main_content
) const {
161 *poster_image
= GURL();
162 if (cross_origin_main_content
)
163 *cross_origin_main_content
= false;
165 GURL content_origin
= GURL(params
.url
).GetOrigin();
169 if (!ExtractDimensions(params
, &width
, &height
))
172 // TODO(alexmos): Update this to use the origin of the RemoteFrame when 426512
173 // is fixed. For now, case 3 in the class level comment doesn't work in
174 // --site-per-process mode.
175 blink::WebFrame
* main_frame
=
176 render_frame()->GetWebFrame()->view()->mainFrame();
177 if (main_frame
->isWebRemoteFrame()) {
178 RecordDecisionMetric(HEURISTIC_DECISION_PERIPHERAL
);
180 *poster_image
= GetPluginInstancePosterImage(params
, plugin_frame_url
);
184 // All same-origin plugin content is essential.
185 GURL main_frame_origin
= GURL(main_frame
->document().url()).GetOrigin();
186 if (content_origin
== main_frame_origin
) {
187 RecordDecisionMetric(HEURISTIC_DECISION_ESSENTIAL_SAME_ORIGIN
);
191 // Whitelisted plugin origins are also essential.
192 if (origin_whitelist_
.count(content_origin
)) {
193 RecordDecisionMetric(HEURISTIC_DECISION_ESSENTIAL_CROSS_ORIGIN_WHITELISTED
);
197 // Never mark tiny content as peripheral.
198 if (width
<= kPeripheralContentTinySize
&&
199 height
<= kPeripheralContentTinySize
) {
200 RecordDecisionMetric(HEURISTIC_DECISION_ESSENTIAL_CROSS_ORIGIN_TINY
);
204 // Plugin content large in both dimensions are the "main attraction".
205 if (width
>= kPeripheralContentMaxWidth
&&
206 height
>= kPeripheralContentMaxHeight
) {
207 RecordDecisionMetric(HEURISTIC_DECISION_ESSENTIAL_CROSS_ORIGIN_BIG
);
208 if (cross_origin_main_content
)
209 *cross_origin_main_content
= true;
213 RecordDecisionMetric(HEURISTIC_DECISION_PERIPHERAL
);
215 *poster_image
= GetPluginInstancePosterImage(params
, plugin_frame_url
);
219 void PluginPowerSaverHelper::WhitelistContentOrigin(
220 const GURL
& content_origin
) {
221 DCHECK_EQ(content_origin
.GetOrigin(), content_origin
);
222 if (origin_whitelist_
.insert(content_origin
).second
) {
223 Send(new FrameHostMsg_PluginContentOriginAllowed(
224 render_frame()->GetRoutingID(), content_origin
));
228 } // namespace content