Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / components / plugins / renderer / loadable_plugin_placeholder.cc
blob339f58321de1cbabfc3b1f6fbfe51cdd10ece5d6
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"
7 #include "base/bind.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 #include "url/gurl.h"
33 #include "url/origin.h"
35 using base::UserMetricsAction;
36 using content::PluginInstanceThrottler;
37 using content::RenderThread;
39 namespace plugins {
41 // TODO(tommycli): After a size update, re-check the size after this delay, as
42 // Blink can report incorrect sizes to plugins while the compositing state is
43 // dirty. Chosen because it seems to work.
44 const int kSizeChangeRecheckDelayMilliseconds = 100;
46 void LoadablePluginPlaceholder::BlockForPowerSaverPoster() {
47 DCHECK(!is_blocked_for_power_saver_poster_);
48 is_blocked_for_power_saver_poster_ = true;
50 render_frame()->RegisterPeripheralPlugin(
51 url::Origin(GURL(GetPluginParams().url)),
52 base::Bind(&LoadablePluginPlaceholder::MarkPluginEssential,
53 weak_factory_.GetWeakPtr(),
54 PluginInstanceThrottler::UNTHROTTLE_METHOD_BY_WHITELIST));
57 void LoadablePluginPlaceholder::SetPremadePlugin(
58 content::PluginInstanceThrottler* throttler) {
59 DCHECK(throttler);
60 DCHECK(!premade_throttler_);
61 premade_throttler_ = throttler;
64 LoadablePluginPlaceholder::LoadablePluginPlaceholder(
65 content::RenderFrame* render_frame,
66 blink::WebLocalFrame* frame,
67 const blink::WebPluginParams& params,
68 const std::string& html_data)
69 : PluginPlaceholderBase(render_frame, frame, params, html_data),
70 is_blocked_for_background_tab_(false),
71 is_blocked_for_prerendering_(false),
72 is_blocked_for_power_saver_poster_(false),
73 power_saver_enabled_(false),
74 premade_throttler_(nullptr),
75 allow_loading_(false),
76 finished_loading_(false),
77 in_size_recheck_(false),
78 weak_factory_(this) {
81 LoadablePluginPlaceholder::~LoadablePluginPlaceholder() {
84 void LoadablePluginPlaceholder::MarkPluginEssential(
85 PluginInstanceThrottler::PowerSaverUnthrottleMethod method) {
86 if (!power_saver_enabled_)
87 return;
89 power_saver_enabled_ = false;
91 if (premade_throttler_)
92 premade_throttler_->MarkPluginEssential(method);
93 else
94 PluginInstanceThrottler::RecordUnthrottleMethodMetric(method);
96 if (is_blocked_for_power_saver_poster_) {
97 is_blocked_for_power_saver_poster_ = false;
98 if (!LoadingBlocked())
99 LoadPlugin();
103 void LoadablePluginPlaceholder::ReplacePlugin(blink::WebPlugin* new_plugin) {
104 CHECK(plugin());
105 if (!new_plugin)
106 return;
107 blink::WebPluginContainer* container = plugin()->container();
108 // Set the new plugin on the container before initializing it.
109 container->setPlugin(new_plugin);
110 // Save the element in case the plugin is removed from the page during
111 // initialization.
112 blink::WebElement element = container->element();
113 bool plugin_needs_initialization =
114 !premade_throttler_ || new_plugin != premade_throttler_->GetWebPlugin();
115 if (plugin_needs_initialization && !new_plugin->initialize(container)) {
116 // We couldn't initialize the new plugin. Restore the old one and abort.
117 container->setPlugin(plugin());
118 return;
121 // The plugin has been removed from the page. Destroy the old plugin. We
122 // will be destroyed as soon as V8 garbage collects us.
123 if (!element.pluginContainer()) {
124 plugin()->destroy();
125 return;
128 // During initialization, the new plugin might have replaced itself in turn
129 // with another plugin. Make sure not to use the passed in |new_plugin| after
130 // this point.
131 new_plugin = container->plugin();
133 plugin()->RestoreTitleText();
134 container->invalidate();
135 container->reportGeometry();
136 plugin()->ReplayReceivedData(new_plugin);
137 plugin()->destroy();
140 void LoadablePluginPlaceholder::SetMessage(const base::string16& message) {
141 message_ = message;
142 if (finished_loading_)
143 UpdateMessage();
146 void LoadablePluginPlaceholder::UpdateMessage() {
147 if (!plugin())
148 return;
149 std::string script =
150 "window.setMessage(" + base::GetQuotedJSONString(message_) + ")";
151 plugin()->web_view()->mainFrame()->executeScript(
152 blink::WebScriptSource(base::UTF8ToUTF16(script)));
155 void LoadablePluginPlaceholder::PluginDestroyed() {
156 if (power_saver_enabled_) {
157 if (premade_throttler_) {
158 // Since the premade plugin has been detached from the container, it will
159 // not be automatically destroyed along with the page.
160 premade_throttler_->GetWebPlugin()->destroy();
161 premade_throttler_ = nullptr;
162 } else if (is_blocked_for_power_saver_poster_) {
163 // Record the NEVER unthrottle count only if there is no throttler.
164 PluginInstanceThrottler::RecordUnthrottleMethodMetric(
165 PluginInstanceThrottler::UNTHROTTLE_METHOD_NEVER);
168 // Prevent processing subsequent calls to MarkPluginEssential.
169 power_saver_enabled_ = false;
172 PluginPlaceholderBase::PluginDestroyed();
175 v8::Local<v8::Object> LoadablePluginPlaceholder::GetV8ScriptableObject(
176 v8::Isolate* isolate) const {
177 // Pass through JavaScript access to the underlying throttled plugin.
178 if (premade_throttler_ && premade_throttler_->GetWebPlugin()) {
179 return premade_throttler_->GetWebPlugin()->v8ScriptableObject(isolate);
181 return v8::Local<v8::Object>();
184 void LoadablePluginPlaceholder::OnUnobscuredRectUpdate(
185 const gfx::Rect& unobscured_rect) {
186 DCHECK(content::RenderThread::Get());
187 if (!power_saver_enabled_ || !premade_throttler_ || !finished_loading_)
188 return;
190 unobscured_rect_ = unobscured_rect;
192 // During a size recheck, we will get another notification into this method.
193 // Use this flag to early exit to prevent reentrancy issues.
194 if (in_size_recheck_)
195 return;
197 if (!size_update_timer_.IsRunning()) {
198 // TODO(tommycli): We have to post a delayed task to recheck the size, as
199 // Blink can report wrong sizes for partially obscured plugins while the
200 // compositing state is dirty. https://crbug.com/343769
201 size_update_timer_.Start(
202 FROM_HERE,
203 base::TimeDelta::FromMilliseconds(kSizeChangeRecheckDelayMilliseconds),
204 base::Bind(&LoadablePluginPlaceholder::RecheckSizeAndMaybeUnthrottle,
205 weak_factory_.GetWeakPtr()));
209 void LoadablePluginPlaceholder::WasShown() {
210 if (is_blocked_for_background_tab_) {
211 is_blocked_for_background_tab_ = false;
212 if (!LoadingBlocked())
213 LoadPlugin();
217 void LoadablePluginPlaceholder::OnLoadBlockedPlugins(
218 const std::string& identifier) {
219 if (!identifier.empty() && identifier != identifier_)
220 return;
222 RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Load_UI"));
223 LoadPlugin();
226 void LoadablePluginPlaceholder::OnSetIsPrerendering(bool is_prerendering) {
227 // Prerendering can only be enabled prior to a RenderView's first navigation,
228 // so no BlockedPlugin should see the notification that enables prerendering.
229 DCHECK(!is_prerendering);
230 if (is_blocked_for_prerendering_) {
231 is_blocked_for_prerendering_ = false;
232 if (!LoadingBlocked())
233 LoadPlugin();
237 void LoadablePluginPlaceholder::LoadPlugin() {
238 // This is not strictly necessary but is an important defense in case the
239 // event propagation changes between "close" vs. "click-to-play".
240 if (hidden())
241 return;
242 if (!plugin())
243 return;
244 if (!allow_loading_) {
245 NOTREACHED();
246 return;
249 if (premade_throttler_) {
250 premade_throttler_->SetHiddenForPlaceholder(false /* hidden */);
251 ReplacePlugin(premade_throttler_->GetWebPlugin());
252 premade_throttler_ = nullptr;
253 } else {
254 ReplacePlugin(CreatePlugin());
258 void LoadablePluginPlaceholder::LoadCallback() {
259 RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Load_Click"));
260 // If the user specifically clicks on the plugin content's placeholder,
261 // disable power saver throttling for this instance.
262 MarkPluginEssential(PluginInstanceThrottler::UNTHROTTLE_METHOD_BY_CLICK);
263 LoadPlugin();
266 void LoadablePluginPlaceholder::DidFinishLoadingCallback() {
267 finished_loading_ = true;
268 if (message_.length() > 0)
269 UpdateMessage();
271 // Wait for the placeholder to finish loading to hide the premade plugin.
272 // This is necessary to prevent a flicker.
273 if (premade_throttler_ && power_saver_enabled_)
274 premade_throttler_->SetHiddenForPlaceholder(true /* hidden */);
276 // Set an attribute and post an event, so browser tests can wait for the
277 // placeholder to be ready to receive simulated user input.
278 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
279 switches::kEnablePluginPlaceholderTesting)) {
280 blink::WebElement element = plugin()->container()->element();
281 element.setAttribute("placeholderLoaded", "true");
283 scoped_ptr<content::V8ValueConverter> converter(
284 content::V8ValueConverter::create());
285 base::StringValue value("placeholderLoaded");
286 blink::WebSerializedScriptValue message_data =
287 blink::WebSerializedScriptValue::serialize(converter->ToV8Value(
288 &value, element.document().frame()->mainWorldScriptContext()));
290 blink::WebDOMEvent event = element.document().createEvent("MessageEvent");
291 blink::WebDOMMessageEvent msg_event = event.to<blink::WebDOMMessageEvent>();
292 msg_event.initMessageEvent("message", // type
293 false, // canBubble
294 false, // cancelable
295 message_data, // data
296 "", // origin [*]
297 NULL, // source [*]
298 element.document(), // target document
299 ""); // lastEventId
300 element.dispatchEvent(msg_event);
304 void LoadablePluginPlaceholder::SetPluginInfo(
305 const content::WebPluginInfo& plugin_info) {
306 plugin_info_ = plugin_info;
309 const content::WebPluginInfo& LoadablePluginPlaceholder::GetPluginInfo() const {
310 return plugin_info_;
313 void LoadablePluginPlaceholder::SetIdentifier(const std::string& identifier) {
314 identifier_ = identifier;
317 const std::string& LoadablePluginPlaceholder::GetIdentifier() const {
318 return identifier_;
321 bool LoadablePluginPlaceholder::LoadingBlocked() const {
322 DCHECK(allow_loading_);
323 return is_blocked_for_background_tab_ || is_blocked_for_power_saver_poster_ ||
324 is_blocked_for_prerendering_;
327 void LoadablePluginPlaceholder::RecheckSizeAndMaybeUnthrottle() {
328 DCHECK(content::RenderThread::Get());
329 DCHECK(!in_size_recheck_);
331 if (!plugin())
332 return;
334 in_size_recheck_ = true;
336 // Re-check the size in case the reported size was incorrect.
337 plugin()->container()->reportGeometry();
339 float zoom_factor = plugin()->container()->pageZoomFactor();
341 // Adjust padding using clip coordinates to center play button for plugins
342 // that have their top or left portions obscured.
343 if (is_blocked_for_power_saver_poster_) {
344 int x = roundf(unobscured_rect_.x() / zoom_factor);
345 int y = roundf(unobscured_rect_.y() / zoom_factor);
346 std::string script =
347 base::StringPrintf("window.setPosterMargin('%dpx', '%dpx')", x, y);
348 plugin()->web_view()->mainFrame()->executeScript(
349 blink::WebScriptSource(base::UTF8ToUTF16(script)));
352 if (PluginInstanceThrottler::IsLargeContent(
353 roundf(unobscured_rect_.width() / zoom_factor),
354 roundf(unobscured_rect_.height() / zoom_factor))) {
355 MarkPluginEssential(
356 PluginInstanceThrottler::UNTHROTTLE_METHOD_BY_SIZE_CHANGE);
359 in_size_recheck_ = false;
362 } // namespace plugins