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/json/string_escape.h"
10 #include "base/strings/string_piece.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "content/public/renderer/render_frame.h"
14 #include "content/public/renderer/render_thread.h"
15 #include "gin/object_template_builder.h"
16 #include "third_party/WebKit/public/web/WebElement.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/WebScriptSource.h"
21 #include "third_party/WebKit/public/web/WebView.h"
22 #include "third_party/re2/re2/re2.h"
24 using base::UserMetricsAction
;
25 using blink::WebElement
;
26 using blink::WebLocalFrame
;
27 using blink::WebMouseEvent
;
29 using blink::WebPlugin
;
30 using blink::WebPluginContainer
;
31 using blink::WebPluginParams
;
32 using blink::WebScriptSource
;
33 using blink::WebURLRequest
;
34 using content::PluginInstanceThrottler
;
35 using content::RenderThread
;
39 #if defined(ENABLE_PLUGINS)
40 void LoadablePluginPlaceholder::BlockForPowerSaverPoster() {
41 DCHECK(!is_blocked_for_power_saver_poster_
);
42 is_blocked_for_power_saver_poster_
= true;
44 render_frame()->RegisterPeripheralPlugin(
45 GURL(GetPluginParams().url
).GetOrigin(),
46 base::Bind(&LoadablePluginPlaceholder::MarkPluginEssential
,
47 weak_factory_
.GetWeakPtr(),
48 PluginInstanceThrottler::UNTHROTTLE_METHOD_BY_WHITELIST
));
52 void LoadablePluginPlaceholder::SetPremadePlugin(
53 blink::WebPlugin
* plugin
,
54 content::PluginInstanceThrottler
* throttler
) {
57 DCHECK(!premade_plugin_
);
58 DCHECK(!premade_throttler_
);
59 premade_plugin_
= plugin
;
60 premade_throttler_
= throttler
;
62 premade_throttler_
->AddObserver(this);
65 LoadablePluginPlaceholder::LoadablePluginPlaceholder(
66 content::RenderFrame
* render_frame
,
68 const WebPluginParams
& params
,
69 const std::string
& html_data
,
70 GURL placeholderDataUrl
)
71 : PluginPlaceholder(render_frame
,
76 is_blocked_for_background_tab_(false),
77 is_blocked_for_prerendering_(false),
78 is_blocked_for_power_saver_poster_(false),
79 power_saver_enabled_(false),
80 plugin_marked_essential_(false),
81 premade_plugin_(nullptr),
82 premade_throttler_(nullptr),
83 allow_loading_(false),
84 placeholder_was_replaced_(false),
86 finished_loading_(false),
90 LoadablePluginPlaceholder::~LoadablePluginPlaceholder() {
91 #if defined(ENABLE_PLUGINS)
92 DCHECK(!premade_plugin_
);
93 DCHECK(!premade_throttler_
);
95 if (!plugin_marked_essential_
&& !placeholder_was_replaced_
&&
96 !is_blocked_for_prerendering_
&& is_blocked_for_power_saver_poster_
) {
97 PluginInstanceThrottler::RecordUnthrottleMethodMetric(
98 PluginInstanceThrottler::UNTHROTTLE_METHOD_NEVER
);
103 #if defined(ENABLE_PLUGINS)
104 void LoadablePluginPlaceholder::MarkPluginEssential(
105 PluginInstanceThrottler::PowerSaverUnthrottleMethod method
) {
106 if (plugin_marked_essential_
)
109 plugin_marked_essential_
= true;
110 if (premade_throttler_
) {
111 premade_throttler_
->MarkPluginEssential(method
);
114 if (is_blocked_for_power_saver_poster_
) {
115 is_blocked_for_power_saver_poster_
= false;
116 PluginInstanceThrottler::RecordUnthrottleMethodMetric(method
);
117 if (!LoadingBlocked())
123 gin::ObjectTemplateBuilder
LoadablePluginPlaceholder::GetObjectTemplateBuilder(
124 v8::Isolate
* isolate
) {
125 return gin::Wrappable
<PluginPlaceholder
>::GetObjectTemplateBuilder(isolate
)
126 .SetMethod("load", &LoadablePluginPlaceholder::LoadCallback
)
127 .SetMethod("hide", &LoadablePluginPlaceholder::HideCallback
)
128 .SetMethod("didFinishLoading",
129 &LoadablePluginPlaceholder::DidFinishLoadingCallback
);
132 void LoadablePluginPlaceholder::ReplacePlugin(WebPlugin
* new_plugin
) {
136 WebPluginContainer
* container
= plugin()->container();
137 // Set the new plug-in on the container before initializing it.
138 container
->setPlugin(new_plugin
);
139 // Save the element in case the plug-in is removed from the page during
141 WebElement element
= container
->element();
142 if (new_plugin
!= premade_plugin_
&& !new_plugin
->initialize(container
)) {
143 // We couldn't initialize the new plug-in. Restore the old one and abort.
144 container
->setPlugin(plugin());
148 // The plug-in has been removed from the page. Destroy the old plug-in. We
149 // will be destroyed as soon as V8 garbage collects us.
150 if (!element
.pluginContainer()) {
155 placeholder_was_replaced_
= true;
157 // During initialization, the new plug-in might have replaced itself in turn
158 // with another plug-in. Make sure not to use the passed in |new_plugin| after
160 new_plugin
= container
->plugin();
162 plugin()->RestoreTitleText();
163 container
->invalidate();
164 container
->reportGeometry();
165 plugin()->ReplayReceivedData(new_plugin
);
169 void LoadablePluginPlaceholder::HidePlugin() {
173 WebPluginContainer
* container
= plugin()->container();
174 WebElement element
= container
->element();
175 element
.setAttribute("style", "display: none;");
176 // If we have a width and height, search for a parent (often <div>) with the
177 // same dimensions. If we find such a parent, hide that as well.
178 // This makes much more uncovered page content usable (including clickable)
179 // as opposed to merely visible.
180 // TODO(cevans) -- it's a foul heurisitc but we're going to tolerate it for
181 // now for these reasons:
182 // 1) Makes the user experience better.
183 // 2) Foulness is encapsulated within this single function.
184 // 3) Confidence in no fasle positives.
185 // 4) Seems to have a good / low false negative rate at this time.
186 if (element
.hasAttribute("width") && element
.hasAttribute("height")) {
187 std::string
width_str("width:[\\s]*");
188 width_str
+= element
.getAttribute("width").utf8().data();
189 if (EndsWith(width_str
, "px", false)) {
190 width_str
= width_str
.substr(0, width_str
.length() - 2);
192 base::TrimWhitespace(width_str
, base::TRIM_TRAILING
, &width_str
);
193 width_str
+= "[\\s]*px";
194 std::string
height_str("height:[\\s]*");
195 height_str
+= element
.getAttribute("height").utf8().data();
196 if (EndsWith(height_str
, "px", false)) {
197 height_str
= height_str
.substr(0, height_str
.length() - 2);
199 base::TrimWhitespace(height_str
, base::TRIM_TRAILING
, &height_str
);
200 height_str
+= "[\\s]*px";
201 WebNode parent
= element
;
202 while (!parent
.parentNode().isNull()) {
203 parent
= parent
.parentNode();
204 if (!parent
.isElementNode())
206 element
= parent
.toConst
<WebElement
>();
207 if (element
.hasAttribute("style")) {
208 std::string style_str
= element
.getAttribute("style").utf8();
209 if (RE2::PartialMatch(style_str
, width_str
) &&
210 RE2::PartialMatch(style_str
, height_str
))
211 element
.setAttribute("style", "display: none;");
217 void LoadablePluginPlaceholder::SetMessage(const base::string16
& message
) {
219 if (finished_loading_
)
223 void LoadablePluginPlaceholder::UpdateMessage() {
227 "window.setMessage(" + base::GetQuotedJSONString(message_
) + ")";
228 plugin()->web_view()->mainFrame()->executeScript(
229 WebScriptSource(base::UTF8ToUTF16(script
)));
232 void LoadablePluginPlaceholder::PluginDestroyed() {
233 // Since the premade plugin has been detached from the container, it will not
234 // be automatically destroyed along with the page.
235 if (!placeholder_was_replaced_
&& premade_plugin_
) {
236 DCHECK(premade_throttler_
);
237 premade_throttler_
->RemoveObserver(this);
238 premade_throttler_
= nullptr;
240 premade_plugin_
->destroy();
241 premade_plugin_
= nullptr;
244 PluginPlaceholder::PluginDestroyed();
247 void LoadablePluginPlaceholder::WasShown() {
248 if (is_blocked_for_background_tab_
) {
249 is_blocked_for_background_tab_
= false;
250 if (!LoadingBlocked())
255 void LoadablePluginPlaceholder::OnThrottleStateChange() {
256 DCHECK(premade_plugin_
);
257 DCHECK(premade_throttler_
);
258 if (!premade_throttler_
->IsThrottled()) {
259 // Premade plugin has been unthrottled externally (by audio playback, etc.).
264 void LoadablePluginPlaceholder::OnLoadBlockedPlugins(
265 const std::string
& identifier
) {
266 if (!identifier
.empty() && identifier
!= identifier_
)
269 RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Load_UI"));
273 void LoadablePluginPlaceholder::OnSetIsPrerendering(bool is_prerendering
) {
274 // Prerendering can only be enabled prior to a RenderView's first navigation,
275 // so no BlockedPlugin should see the notification that enables prerendering.
276 DCHECK(!is_prerendering
);
277 if (is_blocked_for_prerendering_
) {
278 is_blocked_for_prerendering_
= false;
279 if (!LoadingBlocked())
284 void LoadablePluginPlaceholder::LoadPlugin() {
285 // This is not strictly necessary but is an important defense in case the
286 // event propagation changes between "close" vs. "click-to-play".
291 if (!allow_loading_
) {
296 if (premade_plugin_
) {
297 premade_throttler_
->RemoveObserver(this);
298 premade_throttler_
->SetHiddenForPlaceholder(false /* hidden */);
299 premade_throttler_
= nullptr;
301 ReplacePlugin(premade_plugin_
);
302 premade_plugin_
= nullptr;
304 // TODO(mmenke): In the case of prerendering, feed into
305 // ChromeContentRendererClient::CreatePlugin instead, to
306 // reduce the chance of future regressions.
307 scoped_ptr
<PluginInstanceThrottler
> throttler
;
308 #if defined(ENABLE_PLUGINS)
309 // If the plugin has already been marked essential in its placeholder form,
310 // we shouldn't create a new throttler and start the process all over again.
311 if (!plugin_marked_essential_
)
312 throttler
= PluginInstanceThrottler::Create(power_saver_enabled_
);
314 WebPlugin
* plugin
= render_frame()->CreatePlugin(
315 GetFrame(), plugin_info_
, GetPluginParams(), throttler
.Pass());
317 ReplacePlugin(plugin
);
321 void LoadablePluginPlaceholder::LoadCallback() {
322 RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Load_Click"));
323 #if defined(ENABLE_PLUGINS)
324 // If the user specifically clicks on the plug-in content's placeholder,
325 // disable power saver throttling for this instance.
326 MarkPluginEssential(PluginInstanceThrottler::UNTHROTTLE_METHOD_BY_CLICK
);
331 void LoadablePluginPlaceholder::HideCallback() {
332 RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Hide_Click"));
336 void LoadablePluginPlaceholder::DidFinishLoadingCallback() {
337 finished_loading_
= true;
338 if (message_
.length() > 0)
341 // Wait for the placeholder to finish loading to hide the premade plugin.
342 // This is necessary to prevent a flicker.
343 if (premade_plugin_
&& !placeholder_was_replaced_
)
344 premade_throttler_
->SetHiddenForPlaceholder(true /* hidden */);
347 void LoadablePluginPlaceholder::SetPluginInfo(
348 const content::WebPluginInfo
& plugin_info
) {
349 plugin_info_
= plugin_info
;
352 const content::WebPluginInfo
& LoadablePluginPlaceholder::GetPluginInfo() const {
356 void LoadablePluginPlaceholder::SetIdentifier(const std::string
& identifier
) {
357 identifier_
= identifier
;
360 bool LoadablePluginPlaceholder::LoadingBlocked() const {
361 DCHECK(allow_loading_
);
362 return is_blocked_for_background_tab_
|| is_blocked_for_power_saver_poster_
||
363 is_blocked_for_prerendering_
;
366 } // namespace plugins