Don't preload rarely seen large images
[chromium-blink-merge.git] / components / plugins / renderer / loadable_plugin_placeholder.cc
blobeb884da536803442fcc9e0fca1c2b6c991acf02b
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/utf_string_conversions.h"
13 #include "base/thread_task_runner_handle.h"
14 #include "base/values.h"
15 #include "content/public/child/v8_value_converter.h"
16 #include "content/public/common/content_switches.h"
17 #include "content/public/renderer/render_frame.h"
18 #include "content/public/renderer/render_thread.h"
19 #include "gin/handle.h"
20 #include "gin/object_template_builder.h"
21 #include "third_party/WebKit/public/web/WebDOMMessageEvent.h"
22 #include "third_party/WebKit/public/web/WebDocument.h"
23 #include "third_party/WebKit/public/web/WebElement.h"
24 #include "third_party/WebKit/public/web/WebInputEvent.h"
25 #include "third_party/WebKit/public/web/WebKit.h"
26 #include "third_party/WebKit/public/web/WebLocalFrame.h"
27 #include "third_party/WebKit/public/web/WebPluginContainer.h"
28 #include "third_party/WebKit/public/web/WebScriptSource.h"
29 #include "third_party/WebKit/public/web/WebSerializedScriptValue.h"
30 #include "third_party/WebKit/public/web/WebView.h"
32 using base::UserMetricsAction;
33 using content::PluginInstanceThrottler;
34 using content::RenderThread;
36 namespace plugins {
38 // TODO(tommycli): After an unthrottling size update, re-check the size after
39 // this delay, as Blink can report incorrect sizes to plugins while the
40 // compositing state is 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) {
56 DCHECK(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_(true),
73 finished_loading_(false),
74 in_size_recheck_(false),
75 weak_factory_(this) {
78 LoadablePluginPlaceholder::~LoadablePluginPlaceholder() {
81 void LoadablePluginPlaceholder::MarkPluginEssential(
82 PluginInstanceThrottler::PowerSaverUnthrottleMethod method) {
83 if (!power_saver_enabled_)
84 return;
86 power_saver_enabled_ = false;
88 if (premade_throttler_)
89 premade_throttler_->MarkPluginEssential(method);
90 else
91 PluginInstanceThrottler::RecordUnthrottleMethodMetric(method);
93 if (is_blocked_for_power_saver_poster_) {
94 is_blocked_for_power_saver_poster_ = false;
95 if (!LoadingBlocked())
96 LoadPlugin();
100 void LoadablePluginPlaceholder::ReplacePlugin(blink::WebPlugin* new_plugin) {
101 CHECK(plugin());
102 if (!new_plugin)
103 return;
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
108 // initialization.
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());
115 return;
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()) {
121 plugin()->destroy();
122 return;
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
127 // this point.
128 new_plugin = container->plugin();
130 plugin()->RestoreTitleText();
131 container->invalidate();
132 container->reportGeometry();
133 plugin()->ReplayReceivedData(new_plugin);
134 plugin()->destroy();
137 void LoadablePluginPlaceholder::SetMessage(const base::string16& message) {
138 message_ = message;
139 if (finished_loading_)
140 UpdateMessage();
143 void LoadablePluginPlaceholder::UpdateMessage() {
144 if (!plugin())
145 return;
146 std::string script =
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::OnUnobscuredSizeUpdate(
182 const gfx::Size& unobscured_size) {
183 DCHECK(
184 content::RenderThread::Get()->GetTaskRunner()->BelongsToCurrentThread());
185 if (!power_saver_enabled_ || !premade_throttler_ || !finished_loading_)
186 return;
188 unobscured_size_ = unobscured_size;
190 // During a size recheck, we will get another notification into this method.
191 // Use this flag to early exit to prevent reentrancy issues.
192 if (in_size_recheck_)
193 return;
195 if (PluginInstanceThrottler::IsLargeContent(unobscured_size.width(),
196 unobscured_size.height())) {
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, base::TimeDelta::FromMilliseconds(
203 kSizeChangeRecheckDelayMilliseconds),
204 base::Bind(&LoadablePluginPlaceholder::RecheckSizeAndMaybeUnthrottle,
205 weak_factory_.GetWeakPtr()));
207 } else {
208 // Cancel any pending unthrottle due to resize calls.
209 size_update_timer_.Stop();
213 void LoadablePluginPlaceholder::WasShown() {
214 if (is_blocked_for_background_tab_) {
215 is_blocked_for_background_tab_ = false;
216 if (!LoadingBlocked())
217 LoadPlugin();
221 void LoadablePluginPlaceholder::OnLoadBlockedPlugins(
222 const std::string& identifier) {
223 if (!identifier.empty() && identifier != identifier_)
224 return;
226 RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Load_UI"));
227 LoadPlugin();
230 void LoadablePluginPlaceholder::OnSetIsPrerendering(bool is_prerendering) {
231 // Prerendering can only be enabled prior to a RenderView's first navigation,
232 // so no BlockedPlugin should see the notification that enables prerendering.
233 DCHECK(!is_prerendering);
234 if (is_blocked_for_prerendering_) {
235 is_blocked_for_prerendering_ = false;
236 if (!LoadingBlocked())
237 LoadPlugin();
241 void LoadablePluginPlaceholder::LoadPlugin() {
242 // This is not strictly necessary but is an important defense in case the
243 // event propagation changes between "close" vs. "click-to-play".
244 if (hidden())
245 return;
246 if (!plugin())
247 return;
248 if (!allow_loading_) {
249 NOTREACHED();
250 return;
253 if (premade_throttler_) {
254 premade_throttler_->SetHiddenForPlaceholder(false /* hidden */);
255 ReplacePlugin(premade_throttler_->GetWebPlugin());
256 premade_throttler_ = nullptr;
257 } else {
258 ReplacePlugin(CreatePlugin());
262 void LoadablePluginPlaceholder::LoadCallback() {
263 RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Load_Click"));
264 // If the user specifically clicks on the plugin content's placeholder,
265 // disable power saver throttling for this instance.
266 MarkPluginEssential(PluginInstanceThrottler::UNTHROTTLE_METHOD_BY_CLICK);
267 LoadPlugin();
270 void LoadablePluginPlaceholder::DidFinishLoadingCallback() {
271 finished_loading_ = true;
272 if (message_.length() > 0)
273 UpdateMessage();
275 // Wait for the placeholder to finish loading to hide the premade plugin.
276 // This is necessary to prevent a flicker.
277 if (premade_throttler_ && power_saver_enabled_)
278 premade_throttler_->SetHiddenForPlaceholder(true /* hidden */);
280 // Set an attribute and post an event, so browser tests can wait for the
281 // placeholder to be ready to receive simulated user input.
282 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
283 switches::kEnablePluginPlaceholderTesting)) {
284 blink::WebElement element = plugin()->container()->element();
285 element.setAttribute("placeholderLoaded", "true");
287 scoped_ptr<content::V8ValueConverter> converter(
288 content::V8ValueConverter::create());
289 base::StringValue value("placeholderLoaded");
290 blink::WebSerializedScriptValue message_data =
291 blink::WebSerializedScriptValue::serialize(converter->ToV8Value(
292 &value, element.document().frame()->mainWorldScriptContext()));
294 blink::WebDOMEvent event = element.document().createEvent("MessageEvent");
295 blink::WebDOMMessageEvent msg_event = event.to<blink::WebDOMMessageEvent>();
296 msg_event.initMessageEvent("message", // type
297 false, // canBubble
298 false, // cancelable
299 message_data, // data
300 "", // origin [*]
301 NULL, // source [*]
302 ""); // lastEventId
303 element.dispatchEvent(msg_event);
307 void LoadablePluginPlaceholder::SetPluginInfo(
308 const content::WebPluginInfo& plugin_info) {
309 plugin_info_ = plugin_info;
312 const content::WebPluginInfo& LoadablePluginPlaceholder::GetPluginInfo() const {
313 return plugin_info_;
316 void LoadablePluginPlaceholder::SetIdentifier(const std::string& identifier) {
317 identifier_ = identifier;
320 const std::string& LoadablePluginPlaceholder::GetIdentifier() const {
321 return identifier_;
324 bool LoadablePluginPlaceholder::LoadingBlocked() const {
325 DCHECK(allow_loading_);
326 return is_blocked_for_background_tab_ || is_blocked_for_power_saver_poster_ ||
327 is_blocked_for_prerendering_;
330 void LoadablePluginPlaceholder::RecheckSizeAndMaybeUnthrottle() {
331 DCHECK(
332 content::RenderThread::Get()->GetTaskRunner()->BelongsToCurrentThread());
333 DCHECK(!in_size_recheck_);
335 if (!plugin())
336 return;
338 in_size_recheck_ = true;
340 // Re-check the size in case the reported size was incorrect.
341 plugin()->container()->reportGeometry();
343 if (PluginInstanceThrottler::IsLargeContent(unobscured_size_.width(),
344 unobscured_size_.height())) {
345 MarkPluginEssential(
346 PluginInstanceThrottler::UNTHROTTLE_METHOD_BY_SIZE_CHANGE);
349 in_size_recheck_ = false;
352 } // namespace plugins