blob: df85dd562bd181041fd12c095143e0a83406458a [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/plugins/renderer/plugin_placeholder.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/json/string_escape.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "content/public/common/content_constants.h"
#include "content/public/common/context_menu_params.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_thread.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebElement.h"
#include "third_party/WebKit/public/web/WebFrame.h"
#include "third_party/WebKit/public/web/WebInputEvent.h"
#include "third_party/WebKit/public/web/WebPluginContainer.h"
#include "third_party/WebKit/public/web/WebScriptSource.h"
#include "third_party/WebKit/public/web/WebView.h"
#include "third_party/re2/re2/re2.h"
using content::RenderThread;
using content::UserMetricsAction;
using blink::WebElement;
using blink::WebFrame;
using blink::WebMouseEvent;
using blink::WebNode;
using blink::WebPlugin;
using blink::WebPluginContainer;
using blink::WebPluginParams;
using blink::WebScriptSource;
using blink::WebURLRequest;
using webkit_glue::CppArgumentList;
using webkit_glue::CppVariant;
namespace plugins {
PluginPlaceholder::PluginPlaceholder(content::RenderFrame* render_frame,
WebFrame* frame,
const WebPluginParams& params,
const std::string& html_data,
GURL placeholderDataUrl)
: content::RenderFrameObserver(render_frame),
frame_(frame),
plugin_params_(params),
plugin_(WebViewPlugin::Create(this,
render_frame->GetWebkitPreferences(),
html_data,
placeholderDataUrl)),
is_blocked_for_prerendering_(false),
allow_loading_(false),
hidden_(false),
finished_loading_(false) {}
PluginPlaceholder::~PluginPlaceholder() {}
void PluginPlaceholder::BindWebFrame(WebFrame* frame) {
BindToJavascript(frame, "plugin");
BindCallback(
"load",
base::Bind(&PluginPlaceholder::LoadCallback, base::Unretained(this)));
BindCallback(
"hide",
base::Bind(&PluginPlaceholder::HideCallback, base::Unretained(this)));
BindCallback("didFinishLoading",
base::Bind(&PluginPlaceholder::DidFinishLoadingCallback,
base::Unretained(this)));
}
void PluginPlaceholder::ReplacePlugin(WebPlugin* new_plugin) {
CHECK(plugin_);
if (!new_plugin) return;
WebPluginContainer* container = plugin_->container();
// Set the new plug-in on the container before initializing it.
container->setPlugin(new_plugin);
// Save the element in case the plug-in is removed from the page during
// initialization.
WebElement element = container->element();
if (!new_plugin->initialize(container)) {
// We couldn't initialize the new plug-in. Restore the old one and abort.
container->setPlugin(plugin_);
return;
}
// The plug-in has been removed from the page. Destroy the old plug-in
// (which will destroy us).
if (!element.pluginContainer()) {
plugin_->destroy();
return;
}
// During initialization, the new plug-in might have replaced itself in turn
// with another plug-in. Make sure not to use the passed in |new_plugin| after
// this point.
new_plugin = container->plugin();
plugin_->RestoreTitleText();
container->invalidate();
container->reportGeometry();
plugin_->ReplayReceivedData(new_plugin);
plugin_->destroy();
}
void PluginPlaceholder::HidePlugin() {
hidden_ = true;
WebPluginContainer* container = plugin_->container();
WebElement element = container->element();
element.setAttribute("style", "display: none;");
// If we have a width and height, search for a parent (often <div>) with the
// same dimensions. If we find such a parent, hide that as well.
// This makes much more uncovered page content usable (including clickable)
// as opposed to merely visible.
// TODO(cevans) -- it's a foul heurisitc but we're going to tolerate it for
// now for these reasons:
// 1) Makes the user experience better.
// 2) Foulness is encapsulated within this single function.
// 3) Confidence in no fasle positives.
// 4) Seems to have a good / low false negative rate at this time.
if (element.hasAttribute("width") && element.hasAttribute("height")) {
std::string width_str("width:[\\s]*");
width_str += element.getAttribute("width").utf8().data();
if (EndsWith(width_str, "px", false)) {
width_str = width_str.substr(0, width_str.length() - 2);
}
TrimWhitespace(width_str, TRIM_TRAILING, &width_str);
width_str += "[\\s]*px";
std::string height_str("height:[\\s]*");
height_str += element.getAttribute("height").utf8().data();
if (EndsWith(height_str, "px", false)) {
height_str = height_str.substr(0, height_str.length() - 2);
}
TrimWhitespace(height_str, TRIM_TRAILING, &height_str);
height_str += "[\\s]*px";
WebNode parent = element;
while (!parent.parentNode().isNull()) {
parent = parent.parentNode();
if (!parent.isElementNode())
continue;
element = parent.toConst<WebElement>();
if (element.hasAttribute("style")) {
std::string style_str = element.getAttribute("style").utf8();
if (RE2::PartialMatch(style_str, width_str) &&
RE2::PartialMatch(style_str, height_str))
element.setAttribute("style", "display: none;");
}
}
}
}
void PluginPlaceholder::WillDestroyPlugin() { delete this; }
void PluginPlaceholder::SetMessage(const base::string16& message) {
message_ = message;
if (finished_loading_)
UpdateMessage();
}
void PluginPlaceholder::UpdateMessage() {
std::string script =
"window.setMessage(" + base::GetQuotedJSONString(message_) + ")";
plugin_->web_view()->mainFrame()->executeScript(
WebScriptSource(ASCIIToUTF16(script)));
}
void PluginPlaceholder::ShowContextMenu(const WebMouseEvent& event) {
// Does nothing by default. Will be overridden if a specific browser wants
// a context menu.
return;
}
void PluginPlaceholder::OnLoadBlockedPlugins(const std::string& identifier) {
if (!identifier.empty() && identifier != identifier_)
return;
RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Load_UI"));
LoadPlugin();
}
void PluginPlaceholder::OnSetIsPrerendering(bool is_prerendering) {
// Prerendering can only be enabled prior to a RenderView's first navigation,
// so no BlockedPlugin should see the notification that enables prerendering.
DCHECK(!is_prerendering);
if (is_blocked_for_prerendering_ && !is_prerendering)
LoadPlugin();
}
void PluginPlaceholder::LoadPlugin() {
// This is not strictly necessary but is an important defense in case the
// event propagation changes between "close" vs. "click-to-play".
if (hidden_)
return;
if (!allow_loading_) {
NOTREACHED();
return;
}
// TODO(mmenke): In the case of prerendering, feed into
// ChromeContentRendererClient::CreatePlugin instead, to
// reduce the chance of future regressions.
WebPlugin* plugin =
render_frame()->CreatePlugin(frame_, plugin_info_, plugin_params_);
ReplacePlugin(plugin);
}
void PluginPlaceholder::LoadCallback(const CppArgumentList& args,
CppVariant* result) {
RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Load_Click"));
LoadPlugin();
}
void PluginPlaceholder::HideCallback(const CppArgumentList& args,
CppVariant* result) {
RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Hide_Click"));
HidePlugin();
}
void PluginPlaceholder::DidFinishLoadingCallback(const CppArgumentList& args,
CppVariant* result) {
finished_loading_ = true;
if (message_.length() > 0)
UpdateMessage();
}
void PluginPlaceholder::SetPluginInfo(
const content::WebPluginInfo& plugin_info) {
plugin_info_ = plugin_info;
}
const content::WebPluginInfo& PluginPlaceholder::GetPluginInfo() const {
return plugin_info_;
}
void PluginPlaceholder::SetIdentifier(const std::string& identifier) {
identifier_ = identifier;
}
blink::WebFrame* PluginPlaceholder::GetFrame() { return frame_; }
const blink::WebPluginParams& PluginPlaceholder::GetPluginParams() const {
return plugin_params_;
}
} // namespace plugins