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