1// Copyright 2013 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.
4
5#include "components/plugins/renderer/plugin_placeholder.h"
6
7#include "base/bind.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 "base/values.h"
14#include "content/public/common/content_constants.h"
15#include "content/public/common/context_menu_params.h"
16#include "content/public/renderer/render_frame.h"
17#include "content/public/renderer/render_thread.h"
18#include "gin/object_template_builder.h"
19#include "third_party/WebKit/public/web/WebDocument.h"
20#include "third_party/WebKit/public/web/WebElement.h"
21#include "third_party/WebKit/public/web/WebInputEvent.h"
22#include "third_party/WebKit/public/web/WebLocalFrame.h"
23#include "third_party/WebKit/public/web/WebPluginContainer.h"
24#include "third_party/WebKit/public/web/WebScriptSource.h"
25#include "third_party/WebKit/public/web/WebView.h"
26#include "third_party/re2/re2/re2.h"
27
28using base::UserMetricsAction;
29using blink::WebElement;
30using blink::WebLocalFrame;
31using blink::WebMouseEvent;
32using blink::WebNode;
33using blink::WebPlugin;
34using blink::WebPluginContainer;
35using blink::WebPluginParams;
36using blink::WebScriptSource;
37using blink::WebURLRequest;
38using content::RenderThread;
39
40namespace plugins {
41
42gin::WrapperInfo PluginPlaceholder::kWrapperInfo = {gin::kEmbedderNativeGin};
43
44PluginPlaceholder::PluginPlaceholder(content::RenderFrame* render_frame,
45                                     WebLocalFrame* frame,
46                                     const WebPluginParams& params,
47                                     const std::string& html_data,
48                                     GURL placeholderDataUrl)
49    : content::RenderFrameObserver(render_frame),
50      frame_(frame),
51      plugin_params_(params),
52      plugin_(WebViewPlugin::Create(this,
53                                    render_frame->GetWebkitPreferences(),
54                                    html_data,
55                                    placeholderDataUrl)),
56      is_blocked_for_prerendering_(false),
57      allow_loading_(false),
58      hidden_(false),
59      finished_loading_(false) {}
60
61PluginPlaceholder::~PluginPlaceholder() {}
62
63gin::ObjectTemplateBuilder PluginPlaceholder::GetObjectTemplateBuilder(
64    v8::Isolate* isolate) {
65  return gin::Wrappable<PluginPlaceholder>::GetObjectTemplateBuilder(isolate)
66      .SetMethod("load", &PluginPlaceholder::LoadCallback)
67      .SetMethod("hide", &PluginPlaceholder::HideCallback)
68      .SetMethod("didFinishLoading",
69                 &PluginPlaceholder::DidFinishLoadingCallback);
70}
71
72void PluginPlaceholder::ReplacePlugin(WebPlugin* new_plugin) {
73  CHECK(plugin_);
74  if (!new_plugin) return;
75  WebPluginContainer* container = plugin_->container();
76  // Set the new plug-in on the container before initializing it.
77  container->setPlugin(new_plugin);
78  // Save the element in case the plug-in is removed from the page during
79  // initialization.
80  WebElement element = container->element();
81  if (!new_plugin->initialize(container)) {
82    // We couldn't initialize the new plug-in. Restore the old one and abort.
83    container->setPlugin(plugin_);
84    return;
85  }
86
87  // The plug-in has been removed from the page. Destroy the old plug-in. We
88  // will be destroyed as soon as V8 garbage collects us.
89  if (!element.pluginContainer()) {
90    plugin_->destroy();
91    return;
92  }
93
94  // During initialization, the new plug-in might have replaced itself in turn
95  // with another plug-in. Make sure not to use the passed in |new_plugin| after
96  // this point.
97  new_plugin = container->plugin();
98
99  plugin_->RestoreTitleText();
100  container->invalidate();
101  container->reportGeometry();
102  plugin_->ReplayReceivedData(new_plugin);
103  plugin_->destroy();
104}
105
106void PluginPlaceholder::HidePlugin() {
107  hidden_ = true;
108  if (!plugin_)
109    return;
110  WebPluginContainer* container = plugin_->container();
111  WebElement element = container->element();
112  element.setAttribute("style", "display: none;");
113  // If we have a width and height, search for a parent (often <div>) with the
114  // same dimensions. If we find such a parent, hide that as well.
115  // This makes much more uncovered page content usable (including clickable)
116  // as opposed to merely visible.
117  // TODO(cevans) -- it's a foul heurisitc but we're going to tolerate it for
118  // now for these reasons:
119  // 1) Makes the user experience better.
120  // 2) Foulness is encapsulated within this single function.
121  // 3) Confidence in no fasle positives.
122  // 4) Seems to have a good / low false negative rate at this time.
123  if (element.hasAttribute("width") && element.hasAttribute("height")) {
124    std::string width_str("width:[\\s]*");
125    width_str += element.getAttribute("width").utf8().data();
126    if (EndsWith(width_str, "px", false)) {
127      width_str = width_str.substr(0, width_str.length() - 2);
128    }
129    base::TrimWhitespace(width_str, base::TRIM_TRAILING, &width_str);
130    width_str += "[\\s]*px";
131    std::string height_str("height:[\\s]*");
132    height_str += element.getAttribute("height").utf8().data();
133    if (EndsWith(height_str, "px", false)) {
134      height_str = height_str.substr(0, height_str.length() - 2);
135    }
136    base::TrimWhitespace(height_str, base::TRIM_TRAILING, &height_str);
137    height_str += "[\\s]*px";
138    WebNode parent = element;
139    while (!parent.parentNode().isNull()) {
140      parent = parent.parentNode();
141      if (!parent.isElementNode())
142        continue;
143      element = parent.toConst<WebElement>();
144      if (element.hasAttribute("style")) {
145        std::string style_str = element.getAttribute("style").utf8();
146        if (RE2::PartialMatch(style_str, width_str) &&
147            RE2::PartialMatch(style_str, height_str))
148          element.setAttribute("style", "display: none;");
149      }
150    }
151  }
152}
153
154void PluginPlaceholder::SetMessage(const base::string16& message) {
155  message_ = message;
156  if (finished_loading_)
157    UpdateMessage();
158}
159
160void PluginPlaceholder::UpdateMessage() {
161  if (!plugin_)
162    return;
163  std::string script =
164      "window.setMessage(" + base::GetQuotedJSONString(message_) + ")";
165  plugin_->web_view()->mainFrame()->executeScript(
166      WebScriptSource(base::UTF8ToUTF16(script)));
167}
168
169void PluginPlaceholder::ShowContextMenu(const WebMouseEvent& event) {
170  // Does nothing by default. Will be overridden if a specific browser wants
171  // a context menu.
172  return;
173}
174
175void PluginPlaceholder::PluginDestroyed() {
176  plugin_ = NULL;
177}
178
179void PluginPlaceholder::OnDestruct() {
180  frame_ = NULL;
181}
182
183void PluginPlaceholder::OnLoadBlockedPlugins(const std::string& identifier) {
184  if (!identifier.empty() && identifier != identifier_)
185    return;
186
187  RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Load_UI"));
188  LoadPlugin();
189}
190
191void PluginPlaceholder::OnSetIsPrerendering(bool is_prerendering) {
192  // Prerendering can only be enabled prior to a RenderView's first navigation,
193  // so no BlockedPlugin should see the notification that enables prerendering.
194  DCHECK(!is_prerendering);
195  if (is_blocked_for_prerendering_ && !is_prerendering)
196    LoadPlugin();
197}
198
199void PluginPlaceholder::LoadPlugin() {
200  // This is not strictly necessary but is an important defense in case the
201  // event propagation changes between "close" vs. "click-to-play".
202  if (hidden_)
203    return;
204  if (!plugin_)
205    return;
206  if (!allow_loading_) {
207    NOTREACHED();
208    return;
209  }
210
211  // TODO(mmenke):  In the case of prerendering, feed into
212  //                ChromeContentRendererClient::CreatePlugin instead, to
213  //                reduce the chance of future regressions.
214  WebPlugin* plugin =
215      render_frame()->CreatePlugin(frame_, plugin_info_, plugin_params_);
216  ReplacePlugin(plugin);
217}
218
219void PluginPlaceholder::LoadCallback() {
220  RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Load_Click"));
221  LoadPlugin();
222}
223
224void PluginPlaceholder::HideCallback() {
225  RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Hide_Click"));
226  HidePlugin();
227}
228
229void PluginPlaceholder::DidFinishLoadingCallback() {
230  finished_loading_ = true;
231  if (message_.length() > 0)
232    UpdateMessage();
233}
234
235void PluginPlaceholder::SetPluginInfo(
236    const content::WebPluginInfo& plugin_info) {
237  plugin_info_ = plugin_info;
238}
239
240const content::WebPluginInfo& PluginPlaceholder::GetPluginInfo() const {
241  return plugin_info_;
242}
243
244void PluginPlaceholder::SetIdentifier(const std::string& identifier) {
245  identifier_ = identifier;
246}
247
248blink::WebLocalFrame* PluginPlaceholder::GetFrame() { return frame_; }
249
250const blink::WebPluginParams& PluginPlaceholder::GetPluginParams() const {
251  return plugin_params_;
252}
253
254}  // namespace plugins
255