910e62b5创建于 1月15日历史提交
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/renderer/plugins/chrome_plugin_placeholder.h"

#include <memory>
#include <set>
#include <utility>

#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/user_metrics_action.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/common/buildflags.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/grit/renderer_resources.h"
#include "chrome/renderer/chrome_content_renderer_client.h"
#include "chrome/renderer/custom_menu_commands.h"
#include "components/content_settings/renderer/content_settings_agent_impl.h"
#include "components/no_state_prefetch/renderer/no_state_prefetch_observer_list.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/common/content_switches.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_thread.h"
#include "gin/object_template_builder.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
#include "third_party/blink/public/common/context_menu_data/untrustworthy_context_menu_params.h"
#include "third_party/blink/public/common/input/web_input_event.h"
#include "third_party/blink/public/common/input/web_mouse_event.h"
#include "third_party/blink/public/common/page/page_zoom.h"
#include "third_party/blink/public/platform/url_conversion.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_frame_widget.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 "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/webui/jstemplate_builder.h"
#include "ui/gfx/geometry/size.h"
#include "url/origin.h"
#include "url/url_util.h"
#include "v8/include/cppgc/allocation.h"
#include "v8/include/v8-cppgc.h"

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

namespace {
const ChromePluginPlaceholder* g_last_active_menu = nullptr;

const char kPlaceholderSetKey[] = "kPlaceholderSetKey";

class PlaceholderSet : public base::SupportsUserData::Data {
 public:
  ~PlaceholderSet() override = default;

  static PlaceholderSet* Get(content::RenderFrame* render_frame) {
    DCHECK(render_frame);
    return static_cast<PlaceholderSet*>(
        render_frame->GetUserData(kPlaceholderSetKey));
  }

  static PlaceholderSet* GetOrCreate(content::RenderFrame* render_frame) {
    PlaceholderSet* set = Get(render_frame);
    if (!set) {
      set = new PlaceholderSet();
      render_frame->SetUserData(kPlaceholderSetKey, base::WrapUnique(set));
    }
    return set;
  }

  std::set<raw_ptr<ChromePluginPlaceholder, SetExperimental>>& placeholders() {
    return placeholders_;
  }

 private:
  PlaceholderSet() = default;

  std::set<raw_ptr<ChromePluginPlaceholder, SetExperimental>> placeholders_;
};

}  // namespace

ChromePluginPlaceholder::ChromePluginPlaceholder(
    content::RenderFrame* render_frame,
    const blink::WebPluginParams& params,
    const std::u16string& title)
    : plugins::LoadablePluginPlaceholder(render_frame, params),
      status_(chrome::mojom::PluginStatus::kAllowed),
      title_(title) {
  RenderThread::Get()->AddObserver(this);
  prerender::NoStatePrefetchObserverList::AddObserverForFrame(render_frame,
                                                              this);

  // Keep track of all placeholders associated with |render_frame|.
  PlaceholderSet::GetOrCreate(render_frame)->placeholders().insert(this);
  self_ = this;
}

ChromePluginPlaceholder::~ChromePluginPlaceholder() {
  RenderThread::Get()->RemoveObserver(this);

  // The render frame may already be gone.
  if (render_frame()) {
    PlaceholderSet* set = PlaceholderSet::Get(render_frame());
    if (set)
      set->placeholders().erase(this);

    prerender::NoStatePrefetchObserverList::RemoveObserverForFrame(
        render_frame(), this);
  }
}

// TODO(bauerb): Move this method to NonLoadablePluginPlaceholder?
// static
ChromePluginPlaceholder* ChromePluginPlaceholder::CreateLoadableMissingPlugin(
    content::RenderFrame* render_frame,
    const blink::WebPluginParams& params) {
  std::string template_html =
      ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
          IDR_BLOCKED_PLUGIN_HTML);

  base::Value::Dict values;
  values.Set("name", "");
  values.Set("message", l10n_util::GetStringUTF8(IDS_PLUGIN_NOT_SUPPORTED));

  std::string html_data = webui::GetI18nTemplateHtml(template_html, values);
  v8::Isolate* isolate = render_frame->GetAgentGroupScheduler().Isolate();
  // Will be garbage collected when its WebViewPlugin is going away.
  auto* placeholder = cppgc::MakeGarbageCollected<ChromePluginPlaceholder>(
      isolate->GetCppHeap()->GetAllocationHandle(), render_frame, params,
      params.mime_type.Utf16());
  placeholder->Init(html_data);
  return placeholder;
}

// static
ChromePluginPlaceholder* ChromePluginPlaceholder::CreateBlockedPlugin(
    content::RenderFrame* render_frame,
    const blink::WebPluginParams& params,
    const content::WebPluginInfo& info,
    const std::string& identifier,
    const std::u16string& name,
    int template_id,
    const std::u16string& message) {
  base::Value::Dict values;
  values.Set("message", message);
  values.Set("name", name);
  values.Set("hide", l10n_util::GetStringUTF8(IDS_PLUGIN_HIDE));
  values.Set(
      "pluginType",
      render_frame->IsMainFrame() &&
              render_frame->GetWebFrame()->GetDocument().IsPluginDocument()
          ? "document"
          : "embedded");

  std::string template_html =
      ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
          template_id);

  DCHECK(!template_html.empty()) << "unable to load template. ID: "
                                 << template_id;
  std::string html_data = webui::GetI18nTemplateHtml(template_html, values);

  // |blocked_plugin| will be garbage collected when its WebViewPlugin is going
  // away.
  ChromePluginPlaceholder* blocked_plugin =
      cppgc::MakeGarbageCollected<ChromePluginPlaceholder>(
          render_frame->GetWebFrame()
              ->GetAgentGroupScheduler()
              ->Isolate()
              ->GetCppHeap()
              ->GetAllocationHandle(),
          render_frame, params, name);
  blocked_plugin->Init(html_data);
  blocked_plugin->SetPluginInfo(info);
  blocked_plugin->SetIdentifier(identifier);

  return blocked_plugin;
}

// static
void ChromePluginPlaceholder::ForEach(
    content::RenderFrame* render_frame,
    const base::RepeatingCallback<void(ChromePluginPlaceholder*)>& callback) {
  PlaceholderSet* set = PlaceholderSet::Get(render_frame);
  if (set) {
    for (ChromePluginPlaceholder* placeholder : set->placeholders()) {
      callback.Run(placeholder);
    }
  }
}

void ChromePluginPlaceholder::SetStatus(chrome::mojom::PluginStatus status) {
  status_ = status;
}

void ChromePluginPlaceholder::OnDestruct() {
  self_.Clear();
}

void ChromePluginPlaceholder::SetIsNoStatePrefetching(
    bool is_no_state_prefetching) {
  OnSetIsNoStatePrefetching(is_no_state_prefetching);
}

void ChromePluginPlaceholder::PluginListChanged() {
  if (!render_frame() || !plugin())
    return;

  chrome::mojom::PluginInfoPtr plugin_info = chrome::mojom::PluginInfo::New();
  std::string mime_type(GetPluginParams().mime_type.Utf8());

  mojo::AssociatedRemote<chrome::mojom::PluginInfoHost> plugin_info_host;
  render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(
      &plugin_info_host);
  plugin_info_host->GetPluginInfo(
      GetPluginParams().url,
      render_frame()->GetWebFrame()->Top()->GetSecurityOrigin(), mime_type,
      &plugin_info);
  if (plugin_info->status == status_)
    return;
  blink::WebPlugin* new_plugin = ChromeContentRendererClient::CreatePlugin(
      render_frame(), GetPluginParams(), *plugin_info);
  ReplacePlugin(new_plugin);
}

v8::Local<v8::Value> ChromePluginPlaceholder::GetV8Handle(
    v8::Isolate* isolate) {
  return GetWrapper(isolate).ToLocalChecked();
}

void ChromePluginPlaceholder::ShowContextMenu(
    const blink::WebMouseEvent& event) {
  if (context_menu_client_receiver_.is_bound())
    return;  // Don't allow nested context menu requests.

  if (!render_frame())
    return;

  blink::UntrustworthyContextMenuParams params;

  if (!title_.empty()) {
    auto name_item = blink::mojom::CustomContextMenuItem::New();
    name_item->label = title_;
    params.custom_items.push_back(std::move(name_item));

    auto separator_item = blink::mojom::CustomContextMenuItem::New();
    separator_item->type = blink::mojom::CustomContextMenuItemType::kSeparator;
    params.custom_items.push_back(std::move(separator_item));
  }

  if (!GetPluginInfo().path.value().empty()) {
    auto run_item = blink::mojom::CustomContextMenuItem::New();
    run_item->action = MENU_COMMAND_PLUGIN_RUN;
    // Disable this menu item if the plugin is blocked by policy.
    run_item->enabled = LoadingAllowed();
    run_item->label = l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_PLUGIN_RUN);
    params.custom_items.push_back(std::move(run_item));
  }

  auto hide_item = blink::mojom::CustomContextMenuItem::New();
  hide_item->action = MENU_COMMAND_PLUGIN_HIDE;
  bool is_main_frame_plugin_document =
      render_frame()->IsMainFrame() &&
      render_frame()->GetWebFrame()->GetDocument().IsPluginDocument();
  hide_item->enabled = !is_main_frame_plugin_document;
  hide_item->label = l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_PLUGIN_HIDE);
  params.custom_items.push_back(std::move(hide_item));

  gfx::Point point =
      gfx::Point(event.PositionInWidget().x(), event.PositionInWidget().y());
  if (plugin() && plugin()->Container())
    point = plugin()->Container()->LocalToRootFramePoint(point);

  // TODO(crbug.com/40699157): This essentially is a floor of the coordinates.
  // Determine if rounding is more appropriate.
  gfx::Rect position_in_dips =
      render_frame()
          ->GetWebFrame()
          ->LocalRoot()
          ->FrameWidget()
          ->BlinkSpaceToEnclosedDIPs(gfx::Rect(point.x(), point.y(), 0, 0));

  params.x = position_in_dips.x();
  params.y = position_in_dips.y();

  render_frame()->GetWebFrame()->ShowContextMenuFromExternal(
      params, context_menu_client_receiver_.BindNewEndpointAndPassRemote());

  g_last_active_menu = this;
}

void ChromePluginPlaceholder::CustomContextMenuAction(uint32_t action) {
  if (g_last_active_menu != this)
    return;
  switch (action) {
    case MENU_COMMAND_PLUGIN_RUN: {
      RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Load_Menu"));
      LoadPlugin();
      break;
    }
    case MENU_COMMAND_PLUGIN_HIDE: {
      RenderThread::Get()->RecordAction(UserMetricsAction("Plugin_Hide_Menu"));
      HidePlugin();
      break;
    }
    default:
      NOTREACHED();
  }
}

void ChromePluginPlaceholder::ContextMenuClosed(
    const GURL&,
    const std::optional<blink::Impression>&) {
  context_menu_client_receiver_.reset();
  render_frame()->GetWebFrame()->View()->DidCloseContextMenu();
}

blink::WebPlugin* ChromePluginPlaceholder::CreatePlugin() {
  return nullptr;
}

const gin::WrapperInfo* ChromePluginPlaceholder::wrapper_info() const {
  return &kWrapperInfo;
}

gin::ObjectTemplateBuilder ChromePluginPlaceholder::GetObjectTemplateBuilder(
    v8::Isolate* isolate) {
  gin::ObjectTemplateBuilder builder =
      gin::Wrappable<ChromePluginPlaceholder>::GetObjectTemplateBuilder(isolate)
          .SetMethod<void (ChromePluginPlaceholder::*)()>(
              "hide", &ChromePluginPlaceholder::HideCallback)
          .SetMethod<void (ChromePluginPlaceholder::*)()>(
              "load", &ChromePluginPlaceholder::LoadCallback)
          .SetMethod<void (ChromePluginPlaceholder::*)()>(
              "didFinishLoading",
              &ChromePluginPlaceholder::DidFinishLoadingCallback);

  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kEnablePluginPlaceholderTesting)) {
    builder.SetMethod<void (ChromePluginPlaceholder::*)()>(
        "notifyPlaceholderReadyForTesting",
        &ChromePluginPlaceholder::NotifyPlaceholderReadyForTestingCallback);
  }

  return builder;
}