// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/plugins/renderer/loadable_plugin_placeholder.h"

#include <memory>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/json/string_escape.h"
#include "base/metrics/user_metrics_action.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_thread.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/public/web/web_plugin_container.h"
#include "third_party/blink/public/web/web_script_source.h"
#include "third_party/blink/public/web/web_view.h"
#include "url/gurl.h"
#include "url/origin.h"

using base::UserMetricsAction;
using content::RenderFrame;
using content::RenderThread;

namespace plugins {

void LoadablePluginPlaceholder::MaybeLoadBlockedPlugin(
    const std::string& identifier) {
  if (!identifier.empty() && identifier != identifier_)
    return;

  RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Load_UI"));
  LoadPlugin();
}

LoadablePluginPlaceholder::LoadablePluginPlaceholder(
    RenderFrame* render_frame,
    const blink::WebPluginParams& params)
    : PluginPlaceholderBase(render_frame, params) {}

LoadablePluginPlaceholder::~LoadablePluginPlaceholder() {
}

void LoadablePluginPlaceholder::ReplacePlugin(blink::WebPlugin* new_plugin) {
  CHECK(plugin());
  if (!new_plugin)
    return;
  blink::WebPluginContainer* container = plugin()->Container();
  // This can occur if the container has been destroyed.
  if (!container) {
    new_plugin->Destroy();
    return;
  }

  container->SetPlugin(new_plugin);
  if (!new_plugin->Initialize(container)) {
    if (new_plugin->Container()) {
      // Since the we couldn't initialize the new plugin, but the container
      // still exists, restore the placeholder and destroy the new plugin.
      container->SetPlugin(plugin());
      new_plugin->Destroy();
    } else {
      // The container has been destroyed, along with the new plugin. Destroy
      // our placeholder plugin also.
      plugin()->Destroy();
    }
    return;
  }

  // During initialization, the new plugin might have replaced itself in turn
  // with another plugin. Make sure not to use the passed in |new_plugin| after
  // this point.
  new_plugin = container->Plugin();

  container->Invalidate();
  container->ReportGeometry();
  container->GetElement().SetAttribute("title", plugin()->old_title());
  plugin()->ReplayReceivedData(new_plugin);
  plugin()->Destroy();
}

void LoadablePluginPlaceholder::UpdateMessage() {
  if (!plugin())
    return;
  std::string script =
      "window.setMessage(" + base::GetQuotedJSONString(message_) + ")";
  plugin()->main_frame()->ExecuteScript(
      blink::WebScriptSource(blink::WebString::FromUtf8(script)));
}

bool LoadablePluginPlaceholder::IsErrorPlaceholder() {
  return !allow_loading_;
}

void LoadablePluginPlaceholder::OnSetIsNoStatePrefetching(
    bool is_no_state_prefetching) {
  // NoStatePrefetching can only be enabled prior to a RenderView's first
  // navigation, so no BlockedPlugin should see the notification that enables
  // NoStatePrefetching.
  DCHECK(!is_no_state_prefetching);
  if (is_blocked_for_no_state_prefetching_) {
    is_blocked_for_no_state_prefetching_ = false;
    if (!LoadingBlocked())
      LoadPlugin();
  }
}

void LoadablePluginPlaceholder::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 (!plugin())
    return;
  if (!allow_loading_) {
    NOTREACHED();
  }

  ReplacePlugin(CreatePlugin());
}

void LoadablePluginPlaceholder::LoadCallback() {
  RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Load_Click"));
  // If the user specifically clicks on the plugin content's placeholder,
  // disable power saver throttling for this instance.
  LoadPlugin();
}

void LoadablePluginPlaceholder::DidFinishLoadingCallback() {
  finished_loading_ = true;
  if (message_.length() > 0)
    UpdateMessage();

  // In case our initial geometry was reported before the placeholder finished
  // loading, request another one. Needed for correct large poster unthrottling.
  if (plugin()) {
    CHECK(plugin()->Container());
    plugin()->Container()->ReportGeometry();
  }
}

void LoadablePluginPlaceholder::SetPluginInfo(
    const content::WebPluginInfo& plugin_info) {
  plugin_info_ = plugin_info;
}

const content::WebPluginInfo& LoadablePluginPlaceholder::GetPluginInfo() const {
  return plugin_info_;
}

void LoadablePluginPlaceholder::SetIdentifier(const std::string& identifier) {
  identifier_ = identifier;
}

bool LoadablePluginPlaceholder::LoadingBlocked() const {
  DCHECK(allow_loading_);
  return is_blocked_for_no_state_prefetching_;
}

}  // namespace plugins
