[Android] Add tests for toolbar of Chrome Custom Tabs
[chromium-blink-merge.git] / components / plugins / renderer / loadable_plugin_placeholder.cc
blobea288e2f424c972346428367c6863f9c53c60ea8
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/string_util.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 "third_party/re2/re2/re2.h"
34 using base::UserMetricsAction;
35 using blink::WebElement;
36 using blink::WebLocalFrame;
37 using blink::WebMouseEvent;
38 using blink::WebNode;
39 using blink::WebPlugin;
40 using blink::WebPluginContainer;
41 using blink::WebPluginParams;
42 using blink::WebScriptSource;
43 using blink::WebURLRequest;
44 using content::PluginInstanceThrottler;
45 using content::RenderThread;
47 namespace plugins {
49 #if defined(ENABLE_PLUGINS)
50 // TODO(tommycli): After an unthrottling size update, re-check the size after
51 // this delay, as Blink can report incorrect sizes to plugins while the
52 // compositing state is dirty. Chosen because it seems to work.
53 const int kSizeChangeRecheckDelayMilliseconds = 100;
55 void LoadablePluginPlaceholder::BlockForPowerSaverPoster() {
56 DCHECK(!is_blocked_for_power_saver_poster_);
57 is_blocked_for_power_saver_poster_ = true;
59 render_frame()->RegisterPeripheralPlugin(
60 GURL(GetPluginParams().url).GetOrigin(),
61 base::Bind(&LoadablePluginPlaceholder::MarkPluginEssential,
62 weak_factory_.GetWeakPtr(),
63 PluginInstanceThrottler::UNTHROTTLE_METHOD_BY_WHITELIST));
66 void LoadablePluginPlaceholder::SetPremadePlugin(
67 content::PluginInstanceThrottler* throttler) {
68 DCHECK(throttler);
69 DCHECK(!premade_throttler_);
70 premade_throttler_ = throttler;
72 #endif
74 LoadablePluginPlaceholder::LoadablePluginPlaceholder(
75 content::RenderFrame* render_frame,
76 WebLocalFrame* frame,
77 const WebPluginParams& params,
78 const std::string& html_data,
79 GURL placeholderDataUrl)
80 : PluginPlaceholder(render_frame,
81 frame,
82 params,
83 html_data,
84 placeholderDataUrl),
85 is_blocked_for_background_tab_(false),
86 is_blocked_for_prerendering_(false),
87 is_blocked_for_power_saver_poster_(false),
88 power_saver_enabled_(false),
89 premade_throttler_(nullptr),
90 allow_loading_(false),
91 hidden_(false),
92 finished_loading_(false),
93 in_size_recheck_(false),
94 weak_factory_(this) {
97 LoadablePluginPlaceholder::~LoadablePluginPlaceholder() {
100 #if defined(ENABLE_PLUGINS)
101 void LoadablePluginPlaceholder::MarkPluginEssential(
102 PluginInstanceThrottler::PowerSaverUnthrottleMethod method) {
103 if (!power_saver_enabled_)
104 return;
106 power_saver_enabled_ = false;
108 if (premade_throttler_)
109 premade_throttler_->MarkPluginEssential(method);
110 else
111 PluginInstanceThrottler::RecordUnthrottleMethodMetric(method);
113 if (is_blocked_for_power_saver_poster_) {
114 is_blocked_for_power_saver_poster_ = false;
115 if (!LoadingBlocked())
116 LoadPlugin();
119 #endif
121 void LoadablePluginPlaceholder::BindWebFrame(blink::WebFrame* frame) {
122 v8::Isolate* isolate = blink::mainThreadIsolate();
123 v8::HandleScope handle_scope(isolate);
124 v8::Local<v8::Context> context = frame->mainWorldScriptContext();
125 DCHECK(!context.IsEmpty());
127 v8::Context::Scope context_scope(context);
128 v8::Local<v8::Object> global = context->Global();
129 global->Set(gin::StringToV8(isolate, "plugin"),
130 gin::CreateHandle(isolate, this).ToV8());
133 gin::ObjectTemplateBuilder LoadablePluginPlaceholder::GetObjectTemplateBuilder(
134 v8::Isolate* isolate) {
135 return gin::Wrappable<PluginPlaceholder>::GetObjectTemplateBuilder(isolate)
136 .SetMethod("load", &LoadablePluginPlaceholder::LoadCallback)
137 .SetMethod("hide", &LoadablePluginPlaceholder::HideCallback)
138 .SetMethod("didFinishLoading",
139 &LoadablePluginPlaceholder::DidFinishLoadingCallback);
142 void LoadablePluginPlaceholder::ReplacePlugin(WebPlugin* new_plugin) {
143 CHECK(plugin());
144 if (!new_plugin)
145 return;
146 WebPluginContainer* container = plugin()->container();
147 // Set the new plugin on the container before initializing it.
148 container->setPlugin(new_plugin);
149 // Save the element in case the plugin is removed from the page during
150 // initialization.
151 WebElement element = container->element();
152 bool plugin_needs_initialization =
153 !premade_throttler_ || new_plugin != premade_throttler_->GetWebPlugin();
154 if (plugin_needs_initialization && !new_plugin->initialize(container)) {
155 // We couldn't initialize the new plugin. Restore the old one and abort.
156 container->setPlugin(plugin());
157 return;
160 // The plugin has been removed from the page. Destroy the old plugin. We
161 // will be destroyed as soon as V8 garbage collects us.
162 if (!element.pluginContainer()) {
163 plugin()->destroy();
164 return;
167 // During initialization, the new plugin might have replaced itself in turn
168 // with another plugin. Make sure not to use the passed in |new_plugin| after
169 // this point.
170 new_plugin = container->plugin();
172 plugin()->RestoreTitleText();
173 container->invalidate();
174 container->reportGeometry();
175 plugin()->ReplayReceivedData(new_plugin);
176 plugin()->destroy();
179 void LoadablePluginPlaceholder::HidePlugin() {
180 hidden_ = true;
181 if (!plugin())
182 return;
183 WebPluginContainer* container = plugin()->container();
184 WebElement element = container->element();
185 element.setAttribute("style", "display: none;");
186 // If we have a width and height, search for a parent (often <div>) with the
187 // same dimensions. If we find such a parent, hide that as well.
188 // This makes much more uncovered page content usable (including clickable)
189 // as opposed to merely visible.
190 // TODO(cevans) -- it's a foul heuristic but we're going to tolerate it for
191 // now for these reasons:
192 // 1) Makes the user experience better.
193 // 2) Foulness is encapsulated within this single function.
194 // 3) Confidence in no fasle positives.
195 // 4) Seems to have a good / low false negative rate at this time.
196 if (element.hasAttribute("width") && element.hasAttribute("height")) {
197 std::string width_str("width:[\\s]*");
198 width_str += element.getAttribute("width").utf8().data();
199 if (EndsWith(width_str, "px", false)) {
200 width_str = width_str.substr(0, width_str.length() - 2);
202 base::TrimWhitespace(width_str, base::TRIM_TRAILING, &width_str);
203 width_str += "[\\s]*px";
204 std::string height_str("height:[\\s]*");
205 height_str += element.getAttribute("height").utf8().data();
206 if (EndsWith(height_str, "px", false)) {
207 height_str = height_str.substr(0, height_str.length() - 2);
209 base::TrimWhitespace(height_str, base::TRIM_TRAILING, &height_str);
210 height_str += "[\\s]*px";
211 WebNode parent = element;
212 while (!parent.parentNode().isNull()) {
213 parent = parent.parentNode();
214 if (!parent.isElementNode())
215 continue;
216 element = parent.toConst<WebElement>();
217 if (element.hasAttribute("style")) {
218 std::string style_str = element.getAttribute("style").utf8();
219 if (RE2::PartialMatch(style_str, width_str) &&
220 RE2::PartialMatch(style_str, height_str))
221 element.setAttribute("style", "display: none;");
227 void LoadablePluginPlaceholder::SetMessage(const base::string16& message) {
228 message_ = message;
229 if (finished_loading_)
230 UpdateMessage();
233 void LoadablePluginPlaceholder::UpdateMessage() {
234 if (!plugin())
235 return;
236 std::string script =
237 "window.setMessage(" + base::GetQuotedJSONString(message_) + ")";
238 plugin()->web_view()->mainFrame()->executeScript(
239 WebScriptSource(base::UTF8ToUTF16(script)));
242 void LoadablePluginPlaceholder::PluginDestroyed() {
243 #if defined(ENABLE_PLUGINS)
244 if (power_saver_enabled_) {
245 if (premade_throttler_) {
246 // Since the premade plugin has been detached from the container, it will
247 // not be automatically destroyed along with the page.
248 premade_throttler_->GetWebPlugin()->destroy();
249 premade_throttler_ = nullptr;
250 } else if (is_blocked_for_power_saver_poster_) {
251 // Record the NEVER unthrottle count only if there is no throttler.
252 PluginInstanceThrottler::RecordUnthrottleMethodMetric(
253 PluginInstanceThrottler::UNTHROTTLE_METHOD_NEVER);
256 // Prevent processing subsequent calls to MarkPluginEssential.
257 power_saver_enabled_ = false;
259 #endif
261 PluginPlaceholder::PluginDestroyed();
264 v8::Local<v8::Object> LoadablePluginPlaceholder::GetV8ScriptableObject(
265 v8::Isolate* isolate) const {
266 #if defined(ENABLE_PLUGINS)
267 // Pass through JavaScript access to the underlying throttled plugin.
268 if (premade_throttler_ && premade_throttler_->GetWebPlugin()) {
269 return premade_throttler_->GetWebPlugin()->v8ScriptableObject(isolate);
271 #endif
272 return v8::Local<v8::Object>();
275 #if defined(ENABLE_PLUGINS)
276 void LoadablePluginPlaceholder::OnUnobscuredSizeUpdate(
277 const gfx::Size& unobscured_size) {
278 DCHECK(
279 content::RenderThread::Get()->GetTaskRunner()->BelongsToCurrentThread());
280 if (!power_saver_enabled_ || !premade_throttler_ || !finished_loading_)
281 return;
283 unobscured_size_ = unobscured_size;
285 // During a size recheck, we will get another notification into this method.
286 // Use this flag to early exit to prevent reentrancy issues.
287 if (in_size_recheck_)
288 return;
290 if (PluginInstanceThrottler::IsLargeContent(unobscured_size.width(),
291 unobscured_size.height())) {
292 if (!size_update_timer_.IsRunning()) {
293 // TODO(tommycli): We have to post a delayed task to recheck the size, as
294 // Blink can report wrong sizes for partially obscured plugins while the
295 // compositing state is dirty. https://crbug.com/343769
296 size_update_timer_.Start(
297 FROM_HERE, base::TimeDelta::FromMilliseconds(
298 kSizeChangeRecheckDelayMilliseconds),
299 base::Bind(&LoadablePluginPlaceholder::RecheckSizeAndMaybeUnthrottle,
300 weak_factory_.GetWeakPtr()));
302 } else {
303 // Cancel any pending unthrottle due to resize calls.
304 size_update_timer_.Stop();
307 #endif // defined(ENABLE_PLUGINS)
309 void LoadablePluginPlaceholder::WasShown() {
310 if (is_blocked_for_background_tab_) {
311 is_blocked_for_background_tab_ = false;
312 if (!LoadingBlocked())
313 LoadPlugin();
317 void LoadablePluginPlaceholder::OnLoadBlockedPlugins(
318 const std::string& identifier) {
319 if (!identifier.empty() && identifier != identifier_)
320 return;
322 RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Load_UI"));
323 LoadPlugin();
326 void LoadablePluginPlaceholder::OnSetIsPrerendering(bool is_prerendering) {
327 // Prerendering can only be enabled prior to a RenderView's first navigation,
328 // so no BlockedPlugin should see the notification that enables prerendering.
329 DCHECK(!is_prerendering);
330 if (is_blocked_for_prerendering_) {
331 is_blocked_for_prerendering_ = false;
332 if (!LoadingBlocked())
333 LoadPlugin();
337 void LoadablePluginPlaceholder::LoadPlugin() {
338 // This is not strictly necessary but is an important defense in case the
339 // event propagation changes between "close" vs. "click-to-play".
340 if (hidden_)
341 return;
342 if (!plugin())
343 return;
344 if (!allow_loading_) {
345 NOTREACHED();
346 return;
349 if (premade_throttler_) {
350 premade_throttler_->SetHiddenForPlaceholder(false /* hidden */);
351 ReplacePlugin(premade_throttler_->GetWebPlugin());
352 premade_throttler_ = nullptr;
353 } else {
354 ReplacePlugin(CreatePlugin());
358 void LoadablePluginPlaceholder::LoadCallback() {
359 RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Load_Click"));
360 #if defined(ENABLE_PLUGINS)
361 // If the user specifically clicks on the plugin content's placeholder,
362 // disable power saver throttling for this instance.
363 MarkPluginEssential(PluginInstanceThrottler::UNTHROTTLE_METHOD_BY_CLICK);
364 #endif
365 LoadPlugin();
368 void LoadablePluginPlaceholder::HideCallback() {
369 RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Hide_Click"));
370 HidePlugin();
373 void LoadablePluginPlaceholder::DidFinishLoadingCallback() {
374 finished_loading_ = true;
375 if (message_.length() > 0)
376 UpdateMessage();
378 // Wait for the placeholder to finish loading to hide the premade plugin.
379 // This is necessary to prevent a flicker.
380 if (premade_throttler_ && power_saver_enabled_)
381 premade_throttler_->SetHiddenForPlaceholder(true /* hidden */);
383 // Set an attribute and post an event, so browser tests can wait for the
384 // placeholder to be ready to receive simulated user input.
385 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
386 switches::kEnablePluginPlaceholderTesting)) {
387 WebElement element = plugin()->container()->element();
388 element.setAttribute("placeholderLoaded", "true");
390 scoped_ptr<content::V8ValueConverter> converter(
391 content::V8ValueConverter::create());
392 base::StringValue value("placeholderLoaded");
393 blink::WebSerializedScriptValue message_data =
394 blink::WebSerializedScriptValue::serialize(converter->ToV8Value(
395 &value, element.document().frame()->mainWorldScriptContext()));
397 blink::WebDOMEvent event = element.document().createEvent("MessageEvent");
398 blink::WebDOMMessageEvent msg_event = event.to<blink::WebDOMMessageEvent>();
399 msg_event.initMessageEvent("message", // type
400 false, // canBubble
401 false, // cancelable
402 message_data, // data
403 "", // origin [*]
404 NULL, // source [*]
405 ""); // lastEventId
406 element.dispatchEvent(msg_event);
410 void LoadablePluginPlaceholder::SetPluginInfo(
411 const content::WebPluginInfo& plugin_info) {
412 plugin_info_ = plugin_info;
415 const content::WebPluginInfo& LoadablePluginPlaceholder::GetPluginInfo() const {
416 return plugin_info_;
419 void LoadablePluginPlaceholder::SetIdentifier(const std::string& identifier) {
420 identifier_ = identifier;
423 const std::string& LoadablePluginPlaceholder::GetIdentifier() const {
424 return identifier_;
427 bool LoadablePluginPlaceholder::LoadingBlocked() const {
428 DCHECK(allow_loading_);
429 return is_blocked_for_background_tab_ || is_blocked_for_power_saver_poster_ ||
430 is_blocked_for_prerendering_;
433 #if defined(ENABLE_PLUGINS)
434 void LoadablePluginPlaceholder::RecheckSizeAndMaybeUnthrottle() {
435 DCHECK(
436 content::RenderThread::Get()->GetTaskRunner()->BelongsToCurrentThread());
437 DCHECK(!in_size_recheck_);
438 in_size_recheck_ = true;
440 // Re-check the size in case the reported size was incorrect.
441 plugin()->container()->reportGeometry();
443 if (PluginInstanceThrottler::IsLargeContent(unobscured_size_.width(),
444 unobscured_size_.height())) {
445 MarkPluginEssential(
446 PluginInstanceThrottler::UNTHROTTLE_METHOD_BY_SIZE_CHANGE);
449 in_size_recheck_ = false;
451 #endif // defined(ENABLE_PLUGINS)
453 } // namespace plugins