Home | History | Annotate | Download | only in renderer
      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 
     27 using content::RenderThread;
     28 using content::UserMetricsAction;
     29 using blink::WebElement;
     30 using blink::WebFrame;
     31 using blink::WebMouseEvent;
     32 using blink::WebNode;
     33 using blink::WebPlugin;
     34 using blink::WebPluginContainer;
     35 using blink::WebPluginParams;
     36 using blink::WebScriptSource;
     37 using blink::WebURLRequest;
     38 using webkit_glue::CppArgumentList;
     39 using webkit_glue::CppVariant;
     40 
     41 namespace plugins {
     42 
     43 PluginPlaceholder::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 
     60 PluginPlaceholder::~PluginPlaceholder() {}
     61 
     62 void 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 
     75 void 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 
    109 void 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 
    155 void PluginPlaceholder::WillDestroyPlugin() { delete this; }
    156 
    157 void PluginPlaceholder::SetMessage(const base::string16& message) {
    158   message_ = message;
    159   if (finished_loading_)
    160     UpdateMessage();
    161 }
    162 
    163 void PluginPlaceholder::UpdateMessage() {
    164   std::string script =
    165       "window.setMessage(" + base::GetQuotedJSONString(message_) + ")";
    166   plugin_->web_view()->mainFrame()->executeScript(
    167       WebScriptSource(ASCIIToUTF16(script)));
    168 }
    169 
    170 void 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 
    176 void 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 
    184 void 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 
    192 void 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 
    210 void PluginPlaceholder::LoadCallback(const CppArgumentList& args,
    211                                      CppVariant* result) {
    212   RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Load_Click"));
    213   LoadPlugin();
    214 }
    215 
    216 void PluginPlaceholder::HideCallback(const CppArgumentList& args,
    217                                      CppVariant* result) {
    218   RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Hide_Click"));
    219   HidePlugin();
    220 }
    221 
    222 void PluginPlaceholder::DidFinishLoadingCallback(const CppArgumentList& args,
    223                                                  CppVariant* result) {
    224   finished_loading_ = true;
    225   if (message_.length() > 0)
    226     UpdateMessage();
    227 }
    228 
    229 void PluginPlaceholder::SetPluginInfo(
    230     const content::WebPluginInfo& plugin_info) {
    231   plugin_info_ = plugin_info;
    232 }
    233 
    234 const content::WebPluginInfo& PluginPlaceholder::GetPluginInfo() const {
    235   return plugin_info_;
    236 }
    237 
    238 void PluginPlaceholder::SetIdentifier(const std::string& identifier) {
    239   identifier_ = identifier;
    240 }
    241 
    242 blink::WebFrame* PluginPlaceholder::GetFrame() { return frame_; }
    243 
    244 const blink::WebPluginParams& PluginPlaceholder::GetPluginParams() const {
    245   return plugin_params_;
    246 }
    247 
    248 }  // namespace plugins
    249