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