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 "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 
     28 using base::UserMetricsAction;
     29 using blink::WebElement;
     30 using blink::WebLocalFrame;
     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 content::RenderThread;
     39 
     40 namespace plugins {
     41 
     42 gin::WrapperInfo PluginPlaceholder::kWrapperInfo = {gin::kEmbedderNativeGin};
     43 
     44 PluginPlaceholder::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 
     61 PluginPlaceholder::~PluginPlaceholder() {}
     62 
     63 gin::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 
     72 void 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 
    106 void 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 
    154 void PluginPlaceholder::SetMessage(const base::string16& message) {
    155   message_ = message;
    156   if (finished_loading_)
    157     UpdateMessage();
    158 }
    159 
    160 void 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::ASCIIToUTF16(script)));
    167 }
    168 
    169 void 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 
    175 void PluginPlaceholder::PluginDestroyed() {
    176   plugin_ = NULL;
    177 }
    178 
    179 void PluginPlaceholder::OnDestruct() {
    180   frame_ = NULL;
    181 }
    182 
    183 void 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 
    191 void 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 
    199 void 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 
    219 void PluginPlaceholder::LoadCallback() {
    220   RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Load_Click"));
    221   LoadPlugin();
    222 }
    223 
    224 void PluginPlaceholder::HideCallback() {
    225   RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Hide_Click"));
    226   HidePlugin();
    227 }
    228 
    229 void PluginPlaceholder::DidFinishLoadingCallback() {
    230   finished_loading_ = true;
    231   if (message_.length() > 0)
    232     UpdateMessage();
    233 }
    234 
    235 void PluginPlaceholder::SetPluginInfo(
    236     const content::WebPluginInfo& plugin_info) {
    237   plugin_info_ = plugin_info;
    238 }
    239 
    240 const content::WebPluginInfo& PluginPlaceholder::GetPluginInfo() const {
    241   return plugin_info_;
    242 }
    243 
    244 void PluginPlaceholder::SetIdentifier(const std::string& identifier) {
    245   identifier_ = identifier;
    246 }
    247 
    248 blink::WebLocalFrame* PluginPlaceholder::GetFrame() { return frame_; }
    249 
    250 const blink::WebPluginParams& PluginPlaceholder::GetPluginParams() const {
    251   return plugin_params_;
    252 }
    253 
    254 }  // namespace plugins
    255