1 // Copyright 2015 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 "components/plugins/renderer/loadable_plugin_placeholder.h"
8 #include "base/bind_helpers.h"
9 #include "base/command_line.h"
10 #include "base/json/string_escape.h"
11 #include "base/strings/string_piece.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/thread_task_runner_handle.h"
15 #include "base/values.h"
16 #include "content/public/child/v8_value_converter.h"
17 #include "content/public/common/content_switches.h"
18 #include "content/public/renderer/render_frame.h"
19 #include "content/public/renderer/render_thread.h"
20 #include "gin/handle.h"
21 #include "gin/object_template_builder.h"
22 #include "third_party/WebKit/public/web/WebDOMMessageEvent.h"
23 #include "third_party/WebKit/public/web/WebDocument.h"
24 #include "third_party/WebKit/public/web/WebElement.h"
25 #include "third_party/WebKit/public/web/WebInputEvent.h"
26 #include "third_party/WebKit/public/web/WebKit.h"
27 #include "third_party/WebKit/public/web/WebLocalFrame.h"
28 #include "third_party/WebKit/public/web/WebPluginContainer.h"
29 #include "third_party/WebKit/public/web/WebScriptSource.h"
30 #include "third_party/WebKit/public/web/WebSerializedScriptValue.h"
31 #include "third_party/WebKit/public/web/WebView.h"
32 using base::UserMetricsAction
;
33 using content::PluginInstanceThrottler
;
34 using content::RenderThread
;
38 // TODO(tommycli): After a size update, re-check the size after this delay, as
39 // Blink can report incorrect sizes to plugins while the compositing state is
40 // dirty. Chosen because it seems to work.
41 const int kSizeChangeRecheckDelayMilliseconds
= 100;
43 void LoadablePluginPlaceholder::BlockForPowerSaverPoster() {
44 DCHECK(!is_blocked_for_power_saver_poster_
);
45 is_blocked_for_power_saver_poster_
= true;
47 render_frame()->RegisterPeripheralPlugin(
48 GURL(GetPluginParams().url
).GetOrigin(),
49 base::Bind(&LoadablePluginPlaceholder::MarkPluginEssential
,
50 weak_factory_
.GetWeakPtr(),
51 PluginInstanceThrottler::UNTHROTTLE_METHOD_BY_WHITELIST
));
54 void LoadablePluginPlaceholder::SetPremadePlugin(
55 content::PluginInstanceThrottler
* throttler
) {
57 DCHECK(!premade_throttler_
);
58 premade_throttler_
= throttler
;
61 LoadablePluginPlaceholder::LoadablePluginPlaceholder(
62 content::RenderFrame
* render_frame
,
63 blink::WebLocalFrame
* frame
,
64 const blink::WebPluginParams
& params
,
65 const std::string
& html_data
)
66 : PluginPlaceholderBase(render_frame
, frame
, params
, html_data
),
67 is_blocked_for_background_tab_(false),
68 is_blocked_for_prerendering_(false),
69 is_blocked_for_power_saver_poster_(false),
70 power_saver_enabled_(false),
71 premade_throttler_(nullptr),
72 allow_loading_(false),
73 finished_loading_(false),
74 in_size_recheck_(false),
78 LoadablePluginPlaceholder::~LoadablePluginPlaceholder() {
81 void LoadablePluginPlaceholder::MarkPluginEssential(
82 PluginInstanceThrottler::PowerSaverUnthrottleMethod method
) {
83 if (!power_saver_enabled_
)
86 power_saver_enabled_
= false;
88 if (premade_throttler_
)
89 premade_throttler_
->MarkPluginEssential(method
);
91 PluginInstanceThrottler::RecordUnthrottleMethodMetric(method
);
93 if (is_blocked_for_power_saver_poster_
) {
94 is_blocked_for_power_saver_poster_
= false;
95 if (!LoadingBlocked())
100 void LoadablePluginPlaceholder::ReplacePlugin(blink::WebPlugin
* new_plugin
) {
104 blink::WebPluginContainer
* container
= plugin()->container();
105 // Set the new plugin on the container before initializing it.
106 container
->setPlugin(new_plugin
);
107 // Save the element in case the plugin is removed from the page during
109 blink::WebElement element
= container
->element();
110 bool plugin_needs_initialization
=
111 !premade_throttler_
|| new_plugin
!= premade_throttler_
->GetWebPlugin();
112 if (plugin_needs_initialization
&& !new_plugin
->initialize(container
)) {
113 // We couldn't initialize the new plugin. Restore the old one and abort.
114 container
->setPlugin(plugin());
118 // The plugin has been removed from the page. Destroy the old plugin. We
119 // will be destroyed as soon as V8 garbage collects us.
120 if (!element
.pluginContainer()) {
125 // During initialization, the new plugin might have replaced itself in turn
126 // with another plugin. Make sure not to use the passed in |new_plugin| after
128 new_plugin
= container
->plugin();
130 plugin()->RestoreTitleText();
131 container
->invalidate();
132 container
->reportGeometry();
133 plugin()->ReplayReceivedData(new_plugin
);
137 void LoadablePluginPlaceholder::SetMessage(const base::string16
& message
) {
139 if (finished_loading_
)
143 void LoadablePluginPlaceholder::UpdateMessage() {
147 "window.setMessage(" + base::GetQuotedJSONString(message_
) + ")";
148 plugin()->web_view()->mainFrame()->executeScript(
149 blink::WebScriptSource(base::UTF8ToUTF16(script
)));
152 void LoadablePluginPlaceholder::PluginDestroyed() {
153 if (power_saver_enabled_
) {
154 if (premade_throttler_
) {
155 // Since the premade plugin has been detached from the container, it will
156 // not be automatically destroyed along with the page.
157 premade_throttler_
->GetWebPlugin()->destroy();
158 premade_throttler_
= nullptr;
159 } else if (is_blocked_for_power_saver_poster_
) {
160 // Record the NEVER unthrottle count only if there is no throttler.
161 PluginInstanceThrottler::RecordUnthrottleMethodMetric(
162 PluginInstanceThrottler::UNTHROTTLE_METHOD_NEVER
);
165 // Prevent processing subsequent calls to MarkPluginEssential.
166 power_saver_enabled_
= false;
169 PluginPlaceholderBase::PluginDestroyed();
172 v8::Local
<v8::Object
> LoadablePluginPlaceholder::GetV8ScriptableObject(
173 v8::Isolate
* isolate
) const {
174 // Pass through JavaScript access to the underlying throttled plugin.
175 if (premade_throttler_
&& premade_throttler_
->GetWebPlugin()) {
176 return premade_throttler_
->GetWebPlugin()->v8ScriptableObject(isolate
);
178 return v8::Local
<v8::Object
>();
181 void LoadablePluginPlaceholder::OnUnobscuredRectUpdate(
182 const gfx::Rect
& unobscured_rect
) {
183 DCHECK(content::RenderThread::Get());
184 if (!power_saver_enabled_
|| !premade_throttler_
|| !finished_loading_
)
187 unobscured_rect_
= unobscured_rect
;
189 // During a size recheck, we will get another notification into this method.
190 // Use this flag to early exit to prevent reentrancy issues.
191 if (in_size_recheck_
)
194 if (!size_update_timer_
.IsRunning()) {
195 // TODO(tommycli): We have to post a delayed task to recheck the size, as
196 // Blink can report wrong sizes for partially obscured plugins while the
197 // compositing state is dirty. https://crbug.com/343769
198 size_update_timer_
.Start(
200 base::TimeDelta::FromMilliseconds(kSizeChangeRecheckDelayMilliseconds
),
201 base::Bind(&LoadablePluginPlaceholder::RecheckSizeAndMaybeUnthrottle
,
202 weak_factory_
.GetWeakPtr()));
206 void LoadablePluginPlaceholder::WasShown() {
207 if (is_blocked_for_background_tab_
) {
208 is_blocked_for_background_tab_
= false;
209 if (!LoadingBlocked())
214 void LoadablePluginPlaceholder::OnLoadBlockedPlugins(
215 const std::string
& identifier
) {
216 if (!identifier
.empty() && identifier
!= identifier_
)
219 RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Load_UI"));
223 void LoadablePluginPlaceholder::OnSetIsPrerendering(bool is_prerendering
) {
224 // Prerendering can only be enabled prior to a RenderView's first navigation,
225 // so no BlockedPlugin should see the notification that enables prerendering.
226 DCHECK(!is_prerendering
);
227 if (is_blocked_for_prerendering_
) {
228 is_blocked_for_prerendering_
= false;
229 if (!LoadingBlocked())
234 void LoadablePluginPlaceholder::LoadPlugin() {
235 // This is not strictly necessary but is an important defense in case the
236 // event propagation changes between "close" vs. "click-to-play".
241 if (!allow_loading_
) {
246 if (premade_throttler_
) {
247 premade_throttler_
->SetHiddenForPlaceholder(false /* hidden */);
248 ReplacePlugin(premade_throttler_
->GetWebPlugin());
249 premade_throttler_
= nullptr;
251 ReplacePlugin(CreatePlugin());
255 void LoadablePluginPlaceholder::LoadCallback() {
256 RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Load_Click"));
257 // If the user specifically clicks on the plugin content's placeholder,
258 // disable power saver throttling for this instance.
259 MarkPluginEssential(PluginInstanceThrottler::UNTHROTTLE_METHOD_BY_CLICK
);
263 void LoadablePluginPlaceholder::DidFinishLoadingCallback() {
264 finished_loading_
= true;
265 if (message_
.length() > 0)
268 // Wait for the placeholder to finish loading to hide the premade plugin.
269 // This is necessary to prevent a flicker.
270 if (premade_throttler_
&& power_saver_enabled_
)
271 premade_throttler_
->SetHiddenForPlaceholder(true /* hidden */);
273 // Set an attribute and post an event, so browser tests can wait for the
274 // placeholder to be ready to receive simulated user input.
275 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
276 switches::kEnablePluginPlaceholderTesting
)) {
277 blink::WebElement element
= plugin()->container()->element();
278 element
.setAttribute("placeholderLoaded", "true");
280 scoped_ptr
<content::V8ValueConverter
> converter(
281 content::V8ValueConverter::create());
282 base::StringValue
value("placeholderLoaded");
283 blink::WebSerializedScriptValue message_data
=
284 blink::WebSerializedScriptValue::serialize(converter
->ToV8Value(
285 &value
, element
.document().frame()->mainWorldScriptContext()));
287 blink::WebDOMEvent event
= element
.document().createEvent("MessageEvent");
288 blink::WebDOMMessageEvent msg_event
= event
.to
<blink::WebDOMMessageEvent
>();
289 msg_event
.initMessageEvent("message", // type
292 message_data
, // data
295 element
.document(), // target document
297 element
.dispatchEvent(msg_event
);
301 void LoadablePluginPlaceholder::SetPluginInfo(
302 const content::WebPluginInfo
& plugin_info
) {
303 plugin_info_
= plugin_info
;
306 const content::WebPluginInfo
& LoadablePluginPlaceholder::GetPluginInfo() const {
310 void LoadablePluginPlaceholder::SetIdentifier(const std::string
& identifier
) {
311 identifier_
= identifier
;
314 const std::string
& LoadablePluginPlaceholder::GetIdentifier() const {
318 bool LoadablePluginPlaceholder::LoadingBlocked() const {
319 DCHECK(allow_loading_
);
320 return is_blocked_for_background_tab_
|| is_blocked_for_power_saver_poster_
||
321 is_blocked_for_prerendering_
;
324 void LoadablePluginPlaceholder::RecheckSizeAndMaybeUnthrottle() {
325 DCHECK(content::RenderThread::Get());
326 DCHECK(!in_size_recheck_
);
331 in_size_recheck_
= true;
333 // Re-check the size in case the reported size was incorrect.
334 plugin()->container()->reportGeometry();
336 float zoom_factor
= plugin()->container()->pageZoomFactor();
338 // Adjust padding using clip coordinates to center play button for plugins
339 // that have their top or left portions obscured.
340 if (is_blocked_for_power_saver_poster_
) {
341 int x
= roundf(unobscured_rect_
.x() / zoom_factor
);
342 int y
= roundf(unobscured_rect_
.y() / zoom_factor
);
344 base::StringPrintf("window.setPosterMargin('%dpx', '%dpx')", x
, y
);
345 plugin()->web_view()->mainFrame()->executeScript(
346 blink::WebScriptSource(base::UTF8ToUTF16(script
)));
349 if (PluginInstanceThrottler::IsLargeContent(
350 roundf(unobscured_rect_
.width() / zoom_factor
),
351 roundf(unobscured_rect_
.height() / zoom_factor
))) {
353 PluginInstanceThrottler::UNTHROTTLE_METHOD_BY_SIZE_CHANGE
);
356 in_size_recheck_
= false;
359 } // namespace plugins