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