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